diff --git a/.gitignore b/.gitignore index 3044b9590f058f..ac72e2f2537f9a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ # Normal rules (sorted alphabetically) # .* +# The leading-dot rule above would otherwise hide the CI configuration, +# which needs to be tracked for the workflow to run. +!.github/ +!.github/workflows/ *.a *.asn1.[ch] *.bc diff --git a/Documentation/clk/mediatek-mt6589-unused-gates.rst b/Documentation/clk/mediatek-mt6589-unused-gates.rst new file mode 100644 index 00000000000000..269163d3cea5bf --- /dev/null +++ b/Documentation/clk/mediatek-mt6589-unused-gates.rst @@ -0,0 +1,140 @@ +================================= +MediaTek MT6589 unreferenced clocks +================================= + +The MT6589 clock drivers in ``drivers/clk/mediatek/clk-mt6589-*.c`` define +188 clocks. 88 of them are referenced by a Device Tree node; the rest are +either internal graph nodes (which correctly have no consumer) or leaf gates +for blocks this board does not drive. + +This file records what the unreferenced leaf gates belong to, so that a later +reader does not mistake them for an oversight and add a consumer for a block +that has no driver. A wrong clock consumer is worse than a missing one: it +programs a gate register for a block that is not there. + +Internal graph nodes (not gates) +================================ + +Most of the unreferenced clocks are not gates at all but the mux and divider +outputs that form the internal clock graph. They must not be given DT +consumers; they are reached through the gates below them. + +* ``CLK_TOP_MUX_*`` - ``axi_sel``, ``mem_sel``, ``smi_sel``, ``audintbus_sel``, + ``audio_sel``, ``cam_sel``, ``camtg_sel``, ``dpilvds_sel``, ``fd_sel``, + ``fix_sel``, ``hyd_sel``, ``irda_sel``, ``jpg_sel``, ``pmicspi_sel``, + ``uart_sel``, ``usb20_sel``. +* PLL post-dividers - ``CLK_TOP_SYSPLL_D*``, ``CLK_TOP_UNIVPLL_D*``, + ``CLK_TOP_UNIVPLL1_D*``, ``CLK_TOP_UNIVPLL2_D*``, ``CLK_TOP_MMPLL_D4``, + ``CLK_TOP_MMPLL_D6``, ``CLK_TOP_LVDSPLL*``, ``CLK_TOP_MEMPLL_MCK_D4``. +* Miscellaneous - ``CLK_TOP_CLK_NULL``, ``CLK_TOP_CLKPH_MCK``, + ``CLK_TOP_CPUM_TCK_IN``. + +Display +======= + +The following gates are not referenced by any node. + +* ``CLK_DISP0_SCL`` (``disp0_scl``) - the display scaler. MT6589 drives its + panel through the MIPI DSI path, and nothing in this tree uses a scaler + block. +* ``CLK_DISP0_CMDQ_ENGINE`` / ``CLK_DISP0_CMDQ_SMI`` - the display command + queue. +* ``CLK_DISP0_GAMMA_ENGINE`` / ``CLK_DISP0_GAMMA_PIXEL`` - the gamma + correction block. +* ``CLK_DISP0_ROT_ENGINE`` / ``CLK_DISP0_ROT_SMI`` - the hardware rotator. + The MT6589 has no rotator in use here. +* ``CLK_DISP1_LCD``, ``CLK_DISP1_SLCD``, ``CLK_DISP1_DPI0``, ``CLK_DISP1_DPI1``, + ``CLK_DISP1_DBI_ENGINE``, ``CLK_DISP1_DBI_OUTPUT``, ``CLK_DISP1_DBI_SMI`` - + the parallel LCD output paths (SLCD, DPI, DBI). The Lenovo Blade drives + MIPI DSI and has no parallel LCD interface wired. + +Infrastructure +============== + +Defined in ``clk-mt6589-infracfg.c``, unreferenced by any node. + +* ``CLK_INFRA_MD1MCUAXI``, ``CLK_INFRA_MD1HWMIXAXI``, ``CLK_INFRA_MD1AHB``, + ``CLK_INFRA_MD2MCUAXI``, ``CLK_INFRA_MD2HWMIXAXI``, ``CLK_INFRA_MD2AHB`` - + the two Cortex-M4 MCU subsystems (MD1, MD2). There is no MCU driver in + this tree, so no consumer can exist. +* ``CLK_INFRA_CPUM`` (``infra_cpum``) - the M4U-side CPU clock, same reason. +* ``CLK_INFRA_AUDIO``, ``CLK_INFRA_SPI0``, ``CLK_INFRA_DBGCLK``, + ``CLK_INFRA_SMI``, ``CLK_INFRA_MFGAXI``, ``CLK_INFRA_CCIF0``, + ``CLK_INFRA_CCIF1``. + +.. note:: + + The gate array in ``clk-mt6589-infracfg.c`` does not line up with + ``include/dt-bindings/clock/mediatek,mt6589-clk.h``: ``CLK_INFRA_M4U`` is + ID 7 in the header but is shifted to 8 in the driver, and the gates run to + shift 23 while the header stops at 19 plus ``CLK_INFRA_ARMDIV1`` (20). + Several entries also still carry the original author's ``/* maybe */`` and + ``/* mt8135 */`` comments. That misalignment is a separate bug and has + deliberately not been changed here, because correcting it would renumber + live gate registers. + +Peripheral +========== + +Defined in ``clk-mt6589-pericfg.c``, unreferenced by any node. + +* ``CLK_PERI0_USB1`` (``peri_usb1``) - this board uses the internal HSIC PHY, + not the USB1 controller clock path. +* ``CLK_PERI0_APHIF``, ``CLK_PERI0_MDHIF`` - the audio and multimedia PD/HIF + controllers. No MT6589 driver in this tree claims them. +* ``CLK_PERI0_IRDA``, ``CLK_PERI0_NLI`` - IrDA and the nativeland interface, + neither wired on this board. +* ``CLK_PERI1_FHCTL`` (``peri_fhctl``) - the frequency-hopping controller. + ``clk-fhctl.c`` exists in this tree but no MT6589 node consumes the hop + clock. + +Image +===== + +Defined in ``clk-mt6589-img.c``, unreferenced by any node. The MT6589 camera +capture path has no driver in this tree - the camera dtsi files are GPIO pin +definitions only - so none of these can have a consumer yet. + +* ``CLK_IMAGE_CAM_CAM``, ``CLK_IMAGE_CAM_SMI``, ``CLK_IMAGE_COMMON_SMI``, + ``CLK_IMAGE_SEN_CAM``, ``CLK_IMAGE_SEN_TG``. + +GPU +=== + +* ``CLK_MFG_HYD`` (``mfg_hyd``) - defined in ``clk-mt6589-mfg.c``, unreferenced. + No GPU driver in this tree drives this clock. + +Top-level PMIC SPI +================== + +* ``CLK_TOPCK_PMICSPI`` (``topck_pmicspi``) - a top-level PMIC SPI clock, + distinct from ``CLK_INFRA_PMICSPI``. The ``pwrap`` node takes the + infracfg one (mt6589.dtsi). + +CPU intermediate clock +====================== + +``CLK_INFRA_ARMDIV1`` (``armdiv1``) is defined in +``clk-mt6589-infracfg.c`` as a divider on the CPU mux, with +``CLK_SET_RATE_PARENT`` set. Until the Device Tree was corrected it had no +consumer at all, because the CPU nodes named ``clk26m`` as their +"intermediate" clock. + +That is not a cosmetic error. ``mediatek-cpufreq.c`` reads the clock by that +name and reparents the CPU onto it while ARMPLL is being reprogrammed, then +looks up an OPP matching the intermediate rate to derive a safe intermediate +voltage. Pointing it at an unrelated always-on oscillator made both the +reparent target and that voltage lookup meaningless. + +The CPU nodes now name ``CLK_INFRA_ARMDIV1``, which divides the mux output +through ``TOP_CKDIV1.clkdiv1_sel`` (bits [4:0], datasheet pp. 435-437). + +.. warning:: + + ``mt6589_armdiv_determine_rate()`` selects the ratio *nearest* the request + rather than refusing a rate it cannot hit exactly + (``mt6589_armdiv_find_rate()``, ``clk-mt6589-infracfg.c``). A mis-specified + target frequency therefore yields a silently wrong - but valid - rate + instead of an error. This is left as-is deliberately: changing it alters + runtime clock semantics beyond the scope of this correction. It is called + out here so the behaviour is not mistaken for exact division. diff --git a/Documentation/devicetree/bindings/clock/mediatek,mt6589-mempll.yaml b/Documentation/devicetree/bindings/clock/mediatek,mt6589-mempll.yaml new file mode 100644 index 00000000000000..11479db1b657d7 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/mediatek,mt6589-mempll.yaml @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/mediatek,mt6589-mempll.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MT6589 MEMPLL (DRAM PLL) + +maintainers: + - Akari Tsuyukusa + +description: | + The MT6589 MEMPLL is the PLL that clocks the DRAM controller. Its + registers are not in the apmixedsys window: MEMPLL0..MEMPLL12 and + MEMPLL_DIVIDER live in the ddrphy_conf module at 0x1001_1600..0x1001_1640, + and the power-on debounce registers MPLLPON0..3 at 0x1001_15C0, per the + MT6589 datasheet section 13.4.3 ("DDRPHY Registers"). The node therefore + lives in the ddrphy_conf window rather than under apmixedsys. + + The binding exposes the MEMPLL as a single read-only clock output. The + rate is declared by the board with "clock-frequency" rather than decoded + from the registers, because the register set does not determine it + unambiguously: MEMPLL0's feedback divider field is not the divider for + PLL1, the reference can be either the crystal or the APLL plus a divider + (MEMPLL1[12:11]), and the downstream frequency tables map several + different DRAM rates onto the same DDS value. + + The MEMPLL selection for the DRAM clock is still made in topckgen, not + here; the clock output of this node is what topckgen's "clkph_mck" is + parented to. + +properties: + compatible: + const: mediatek,mt6589-mempll + + reg: + maxItems: 1 + description: | + The ddrphy_conf window, at minimum covering the MEMPLL registers, + i.e. 0x600 through 0x640 of that block. + + clock-frequency: + description: | + Frequency of the MEMPLL output in Hz, as configured by the bootloader + for the DRAM rate this board runs at. Defaults to 266500000, the + figure the datasheet states for this clock ("MEMPLL_MCK 266.5MHz"). + minimum: 1 + + "#clock-cells": + const: 0 + +required: + - compatible + - reg + - "#clock-cells" + +allOf: + - $ref: /schemas/clock/clock.yaml# + +additionalProperties: false + +examples: + - | + #include + + ddrphy: syscon@10011000 { + compatible = "mediatek,mt6589-mempll"; + reg = <0x10011000 0x1000>; + #clock-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/clock/mediatek,mt8186-fhctl.yaml b/Documentation/devicetree/bindings/clock/mediatek,mt8186-fhctl.yaml index d00327d12e1e5e..e379338f8ff7f7 100644 --- a/Documentation/devicetree/bindings/clock/mediatek,mt8186-fhctl.yaml +++ b/Documentation/devicetree/bindings/clock/mediatek,mt8186-fhctl.yaml @@ -17,6 +17,7 @@ description: | properties: compatible: enum: + - mediatek,mt6589-fhctl - mediatek,mt6795-fhctl - mediatek,mt8173-fhctl - mediatek,mt8186-fhctl @@ -40,6 +41,20 @@ properties: minimum: 0 maximum: 8 + mediatek,fhctl-hopping-enabled: + description: + Whether a rate change on the corresponding PLL is driven through the + FHCTL hardware, one cell per entry in "clocks". This is separate from + mediatek,hopping-ssc-percent, which is the spread-spectrum modulation + depth and says nothing about whether the FHCTL path is used at all. + A PLL can hop without spreading, and conversely a spread-spectrum + percentage does not by itself make the hop work. + minItems: 0 + maxItems: 30 + items: + enum: [0, 1] + default: 0 + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,color.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,color.yaml index 5564f4063317b2..4c410bd427b30a 100644 --- a/Documentation/devicetree/bindings/display/mediatek/mediatek,color.yaml +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,color.yaml @@ -24,6 +24,7 @@ properties: oneOf: - enum: - mediatek,mt2701-disp-color + - mediatek,mt6589-disp-color - mediatek,mt8167-disp-color - mediatek,mt8173-disp-color - mediatek,mt8195-mdp3-color diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.yaml index 27ffbccc2a082e..0893f20c89206c 100644 --- a/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.yaml +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.yaml @@ -18,12 +18,30 @@ description: | allOf: - $ref: /schemas/display/dsi-controller.yaml# + - if: + properties: + compatible: + contains: + const: mediatek,mt6589-dsi + then: + properties: + clocks: + minItems: 4 + clock-names: + minItems: 4 + else: + properties: + clocks: + maxItems: 3 + clock-names: + maxItems: 3 properties: compatible: oneOf: - enum: - mediatek,mt2701-dsi + - mediatek,mt6589-dsi - mediatek,mt7623-dsi - mediatek,mt8167-dsi - mediatek,mt8173-dsi @@ -50,16 +68,22 @@ properties: maxItems: 1 clocks: + minItems: 3 + maxItems: 4 items: - description: Engine Clock - description: Digital Clock - description: HS Clock + - description: Digital Lane Clock (MT6589 only) clock-names: + minItems: 3 + maxItems: 4 items: - const: engine - const: digital - const: hs + - const: lane resets: maxItems: 1 diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,ovl.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,ovl.yaml index 679f731f0f15f7..ee9599a53e5a94 100644 --- a/Documentation/devicetree/bindings/display/mediatek/mediatek,ovl.yaml +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,ovl.yaml @@ -23,6 +23,7 @@ properties: oneOf: - enum: - mediatek,mt2701-disp-ovl + - mediatek,mt6589-disp-ovl - mediatek,mt8167-disp-ovl - mediatek,mt8173-disp-ovl - mediatek,mt8183-disp-ovl diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,rdma.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,rdma.yaml index cb187a95c11ea4..5eeedea4ae2d2b 100644 --- a/Documentation/devicetree/bindings/display/mediatek/mediatek,rdma.yaml +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,rdma.yaml @@ -25,6 +25,7 @@ properties: oneOf: - enum: - mediatek,mt2701-disp-rdma + - mediatek,mt6589-disp-rdma - mediatek,mt8173-disp-rdma - mediatek,mt8183-disp-rdma - mediatek,mt8195-disp-rdma diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,tdshp.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,tdshp.yaml new file mode 100644 index 00000000000000..1ce5030494be8e --- /dev/null +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,tdshp.yaml @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/mediatek/mediatek,tdshp.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek Two-Dimension Sharpness Processor (TDSHP) + +maintainers: + - AngeloGioacchino Del Regno + - Matthias Brugger + +description: | + The MediaTek TDSHP is a two-dimension sharpness processor found in + various MediaTek SoCs. On MT6589 the block sits between the COLOR + processor and BLS in the display pipeline and is used in relay mode. + +properties: + compatible: + const: mediatek,mt6589-disp-tdshp + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + +required: + - compatible + - reg + - clocks + +additionalProperties: false + +examples: + - | + #include + #include + #include + + soc { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + tdshp@1400b000 { + compatible = "mediatek,mt6589-disp-tdshp"; + reg = <0x1400b000 0x1000>; + interrupts = ; + clocks = <&dispsys CLK_DISP0_2DSHP>; + }; + }; diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml index 816841a96133db..63e6b7c9b2a2b6 100644 --- a/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,wdma.yaml @@ -28,6 +28,10 @@ properties: - mediatek,mt6795-disp-wdma - mediatek,mt8167-disp-wdma - const: mediatek,mt8173-disp-wdma + - items: + - enum: + - mediatek,mt6589-disp-wdma0 + - mediatek,mt6589-disp-wdma1 reg: maxItems: 1 @@ -35,6 +39,9 @@ properties: interrupts: maxItems: 1 + resets: + maxItems: 1 + power-domains: description: A phandle and PM domain specifier as defined by bindings of the power controller specified by phandle. See @@ -42,7 +49,9 @@ properties: clocks: items: - - description: WDMA Clock + - description: WDMA engine clock + additionalItems: + - description: WDMA SMI clock, present on the MT6589 instances iommus: description: diff --git a/Documentation/devicetree/bindings/display/panel/lenovo,b6000-panels.yaml b/Documentation/devicetree/bindings/display/panel/lenovo,b6000-panels.yaml new file mode 100644 index 00000000000000..b37be1d723f277 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/lenovo,b6000-panels.yaml @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/lenovo,b6000-panels.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Lenovo YOGA Tablet 8 (B6000) DSI panels + +maintainers: + - Akari Tsuyukusa + +description: | + The Lenovo YOGA Tablet 8 (B6000) is fitted with one of two 800x1280 + panels, distinguished by the compatible string: + + nt35521_cmi_080 Novatek NT35521 + s6d7aa0x04_cpt_080 Samsung S6D7AA0 + + Neither can be described as a "simple panel": both need a manufacturer + command init sequence before they will display anything. + +allOf: + - $ref: panel-common.yaml# + +properties: + compatible: + enum: + - lenovo,b6000-nt35521-cmi + - lenovo,b6000-s6d7aa0x04-cpt + + reg: + maxItems: 1 + + vdd-supply: + description: Main panel supply. + vddi-supply: + description: I/O supply. + + backlight: true + port: true + reset-gpios: true + + enable-gpios: + description: | + Optional enable line driven high before the reset sequence. The + vendor's lcd_power_en() helper was not recovered, so this is modelled + from the board wiring rather than copied from it. + +required: + - compatible + - reg + - port + +additionalProperties: false + +examples: + - | + #include + + dsi { + #address-cells = <1>; + #size-cells = <0>; + + panel@0 { + compatible = "lenovo,b6000-nt35521-cmi"; + reg = <0>; + + vdd-supply = <&panel_vdd>; + vddi-supply = <&panel_vddi>; + reset-gpios = <&pio 137 GPIO_ACTIVE_LOW>; + + port { + panel_in: endpoint { + remote-endpoint = <&dsi_out>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml index c31f5a5ddde31e..df57c20789d473 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml @@ -31,7 +31,7 @@ properties: # AU Optronics Corporation 8.0" WUXGA TFT LCD panel - auo,b080uan01 - # BOE Technology 10.1" WXGA panel with Himax HX8896-A01 TCON + # BOE Technology 10.1" WXGA panel with Himax HX8896-A01 TCON (BP101WX1-207) - boe,hx8896-a01-panel # Boe Corporation 8.0" WUXGA TFT LCD panel - boe,tv080wum-nl0 diff --git a/Documentation/devicetree/bindings/dma/mediatek,mt6589-gdma.yaml b/Documentation/devicetree/bindings/dma/mediatek,mt6589-gdma.yaml new file mode 100644 index 00000000000000..b16cfe2b2e8209 --- /dev/null +++ b/Documentation/devicetree/bindings/dma/mediatek,mt6589-gdma.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dma/mediatek,mt6589-gdma.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MT6589 AP DMA General DMA Controller + +maintainers: + - MediaTek, Inc. + +description: > + The AP DMA block of the MT6589 contains eighteen DMA engines, but only two of + them are generic: G_DMA0 and G_DMA1. The remaining sixteen are wired to + internal peripherals (HIF, IrDA, I2C and UART) and are described by a + separate binding. + + The two general DMA engines have no descriptor ring and no scatter-gather + support. A transfer is programmed directly into the SRC, DST, LEN1 and LEN2 + registers and completion is signalled through a single interrupt flag. + +allOf: + - $ref: dma-controller.yaml# + +properties: + compatible: + enum: + - mediatek,mt6589-gdma + + reg: + maxItems: 1 + + interrupts: + description: + One interrupt per general DMA engine, G_DMA0 first and G_DMA1 second. + minItems: 2 + maxItems: 2 + + interrupt-names: + items: + - const: gdma0 + - const: gdma1 + + clocks: + maxItems: 1 + + clock-names: + const: main + + resets: + maxItems: 1 + + reset-names: + const: main + + "#dma-cells": + description: Channel number, 0 for G_DMA0 and 1 for G_DMA1 + const: 1 + +required: + - reg + - interrupts + - interrupt-names + - clocks + - clock-names + - resets + - reset-names + +# This controller is deliberately described without an "iommus" or a +# "power-domains" property. AP DMA is attached straight to the peripheral AXI +# fabric, so there is no M4U or LARB in front of it and it has no power domain +# of its own; the datasheet's AP_DMA_PD is the same PERICFG PDN0 bit that gates +# CLK_PERI0_APDMA, so the clock above already covers it. Adding either property +# here would describe a path that does not exist. + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + dma-controller@11000000 { + compatible = "mediatek,mt6589-gdma"; + reg = <0x11000000 0x180>; + interrupts = , + ; + interrupt-names = "gdma0", "gdma1"; + clocks = <&pericfg CLK_PERI0_APDMA>; + clock-names = "main"; + resets = <&pericfg MT6589_PERI_DMA_SW_RS_RST>; + reset-names = "main"; + #dma-cells = <1>; + status = "disabled"; + }; diff --git a/Documentation/devicetree/bindings/iio/magnetometer/bosch,bmc150_magn.yaml b/Documentation/devicetree/bindings/iio/magnetometer/bosch,bmc150_magn.yaml index c1a6892b0194ca..7897b39beeeac9 100644 --- a/Documentation/devicetree/bindings/iio/magnetometer/bosch,bmc150_magn.yaml +++ b/Documentation/devicetree/bindings/iio/magnetometer/bosch,bmc150_magn.yaml @@ -27,6 +27,7 @@ properties: - bosch,bmc156_magn - bosch,bmm150 - bosch,bmm150_magn + - bosch,bmm050 - items: - const: bosch,bmx055-magn - const: bosch,bmc150_magn diff --git a/Documentation/devicetree/bindings/leds/mediatek,mt6320-led.yaml b/Documentation/devicetree/bindings/leds/mediatek,mt6320-led.yaml new file mode 100644 index 00000000000000..3d5ef6583675d1 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/mediatek,mt6320-led.yaml @@ -0,0 +1,161 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/mediatek,mt6320-led.yaml# +$schema: http://devicetree.org/schemas/base.yaml# + +title: MediaTek MT6320 PMIC LEDs + +maintainers: + - Lenovo Linux Team + +description: > + The MT6320 companion PMIC, used with MediaTek MT6589 SoCs, provides three + constant-current LED sinks (ISINK, called NLED in the vendor sources) plus + a fourth sink dedicated to the keypad backlight. These LEDs are wired into + the PMIC rather than to SoC GPIOs, so each one needs its own current step, + dimming counter and enable bit programmed inside the PMIC. + + Each LED is a child node of the PMIC node. There are two distinct kinds + of child, and they are described separately below because their required + properties differ: an ISINK channel, identified by its "reg" index, and + the single keypad backlight sink, identified by "mediatek,is-kpled". + +properties: + compatible: + oneOf: + - const: mediatek,mt6320-led + - items: + - const: mediatek,mt6320 + - const: mediatek,mt6320-led + description: > + Conformance to the PMIC type is optional for compatibility with + existing vendor bindings, which often only set the first string. + +# Child nodes carry no unit address, so they are matched by name: boards name +# them freely ("keypad-backlight", "red", "isink-ch0", ...), so a catch-all +# pattern is needed rather than the "led@[0-9a-f]" form used by I2C LED +# controllers. +patternProperties: + "^[a-zA-Z0-9][a-zA-Z0-9,_-]*$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + description: > + Index of the LED current sink within the PMIC. This is a channel + number, not an MMIO address: the sinks live in the ISINKS_CONx + registers and are addressed by an index 0, 1 or 2. + enum: [0, 1, 2] + + mediatek,is-kpled: + type: boolean + description: > + Marks this node as the dedicated keypad backlight sink on KPLED_CON0 + rather than one of the three ISINK channels. That sink has no + channel index and no current step, so a node carrying this property + must not have "reg" or "mediatek,num-steps". + + mediatek,num-steps: + minimum: 1 + maximum: 8 + default: 4 + description: > + Number of current step codes this sink exposes. This is the sink's + max_brightness and it is the only control on the current level: a + brightness of N selects current step N-1, so with the default of 4 a + brightness of 4 lights the sink at the highest step code available. + A brightness of 0 switches the sink off. + + The MT6320 step field is three bits wide, so 1..8 codes is + representable, but the vendor driver only uses codes 0..3. A board + that wired its LED for a fixed current, as the vendor driver does, + can expose a single code with num-steps = 1 and then always run it + at brightness 1. + + delay-on: + $ref: /schemas/types.yaml#/definitions/uint32 + description: > + Default blink on-time in milliseconds. Must be set together with + delay-off, and both must be non-zero. Optional; without it the LED + has no default timing, and a blink request that asks to keep the + current timing before the LED has ever been blinked is declined so + that the LED core picks the fallback instead. + + delay-off: + $ref: /schemas/types.yaml#/definitions/uint32 + description: > + Default blink off-time in milliseconds. Must be set together with + delay-on, and both must be non-zero. + + These are the timings this LED is programmed with before it has been + blinked explicitly. A blink request of zero for both, which the LED + core issues to mean "keep the current timing", re-applies these + rather than changing them. + + led-names: + description: Deprecated, use label instead. + + # The two child shapes. Property definitions live above so that + # unevaluatedProperties can see them; these branches only state which of + # them a given node must and must not carry. + oneOf: + # The keypad sink: no channel index, on/off only. + - required: + - mediatek,is-kpled + + not: + anyOf: + - required: ["reg"] + - required: ["mediatek,num-steps"] + + # An ISINK channel: must name its channel index, and is not a keypad + # sink. + - required: + - reg + + not: + required: + - mediatek,is-kpled + +required: + - compatible + +additionalProperties: false + +examples: + - | + leds { + compatible = "mediatek,mt6320-led"; + + red { + reg = <0>; + mediatek,num-steps = <4>; + label = "red"; + }; + + green { + reg = <1>; + mediatek,num-steps = <4>; + label = "green"; + }; + + blue { + reg = <2>; + mediatek,num-steps = <4>; + label = "blue"; + }; + }; + + - | + leds { + compatible = "mediatek,mt6320-led"; + + keypad-backlight { + label = "button-backlight"; + mediatek,is-kpled; + }; + }; +... diff --git a/Documentation/devicetree/bindings/media/i2c/mediatek,fm50af.yaml b/Documentation/devicetree/bindings/media/i2c/mediatek,fm50af.yaml new file mode 100644 index 00000000000000..23971b7f8f8d5f --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/mediatek,fm50af.yaml @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/mediatek,fm50af.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: FM50AF voice coil motor + +maintainers: + - Akari Tsuyukusa + +description: | + The FM50AF is a 10-bit DAC driving a camera lens voice coil motor, used + for autofocus. It has no register address: an I2C transfer carries the + DAC value directly. + +properties: + compatible: + const: mediatek,fm50af + + reg: + maxItems: 1 + + powerdown-gpios: + maxItems: 1 + description: + Optional power-down line. The FM50AF on the Lenovo B8000 does not + use one; the actuator is powered whenever its rail is up. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + lens@18 { + compatible = "mediatek,fm50af"; + reg = <0x18>; + }; + }; diff --git a/Documentation/devicetree/bindings/media/i2c/onnn,a5142.yaml b/Documentation/devicetree/bindings/media/i2c/onnn,a5142.yaml new file mode 100644 index 00000000000000..268144e56416a7 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/onnn,a5142.yaml @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/onnn,a5142.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ON Semiconductor A5142 MIPI CSI-2 sensor + +maintainers: + - Akari Tsuyukusa + +description: | + The A5142 is an ON Semiconductor (Aptina) 5 megapixel raw CMOS image + sensor with a MIPI CSI-2 output and an I2C control interface. It is the + main camera of the Lenovo YOGA Tablet 8/10 (MT6589). + + Registers are 16-bit addressed. Most of the vendor's register sequence + writes 16-bit data and a small part of it writes 8-bit data, so the + driver carries the transfer width per register rather than assuming one. + + The part has no internal autofocus; the FM50AF voice coil motor is a + separate I2C device on the same camera module. + +properties: + compatible: + const: onnn,a5142 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + const: extclk + + vdd-supply: + description: + Digital core supply. On the Lenovo YOGA Tablet this is the rail the + vendor calls VCAMD, wired to MT6320 VGP1. + + vddio-supply: + description: + I/O supply, the vendor's VCAMIO. + + reset-gpios: + description: Reset line, active low. + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: | + Video output port. + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + bus-type: + const: 4 + data-lanes: + description: + The vendor register tables program two lanes + (0x31AE = 0x0202), so exactly two are valid here. + items: + - const: 1 + - const: 2 + link-frequencies: true + + required: + - bus-type + - data-lanes + +required: + - compatible + - reg + - clocks + - clock-names + - vdd-supply + - vddio-supply + - port + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera@6c { + compatible = "onnn,a5142"; + reg = <0x6c>; + + clocks = <&camclk 0>; + clock-names = "extclk"; + + vdd-supply = <&cam_2v8>; + vddio-supply = <&cam_1v8>; + + reset-gpios = <&pio 211 GPIO_ACTIVE_LOW>; + + port { + a5142_out: endpoint { + remote-endpoint = <&csi2_in>; + bus-type = <4>; + data-lanes = <1 2>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov9760.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov9760.yaml new file mode 100644 index 00000000000000..b4b2d49db418b0 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov9760.yaml @@ -0,0 +1,116 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ovti,ov9760.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: OmniVision OV9760 MIPI CSI-2 sensor + +maintainers: + - Akari Tsuyukusa + +description: | + The OV9760 is an OmniVision 1.2 megapixel raw CMOS image sensor with a + MIPI CSI-2 output and an I2C control interface. It is the sub camera of + the Lenovo YOGA Tablet 8/10 (MT6589), where its lens entry is dummy_lens + because the module has no autofocus actuator. + + Registers are 16-bit addressed and 8-bit wide. + + Note on the I2C address: the vendor driver probes two write addresses in + turn, 0x6c and 0x20, and adopts whichever answers with sensor ID 0x9760. + The strapping on any given board is therefore a board property and must be + given by reg. + +properties: + compatible: + const: ovti,ov9760 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + const: extclk + + vdd-supply: + description: + Digital core supply. + + vddio-supply: + description: + I/O supply. + + reset-gpios: + description: Reset line, active low. + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: | + Video output port. + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + bus-type: + const: 4 + data-lanes: + description: + The vendor register tables are built for a single lane, + matching the "MIPI_1_LANE" note above OV9760MIPIPreview(). + items: + - const: 1 + link-frequencies: true + + required: + - bus-type + - data-lanes + +required: + - compatible + - reg + - clocks + - clock-names + - vdd-supply + - vddio-supply + - port + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera@6c { + compatible = "ovti,ov9760"; + reg = <0x6c>; + + clocks = <&camclk 1>; + clock-names = "extclk"; + + vdd-supply = <&cam_2v8>; + vddio-supply = <&cam_1v8>; + + reset-gpios = <&pio 124 GPIO_ACTIVE_LOW>; + + port { + ov9760_out: endpoint { + remote-endpoint = <&csi2_in>; + bus-type = <4>; + data-lanes = <1>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/media/mediatek,mt6589-cam-graph.yaml b/Documentation/devicetree/bindings/media/mediatek,mt6589-cam-graph.yaml new file mode 100644 index 00000000000000..eaeecca9b14d5e --- /dev/null +++ b/Documentation/devicetree/bindings/media/mediatek,mt6589-cam-graph.yaml @@ -0,0 +1,123 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/mediatek,mt6589-cam-graph.yaml# +$schema: http://devicetree.org/schemas/base.yaml# + +title: MediaTek MT6589 Camera Media Graph + +maintainers: + - Lenovo Linux Team + +description: | + The MT6589 camera path is a chain of three blocks, each of which is its own + platform device and its own subdev: + + csi2-rx (MIPI CSI-2 receiver) -> scam (sensor-CAM adaptor) -> cam (ISP) + + The blocks declare "remote-endpoint" properties pointing at each other, but + that is not enough to build a media graph. A media graph is a runtime object: + something has to register the media_device and the v4l2_device, run the + v4l2_async_notifier that pulls the subdevs into that v4l2_device, and create + the media_links. + + This binding describes the node that owns all of that. The driver bound to + it maps no registers and programs no hardware; it is pure plumbing, and it + exists because none of the three hardware blocks can own the graph without + making its own probe order and unbind order decide the whole pipeline. + + One node is expected per camera path. It waits on one endpoint per block it + should wait for, and the links between the blocks are derived from the DT + endpoints by the driver, not listed here. + + "One endpoint per block" is load-bearing, not a convention. The only thing + that ever puts a v4l2_async_connection on notifier->waiting_list is an + endpoint on THIS node, and the only thing that registers a subdev into this + node's v4l2_device is such a connection matching that subdev. A block that + declares no endpoint here never joins v4l2_device->subdevs, and the driver's + link creation walks only that list -- so the links that touch it are silently + never created, even when its pads and its "remote-endpoint" properties all + exist and point at the right peers. + +properties: + compatible: + const: mediatek,mt6589-cam-graph + + ports: + description: | + The blocks this driver should wait for. Each endpoint names a remote + endpoint belonging to one of the camera subdevs. + + One endpoint per block in the chain, INCLUDING the last one. For the + chain above that is three: seninf_out (the receiver's output), scam_out + (SCAM's output) and cam_in (CAM's input). + + A remote may sit on either a source or a sink endpoint of its block; the + match is by fwnode, so both resolve. Source endpoints read more + naturally, but the important thing is that a remote is named at all. + + Naming cam_in as scam_out's remote does NOT substitute for an endpoint + here. It describes the SCAM -> CAM wiring for link creation, but it + binds nothing: v4l2_async_find_match() only inspects descriptors taken + from this node's own endpoints, so cam would stay out of + v4l2_device->subdevs and the SCAM -> CAM link would never be created. + + An endpoint whose remote does not resolve is not an error. The driver + counts what resolved, reports how many at probe, and carries on, so a + graph_to_sensor endpoint can be declared ahead of the sensor node. + + Note that adding a sensor is not a DT-only change on the sensor side: the + receiver has no sink endpoint for a sensor to point at (its ports carry + only seninf_out, port@1) and the MIPI CSI-2 D-PHY between the two is not + modelled. Restoring the receiver's seninf_in endpoint and adding a + graph_to_sensor endpoint here are both required before the sensor -> + receiver link can exist. + $ref: /schemas/graph/ports.yaml# + + properties: + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +required: + - compatible + - ports + +additionalProperties: false + +examples: + - | + mtk-cam-graph { + compatible = "mediatek,mt6589-cam-graph"; + + /* + * Wait for the receiver's output, for SCAM's output and for CAM's + * input. None of these endpoints has a peer back on this node, so + * dtc reports each of them as not bidirectional; that is expected for + * a graph owner, which owns a media_device and a notifier rather than + * pads. + */ + ports { + graph_to_rx: port@0 { + graph_to_rx_ep: endpoint { + remote-endpoint = <&seninf_out_ep>; + }; + }; + + graph_to_scam: port@1 { + graph_to_scam_ep: endpoint { + remote-endpoint = <&scam_out_ep>; + }; + }; + + graph_to_cam: port@2 { + graph_to_cam_ep: endpoint { + remote-endpoint = <&cam_in_ep>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/mediatek,mt6589-cam.yaml b/Documentation/devicetree/bindings/media/mediatek,mt6589-cam.yaml new file mode 100644 index 00000000000000..740f818bbb214f --- /dev/null +++ b/Documentation/devicetree/bindings/media/mediatek,mt6589-cam.yaml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/mediatek,mt6589-cam.yaml# +$schema: http://devicetree.org/schemas/base.yaml# + +title: MediaTek MT6589 CAM/ISP control plane + +maintainers: + - Lenovo Linux Team + +description: > + The CAM block on the MT6589 is the image signal processor. Its control + registers are at 0x15004000; the image processing itself runs in the TG + and CPIPE stages that follow. + + WARNING: the driver behind this binding is a scaffold. It programs the + control plane (software reset, block enable, start/stop strobes) and the + IMGO output DMA, and it implements one capture video node. The image + pipeline is NOT implemented: no CPIPE stage is enabled, no TG is started + and no command queue is armed. Loading it will not produce a picture, and + on a board with no sensor attached STREAMON succeeds while DQBUF blocks + forever. See the driver source. + +properties: + compatible: + const: mediatek,mt6589-cam + + reg: + description: | + Register window of the CAM block. The control registers occupy + 0x0000-0x00ff; the rest of the block is the pipeline itself. + maxItems: 1 + + interrupts: + description: | + The CAM block's interrupt, GIC SPI 151 (Cam_irq_b), level active low. + The driver enables IMGO_DONE_EN in CAM_CTL_DMA_INT and keys its + handler off IMGO_DONE_ST, so without this a capture can start but no + frame ever completes. + + The property is optional: the driver falls back to running without an + interrupt, which cannot deliver a frame, rather than failing probe. + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + maxItems: 1 + + resets: + maxItems: 1 + + reset-names: + const: cam + + power-domains: + maxItems: 1 + + # One sink pad, fed by the SCAM adaptor that follows the CSI-2 receiver. + # + # The driver also creates a source pad, and a capture video node behind it, + # which is where the IMGO DMA lands. That pad needs no endpoint here: the + # video node is not described by the device tree, and the core creates the + # interface link between the two itself when the node registers. + ports: + $ref: /schemas/graph/ports.yaml# + +required: + - compatible + - reg + - ports + +additionalProperties: false + +examples: + - | + cam: cam@15004000 { + compatible = "mediatek,mt6589-cam"; + reg = <0x0 0x15004000 0x0 0x400>; + interrupts = <151 8>; + + ports { + cam_in: port@0 { + cam_in_ep: endpoint { + remote-endpoint = <&scam_out_ep>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/mediatek,mt6589-csi2-rx.yaml b/Documentation/devicetree/bindings/media/mediatek,mt6589-csi2-rx.yaml new file mode 100644 index 00000000000000..903798570720c4 --- /dev/null +++ b/Documentation/devicetree/bindings/media/mediatek,mt6589-csi2-rx.yaml @@ -0,0 +1,255 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/mediatek,mt6589-csi2-rx.yaml# +$schema: http://devicetree.org/schemas/base.yaml# + +title: MediaTek MT6589 MIPI CSI-2 Receiver + +maintainers: + - Lenovo Linux Team + +description: | + The MT6589 image subsystem contains two independent MIPI CSI-2 receivers + (called seninf/csi2 in the vendor sources) in a single register window at + 0x15008000, one per camera port. Each receiver unpacks the MIPI packets, + checks their ECC/CRC, and forwards the raw byte stream together with + frame and line synchronisation to the sensor-CAM (SCAM) block that feeds + the ISP. + + One DT node describes the window; mediatek,seninf-port selects which of + the two receivers the node programs. + + DRAFT DRIVER - NO MIPI CSI-2 D-PHY EXISTS FOR THIS SOC + ------------------------------------------------------ + This receiver driver programs the seninf/csi2 registers. It does not + program any MIPI PHY, because on the MT6589 there is no such PHY to + program: + + - The MT6589 data sheet maps the imgsys aperture in full and documents no + MIPI RX PHY registers at all. The PHY appears only as the word + "mipi_rx" in the img_rst description of WDT_SWSYSRST. + - There is no vendor PHY driver for this SoC to derive a register map from. + + An earlier revision of this tree carried a D-PHY binding and a stub + driver whose power_on() returned success without touching the hardware. Both + were removed rather than kept as a placeholder. A "phys" reference here + therefore names a PHY that this tree provides no driver for: the receiver + does the full phy_init()/phy_power_on()/phy_power_off()/phy_exit() lifecycle + on it and a missing reference is non-fatal, but no lanes will be driven. + Expect no frames from this receiver until a real D-PHY driver exists. + + In the same spirit, the three clocks below are optional and the reset is + optional, for reasons documented on each property. A node with neither + clocks nor resets probes and its registers are programmed; that is all that + is being claimed. + +properties: + compatible: + const: mediatek,mt6589-csi2-rx + + reg: + description: | + Register window covering what this driver programs: seninf_top at + 0x15008000 and the two csi2 receivers at 0x15008100 and 0x15008180, + 0x200 bytes in total. + + The window must NOT be widened to cover the SCAM blocks at + 0x15008200: those have their own node and their own driver, and two + platform devices reserving one aperture is a real bug - one of them + ends up driving the other's registers. Claim 0x200 and no more. + maxItems: 1 + + mediatek,seninf-port: + description: | + Which receiver in the window this node programs. Port 0 is the + seninf1/csi2 receiver pair at 0x15008100, port 1 is seninf2/csi2 at + 0x15008180. On the Lenovo YOGA Tablet 8/10 port 0 is the main (rear) + camera and port 1 is the front camera. + enum: [0, 1] + + mediatek,hs-settle: + description: | + LP11 -> HS data settle delay written to CSI2_DELAY[23:16], in the + receiver's timing steps. This is how long the receiver waits after a + lane leaves LP11 before it starts sampling, and it has to cover the + sensor's own transition time plus the PCB propagation delay. + + A property of the sensor and the board, not of the SoC, so it belongs + in the board DTS. Where it is absent the register keeps its reset + value. + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 0xff + + mediatek,hs-term: + description: | + LP11 -> HS data termination delay written to CSI2_DELAY[31:24], in the + receiver's timing steps. Companion to mediatek,hs-settle with the same + "board property, reset value if absent" rule. + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 0xff + + clocks: + description: | + Up to three clocks, in this order: + seninf - the sensor front end block itself + csi2 - the CSI-2 receiver sampling logic + tg_grp - the timing generator feeding the parallel sensor clock + + These are OPTIONAL and the driver treats each one as optional: it takes + them with devm_clk_get_optional() and, for each gate that is present, + enables it with clk_prepare_enable() on resume and drops it on suspend. + + They are optional because the gate bits behind these names cannot be + derived on this SoC. The MT6589 clock bindings stop at CLK_IMAGE_FPC and + the data sheet documents SENINF at 0x15008000 but never describes the + CAM_TG_CG register that the gate bits would live in, so the bit positions + are unknown. Rather than guess - a wrong bit could gate off a block + some other driver is using - the in-tree board leaves the property off + entirely. + + When a gate is absent the driver simply programs the receiver off + whatever clock state it finds: it skips the clk_prepare_enable() for + that gate, still deasserts the reset, still powers the D-PHY up if one + is wired, and still writes the receiver's registers. So a node with no + "clocks" probes and its ports program correctly; add the property once + the real gate positions have been read from hardware. + minItems: 3 + maxItems: 3 + + clock-names: + description: | + Names of the gates in "clocks", in the order listed there. Both + properties are optional and must either both be present or both be + absent. + items: + - seninf + - csi2 + - tg_grp + + phys: + description: | + The MIPI CSI-2 D-PHY this receiver sits on top of. The driver holds + the PHY's whole lifetime: phy_init() once in the probe function and + phy_exit() once in remove, with phy_power_on()/phy_power_off() per + runtime-PM resume and suspend. On remove the block is taken down + explicitly before phy_exit() runs, so the PHY is never left powered. + + There is no in-tree D-PHY driver for the MT6589 - see the description at + the top of this file - so this property cannot currently name anything + that drives lanes. It is not required: a node without it still probes + and still programs the receiver's registers. + maxItems: 1 + + phy-names: + description: Name of the D-PHY reference in "phys". + const: dphy + + resets: + description: | + Reset for the sensor front end. OPTIONAL, and deliberately not + required: the driver takes it with devm_reset_control_get_optional() and + guards its reset_control_assert()/reset_control_deassert() calls on the + control being present, so a node without this property probes and its + ports program correctly. + + The property is optional because the provider for this reset does not + exist yet. The data sheet puts img_rst at bit 5 of WDT_SWSYSRST + ("writes 1 to reset imgsys and its related pad macro (cam, mipi_rx)"), + and that register is at 0x10000000 + 0x18 in the WDT block - not in + topckgen, which is syscon@10000100 and which declares no #reset-cells at + all. The in-tree board therefore omits the property rather than + reference a node and index that cannot resolve. + + The honest cost of omitting it: without a reset, whatever state the + imgsys/mipi_rx registers were left in is not cleared before this driver + programs them. On a cold boot the block is already at its documented + reset values and this is harmless; after a warm reboot, stale state can + survive. The driver restores the registers it cares about (CSI2_CTRL, + CSI2_INTSTA) but cannot claim the rest are clean. + + Add this property once a reset provider for WDT_SWSYSRST bit 5 exists; + the driver needs no change to pick it up. + maxItems: 1 + + reset-names: + const: seninf + + power-domains: + maxItems: 1 + + # The receiver presents a sink pad facing the sensor (or the MIPI D-PHY + # that stands in for it) and a source pad facing the SCAM adaptor. + ports: + $ref: /schemas/graph/ports.yaml# + + properties: + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + interrupts: + description: | + The receiver's interrupt line. Optional, because on MT6589 the SENINF + interrupt is shared with the ISP block and a board may route it through + the ISP driver instead. + + When present the driver enables the CSI2 CRC/ECC fault reports and + acknowledges them through this line. When absent it leaves them + disabled: an enabled report with nobody to acknowledge it only asserts + the block's interrupt output, since the status bits are latched. + maxItems: 1 + +required: + - compatible + - reg + - mediatek,seninf-port + - ports + +additionalProperties: false + +examples: + - | + seninf: csi2-rx@15008000 { + compatible = "mediatek,mt6589-csi2-rx"; + reg = <0x0 0x15008000 0x0 0x200>; + + mediatek,seninf-port = <0>; + + /* + * No "clocks" / "clock-names" here, and that is deliberate - see the + * description on "clocks". The driver takes all three gates as + * optional and programs the receiver without them. + */ + + /* + * No "resets" here either, for the same reason as the clocks: the + * provider does not exist yet. The driver takes the reset as + * optional, so this node probes without it. See the description on + * "resets". + */ + + /* + * Only the source pad is shown, pointing at the SCAM adaptor that + * follows the receiver; scam_in_ep is defined in the + * mediatek,mt6589-scam binding. + * + * The sink pad is absent because there is nothing in this tree to + * point it at: the A5142 sensor driver was deleted (it never + * answered on I2C on this board) and the MIPI CSI-2 D-PHY node went + * with it, the MT6589 data sheet not describing the PHY registers + * at all. The receiver still creates both media pads and it takes + * a missing "phys" as non-fatal; the link is simply not made. + */ + ports { + seninf_out: port@1 { + seninf_out_ep: endpoint { + remote-endpoint = <&scam_in_ep>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/mediatek,mt6589-g2d.yaml b/Documentation/devicetree/bindings/media/mediatek,mt6589-g2d.yaml new file mode 100644 index 00000000000000..c873a14127b001 --- /dev/null +++ b/Documentation/devicetree/bindings/media/mediatek,mt6589-g2d.yaml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/mediatek,mt6589-g2d.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MT6589 2D Accelerator (G2D) + +maintainers: + - Akari Tsuyukusa + +description: > + MT6589 contains a 2D graphics accelerator, G2D, that is a separate engine + from the display pipeline. It has its own MMIO block, its own interrupt, its + own clock gates, its own DMA master, and its own M4U (IOMMU) port, and it can + copy or fill rectangles between buffers. + + The block moves pixels between two memory buffers and has nothing to do with + the display pipeline, so it is a V4L2 memory-to-memory device: a /dev/videoN + node with an OUTPUT queue (the surface it reads) and a CAPTURE queue (the one + it writes). Buffers reach it as dma-buf fds, imported per queue, so the + addresses it is programmed with are mapped through its own M4U port. The + rectangle is described with the standard V4L2 selection API and a fill is the + same queue pair with a zero-sized COMPOSE selection, so no driver-private + userspace interface is defined. + + It is not a child of the display subsystem in the bindings sense either: it + has its own MMIO block, interrupt and clock gates, and the only thing it + shares with the display blocks is the power domain. + +properties: + compatible: + enum: + - mediatek,mt6589-g2d + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + minItems: 2 + maxItems: 2 + + clock-names: + items: + - g2d-engine + - g2d-smi + + power-domains: + maxItems: 1 + + iommus: + description: > + M4U port for the G2D engine's SRC, W2M and DST address registers. + G2D is a DMA client with its own address registers, so it needs its + own translation: a dma-buf imported on either queue is mapped through + this device, and the resulting IOVA is what gets programmed. The + driver cannot work without it, because the address it writes into + SRC_ADDR/W2M_ADDR is meaningful only to the page table of the port + that produced it. + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - iommus + +additionalProperties: false + +examples: + - | + g2d: g2d@14013000 { + compatible = "mediatek,mt6589-g2d"; + reg = <0x14013000 0x1000>; + interrupts = ; + clocks = <&dispsys CLK_DISP0_G2D_ENGINE>, + <&dispsys CLK_DISP0_G2D_SMI>; + clock-names = "g2d-engine", "g2d-smi"; + iommus = <&iommu MT6589_M4U_PORT_G2D>; + power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; + status = "okay"; + }; diff --git a/Documentation/devicetree/bindings/media/mediatek,mt6589-scam.yaml b/Documentation/devicetree/bindings/media/mediatek,mt6589-scam.yaml new file mode 100644 index 00000000000000..8b105b7ab7bc90 --- /dev/null +++ b/Documentation/devicetree/bindings/media/mediatek,mt6589-scam.yaml @@ -0,0 +1,106 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/mediatek,mt6589-scam.yaml# +$schema: http://devicetree.org/schemas/base.yaml# + +title: MediaTek MT6589 sensor-CAM (SCAM) adaptor + +maintainers: + - Lenovo Linux Team + +description: > + The SCAM blocks sit between each CSI-2 receiver and the CAM/ISP on the + MT6589. They take the unpacked byte stream plus the frame and line + synchronisation the receiver produces, convert it to the ISP's line-based + handshake, and raise the interrupts the ISP uses. + + The blocks are at 0x15008200 with a 0x80 stride between the two instances. + + WARNING: the driver behind this binding is partial. The reset, enable + and size path is implemented and it will probe, but the pixel-format + handling and the media-controller glue towards the ISP are not written. + It will not yet produce frames. + +properties: + compatible: + const: mediatek,mt6589-scam + + reg: + description: | + Register window of THIS PORT'S SCAM BLOCK ONLY. Port 0 is SCAM1_CFG at + 0x15008200 through SCAM1_STA at 0x15008240 plus its 32 bits, i.e. + 0x44 bytes; port 1 is the same layout 0x80 further up at 0x15008280. + + The window must NOT be widened. It used to be documented as + <0x0 0x15008000 0x0 0x900>, which is byte-for-byte the window the + CSI-2 receiver claims: two platform devices cannot share one aperture, + and the SCAM driver computes its offsets from the start of its own + block, so pointing it at 0x15008000 made every SCAM1_CFG access land on + the receiver's SENINF_TOP_CTRL. + maxItems: 1 + + mediatek,scam-port: + description: | + Which SCAM instance this node programs. Port 0 is SCAM1 at + 0x15008200, port 1 is SCAM2 at 0x15008280. On the Lenovo YOGA + Tablet 8/10 port 0 is the main (rear) camera. + + One node per port: the "reg" above is per-port, so a second receiver + needs a second node with mediatek,scam-port = <1> and reg = + <0x0 0x15008280 0x0 0x44>, not a wider window on the first node. + enum: [0, 1] + default: 0 + + power-domains: + maxItems: 1 + + resets: + maxItems: 1 + + # A sink pad fed by the CSI-2 receiver (the mediatek,mt6589-csi2-rx + # binding's seninf_out endpoint) and a source pad feeding the CAM/ISP. + ports: + $ref: /schemas/graph/ports.yaml# + +required: + - compatible + - reg + - mediatek,scam-port + - ports + +additionalProperties: false + +examples: + - | + scam: scam@15008200 { + compatible = "mediatek,mt6589-scam"; + reg = <0x0 0x15008200 0x0 0x44>; + + mediatek,scam-port = <0>; + + /* + * scam_in_ep is the receiver's source endpoint (seninf_out_ep in + * the mediatek,mt6589-csi2-rx binding) and cam_in_ep is the CAM/ISP + * node's sink endpoint (mediatek,mt6589-cam); both are defined in + * their own bindings. + * + * Port 1 would be a second, separate node at 0x15008280/0x44 with + * mediatek,scam-port = <1> - not a wider window on this one. + */ + ports { + scam_in: port@0 { + scam_in_ep: endpoint { + remote-endpoint = <&seninf_out_ep>; + }; + }; + + scam_out: port@1 { + scam_out_ep: endpoint { + remote-endpoint = <&cam_in_ep>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/mediatek,mt6589-vdec.yaml b/Documentation/devicetree/bindings/media/mediatek,mt6589-vdec.yaml new file mode 100644 index 00000000000000..480cf9b3b342c5 --- /dev/null +++ b/Documentation/devicetree/bindings/media/mediatek,mt6589-vdec.yaml @@ -0,0 +1,129 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR MIT) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mediatek,mt6589-vdec.yaml# +$schema: http://devicetree.org/schemas/bindings.yaml# + +title: MediaTek MT6589 Video Decoder (VDEC) + +description: | + The MT6589 video decoder is a hardwired RTL accelerator at 0x16020000 with no + coprocessor and no firmware, described in data sheet chapter 59 as a + multi-standard block: MPEG-1/2, MPEG-4, DivX, Xvid, H.263, VC-1, WMV9, H.264 up + to level 4.2 and VP8. It is programmed entirely through MMIO and power is a + plain clock gate in the VDE power domain. + + This node is the decoder half of what was previously one combined + "mediatek,mt6589-vcodec" node also covering the encoder. VENC and VDEC are + separate hardware blocks - separate register window, separate interrupt + (MT_VDEC_IRQ_ID = GIC SPI 140 against MT_VENC_IRQ_ID = SPI 136), separate clock + controller and separate power domain - and separating them is what allows each to + attach its single power domain: genpd_dev_pm_attach() attaches nothing at all + unless the "power-domains" property has exactly one phandle. + + WHAT THIS NODE DELIVERS, PRECISELY + ----------------------------------- + A resource layer and an honest V4L2 node. The node registers, opens, answers + VIDIOC_QUERYCAP and negotiates formats. Its device_run() reports -ENODEV for + every buffer, and that is the correct answer rather than a stub: + + - The vendor stack does not program the VDEC datapath from the kernel. Its + kernel driver (videocodec_kernel_driver.c, 2391 lines) touches the decoder only + to power it up and to acknowledge the frame-end interrupt (:300-301); it never + writes a datapath register. The sequence is shipped from CLOSED USERSPACE as a + queue of WRITE_REG_CMD records (hal_api.h:33-41, :107-124) that the driver + merely walks over the MFV_SET_CMD_CMD ioctl: + + case WRITE_REG_CMD: + VDO_HW_WRITE(cmd_queue->address + cmd_queue->offset, + cmd_queue->value); (videocodec_kernel_driver.c:1758) + + The decode sequence therefore lives in stripped, partly encrypted ARM blobs + (libvp8dec_sa.ca7.so and siblings). Porting it is a userspace + reverse-engineering project, not a kernel one. + - The data sheet has no register-definition section for this block at all: + chapter 59 stops at the block diagram, and "16000000" does not appear anywhere + in it. The only readable register map is the vendor LDVT register-test + harness, which sets CONFIG_CHIP_VER_CURR 80 (include/vdec_info_common.h:125) - + that same header defines 80 as MT8580 (:130), a different SoC. + - VP8 additionally requires its probability tables pushed bit-by-bit into parser + SRAM before any frame can decode. + + Having its own driver, its own power domain and its own node does not change any + of that. Userspace of this driver must not expect a decoded picture. + +maintainers: + - Akari Tsuyukusa + +properties: + compatible: + oneOf: + - items: + - const: mediatek,mt6589-vdec + + reg: + maxItems: 1 + description: | + The decoder register window: 0x29000 bytes at 0x16020000, itself split into + a MISC block at offset 0 and a VLD (bitstream parser) block at offset 0x1000. + + Note this is 0x16020000 and NOT 0x16000000, which is the vdecsys clock + controller. It comes from the stock mt_reg_base.h VDEC_BASE 0xF6020000 + after the +0xE0000000 remap. A binding cannot describe the register layout, + because no register definition for this block exists in the data sheet. + + interrupts: + maxItems: 1 + description: | + One interrupt line: MT_VDEC_IRQ_ID = GIC_PRIVATE_SIGNALS(32) + 140 = SPI 140 + (kernel/core/include/mach/mt_irq.h:103). + + It is asserted HIGH. The vendor driver registers it with + IRQF_TRIGGER_HIGH (videocodec_kernel_driver.c:2113), the opposite polarity to + the encoder's, so IRQ_TYPE_LEVEL_LOW here would silently never fire. + + clocks: + minItems: 2 + maxItems: 2 + description: | + Two clock gates from the vdecsys provider: the decoder engine (MT_CG_VDEC0_VDE + / CLK_VDEC0_VDE) and its SMI/LARB clock (MT_CG_VDEC1_SMI / CLK_VDEC1_SMI). + + clock-names: + items: + - const: vdec-vde + - const: vdec-smi + description: | + Required rather than implied: vencsys and vdecsys each number their gates + from zero, so provider-local clock ids are not a unique key and index-based + lookup cannot tell the providers apart. + + power-domains: + maxItems: 1 + description: | + The decoder power domain, VDE. It arrives powered off at reset (PWR_STATUS + bit 8 is 0) and has to be raised by the SCM power sequence. + + This property must name exactly ONE domain. genpd_dev_pm_attach() attaches + a domain only when the phandle count is exactly 1 (drivers/pmdomain/core.c), + so a node naming two domains gets no domain at all and no deferred probe - + which is why this block is a node of its own rather than a second window in + a combined node. + + iommus: + minItems: 1 + items: + description: | + M4U port for a decoder DMA engine. All of the decoder's ports are on + larb1, and they must be: mtk_iommu_v1_probe_device() rejects a device + whose master ids span two larbs. + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - power-domains + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/media/mediatek,mt6589-venc.yaml b/Documentation/devicetree/bindings/media/mediatek,mt6589-venc.yaml new file mode 100644 index 00000000000000..a0701f3464fee6 --- /dev/null +++ b/Documentation/devicetree/bindings/media/mediatek,mt6589-venc.yaml @@ -0,0 +1,137 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR MIT) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mediatek,mt6589-venc.yaml# +$schema: http://devicetree.org/schemas/bindings.yaml# + +title: MediaTek MT6589 H.264/VP8 Video Encoder (VENC) + +description: | + The MT6589 video encoder is a hardwired RTL accelerator at 0x17002000 with no + coprocessor and no firmware, described in data sheet chapter 60 (H.264/VP8) and + chapter 61 (MPEG-4, inside the same window). Unlike the later MediaTek VPU/IPI + generations, the block is programmed entirely through MMIO and power is a plain + clock gate in the VEN power domain. + + This node is the encoder half of what was previously one combined + "mediatek,mt6589-vcodec" node also covering the decoder. VENC and VDEC are + separate hardware blocks - separate register window, separate interrupt + (MT_VENC_IRQ_ID = GIC SPI 136 against MT_VDEC_IRQ_ID = SPI 140), separate clock + controller and separate power domain - and separating them is what allows each to + attach its single power domain: genpd_dev_pm_attach() attaches nothing at all + unless the "power-domains" property has exactly one phandle. + + DRAFT DRIVER - SCOPE OF WHAT ACTUALLY WORKS + ------------------------------------------ + This driver implements the chapter-60 H.264 datapath. Read this before assuming + a conformant codec is present. + + What it does do: + + - Probes the register window, the VENC clock and the encoder interrupt. + - Brings the engine out of reset and gates its clock through runtime PM. + - Programs the H.264 datapath: the bitstream, source, reference and + reconstruction address registers, and the quantiser triple, target bit rate, + CBR/initial-QP mode bits, frame rate and P/B QP adjust limiters, rejecting + quantisers outside the documented 0..51 range. + - Allocates and owns the bitstream, reference, reconstruction and rate control + scratch buffers those address registers need. The reference and + reconstruction planes are a two-buffer ping-pong within one coherent + allocation of frame_size * 3 / 2 per buffer, with frame_size remaining the + luma size because it is also the chroma offset. + - Acknowledges the encoder frame-done interrupt, handles a bitstream-buffer + overflow, and reports the coded bitstream length; a watchdog + (MTK_VENC_TIMEOUT_MS) retires a frame the hardware never completes, so a + wedged engine cannot pin a clock reference forever. + - Exposes the H.264 datapath as a V4L2 memory-to-memory codec node (NV12 in, + bitstream out). + + What it does NOT do: + + - Conformant bitstreams. Entropy coding, motion estimation and the coefficient + buffer exist only in the vendor's closed userspace libraries and were not + ported, and the bitrate algorithm behind the rate control registers is the + vendor's. The registers are programmed; the codec is not implemented. + Userspace must treat the output as opaque. + - Slice control (a frame larger than one MB row is never split into slices), + and SPS/PPS emission (no parameter-set buffer is allocated). + - MPEG-4 encoding: its address programming and interrupt exist, but the three + chroma planes and the per-MB side-information buffer at VENC_MP4_SIDE_ADDR + are not allocated, so a submit is rejected with -ENODEV. + - VP8 beyond what mtk_venc_bitstream_size() restricts it to. + + There is no hardware to test against, so none of this is claimed to work end + to end. + + Status: the node in arch/arm/boot/dts/mediatek/mt6589-vcodec.dtsi is disabled, + because the VEN power-on is undiagnosed. See + drivers/pmdomain/mediatek/mt6589-power-domains.md. + +maintainers: + - Akari Tsuyukusa + +properties: + compatible: + oneOf: + - items: + - const: mediatek,mt6589-venc + + reg: + maxItems: 1 + description: | + The encoder register window: 4 KiB at 0x17002000, documented twice over as + data sheet chapter 60 (H.264 and VP8) and chapter 61 (MPEG-4, starting at + offset 0x600 inside the same window). + + interrupts: + maxItems: 1 + description: | + One interrupt line: MT_VENC_IRQ_ID = GIC_PRIVATE_SIGNALS(32) + 136 = SPI 136 + (kernel/core/include/mach/mt_irq.h:101). + + It is asserted LOW. The vendor driver registers it with IRQF_TRIGGER_LOW + (videocodec_kernel_driver.c:2104), and the decoder's line is the opposite + polarity, so IRQ_TYPE_LEVEL_HIGH here would silently never fire. + + clocks: + maxItems: 1 + description: | + One clock gate: the encoder engine, CLK_VENC_VEN from the vencsys provider. + + clock-names: + items: + - const: venc + description: | + Required rather than implied. vencsys and vdecsys each number their gates + from zero, so the provider-local clock ids are not a unique key and + index-based lookup cannot tell the two providers apart. + + power-domains: + maxItems: 1 + description: | + The encoder power domain, VEN. It arrives powered off at reset (PWR_STATUS + bit 7 is 0) and has to be raised by the SCM power sequence. + + This property must name exactly ONE domain. genpd_dev_pm_attach() attaches + a domain only when the phandle count is exactly 1 (drivers/pmdomain/core.c), + so a node naming two domains gets no domain at all and no deferred probe - + which is why this block is a node of its own rather than a second window in + a combined node. + + iommus: + minItems: 1 + items: + description: | + M4U port for an encoder DMA engine. All of the encoder's ports are on + larb0, and they must be: mtk_iommu_v1_probe_device() rejects a device + whose master ids span two larbs. + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - power-domains + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/memory-controllers/mediatek,mt6589-dramc.yaml b/Documentation/devicetree/bindings/memory-controllers/mediatek,mt6589-dramc.yaml new file mode 100644 index 00000000000000..a32086e50bce29 --- /dev/null +++ b/Documentation/devicetree/bindings/memory-controllers/mediatek,mt6589-dramc.yaml @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/memory-controllers/mediatek,mt6589-dramc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MT6589 DRAM controller and DDR PHY + +maintainers: + - Akari Tsuyukusa + +description: | + The MT6589 DRAM controller is deliberately split across three register + windows, which the datasheet names in its DRAMC chapter: + + DRAMC AO base +0x1000_4000 - timings, configuration, MRS + DRAMC NAO base +0x1020_F000 - always-on bank counters + DDRPHY base +0x1001_1000 - pad and DLL controls, and MEMPLL + + Downstream agrees exactly (mt_reg_base.h: DRAMC0_BASE 0xF0004000, + DDRPHY_BASE 0xF0011000, DRAMC_NAO_BASE 0xF020F000) and its accessors + deliberately OR and write all three windows at the same offset, which + makes a register that is only implemented in one of them look present in + all three. This binding lists them separately so a driver can say which + window a value came from. + + The driver is read-only and takes the DDRPHY window only for the + controls it reads - it does not claim the MEMPLL registers at 0x600.. of + that window, which belong to the "mediatek,mt6589-mempll" clock node. + The two nodes therefore both reference 0x1001_1000 but map separate reg + entries, so neither takes ownership of the other's registers. + +properties: + compatible: + const: mediatek,mt6589-dramc + + reg: + minItems: 1 + maxItems: 3 + description: | + The DRAMC AO window at 0x1000_4000 is required and comes first. + Optionally followed by the NAO window at 0x1020_F000 and the DDRPHY + window at 0x1001_1000, in that order. + + reg-names: + minItems: 1 + items: + - const: ao + - const: nao + - const: ddrphy + + clocks: + maxItems: 1 + description: The DRAM clock, MEMPLL_MCK_D4. Informational only. + + clock-names: + const: ddr + + mediatek,emi: + $ref: /schemas/types.yaml#/definitions/phandle + description: | + Phandle to the EMI node, from which the address-mapping register + EMI_CONA is read to derive the DRAM geometry. Optional: without it + the timing registers are still reported, but the size and geometry + cannot be derived. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + + dramc: memory-controller@10004000 { + compatible = "mediatek,mt6589-dramc"; + reg = <0x10004000 0x1000>, + <0x1020f000 0x1000>, + <0x10011000 0x1000>; + reg-names = "ao", "nao", "ddrphy"; + clocks = <&topckgen CLK_TOP_MEMPLL_MCK_D4>; + clock-names = "ddr"; + }; diff --git a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml index 68a116ee3177c9..ad604cfc4fc93c 100644 --- a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml +++ b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml @@ -182,6 +182,16 @@ properties: clock-names: true + amp-en-gpios: + maxItems: 1 + description: + Enable line of an external speaker amplifier, where the board + drives its speaker through one instead of the PMIC's own Class-D + output. The codec asserts it after the analogue path has settled + and releases it before powering down, so the amplifier is only on + while audio is playing. Omitted on boards that use the PMIC + speaker output directly. + required: - compatible diff --git a/Documentation/devicetree/bindings/mmc/mediatek,mt6628-pwrseq.yaml b/Documentation/devicetree/bindings/mmc/mediatek,mt6628-pwrseq.yaml new file mode 100644 index 00000000000000..cd35fb98416c91 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/mediatek,mt6628-pwrseq.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-2.0-only +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/mediatek,mt6628-pwrseq.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MT6628 combo-chip power sequence provider + +maintainers: + - Akari Tsuyukusa + +description: + The MT6628 WLAN/BT/FM/GNSS combo chip is attached to an MMC host over + SDIO. This provider sequences its PMU_EN and active-low RESET inputs. + The PMIC RTC provider is referenced because the chip requires the + RTC_GPIO 32 kHz output to be enabled before power-up. + +properties: + compatible: + const: mediatek,mt6628-pwrseq + + mediatek,rtc: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Reference to the MediaTek PMIC RTC that supplies the 32 kHz output. + + pmu-gpios: + maxItems: 1 + description: Active-high combo-chip PMU enable. + + reset-gpios: + maxItems: 1 + description: Active-low combo-chip reset. + + pinctrl-names: + items: + - const: default + + pinctrl-0: true + status: true + +required: + - compatible + - mediatek,rtc + - pmu-gpios + - reset-gpios + - pinctrl-names + - pinctrl-0 + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/phy/mediatek,tphy.yaml b/Documentation/devicetree/bindings/phy/mediatek,tphy.yaml index 983b038cee661b..7b4e78f62a8551 100644 --- a/Documentation/devicetree/bindings/phy/mediatek,tphy.yaml +++ b/Documentation/devicetree/bindings/phy/mediatek,tphy.yaml @@ -361,7 +361,7 @@ examples: #size-cells = <1>; ranges; - u2port0: usb-phy@11220800 { + usb-phy@11220800 { reg = <0x11220800 0x0100>; clocks = <&topckgen CLK_TOP_UNIVPLL_D26>; clock-names = "ref"; diff --git a/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml b/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml index 48aff8e58e05b5..eeec319c789dcc 100644 --- a/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml +++ b/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml @@ -58,6 +58,21 @@ properties: power-domains: maxItems: 1 + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: + BLS input and output ports in the display pipeline. + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: BLS input from OVL or COLOR + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: BLS output to RDMA + required: + - port@0 + - port@1 + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/soc/mediatek/devapc.yaml b/Documentation/devicetree/bindings/soc/mediatek/devapc.yaml index 99e2caafeadff9..e12fd5f7719379 100644 --- a/Documentation/devicetree/bindings/soc/mediatek/devapc.yaml +++ b/Documentation/devicetree/bindings/soc/mediatek/devapc.yaml @@ -21,10 +21,16 @@ properties: enum: - mediatek,mt6779-devapc - mediatek,mt8186-devapc + - mediatek,mt6589-devapc reg: - description: The base address of devapc register bank - maxItems: 1 + description: | + The base address of devapc register bank. + + MT6589 describes five independent instances, each as a pair of reg + entries (an always-on permission window followed by a power-down + violation window), so instance n uses reg entries 2n and 2n+1. + maxItems: 10 interrupts: description: A single interrupt specifier @@ -38,6 +44,49 @@ properties: description: Names of the clocks list in clocks property maxItems: 1 + mediatek,devapc-forbid-slaves: + description: | + Permission policy: a flat list of 4-tuples that programs each named + slave's access permission from the always-on window. + + + + domain-mask is a bitmask of the domain indices (1 = AP, 2 = MD1, + 4 = MD2, 8 = MM) and perm is 0..3, where 0 (L0) is no protection and + 3 (L3) forbids the access and raises a violation. Slaves not listed + stay at L0. + + This is entirely independent of mediatek,vio-mask-interrupts: a slave + can be locked to L3 yet raise no interrupt. + type: array + items: + type: integer + minItems: 4 + maxItems: 256 + + mediatek,vio-mask-interrupts: + type: boolean + description: | + Interrupt-monitoring policy, independent of the permission policy. + + When this property is present, violation interrupts are masked for + every slave of every instance, so the device raises no interrupt at + all. Violations are still latched in the D_VIO_STA status + registers and the permissions from mediatek,devapc-forbid-slaves + still apply; only the notification is suppressed. This is useful + once the permission policy has been settled, to keep a denied access + from generating interrupt traffic while leaving the status readable. + + When this property is absent (the default), violation interrupts are + unmasked for all slaves so that every violation is reported, which + is what bring-up wants. + + Note that this is deliberately a boolean rather than a bitmask. The + D_VIO_MASK registers are per-slave and per-domain, so a real mask + would need to be a nested matrix; exposing a scalar that only ever + tested for non-zero would imply precision the driver does not have. + A boolean states exactly what is implemented. + required: - compatible - reg @@ -59,3 +108,25 @@ examples: clocks = <&infracfg_ao CLK_INFRA_DEVICE_APC>; clock-names = "devapc-infra-clock"; }; + + - | + #include + #include + + devapc: devapc@10010000 { + compatible = "mediatek,mt6589-devapc"; + reg = <0x10010000 0x100>, <0x10207000 0x100>, + <0x10010100 0x100>, <0x10207100 0x100>, + <0x10010200 0x100>, <0x10207200 0x100>, + <0x10010300 0x100>, <0x10207300 0x100>, + <0x10010400 0x100>, <0x10207400 0x100>; + interrupts = ; + clocks = <&infracfg CLK_INFRA_DEVAPC>; + clock-names = "devapc-infra-clock"; + + /* Lock modules 1 and 2 of instance 0 to L3 for the MD domains. */ + mediatek,devapc-forbid-slaves = <0 1 0x6 0x3>, + <0 2 0x6 0x3>; + + /* Default: leave violation interrupts unmasked. */ + }; diff --git a/Documentation/devicetree/bindings/soc/mediatek/mediatek,mt6589-emi.yaml b/Documentation/devicetree/bindings/soc/mediatek/mediatek,mt6589-emi.yaml new file mode 100644 index 00000000000000..dd49df3cf53f22 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/mediatek/mediatek,mt6589-emi.yaml @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/soc/mediatek/mediatek,mt6589-emi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MT6589 EMI (External Memory Interface) + +maintainers: + - Akari Tsuyukusa + +description: | + The MT6589 EMI block sits between the bus and the DRAM controller and + provides three things this driver exposes: + + - a bus monitor that samples DRAM bandwidth and transaction counts, both + in total and for one selected master; + - a memory protection unit with eight regions, each with a base and end + address in 64KB units and four 3-bit permissions, one per bus domain + (AP, modem 0, modem 1, multimedia), plus a violation interrupt; + - per-master bandwidth filter and page-miss controls. + + The register window is 0x1020_3000. Downstream names the same block at + 0xF0203000 (mt_reg_base.h "EMI_BASE"), which maps back to 0x1020_3000. + + No MPU region is programmed unless the board describes one, because + setting a region's permissions wrongly on this block stops the CPU from + reading its own memory and there is no way back at runtime. The + downstream default table needs the modem image memory map, which a mainline + board does not have. + +properties: + compatible: + const: mediatek,mt6589-emi + + reg: + maxItems: 1 + description: The EMI configuration window, 0x1020_3000. + + interrupts: + maxItems: 1 + description: | + The bus-protection violation line, GIC SPI 94. On MT6589 this line is + shared: the device APC raises it for its own violations and the SMI + does for its, which is why the downstream driver requests it with + IRQF_SHARED. The interrupt is optional; without it the monitor and the + MPU region API still work, and violations are still latched in the + status registers. + + mediatek,mpu-region: + description: | + Optionally pre-program one or more MPU regions. Each entry is + , where region is 0..7, start and end + are physical addresses and are rounded down to 64KB, and the four + permissions are values from the table in the datasheet (p. 615): + 0 = no protection, 1 = secure read/write, 2 = secure read/write plus + non-secure read, 3 = secure read/write plus non-secure write, + 4 = read only, 5 = forbidden. + + Omit it to leave the hardware as the bootloader configured it, which + is the safe default and the only one that is right without knowing the + board's memory layout. + $ref: /schemas/types.yaml#/definitions/uint32-matrix + items: + items: + - description: region index, 0..7 + minimum: 0 + maximum: 7 + - description: start address, physical + - description: end address, physical + - description: permission for the application processor domain + minimum: 0 + maximum: 5 + - description: permission for the modem 0 domain + minimum: 0 + maximum: 5 + - description: permission for the modem 1 domain + minimum: 0 + maximum: 5 + - description: permission for the multimedia domain + minimum: 0 + maximum: 5 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + + emi: memory-controller@10203000 { + compatible = "mediatek,mt6589-emi"; + reg = <0x10203000 0x1000>; + interrupts = ; + }; diff --git a/Documentation/devicetree/bindings/soc/mediatek/mediatek,mutex.yaml b/Documentation/devicetree/bindings/soc/mediatek/mediatek,mutex.yaml index 5267cfe92572dd..802bc0c7c1203b 100644 --- a/Documentation/devicetree/bindings/soc/mediatek/mediatek,mutex.yaml +++ b/Documentation/devicetree/bindings/soc/mediatek/mediatek,mutex.yaml @@ -26,6 +26,7 @@ properties: enum: - mediatek,mt2701-disp-mutex - mediatek,mt2712-disp-mutex + - mediatek,mt6589-disp-mutex - mediatek,mt6795-disp-mutex - mediatek,mt8167-disp-mutex - mediatek,mt8173-disp-mutex diff --git a/arch/arm/boot/dts/mediatek/Makefile b/arch/arm/boot/dts/mediatek/Makefile index b67b94b7cbd018..253eb5d76f4895 100644 --- a/arch/arm/boot/dts/mediatek/Makefile +++ b/arch/arm/boot/dts/mediatek/Makefile @@ -9,6 +9,7 @@ dtb-$(CONFIG_ARCH_MEDIATEK) += \ mt6589-aquaris5.dtb \ mt6589-fairphone-fp1.dtb \ mt6589-lenovo-b8000-f.dtb \ + mt6589-lenovo-b8000-f-emu.dtb \ mt6592-evb.dtb \ mt7623a-rfb-emmc.dtb \ mt7623a-rfb-nand.dtb \ diff --git a/arch/arm/boot/dts/mediatek/mt6320.dtsi b/arch/arm/boot/dts/mediatek/mt6320.dtsi index b7b883a875ca0c..a2c462bc8f3616 100644 --- a/arch/arm/boot/dts/mediatek/mt6320.dtsi +++ b/arch/arm/boot/dts/mediatek/mt6320.dtsi @@ -28,7 +28,7 @@ #power-domain-cells = <0>; }; - rtc { + mt6320_rtc: rtc { compatible = "mediatek,mt6320-rtc", "mediatek,mt6397-rtc"; }; @@ -53,7 +53,7 @@ #clock-cells = <1>; }; - audio-codec { + mt6320_codec: audio-codec { compatible = "mediatek,mt6320-sound"; clocks = <&mt6320clock MT6320_TOPCKPDN_AUD_26M>; clock-names = "top-aud26m"; @@ -68,6 +68,8 @@ mt6320_adc: adc { compatible = "mediatek,mt6320-auxadc"; #io-channel-cells = <1>; + clocks = <&mt6320clock MT6320_TOPCKPDN_AUXADC_CK>; + clock-names = "auxadc"; }; /* @@ -326,6 +328,17 @@ }; }; +/ { + /* + * Expose the PMIC's junction temperature AUXADC channel + * through the generic IIO hwmon bridge. + */ + pmic-temp { + compatible = "iio-hwmon"; + io-channels = <&mt6320_adc 4>; + }; +}; + &pio6320 { mt6320_i2c0_pins: i2c0_pins-default { pin-i2c0{ diff --git a/arch/arm/boot/dts/mediatek/mt6589-lenovo-b6000.dtsi b/arch/arm/boot/dts/mediatek/mt6589-lenovo-b6000.dtsi index d9cc6b3417455d..882d245d107b36 100644 --- a/arch/arm/boot/dts/mediatek/mt6589-lenovo-b6000.dtsi +++ b/arch/arm/boot/dts/mediatek/mt6589-lenovo-b6000.dtsi @@ -5,8 +5,18 @@ #include "mt6589-lenovo-blade.dtsi" +/* + * Geometry confirmed from both candidate panels in the shipped kernel: the + * two lcm_get_params() implementations both report width 800 and + * height 0x500 = 1280 + * + * lcm_get_params_2 (nt35521_cmi_080) 800 x 1280 + * lcm_get_params_3 (s6d7aa0x04_cpt_080) 800 x 1280 + * + * so a 16-bit r5g6b5 framebuffer of 800 * 2 bytes per line is right for + * either part, and the previous "TODO: recheck it" is resolved. + */ / { - // TODO: recheck it framebuffer0: framebuffer@bf600000 { compatible = "simple-framebuffer"; memory-region = <&framebuffer_reserved>; @@ -51,3 +61,71 @@ &bq24196 { monitored-battery = <&battery>; }; + +/* + * Panel pins. The equivalent group for the B8000 lives in + * mt6589-lenovo-b8000.dtsi, which this board does not include, so it is + * declared here rather than referenced across files. + */ +&pio { + b6000_panel_pins: b6000-panel-pins { + pins_power_en { + pinmux = ; + }; + }; +}; + +/* + * The B6000 uses a different panel from the B8000 tablet and it is not a + * "simple panel" - both candidate parts need a manufacturer init sequence, + * so they are handled by panel-lenovo-b6000.c rather than panel-simple. + * + * The vendor kernel picks between two parts at runtime by reading board-ID + * GPIO 0x9a (154) in lcm_compare_id_2(). This tree names the part in DT + * instead; a board that can carry either should override + * "lenovo,b6000-nt35521-cmi" with "lenovo,b6000-s6d7aa0x04-cpt". + */ +&dsi { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + panel@0 { + compatible = "lenovo,b6000-nt35521-cmi"; + reg = <0>; + + vdd-supply = <&mt6320_ldo_vrf28_2>; + vddi-supply = <&mt6320_ldo_vrf28_2>; + backlight = <&backlight_lcd>; + /* + * Two separate panel lines, both recovered from the B6000 + * vendor kernel: + * + * GPIO137 power enable, driven high and left there + * (lcd_power_en_constprop_2 @ c03a2f08: + * hwPowerOn(LDO 0x12, 1800mV), then GPIO 0x89 out=1) + * GPIO131 reset, asserted low then released high + * (lcd_reset @ c03a2ea4 drives GPIO 0x83) + * + * GPIO131 is also listed as MSDC4_DAT1 in the SoC dtsi, but + * mmc4 is never enabled on this board so that pinmux state is + * not in force; the panel owns the pin here. If an SD slot is + * ever wired to mmc4 this conflict has to be resolved. + */ + enable-gpios = <&pio 137 GPIO_ACTIVE_HIGH>; + reset-gpios = <&pio 131 GPIO_ACTIVE_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&b6000_panel_pins>; + + port { + panel_in: endpoint { + remote-endpoint = <&dsi_out>; + }; + }; + }; +}; + +/* The panel's DSI output endpoint is declared by the SoC dtsi. */ +&dsi_out { + remote-endpoint = <&panel_in>; +}; diff --git a/arch/arm/boot/dts/mediatek/mt6589-lenovo-b8000-f-emu.dts b/arch/arm/boot/dts/mediatek/mt6589-lenovo-b8000-f-emu.dts new file mode 100644 index 00000000000000..dd68bb7908ad78 --- /dev/null +++ b/arch/arm/boot/dts/mediatek/mt6589-lenovo-b8000-f-emu.dts @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +/* + * Emulator-only variant of the B8000 device tree. + * + * This exists for exactly one reason: the Renode ARM CPU model used for the + * MT6589 emulator does not service the CP15 generic timer. Measured with two + * bare-metal stubs on the same machine file: + * + * nop + b . PC reaches 0x80000004 normally + * mrc p15,0,r0,c14,c0,0 (read CNTVCT) hangs, no PC ever reported + * + * So `arm,armv7-timer` cannot be used there. The kernel binds the first timer in + * the tree for the sched clock and then reads CNTVCT in hrtimers_init() / + * timekeeping_init(), which is precisely where the emulated boot stops (the log + * ends at srcu_init, the statement immediately before hrtimers_init()). + * + * Disabling the node here makes timer_probe() fall through to + * `mediatek,mt6577-timer`, which is a normal MMIO peripheral served by the + * emulator's GPT model. + * + * `arm,armv7-timer` is CORRECT for real MT6589 hardware, so this must not be + * folded into mt6589.dtsi or into the board dts. The real trees stay honest and + * this file carries the workaround, which is also the thing to delete once the + * CPU model grows a generic timer. + */ + +#include "mt6589-lenovo-b8000-f.dts" + +/ { + model = "Lenovo YOGA Tablet 10 (Wi-Fi) (emulator)"; +}; + +/* The generic timer node has no label in mt6589.dtsi, so it is addressed by + * path. Note `timer` as a label belongs to timer@10008000 (the GPT) - using + * &timer here would disable the wrong device. */ +&{/timer} { + /* see the file header: the emulated CPU does not implement CNTVCT */ + status = "disabled"; +}; + +/* + * calibrate_delay() spins a busy loop timed against the clock to derive + * loops_per_jiffy. On an interpreted CPU model that takes an enormous amount of + * emulated time - the boot sits in calibrate_delay_converge() and looks hung. + * + * lpj= sets preset_lpj, which makes calibrate_delay() skip the measurement. It + * has to be here rather than in the ATAG command line: the ATAG line is merged + * into /chosen/bootargs by atags_to_fdt(), and the merge did not take effect + * (the kernel's reported command line showed no lpj=). + */ +&{/chosen} { + /* + * `console=ttyS0` alone does not survive on this SoC. univ8250_console_init + * is a console_initcall, so it registers the ttyS console before any + * DT-discovered 8250 port exists; with nothing to match it, the console is + * never registered and printk stops as soon as the earlycon is replaced. + * + * The mmio form below is the documented escape: univ8250_console_match() + * explicitly handles + * + * console=uart8250,mmio32, + * + * which lets the console bind by address instead of by ttyS index. Both are + * given: ttyS0 for the normal case and the mmio form for the case where the + * port is only discovered later. + * + * Measured without it: printk ends at 4.341801 s while the PC sampler shows + * the kernel still executing at vt=14 s. + * + * ORDER MATTERS, and the measurement contradicts the obvious guess. + * __add_preferred_console() sets preferred_console to the LAST console= on + * the command line, so putting the address form last should make it + * preferred - but "uart8250" is not a name any driver registers, so that + * ordering only reached 3.261907 s, WORSE than ttyS0 alone. Keeping + * `console=ttyS0` last reaches 4.613663 s, which is the best measured so + * far. So the address form helps as a supplement, not as the preference. + */ + bootargs = "console=ttyS0,115200n8 console=uart8250,mmio32,0x11006000 earlycon=uart8250,mmio32,0x11006000 lpj=1000000 maxcpus=1 initcall_debug ignore_loglevel loglevel=8 clk_ignore_unused printk.always_kmsg_dump=1 log_buf_len=16M"; + /* + * `console=ttyS0` above is not enough on its own: when the 8250 driver + * unbinds and rebinds ttyS0 during probe, the kernel drops the boot console + * and needs stdout-path to know which device to pick up again. Without it + * printk simply stops - measured, the log ended at fb0 device_add while the + * guest was still running, sitting in cpu_v7_do_idle. + * + * MT6589-aquaris5.dts, same SoC, uses stdout-path = &uart0. + */ + stdout-path = &uart0; +}; diff --git a/arch/arm/boot/dts/mediatek/mt6589-lenovo-b8000.dtsi b/arch/arm/boot/dts/mediatek/mt6589-lenovo-b8000.dtsi index ca134aac10dcae..ae37a2dd38ce38 100644 --- a/arch/arm/boot/dts/mediatek/mt6589-lenovo-b8000.dtsi +++ b/arch/arm/boot/dts/mediatek/mt6589-lenovo-b8000.dtsi @@ -6,6 +6,7 @@ #include "mt6589-lenovo-blade.dtsi" / { + // This is unnecessary if MediaTek DRM is working correctly. framebuffer0: framebuffer@bf600000 { compatible = "simple-framebuffer"; memory-region = <&framebuffer_reserved>; @@ -13,8 +14,91 @@ height = <800>; stride = <(1280 * 2)>; format = "r5g6b5"; + + status = "disabled"; + }; +}; + +&pio { + panel_pins: panel-pins { + pins_power_en { + pinmux = ; + }; + }; + + /* GPIO129 is the board's DISP_PWM output (function 1). */ + disp_pwm_pins: disp-pwm-pins { + pin-disp-pwm { + pinmux = ; + bias-disable; + }; + }; +}; + +&bls { + pinctrl-names = "default"; + pinctrl-0 = <&disp_pwm_pins>; +}; + +&dsi_out { + remote-endpoint = <&panel_in>; +}; + +&mt6320_ldo_vrf28_2 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; +}; + +&dsi { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + /* + * The board ships with one of two DSI panels, told apart by the + * ID strap on GPIO154 (GPIO_LCM_ID_PIN in the vendor DCT): + * + * strap == 0 -> cm_hx8896a01_dsi_vdo (CMI) + * 1521x833, hfp186 hsw5 hbp50, vfp21 vsw2 vbp10 + * strap == 1 -> cm_hx8896a01_dsi_vdo_boe (BOE) + * 1416x822, hfp100 hsw4 hbp32, vfp10 vsw2 vbp10 + * + * Both were recovered from the Blade kernel image + * (kernel_blade10_row_wifi.elf): lcm_get_params_0 @ c0461a2c and + * lcm_get_params_1 @ c0461c58, with lcm_compare_id_0/_1 reading the + * strap and returning mutually exclusive answers. The vendor's + * disp_drv_get_lcm_driver() @ c050dfd8 walks lcm_driver_list in + * ProjectConfig order, calls compare_id() on each and takes the first + * match, so exactly one is ever selected. + * + * Neither panel takes a DCS init sequence: lcm_init_0/_1 are literally + * "lcd_power_en(); return;" - power the rail, drive GPIO137, done. + * That is why panel-simple fits this board, unlike the B6000. + * + * panel-simple cannot express the strap, so the panel is chosen here. + * The compatible below selects the CMI timing; a unit with a BOE panel + * needs "boe,hx8896-a01-panel" instead: "cmi,hx8896-a01-panel" carries + * the CMI timings (76019 kHz) and "boe,hx8896-a01-panel" the BOE ones + * (69837 kHz). The older "innolux,hx8896-a01-panel" name still maps to + * the CMI timings for compatibility. + */ + panel@0 { + compatible = "cmi,hx8896-a01-panel"; + reg = <0>; + power-supply = <&mt6320_ldo_vrf28_2>; + backlight = <&backlight_lcd>; + enable-gpios = <&pio 137 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&panel_pins>; + + port { + panel_in: endpoint { + remote-endpoint = <&dsi_out>; + }; + }; }; + battery: battery { compatible = "simple-battery"; device-chemistry = "lithium-ion-polymer"; @@ -35,8 +119,20 @@ */ voltage-max-design-microvolt = <4352000>; - /* BQ24196 stock configuration. */ - precharge-current-microamp = <512000>; + /* + * BQ24196 stock configuration. ChargerHwInit_bq24196() in the + * shipped kernel calls both setters with code 1: + * + * bq24196_set_iprechg(1) reg3 bits 7:4 + * bq24196_set_iterm(1) reg3 bits 3:0 + * + * and the BQ24196 encodes each field as 128 mA steps starting at + * 128 mA, so code 1 is 256 mA in both cases. Precharge was + * listed here as 512 mA, which the hardware is not programmed + * for; it only matters for a deeply discharged pack, but that is + * exactly when getting it wrong is least welcome. + */ + precharge-current-microamp = <256000>; charge-term-current-microamp = <256000>; constant-charge-voltage-max-microvolt = <4352000>; diff --git a/arch/arm/boot/dts/mediatek/mt6589-lenovo-blade-camera.dtsi b/arch/arm/boot/dts/mediatek/mt6589-lenovo-blade-camera.dtsi new file mode 100644 index 00000000000000..585549b97c773e --- /dev/null +++ b/arch/arm/boot/dts/mediatek/mt6589-lenovo-blade-camera.dtsi @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +/* + * MIPI CSI-2 receiver, SCAM adaptor and CAM/ISP nodes for the Lenovo YOGA + * Tablet 8/10 (MT6589). + * + * DRAFT: not merged, and the camera pipeline does not work end to end. The + * summary is: + * + * - The receiver and SCAM register maps are transcribed from the MT6589 + * data sheet (and cross-checked against the vendor tree). + * - The two clock gates the receiver wants are drafted but their bit + * positions are unverified, so the driver takes them as optional. + * - The MIPI CSI-2 D-PHY is NOT modelled at all. It previously had a + * placeholder node claiming an address that three other nodes also + * claimed; see the note on the receiver node below for the details. + * - The CAM/ISP node registers as a subdev with a sink pad, so it is the + * downstream endpoint of the graph, but there is no video node and no + * DMA behind it: the link ends at a subdev that consumes nothing yet. + * - The sensor step is absent. There is no D-PHY node and no sensor node, + * so the receiver's sink pad exists in the driver but has no peer in the + * graph. The receiver -> SCAM -> CAM links do resolve, because the + * mtk-cam-graph node below owns the media_device, waits on one endpoint + * per block -- all three of them, CAM included -- and creates the links + * from the remote-endpoint properties in this file. + * + * Address map of the sensor front end, from the data sheet: + * + * 0x15008000 seninf_top per-port parallel clock gates + * 0x15008010 seninf + * 0x15008100 csi2 receiver 0 (main camera) + * 0x15008180 csi2 receiver 1 + * 0x15008200 SCAM adaptor 0 (0x44 bytes, own node) + * 0x15008280 SCAM adaptor 1 (0x44 bytes, own node) + * 0x15008380 seninf TG1/TG2 + * 0x15008400 seninf_ccir656 + * 0x15008500 N3D + * 0x15009000 jpgdec + * 0x1500A000 jpgenc + * 0x15004000 CAM / ISP + * + * Every device below claims a disjoint slice of that map. The two that used + * to overlap -- the receiver and the SCAM adaptor, both claiming + * 0x15008000/0x900 -- no longer do; see the notes on the two nodes. + * + * Note this window is NOT inside the imgsys syscon node, which only covers + * 0x15000000/0x1000. The sensor front end therefore needs its own node, + * and it takes a multi-range reg in the same style as smi_common in + * mt6589.dtsi rather than being nested under &imgsys. + * + * The wiring that does exist is therefore: + * + * csi2-rx (seninf port 0) -> scam (port 0) -> cam + * + * with the D-PHY step deliberately absent between the sensor and the + * receiver. The media_device, the async notifier and the media links that + * make that chain a graph are owned by the mtk-cam-graph node at the bottom + * of this file; the three hardware nodes below own no graph between them. + */ + +#include + +/ { + /* + * The whole sensor front end: the two CSI-2 receivers, the SCAM + * adaptors that follow them, the CCIR-656 and N3D blocks, and the + * CAM/ISP they feed. + * + * The ranges are listed as separate entries rather than one span so + * that the documented holes are not claimed as registers. + * + * Note what this node does NOT claim. The first range below is + * 0x200 bytes, ending at 0x150081FF, which is deliberately *short* of + * the 0x900 it used to claim: + * + * - this driver programs SENINF_TOP_CTRL (0x15008000) and the csi2 + * registers (port 0 at 0x15008100, port 1 at 0x15008180, each 0x80 + * long and ending at +0x3c), so nothing past 0x150081BC is ever + * touched; + * - SCAM1 at 0x15008200 is the scam device's, and claiming it here + * as well would make two platform devices reserve one aperture. + * + * The rest of the 0x900 span (seninf_ccir656 at 0x15008400, N3D, the TG + * blocks at 0x15008380) is left unclaimed rather than reserved for a + * driver that does not exist yet. + * + * This node also used to carry two more ranges, <0x15009000 0x400> and + * <0x1500a000 0x400>. Those are NOT the receiver's: mtk-csi2-rx.c never + * touches an offset above +0x200, and both ranges sit inside registers + * that mt6589.dtsi already gives to other drivers (jpgdec at + * 0x15009000/0x1000 and jpgenc at 0x1500a000/0x1000), so claiming even + * half of each was a second overlapping reservation. + * + * The three reg properties below are two-cell
tuples, + * not four. The parent soc node is #address-cells = <1>, + * #size-cells = <1> (mt6589.dtsi), and so is every other imgsys child. + * Written as four cells they are not rejected at build time - dtc does + * not check reg against the parent - but the kernel reads cell 0 as the + * address and cell 1 as the size, so "reg = <0x0 0x15008000 0x0 0x200>" + * became a request for 0x00000000..0x15007fff and probe died with + * -EBUSY on all three drivers. + */ + seninf: csi2-rx@15008000 { + compatible = "mediatek,mt6589-csi2-rx"; + reg = <0x15008000 0x200>; + + /* + * No "clocks" here on purpose. The receiver wants seninf, csi2 + * and tg_grp gates, but the MT6589 clock bindings stop at + * CLK_IMAGE_FPC (11) and the data sheet does not describe the + * CAM_TG_CG register those gate bits would live in, so their + * positions cannot be derived. The driver takes all three as + * optional, so it probes and programs the ports without them; + * add the property once the real bit positions are read from + * hardware. Inventing gate bits risks disabling a block some + * other driver is using. + */ + + /* + * Port 0 is the seninf1/csi2 receiver pair at 0x15008100, which + * carries the main (rear) camera. A second receiver at + * 0x15008180 would be port 1 for the front camera; it is not + * drafted here. + */ + mediatek,seninf-port = <0>; + + /* + * img_rst in the data sheet resets "imgsys and its related + * pad macro (cam, mipi_rx)", so the sensor front end shares + * the imgsys top reset. + * + * It is left out here, deliberately, and NOT because the + * property is wrong or because the driver insists on it. The + * driver takes the reset as optional (devm_reset_control_get_ + * optional), so it probes without this property; see the note + * in mtk-csi2-rx.c for the full contract. + * + * The earlier comment here said the reset was "TOPRGU bit 5" and + * pointed at topckgen. The bit position is right but the node + * is not: img_rst is bit 5 of WDT_SWSYSRST, and that register is + * at 0x10000000 + 0x18 in the WDT block + * (draft/ds/ovl.txt:55062 and 55144), not in topckgen. The + * vendor tree agrees on the bit: + * + * mtk_wdt.h: #define MTK_WDT_SWSYS_RST_IMG_RST (0x0020) + * + * and the datasheet's own description of bit 5 is "writes 1 to + * reset imgsys and its related pad macro (cam, mipi_rx)". + * + * topckgen in this tree is syscon@10000100, a different node, + * and it declares only #clock-cells with no #reset-cells, so + * "&topckgen ..." could not have resolved even with the right + * index. Restoring that line as written would not build. + * + * What is needed to bring the reset back is a provider node for + * WDT_SWSYSRST with #reset-cells = <1>, and then: + * + * resets = <&wdt MT6589_WDT_SWSYSRST_IMG_RST>; + * + * The driver needs no change to pick that up. + */ + // resets = <&wdt 5>; + + /* + * The D-PHY this receiver sits on top of. + * + * NOT MODELLED. There was previously a "mediatek,mt6589-mipi-csi-dphy" + * node here, claiming reg = <0x15008000 0x900> -- the same window + * already claimed by this receiver (0x15008000/0x900) and, at the + * time, by SCAM below as well -- with a comment admitting the + * address was not known to be correct. Three nodes claiming one + * register window is not a graph, it is a guess that would make + * the mtk-csi2-rx driver map the wrong registers and then DMA on + * top of them. + * + * So the node is gone rather than left to masquerade as a working + * driver. Do NOT reintroduce it with an invented address: the + * MT6589 data sheet does not contain the MIPI RX PHY register map + * at all (the PHY appears only as the word "mipi_rx" in the img_rst + * reset description), so there is currently nothing to derive the + * reg from. When a real PHY binding and driver exist, add the + * node, restore "phys" / "phy-names" below, and point + * seninf_in_ep back at the PHY's source endpoint. + * + * mtk-csi2-rx takes a missing "phys" as non-fatal: it still probes + * and programs the receiver, and the lanes simply stay idle. + */ + + ports { + /* + * Source: the receiver feeds the SCAM adaptor. + * + * This port used to sit alongside a seninf_in sink port + * that pointed at the A5142's source endpoint. That + * sensor is gone (the driver oopsed at probe and never + * answered), and removing the whole ports node took the + * receiver's own source endpoint with it. + * + * It was then relocated *inside* scam's scam_in port, as a + * nested "ports { port@1 }". That cannot work and has + * been moved back here, where it belongs, because it + * describes the receiver, not SCAM: + * + * of_fwnode_graph_get_port_parent() (drivers/of/ + * property.c) resolves a port's owner by walking + * port -> parent -> (if that parent is named "ports") + * its own parent. With the nested layout the owner + * of port@1 resolved to scam_in, i.e. to a *port* + * node rather than to a device node, and + * v4l2_subdev_get_fwnode_pad_1_to_1() then failed its + * device_match_fwnode() check with -ENXIO. The + * receiver -> SCAM link was silently never created, + * even with the pads present. + * + * A port node's parent must be the device node that owns + * the pads, so this endpoint lives on the receiver. + */ + seninf_out: port@1 { + seninf_out_ep: endpoint { + remote-endpoint = <&scam_in_ep>; + }; + }; + }; + + }; + + /* + * The MIPI CSI-2 D-PHY that belongs between the A5142 and the receiver + * is intentionally absent. See the long note on the receiver node + * above for why, and for what has to be added before it can come back. + */ + + /* + * The SCAM adaptor for port 0, and the CAM/ISP it feeds. Both are + * described so that the media graph closes out end to end; see + * README.md for how much of each is actually implemented. + * + * The reg is THIS PORT'S BLOCK ONLY: SCAM1_CFG at 0x15008200 through + * SCAM1_STA at 0x15008240 plus its 32 bits, so 0x44 bytes. It used to + * be + * + * reg = <0x0 0x15008000 0x0 0x900>; + * + * which is byte-for-byte the same window the seninf node above claims. + * Two platform devices cannot share one aperture: the second + * devm_platform_ioremap_resource() either fails probe outright or, on a + * kernel that allows it, hands out a second mapping over registers + * another driver is already driving. + * + * Worse, the driver computed its own offsets assuming the window started + * at SCAM1_CFG (0x15008200) while the DT pointed it at 0x15008000, so + * every SCAM1_CFG access landed on SENINF_TOP_CTRL and the whole driver + * was reading and writing the receiver's registers. + * + * SCAM2 would be a second node at 0x15008280/0x44, the same layout 0x80 + * further up; it is not drafted here because only port 0 is wired. + */ + scam: scam@15008200 { + compatible = "mediatek,mt6589-scam"; + reg = <0x15008200 0x44>; + + mediatek,scam-port = <0>; + + /* + * No "resets" property, and none should be added here without a + * driver change: mtk-scam.c requests no reset control at all. It + * brings the block up through SCAM_CON's own documented + * enable/reset field instead. + * + * An earlier version of this file carried the same commented-out + * topckgen reference as the receiver, which implied the SCAM + * driver wanted a reset it never asked for. It did not, and + * adding the property would have no effect. See the long note on + * the receiver node for why the topckgen reference is wrong in + * the first place. + */ + + ports { + scam_in: port@0 { + scam_in_ep: endpoint { + remote-endpoint = <&seninf_out_ep>; + }; + }; + + scam_out: port@1 { + scam_out_ep: endpoint { + remote-endpoint = <&cam_in_ep>; + }; + }; + }; + }; + + cam: cam@15004000 { + compatible = "mediatek,mt6589-cam"; + reg = <0x15004000 0x400>; + + /* + * GIC SPI 151, Cam_irq_b - the CAM/ISP block's own interrupt, + * level active low. + * + * The data sheet's interrupt table (Table 7-1) lists SPI 151 as + * Cam_irq_b, and the vendor tree agrees: + * + * #define CAMERA_ISP_IRQ0_ID (GIC_PRIVATE_SIGNALS + 151) + * // cam_irq_b + * + * with GIC_PRIVATE_SIGNALS == 32, and it is that one the ISP + * driver requests (camera_isp.c:4053) and drives level-sensitive + * (camera_isp.c:4198). The other three are cam_irq1_b (150), + * cam_irq2_b (149) and cam_irq3_b (144), which belong to the + * TG1/TG2 and CQ stages rather than to this block's control + * plane. + * + * The driver enables IMGO_DONE_EN in CAM_CTL_DMA_INT and keys + * mtk_cam_irq() off IMGO_DONE_ST, so this is the interrupt it + * actually needs: without it the node still probed, but the + * error said "IRQ index 0 not found" and no frame could ever + * complete. + */ + interrupts = ; + + ports { + cam_in: port@0 { + cam_in_ep: endpoint { + remote-endpoint = <&scam_out_ep>; + }; + }; + }; + }; + + /* + * The owner of the media graph the three nodes above form. + * + * This node owns no hardware: there is no "reg", it maps nothing, and + * it programs no registers. It exists because a media graph is a + * runtime object that has to be built, and the device tree alone does + * not build it. Concretely, the driver bound here registers the + * media_device (/dev/media0) and the v4l2_device, runs the + * v4l2_async_notifier that pulls csi2-rx, scam and cam into that + * v4l2_device -- which is what registers their entities into the + * media_device -- and creates the media links between them. + * + * Without this node the three subdevs still register as async subdevs, + * but nothing is waiting for them, so no entity joins a media_device + * and no media_link is ever created. Their pads would exist and this + * file's remote-endpoint properties would point at each other, and the + * graph would still be empty: a media_link is created by the driver, not + * by the DT. + * + * The endpoints below are the driver's OWN endpoints, one per block it + * should wait for. Each names a remote that lives on the block in + * question: + * + * graph_to_rx -> seninf_out_ep, i.e. the receiver's output + * graph_to_scam -> scam_out_ep, i.e. SCAM's output + * graph_to_cam -> cam_in_ep, i.e. CAM's input + * + * There is ONE endpoint per block, and every block needs its own -- CAM + * included. An earlier version of this file had only the first two, on + * the reasoning that CAM "is reached through scam_out_ep, whose remote is + * cam_in_ep". That reasoning is wrong about how the async core works, + * and the graph it described did not exist: + * + * - v4l2_async_nf_add_fwnode_remote() adds a connection whose match + * descriptor is the *remote* endpoint fwnode. That is the only way a + * subdev is ever pulled into a v4l2_device: when a subdev registers, + * v4l2_async_find_match() walks the notifier's waiting_list and + * match_fwnode_one() compares that subdev's own fwnode against each + * descriptor (or against fwnode_graph_get_port_parent() of it). A + * connection that does not exist cannot match, so the subdev never + * binds. + * + * - cam_in_ep being *named* as the remote of scam_out_ep tells the + * notifier nothing. Only the endpoints on THIS node are scanned by + * mtk_cam_graph_add_endpoints(), so with no endpoint here pointing at + * cam_in_ep there was no connection to match against and CAM never + * bound. + * + * - A subdev that never matched is never passed to + * __v4l2_device_register_subdev(), so it never lands on + * v4l2_dev->subdevs. mtk_cam_graph_create_links() walks exactly that + * list, so with CAM absent from it the SCAM -> CAM link was never + * created even though both pads and both remote-endpoint properties + * existed and pointed at each other. + * + * So graph_to_cam is load-bearing, not a second spelling of + * graph_to_scam: the latter gets SCAM into the v4l2_device, the former + * gets CAM in, and only then does the cross-product in create_links() + * have a (SCAM source, CAM sink) pair to resolve. + * + * Matching is per *endpoint* rather than per block, and one subdev + * matching more than one of these connections is harmless: + * v4l2_async_match_notify() calls __v4l2_device_register_subdev() only + * when sd->asc_list is empty, and v4l2_create_fwnode_links_to_pad() + * skips a link that already exists. + * + * Note there is deliberately no endpoint for the receiver's SINK pad. + * That pad has no peer: the sensor step and the MIPI CSI-2 D-PHY are + * both absent from this tree (see the long note on the receiver node), + * so there is no node for that endpoint to point at. The driver treats + * an unresolvable remote as "not present", not as an error, which is + * what lets this node build and probe on a board with no sensor wired. + * + * What a sensor would ACTUALLY require, precisely, because "when a sensor + * lands its source endpoint will resolve and the link will be created with + * no change here" was a claim this code does not deliver. There are two + * separate obstacles, not one: + * + * 1. The receiver has no sink endpoint at all. Its ports node above + * carries only seninf_out (port@1); the seninf_in sink port and its + * seninf_in_ep were removed together with the dead A5142 node. A + * sensor has nothing to point its own remote-endpoint at, and the + * D-PHY that would sit between the two is not modelled either. + * Restoring + * + * seninf_in: port@0 { + * seninf_in_ep: endpoint { + * remote-endpoint = <&dphy_out_ep>; + * }; + * }; + * + * on the receiver, plus the PHY node's endpoint pointing back at the + * sensor, is the first half. + * + * 2. Nothing would wait for the sensor even then. The notifier only + * ever binds subdevs named by an endpoint on THIS node, so a sensor + * that registered a subdev and a sensor_out_ep of its own would still + * be absent from v4l2_dev->subdevs, and + * mtk_cam_graph_create_links() -- which iterates that list and + * nothing else -- would never consider it as a link source. A fourth + * endpoint here (graph_to_sensor -> sensor_out_ep) is what makes that + * work, and it is the second half. + * + * Until both exist, the receiver's sink pad stays unconnected and the + * graph honestly begins at the receiver's source pad. + * + * dtc emits THREE "graph connection ... is not bidirectional" warnings + * for the endpoints below -- one per endpoint. They are expected and are + * not suppressed. The four endpoints the three hardware nodes declare + * ARE mutually bidirectional (seninf_out_ep <-> scam_in_ep and + * scam_out_ep <-> cam_in_ep), but these three belong to a node that is not + * part of the image path: it owns a media_device and a notifier, not pads, + * so there is no peer endpoint on it for these to pair with. Adding + * back-pointing endpoints to silence the warning was tried and makes + * things worse -- it adds a further warning for the new edge and + * describes a connection that does not exist. A node with no pads is the + * normal way a graph owner is expressed; see the amlogic c3 "adap" node for + * the same shape done with pads. Nothing in this tree suppresses this + * warning by name, so neither does this file. + */ + mtk-cam-graph { + compatible = "mediatek,mt6589-cam-graph"; + + ports { + graph_to_rx: port@0 { + graph_to_rx_ep: endpoint { + remote-endpoint = <&seninf_out_ep>; + }; + }; + + graph_to_scam: port@1 { + graph_to_scam_ep: endpoint { + remote-endpoint = <&scam_out_ep>; + }; + }; + + /* + * CAM has to be waited for like any other block. See + * the note above: without this endpoint CAM never + * matched, never joined v4l2_dev->subdevs, and the + * scam_out -> cam_in link was never created. + */ + graph_to_cam: port@2 { + graph_to_cam_ep: endpoint { + remote-endpoint = <&cam_in_ep>; + }; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/mediatek/mt6589-lenovo-blade.dtsi b/arch/arm/boot/dts/mediatek/mt6589-lenovo-blade.dtsi index c74efa98a8ace2..b79418d1658952 100644 --- a/arch/arm/boot/dts/mediatek/mt6589-lenovo-blade.dtsi +++ b/arch/arm/boot/dts/mediatek/mt6589-lenovo-blade.dtsi @@ -9,13 +9,15 @@ #include "mt6589.dtsi" #include "mt6320.dtsi" +#include "mt6589-vcodec.dtsi" +#include "mt6589-lenovo-blade-camera.dtsi" #include / { chosen { /* DEBUG: testing clock drivers */ - bootargs = "clk_ignore_unused printk.always_kmsg_dump=1 log_buf_len=16M"; + bootargs = "clk_ignore_unused printk.always_kmsg_dump=1 log_buf_len=16M slub_debug=FZPU"; }; memory { @@ -56,6 +58,62 @@ mediatek,platform = <&afe>; aux-devs = <&mt6320_accdet>; }; + + vibrator { + compatible = "regulator-haptic"; + haptic-supply = <&mt6320_ldo_vibr>; + min-microvolt = <2800000>; + max-microvolt = <2800000>; + }; + + /* + * Backlight. + * + * Declared here at root level rather than as a child of &bls: a child + * node of a platform device needs a "reg" to be enumerated, and this + * one has none. Nested under the PWM node it was silently skipped, + * so no backlight_device was ever created - see the reasoning on the + * &bls override below. + * + * The BLS block is both the backlight PWM generator and a stage of the + * display pipeline, and nothing consumed the PWM side of it: there was + * no backlight device in this tree at all, so the panel would stay + * dark even once the video pipeline worked. + * + * The cells are PWM index then period in ns, so <&bls 0 100000> + * selects the chip's only PWM with a 100 us period, which is what the + * upstream mt2701-evb node uses for this same block. + */ + backlight_lcd: backlight_lcd { + compatible = "pwm-backlight"; + pwms = <&bls 0 100000>; + brightness-levels = < + 0 16 32 48 64 80 96 112 + 128 144 160 176 192 208 224 240 + 255 + >; + default-brightness-level = <13>; + }; + + /* + * Keep this provider at the root level. The MSDC host is not an + * OF bus, so a power-sequence device nested under &mmc3 is not + * instantiated as an independent platform device. + * + * The RTC reference also ensures that the PMIC's 32 kHz output is + * enabled before the MMC host starts enumerating the combo chip. + */ + mt6628_pwrseq: mt6628-pwrseq { + compatible = "mediatek,mt6628-pwrseq"; + status = "okay"; + mediatek,rtc = <&mt6320_rtc>; + + pmu-gpios = <&pio 14 GPIO_ACTIVE_HIGH>; + reset-gpios = <&pio 225 GPIO_ACTIVE_LOW>; + + pinctrl-names = "default"; + pinctrl-0 = <&mt6628_pins_default>; + }; }; &cpu0 { @@ -79,6 +137,25 @@ }; &pio { + /* + * DISP_PWM on GPIO129, function 1. + * + * The BLS block generates the panel backlight PWM, but the waveform + * only leaves the SoC if this pin is muxed away from GPIO. The + * vendor board file programmes GPIO129_MODE = GPIO_MODE_01 in + * cust_gpio_boot.h, which is this same function; without it every + * BLS register reads back exactly what was written (PWM_En set, the + * duty tracking brightness) while the panel sees nothing, because + * the pin is still a plain GPIO. + */ + disp_pwm_pins: disp-pwm-pins { + pin-disp-pwm { + pinmux = ; + /* GPIO129_PULLEN/PULL as the vendor sets them. */ + bias-pull-down; + }; + }; + pmic_eint_pins: pmic-eint-pins { pin-pmic-eint { pinmux = ; @@ -86,6 +163,7 @@ }; }; + /* The AL3320 ambient-light sensor's interrupt. */ alsps_eint_pins: alsps-eint-pins { pin-alsps-int { pinmux = ; @@ -94,6 +172,51 @@ }; }; + /* + * The board uses GPIO14 for MT6628 PMU_EN and GPIO225 for its + * active-low reset input. Do not apply an unverified strap phase: + * the vendor MT6628 power-up sequence does not document one on + * these pins. + */ + mt6628_pins_default: mt6628-pins-default { + pmu_en { + pinmux = ; + bias-disable; + }; + reset { + pinmux = ; + bias-disable; + }; + }; + + mt6628_gnss_pins_default: mt6628-gnss-pins-default { + pin-gps-sync { + pinmux = ; + output-low; + bias-disable; + }; + }; + + mt6628_gnss_pins_sync: mt6628-gnss-pins-sync { + pin-gps-sync { + pinmux = ; + bias-disable; + }; + }; + + /* + * GPIO128 is the board's active-low, level-triggered Wi-Fi wake + * interrupt (EINT9 in the vendor board configuration). It is used + * for out-of-band wakeup; normal SDIO interrupts remain in-band. + */ + mt6628_wifi_eint_pins: mt6628-wifi-eint-pins { + pin-wifi-eint { + pinmux = ; + input-enable; + bias-pull-up; + }; + }; + usb_pins: usb-pins { pins_iddig { pinmux = ; @@ -126,11 +249,68 @@ }; }; + /* + * Headset jack detect. EINT10 is NOT exclusive to the pad the jack + * switch is wired to: the pin controller exposes it on three of them, + * GPIO92 (mode 2), GPIO115 (mode 1) and GPIO120 (mode 2). + * + * mtk_xt_find_eint_num() resolves an EIC input to a pad by walking the + * pin table in order and taking the first pin whose eint_n matches and + * whose *current* mode equals that pin's EINT mux. Pins 92 and 120 are + * both examined before pin 115, so if either is left sitting in its + * EINT10 mux - by the bootloader, or by a pinctrl state that never got + * applied - the EIC line is routed to that pad instead and the jack + * edge is never seen. Nothing reports an error: the IRQ is registered, + * EINT_MASK and EINT_D0EN are set for the line, and only the status bit + * stays clear. + * + * So drive the two unused pads to plain GPIO explicitly rather than + * relying on whatever mode they happen to reset to. This is what makes + * the EINT10 assignment to GPIO115 deterministic; the pull-up and + * input-enable belong to the jack pad alone. + */ accdet_pins: accdet-pins { pin-accdet { pinmux = ; input-enable; - bias-pull-up; + /* + * No internal pull. The downstream driver disables it + * explicitly before arming the EINT: + * + * mt_set_gpio_mode(0x73, 1); // EINT10 + * mt_set_gpio_dir(0x73, 0); // input + * mt_set_gpio_pull_enable(0x73, 0); // pull OFF + * mt_eint_set_sens(10, 1); // level + * + * The jack switch is the pull: an internal pull-up holds + * the pad high against it, so the level the level- + * triggered EINT samples never crosses its threshold and + * EINT10 never fires. That is the observed symptom - + * mt-eint 10 stuck at the same count across boots. + */ + bias-disable; + }; + + /* Not the jack pad: keep these out of the EINT10 mux. */ + pin-accdet-eint10-gpio92 { + pinmux = ; + }; + + pin-accdet-eint10-gpio120 { + pinmux = ; + }; + }; + + /* MT6628 BGF UART: GPIO219 is URXD3 and GPIO220 is UTXD3. */ + mt6628_uart_pins: mt6628-uart-pins { + pin-uart-rx { + pinmux = ; + input-enable; + bias-disable; + }; + pin-uart-tx { + pinmux = ; + bias-disable; }; }; }; @@ -138,6 +318,56 @@ &pmic { pinctrl-names = "default"; pinctrl-0 = <&pmic_eint_pins>; + + /* + * MT6320 LED current sinks. + * + * Only the dedicated keypad/button backlight is declared, because + * that is the only LED sink whose presence on this board is + * actually established. See below for what is NOT known. + * + * The panel's own backlight is the display BLS PWM, declared + * separately as backlight_lcd above. + * + * The MT6320 has three further constant-current sinks, ISINK0..2, + * and the driver supports all three (children distinguished by + * "reg = <0..2>", plus per-channel "mediatek,num-steps"). They + * are deliberately left undeclared here, because no source + * available in this tree says which of them this board populates: + * + * - There is no Lenovo/MediaTek BSP for this board here. The + * only vendor MT6589 tree present is aquaris-5, which is the + * bq Aquaris 5 (projects "eastaeon89_wet_kk" and + * "eastaeon89_wet_td"), a different handset. Its cust_leds.c + * tables say nothing about this device and must not be read as + * if they did. + * - No pinctrl group in this board chain reserves an LED pin, and + * there is no LED GPIO either; these are PMIC sinks, so unlike + * the GPIOs there is no pinmux to corroborate them. + * - The MT6589 SoC datasheet does not cover the MT6320 PMIC; it + * contains no ISINK or pinout information for it. There is no + * MT6320 register manual or datasheet anywhere in this tree. + * + * So the reg -> colour mapping for ISINK0..2 is unverified. A + * wrong ISINK channel mapping does not merely add a dead sysfs + * entry, it labels every colour of a working notification LED + * wrongly, which is worse than leaving them absent. Add the three + * channels only once the board's own BSP or a hardware check + * establishes the mapping. + * + * Note also that the channel order is not RGB-sequential: on the + * Aquaris 5, whose cust_leds.c does populate two sinks, "red" is + * ISINK1 and "green" is ISINK0. So "reg = <0> is red" must not be + * assumed on any board. + */ + leds { + compatible = "mediatek,mt6320-led"; + + keypad-backlight { + label = "button-backlight"; + mediatek,is-kpled; + }; + }; }; &mt6320_accdet { @@ -148,6 +378,32 @@ io-channel-names = "key"; }; +/* + * Speaker amplifier enable. + * + * This board does not use the MT6320's own Class-D speaker output. Its + * speaker is driven by an external SN2145B amplifier that is switched on by + * GPIO73; the vendor kernel sets that pin (and configures it, together with + * GPIO142, as an output in Speaker_Init()) and then waits for the amp to warm + * up before letting audio through. Two independent sources agree: + * + * - the Blade KitKat kernel image (kernel_blade10_row_wifi-kk.elf): + * Speaker_Init() configures GPIO 0x49 (73) and 0x8e (142), + * Sound_Speaker_Turnon() drives 0x49 high and sleeps, and + * Sound_Speaker_Turnoff() drives it back low; + * - ProjectConfig.mk for this project sets + * CUSTOM_KERNEL_SOUND = amp_sn2145b + * and the working Linux port of the same board + * (kz-creator/lenovo-b8000h-linux, mt6589_audio.c) toggles GPIO73 with a + * 40 ms warm-up. + * + * GPIO142 drives a second, alternative amplifier path and is not needed for + * the speaker route, so only GPIO73 is wired here. + */ +&mt6320_codec { + amp-en-gpios = <&pio 73 GPIO_ACTIVE_HIGH>; +}; + &mt6320_ldo_vgp4 { regulator-min-microvolt = <2800000>; regulator-max-microvolt = <2800000>; @@ -222,12 +478,120 @@ status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&i2c1_pins>; + + /* + * Main camera autofocus actuator. Address and protocol come from the + * vendor driver (mediatek/custom/common/kernel/lens/fm50af/FM50AF.c: + * I2C_BOARD_INFO("FM50AF", 0x18)) and from the stock ROM's live I2C + * bus, where 1-0018 is bound to FM50AF. + */ + fm50af: lens@18 { + compatible = "mediatek,fm50af"; + reg = <0x18>; + }; + + /* + * Main camera sensor. + * + * Address: the vendor probe adopts the first of two write ids that + * answers, 0x6c then 0x6e + * (a5142mipi_Sensor.c:1047 and a5142mipi_Sensor.h:84-86), and 0x6c is + * the one this board uses. It does not collide with the FM50AF at + * 0x18 above. + * + * Bus: this is bus 1 because the A5142 sits on the same physical + * camera module as that FM50AF, and the vendor registers the lens on + * bus 1 (FM50AF.c:17 "#define LENS_I2C_BUSNUM 1", :504 + * i2c_register_board_info()). Note that the vendor's *sensor* bus + * numbering is not usable as evidence here: in the mt6589 override + * kd_sensorlist.c:27-28 it is 1 and 2, in the common one :26-27 it is + * 0 and only 0, and the A5142 branch (kd_camera_hw.c:932) never sets + * the bus at all, so the two trees contradict each other for the same + * sensor. + * + * Deliberately absent, because none of it can be derived yet: + * + * - clocks/clock-names. The vendor PLL tables are built for a + * 26 MHz MCLK (a5142mipi_Sensor.c:996) but no source states that + * the board's CAMTG actually produces 26 MHz; CLK_TOP_MUX_CAMTG + * (clk-mt6589-topckgen.c:377, parents at :238-245) can select + * 26 MHz, 208 MHz and three divided PLL rates, so the rate is a + * real choice and not a fixed property. Guessing it would set a + * wrong pixel clock. The driver refuses to probe without it. + * - vdd-supply / vddio-supply. The vendor camera power helper + * enables VCAMA (2.8 V), VGP2 as VCAMIO (1.8 V), VGP1 as VCAMD + * and VGP3 as VCAMAF (kd_camera_hw.h:13-16) but does not say + * which of them a5142_mipi_raw consumes; that mapping is per + * module. The driver requires both rails. + * - port/endpoint. mt6589-lenovo-blade-camera.dtsi has no MIPI + * CSI-2 D-PHY node (see the long note on &seninf), so there is no + * remote endpoint to point at yet. + * + * Reset and power-down are GPIO211 and GPIO212, now confirmed in the + * shipped kernel rather than only in the board DCT: kdCISModulePowerOn() + * (kernel_blade10_row_wifi-kk.elf @ c0454c40) copies its camera GPIO + * descriptor from the table at c07133e4, which holds two 8-int entries: + * + * camera 0 (main) GPIO211, GPIO212 mode 0, driven 0 then 1 + * camera 1 (sub) GPIO 32, GPIO 31 mode 0, driven 0 then 1 + * + * Both are plain GPIO outputs toggled low-then-high with 1-5 ms gaps + * between them; the order in the code is powerdown first, then reset. + * + * The driver does not model these lines yet, so they are still not + * listed here - the values are recorded so the binding can be written + * against something verified rather than inferred. + */ + a5142: camera@6c { + compatible = "onnn,a5142"; + reg = <0x6c>; + status = "disabled"; + }; }; &i2c2 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&i2c2_pins>; + + /* + * Sub camera sensor: NOT ENABLED, because its bus cannot be + * determined from the sources available here. + * + * What is known: + * + * - The OV9760 driver is the one the blade10 config selects: + * CUSTOM_KERNEL_SUB_IMGSENSOR = ov9760_mipi_raw + * (mediatek/config/blade10/ProjectConfig-jb2.mk:95), lens + * dummy_lens (:96), MTK_PLATFORM = MT6589 (:408). + * - The vendor driver tries write id 0x6c and then 0x20 + * (ov9760mipiraw_Sensor.c:199-206, ov9760mipiraw_Sensor.h:167-168) + * and uses whichever answers with id 0x9760. + * - The address collides with the A5142 at 0x6c, so the two cannot + * both be 0x6c on one segment. + * + * Why that is not enough to place the node: + * + * - The vendor's bus selection only ever switches to bus 2 for + * DUAL_CAMERA_MAIN_2_SENSOR, never for the sub sensor + * (kd_sensorlist.c:606/725/766/819/908 all test MAIN_2 only), so + * under this board's build main and sub are addressed on the same + * bus - which contradicts the 0x6c collision. + * - kd_camera_hw.c, the DCT GPIO/power tables and the lens list name + * no bus per socket. + * - The two kd_sensorlist.c variants in the tree disagree with each + * other (bus 1+2 vs bus 0 only), so neither can settle it. + * + * The plausible answers - 0x20 on i2c1, or 0x6c on i2c2 - are both + * guesses. Uncomment and fill in once the bus is known, e.g. from + * "/sys/bus/i2c/devices" on the stock firmware. + * + * camera@6c { + * compatible = "ovti,ov9760"; + * reg = <0x6c>; + * status = "disabled"; + * }; + */ }; &i2c3 { @@ -247,7 +611,13 @@ }; magnetometer@13 { - compatible = "bosch,bmm150"; + /* + * The stock ROM binds this address to its "bmm050" driver, and + * bmm050.h gives chip-id 0x40 for the part, against 0x32 for a + * BMM150 on the same register. Claiming bmm150 here would fail + * probe with -ENODEV once the driver compares the id. + */ + compatible = "bosch,bmm050"; reg = <0x13>; // TODO: check @@ -322,6 +692,99 @@ status = "okay"; }; +/* MT6628 shared Bluetooth/FM/GNSS transport selected by UART_RTS strap. */ +/* UART0 = UTXD3/URXD3 */ +&uart2 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&mt6628_uart_pins>; + /* Temporary diagnostic: test UART in PIO mode. */ + /delete-property/ dmas; + /delete-property/ dma-names; + + mt6628_stp_uart: mt6628-stp-uart { + compatible = "mediatek,mt6628-stp-uart"; + mediatek,combo-pwrseq = <&mt6628_pwrseq>; + mediatek,baudrate = <4000000>; + + /* + * WiFi/BT/FM antenna multiplexing mode and SDIO output drive + * strength. The value is MT6628 family-wide rather than + * per-board: it is the single antenna configuration the + * vendor ships for this combo chip, + * CUSTOM_HAL_ANT=mt6628_ant_m1, whose file + * custom/common/hal/ant/mt6628_ant_m1/mt6628_ant_m1.cfg + * contains + * + * coex_wmt_ant_mode=1 + * sdio_driving_cfg=0x00077777 + * + * and it is also the chip's own default antenna selection + * (mt6628.defAnt=mt6628_ant_m1.cfg in + * custom/common/hal/ant/WMT.cfg). Every MT6628 project config + * in the vendor tree selects m1; no other mode is used by any + * of them. The vendor driver passes both values straight + * through - coex_wmt_ant_mode becomes byte 5 of the WMT coex + * command (wmt_ic_6628.c:1323), and sdio_driving_cfg is split + * into the DAT0/1, DAT2/3 and CMD drive nibbles + * (wmt_ic_6628.c:1405-1411). mtk-stp.c encodes both the same + * way, in mt6628_wmt_coex_init() and + * mt6628_wmt_set_sdio_driving(). + * + * Without these the driver skips both commands and the chip + * keeps whatever it powered up with. + * + * Caveat worth stating plainly: the one project config that + * can be tied to a specific board in this tree is eastaeon89 + * (the bq Aquaris 5), and that board has no MT6628 node at + * all - this dtsi is included only by the Lenovo B6000 and + * B8000. So for these particular boards the values rest on + * "every MT6628 board the vendor ships uses m1", not on a + * verified per-board config. If a B6000 or B8000 antenna + * layout ever differs, this is the property to re-check. The + * numeric meaning of the antenna modes is not documented + * anywhere in the vendor tree. + */ + mediatek,coex-ant-mode = <1>; + mediatek,sdio-driving-cfg = <0x00077777>; + mediatek,fm-strap-mode = <2>; + + /* + * Deliberately absent, with reasons: + * + * - mediatek,crystal-trim is a per-board calibration value. + * The vendor reads it from NVRAM at offset 0x6D and its + * high bit says whether the trim is even enabled + * (wmt_ic_6628.c:1420-1470). There is no NVRAM in this + * tree and no calibration data for this board, so any + * value here would be invented. + * + * - mediatek,co-clock defaults to off because the vendor + * WMT.cfg for this board does not set co_clock_flag + * (wmt_lib.c:2039-2046 falls back to 0). Leaving the + * property out produces exactly that behaviour. + * + * - mediatek,fm-strap-mode has no counterpart in the vendor + * tree at all; the driver's default of 2 is what it has + * always used here. + */ + bluetooth { + compatible = "mediatek,mt6628-bt"; + }; + + fm { + compatible = "mediatek,mt6628-fm"; + }; + + gnss { + compatible = "mediatek,mt6628-gnss"; + pinctrl-names = "default", "gps-sync"; + pinctrl-0 = <&mt6628_gnss_pins_default>; + pinctrl-1 = <&mt6628_gnss_pins_sync>; + }; + }; +}; + /* Internal storage */ &mmc0 { status = "okay"; @@ -485,12 +948,25 @@ /* SDIO */ &mmc3 { - status = "disabled"; - pinctrl-names = "default", "state_uhs"; + status = "okay"; + pinctrl-names = "default", "state_uhs", "state_eint"; pinctrl-0 = <&mmc3_pins_default>; pinctrl-1 = <&mmc3_pins_uhs>; + pinctrl-2 = <&mt6628_wifi_eint_pins>; + interrupts-extended = <&sysirq GIC_SPI 42 IRQ_TYPE_LEVEL_LOW>, + <&pio 9 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "msdc", "sdio_wakeup"; /* FIXME: always 1.8V? */ - vmmc-supply = <&mt6320_ldo_vemc_3v3>; + //vmmc-supply = <&mt6320_ldo_vemc_3v3>; + vmmc-supply = <&mt6320_ldo_vemc_1v8>; + /* + * vqmmc names the same rail as vmmc, and must stay: mtk-sd's + * msdc_ops_switch_volt() only runs at all when vqmmc is present, and it + * is the only place that selects the "state_uhs" pin configuration and + * re-selects pins_uhs after an SDIO EINT wake. Dropping vqmmc to avoid + * the double enable would silently disable both. The double + * regulator_enable() is the lesser problem of the two. + */ vqmmc-supply = <&mt6320_ldo_vemc_1v8>; bus-width = <4>; @@ -498,12 +974,41 @@ non-removable; no-mmc; no-sd; - - max-frequency = <200000000>; + keep-power-in-suspend; + wakeup-source; + + max-frequency = <25000000>; +// max-frequency = <50000000>; + + /* + * The MT6628 combo chip needs its supply enabled before the host + * enumerates it. + * + * of_parse_phandle() ignores a node's status, so this may only name + * the pwrseq node above while that node is enabled: pointing at a + * disabled one leaves mmc_pwrseq_alloc() with a NULL pwrseq and defers + * this host forever, which is why the two changes have to land + * together. + */ + mmc-pwrseq = <&mt6628_pwrseq>; + +// max-frequency = <200000000>; cap-sd-highspeed; sd-uhs-sdr104; sd-uhs-sdr50; sd-uhs-ddr50; + + /* + * WLAN is carried by SDIO function 1. BGF/STP is routed to UART + * on these boards, so function 2 has no STP driver node. + * Boards routed to SDIO should describe their function-2 STP node + * in their own DTS instead. + */ + mt6628-wlan@1 { + reg = <1>; + mediatek,combo-pwrseq = <&mt6628_pwrseq>; + mediatek,stp = <&mt6628_stp_uart>; + }; }; &keyboard { @@ -521,4 +1026,30 @@ &bls { status = "okay"; + + /* + * Route the PWM out to GPIO129. The BLS block is usable without this + * (its registers are written and read back correctly), which is why + * the missing pinmux was not obvious: everything except the light + * worked. + */ + pinctrl-names = "default"; + pinctrl-0 = <&disp_pwm_pins>; +}; + +/* + * EMI bus monitor and DRAM controller diagnostics. + * + * Both are read-only apart from the EMI bus monitor's own enable bit, so + * turning them on cannot change the DRAM timing the bootloader calibrated. + * The EMI MPU is left unprogrammed: this node carries no + * mediatek,mpu-region property, so no protection region is written and the + * hardware keeps the bootloader's settings. + */ +&emi { + status = "okay"; +}; + +&dramc { + status = "okay"; }; diff --git a/arch/arm/boot/dts/mediatek/mt6589-vcodec.dtsi b/arch/arm/boot/dts/mediatek/mt6589-vcodec.dtsi new file mode 100644 index 00000000000000..5362452e4efbb6 --- /dev/null +++ b/arch/arm/boot/dts/mediatek/mt6589-vcodec.dtsi @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026 Akari Tsuyukusa + */ + +#include +#include +#include +#include +#include + +/* + * MediaTek MT6589 video codec: VENC and VDEC as two separate nodes. + * + * These are separate hardware blocks and are declared as two nodes because they + * really are two devices. The data sheet gives them separate chapters with + * separate register definitions (ch.60 "H.264/VP8 Video Encoder" plus ch.61 + * "MPEG-4 Video Encoder" against ch.59 "Video Decoder"), they sit at separate + * register bases, they have separate GIC lines (MT_VENC_IRQ_ID = SPI 136 against + * MT_VDEC_IRQ_ID = SPI 140), separate clock controllers (vencsys against + * vdecsys) and separate power domains (VEN against VDE). + * + * The reason this tree moved off one merged node is not tidiness. A device with + * more than one "power-domains" phandle gets no domain at all: + * genpd_dev_pm_attach() counts the phandles and returns without attaching + * anything unless there is exactly one (the count != 1 test in + * drivers/pmdomain/core.c). The merged node therefore probed successfully with no + * power domain, no clock isolation and no deferral - and the encoder's power-on + * path only takes a clock, it cannot raise MTCMOS, so 0x17002000 was an + * unisolated access to a dark domain. That node was disabled in commit + * f1b204d85ff6 ("disable a node that never gets its domains"), which turned off + * the H.264 encoder outright. One domain per node is what lets each half attach + * for real, and a domain that fails to come up then becomes a visible + * -EPROBE_DEFER rather than a silent success against unpowered hardware. + * + * Note the register bases differ from the clock nodes: vencsys is at 0x17000000 + * and vdecsys at 0x16000000, while the engines themselves are at 0x17002000 and + * 0x16020000 respectively. + * + * The vendor driver treats the decoder as two clocks from vdecsys (MT_CG_VDEC0_VDE + * and MT_CG_VDEC1_SMI) plus one from vencsys (MT_CG_VENC_VEN). + */ + +&soc { + venc: venc@17002000 { + compatible = "mediatek,mt6589-venc"; + /* + * VENC is 4 KiB at 0x17002000 (data sheet ch.60 and ch.61, both the + * H.264/VP8 and the MPEG-4 encoder, the latter starting at offset + * 0x600 inside the same window). + */ + reg = <0x17002000 0x1000>; + + /* + * VENC IRQ is MT_VENC_IRQ_ID = GIC_PRIVATE_SIGNALS(32) + 136 = SPI 136 + * (kernel/core/include/mach/mt_irq.h:101). + * + * The vendor driver registers it with IRQF_TRIGGER_LOW + * (videocodec_kernel_driver.c:2104), so the line is wired active low. + * Note this is the OPPOSITE polarity to the decoder's, which is + * IRQF_TRIGGER_HIGH (:2113); using one polarity for both would + * silently never fire on one of them. + */ + interrupts = ; + + /* + * clock-names is required rather than implied: vencsys and vdecsys + * each number their gates from zero, so the provider-local clock ids + * are not a unique key and index-based lookup cannot tell them apart. + */ + clocks = <&vencsys CLK_VENC_VEN>; + clock-names = "venc"; + + /* + * VENC lives in the VEN domain, which arrives powered off at reset and + * must be brought up by the SCM power sequence. One phandle, so + * genpd_dev_pm_attach() attaches it properly - see the note above. + * + * STATUS: this node is DISABLED, and the reasoning is the whole + * point of splitting the node, so it is worth stating exactly. + * + * Enabled is now genuinely an option that can be reasoned about + * rather than a shrug, because this node attaches its domain for + * real: a failed power-on becomes a visible -EPROBE_DEFER naming + * this device, instead of the silent probe-success-against-a-dark- + * domain the merged node had. So the choice is between a deferred + * probe that names the culprit, and no node at all. + * + * It is left off, and the evidence is the asymmetry between the two + * domains in drivers/pmdomain/mediatek/mt6589-pm-domains.h: + * + * VDE .sram_pdn_ack_bits = BIT(12) <- diagnosed, fixed + * VEN .sram_pdn_ack_bits = GENMASK(15, 12) <- undiagnosed + * + * VDE's power-on WAS understood and repaired: the stock driver waits + * on VDE_SRAM_ACK = (0x1 << 12) while waiting on VEN_SRAM_ACK = + * (0xf << 12), so polling all four VDE acknowledge bits spun until + * MTK_POLL_TIMEOUT on three bits the hardware never asserts, and + * genpd_power_on() then failed. Since scpsys_sram_enable() polls + * (ctl & sram_pdn_ack_bits) == 0, a narrower mask can only make the + * wait easier to satisfy, never harder - so that fix is safe in the + * direction that matters. + * + * VEN needed no width change - its four-bit poll is correct, and + * the vendor's own VEN_SRAM_ACK is (0xf << 12) too. What was wrong + * was the position: our power-on stopped at PWR_RST_B and deferred + * the SRAM release to scpsys_sram_enable(), which runs later and + * after bus protection is touched. The stock driver releases SRAM + * inline, right after PWR_RST_B (mt_spm_mtcmos.c:443-462), and + * scpsys_ctl_pwrseq_on() now does that for the domains marked + * MTK_SCPD_SRAM_PDN_INLINE, which is this one and VDE. + * + * That is unverified on hardware - it is a change to a power + * sequence, and there is no board here to characterise it - but it + * is the vendor's own ordering rather than an invention, and with + * no spare hardware a deferred probe is at least a visible failure + * rather than a silent one. + */ + power-domains = <&spm MT6589_POWER_DOMAIN_VEN>; + + /* + * The three larb0 ports the H.264 datapath DMAs through: source luma, + * source chroma and bitstream out. One larb only, which is required - + * mtk_iommu_v1_probe_device() rejects a device whose master ids span + * two larbs ("Can only use one larb"), which the merged node's five + * iommus did. + */ + iommus = <&iommu MT6589_M4U_PORT_VENC_CUR_LUMA>, + <&iommu MT6589_M4U_PORT_VENC_CUR_CHROMA>, + <&iommu MT6589_M4U_PORT_VENC_BSDMA>; + + }; + + vdec: vdec@16020000 { + compatible = "mediatek,mt6589-vdec"; + /* + * VDEC is 0x29000 at 0x16020000 - NOT 0x16000000, which is the vdecsys + * clock controller. 0x16020000 is mt_reg_base.h VDEC_BASE 0xF6020000 + * after the +0xE0000000 remap. The data sheet has no register-definition + * section for the decoder at all (ch.59 stops at the block diagram and + * the string "16000000" does not appear anywhere in it), so the offsets + * come from the vendor LDVT register-level test harness. + */ + reg = <0x16020000 0x29000>; + + /* + * VDEC IRQ is MT_VDEC_IRQ_ID = GIC_PRIVATE_SIGNALS(32) + 140 = SPI 140 + * (kernel/core/include/mach/mt_irq.h:103), and unlike VENC's it is + * asserted HIGH (videocodec_kernel_driver.c:2113). + */ + interrupts = ; + + clocks = <&vdecsys CLK_VDEC0_VDE>, + <&vdecsys CLK_VDEC1_SMI>; + clock-names = "vdec-vde", + "vdec-smi"; + + /* + * VDEC lives in the VDE domain. One phandle, so this one attaches for + * real too. + * + * STATUS: enabled, unlike its encoder sibling, and the asymmetry is + * evidence-based rather than optimistic. See the VEN comment above for + * the full comparison; the short version is in + * drivers/pmdomain/mediatek/mt6589-pm-domains.h: + * + * VDE .sram_pdn_ack_bits = BIT(12) <- diagnosed, fixed + * VEN .sram_pdn_ack_bits = GENMASK(15, 12) <- undiagnosed + * + * VDE's power-on failure was understood (polling three acknowledge + * bits the hardware never asserts, until timeout) and fixed, and the + * fix is safe in the only direction that matters because + * scpsys_sram_enable() polls for the bits to CLEAR. VEN has no + * equivalent fix. So this is the half whose domain can be expected to + * come up, and this is the half that is enabled. + * + * Note what enabling it does and does not buy. The node now probes, + * attaches its domain, registers and opens - and then refuses every + * buffer with -ENODEV, because the MT6589 decode sequence lives in + * closed userspace that programs the block over MFV_SET_CMD_CMD (see + * the Kconfig text and mtk-vdec-mt6589.c). A correctly attached + * resource layer is not a datapath, and this node does not pretend to + * be one. + */ + power-domains = <&spm MT6589_POWER_DOMAIN_VDE>; + + /* The two larb1 ports the decoder's output paths DMA through. */ + iommus = <&iommu MT6589_M4U_PORT_HW_VDEC_PP_EXT>, + <&iommu MT6589_M4U_PORT_HW_VDEC_VLD_EXT>; + + status = "okay"; + }; +}; \ No newline at end of file diff --git a/arch/arm/boot/dts/mediatek/mt6589.dtsi b/arch/arm/boot/dts/mediatek/mt6589.dtsi index d613d36a9569c7..ee867b4dce8ba5 100644 --- a/arch/arm/boot/dts/mediatek/mt6589.dtsi +++ b/arch/arm/boot/dts/mediatek/mt6589.dtsi @@ -21,6 +21,17 @@ compatible = "mediatek,mt6589"; interrupt-parent = <&sysirq>; + aliases { + bls0 = &bls; + color0 = &color; + tdshp0 = &tdshp; + ovl0 = &ovl; + rdma0 = &rdma0; + rdma1 = &rdma1; + mutex0 = &mutex; + dsi0 = &dsi; + }; + cpu_opp_table: opp-table-cpu { compatible = "operating-points-v2"; opp-shared; @@ -290,7 +301,7 @@ }; }; - soc { + soc: soc { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; @@ -336,6 +347,99 @@ #clock-cells = <1>; }; + /* + * MEMPLL, the DRAM PLL. Its registers are in the ddrphy_conf + * block at 0x1001_1600..0x1001_1630 (MEMPLL0..MEMPLL12) with + * MEMPLL_DIVIDER at 0x1001_1640 and MPLLPON0..3 at + * 0x1001_15C0, per datasheet 13.4.3 "DDRPHY Registers" + * (register list p. 708). It is NOT in apmixedsys, which is + * why the apmixedsys clock driver cannot provide it, and why + * this node is a separate clock provider rather than a + * child of &apmixedsys. + * + * Clocked from the 26 MHz crystal: MEMPLL1[12:11] + * RG_MEMPLL_REFCK_SEL = 00 selects "Xtal clock" (p. 730). + * No second reg entry for the DRAMC AO window is taken here + * because nothing in the MEMPLL register range needs it. + * + * Always enabled rather than status = "disabled", because + * topckgen's "clkph_mck" is parented to the "mempll" clock + * this node provides, and that clock must exist for the + * "mempll_mck_d4" divider below it to report a real rate. + * The driver is read-only, so enabling it cannot disturb the + * DRAM timing the bootloader calibrated. + */ + mempll: clock-controller@10011000 { + compatible = "mediatek,mt6589-mempll"; + reg = <0x10011000 0x1000>; + #clock-cells = <0>; + /* + * MEMPLL_MCK, the DRAM PLL output. The + * datasheet's own figure (p. 402: "MEMPLL_MCK_D4 + * (266.5/4)") and the reason the driver does not + * decode it from MEMPLL0 are in + * drivers/clk/mediatek/clk-mt6589-mempll.c. + */ + clock-frequency = <266500000>; + }; + + /* + * DRAM controller. Three windows, as the datasheet's DRAMC + * chapter lists them: DRAMC AO at 0x1000_4000 (timings, config, + * MRS), DRAMC NAO at 0x1020_F000 (always-on bank counters) and + * DDRPHY at 0x1001_1000 (pad and DLL controls). Downstream's + * mt_reg_base.h has the same three as DRAMC0_BASE, DRAMC_NAO_BASE + * and DDRPHY_BASE; its accessor ORs all three at one offset, + * which this driver does not do because it would report an + * unimplemented alias as a real value. + * + * The DDRPHY entry maps the same page as &mempll above but a + * different reg entry, so each driver owns its own mapping and + * neither claims the other's registers: this node reads the pad + * and DLL block below 0x600, &mempll reads 0x600..0x640. + * + * Read-only. There is no rate-change path, so no "operating + * points" are described here. + */ + dramc: memory-controller@10004000 { + compatible = "mediatek,mt6589-dramc"; + reg = <0x10004000 0x1000>, + <0x1020f000 0x1000>, + <0x10011000 0x1000>; + reg-names = "ao", "nao", "ddrphy"; + clocks = <&topckgen CLK_TOP_MEMPLL_MCK_D4>; + clock-names = "ddr"; + mediatek,emi = <&emi>; + status = "disabled"; + }; + + /* + * EMI: bus monitor, MPU and bandwidth limiter at 0x1020_3000. + * + * The violation interrupt is GIC SPI 94, the same line the + * &devapc node above uses. That is not a mistake and not a + * conflict: on MT6589 the device APC raises the bus-protection + * interrupt on behalf of the blocks that do not have their own, + * so DEVAPC, the SMI and the EMI MPU all report through it. + * Downstream requests it with IRQF_SHARED for exactly this + * reason (emi_mpu.c: "Interrupts of violation (including SPC in + * SMI, or EMI MPU) are triggered by the device APC"), and this + * driver does the same and returns IRQ_NONE when EMI_MPUS shows + * no MPU evidence. + * + * No mediatek,mpu-region property: the MPU is left exactly as + * the bootloader set it. Programming a region wrongly here + * stops the CPU reading its own memory, and the downstream + * region table needs the modem image memory map, which this + * board does not have. + */ + emi: memory-controller@10203000 { + compatible = "mediatek,mt6589-emi"; + reg = <0x10203000 0x1000>; + interrupts = ; + status = "disabled"; + }; + infracfg: syscon@10001000 { compatible = "mediatek,mt6589-infracfg", "syscon"; reg = <0x10001000 0x1000>; @@ -655,6 +759,7 @@ compatible = "mediatek,mt6589-mfgsys", "syscon"; reg = <0x10206000 0x1000>; #clock-cells = <1>; + #reset-cells = <1>; }; apmixedsys: syscon@10209000 { @@ -663,6 +768,62 @@ #clock-cells = <1>; }; + /* + * FHCTL sits in the perisys block at 0x11006000, not at + * 0xf1005000. The datasheet memory map lists FHCTL between + * IRDA (0x11005000) and UART0 (0x11007000); 0xf1005000 maps + * back to 0x11005000, which is IRDA. + * + * Pointing this at the wrong window is why hopping never + * worked: PLL_HP_CON0 happens to exist at the right offset in + * some other block, so hp_en read back plausibly, while every + * FHCTLx register read as zero and the DDS monitor never + * reached the new value - "FHCTL hopping timeout" on every + * attempt. The stock tree carries the same wrong constant + * (FHCTL_BASE 0xF1005000 in mt_fhreg.h), so it cannot be used + * as corroboration here. + * + * Two properties describe two different things and must not be + * read as one: + * + * mediatek,hopping-ssc-percent - how far to modulate the + * emitted clock around the nominal frequency. It says nothing + * about whether the FHCTL hop works on the channel. + * + * mediatek,fhctl-hopping-enabled - whether a rate change on + * that channel goes through the FHCTL hardware at all. It is + * listed explicitly for every channel here precisely so that + * the answer is on the record; only MSDCPLL is on. + * + * The reference implementation confirms the two are independent: + * it has a third state FH_FH_ENABLE_DFH, hopping with no SSC + * (mt_freqhopping.h:42-46), and lists all five channels as + * FH_PLL_ENABLE with "default SSC disable" + * (mt_freqhopping.c:87-92) - the hopping path is expected to + * exist for them but to be off by default. + * + * They are left off here because that is where measurement, not + * the reference code, decides. On ARMPLL, MAINPLL, TVDPLL and + * LVDSPLL the hop has been observed not to complete: every + * FHCTLx register read back zero and the DDS monitor never + * reached the new value, so each attempt cost about a second at + * boot. Turning them on needs a boot log to show a completed + * hop, not just a changed clock. Until then they reprogram the + * PCW field directly, which is what the data sheet describes for + * these non-SDM PLLs. + */ + fhctl: clock-controller@11006000 { + compatible = "mediatek,mt6589-fhctl"; + reg = <0x11006000 0x100>; + clocks = <&apmixedsys CLK_APMIXED_ARMPLL>, + <&apmixedsys CLK_APMIXED_MAINPLL>, + <&apmixedsys CLK_APMIXED_MSDCPLL>, + <&apmixedsys CLK_APMIXED_TVDPLL>, + <&apmixedsys CLK_APMIXED_LVDSPLL>; + mediatek,hopping-ssc-percent = <0>, <0>, <3>, <0>, <0>; + mediatek,fhctl-hopping-enabled = <0>, <0>, <1>, <0>, <0>; + }; + auxadc: adc@11003000 { compatible = "mediatek,mt6589-auxadc"; reg = <0x11003000 0x1000>; @@ -887,12 +1048,24 @@ <&mfgsys CLK_MFG_MEM>, <&mfgsys CLK_MFG_AXI>; clock-names = "core", "mem", "sys"; + power-domains = <&spm MT6589_POWER_DOMAIN_MFG>; }; dispsys: syscon@14000000 { compatible = "mediatek,mt6589-dispsys", "syscon"; reg = <0x14000000 0x1000>; #clock-cells = <1>; + #reset-cells = <1>; + + port { + #address-cells = <1>; + #size-cells = <0>; + + dispsys_ep_main: endpoint@0 { + reg = <0>; + remote-endpoint = <&ovl0_in>; + }; + }; }; bls: pwm@14008000 { @@ -901,8 +1074,63 @@ #pwm-cells = <2>; clocks = <&dispsys CLK_DISP0_BLS>; clock-names = "main"; + resets = <&dispsys MT6589_DISP_BLS_RST>; power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; status = "disabled"; + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + bls_in: endpoint { + remote-endpoint = <&color_out>; + }; + }; + + port@1 { + reg = <1>; + bls_out: endpoint { + remote-endpoint = <&rdma0_in>; + }; + }; + }; + }; + + color: color@1400a000 { + compatible = "mediatek,mt6589-disp-color"; + reg = <0x1400a000 0x1000>; + interrupts = ; + clocks = <&dispsys CLK_DISP0_COLOR>; + resets = <&dispsys MT6589_DISP_COLOR_RST>; + power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + color_in: endpoint { + remote-endpoint = <&ovl0_out>; + }; + }; + + port@1 { + reg = <1>; + color_out: endpoint { + remote-endpoint = <&bls_in>; + }; + }; + }; + }; + + tdshp: tdshp@1400b000 { + compatible = "mediatek,mt6589-disp-tdshp"; + reg = <0x1400b000 0x1000>; + interrupts = ; + clocks = <&dispsys CLK_DISP0_2DSHP>; + resets = <&dispsys MT6589_DISP_TDSHP_RST>; + power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; }; /* NAND flash controller */ @@ -985,11 +1213,34 @@ reset-names = "pwrap", "pwrap-bridge"; }; + /* + * MIPI DSI TX PHY and its DSI PLL. + * + * "clocks" here is the PLL's 26 MHz reference, which is + * clk26m - correct, and not a mistake. The DSI PLL is + * self-contained: DSI_PLL0_CON0 (10012050) has PREDIV, + * FBKDIV and TXDIV fields but no source-select mux, so it has + * exactly one input and no second candidate to pick. The + * driver's rate formula confirms it, since mt6589_pll_rate() is + * "26 * (fbk_div + 1) * fbk_sel * 2 / (txdiv0 * txdiv1 * + * pre_div)" MHz - the 26 being this reference clock. The + * datasheet's own power-on value for the register, + * DSI_PLL0_CON0 = 0xBE510026, decodes to the same + * divider set. + * + * mipi_tx0_pll below is the *output* of this PLL, not its + * source. The vblank/HS lane clock is taken from it through + * clock-output-names, and the DSI PLL must not be treated as + * its own parent. Every MediaTek SoC in this tree declares + * exactly the same single clk26m parent for its mipi-tx node + * (mt7623n.dtsi:248 and others), so devm_clk_get(dev, NULL) + * in the generic probe - which resolves index 0 when there is + * no clock-names - resolves correctly. + */ mipi_tx0: dsi-phy@10012000 { compatible = "mediatek,mt6589-mipi-tx"; reg = <0x10012000 0x90>; clocks = <&clk26m>; - /* <&dispsys CLK_DISP1_DSI_DIGITAL_LANE> ? */ clock-output-names = "mipi_tx0_pll"; #clock-cells = <0>; #phy-cells = <0>; @@ -1004,9 +1255,9 @@ reg = <0x10200100 0x1c>; }; - smi_common: smi@10202000 { + smi_common: smi@1000e000 { compatible = "mediatek,mt6589-smi-common"; - reg = <0x10202000 0x1000>, <0x1000e000 0x1000>; + reg = <0x1000e000 0x1000>, <0x10202000 0x1000>; clocks = <&infracfg CLK_INFRA_SMI>, <&infracfg CLK_INFRA_SMI>; clock-names = "apb", "smi"; @@ -1042,6 +1293,52 @@ <0x10216000 0x2000>; }; + /* + * AP_DMA: the general purpose half of the AP DMA block (data + * sheet chapter 18). It contains eighteen engines, of which + * only G_DMA0 and G_DMA1 are generic - the rest belong to HIF, + * IrDA, I2C and UART and are described by the "apdma" node + * below. These two sit at 0x11000080 and 0x11000100 and are + * the only engines with their own interrupt (GIC SPI 57/58, + * data sheet Table 7-1). + * + * The window is 0x180 bytes: the global control registers end + * at 0x78, and the block belonging to the next engine (HIF0) + * starts at 0x180. + * + * Note there is deliberately no iommus and no power-domains + * here. AP_DMA sits directly on the peripheral AXI fabric + * (data sheet p.869), so unlike the display engines it is not + * behind an M4U or a LARB. The datasheet's AP_DMA_PD is + * PERICFG PDN0 bit 12, which is the very same bit as the + * CLK_PERI0_APDMA clock gate below - it is a clock gate wearing + * a power-down name, not a separate power domain. + * + * The interrupts are declared at the polarity the data sheet + * gives (Table 7-1: both "Positive"), which contradicts the + * vendor driver (it uses IRQF_TRIGGER_LOW). That is unresolved + * and is spelled out in mtk-gdma.c; nothing here depends on it + * yet. + * + * status = "disabled" because nothing consumes these channels + * yet - no DMAENGINE client in the tree names them, and + * mtk-gdma.c at this stage only proves the clock, reset and + * interrupt wiring rather than moving data. Enabling the node + * would claim a working DMA controller where there is not one. + */ + gdma: dma-controller@11000000 { + compatible = "mediatek,mt6589-gdma"; + reg = <0x11000000 0x180>; + interrupts = , + ; + interrupt-names = "gdma0", "gdma1"; + clocks = <&pericfg CLK_PERI0_APDMA>; + clock-names = "main"; + resets = <&pericfg MT6589_PERI_DMA_SW_RS_RST>; + #dma-cells = <1>; + status = "disabled"; + }; + apdma: dma-controller@11000680 { compatible = "mediatek,mt6589-uart-dma", "mediatek,mt6577-uart-dma"; @@ -1220,6 +1517,316 @@ #size-cells = <0>; }; + ovl: ovl@14003000 { + compatible = "mediatek,mt6589-disp-ovl"; + reg = <0x14003000 0x1000>; + interrupts = ; + clocks = <&dispsys CLK_DISP0_OVL_ENGINE>, + <&dispsys CLK_DISP0_OVL_SMI>; + iommus = <&iommu MT6589_M4U_PORT_OVL_CH0>, + <&iommu MT6589_M4U_PORT_OVL_CH1>, + <&iommu MT6589_M4U_PORT_OVL_CH2>, + <&iommu MT6589_M4U_PORT_OVL_CH3>; + resets = <&dispsys MT6589_DISP_OVL_RST>; + power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + ovl0_in: endpoint { + remote-endpoint = <&dispsys_ep_main>; + }; + }; + + port@1 { + reg = <1>; + ovl0_out: endpoint { + remote-endpoint = <&color_in>; + }; + }; + }; + }; + + /* + * DISP_WDMA: the display subsystem's write-DMA engine. It takes + * a pixel stream from an upstream block and writes it out to + * memory - the counterpart of RDMA, which reads from memory. + * MT6589 has two instances, 4 KiB apart. + * + * Both are left status = "disabled" because neither has a + * consumer on this board, and the routing table having entries + * that mention WDMA is not the same thing as anything using + * them. mt6589-dispsys.h works the two cases out: + * + * - WDMA1's consumer is panel readback: the vendor routes + * OVL0 -> WDMA1 for memory-out (ddp_path.c:566, + * disp_path_config_mem_out()) and for suspend-mode screen + * capture (ddp_path.c:649). Both of those exist to feed a + * readback path, and the live route here is OVL0 -> BLS -> + * RDMA0 -> DSI0. Panel readback needs the DBI block + * (mt6589-dispsys.h:33 notes 0x1400c000), and this panel is + * DSI video mode, which does not use DBI - there is no DBI + * node on this SoC and no DBI driver here. + * + * - WDMA0's consumer is an SCL-to-memory leg that upstream + * marks as an unfinished HDMI feature ("FIXME: for hdmi + * temp", quoted at mt6589-dispsys.h:42-43). The other two + * WDMA0 routes, COLOR0 -> WDMA0 and TDSHP -> WDMA0, start + * from blocks that are themselves off this board's display + * path (see mtk_drm_drv.c, and commits c9e543f22025 and + * 2d59d9693be8 for why putting COLOR or TDSHP in a path + * array without a matching route silently disables the + * display rather than enabling anything). + * + * So there is nothing to enable. Adding either node would be + * a claim this board cannot back, and the register map itself + * is trustworthy (data sheet ch. 41 matches the vendor + * ddp_reg.h:442-474 exactly), so this is a missing driver, + * not uncertain hardware. Enabling one later needs the route + * table and a path array to agree with each other first. + * + * Note there are no clock-names here even though both blocks + * take two clocks. mtk_disp_wdma.c uses devm_clk_bulk_get_all() + * precisely because of that, matching mtk_disp_rdma.c. + */ + wdma0: wdma@14004000 { + compatible = "mediatek,mt6589-disp-wdma0"; + reg = <0x14004000 0x1000>; + interrupts = ; + clocks = <&dispsys CLK_DISP0_WDMA0_ENGINE>, + <&dispsys CLK_DISP0_WDMA0_SMI>; + iommus = <&iommu MT6589_M4U_PORT_WDMA0>; + resets = <&dispsys MT6589_DISP_WDMA0_RST>; + power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; + status = "disabled"; + }; + + wdma1: wdma@14005000 { + compatible = "mediatek,mt6589-disp-wdma1"; + reg = <0x14005000 0x1000>; + interrupts = ; + clocks = <&dispsys CLK_DISP0_WDMA1_ENGINE>, + <&dispsys CLK_DISP0_WDMA1_SMI>; + iommus = <&iommu MT6589_M4U_PORT_WDMA1>; + resets = <&dispsys MT6589_DISP_WDMA1_RST>; + power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; + status = "disabled"; + }; + + /* + * G2D: a 2D blitter in the display subsystem. RGB565/888, + * ARGB8888, constant-colour fill and bitblt. The hardware also + * has flips, alpha blending and dithering, which the driver + * does not yet use. + * + * The data sheet gives the base as 0x14013000, the interrupt + * as 0xb1 (177), and the two clock gates as CLK_DISP0_G2D_ENGINE + * and CLK_DISP0_G2D_SMI. + * + * "iommus" arms LARB2 port 11 (MT6589_M4U_PORT_G2D) and installs + * the M4U's DMA ops on this platform device. The port assignment + * is real - G2D is a DMA client with its own SRC/W2M/DST + * addresses - and the property is here because the driver gives + * G2D a DMA identity to match: G2D is its own drm_device + * (DRIVER_GEM | DRIVER_RENDER, no KMS), its dma_dev is this + * platform device rather than OVL, and it owns its own GEM + * namespace, importing display buffers through PRIME/dma-buf so + * each engine's address is mapped in the address space that + * engine reads. A GEM dma_addr allocated against this device is + * then an IOVA the engine can actually be handed, which is the + * whole reason the property exists. + * + * The ordering is the point and it is not negotiable: this must + * never be added to a driver still borrowing another device's + * addresses. Arming the port switches the engine to virtual + * addressing - mtk-smi.c programs SMI_SECUR_CON_VAL_VIRT for + * exactly those ports a domain attached - while a borrower would + * still be programming the physical addresses its host produced, + * and the engine would then read and write through a page table + * that does not describe its buffers. + * + * One link in this chain is not closed by reading the code: + * mtk-smi.c writes SMI_SECUR_CON_VAL_VIRT from + * mtk_smi_larb_resume(), which runs on the LARB's pm_runtime + * resume, while the port bit is armed by the IOMMU attaching this + * device. A port armed after the LARB's last resume would have + * its mmu bit set without a matching SECUR_CON write. That + * ordering is identical for every display client that already + * carries an "iommus" on this SoC (ovl, rdma0, rdma1), so this + * node introduces no new class of problem; it is written down + * because it is the part that can only be settled on hardware. + */ + g2d: g2d@14013000 { + compatible = "mediatek,mt6589-g2d"; + reg = <0x14013000 0x1000>; + interrupts = ; + clocks = <&dispsys CLK_DISP0_G2D_ENGINE>, + <&dispsys CLK_DISP0_G2D_SMI>; + clock-names = "g2d-engine", "g2d-smi"; + iommus = <&iommu MT6589_M4U_PORT_G2D>; + power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; + }; + + rdma0: rdma@14006000 { + compatible = "mediatek,mt6589-disp-rdma"; + reg = <0x14006000 0x1000>; + interrupts = ; + clocks = <&dispsys CLK_DISP0_RDMA0_ENGINE>, + <&dispsys CLK_DISP0_RDMA0_SMI>, + <&dispsys CLK_DISP0_RDMA0_OUTPUT>; + iommus = <&iommu MT6589_M4U_PORT_RDMA0>; + resets = <&dispsys MT6589_DISP_RDMA0_RST>; + power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + rdma0_in: endpoint { + remote-endpoint = <&bls_out>; + }; + }; + + port@1 { + reg = <1>; + rdma0_out: endpoint { + remote-endpoint = <&dsi_in>; + }; + }; + }; + }; + + rdma1: rdma@14007000 { + compatible = "mediatek,mt6589-disp-rdma"; + reg = <0x14007000 0x1000>; + interrupts = ; + clocks = <&dispsys CLK_DISP0_RDMA1_ENGINE>, + <&dispsys CLK_DISP0_RDMA1_SMI>, + <&dispsys CLK_DISP0_RDMA1_OUTPUT>; + iommus = <&iommu MT6589_M4U_PORT_RDMA1>; + resets = <&dispsys MT6589_DISP_RDMA1_RST>; + power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; + }; + + devapc: devapc@10010000 { + compatible = "mediatek,mt6589-devapc"; + /* + * Five DEVAPC instances, each an always-on (permission) window + * followed by a power-down (violation) window, 0x100 apart. + * Instance n uses reg entries 2n and 2n+1. + */ + reg = <0x10010000 0x100>, /* DEVAPC0 AO */ + <0x10207000 0x100>, /* DEVAPC0 PD */ + <0x10010100 0x100>, /* DEVAPC1 AO */ + <0x10207100 0x100>, /* DEVAPC1 PD */ + <0x10010200 0x100>, /* DEVAPC2 AO */ + <0x10207200 0x100>, /* DEVAPC2 PD */ + <0x10010300 0x100>, /* DEVAPC3 AO */ + <0x10207300 0x100>, /* DEVAPC3 PD */ + <0x10010400 0x100>, /* DEVAPC4 AO */ + <0x10207400 0x100>; /* DEVAPC4 PD */ + interrupts = ; + clocks = <&infracfg CLK_INFRA_DEVAPC>; + clock-names = "devapc-infra-clock"; + /* + * No power-domains property: DEVAPC is not part of + * any switchable SPM domain. + * + * Both register windows are listed in Table 3-2 + * "Infrastructure System Memory Map" - 0x1001_0000 is + * "Device APC AO" and 0x1020_7000 is "device_apc + * monitor module" - whereas the display blocks live + * in the separate multimedia map. The datasheet has + * no DEVAPC entry among the SPM *_PWR_CON registers + * (CPU, DBG, MD1, MD2, MFG, VDE, VEN, IFR, ISP, DIS, + * DPY, FC0-3) and no devapc bit in PWR_STATUS, so no + * power domain controls this block. + * + * That makes MT6589_POWER_DOMAIN_DIS not merely + * redundant but wrong: it is a real switchable domain + * (SPM_DIS_PWR_CON, display SRAM power-down in + * GENMASK(11,8)). Claiming it would tie the lifetime + * of a security-monitoring block to the display with + * no hardware relationship, and risks leaving DEVAPC + * without register access if DIS is powered down. + * Its only gate is the infracfg clock above, which is + * what actually has to be enabled before any register + * is touched. + * + * Upstream agrees: the MT6779 devapc node also carries + * no power-domains property. + */ + }; + + /* + * DISP_MUTEX: six handshake registers arbitrating access to the + * shadow registers, per the data sheet's DISP_MUTEX chapter + * (ch. 52, p. 1897: "six independent mutex controllers for + * maximum three independent linked paths"). + * + * No clocks property, and that is deliberate - there is no gate + * to give it. The DISP0 gate register CG_CON0 (CG0) names 22 + * gates between bits 3 and 24 - SCL, OVL, color, 2DSHP, BLS, + * WDMA0/1, RDMA0/1, gamma, command queue and G2D - with the + * rest reserved, and none of them is a mutex. Nothing in + * drivers/clk/mediatek/ defines an MT6589 mutex gate either, + * unlike the SoCs that do have one (CLK_MM_MUTEX on MT2701, + * CLK_MDP_MUTEX0 on MT8186, and so on). The block therefore + * shares the display subsystem's own gating and lives in the + * DIS power domain below. + * + * The previous "clocks = <&clk26m>" was a placeholder - the + * comment beside it admitted "not found in clock list" - and + * taking clk26m told the driver to pin an unrelated + * always-on oscillator, which states nothing true about how this + * block is clocked. Better to declare no clock than a wrong one. + * + * interrupts is GIC_SPI 160, which the data sheet's interrupt + * table (p. 417) names Disp_mutex_irq_b, active low - matching + * the negative polarity and the GIC_SPI 160 declared here. + */ + mutex: mutex@14011000 { + compatible = "mediatek,mt6589-disp-mutex"; + reg = <0x14011000 0x1000>; + interrupts = ; + power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; + }; + + dsi: dsi@1400d000 { + compatible = "mediatek,mt6589-dsi"; + reg = <0x1400d000 0x1000>; + interrupts = ; + clocks = <&dispsys CLK_DISP1_DSI_ENGINE>, + <&dispsys CLK_DISP1_DSI_DIGITAL>, + <&mipi_tx0>, + <&dispsys CLK_DISP1_DSI_DIGITAL_LANE>; + clock-names = "engine", "digital", "hs", "lane"; + phys = <&mipi_tx0>; + phy-names = "dphy"; + resets = <&dispsys MT6589_DISP_DSI_RST>; + power-domains = <&spm MT6589_POWER_DOMAIN_DIS>; + status = "disabled"; + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dsi_in: endpoint { + remote-endpoint = <&rdma0_out>; + }; + }; + + port@1 { + reg = <1>; + dsi_out: endpoint { }; + }; + }; + }; + larb0: larb@17001000 { compatible = "mediatek,mt6589-smi-larb"; reg = <0x17001000 0x1000>; @@ -1437,12 +2044,14 @@ , ; input-enable; + input-schmitt-enable; drive-strength = <4>; mediatek,pull-up-adv = <2>; }; pins-clk { pinmux = ; + input-schmitt-enable; mediatek,pull-up-adv = <2>; drive-strength = <4>; }; @@ -1456,11 +2065,13 @@ , ; input-enable; + input-schmitt-enable; drive-strength = <4>; }; pins-clk { pinmux = ; + input-schmitt-enable; drive-strength = <4>; }; }; diff --git a/arch/arm/configs/lenovo-blade_defconfig b/arch/arm/configs/lenovo-blade_defconfig index aab6364882dabc..aa07a4bbc0a900 100644 --- a/arch/arm/configs/lenovo-blade_defconfig +++ b/arch/arm/configs/lenovo-blade_defconfig @@ -25,6 +25,7 @@ CONFIG_MTK_INFRACFG=y ## CPU CONFIG_CPU_IDLE=y CONFIG_ARM_CPUIDLE=y +CONFIG_MTK_DCM=y CONFIG_CPU_FREQ=y CONFIG_ARM_MEDIATEK_CPUFREQ=y @@ -36,12 +37,33 @@ CONFIG_MTK_IOMMU_V1=y CONFIG_MEMORY=y CONFIG_MTK_SMI=y +## DRAM controller and EMI +CONFIG_MTK_MT6589_DRAMC=y +CONFIG_MTK_MT6589_EMI=y + ## Display CONFIG_DRM=y CONFIG_DRM_SIMPLEDRM=y CONFIG_DRM_MEDIATEK=y CONFIG_DRM_PANEL=y CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_MTK_DEVAPC=y +CONFIG_DMADEVICES=y +CONFIG_MTK_GDMA=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_BACKLIGHT_PWM=y +CONFIG_DRM_MEDIATEK_WDMA=y +CONFIG_LEDS_MT6320=y +CONFIG_VIDEO_MEDIATEK_VCODEC_VENC_MT6589=y +CONFIG_VIDEO_MEDIATEK_VCODEC_VDEC_MT6589=y +CONFIG_VIDEO_MTK_CSI2_RX=y +CONFIG_VIDEO_MTK_SCAM=y +CONFIG_VIDEO_MTK_CAM=y +CONFIG_PHY_MEDIATEK_MIPI_CSI_DPHY=y + +CONFIG_LEDS_MT6320_KPLED=y +CONFIG_INPUT_REGULATOR_HAPTIC=y CONFIG_DRM_PANEL_SIMPLE=y CONFIG_PHY_MTK_MIPI_DSI=y CONFIG_MTK_MMSYS=y @@ -60,6 +82,7 @@ CONFIG_COMMON_CLK_MT6589_IMGSYS=y CONFIG_COMMON_CLK_MT6589_MFGSYS=y CONFIG_COMMON_CLK_MT6589_VDECSYS=y CONFIG_COMMON_CLK_MT6589_VENCSYS=y +CONFIG_COMMON_CLK_MT6589_MEMPLL=y CONFIG_COMMON_CLK_MT6320=y ## Timer @@ -76,6 +99,8 @@ CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250_MT6577=y CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_DEV_BUS=y +CONFIG_SERIAL_DEV_CTRL_TTYPORT=y CONFIG_MTK_UART_APDMA=y ## I2C @@ -122,6 +147,7 @@ CONFIG_U_SERIAL_CONSOLE=y ## MMC (eMMC/SD) CONFIG_MMC=y CONFIG_MMC_MTK=y +CONFIG_PWRSEQ_MT6628=y CONFIG_PARTITION_ADVANCED=y CONFIG_OF_PARTITION=y @@ -194,6 +220,7 @@ CONFIG_MEDIA_SUPPORT=y CONFIG_MEDIA_PLATFORM_SUPPORT=y CONFIG_VIDEO_DEV=y CONFIG_V4L_MEM2MEM_DRIVERS=y +CONFIG_VIDEO_MEDIATEK_G2D=y CONFIG_VIDEO_MEDIATEK_JPEG=y ## GPU @@ -207,7 +234,25 @@ CONFIG_UNIX=y CONFIG_NETDEVICES=y CONFIG_PACKET=y -## Wi-Fi / Bluetooth +## MT6628 +CONFIG_MTK_MT6628_STP=y +CONFIG_MTK_MT6628_STP_SDIO=y +CONFIG_MTK_MT6628_STP_UART=y +### Wi-Fi +CONFIG_CFG80211=y +CONFIG_WLAN_VENDOR_MEDIATEK_MT6628=y +CONFIG_MTK_MT6628_WLAN=y +### GNSS +CONFIG_GNSS=y +CONFIG_GNSS_MT6628=y +### FM +CONFIG_MTK_MT6628_FM=y +### Bluetooth +CONFIG_BT=y +CONFIG_BT_MT6628_STP=y + +CONFIG_MEDIA_RADIO_SUPPORT=y +CONFIG_RADIO_ADAPTERS=y ## Thermal CONFIG_THERMAL=y @@ -283,3 +328,28 @@ CONFIG_DEVTMPFS=y CONFIG_SYSVIPC=y CONFIG_TMPFS_POSIX_ACL=y CONFIG_CGROUPS=y + +### hwmon +CONFIG_HWMON=y +CONFIG_SENSORS_IIO_HWMON=y +CONFIG_VIDEO_FM50AF=m +CONFIG_VIDEO_A5142=m +CONFIG_VIDEO_OV9760=m +CONFIG_MEDIA_CAMERA_SUPPORT=y +CONFIG_DRM_PANEL_LENOVO_B6000=m + +### Early console for emulator bring-up +# Without this the kernel reaches dump_machine_table's `b .` with no output at +# all, which is indistinguishable from a hang. DEBUG_LL gives printascii before +# the real console exists; the generic 8250 driver is used because +# mach-mediatek has no debug-macro.S of its own. +# CONFIG_DEBUG_KERNEL/EXPERT are prerequisites of DEBUG_LL, hence both. +CONFIG_EXPERT=y +CONFIG_DEBUG_KERNEL=y +CONFIG_DEBUG_LL=y +CONFIG_EARLY_PRINTK=y +CONFIG_DEBUG_UART_8250=y +CONFIG_DEBUG_UART_PHYS=0x11006000 +CONFIG_DEBUG_UART_VIRT=0xf1006000 +CONFIG_DEBUG_UART_8250_SHIFT=2 +CONFIG_DEBUG_UART_8250_WORD=y diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index c5d45cf91f8827..e6c15435125063 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -438,6 +438,16 @@ config BT_MTKSDIO Say Y here to compile support for MediaTek Bluetooth SDIO devices into the kernel or say M to compile it as module (btmtksdio). +config BT_MT6628_STP + tristate "MediaTek MT6628 Bluetooth over STP" + depends on MTK_MT6628_STP + help + Bluetooth HCI transport for the MT6628 shared STP function, over + the UART or SDIO backend selected for the board. + + This driver uses the shared BGF/STP transport and cannot use the + generic MediaTek SDIO Bluetooth driver. + config BT_MTKUART tristate "MediaTek HCI UART driver" depends on BT_HCIUART diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 81856512ddd030..5050c0acee40e3 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_BT_ATH3K) += ath3k.o obj-$(CONFIG_BT_MRVL) += btmrvl.o obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o obj-$(CONFIG_BT_MTKSDIO) += btmtksdio.o +obj-$(CONFIG_BT_MT6628_STP) += mt6628-bt.o obj-$(CONFIG_BT_MTKUART) += btmtkuart.o obj-$(CONFIG_BT_QCOMSMD) += btqcomsmd.o obj-$(CONFIG_BT_BCM) += btbcm.o diff --git a/drivers/bluetooth/btmtk.h b/drivers/bluetooth/btmtk.h index c564aedc0ce0f8..462a2dbd9cb419 100644 --- a/drivers/bluetooth/btmtk.h +++ b/drivers/bluetooth/btmtk.h @@ -3,6 +3,7 @@ #define FIRMWARE_MT7622 "mediatek/mt7622pr2h.bin" #define FIRMWARE_MT7663 "mediatek/mt7663pr2h.bin" +#define FIRMWARE_MT6628 "mediatek/mt6628pr2h.bin" #define FIRMWARE_MT7668 "mediatek/mt7668pr2h.bin" #define FIRMWARE_MT7922 "mediatek/BT_RAM_CODE_MT7922_1_1_hdr.bin" #define FIRMWARE_MT7902 "mediatek/BT_RAM_CODE_MT7902_1_1_hdr.bin" diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index 5b0fab7b89b5a5..f054bc2ec4d8f7 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -1372,6 +1372,16 @@ static int btmtksdio_probe(struct sdio_func *func, if (!bdev->data) return -ENODEV; + /* + * The combo chip exposes several SDIO functions with identical + * vendor/device ids. The BT/WMT control function is number 2 on + * the MT6628 generation (function 1 is the WLAN data path); the + * newer chips only implement one function. Skip anything that is + * not ours so that the WLAN driver can claim its own function. + */ + if (bdev->data->chipid == 0x6628 && func->num != 2) + return -ENODEV; + bdev->dev = &func->dev; bdev->func = func; diff --git a/drivers/bluetooth/mt6628-bt.c b/drivers/bluetooth/mt6628-bt.c new file mode 100644 index 00000000000000..69d10a6398db27 --- /dev/null +++ b/drivers/bluetooth/mt6628-bt.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MediaTek MT6628 Bluetooth over shared STP transport + */ + +#include +#include +#include +#include +#include + +#include +#include + +struct mt6628_bt { + struct mt6628_wmt *wmt; + struct hci_dev *hdev; +}; + +static int mt6628_bt_vendor_cmd(struct hci_dev *hdev, u16 opcode, + const void *param, u32 plen) +{ + int ret; + + ret = __hci_cmd_sync_status(hdev, opcode, plen, param, 1000); + if (ret < 0) + return ret; + if (ret) { + bt_dev_err(hdev, "vendor opcode %#.4x failed, status %#x", + opcode, ret); + return -EIO; + } + + return 0; +} + +static int mt6628_bt_setup(struct hci_dev *hdev) +{ + static const u8 link_key_type[] = { 0x01 }; + static const u8 unit_key[] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + static const u8 encryption[] = { 0x00, 0x02, 0x10 }; + static const u8 pin_code_type[] = { 0x00 }; + static const u8 voice[] = { 0x60, 0x00 }; + static const u8 pcm[] = { 0x63, 0x10, 0x00, 0x00 }; + static const u8 radio[] = { + 0x07, 0x80, 0x00, 0x06, 0x05, 0x07, + }; + static const u8 tx_power_offset[] = { + 0xff, 0xff, 0xff, + }; + static const u8 sleep_timeout[] = { + 0x03, 0x40, 0x1f, 0x40, 0x1f, 0x00, 0x04, + }; + static const u8 bt_ftr[] = { 0x80, 0x00 }; + static const u8 osc_info[] = { + 0x01, 0x01, 0x14, 0x0a, 0x08, + }; + static const u8 lpo_info[] = { + 0x01, 0xfa, 0x0a, 0x02, 0x00, + 0xa6, 0x0e, 0x00, 0x40, 0x00, + }; + static const u8 pta[] = { + 0xc9, 0x8b, 0xbf, 0x00, 0x00, + 0x52, 0x0e, 0x0e, 0x1f, 0x1b, + }; + int ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfc1b, + link_key_type, sizeof(link_key_type)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfc75, + unit_key, sizeof(unit_key)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfc76, + encryption, sizeof(encryption)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0x0c0a, + pin_code_type, sizeof(pin_code_type)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0x0c26, + voice, sizeof(voice)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfc72, + pcm, sizeof(pcm)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfc79, + radio, sizeof(radio)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfc93, + tx_power_offset, sizeof(tx_power_offset)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfc7a, + sleep_timeout, sizeof(sleep_timeout)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfc7d, + bt_ftr, sizeof(bt_ftr)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfc7b, + osc_info, sizeof(osc_info)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfc7c, + lpo_info, sizeof(lpo_info)); + if (ret) + return ret; + + return mt6628_bt_vendor_cmd(hdev, 0xfc74, + pta, sizeof(pta)); +} + +static int mt6628_bt_post_init(struct hci_dev *hdev) +{ + static const u8 ble_pta[] = { + 0x16, 0x0e, 0x0e, 0x00, 0x07, + }; + static const u8 internal_pta_1[] = { + 0x00, 0x01, 0x0f, 0x0f, 0x01, + 0x0f, 0x0f, 0x01, 0x0f, 0x0f, + 0x01, 0x0f, 0x0f, 0x02, 0x01, + }; + static const u8 internal_pta_2[] = { + 0x01, 0x19, 0x19, 0x07, 0xd0, 0x00, 0x01, + }; + static const u8 rf_reg_100[] = { + 0x64, 0x01, 0x02, 0x00, 0x00, 0x00, + }; + int ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfcfc, + ble_pta, sizeof(ble_pta)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfcfb, + internal_pta_1, sizeof(internal_pta_1)); + if (ret) + return ret; + + ret = mt6628_bt_vendor_cmd(hdev, 0xfcfb, + internal_pta_2, sizeof(internal_pta_2)); + if (ret) + return ret; + + return mt6628_bt_vendor_cmd(hdev, 0xfcb0, + rf_reg_100, sizeof(rf_reg_100)); +} + +static int mt6628_bt_open(struct hci_dev *hdev) +{ + struct mt6628_bt *bt = hci_get_drvdata(hdev); + + return mt6628_wmt_func_ctrl(bt->wmt, MT6628_WMT_FUNC_BT, true); +} + +static int mt6628_bt_close(struct hci_dev *hdev) +{ + struct mt6628_bt *bt = hci_get_drvdata(hdev); + + return mt6628_wmt_func_ctrl(bt->wmt, MT6628_WMT_FUNC_BT, false); +} + +static int mt6628_bt_flush(struct hci_dev *hdev) +{ + return 0; +} + +static int mt6628_bt_send_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct mt6628_bt *bt = hci_get_drvdata(hdev); + u8 *buf; + int ret; + + switch (hci_skb_pkt_type(skb)) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + if (skb->len >= 4 && get_unaligned_le16(skb->data) == 0x1004) + bt_dev_dbg(hdev, + "HCI TX Read Local Extended Features page=%u", + skb->data[3]); + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + case HCI_ISODATA_PKT: + break; + default: + return -EILSEQ; + } + + buf = kmalloc(skb->len + 1, GFP_ATOMIC); + if (!buf) + return -ENOMEM; + + buf[0] = hci_skb_pkt_type(skb); + memcpy(buf + 1, skb->data, skb->len); + + ret = mt6628_stp_send(bt->wmt, MT6628_STP_TASK_BT, + buf, skb->len + 1); + kfree(buf); + if (ret < 0) { + hdev->stat.err_tx++; + return ret; + } + + hdev->stat.byte_tx += skb->len; + kfree_skb(skb); + return 0; +} + +static void mt6628_bt_rx(void *priv, const u8 *buf, size_t len) +{ + struct mt6628_bt *bt = priv; + struct sk_buff *skb; + u8 type; + + if (len < 1) { + /* Not even a packet type byte: nothing can be handed on. */ + bt->hdev->stat.err_rx++; + return; + } + + type = buf[0]; + /* HCI Command Complete for Read Local Extended Features. */ + if (type == HCI_EVENT_PKT && len >= 9 && buf[1] == 0x0e && + get_unaligned_le16(buf + 4) == 0x1004) + bt_dev_dbg(bt->hdev, + "HCI RX Read Local Extended Features status=%#x page=%u max_page=%u len=%zu", + buf[6], buf[7], buf[8], len - 1); + + switch (type) { + case HCI_EVENT_PKT: + case HCI_ACLDATA_PKT: + case HCI_SCODATA_PKT: + case HCI_ISODATA_PKT: + break; + default: + bt->hdev->stat.err_rx++; + return; + } + + skb = bt_skb_alloc(len - 1, GFP_KERNEL); + if (!skb) { + bt->hdev->stat.err_rx++; + return; + } + + skb_put_data(skb, buf + 1, len - 1); + hci_skb_pkt_type(skb) = type; + bt->hdev->stat.byte_rx += len - 1; + + /* + * hci_recv_frame() takes ownership of the skb unconditionally and + * frees it on each of its error returns (net/bluetooth/hci_core.c + * :2887 and :2921), so there is nothing left to free here and no + * driver-side rejection to account for. A negative return means + * the core refused the frame (device not up, or a packet type it + * will not accept), which is not this driver's rx error, so + * err_rx is not incremented for it. + */ + hci_recv_frame(bt->hdev, skb); +} + +static int mt6628_bt_probe(struct platform_device *pdev) +{ + struct mt6628_bt *bt; + struct hci_dev *hdev; + enum mt6628_stp_hif hif; + int ret; + + bt = devm_kzalloc(&pdev->dev, sizeof(*bt), GFP_KERNEL); + if (!bt) + return -ENOMEM; + + bt->wmt = dev_get_drvdata(pdev->dev.parent); + if (!bt->wmt) + return -EPROBE_DEFER; + + ret = mt6628_stp_get_hif(bt->wmt, &hif); + if (ret) + return ret; + + hdev = hci_alloc_dev(); + if (!hdev) + return -ENOMEM; + + bt->hdev = hdev; + hdev->bus = hif == MT6628_STP_HIF_UART ? HCI_UART : HCI_SDIO; + hdev->open = mt6628_bt_open; + hdev->close = mt6628_bt_close; + hdev->flush = mt6628_bt_flush; + hdev->send = mt6628_bt_send_frame; + hdev->setup = mt6628_bt_setup; + hdev->post_init = mt6628_bt_post_init; + SET_HCIDEV_DEV(hdev, &pdev->dev); + hci_set_drvdata(hdev, bt); + + ret = mt6628_stp_register_rx(bt->wmt, MT6628_STP_TASK_BT, + mt6628_bt_rx, bt); + if (ret) + goto err_free_hdev; + + ret = hci_register_dev(hdev); + if (ret < 0) + goto err_unregister_rx; + + platform_set_drvdata(pdev, bt); + return 0; + +err_unregister_rx: + mt6628_stp_unregister_rx(bt->wmt, MT6628_STP_TASK_BT, + mt6628_bt_rx, bt); +err_free_hdev: + hci_free_dev(hdev); + return ret; +} + +static void mt6628_bt_remove(struct platform_device *pdev) +{ + struct mt6628_bt *bt = platform_get_drvdata(pdev); + + if (!bt) + return; + + /* + * Drop the STP RX callback before the hci_dev goes away. + * + * mt6628_stp_unregister_rx() takes wmt->rx_lock + * (drivers/net/wireless/mediatek/mt6628/mtk-stp.c:740), which is + * the lock mt6628_bt_rx() itself is dispatched under (mtk-stp.c + * :451-456), so once it returns no frame is in flight and none can + * start: bt->hdev cannot be touched again. + * + * hci_unregister_dev() must therefore come second. It ends with + * device_del() plus hci_dev_put() (net/bluetooth/hci_core.c + * :2700-2702); the last put_device() then runs the bt_host release + * method, which calls hci_release_dev() and finally kfree(hdev) + * (net/bluetooth/hci_sysfs.c:82-92, net/bluetooth/hci_core.c + * :2707 and :2740). The reverse order would leave the callback + * registered against a freed hci_dev. + * + * remove() runs from the MFD teardown of the parent transport + * (mtk-stp.c:1640-1647), not from a STP RX callback, so it does + * not already hold rx_lock. + */ + mt6628_stp_unregister_rx(bt->wmt, MT6628_STP_TASK_BT, + mt6628_bt_rx, bt); + hci_unregister_dev(bt->hdev); + hci_free_dev(bt->hdev); +} + +static struct platform_driver mt6628_bt_driver = { + .probe = mt6628_bt_probe, + .remove = mt6628_bt_remove, + .driver = { + .name = "mt6628-bt", + }, +}; +module_platform_driver(mt6628_bt_driver); + +MODULE_AUTHOR("Akari Tsuyukusa "); +MODULE_DESCRIPTION("MediaTek MT6628 Bluetooth over STP"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/mediatek/Kconfig b/drivers/clk/mediatek/Kconfig index 039e398661ea32..dd84d6858029fb 100644 --- a/drivers/clk/mediatek/Kconfig +++ b/drivers/clk/mediatek/Kconfig @@ -135,6 +135,7 @@ config COMMON_CLK_MT6589 tristate "Main clock drivers for MediaTek MT6589" depends on ARCH_MEDIATEK || COMPILE_TEST select COMMON_CLK_MEDIATEK + select COMMON_CLK_MEDIATEK_FHCTL help This enables drivers for main clocks (apmixedsys, topckgen, pericfg, infracfg) on the MediaTek MT6589 SoC. @@ -181,6 +182,24 @@ config COMMON_CLK_MT6589_VENCSYS This enables a driver for clocks provided by vencsys on the MediaTek MT6589 SoC. +config COMMON_CLK_MT6589_MEMPLL + bool "MediaTek MT6589 MEMPLL clock" + depends on COMMON_CLK_MT6589 + help + Say yes here to expose the MT6589 MEMPLL (the DRAM PLL) as a + clock, so that the topckgen "clkph_mck" and the "mempll_mck_d4" + divider derived from it report a real rate instead of the 0 Hz + of the clk_null placeholder they are currently parented to. + + The MEMPLL registers live in the ddrphy_conf block at + 0x1001_1600, not in apmixedsys, so this is a separate driver + from clk-mt6589-apmixedsys.c. It is read-only: it reports the + rate the board declares with "clock-frequency" (266.5 MHz + by default, which is the datasheet's figure for this clock) and + the enable state, and never reprograms the PLL, because the DRAM + timing the bootloader calibrated was derived from the value the + bootloader programmed. + config COMMON_CLK_MT6735 tristate "Main clock drivers for MediaTek MT6735" depends on ARCH_MEDIATEK || COMPILE_TEST diff --git a/drivers/clk/mediatek/Makefile b/drivers/clk/mediatek/Makefile index 1b66e39f638f30..4773940199e89b 100644 --- a/drivers/clk/mediatek/Makefile +++ b/drivers/clk/mediatek/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_COMMON_CLK_MT6589_IMGSYS) += clk-mt6589-img.o obj-$(CONFIG_COMMON_CLK_MT6589_MFGSYS) += clk-mt6589-mfg.o obj-$(CONFIG_COMMON_CLK_MT6589_VDECSYS) += clk-mt6589-vdec.o obj-$(CONFIG_COMMON_CLK_MT6589_VENCSYS) += clk-mt6589-venc.o +obj-$(CONFIG_COMMON_CLK_MT6589_MEMPLL) += clk-mt6589-mempll.o obj-$(CONFIG_COMMON_CLK_MT6735) += clk-mt6735-apmixedsys.o clk-mt6735-infracfg.o clk-mt6735-pericfg.o clk-mt6735-topckgen.o obj-$(CONFIG_COMMON_CLK_MT6735_IMGSYS) += clk-mt6735-imgsys.o obj-$(CONFIG_COMMON_CLK_MT6735_MFGCFG) += clk-mt6735-mfgcfg.o diff --git a/drivers/clk/mediatek/clk-fhctl.c b/drivers/clk/mediatek/clk-fhctl.c index 33b6ad8fdc2efa..b5e4f5fe78766f 100644 --- a/drivers/clk/mediatek/clk-fhctl.c +++ b/drivers/clk/mediatek/clk-fhctl.c @@ -40,6 +40,35 @@ static const struct fhctl_offset fhctl_offset_v2 = { .offset_mon = 0x10, }; +/* + * MT6589: the per-channel registers are laid out as FHCTLx_CFG + * (0x4c + i * 0x10), UPDNLMT, DDS, MON with no DVFS register; hopping + * is triggered through DDS bit 31. The global "handoff permission" + * register is PLL_HP_CON0 at apmixed offset 0x14 and the two slope + * values live in the hopping SRAM at 0x20 / 0x24. + * + * The channel registers are addressed through fhx_base, which is already + * the channel's own base (fhx_offset is 0x4c for FHCTL0, 0x5c for FHCTL1, + * and so on, matching OFFSET_FHCTLx_CFG in the stock mt_fhreg.h). These + * offsets are therefore relative to that base and must be the per-register + * delta from the channel's CFG - not the absolute offsets. Carrying the + * absolute values here added the channel base twice and every access landed + * 0x4c past the intended register, which is how hopping ended up writing a + * zero to some other block and then timing out on the DDS monitor. + */ +static const struct fhctl_offset fhctl_offset_v3 = { + .offset_hp_en = 0x14, + .offset_clk_con = 0x20, + .offset_rst_con = 0x24, + .offset_slope0 = 0x20, + .offset_slope1 = 0x24, + .offset_cfg = 0x0, + .offset_updnlmt = 0x4, + .offset_dds = 0x8, + .offset_dvfs = 0x8, + .offset_mon = 0xc, +}; + const struct fhctl_offset *fhctl_get_offset_table(enum fhctl_variant v) { switch (v) { @@ -47,6 +76,8 @@ const struct fhctl_offset *fhctl_get_offset_table(enum fhctl_variant v) return &fhctl_offset_v1; case FHCTL_PLLFH_V2: return &fhctl_offset_v2; + case FHCTL_PLLFH_V3: + return &fhctl_offset_v3; default: return ERR_PTR(-EINVAL); }; @@ -56,8 +87,10 @@ static void dump_hw(struct mtk_clk_pll *pll, struct fh_pll_regs *regs, const struct fh_pll_data *data) { pr_info("hp_en<%x>,clk_con<%x>,slope0<%x>,slope1<%x>\n", - readl(regs->reg_hp_en), readl(regs->reg_clk_con), - readl(regs->reg_slope0), readl(regs->reg_slope1)); + readl(regs->reg_hp_en), + regs->reg_clk_con ? readl(regs->reg_clk_con) : 0, + regs->reg_slope0 ? readl(regs->reg_slope0) : 0, + regs->reg_slope1 ? readl(regs->reg_slope1) : 0); pr_info("cfg<%x>,lmt<%x>,dds<%x>,dvfs<%x>,mon<%x>\n", readl(regs->reg_cfg), readl(regs->reg_updnlmt), readl(regs->reg_dds), readl(regs->reg_dvfs), @@ -130,8 +163,10 @@ static int hopping_hw_flow(struct mtk_clk_pll *pll, struct fh_pll_regs *regs, writel(readl(regs->reg_cfg) | data->sfstrx_en, regs->reg_cfg); writel(readl(regs->reg_cfg) | data->fhctlx_en, regs->reg_cfg); - writel(data->slope0_value, regs->reg_slope0); - writel(data->slope1_value, regs->reg_slope1); + if (regs->reg_slope0) + writel(data->slope0_value, regs->reg_slope0); + if (regs->reg_slope1) + writel(data->slope1_value, regs->reg_slope1); writel(readl(regs->reg_hp_en) | BIT(data->fh_id), regs->reg_hp_en); writel((new_dds) | (data->dvfs_tri), regs->reg_dvfs); @@ -190,6 +225,55 @@ static int fhctl_hopping(struct mtk_fh *fh, unsigned int new_dds, unsigned long flags = 0; int ret; + /* + * Frequency hopping and spread spectrum are two independent things. + * FH means changing the PLL frequency through the FHCTL hardware; + * SSC means modulating the emitted clock around the nominal + * frequency. The vendor API spells this out with three states - + * FH_FH_DISABLE, FH_FH_ENABLE_SSC and FH_FH_ENABLE_DFH, the last + * being hopping with no SSC at all (mt_freqhopping.h:42-46) - and + * lists ARMPLL, MAINPLL, MSDCPLL, TVDPLL and LVDSPLL as FH_PLL_ENABLE + * with "default SSC disable" (mt_freqhopping.c:87-92), i.e. their + * hopping path is expected to exist but to be off by default. + * + * So the hop is gated on state->fh_capable, which the device tree + * sets per PLL, and state->ssc_rate only ever decides whether the + * spread-spectrum modulation is programmed around the hop. + * + * A channel that is wired to an FHCTL channel but is not marked + * capable falls back to reprogramming the PCW field directly, which + * is what the data sheet prescribes for these non-SDM PLLs anyway. + * That fallback is not a nicety: running the hop on a channel that + * is not capable does not complete. On this part every FHCTL + * channel register read back zero and the DDS monitor never reached + * the new value, so boot spent a second per attempt in "FHCTL + * hopping timeout". + */ + if (!state->fh_capable) { + unsigned int pcw; + u32 val; + + /* + * new_dds is the postdiv-adjusted PCW value already computed + * by mtk_fhctl_set_rate(); apply it directly rather than + * running the hop. + * + * The postdiv handling below is deliberately not reached: it + * only does anything for a PLL that has a postdiv divider + * table, and the channels that reach this branch on this part - + * ARMPLL, MAINPLL, TVDPLL, LVDSPLL - are declared without one. + */ + pcw = new_dds & data->dds_mask; + + spin_lock_irqsave(lock, flags); + val = readl(pll->pcw_addr); + writel((val & ~data->dds_mask) | pcw | data->pcwchg, + pll->pcw_addr); + spin_unlock_irqrestore(lock, flags); + + return 0; + } + if (postdiv) { pll_postdiv = __get_postdiv(pll); @@ -246,13 +330,15 @@ void fhctl_hw_init(struct mtk_fh *fh) u32 val; /* initial hw register */ - val = readl(regs.reg_clk_con) | BIT(data.fh_id); - writel(val, regs.reg_clk_con); - - val = readl(regs.reg_rst_con) & ~BIT(data.fh_id); - writel(val, regs.reg_rst_con); - val = readl(regs.reg_rst_con) | BIT(data.fh_id); - writel(val, regs.reg_rst_con); + if (regs.reg_clk_con && regs.reg_rst_con) { + val = readl(regs.reg_clk_con) | BIT(data.fh_id); + writel(val, regs.reg_clk_con); + + val = readl(regs.reg_rst_con) & ~BIT(data.fh_id); + writel(val, regs.reg_rst_con); + val = readl(regs.reg_rst_con) | BIT(data.fh_id); + writel(val, regs.reg_rst_con); + } writel(0x0, regs.reg_cfg); writel(0x0, regs.reg_updnlmt); diff --git a/drivers/clk/mediatek/clk-fhctl.h b/drivers/clk/mediatek/clk-fhctl.h index bfa6d281a3ee3f..9b3808f7917caa 100644 --- a/drivers/clk/mediatek/clk-fhctl.h +++ b/drivers/clk/mediatek/clk-fhctl.h @@ -12,6 +12,7 @@ enum fhctl_variant { FHCTL_PLLFH_V1, FHCTL_PLLFH_V2, + FHCTL_PLLFH_V3, /* MT6589: per-channel regs at 0x4c + i * 0x10 */ }; struct fhctl_offset { diff --git a/drivers/clk/mediatek/clk-mt6589-apmixedsys.c b/drivers/clk/mediatek/clk-mt6589-apmixedsys.c index ff5c79f533b6b8..dbafa8135566b1 100644 --- a/drivers/clk/mediatek/clk-mt6589-apmixedsys.c +++ b/drivers/clk/mediatek/clk-mt6589-apmixedsys.c @@ -9,12 +9,16 @@ * AngeloGioacchino Del Regno */ #include +#include +#include #include #include +#include #include #include "clk-pll.h" -#include "clk-gate.h" +#include "clk-pllfh.h" +#include "clk-fhctl.h" #include "clk-mtk.h" #include @@ -75,6 +79,7 @@ .pcwbits = _pcwbits, \ .pd_reg = _pd_reg, \ .pd_mask = 0x3, \ + .pd_valid_mask = GENMASK(2, 0), \ .pd_shift = _pd_shift, \ .tuner_reg = VOID_REG, \ .pcw_reg = _pcw_reg, \ @@ -89,24 +94,29 @@ static int mt6589_lc_pll_set_rate(struct clk_hw *hw, unsigned long rate, struct mtk_clk_pll *pll = to_mtk_clk_pll(hw); u32 pcw = 0; u32 postdiv; - u32 mask, val; + u32 mask, pcw_mask, rate_mask, old_val, val; bool prepared; - int ret; + int ret, rollback_ret; mtk_pll_calc_values(pll, &pcw, &postdiv, rate, parent_rate); prepared = mtk_pll_is_prepared(hw); + mask = pll->data->pd_mask ?: 0x3; + pcw_mask = GENMASK(pll->data->pcw_shift + + pll->data->pcwbits - 1, + pll->data->pcw_shift); + rate_mask = (mask << pll->data->pd_shift) | pcw_mask; + old_val = readl(pll->base_addr); + if (prepared) mtk_pll_unprepare(hw); /* LC PLL: write directly to CON0, no PCW_CHG trigger */ - val = readl(pll->base_addr); + val = old_val; /* Clear postdiv field (2 bits) */ - mask = pll->data->pd_mask ?: 0x3; val &= ~(mask << pll->data->pd_shift); /* Clear FBKDIV field (pcwbits, from pcw_shift) */ - val &= ~(GENMASK(pll->data->pcw_shift + pll->data->pcwbits - 1, - pll->data->pcw_shift)); + val &= ~pcw_mask; /* Set new postdiv and pcw */ val |= ((ffs(postdiv) - 1) << pll->data->pd_shift); val |= (pcw << pll->data->pcw_shift); @@ -116,8 +126,32 @@ static int mt6589_lc_pll_set_rate(struct clk_hw *hw, unsigned long rate, if (prepared) { ret = mtk_pll_prepare(hw); - if (ret) - return ret; + if (!ret) + return 0; + + /* + * set_rate must not leave the CCF-visible prepared state + * different from the hardware state when re-prepare fails. + */ + mtk_pll_unprepare(hw); + + /* Restore the old rate while the PLL is known to be off. */ + val = readl(pll->base_addr); + val &= ~rate_mask; + val |= old_val & rate_mask; + writel(val, pll->base_addr); + + /* + * Restore the state expected by the caller. If even rollback + * preparation fails, leave the PLL unprepared rather than + * pretending that the state was recovered. + */ + rollback_ret = mtk_pll_prepare(hw); + if (rollback_ret) { + mtk_pll_unprepare(hw); + } + + return ret; } return 0; @@ -141,6 +175,71 @@ static const struct clk_ops mt6589_fixed_lc_pll_ops = { /* no .set_rate */ }; +/* + * MT6589 frequency hopping / spread spectrum controller. + * + * The FHCTL lives in its own address range (see the "mediatek, + * mt6589-fhctl" node); it can take over five of the SDM PLLs + * (ARMPLL, MAINPLL, MSDCPLL, TVDPLL and LVDSPLL). Hopping is + * started by writing the target NCPO with bit 31 set into the + * channel's DDS register, which the common code does through the + * dvfs register alias. + */ +/* + * MT6589 FHCTL channel mapping (from mt_freqhopping.h / mt_fhreg.h): + * + * CH0 offset 0x4c PLL_HP_CON0 bit 0 ARMPLL + * CH1 offset 0x5c PLL_HP_CON0 bit 1 MAINPLL + * CH2 offset 0x6c PLL_HP_CON0 bit 2 MEMPLL (not an SDM PLL, not registered) + * CH3 offset 0x7c PLL_HP_CON0 bit 3 MSDCPLL + * CH4 offset 0x8c PLL_HP_CON0 bit 4 TVDPLL + * CH5 offset 0x9c PLL_HP_CON0 bit 5 LVDSPLL + * + * MEMPLL occupies CH2 but is not registered here, so MSDCPLL/TVDPLL/LVDSPLL + * start at CH3/CH4/CH5. fh_id is the PLL_HP_CON0 bit index; fhx_offset is + * the byte offset of FHCTLn_CFG within the FHCTL MMIO window. + */ +enum fh_pll_id { + FH_ARMPLL = 0, + FH_MAINPLL = 1, + /* CH2 = MEMPLL, not registered */ + FH_MSDCPLL = 3, + FH_TVDPLL = 4, + FH_LVDSPLL = 5, +}; + +#define _FH(_pllid, _fhid, _offset) { \ + .data = { \ + .pll_id = _pllid, \ + .fh_id = _fhid, \ + .fh_ver = FHCTL_PLLFH_V3, \ + .fhx_offset = _offset, \ + .dds_mask = GENMASK(20, 0), \ + .slope0_value = 0x6003c97, \ + .slope1_value = 0x6003c97, \ + .sfstrx_en = BIT(2), \ + .frddsx_en = BIT(1), \ + .fhctlx_en = BIT(0), \ + .tgl_org = BIT(31), \ + .dvfs_tri = BIT(31), \ + .pcwchg = BIT(31), \ + .dt_val = 0x0, \ + .df_val = 0x9, \ + .updnlmt_shft = 16, \ + .msk_frddsx_dys = GENMASK(23, 20), \ + .msk_frddsx_dts = GENMASK(19, 16), \ + }, \ + } + +static struct mtk_pllfh_data pllfhs[] = { + _FH(CLK_APMIXED_ARMPLL, FH_ARMPLL, 0x4c), /* CH0 */ + _FH(CLK_APMIXED_MAINPLL, FH_MAINPLL, 0x5c), /* CH1 */ + /* CH2 = MEMPLL, not registered */ + _FH(CLK_APMIXED_MSDCPLL, FH_MSDCPLL, 0x7c), /* CH3 */ + _FH(CLK_APMIXED_TVDPLL, FH_TVDPLL, 0x8c), /* CH4 */ + _FH(CLK_APMIXED_LVDSPLL, FH_LVDSPLL, 0x9c), /* CH5 */ +}; + static const struct mtk_pll_div_table mt6589_isppll_div_table[] = { { .div = 0, .freq = 1664 * MHZ }, { .div = 1, .freq = 1000 * MHZ }, @@ -148,80 +247,390 @@ static const struct mtk_pll_div_table mt6589_isppll_div_table[] = { { } }; +/* + * TVDPLL_MODE is a dedicated output divider in TVDPLL_CON0[23:22]. + * + * 00: /2 + * 01: /4 + * 10: /8 + * 11: /16 + */ +static const struct clk_div_table mt6589_tvdpll_mode_div_table[] = { + { .val = 0, .div = 2 }, + { .val = 1, .div = 4 }, + { .val = 2, .div = 8 }, + { .val = 3, .div = 16 }, + { } +}; + static const struct mtk_pll_data plls[] = { - PLL(CLK_APMIXED_ARMPLL, "armpll", ARMPLL_CON0, ARMPLL_PWR_CON0, 0x80000001, + /* *_OUT_EN / *_XXXM_EN are modeled as child gates below. */ + PLL(CLK_APMIXED_ARMPLL, "armpll", ARMPLL_CON0, ARMPLL_PWR_CON0, BIT(0), PLL_AO, 21, ARMPLL_CON1, 24, ARMPLL_CON1, 0, NULL, 1508 * MHZ, NULL), - PLL(CLK_APMIXED_MAINPLL, "mainpll", MAINPLL_CON0, MAINPLL_PWR_CON0, 0xf0000001, + PLL(CLK_APMIXED_MAINPLL, "mainpll", MAINPLL_CON0, MAINPLL_PWR_CON0, BIT(0), HAVE_RST_BAR, 21, MAINPLL_CON0, 6, MAINPLL_CON1, 0, NULL, 1768 * MHZ, NULL), - PLL(CLK_APMIXED_UNIVPLL, "univpll", UNIVPLL_CON0, VOID_REG, 0xf3000001, + PLL(CLK_APMIXED_UNIVPLL, "univpll", UNIVPLL_CON0, VOID_REG, BIT(0), HAVE_RST_BAR, 7, UNIVPLL_CON0, 6, UNIVPLL_CON0, 8, &mt6589_fixed_lc_pll_ops, 1248 * MHZ, NULL), - PLL(CLK_APMIXED_MMPLL, "mmpll", MMPLL_CON0, VOID_REG, 0xf0000001, + PLL(CLK_APMIXED_MMPLL, "mmpll", MMPLL_CON0, VOID_REG, BIT(0), HAVE_RST_BAR, 7, MMPLL_CON0, 6, MMPLL_CON0, 8, &mt6589_fixed_lc_pll_ops, 1690 * MHZ, NULL), - PLL(CLK_APMIXED_ISPPLL, "isppll", ISPPLL_CON0, VOID_REG, 0x80000001, + PLL(CLK_APMIXED_ISPPLL, "isppll", ISPPLL_CON0, VOID_REG, BIT(0), 0, 7, ISPPLL_CON0, 6, ISPPLL_CON0, 8, &mt6589_lc_pll_ops, 1664 * MHZ, mt6589_isppll_div_table), - PLL(CLK_APMIXED_MSDCPLL, "msdcpll", MSDCPLL_CON0, MSDCPLL_PWR_CON0, 0x80000001, + PLL(CLK_APMIXED_MSDCPLL, "msdcpll", MSDCPLL_CON0, MSDCPLL_PWR_CON0, BIT(0), 0, 21, MSDCPLL_CON0, 6, MSDCPLL_CON1, 0, NULL, 1664 * MHZ, NULL), - PLL(CLK_APMIXED_TVDPLL, "tvdpll", TVDPLL_CON0, TVDPLL_PWR_CON0, 0x80000001, + PLL(CLK_APMIXED_TVDPLL, "tvdpll", TVDPLL_CON0, TVDPLL_PWR_CON0, BIT(0), 0, 21, TVDPLL_CON0, 6, TVDPLL_CON1, 0, NULL, 2376UL * MHZ, NULL), - PLL(CLK_APMIXED_LVDSPLL, "lvdspll", LVDSPLL_CON0, LVDSPLL_PWR_CON0, 0x80000001, + PLL(CLK_APMIXED_LVDSPLL, "lvdspll", LVDSPLL_CON0, LVDSPLL_PWR_CON0, BIT(0), 0, 21, LVDSPLL_CON0, 6, LVDSPLL_CON1, 0, NULL, 1440 * MHZ, NULL), }; -/* TODO: convert to gate */ -static const struct mtk_fixed_factor pll_divs[] = { - FACTOR(CLK_APMIXED_ARMPLL_1300M, "armpll_1300m", "armpll", 1, 1), +struct mt6589_apmixed_output { + int id; + const char *name; + const char *parent_name; + u32 div; + u32 reg; + u8 div_shift; + u8 gate_shift; + const struct clk_div_table *div_table; + u8 width; +}; + +#define APMIXED_OUTPUT(_id, _name, _parent, _div, _reg, _gate_shift) \ + { \ + .id = _id, .name = _name, .parent_name = _parent, .div = _div, \ + .reg = _reg, .div_shift = 0, .gate_shift = _gate_shift, \ + .div_table = NULL, .width = 0, \ + } + +#define APMIXED_OUTPUT_DIVIDER(_id, _name, _parent, \ + _reg, _div_shift, _gate_shift, _width, _table) \ + { \ + .id = _id, .name = _name, .parent_name = _parent, .div = 1, \ + .reg = _reg, .div_shift = _div_shift, \ + .gate_shift = _gate_shift, \ + .div_table = _table, \ + .width = _width, \ + } + +static const struct mt6589_apmixed_output apmixed_outputs[] = { + APMIXED_OUTPUT(CLK_APMIXED_ARMPLL_1300M, "armpll_1300m", + "armpll", 1, ARMPLL_CON0, 31), + + APMIXED_OUTPUT(CLK_APMIXED_MAINPLL_806M, "mainpll_806m", + "mainpll", 2, MAINPLL_CON0, 31), + APMIXED_OUTPUT(CLK_APMIXED_MAINPLL_537P3M, "mainpll_537p3m", + "mainpll", 3, MAINPLL_CON0, 30), + APMIXED_OUTPUT(CLK_APMIXED_MAINPLL_322P4M, "mainpll_322p4m", + "mainpll", 5, MAINPLL_CON0, 29), + APMIXED_OUTPUT(CLK_APMIXED_MAINPLL_230P3M, "mainpll_230p3m", + "mainpll", 7, MAINPLL_CON0, 28), + + APMIXED_OUTPUT(CLK_APMIXED_UNIVPLL_624M, "univpll_624m", + "univpll", 2, UNIVPLL_CON0, 31), + APMIXED_OUTPUT(CLK_APMIXED_UNIVPLL_416M, "univpll_416m", + "univpll", 3, UNIVPLL_CON0, 30), + APMIXED_OUTPUT(CLK_APMIXED_UNIVPLL_249P6M, "univpll_249p6m", + "univpll", 5, UNIVPLL_CON0, 29), + APMIXED_OUTPUT(CLK_APMIXED_UNIVPLL_178P3M, "univpll_178p3m", + "univpll", 7, UNIVPLL_CON0, 28), + APMIXED_OUTPUT(CLK_APMIXED_UNIVPLL_48M, "univpll_48m", + "univpll", 26, UNIVPLL_CON0, 25), + APMIXED_OUTPUT(CLK_APMIXED_UNIVPLL_USB_48M, "univpll_usb_48m", + "univpll", 26, UNIVPLL_CON0, 24), + + APMIXED_OUTPUT(CLK_APMIXED_MMPLL_D2, "mmpll_d2", + "mmpll", 2, MMPLL_CON0, 31), + APMIXED_OUTPUT(CLK_APMIXED_MMPLL_D3, "mmpll_d3", + "mmpll", 3, MMPLL_CON0, 30), + APMIXED_OUTPUT(CLK_APMIXED_MMPLL_D5, "mmpll_d5", + "mmpll", 5, MMPLL_CON0, 29), + APMIXED_OUTPUT(CLK_APMIXED_MMPLL_D7, "mmpll_d7", + "mmpll", 7, MMPLL_CON0, 28), + + APMIXED_OUTPUT(CLK_APMIXED_ISPPLL_208M, "isppll_208m", + "isppll", 2, ISPPLL_CON0, 31), + + APMIXED_OUTPUT(CLK_APMIXED_MSDCPLL_208M, "msdcpll_208m", + "msdcpll", 2, MSDCPLL_CON0, 31), + + APMIXED_OUTPUT_DIVIDER(CLK_APMIXED_TVDPLL_148P5M, "tvdpll_148p5m", + "tvdpll", + TVDPLL_CON0, 22, 31, 2, + mt6589_tvdpll_mode_div_table), + + APMIXED_OUTPUT(CLK_APMIXED_LVDSPLL_180M, "lvdspll_180m", + "lvdspll", 2, LVDSPLL_CON0, 31), +}; + +static DEFINE_SPINLOCK(mt6589_apmixed_clk_lock); + +struct mt6589_apmixed_output_hw { + struct clk_gate gate; + const struct mt6589_apmixed_output *data; +}; + +static inline struct mt6589_apmixed_output_hw * +to_mt6589_apmixed_output_hw(struct clk_hw *hw) +{ + return container_of(hw, struct mt6589_apmixed_output_hw, gate.hw); +} + +static int mt6589_apmixed_output_enable(struct clk_hw *hw) +{ + return clk_gate_ops.enable(hw); +} + +static void mt6589_apmixed_output_disable(struct clk_hw *hw) +{ + clk_gate_ops.disable(hw); +} + +static int mt6589_apmixed_output_is_enabled(struct clk_hw *hw) +{ + return clk_gate_ops.is_enabled(hw); +} + +static unsigned long +mt6589_apmixed_output_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mt6589_apmixed_output_hw *output_hw = + to_mt6589_apmixed_output_hw(hw); + const struct mt6589_apmixed_output *output = output_hw->data; + u32 val; + + if (!output->div_table) + return parent_rate / output->div; + + val = readl(output_hw->gate.reg) >> output->div_shift; + val &= clk_div_mask(output->width); + + return divider_recalc_rate(hw, parent_rate, val, output->div_table, + 0, output->width); +} + +static int +mt6589_apmixed_output_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct mt6589_apmixed_output_hw *output_hw = + to_mt6589_apmixed_output_hw(hw); + const struct mt6589_apmixed_output *output = output_hw->data; + + if (output->div_table) + return divider_determine_rate(hw, req, output->div_table, + output->width, 0); + + if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) { + unsigned long best_parent; + + best_parent = req->rate * output->div; + req->best_parent_rate = + clk_hw_round_rate(req->best_parent_hw, best_parent); + } + + req->rate = req->best_parent_rate / output->div; + + return 0; +} + +static int +mt6589_apmixed_output_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct mt6589_apmixed_output_hw *output_hw = + to_mt6589_apmixed_output_hw(hw); + const struct mt6589_apmixed_output *output = output_hw->data; + unsigned long flags; + int value; + u32 val; - FACTOR(CLK_APMIXED_MAINPLL_806M, "mainpll_806m", "mainpll", 1, 2), - FACTOR(CLK_APMIXED_MAINPLL_537P3M, "mainpll_537p3m", "mainpll", 1, 3), - FACTOR(CLK_APMIXED_MAINPLL_322P4M, "mainpll_322p4m", "mainpll", 1, 5), - FACTOR(CLK_APMIXED_MAINPLL_230P3M, "mainpll_230p3m", "mainpll", 1, 7), + if (!output->div_table) + return 0; - FACTOR(CLK_APMIXED_UNIVPLL_624M, "univpll_624m", "univpll", 1, 2), - FACTOR(CLK_APMIXED_UNIVPLL_416M, "univpll_416m", "univpll", 1, 3), - FACTOR(CLK_APMIXED_UNIVPLL_249P6M, "univpll_249p6m", "univpll", 1, 5), - FACTOR(CLK_APMIXED_UNIVPLL_178P3M, "univpll_178p3m", "univpll", 1, 7), - FACTOR(CLK_APMIXED_UNIVPLL_48M, "univpll_48m", "univpll", 1, 26), - FACTOR(CLK_APMIXED_UNIVPLL_USB_48M, "univpll_usb_48m", "univpll", 1, 26), + value = divider_get_val(rate, parent_rate, output->div_table, + output->width, 0); + if (value < 0) + return value; - FACTOR(CLK_APMIXED_MMPLL_D2, "mmpll_d2", "mmpll", 1, 2), - FACTOR(CLK_APMIXED_MMPLL_D3, "mmpll_d3", "mmpll", 1, 3), - FACTOR(CLK_APMIXED_MMPLL_D5, "mmpll_d5", "mmpll", 1, 5), - FACTOR(CLK_APMIXED_MMPLL_D7, "mmpll_d7", "mmpll", 1, 7), + spin_lock_irqsave(output_hw->gate.lock, flags); - FACTOR(CLK_APMIXED_ISPPLL_208M, "isppll_208m", "isppll", 1, 2), + val = readl(output_hw->gate.reg); + val &= ~(clk_div_mask(output->width) << output->div_shift); + val |= (u32)value << output->div_shift; + writel(val, output_hw->gate.reg); - FACTOR(CLK_APMIXED_MSDCPLL_208M, "msdcpll_208m", "msdcpll", 1, 2), + spin_unlock_irqrestore(output_hw->gate.lock, flags); - FACTOR(CLK_APMIXED_TVDPLL_148P5M, "tvdpll_148p5m", "tvdpll", 1, 16), + return 0; +} - FACTOR(CLK_APMIXED_LVDSPLL_180M, "lvdspll_180m", "lvdspll", 1, 2), +static unsigned long +mt6589_apmixed_output_recalc_accuracy(struct clk_hw *hw, + unsigned long parent_accuracy) +{ + return parent_accuracy; +} + +static const struct clk_ops mt6589_apmixed_output_ops = { + .enable = mt6589_apmixed_output_enable, + .disable = mt6589_apmixed_output_disable, + .is_enabled = mt6589_apmixed_output_is_enabled, + .recalc_rate = mt6589_apmixed_output_recalc_rate, + .determine_rate = mt6589_apmixed_output_determine_rate, + .set_rate = mt6589_apmixed_output_set_rate, + .recalc_accuracy = mt6589_apmixed_output_recalc_accuracy, }; -static const struct mtk_clk_desc apmixed_desc = { - .plls = plls, - .num_plls = ARRAY_SIZE(plls), - .factor_clks = pll_divs, - .num_factor_clks = ARRAY_SIZE(pll_divs), +struct mt6589_apmixed_priv { + struct clk_hw_onecell_data *clk_data; + struct mt6589_apmixed_output_hw *output_hws[ARRAY_SIZE(apmixed_outputs)]; }; +static void mt6589_apmixed_unregister_outputs(struct mt6589_apmixed_priv *priv) +{ + int i; + + for (i = ARRAY_SIZE(apmixed_outputs) - 1; i >= 0; i--) { + const struct mt6589_apmixed_output *output = &apmixed_outputs[i]; + struct mt6589_apmixed_output_hw *output_hw = + priv->output_hws[i]; + + if (!output_hw) + continue; + + clk_hw_unregister(&output_hw->gate.hw); + kfree(output_hw); + priv->output_hws[i] = NULL; + priv->clk_data->hws[output->id] = ERR_PTR(-ENOENT); + } +} + +static int mt6589_apmixed_register_outputs(struct device *dev, + void __iomem *base, + struct mt6589_apmixed_priv *priv) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(apmixed_outputs); i++) { + const struct mt6589_apmixed_output *output = &apmixed_outputs[i]; + struct mt6589_apmixed_output_hw *output_hw; + struct clk_init_data init = {}; + int ret; + + output_hw = kzalloc_obj(*output_hw); + if (!output_hw) { + mt6589_apmixed_unregister_outputs(priv); + return -ENOMEM; + } + + output_hw->data = output; + output_hw->gate.reg = base + output->reg; + output_hw->gate.bit_idx = output->gate_shift; + output_hw->gate.flags = 0; + output_hw->gate.lock = &mt6589_apmixed_clk_lock; + output_hw->gate.hw.init = &init; + + init.name = output->name; + init.ops = &mt6589_apmixed_output_ops; + init.flags = CLK_SET_RATE_PARENT; + init.parent_names = &output->parent_name; + init.num_parents = 1; + + ret = clk_hw_register(dev, &output_hw->gate.hw); + if (ret) { + kfree(output_hw); + mt6589_apmixed_unregister_outputs(priv); + return ret; + } + + priv->output_hws[i] = output_hw; + priv->clk_data->hws[output->id] = &output_hw->gate.hw; + } + + return 0; +} + +static int clk_mt6589_apmixed_probe(struct platform_device *pdev) +{ + const u8 *fhctl_node = "mediatek,mt6589-fhctl"; + struct mt6589_apmixed_priv *priv; + struct clk_hw_onecell_data *clk_data; + struct device *dev = &pdev->dev; + void __iomem *base; + int r; + + clk_data = mtk_alloc_clk_data(CLK_APMIXED_NR_CLK); + if (!clk_data) + return -ENOMEM; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + r = -ENOMEM; + goto free_clk_data; + } + priv->clk_data = clk_data; + + fhctl_parse_dt(fhctl_node, pllfhs, ARRAY_SIZE(pllfhs)); + r = mtk_clk_register_pllfhs(dev, plls, ARRAY_SIZE(plls), pllfhs, + ARRAY_SIZE(pllfhs), clk_data); + if (r) + goto free_clk_data; + + base = mtk_clk_pll_get_base(clk_data->hws[CLK_APMIXED_ARMPLL], + &plls[CLK_APMIXED_ARMPLL]); + r = mt6589_apmixed_register_outputs(dev, base, priv); + if (r) + goto unregister_plls; + + r = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, + clk_data); + if (r) + goto unregister_outputs; + + platform_set_drvdata(pdev, priv); + + return 0; + +unregister_outputs: + mt6589_apmixed_unregister_outputs(priv); +unregister_plls: + mtk_clk_unregister_pllfhs(plls, ARRAY_SIZE(plls), pllfhs, + ARRAY_SIZE(pllfhs), clk_data); +free_clk_data: + mtk_free_clk_data(clk_data); + return r; +} + +static void clk_mt6589_apmixed_remove(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct mt6589_apmixed_priv *priv = platform_get_drvdata(pdev); + struct clk_hw_onecell_data *clk_data = priv->clk_data; + + of_clk_del_provider(node); + mt6589_apmixed_unregister_outputs(priv); + mtk_clk_unregister_pllfhs(plls, ARRAY_SIZE(plls), pllfhs, + ARRAY_SIZE(pllfhs), clk_data); + mtk_free_clk_data(clk_data); +} + static const struct of_device_id of_match_clk_mt6589_apmixed[] = { - { .compatible = "mediatek,mt6589-apmixedsys", .data = &apmixed_desc }, + { .compatible = "mediatek,mt6589-apmixedsys" }, { /* sentinel */ } }; -MODULE_DEVICE_TABLE(of, of_match_clk_mt6589_apmixed); static struct platform_driver clk_mt6589_apmixed_drv = { - .probe = mtk_clk_simple_probe, - .remove = mtk_clk_simple_remove, + .probe = clk_mt6589_apmixed_probe, + .remove = clk_mt6589_apmixed_remove, .driver = { .name = "clk-mt6589-apmixed", .of_match_table = of_match_clk_mt6589_apmixed, }, }; module_platform_driver(clk_mt6589_apmixed_drv); - MODULE_DESCRIPTION("MediaTek MT6589 apmixedsys clocks driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/clk/mediatek/clk-mt6589-disp.c b/drivers/clk/mediatek/clk-mt6589-disp.c index 96afc6126972f4..3e65359550fb70 100644 --- a/drivers/clk/mediatek/clk-mt6589-disp.c +++ b/drivers/clk/mediatek/clk-mt6589-disp.c @@ -51,13 +51,13 @@ static const struct mtk_gate disp_clks[] = { GATE_DISP0(CLK_DISP0_WDMA1_ENGINE, "disp0_wdma1_engine", "disp_sel", 11), GATE_DISP0(CLK_DISP0_WDMA1_SMI, "disp0_wdma1_smi", "smi_sel", 12), /* mt8135 */ GATE_DISP0(CLK_DISP0_RDMA0_ENGINE, "disp0_rdma0_engine", "disp_sel", 13), - GATE_DISP0(CLK_DISP0_RDMA0_SMI, "disp0_rdma0_smi", "clk_null", 14), /* mt8135 */ - GATE_DISP0(CLK_DISP0_RDMA0_OUTPUT, "disp0_rdma0_output", "clk_null", 15), /* mt8135 */ + GATE_DISP0(CLK_DISP0_RDMA0_SMI, "disp0_rdma0_smi", "smi_sel", 14), /* mt8135 */ + GATE_DISP0(CLK_DISP0_RDMA0_OUTPUT, "disp0_rdma0_output", "disp_sel", 15), GATE_DISP0(CLK_DISP0_RDMA1_ENGINE, "disp0_rdma1_engine", "disp_sel", 16), - GATE_DISP0(CLK_DISP0_RDMA1_SMI, "disp0_rdma1_smi", "clk_null", 17), /* mt8135 */ - GATE_DISP0(CLK_DISP0_RDMA1_OUTPUT, "disp0_rdma1_output", "clk_null", 18), /* mt8135 */ + GATE_DISP0(CLK_DISP0_RDMA1_SMI, "disp0_rdma1_smi", "smi_sel", 17), /* mt8135 */ + GATE_DISP0(CLK_DISP0_RDMA1_OUTPUT, "disp0_rdma1_output", "disp_sel", 18), GATE_DISP0(CLK_DISP0_GAMMA_ENGINE, "disp0_gamma_engine", "disp_sel", 19), - GATE_DISP0(CLK_DISP0_GAMMA_PIXEL, "disp0_gamma_pixel", "clk_null", 20), /* mt8135 */ + GATE_DISP0(CLK_DISP0_GAMMA_PIXEL, "disp0_gamma_pixel", "disp_sel", 20), GATE_DISP0(CLK_DISP0_CMDQ_ENGINE, "disp0_cmdq_engine", "disp_sel", 21), GATE_DISP0(CLK_DISP0_CMDQ_SMI, "disp0_cmdq_smi", "smi_sel", 22), /* mt8135 */ GATE_DISP0(CLK_DISP0_G2D_ENGINE, "disp0_g2d_engine", "disp_sel", 23), diff --git a/drivers/clk/mediatek/clk-mt6589-infracfg.c b/drivers/clk/mediatek/clk-mt6589-infracfg.c index e549fa36b8d738..b259d12a6ebf76 100644 --- a/drivers/clk/mediatek/clk-mt6589-infracfg.c +++ b/drivers/clk/mediatek/clk-mt6589-infracfg.c @@ -4,6 +4,7 @@ */ #include #include +#include #include "clk-mtk.h" #include "clk-gate.h" @@ -11,6 +12,137 @@ #include #define TOP_CKMUXSEL 0x0000 +#define TOP_CKDIV1 0x0008 + +struct mt6589_armdiv_ratio { + u8 val; + u8 num; + u8 den; +}; + +static const struct mt6589_armdiv_ratio mt6589_armdiv_ratios[] = { + { 0x00, 1, 1 }, + { 0x08, 4, 4 }, + { 0x09, 3, 4 }, + { 0x0a, 2, 4 }, + { 0x0b, 1, 4 }, + { 0x10, 5, 5 }, + { 0x11, 4, 5 }, + { 0x12, 3, 5 }, + { 0x13, 2, 5 }, + { 0x14, 1, 5 }, + { 0x18, 6, 6 }, + { 0x19, 5, 6 }, + { 0x1a, 4, 6 }, + { 0x1b, 3, 6 }, + { 0x1c, 2, 6 }, + { 0x1d, 1, 6 }, +}; + +static const struct mt6589_armdiv_ratio * +mt6589_armdiv_find_val(unsigned int val) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mt6589_armdiv_ratios); i++) + if (mt6589_armdiv_ratios[i].val == val) + return &mt6589_armdiv_ratios[i]; + + return NULL; +} + +static const struct mt6589_armdiv_ratio * +mt6589_armdiv_find_rate(unsigned long rate, unsigned long parent_rate) +{ + const struct mt6589_armdiv_ratio *best = NULL; + u64 best_diff = ~0ULL; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mt6589_armdiv_ratios); i++) { + const struct mt6589_armdiv_ratio *ratio = + &mt6589_armdiv_ratios[i]; + u64 candidate; + u64 diff; + + candidate = div_u64((u64)parent_rate * ratio->num, + ratio->den); + diff = candidate > rate ? candidate - rate : rate - candidate; + + if (diff < best_diff) { + best_diff = diff; + best = ratio; + } + } + + return best; +} + +static unsigned long mt6589_armdiv_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_divider *div = to_clk_divider(hw); + const struct mt6589_armdiv_ratio *ratio; + u32 val; + + val = readl(div->reg) >> div->shift; + val &= GENMASK(div->width - 1, 0); + + ratio = mt6589_armdiv_find_val(val); + if (!ratio) + return parent_rate; + + return div_u64((u64)parent_rate * ratio->num, ratio->den); +} + +static int mt6589_armdiv_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + const struct mt6589_armdiv_ratio *ratio; + + ratio = mt6589_armdiv_find_rate(req->rate, req->best_parent_rate); + if (!ratio) + return -EINVAL; + + req->rate = div_u64((u64)req->best_parent_rate * ratio->num, + ratio->den); + + return 0; +} + +static int mt6589_armdiv_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_divider *div = to_clk_divider(hw); + const struct mt6589_armdiv_ratio *ratio; + unsigned long flags; + u32 val; + u32 mask; + + ratio = mt6589_armdiv_find_rate(rate, parent_rate); + if (!ratio) + return -EINVAL; + + mask = GENMASK(div->width - 1, 0); + + if (div->lock) + spin_lock_irqsave(div->lock, flags); + + val = readl(div->reg); + val &= ~(mask << div->shift); + val |= (u32)ratio->val << div->shift; + writel(val, div->reg); + + if (div->lock) + spin_unlock_irqrestore(div->lock, flags); + + return 0; +} + +static const struct clk_ops mt6589_armdiv_ops = { + .recalc_rate = mt6589_armdiv_recalc_rate, + .determine_rate = mt6589_armdiv_determine_rate, + .set_rate = mt6589_armdiv_set_rate, +}; #define INFRA_RST0 0x0030 #define INFRA_RST1 0x0034 #define INFRA_PDN_SET 0x0040 @@ -19,6 +151,22 @@ static DEFINE_SPINLOCK(mt6589_infra_clk_lock); +/* + * TOPCKMUXSEL.mux1_sel is bits [3:2] and takes four values + * (datasheet p. 436): + * + * 00: CLKSQ 01: ARMPLL 10: MAINPLL 11: MMPLL/2 + * + * The first parent is named "clk26m" because CLKSQ is the clock + * squarer output of the external 26 MHz VCXO (datasheet p. 1340 + * section 34.3.7, and the CLKSQ1/CLKSQ2 taps on p. 1341). The name + * is therefore accurate about the frequency but not about the node: + * CLKSQ is a square-wave version of the pad input, not a raw 26 MHz + * tap. Nothing in this driver depends on the spelling - it only has + * to match the "clk26m" fixed-clock in mt6589.dtsi - so it is left + * alone rather than renamed here, since other MT6589 clock trees + * would then have to change with it. + */ static const char * const infra_mux1_parents[] = { "clk26m", "armpll", @@ -46,7 +194,14 @@ static const struct mtk_gate infra_clks[] = { GATE_INFRA(CLK_INFRA_AUDIO, "infra_audio", "audintbus_sel", 5), GATE_INFRA(CLK_INFRA_DEVAPC, "infra_devapc", "axi_sel", 6), /* axi_sel? */ GATE_INFRA(CLK_INFRA_MFGAXI, "infra_mfgaxi", "axi_sel", 7), /* mt8135 */ - GATE_INFRA(CLK_INFRA_M4U, "infra_m4u", "mem_sel", 8), /* mt8135 */ + /* + * The M4U clock is critical: stopping it while the IOMMU is + * attached hangs the whole system (any outstanding translation + * never completes). Keep it on from boot. + */ + GATE_MTK_FLAGS(CLK_INFRA_M4U, "infra_m4u", "mem_sel", + &infra_cg_regs, 8, &mtk_clk_gate_ops_setclr, + CLK_IS_CRITICAL), GATE_INFRA(CLK_INFRA_MD1MCUAXI, "infra_md1mcuaxi", "axi_sel", 9), /* maybe */ GATE_INFRA(CLK_INFRA_MD1HWMIXAXI, "infra_md1hwmixaxi", "axi_sel", 10), /* maybe */ GATE_INFRA(CLK_INFRA_MD1AHB, "infra_md1ahb", "axi_sel", 11), /* maybe */ @@ -69,11 +224,31 @@ static const struct mtk_clk_rst_desc infra_clk_rst_desc = { .rst_bank_nr = ARRAY_SIZE(infrasys_rst_ofs), }; +/* + * The CPU DVFS path divides ARMPLL through this field while + * reprogramming the PLL; exposing it as a clock lets cpufreq switch + * through it instead of poking the register directly. + */ +static const struct mtk_clk_divider infra_dividers[] = { + { + .id = CLK_INFRA_ARMDIV1, + .name = "armdiv1", + .parent_name = "infra_mux1_sel", + .flags = CLK_SET_RATE_PARENT, + .div_reg = TOP_CKDIV1, + .div_shift = 0, + .div_width = 5, + .ops = &mt6589_armdiv_ops, + }, +}; + static const struct mtk_clk_desc infra_desc = { .clks = infra_clks, .num_clks = ARRAY_SIZE(infra_clks), .cpumuxes = cpu_muxes, .num_cpumuxes = ARRAY_SIZE(cpu_muxes), + .divider_clks = infra_dividers, + .num_divider_clks = ARRAY_SIZE(infra_dividers), .rst_desc = &infra_clk_rst_desc, .clk_lock = &mt6589_infra_clk_lock, }; diff --git a/drivers/clk/mediatek/clk-mt6589-mempll.c b/drivers/clk/mediatek/clk-mt6589-mempll.c new file mode 100644 index 00000000000000..a4e62477433c1b --- /dev/null +++ b/drivers/clk/mediatek/clk-mt6589-mempll.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MT6589 MEMPLL clock. + * + * WHERE MEMPLL LIVES -- verified, because it is easy to get wrong twice. + * + * - The MEMPLL registers are inside the ddrphy_conf module (base + * +0x1001_1000h): MEMPLL0..MEMPLL12 at 0x1001_1600..0x1001_1630 and + * MEMPLL_DIVIDER at 0x1001_1640. Datasheet 13.4.3 "DDRPHY Registers", + * module register list p. 708, per-register detail p. 729-741. + * MPLLPON0..3, the power-on debounce sequence registers, are at + * 0x1001_15C0..0x1001_15CC (p. 708, detail p. 729f). + * - The downstream tree agrees. mt_reg_base.h:13 DDRPHY_BASE is + * 0xF0011000, and the preloader writes MEMPLL through + * DRAMC_WRITE_REG(val, (0x0180 << 2)) in + * preloader/src/drivers/mtk_pll.c -- 0xF0011000 + 0x600 = 0xF0011600 + * = 0x1001_1600, i.e. MEMPLL0. Register n is at offset 0x4 * n. + * - MEMPLL is NOT in apmixedsys. The apmixedsys register list + * (datasheet p. 1349, 0x1020_9000..) has AP_PLL_CON0/1/2, PLL_HP_CON0, + * ARMPLL, MAINPLL, UNIVPLL, MMPLL, ISPPLL, MSDCPLL, TVDPLL and LVDSPLL + * and no MEMPLL block at all. That is why + * clk-mt6589-apmixedsys.c cannot register it and registers MSDCPLL, + * TVDPLL and LVDSPLL as frequency-hopping channels 3, 4 and 5. + * + * The "CH2" in that file refers to the frequency-hopping channel, not + * to the PLL: PLL_HP_CON0 at apmixedsys + 0x14 has MEMPLL_HP_EN at bit + * 2 (datasheet p. 1349), and the downstream tree uses + * pll_id 2 for "MEMPLL default SSC enable" in + * kernel/core/mt_freqhopping.c:89. Hopping control and the PLL itself + * are two different register windows, which is the whole trap here. + * + * WHY THIS DRIVER DOES NOT COMPUTE THE RATE FROM THE REGISTERS. + * + * The obvious formula -- XTAL times (FBDIV + 1), divided by PREDIV and + * POSDIV -- does not hold on this block, and adopting it would put a + * confidently wrong number into the clock framework. The verified + * reasons, in increasing order of how much they cost to discover: + * + * 1. MEMPLL0[24:18] is documented as "Feedback divide ratio of MEMPLL" + * (p. 730), but it is not PLL1's feedback divider. The preloader + * writes MEMPLL0 with PREDIV/FBKSEL/POSDIV only and puts the feedback + * dividers for PLL2/3/4 in MEMPLL2/3/4 -- see the + * pllc1_prediv_1_0_n1 | pllc1_blp | pllc1_fbksel_1_0_n3 | + * pllc1_postdiv_1_0_n5 write to (0x0180 << 2) followed by + * mempll2_fbdiv_6_0 going to (0x0182 << 2) in mtk_pll.c. So reading + * MEMPLL0's FBDIV field reads a field nothing programs. + * + * 2. The reference clock is not necessarily the crystal. + * MEMPLL1[12:11] RG_MEMPLL_REFCK_SEL selects 00 = Xtal clock or + * 01 = "APLL + div (close loop)" (p. 730), and MEMPLL1[24] + * RG_MEMPLL_DIV_EN enables a divider for the "(APLL+DDS)" path. With + * nothing in the register set saying which PLL or which divider, the + * multiplier is unknown. + * + * 3. The downstream frequency tables are not invertible. In + * mtk_pll.c the DDS PCW for DDR300, DDR500, DDR800 and DDR1000 is the + * same value (0x00d89d89 << 1, 0x4e << 25), so the PCW in MEMPLL9 + * cannot identify the frequency; and that source is guarded by + * #ifdef MEMPLL_CLK_* macros that no board in this tree defines, so + * the compiled configuration is itself unknown. + * + * Therefore the rate is DECLARED by the board, not derived, and the + * default is the one value the datasheet states outright: 266.5 MHz. + * Datasheet p. 402, clk_mem_sel and clk_ddrphycfg_sel describe their + * clock as "(Maximum id 266.5MHz)" and list "MEMPLL_MCK_D4 (266.5/4)"; + * the SoC summary gives "Memory clock up to 533MHz" and "LPDDR2 533MHz" + * / "DDR3 @ 533MHz" (p. 27, and the EMI chapter's 1066 Mbps per bit). + * MEMPLL_MCK 266.5 MHz with DDR data at 533 Mbps is consistent with all + * of that. + * + * What this driver deliberately does NOT do is check the declared rate + * against the registers. Attempting that from the fields available is + * how claim 1 above goes wrong: MEMPLL0[27:26] FBKSEL reads 1 (the /2 + * setting in the field table) for the state the preloader leaves behind, + * while the same preloader's 266 MHz path writes 0xc0000000 into + * MEMPLL0, i.e. bits 27:26 = 11, an encoding the datasheet does not + * document for that field. A rate decoder built on that table would be + * wrong by a factor of two on the reference case. + * + * A board that runs its DRAM at another rate must say so with + * clock-frequency; nothing here guesses on its behalf. + * + * WHAT THE DRIVER DOES provide is the part that IS readable: enable state + * (MEMPLL0[2] plus MEMPLL_DIVIDER[4]) and a debugfs dump of every field + * the datasheet documents, so that the declared rate can be falsified on + * real hardware rather than trusted. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Offsets inside the ddrphy_conf window, datasheet p. 708. */ +#define MEMPLL_CON(n) (0x600 + 0x4 * (n)) /* 0x1001_1600 + 4n */ +/* MPLLPON0..3, the power-on debounce sequence, are at 0x5c0..0x5cc. They */ +/* are documented (p. 708, p. 729f) but never read here: the sequence has */ +/* already run by the time Linux boots, and nothing in it is a clock field. */ +#define MPLLPON_CON(n) (0x5c0 + 0x4 * (n)) /* 0x1001_15C0 + 4n */ +#define MEMPLL_DIVIDER 0x640 /* 0x1001_1640 */ + +/* MEMPLL0 -- MEMPLL Register Setting 0, datasheet p. 729f. */ +#define MEMPLL0_FBKSEL GENMASK(27, 26) +#define MEMPLL0_FBDIV GENMASK(24, 18) +#define MEMPLL0_PREDIV GENMASK(17, 16) +#define MEMPLL0_POSDIV GENMASK(15, 14) +#define MEMPLL0_VCO_DIV_SEL BIT(13) +#define MEMPLL0_MONCK_EN BIT(5) +#define MEMPLL0_EN BIT(2) + +/* MEMPLL1 -- MEMPLL Register Setting 1, datasheet p. 730. */ +#define MEMPLL1_DIV GENMASK(31, 25) +#define MEMPLL1_DIV_EN BIT(24) +#define MEMPLL1_BIAS_LPF_EN BIT(15) +#define MEMPLL1_BIAS_EN BIT(14) +#define MEMPLL1_REFCK_EN BIT(13) +#define MEMPLL1_REFCK_SEL GENMASK(12, 11) + +/* MEMPLL_DIVIDER, datasheet p. 741. */ +#define MEMPLL_DIV_ALL_CK_EN BIT(4) /* R_DMALLCLK_EN */ +#define MEMPLL_DIV_1PLL_SYNC BIT(2) /* R_DM1PLL_SYNC_MODE */ +#define MEMPLL_DIV_BYP_PLL3 BIT(1) /* R_DMBY_PLL3 */ +#define MEMPLL_DIV_BYP_PLL4 BIT(0) /* R_DMBY_PLL4 */ + +static const char * const mempll_refck_sel_name[] = { + "xtal", "apll+div", "reserved", "reserved", +}; + +struct mt6589_mempll { + void __iomem *base; + struct clk_hw hw; + struct clk *clk; + unsigned long rate; + struct dentry *debugfs; +}; + +static inline struct mt6589_mempll *hw_to_mempll(struct clk_hw *hw) +{ + return container_of(hw, struct mt6589_mempll, hw); +} + +static int mt6589_mempll_is_enabled(struct clk_hw *hw) +{ + struct mt6589_mempll *mempll = hw_to_mempll(hw); + + /* + * MEMPLL0[2] RG_MEMPLL_EN enables the PLL, but the output only + * reaches the SoC once MEMPLL_DIVIDER[4] R_DMALLCLK_EN is set -- + * datasheet p. 741, "enables all output clocks of MEMPLL divider, + * except for feedback clocks to MEMPLL". Both must be up, and + * reporting the first alone would claim a clock that is not + * reaching anything. + */ + if (!(readl(mempll->base + MEMPLL_CON(0)) & MEMPLL0_EN)) + return 0; + + return !!(readl(mempll->base + MEMPLL_DIVIDER) & MEMPLL_DIV_ALL_CK_EN); +} + +static unsigned long mt6589_mempll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return hw_to_mempll(hw)->rate; +} + +static const struct clk_ops mt6589_mempll_ops = { + .is_enabled = mt6589_mempll_is_enabled, + .recalc_rate = mt6589_mempll_recalc_rate, +}; + +static int mt6589_mempll_dump_show(struct seq_file *s, void *unused) +{ + struct mt6589_mempll *mempll = s->private; + u32 con0, con1, divider; + unsigned int i; + + con0 = readl(mempll->base + MEMPLL_CON(0)); + con1 = readl(mempll->base + MEMPLL_CON(1)); + divider = readl(mempll->base + MEMPLL_DIVIDER); + + seq_printf(s, "declared rate : %lu Hz\n", mempll->rate); + seq_printf(s, "MEMPLL0 : 0x%08x\n", con0); + seq_printf(s, " EN : %u\n", !!(con0 & MEMPLL0_EN)); + seq_printf(s, " PREDIV : %lu\n", + FIELD_GET(MEMPLL0_PREDIV, con0)); + seq_printf(s, " POSDIV : %lu\n", + FIELD_GET(MEMPLL0_POSDIV, con0)); + seq_printf(s, " VCO_DIV_SEL : %u\n", + !!(con0 & MEMPLL0_VCO_DIV_SEL)); + seq_printf(s, " FBKSEL : %lu\n", + FIELD_GET(MEMPLL0_FBKSEL, con0)); + seq_printf(s, " FBDIV : %lu (not PLL1's divider, see header)\n", + FIELD_GET(MEMPLL0_FBDIV, con0)); + seq_printf(s, " MONCK_EN : %u\n", + !!(con0 & MEMPLL0_MONCK_EN)); + seq_printf(s, "MEMPLL1 : 0x%08x\n", con1); + seq_printf(s, " REFCK_SEL : %lu (%s)\n", + FIELD_GET(MEMPLL1_REFCK_SEL, con1), + mempll_refck_sel_name[FIELD_GET(MEMPLL1_REFCK_SEL, con1)]); + seq_printf(s, " REFCK_EN : %u\n", + !!(con1 & MEMPLL1_REFCK_EN)); + seq_printf(s, " BIAS_EN : %u\n", !!(con1 & MEMPLL1_BIAS_EN)); + seq_printf(s, " BIAS_LPF_EN : %u\n", + !!(con1 & MEMPLL1_BIAS_LPF_EN)); + seq_printf(s, " DIV_EN : %u\n", !!(con1 & MEMPLL1_DIV_EN)); + seq_printf(s, " DIV : %lu\n", FIELD_GET(MEMPLL1_DIV, con1)); + seq_printf(s, "MEMPLL_DIVIDER : 0x%08x\n", divider); + seq_printf(s, " ALL_CK_EN : %u\n", + !!(divider & MEMPLL_DIV_ALL_CK_EN)); + seq_printf(s, " 1PLL_SYNC_MODE : %u\n", + !!(divider & MEMPLL_DIV_1PLL_SYNC)); + seq_printf(s, " BYP_PLL3 : %u\n", + !!(divider & MEMPLL_DIV_BYP_PLL3)); + seq_printf(s, " BYP_PLL4 : %u\n", + !!(divider & MEMPLL_DIV_BYP_PLL4)); + + seq_puts(s, "\nper-PLL feedback dividers (MEMPLL2/3/4, p. 731f)\n"); + for (i = 2; i <= 4; i++) { + u32 con = readl(mempll->base + MEMPLL_CON(i)); + + seq_printf(s, " MEMPLL%u : 0x%08x FBDIV %lu PREDIV %lu\n", + i, con, FIELD_GET(GENMASK(8, 2), con), + FIELD_GET(GENMASK(1, 0), con)); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mt6589_mempll_dump); + +static int mt6589_mempll_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct clk_init_data init = {}; + struct mt6589_mempll *mempll; + struct resource *res; + u32 rate; + int ret; + + mempll = devm_kzalloc(dev, sizeof(*mempll), GFP_KERNEL); + if (!mempll) + return -ENOMEM; + + /* + * Map the ddrphy_conf page without reserving it. The same + * 0x1001_1000 page is also described by the DRAMC node (as its + * "ddrphy" window), and this driver only reads MEMPLL0..12 at +0x600 + * to report them through debugfs - it never writes, and it does not + * program the PLL. Reserving the region would make one of the two + * drivers lose to the other purely on probe order: this clock must + * win, because topckgen's clkph_mck hangs off it and mempll_mck_d4 + * would report 0 Hz without it. So take a plain devm_ioremap() over + * the first reg entry and leave region ownership to DRAMC, which + * tolerates -EBUSY on this window. + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return dev_err_probe(dev, -ENODEV, + "no ddrphy_conf reg entry\n"); + mempll->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!mempll->base) + return dev_err_probe(dev, -ENOMEM, + "failed to map ddrphy_conf window\n"); + + /* + * 266.5 MHz is the datasheet's own figure for this clock + * (clk_mem_sel/clk_ddrphycfg_sel "Maximum id 266.5MHz", and + * "MEMPLL_MCK_D4 (266.5/4)", p. 402). See the header for why it is + * declared instead of decoded from MEMPLL0. + */ + rate = 266500000; + ret = of_property_read_u32(dev->of_node, "clock-frequency", &rate); + if (ret && ret != -EINVAL) + return dev_err_probe(dev, ret, "invalid clock-frequency\n"); + if (!rate) + return dev_err_probe(dev, -EINVAL, "clock-frequency must not be zero\n"); + + mempll->rate = rate; + + init.name = "mempll"; + init.ops = &mt6589_mempll_ops; + init.flags = CLK_GET_RATE_NOCACHE; + + mempll->hw.init = &init; + + mempll->clk = devm_clk_register(dev, &mempll->hw); + if (IS_ERR(mempll->clk)) + return dev_err_probe(dev, PTR_ERR(mempll->clk), + "failed to register MEMPLL\n"); + + ret = of_clk_add_provider(dev->of_node, of_clk_src_simple_get, + mempll->clk); + if (ret) + return dev_err_probe(dev, ret, "failed to add clk provider\n"); + + platform_set_drvdata(pdev, mempll); + + mempll->debugfs = debugfs_create_file("mempll", 0444, NULL, mempll, + &mt6589_mempll_dump_fops); + + dev_info(dev, "MEMPLL %lu Hz, enabled %u (read-only)\n", + mempll->rate, mt6589_mempll_is_enabled(&mempll->hw)); + + return 0; +} + +static void mt6589_mempll_remove(struct platform_device *pdev) +{ + struct mt6589_mempll *mempll = platform_get_drvdata(pdev); + + debugfs_remove(mempll->debugfs); + of_clk_del_provider(pdev->dev.of_node); +} + +static const struct of_device_id mt6589_mempll_of_match[] = { + { .compatible = "mediatek,mt6589-mempll" }, + { } +}; +MODULE_DEVICE_TABLE(of, mt6589_mempll_of_match); + +static struct platform_driver mt6589_mempll_drv = { + .probe = mt6589_mempll_probe, + .remove = mt6589_mempll_remove, + .driver = { + .name = "mt6589-mempll", + .of_match_table = mt6589_mempll_of_match, + }, +}; +module_platform_driver(mt6589_mempll_drv); + +MODULE_DESCRIPTION("MediaTek MT6589 MEMPLL clock"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/mediatek/clk-mt6589-topckgen.c b/drivers/clk/mediatek/clk-mt6589-topckgen.c index fb03916e9f1c13..06bbc29594c2df 100644 --- a/drivers/clk/mediatek/clk-mt6589-topckgen.c +++ b/drivers/clk/mediatek/clk-mt6589-topckgen.c @@ -17,7 +17,18 @@ static const struct mtk_fixed_clk top_fixed_clks[] = { }; static const struct mtk_fixed_factor top_divs[] = { - FACTOR(CLK_TOP_CLKPH_MCK, "clkph_mck", "clk_null", 1, 1), + /* + * clkph_mck is the MEMPLL output (MEMPLL_MCK), the DRAM clock. + * It was parented to clk_null, a fixed clock of rate 0, so this + * clock and mempll_mck_d4 below it both reported 0 Hz. Parent it + * to the real MEMPLL instead (clk-mt6589-mempll.c); CCF reparents + * it when that driver registers. + * + * clk_null remains the parent of cpum_tck_in, which nothing in this + * tree consumes or sets a rate on, so leaving it at 0 Hz is honest + * rather than a fabricated number. + */ + FACTOR(CLK_TOP_CLKPH_MCK, "clkph_mck", "mempll", 1, 1), FACTOR(CLK_TOP_CPUM_TCK_IN, "cpum_tck_in", "clk_null", 1, 1), FACTOR(CLK_TOP_SYSPLL_D2, "syspll_d2", "mainpll_806m", 1, 2), @@ -56,6 +67,12 @@ static const struct mtk_fixed_factor top_divs[] = { FACTOR(CLK_TOP_LVDSPLL_D4, "lvdspll_d4", "lvdspll", 1, 4), FACTOR(CLK_TOP_LVDSPLL_D8, "lvdspll_d8", "lvdspll", 1, 8), + /* + * MEMPLL_MCK_D4 is MEMPLL divided by four. The datasheet names it + * directly as "MEMPLL_MCK_D4 (266.5/4)" in the clk_mem_sel and + * clk_pmicspi_sel descriptions (p. 402), confirming both the /4 and + * the 266.5 MHz MEMPLL_MCK feeding it. + */ FACTOR(CLK_TOP_MEMPLL_MCK_D4, "mempll_mck_d4", "clkph_mck", 1, 4), }; diff --git a/drivers/clk/mediatek/clk-mtk.c b/drivers/clk/mediatek/clk-mtk.c index c3369a04cdecf0..384391e57bae6c 100644 --- a/drivers/clk/mediatek/clk-mtk.c +++ b/drivers/clk/mediatek/clk-mtk.c @@ -416,9 +416,43 @@ int mtk_clk_register_dividers(struct device *dev, continue; } - hw = clk_hw_register_divider(dev, mcd->name, mcd->parent_name, - mcd->flags, base + mcd->div_reg, mcd->div_shift, - mcd->div_width, mcd->clk_divider_flags, lock); + if (mcd->ops) { + struct clk_divider *div; + struct clk_init_data init = { + .name = mcd->name, + .ops = mcd->ops, + .flags = mcd->flags, + .parent_names = &mcd->parent_name, + .num_parents = 1, + }; + int ret; + + div = kzalloc_obj(*div); + if (!div) { + hw = ERR_PTR(-ENOMEM); + goto err; + } + + div->reg = base + mcd->div_reg; + div->shift = mcd->div_shift; + div->width = mcd->div_width; + div->flags = mcd->clk_divider_flags; + div->lock = lock; + div->hw.init = &init; + + ret = clk_hw_register(dev, &div->hw); + if (ret) { + kfree(div); + hw = ERR_PTR(ret); + } else { + hw = &div->hw; + } + } else { + hw = clk_hw_register_divider(dev, mcd->name, + mcd->parent_name, mcd->flags, + base + mcd->div_reg, mcd->div_shift, + mcd->div_width, mcd->clk_divider_flags, lock); + } if (IS_ERR(hw)) { pr_err("Failed to register clk %s: %pe\n", mcd->name, @@ -603,7 +637,7 @@ static int __mtk_clk_simple_probe(struct platform_device *pdev, r = mtk_register_reset_controller_with_dev(&pdev->dev, mcd->rst_desc); if (r) - goto unregister_clks; + goto del_provider; } if (mcd->need_runtime_pm) @@ -611,6 +645,8 @@ static int __mtk_clk_simple_probe(struct platform_device *pdev, return r; +del_provider: + of_clk_del_provider(node); unregister_clks: if (mcd->clks) mtk_clk_unregister_gates(mcd->clks, mcd->num_clks, clk_data); diff --git a/drivers/clk/mediatek/clk-mtk.h b/drivers/clk/mediatek/clk-mtk.h index b6504b3e77ed6a..77aca20ee086db 100644 --- a/drivers/clk/mediatek/clk-mtk.h +++ b/drivers/clk/mediatek/clk-mtk.h @@ -213,6 +213,7 @@ struct mtk_clk_divider { unsigned char div_width; unsigned char clk_divider_flags; const struct clk_div_table *clk_div_table; + const struct clk_ops *ops; }; #define DIV_ADJ(_id, _name, _parent, _reg, _shift, _width) { \ diff --git a/drivers/clk/mediatek/clk-pll.c b/drivers/clk/mediatek/clk-pll.c index 87c2b78e3828a9..cba116fcadcd24 100644 --- a/drivers/clk/mediatek/clk-pll.c +++ b/drivers/clk/mediatek/clk-pll.c @@ -72,6 +72,13 @@ static unsigned long __mtk_pll_recalc_rate(struct mtk_clk_pll *pll, u32 fin, return ((unsigned long)vco + postdiv - 1) / postdiv; } +static bool mtk_pll_postdiv_is_valid(const struct mtk_clk_pll *pll, + u32 val) +{ + return !pll->data->pd_valid_mask || + (pll->data->pd_valid_mask & BIT(val)); +} + static void __mtk_pll_tuner_enable(struct mtk_clk_pll *pll) { u32 r; @@ -152,6 +159,8 @@ void mtk_pll_calc_values(struct mtk_clk_pll *pll, u32 *pcw, u32 *postdiv, u64 _pcw; int ibits; u32 val; + u32 last_val = 0; + bool found = false; if (freq > pll->data->fmax) freq = pll->data->fmax; @@ -167,10 +176,27 @@ void mtk_pll_calc_values(struct mtk_clk_pll *pll, u32 *pcw, u32 *postdiv, *postdiv = 1 << val; } else { for (val = 0; val < 5; val++) { + if (!mtk_pll_postdiv_is_valid(pll, val)) + continue; + *postdiv = 1 << val; + last_val = val; if ((u64)freq * *postdiv >= fmin) + found = true; + if (found) break; } + + /* + * If the requested rate is below the minimum VCO constraint, + * use the largest valid post-divider instead of leaving @val + * past the end of the loop. This also keeps SoCs with reserved + * post-divider encodings from selecting an invalid value. + */ + if (!found) { + val = last_val; + *postdiv = 1 << val; + } } /* _pcw = freq * postdiv / fin * 2^pcwfbits */ @@ -202,6 +228,9 @@ unsigned long mtk_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) u32 mask = pll->data->pd_mask ?: POSTDIV_MASK; postdiv = (readl(pll->pd_addr) >> pll->data->pd_shift) & mask; + if (!mtk_pll_postdiv_is_valid(pll, postdiv)) + return 0; + postdiv = 1 << postdiv; pcw = readl(pll->pcw_addr) >> pll->data->pcw_shift; diff --git a/drivers/clk/mediatek/clk-pll.h b/drivers/clk/mediatek/clk-pll.h index 06e9daced68114..224f09f665cac2 100644 --- a/drivers/clk/mediatek/clk-pll.h +++ b/drivers/clk/mediatek/clk-pll.h @@ -31,6 +31,7 @@ struct mtk_pll_data { u32 fenc_sta_ofs; u32 pd_reg; u32 pd_mask; /* default POSTDIV_MASK if 0 */ + u8 pd_valid_mask; /* valid encoded postdiv values, 0 means all */ u32 tuner_reg; u32 tuner_en_reg; u8 tuner_en_bit; @@ -59,7 +60,9 @@ struct mtk_pll_data { * MediaTek PLLs are configured through their pcw value. The pcw value describes * a divider in the PLL feedback loop which consists of 7 bits for the integer * part and the remaining bits (if present) for the fractional part. Also they - * have a 3 bit power-of-two post divider. + * have a power-of-two post divider. Some SoCs reserve post-divider encodings + * for other functions, so mtk_pll_data::pd_valid_mask describes which + * encodings are actually valid. */ struct mtk_clk_pll { diff --git a/drivers/clk/mediatek/clk-pllfh.c b/drivers/clk/mediatek/clk-pllfh.c index aa95cd9197b3c1..6feeb4c372f11a 100644 --- a/drivers/clk/mediatek/clk-pllfh.c +++ b/drivers/clk/mediatek/clk-pllfh.c @@ -64,7 +64,7 @@ void fhctl_parse_dt(const u8 *compatible_node, struct mtk_pllfh_data *pllfhs, { void __iomem *base; struct device_node *node; - u32 num_clocks, pll_id, ssc_rate; + u32 num_clocks, pll_id, ssc_rate, fh_capable; int offset, i; node = of_find_compatible_node(NULL, NULL, compatible_node); @@ -90,16 +90,46 @@ void fhctl_parse_dt(const u8 *compatible_node, struct mtk_pllfh_data *pllfhs, offset = i * 2; - of_property_read_u32_index(node, "clocks", offset + 1, &pll_id); + if (of_property_read_u32_index(node, "clocks", + offset + 1, &pll_id)) { + pr_err("%s(): invalid clocks entry %d\n", + __func__, i); + goto err; + } + + /* + * The SSC property is optional; omitted entries mean 0%. + * SSC only modulates the clock around the nominal frequency, + * it says nothing about whether the FHCTL hop works here, so + * it must not be used to gate the hardware path. + */ + ssc_rate = 0; of_property_read_u32_index(node, "mediatek,hopping-ssc-percent", i, &ssc_rate); + /* + * Being listed in "clocks" only means the channel is wired to + * an FHCTL channel. Whether the hop sequence actually + * completes on it is a separate, per-board question, so boards + * can opt in per channel. + * + * When the property is absent entirely - every SoC but MT6589 + * today - fall back to the historical behaviour of taking the + * hop whenever SSC is configured, so no existing board changes + * under us. + */ + if (of_property_read_u32_index(node, + "mediatek,fhctl-hopping-enabled", + i, &fh_capable)) + fh_capable = ssc_rate ? 1 : 0; + pllfh = get_pllfh_by_id(pllfhs, num_fhs, pll_id); if (!pllfh) continue; pllfh->state.fh_enable = 1; + pllfh->state.fh_capable = fh_capable ? 1 : 0; pllfh->state.ssc_rate = ssc_rate; pllfh->state.base = base; } @@ -113,7 +143,9 @@ void fhctl_parse_dt(const u8 *compatible_node, struct mtk_pllfh_data *pllfhs, } EXPORT_SYMBOL_GPL(fhctl_parse_dt); -static int pllfh_init(struct mtk_fh *fh, struct mtk_pllfh_data *pllfh_data) +static int pllfh_init(struct mtk_fh *fh, + struct mtk_pllfh_data *pllfh_data, + void __iomem *pll_base) { struct fh_pll_regs *regs = &fh->regs; const struct fhctl_offset *offset; @@ -124,11 +156,21 @@ static int pllfh_init(struct mtk_fh *fh, struct mtk_pllfh_data *pllfh_data) if (IS_ERR(offset)) return PTR_ERR(offset); - regs->reg_hp_en = base + offset->offset_hp_en; - regs->reg_clk_con = base + offset->offset_clk_con; - regs->reg_rst_con = base + offset->offset_rst_con; - regs->reg_slope0 = base + offset->offset_slope0; - regs->reg_slope1 = base + offset->offset_slope1; + if (pllfh_data->data.fh_ver == FHCTL_PLLFH_V3) + { + regs->reg_hp_en = pll_base + offset->offset_hp_en; + regs->reg_clk_con = NULL; + regs->reg_rst_con = NULL; + regs->reg_slope0 = NULL; + regs->reg_slope1 = NULL; + } + else { + regs->reg_hp_en = base + offset->offset_hp_en; + regs->reg_clk_con = base + offset->offset_clk_con; + regs->reg_rst_con = base + offset->offset_rst_con; + regs->reg_slope0 = base + offset->offset_slope0; + regs->reg_slope1 = base + offset->offset_slope1; + } regs->reg_cfg = fhx_base + offset->offset_cfg; regs->reg_updnlmt = fhx_base + offset->offset_updnlmt; @@ -161,7 +203,7 @@ mtk_clk_register_pllfh(struct device *dev, const struct mtk_pll_data *pll_data, if (!fh) return ERR_PTR(-ENOMEM); - ret = pllfh_init(fh, pllfh_data); + ret = pllfh_init(fh, pllfh_data, base); if (ret) { hw = ERR_PTR(ret); goto out; diff --git a/drivers/clk/mediatek/clk-pllfh.h b/drivers/clk/mediatek/clk-pllfh.h index a4f337acad7138..72a8e9ee0126cd 100644 --- a/drivers/clk/mediatek/clk-pllfh.h +++ b/drivers/clk/mediatek/clk-pllfh.h @@ -11,7 +11,17 @@ struct fh_pll_state { void __iomem *base; + /* Channel is wired to an FHCTL channel ("clocks" of the fhctl node). */ u32 fh_enable; + /* + * The FHCTL hop sequence is known to work on this channel, so + * frequency changes go through the hardware rather than being + * written straight into the PCW field. Independent of @fh_enable: + * being wired up to the FHCTL block does not mean the hop completes. + * Set from mediatek,fhctl-hopping-enabled. + */ + u32 fh_capable; + /* Spread-spectrum modulation depth in percent; 0 disables SSC. */ u32 ssc_rate; }; diff --git a/drivers/clk/mediatek/reset.c b/drivers/clk/mediatek/reset.c index 2e3303975096a4..312c77009f320a 100644 --- a/drivers/clk/mediatek/reset.c +++ b/drivers/clk/mediatek/reset.c @@ -113,7 +113,7 @@ static int reset_xlate(struct reset_controller_dev *rcdev, int mtk_register_reset_controller_with_dev(struct device *dev, const struct mtk_clk_rst_desc *desc) { - struct device_node *np = dev->of_node; + struct device_node *np; struct regmap *regmap; const struct reset_control_ops *rcops = NULL; struct mtk_clk_rst_data *data; @@ -124,6 +124,10 @@ int mtk_register_reset_controller_with_dev(struct device *dev, return -EINVAL; } + np = dev->of_node; + if (!np) + return -EINVAL; + switch (desc->version) { case MTK_RST_SIMPLE: rcops = &mtk_reset_ops; diff --git a/drivers/dma/mediatek/Kconfig b/drivers/dma/mediatek/Kconfig index 7a46a545595732..636da9a56a0679 100644 --- a/drivers/dma/mediatek/Kconfig +++ b/drivers/dma/mediatek/Kconfig @@ -1,5 +1,22 @@ # SPDX-License-Identifier: GPL-2.0-only +config MTK_GDMA + tristate "MediaTek AP DMA General DMA controller support" + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on RESET_CONTROLLER + select DMA_ENGINE + help + Enable support for the AP DMA General DMA (G_DMA) engines found on + MediaTek MT6589 SoCs. + + The AP DMA block holds eighteen engines, but only G_DMA0 and G_DMA1 + are generic; the rest are wired to HIF, IrDA, I2C and UART. These + two have no descriptor ring and no scatter-gather support: a + transfer is programmed straight into the SRC/DST/LEN1/LEN2 + registers and completes through a single interrupt flag. + + If unsure, say N. + config MTK_HSDMA tristate "MediaTek High-Speed DMA controller support" depends on ARCH_MEDIATEK || COMPILE_TEST diff --git a/drivers/dma/mediatek/Makefile b/drivers/dma/mediatek/Makefile index 5ba39a5edc1300..f86e768554f508 100644 --- a/drivers/dma/mediatek/Makefile +++ b/drivers/dma/mediatek/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_MTK_GDMA) += mtk-gdma.o obj-$(CONFIG_MTK_UART_APDMA) += mtk-uart-apdma.o obj-$(CONFIG_MTK_HSDMA) += mtk-hsdma.o obj-$(CONFIG_MTK_CQDMA) += mtk-cqdma.o diff --git a/drivers/dma/mediatek/mtk-gdma.c b/drivers/dma/mediatek/mtk-gdma.c new file mode 100644 index 00000000000000..47bdce646fc3e9 --- /dev/null +++ b/drivers/dma/mediatek/mtk-gdma.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 MediaTek, Inc. + * + * Driver for the MT6589 AP DMA "General DMA" (G_DMA) engines. + * + * Data sheet chapter 18 ("AP DMA"). The block contains 18 engines; only + * G_DMA0 and G_DMA1 are generic, they sit at 0x11000080 and 0x11000100, and + * they are the only two with their own interrupt (GIC SPI 57 and 58). + * + * AP_DMA has no descriptor ring and no scatter-gather list: a transfer is + * programmed as plain SRC/DST/LEN1/LEN2 registers and the completion is + * reported through a single INT_FLAG bit. That is why the sibling + * mtk-cqdma.c is the right model for the transfer side and not fsl-edma or + * axi-dmac, all of which need a descriptor linked list built in RAM. + * + * What this first commit deliberately does NOT contain is any transfer logic. + * Nothing in the tree consumes these two channels yet, and there is one + * register question that cannot be answered from the data sheet or from the + * vendor source: the polarity of the two completion interrupts. Until that is + * settled on hardware, the only useful thing a driver can do is prove that + * the clock, the reset and the interrupt line are wired up, which is what + * this commit is. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtk-gdma.h" + +#define MTK_GDMA_RESET_TIMEOUT_US 2000000 + +/** + * struct mtk_gdma_channel - one of the two general DMA engines + * @dev: the parent platform device, used for logging + * @base: base address of this engine's register block + * @irq: the completion interrupt for this engine + */ +struct mtk_gdma_channel { + struct device *dev; + void __iomem *base; + int irq; +}; + +/** + * struct mtk_gdma - the AP DMA General DMA block + * @dev: the platform device + * @regs: the mapped 0x180 register window, global registers at offset 0 + * @chan: the per-engine state, indexed by engine number + * @clk: the "main" clock gating the whole block + * @rstc: the "main" software reset for the block + */ +struct mtk_gdma { + struct device *dev; + void __iomem *regs; + struct mtk_gdma_channel chan[MTK_GDMA_NR_CHANNELS]; + struct clk *clk; + struct reset_control *rstc; +}; + +/* + * Perform the global warm reset the data sheet describes in section 18.5.1 + * (p. 928): set WARM_RST, poll the global running status until every engine + * has gone idle, then clear WARM_RST again. + * + * A hard reset is deliberately not used. The same section warns that it + * "may break the bus protocol and cause system hang", because it drops + * transactions that will then never complete. + * + * The block's own reset register is only half of the job; the peripheral + * domain also has a software reset bit (PERICFG PERI_GLOBALCON_RST0 bit 11, + * data sheet p. 469) which the caller takes care of through the reset + * framework before we get here. + */ +static void mtk_gdma_reset(struct mtk_gdma *gdma) +{ + struct device *dev = gdma->dev; + u32 status; + + writel(MTK_GDMA_RST_WARM_RST, gdma->regs + MTK_GDMA_GLOBAL_RST); + + status = readl_poll_timeout(gdma->regs + MTK_GDMA_GLOBAL_RUNNING_STATUS, + status, !status, 10, + MTK_GDMA_RESET_TIMEOUT_US); + if (status) + dev_warn(dev, "timed out waiting for engines to stop, status %#x\n", + status); + + /* Clear even on timeout: leaving WARM_RST set would wedge the block. */ + writel(0, gdma->regs + MTK_GDMA_GLOBAL_RST); +} + +/* + * Completion handler. This does nothing beyond acknowledging the interrupt + * so that the line can be proven reachable at all; see the note on IRQ + * polarity in mtk_gdma_probe() and in the commit message. + */ +static irqreturn_t mtk_gdma_irq_handler(int irq, void *data) +{ + struct mtk_gdma_channel *channel = data; + + /* Writing 0 clears FLAG (data sheet p.887). */ + writel(0, channel->base + MTK_GDMA_INT_FLAG); + + /* + * Rate limited on purpose. If the line really is level triggered but + * the flag does not clear it, this handler is re-entered immediately + * and an unconditional dev_info() per call would flood the console and + * hide the very fact this commit wants to demonstrate. + */ + dev_info_ratelimited(channel->dev, "interrupt %d taken\n", irq); + + return IRQ_HANDLED; +} + +static int mtk_gdma_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_gdma *gdma; + unsigned int i; + int ret; + + gdma = devm_kzalloc(dev, sizeof(*gdma), GFP_KERNEL); + if (!gdma) + return -ENOMEM; + + gdma->dev = dev; + platform_set_drvdata(pdev, gdma); + + gdma->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(gdma->regs)) + return PTR_ERR(gdma->regs); + + gdma->clk = devm_clk_get(dev, "main"); + if (IS_ERR(gdma->clk)) + return dev_err_probe(dev, PTR_ERR(gdma->clk), + "failed to get main clock\n"); + + ret = clk_prepare_enable(gdma->clk); + if (ret) + return dev_err_probe(dev, ret, "failed to enable main clock\n"); + + gdma->rstc = devm_reset_control_get(dev, "main"); + if (IS_ERR(gdma->rstc)) + return dev_err_probe(dev, PTR_ERR(gdma->rstc), + "failed to get main reset\n"); + + ret = reset_control_assert(gdma->rstc); + if (ret) + return dev_err_probe(dev, ret, "failed to assert main reset\n"); + + ret = reset_control_deassert(gdma->rstc); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert main reset\n"); + + mtk_gdma_reset(gdma); + + /* + * Channel N lives 0x80 bytes apart, G_DMA0 first (data sheet p.887). + */ + for (i = 0; i < MTK_GDMA_NR_CHANNELS; i++) { + struct mtk_gdma_channel *channel = &gdma->chan[i]; + int irq; + + irq = platform_get_irq(pdev, i); + if (irq < 0) + return dev_err_probe(dev, irq, + "failed to get irq for channel %u\n", i); + + channel->dev = dev; + channel->irq = irq; + channel->base = gdma->regs + 0x80 * (i + 1); + + /* + * Data sheet Table 7-1 lists SPI 57 (gdma0) and 58 (gdma1) as + * "Positive", and that is what the DT node asks for. The + * vendor driver asks for the opposite: it passes + * IRQF_TRIGGER_LOW to request_irq() (mt_dma.c:502,511) while + * the lines immediately above that call, which set + * MT65xx_POLARITY_LOW through MT6577_* helpers, are + * commented out. Those helpers name MT6577, not MT6589, so + * this looks like a copy from a SoC whose polarity differs and + * the polarity half was dropped. + * + * Going with the data sheet for now. The handler above does + * nothing but acknowledge the interrupt precisely so that one + * hardware run settles the question; commit 2 (transfers) + * depends on the answer. + */ + ret = devm_request_threaded_irq(dev, irq, mtk_gdma_irq_handler, + NULL, IRQF_ONESHOT, + dev_name(dev), channel); + if (ret) + return dev_err_probe(dev, ret, + "failed to request irq %d for channel %u\n", + irq, i); + + dev_info(dev, "channel %u at %pa, irq %d\n", i, &channel->base, + irq); + } + + return 0; +} + +static void mtk_gdma_remove(struct platform_device *pdev) +{ + struct mtk_gdma *gdma = platform_get_drvdata(pdev); + + /* + * devm unhooks the interrupts, the mapping and the reset control when + * we return, but not the clock: devm_clk_get() only drops the + * reference, and clk_prepare_enable() has to be undone by hand. + * + * There is deliberately no per-engine flush here. This commit starts + * no transfer, so no engine can be running when we get called; the + * flush-and-wait-for-idle dance that a real remove() needs arrives + * with the transfer code in commit 2, where it can be written against + * a channel that is known to be doing something. + */ + clk_disable_unprepare(gdma->clk); +} + +static const struct of_device_id mtk_gdma_of_match[] = { + { .compatible = "mediatek,mt6589-gdma" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mtk_gdma_of_match); + +static struct platform_driver mtk_gdma_driver = { + .probe = mtk_gdma_probe, + .remove = mtk_gdma_remove, + .driver = { + .name = "mtk-gdma", + .of_match_table = mtk_gdma_of_match, + }, +}; +module_platform_driver(mtk_gdma_driver); + +MODULE_DESCRIPTION("MediaTek MT6589 AP DMA General DMA controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dma/mediatek/mtk-gdma.h b/drivers/dma/mediatek/mtk-gdma.h new file mode 100644 index 00000000000000..97370f3e38112f --- /dev/null +++ b/drivers/dma/mediatek/mtk-gdma.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2026 MediaTek, Inc. + * + * Register map for the MT6589 AP_DMA "General DMA" (G_DMA) engines. + * + * The block is described in the MT6589 data sheet, chapter 18 ("AP DMA"): + * section 18.4.1 covers the global control registers (p. 870-872) and + * section 18.4.2 the GDMA registers (p. 887 onwards). The layout below is + * corroborated against the vendor driver + * aquaris-5/mediatek/platform/mt6589/kernel/core/mt_dma.c:33-57, which + * spells the very same offsets with its own DMA_BASE_CH()/DMA_xxx() macros. + * Where the two disagree the data sheet wins; they do not, as far as + * commit 1 looks. + */ + +#ifndef _MTK_GDMA_H +#define _MTK_GDMA_H + +#include +#include + +/* AP_DMA register window, data sheet p. 869 ("(+11000000h)"). */ +#define MTK_GDMA_REG_BASE 0x11000000 + +/* + * How many of the block's 18 engines are generic. Data sheet p. 869: "There + * are total 18 channels in DMA of MT6589" - the other sixteen belong to HIF, + * IrDA, I2C and UART. Only G_DMA0 and G_DMA1 are handed out to general + * purpose callers; they are the two described in section 18.4.2 and they are + * the only ones with their own interrupt line (SPI 57/58, data sheet + * Table 7-1). Matches mt_dma.c:26 (NR_GDMA_CHANNEL). + */ +#define MTK_GDMA_NR_CHANNELS 2 + +/* + * Global control registers, data sheet p. 870-872 (section 18.4.1). These + * are the only "global" registers that exist; the list stops at 0x78 because + * G_DMA0's own register block starts at 0x80. + */ +/* data sheet p.871 - per-engine interrupt flag, one bit per engine */ +#define MTK_GDMA_GLOBAL_INT_FLAG 0x00 +/* data sheet p.872 - bit1 HARD_RST, bit0 WARM_RST */ +#define MTK_GDMA_GLOBAL_RST 0x04 +/* data sheet p.872 - bit0 = G_DMA0, bit1 = G_DMA1, ... bit17 = UART2_RX */ +#define MTK_GDMA_GLOBAL_RUNNING_STATUS 0x08 +/* data sheet p.872 - bit0 = G_DMA0 running, bit1 = G_DMA1 running */ +#define MTK_GDMA_GLOBAL_STATUS_GDMA0 BIT(0) +#define MTK_GDMA_GLOBAL_STATUS_GDMA1 BIT(1) +/* data sheet p.872 - AXI slow down control */ +#define MTK_GDMA_GLOBAL_SLOW_DOWN 0x0c +/* data sheet p.872 - per-engine security enable */ +#define MTK_GDMA_GLOBAL_SEC_EN 0x10 +/* data sheet p.872 - global security enable, gates every global register */ +#define MTK_GDMA_GLOBAL_GSEC_EN 0x14 +/* data sheet p.872 - security violation debug, write-1-to-clear */ +#define MTK_GDMA_GLOBAL_VIO_DBG1 0x18 +#define MTK_GDMA_GLOBAL_VIO_DBG0 0x1c + +/* data sheet p.872 */ +#define MTK_GDMA_RST_HARD_RST BIT(1) +#define MTK_GDMA_RST_WARM_RST BIT(0) + +/* + * Per-engine registers. Data sheet p. 887 gives the G_DMA0 block at + * 0x11000080 and states "For GDMA_1 engine, it has exactly the same + * registers with offset 0x80 to G_DMA_0", i.e. the channel stride is 0x80. + * That agrees with mt_dma.c:36, DMA_BASE_CH(n) = AP_DMA_BASE + 0x80 * (n + 1). + * + * Note the vendor file's block names (EN/START) and the data sheet's (EN) are + * the same register; mt_dma.c calls DMA_START what the data sheet calls + * AP_DMA_G_DMA_n_EN. Use the data sheet spelling here. + */ +/* data sheet p.887 */ +#define MTK_GDMA_INT_FLAG 0x00 +/* data sheet p.887 - bit0 INTEN gates delivery, the flag still sets without it */ +#define MTK_GDMA_INT_EN 0x04 +/* data sheet p.887 - bit0 EN; set to 1 to start, hardware clears when done */ +#define MTK_GDMA_EN 0x08 +/* data sheet p.888 - bit1 HARD_RST, bit0 WARM_RST */ +#define MTK_GDMA_RST 0x0c +/* data sheet p.888 - bit0 PAUSE */ +#define MTK_GDMA_STOP 0x10 +/* data sheet p.887 */ +#define MTK_GDMA_FLUSH 0x14 +/* data sheet p.887, bitfields p. 890-891 */ +#define MTK_GDMA_CON 0x18 +/* data sheet p.892 */ +#define MTK_GDMA_SRC 0x1c +#define MTK_GDMA_DST 0x20 +/* data sheet p.893 - both are 20 bit, hence the 1 MiB ceiling below */ +#define MTK_GDMA_LEN1 0x24 +#define MTK_GDMA_LEN2 0x28 +/* data sheet p.893 - only takes effect when CON.WRAP_EN is set */ +#define MTK_GDMA_JUMP_ADDR 0x2c +/* data sheet p.894 - read-only, bytes sitting in the internal buffer */ +#define MTK_GDMA_INT_BUF_SIZE 0x30 +/* data sheet p.894 - request/ack wiring for the peripheral-facing engines */ +#define MTK_GDMA_CONNECT 0x34 +/* data sheet p.894 - AWCACHE/ARCACHE plus the coherent-bus USER bits */ +#define MTK_GDMA_AXIATTR 0x38 +/* data sheet p.894 */ +#define MTK_GDMA_DEBUG_STATUS 0x50 + +#define MTK_GDMA_INT_FLAG_BIT BIT(0) +#define MTK_GDMA_INT_EN_BIT BIT(0) +#define MTK_GDMA_EN_BIT BIT(0) +#define MTK_GDMA_FLUSH_BIT BIT(0) +#define MTK_GDMA_PAUSE_BIT BIT(0) +#define MTK_GDMA_FLAG BIT(0) + +/* + * CON bitfields, data sheet p. 890-891. mt_dma.c:69-85 encodes the same + * values (DMA_CON_RSIZE_2BYTE = 0x10000000 etc.) but only exercises the + * subset that mtk-cqdma-style memory-to-memory transfers need. + */ +#define MTK_GDMA_CON_RSIZE_MASK GENMASK(29, 28) +#define MTK_GDMA_CON_RSIZE_1BYTE 0x00000000 +#define MTK_GDMA_CON_RSIZE_2BYTE 0x10000000 +#define MTK_GDMA_CON_RSIZE_4BYTE 0x20000000 +#define MTK_GDMA_CON_WSIZE_MASK GENMASK(25, 24) +#define MTK_GDMA_CON_WSIZE_1BYTE 0x00000000 +#define MTK_GDMA_CON_WSIZE_2BYTE 0x01000000 +#define MTK_GDMA_CON_WSIZE_4BYTE 0x02000000 +/* 0: wrap the source pointer, 1: wrap the destination pointer */ +#define MTK_GDMA_CON_WRAP_SEL BIT(20) +/* 0 is a 1-beat burst, the data sheet calls 3 "the best case" */ +#define MTK_GDMA_CON_BURST_LEN_MASK GENMASK(18, 16) +#define MTK_GDMA_CON_BURST_LEN_4 0x00030000 +#define MTK_GDMA_CON_WRAP_EN BIT(15) +/* insert up to 1023 idle cycles; read side only, throttles throughput */ +#define MTK_GDMA_CON_SLOW_CNT_MASK GENMASK(14, 5) +#define MTK_GDMA_CON_RADDR_FIX_EN BIT(4) +#define MTK_GDMA_CON_WADDR_FIX_EN BIT(3) +#define MTK_GDMA_CON_SLOW_EN BIT(2) +/* repeat inserting SRC as a fixed pattern; outranks the two FIX_EN bits */ +#define MTK_GDMA_CON_FIX_EN BIT(1) +#define MTK_GDMA_CON_DIR BIT(0) + +/* + * AXI attribute bits, data sheet p. 894. mt_dma.c:93-94 uses these to reach + * SYSRAM through the coherent bus. + */ +#define MTK_GDMA_AXIATTR_WUSER BIT(20) +#define MTK_GDMA_AXIATTR_RUSER BIT(4) + +/* + * Data sheet p. 893: LEN1 and LEN2 are 19:0, so a single length cannot exceed + * 0xFFFFF bytes. mt_dma.c:68-69 spells the same limit (MAX_TRANSFER_LEN1). + * How the vendor splits a transfer larger than that across the two length + * registers is *not* documented in the data sheet and is not implemented + * here - see the commit message. + */ +#define MTK_GDMA_LEN_MASK GENMASK(19, 0) +#define MTK_GDMA_MAX_LEN 0xfffff + +#endif /* _MTK_GDMA_H */ diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig index d7fe265c286965..017cf1ef37b49b 100644 --- a/drivers/gnss/Kconfig +++ b/drivers/gnss/Kconfig @@ -16,6 +16,17 @@ if GNSS config GNSS_SERIAL tristate +config GNSS_MT6628 + tristate "MediaTek MT6628 GNSS receiver support" + depends on MTK_MT6628_STP + help + Say Y here if you have a MediaTek MT6628 combo chip and want to + use its GNSS receiver. The receiver uses the shared BGF/STP + control channel over the selected UART or SDIO transport. + + To compile this driver as a module, choose M here: the module will + be called gnss-mt6628. + config GNSS_MTK_SERIAL tristate "Mediatek GNSS receiver support" depends on SERIAL_DEV_BUS diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile index bb2cbada34359b..5e6476ca684dd3 100644 --- a/drivers/gnss/Makefile +++ b/drivers/gnss/Makefile @@ -18,5 +18,6 @@ gnss-sirf-y := sirf.o obj-$(CONFIG_GNSS_UBX_SERIAL) += gnss-ubx.o gnss-ubx-y := ubx.o +obj-$(CONFIG_GNSS_MT6628) += gnss-mt6628.o obj-$(CONFIG_GNSS_USB) += gnss-usb.o gnss-usb-y := usb.o diff --git a/drivers/gnss/gnss-mt6628.c b/drivers/gnss/gnss-mt6628.c new file mode 100644 index 00000000000000..9405e49b01cb4f --- /dev/null +++ b/drivers/gnss/gnss-mt6628.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MediaTek MT6628 GNSS driver + * + * Copyright (c) 2026 Akari Tsuyukusa + * + * The GPS receiver of the MT6628 is driven by the on-chip MCU; the host + * only relays bytes between the STP control channel (SDIO function 2, + * task type GPS) and userspace. The downstream stack terminates that + * stream in a vendor daemon (mnld); here the stream is exposed through + * the standard GNSS framework so any NMEA consumer works. + * + * The chip is powered up by the WMT FUNC_ON command for the GPS driver + * type, which the WMT/bring-up owner issues before this device sees + * traffic. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +struct mtk_gnss { + struct gnss_device *gdev; + struct mt6628_wmt *wmt; + struct mutex lock; + struct pinctrl *pinctrl; + struct pinctrl_state *pinctrl_default; + struct pinctrl_state *gps_sync; + bool open; + bool resume_pending; +}; + +static void mtk_gnss_rx(void *priv, const u8 *buf, size_t len) +{ + struct mtk_gnss *gnss = priv; + + mutex_lock(&gnss->lock); + if (gnss->open) { + int ret; + + dev_dbg(&gnss->gdev->dev, "STP GPS RX: %zu bytes\n", len); + ret = gnss_insert_raw(gnss->gdev, buf, len); + if (ret >= 0 && (size_t)ret < len) + dev_warn_ratelimited(&gnss->gdev->dev, + "GNSS RX FIFO full; dropped %zu of %zu bytes\n", + len - (size_t)ret, len); + } else { + dev_dbg(&gnss->gdev->dev, + "discarding STP GPS data while GNSS is closed: %zu bytes\n", + len); + } + mutex_unlock(&gnss->lock); +} + +static int mtk_gnss_write_raw(struct gnss_device *gdev, const u8 *buf, + size_t len) +{ + struct mtk_gnss *priv = gnss_get_drvdata(gdev); + int ret; + + if (!READ_ONCE(priv->open)) + return -EHOSTDOWN; + + ret = mt6628_stp_send(priv->wmt, MT6628_STP_TASK_GPS, buf, len); + + if (ret) + return ret; + + return len; +} + +static int mtk_gnss_open(struct gnss_device *gdev) +{ + struct mtk_gnss *priv = gnss_get_drvdata(gdev); + int ret; + + mutex_lock(&priv->lock); + if (priv->open) { + mutex_unlock(&priv->lock); + return 0; + } + mutex_unlock(&priv->lock); + + if (priv->gps_sync) { + ret = pinctrl_select_state(priv->pinctrl, + priv->gps_sync); + if (ret) + return ret; + } + + ret = mt6628_wmt_gps_sync_ctrl(priv->wmt, true); + if (ret) + goto err_restore_pinctrl; + + ret = mt6628_wmt_func_ctrl(priv->wmt, MT6628_WMT_FUNC_GPS, true); + if (ret) { + mt6628_wmt_gps_sync_ctrl(priv->wmt, false); + goto err_restore_pinctrl; + } + + mutex_lock(&priv->lock); + WRITE_ONCE(priv->open, true); + priv->resume_pending = false; + mutex_unlock(&priv->lock); + + return 0; + +err_restore_pinctrl: + if (priv->pinctrl_default) + pinctrl_select_state(priv->pinctrl, priv->pinctrl_default); + + return ret; +} + +static void mtk_gnss_close(struct gnss_device *gdev) +{ + struct mtk_gnss *priv = gnss_get_drvdata(gdev); + bool was_open; + + mutex_lock(&priv->lock); + was_open = priv->open; + WRITE_ONCE(priv->open, false); + priv->resume_pending = false; + mutex_unlock(&priv->lock); + + if (was_open) { + mt6628_wmt_gps_sync_ctrl(priv->wmt, false); + if (priv->pinctrl_default) + pinctrl_select_state(priv->pinctrl, + priv->pinctrl_default); + mt6628_wmt_func_ctrl(priv->wmt, MT6628_WMT_FUNC_GPS, false); + } +} + +static const struct gnss_operations mtk_gnss_ops = { + .open = mtk_gnss_open, + .close = mtk_gnss_close, + .write_raw = mtk_gnss_write_raw, +}; + +static int mtk_gnss_probe(struct platform_device *pdev) +{ + struct mtk_gnss *priv; + struct gnss_device *gdev; + struct mt6628_wmt *wmt; + int ret; + + wmt = dev_get_drvdata(pdev->dev.parent); + if (!wmt) + return -EPROBE_DEFER; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->wmt = wmt; + mutex_init(&priv->lock); + + priv->pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR(priv->pinctrl)) { + ret = PTR_ERR(priv->pinctrl); + if (ret == -ENODEV) + priv->pinctrl = NULL; + else + return ret; + } + + if (priv->pinctrl) { + priv->pinctrl_default = pinctrl_lookup_state(priv->pinctrl, + PINCTRL_STATE_DEFAULT); + if (IS_ERR(priv->pinctrl_default)) + priv->pinctrl_default = NULL; + + priv->gps_sync = pinctrl_lookup_state(priv->pinctrl, + "gps-sync"); + if (IS_ERR(priv->gps_sync)) { + dev_dbg(&pdev->dev, + "GPS_SYNC pinctrl state not provided\n"); + priv->gps_sync = NULL; + } + } + + gdev = gnss_allocate_device(&pdev->dev); + if (!gdev) + return -ENOMEM; + + priv->gdev = gdev; + gdev->ops = &mtk_gnss_ops; + gdev->type = GNSS_TYPE_MTK; + gnss_set_drvdata(gdev, priv); + + ret = mt6628_stp_register_rx(wmt, MT6628_STP_TASK_GPS, + mtk_gnss_rx, priv); + if (ret) + goto err_put; + + ret = gnss_register_device(gdev); + if (ret) { + mt6628_stp_unregister_rx(wmt, MT6628_STP_TASK_GPS, + mtk_gnss_rx, priv); + goto err_put; + } + + platform_set_drvdata(pdev, priv); + dev_info(&pdev->dev, "MT6628 GNSS registered\n"); + + return 0; + +err_put: + gnss_put_device(gdev); + return ret; +} + +static void mtk_gnss_remove(struct platform_device *pdev) +{ + struct mtk_gnss *priv = platform_get_drvdata(pdev); + bool was_open; + + if (!priv) + return; + + mutex_lock(&priv->lock); + was_open = priv->open; + WRITE_ONCE(priv->open, false); + priv->resume_pending = false; + mutex_unlock(&priv->lock); + + if (was_open) { + mt6628_wmt_gps_sync_ctrl(priv->wmt, false); + if (priv->pinctrl_default) + pinctrl_select_state(priv->pinctrl, + priv->pinctrl_default); + mt6628_wmt_func_ctrl(priv->wmt, MT6628_WMT_FUNC_GPS, false); + } + + mt6628_stp_unregister_rx(priv->wmt, MT6628_STP_TASK_GPS, + mtk_gnss_rx, priv); + gnss_deregister_device(priv->gdev); + gnss_put_device(priv->gdev); +} + +/* + * Stop the GNSS session for suspend. GNSS core retains the FD open count + * and does not call .open() again after resume, so remember whether this + * session was active and let mtk_gnss_resume() restore it. + */ +static int mtk_gnss_suspend(struct device *dev) +{ + struct mtk_gnss *priv = dev_get_drvdata(dev); + bool was_open; + + if (!priv) + return 0; + + mutex_lock(&priv->lock); + was_open = priv->open; + /* + * Stop relaying first: the chip is about to stop answering, and any + * bytes that arrive on the way down belong to a session that is + * being torn down. + */ + WRITE_ONCE(priv->open, false); + priv->resume_pending = was_open; + mutex_unlock(&priv->lock); + + if (!was_open) + return 0; + + /* Same teardown order as close, and as .remove below. */ + mt6628_wmt_gps_sync_ctrl(priv->wmt, false); + if (priv->pinctrl_default) + pinctrl_select_state(priv->pinctrl, priv->pinctrl_default); + mt6628_wmt_func_ctrl(priv->wmt, MT6628_WMT_FUNC_GPS, false); + + dev_dbg(dev, "released the GPS function for suspend\n"); + + return 0; +} + +/* + * GNSS core retains its open count across suspend and does not call .open() + * again on resume. Restore only a session that was open before suspend; + * a client that closed its FD is not powered up automatically. + */ +static int mtk_gnss_resume(struct device *dev) +{ + struct mtk_gnss *priv = dev_get_drvdata(dev); + bool should_resume; + bool resumed; + int ret; + + if (!priv) + return 0; + + mutex_lock(&priv->lock); + should_resume = priv->resume_pending && !priv->open; + mutex_unlock(&priv->lock); + + if (!should_resume) + return 0; + + if (priv->gps_sync) { + ret = pinctrl_select_state(priv->pinctrl, priv->gps_sync); + if (ret) + goto err_restore_pinctrl; + } + + ret = mt6628_wmt_gps_sync_ctrl(priv->wmt, true); + if (ret) + goto err_restore_pinctrl; + + ret = mt6628_wmt_func_ctrl(priv->wmt, MT6628_WMT_FUNC_GPS, true); + if (ret) { + mt6628_wmt_gps_sync_ctrl(priv->wmt, false); + goto err_restore_pinctrl; + } + + mutex_lock(&priv->lock); + if (priv->resume_pending) { + WRITE_ONCE(priv->open, true); + priv->resume_pending = false; + } + resumed = priv->open; + mutex_unlock(&priv->lock); + + /* + * The session may have been closed while the hardware was starting. + * Do not leave GPS enabled without an active GNSS session. + */ + if (!resumed) { + mt6628_wmt_gps_sync_ctrl(priv->wmt, false); + if (priv->pinctrl_default) + pinctrl_select_state(priv->pinctrl, + priv->pinctrl_default); + mt6628_wmt_func_ctrl(priv->wmt, MT6628_WMT_FUNC_GPS, false); + return 0; + } + + dev_dbg(dev, "restored the GNSS function after resume\n"); + return 0; + +err_restore_pinctrl: + if (priv->pinctrl_default) + pinctrl_select_state(priv->pinctrl, priv->pinctrl_default); + + dev_err(dev, "failed to restore GNSS after resume: %d\n", ret); + return ret; +} + +static const struct dev_pm_ops mtk_gnss_pm_ops = { + .suspend = mtk_gnss_suspend, + .resume = mtk_gnss_resume, +}; + +static struct platform_driver mtk_gnss_driver = { + .probe = mtk_gnss_probe, + .remove = mtk_gnss_remove, + .driver = { + .name = "mt6628-gnss", + .pm = &mtk_gnss_pm_ops, + }, +}; +module_platform_driver(mtk_gnss_driver); + +MODULE_AUTHOR("Akari Tsuyukusa "); +MODULE_DESCRIPTION("MediaTek MT6628 GNSS driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig index ad8c8b82368141..c7c4e25c83c9a9 100644 --- a/drivers/gpu/drm/mediatek/Kconfig +++ b/drivers/gpu/drm/mediatek/Kconfig @@ -21,6 +21,28 @@ config DRM_MEDIATEK This driver provides kernel mode setting and buffer management to userspace. +config DRM_MEDIATEK_WDMA + tristate "MediaTek DISP_WDMA write DMA engine" + depends on DRM_MEDIATEK + depends on HAS_IOMEM + depends on RESET_CONTROLLER + help + DISP_WDMA is the display subsystem's write-DMA engine: it takes a + pixel stream from an upstream block and writes it out to memory. + It is the counterpart of RDMA, which reads from memory instead, and + the two are separate blocks. + + WDMA is not a CRTC path component and never carries a display + plane. It appears in no mtk_ddp_ext array on any SoC this tree + supports, so selecting this option on its own will not put anything + on a display pipeline; it registers the component and gives the + DDP_COMPONENT_WDMA0/1 entries in mtk_ddp_comp.c something to call + into. + + Say Y here if you want the driver built in, M to build it as a + module named mtk_disp_wdma, or N to leave WDMA unregistered, which + is the same state the MT6589 display driver is in by default. + config DRM_MEDIATEK_DP tristate "DRM DPTX Support for MediaTek SoCs" depends on DRM_MEDIATEK diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile index 952d294642fb4e..9c4a408373cd59 100644 --- a/drivers/gpu/drm/mediatek/Makefile +++ b/drivers/gpu/drm/mediatek/Makefile @@ -10,6 +10,7 @@ mediatek-drm-y := mtk_crtc.o \ mtk_disp_ovl.o \ mtk_disp_ovl_adaptor.o \ mtk_disp_rdma.o \ + mtk_disp_tdshp.o \ mtk_drm_drv.o \ mtk_dsi.o \ mtk_dpi.o \ @@ -20,6 +21,8 @@ mediatek-drm-y := mtk_crtc.o \ obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o +obj-$(CONFIG_DRM_MEDIATEK_WDMA) += mtk_disp_wdma.o + obj-$(CONFIG_DRM_MEDIATEK_HDMI_COMMON) += mtk_hdmi_common.o obj-$(CONFIG_DRM_MEDIATEK_HDMI) += mtk_cec.o obj-$(CONFIG_DRM_MEDIATEK_HDMI) += mtk_hdmi.o diff --git a/drivers/gpu/drm/mediatek/mtk_crtc.c b/drivers/gpu/drm/mediatek/mtk_crtc.c index fcb16f3f7b23b3..e61053d1bca0dc 100644 --- a/drivers/gpu/drm/mediatek/mtk_crtc.c +++ b/drivers/gpu/drm/mediatek/mtk_crtc.c @@ -41,6 +41,7 @@ struct mtk_crtc { struct drm_crtc base; bool enabled; + bool ddp_start_pending; bool pending_needs_vblank; struct drm_pending_vblank_event *event; @@ -153,10 +154,10 @@ static void mtk_crtc_reset(struct drm_crtc *crtc) { struct mtk_crtc_state *state; - if (crtc->state) + if (crtc->state) { __drm_atomic_helper_crtc_destroy_state(crtc->state); - - kfree(to_mtk_crtc_state(crtc->state)); + kfree(to_mtk_crtc_state(crtc->state)); + } crtc->state = NULL; state = kzalloc_obj(*state); @@ -344,6 +345,7 @@ static int mtk_crtc_ddp_hw_init(struct mtk_crtc *mtk_crtc) struct drm_encoder *encoder; struct drm_connector_list_iter conn_iter; struct drm_device *dev = mtk_crtc->base.dev; + struct mtk_drm_private *priv = dev->dev_private; unsigned int width, height, vrefresh, bpc = MTK_MAX_BPC; int ret; int i; @@ -388,6 +390,30 @@ static int mtk_crtc_ddp_hw_init(struct mtk_crtc *mtk_crtc) goto err_mutex_unprepare; } + /* + * The downstream MT6589 path stops OVL before programming + * its ROI and layers. Linux may be taking over an OVL that + * LK left running, so stop it with its clocks enabled before + * rebuilding the display path. + */ + if (mtk_crtc->ddp_comp_nr && + mtk_crtc->ddp_comp[0]->id == DDP_COMPONENT_OVL0) + mtk_ddp_comp_stop(mtk_crtc->ddp_comp[0]); + + /* + * LK may leave OVL -> BLS enabled in the crossbar. MOUT_EN bits are + * independent output gates, so selecting the OVL -> COLOR path below + * does not clear this bypass. On the MT6589 main path BLS consumes + * COLOR, and the stale unselected OVL output must be disabled during + * handoff rather than carried over from the bootloader state. + */ + if (mtk_crtc->ddp_comp_nr > 1 && + mtk_crtc->ddp_comp[0]->id == DDP_COMPONENT_OVL0 && + mtk_crtc->ddp_comp[1]->id == DDP_COMPONENT_COLOR0) + mtk_mmsys_ddp_disconnect(mtk_crtc->mmsys_dev, + DDP_COMPONENT_OVL0, + DDP_COMPONENT_BLS); + for (i = 0; i < mtk_crtc->ddp_comp_nr - 1; i++) { if (!mtk_ddp_comp_connect(mtk_crtc->ddp_comp[i], mtk_crtc->mmsys_dev, mtk_crtc->ddp_comp[i + 1]->id)) @@ -402,6 +428,12 @@ static int mtk_crtc_ddp_hw_init(struct mtk_crtc *mtk_crtc) mtk_mutex_add_comp(mtk_crtc->mutex, mtk_crtc->ddp_comp[i]->id); mtk_mutex_enable(mtk_crtc->mutex); + /* + * Configure the whole pipeline first, but do not start the engines + * yet. In particular, MT6589 OVL must have its active layer + * programmed before OVL_EN is asserted; starting it with SRC_CON == 0 + * can leave its flow-control FSM in an abnormal state. + */ for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) { struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[i]; @@ -409,10 +441,22 @@ static int mtk_crtc_ddp_hw_init(struct mtk_crtc *mtk_crtc) mtk_ddp_comp_bgclr_in_on(comp); mtk_ddp_comp_config(comp, width, height, vrefresh, bpc, NULL); - mtk_ddp_comp_start(comp); } - /* Initially configure all planes */ + /* + * The initial mode was programmed synchronously above. + * Consume the pending mode flag so the first atomic_flush + * does not reset OVL again after the pipeline has started. + */ + to_mtk_crtc_state(crtc->state)->pending_config = false; + + /* + * Program the initial plane state synchronously, before starting OVL. + * The DRM atomic state has already been swapped by the time the + * modeset enable callback runs. Preparing it here avoids relying on + * the first CMDQ update to activate the framebuffer: that update may + * itself wait for a vblank generated by OVL. + */ for (i = 0; i < mtk_crtc->layer_nr; i++) { struct drm_plane *plane = &mtk_crtc->planes[i]; struct mtk_plane_state *plane_state; @@ -421,14 +465,45 @@ static int mtk_crtc_ddp_hw_init(struct mtk_crtc *mtk_crtc) plane_state = to_mtk_plane_state(plane->state); - /* should not enable layer before crtc enabled */ - plane_state->pending.enable = false; + if (plane_state->base.crtc == crtc && + plane_state->base.fb && + plane_state->base.visible) + mtk_plane_update_new_state(&plane_state->base, plane_state); + else + plane_state->pending.enable = false; + comp = mtk_ddp_comp_for_plane(crtc, plane, &local_layer); if (comp) mtk_ddp_comp_layer_config(comp, local_layer, plane_state, NULL); } + /* + * MT6589 defers its start sequence until bridge enable, after the panel + * supply/GPIO and the DSI PHY are prepared. + * + * The order at that point is the same one every other MediaTek SoC in + * this driver uses - ddp_comp[0..n-1], i.e. the OF graph order, source + * to sink, with DSI last - and it is also the order of the downstream + * MT6589 path, which brings up the overlay and the rest of the source + * path before it pulses DSI START. + * + * A previous version of this ran the loop backwards, on the theory that + * OVL "begins fetching as soon as it is enabled" and so needs its sink + * already running. That has it the wrong way round for this engine: the + * pipeline is gated by the mutex SOF, so OVL cannot complete a frame + * until RDMA0 clocks one out of it, and RDMA0 cannot until DSI pulls + * data. Starting DSI first therefore has OVL's pixels arrive at a + * pipeline that is not producing SOFs yet, which is exactly the + * RDMA0_EOF_ABNORMAL the reordering was meant to avoid. + */ + if (priv->data->defer_start_until_bridge) { + mtk_crtc->ddp_start_pending = true; + } else { + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) + mtk_ddp_comp_start(mtk_crtc->ddp_comp[i]); + } + return 0; err_mutex_unprepare: @@ -438,6 +513,24 @@ static int mtk_crtc_ddp_hw_init(struct mtk_crtc *mtk_crtc) return ret; } +void mtk_crtc_ddp_start_pending(struct drm_crtc *crtc) +{ + struct mtk_crtc *mtk_crtc; + int i; + + if (!crtc) + return; + + mtk_crtc = to_mtk_crtc(crtc); + if (!mtk_crtc->ddp_start_pending) + return; + + /* Clear first so a re-entrant enable cannot start the path twice. */ + mtk_crtc->ddp_start_pending = false; + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) + mtk_ddp_comp_start(mtk_crtc->ddp_comp[i]); +} + static void mtk_crtc_ddp_hw_fini(struct mtk_crtc *mtk_crtc) { struct drm_device *drm = mtk_crtc->base.dev; @@ -445,6 +538,8 @@ static void mtk_crtc_ddp_hw_fini(struct mtk_crtc *mtk_crtc) unsigned long flags; int i; + mtk_crtc->ddp_start_pending = false; + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) { mtk_ddp_comp_stop(mtk_crtc->ddp_comp[i]); if (i == 1) diff --git a/drivers/gpu/drm/mediatek/mtk_crtc.h b/drivers/gpu/drm/mediatek/mtk_crtc.h index 828f109b83e78f..8d71ce7b699d4f 100644 --- a/drivers/gpu/drm/mediatek/mtk_crtc.h +++ b/drivers/gpu/drm/mediatek/mtk_crtc.h @@ -15,6 +15,7 @@ #define MTK_MIN_BPC 3 void mtk_crtc_commit(struct drm_crtc *crtc); +void mtk_crtc_ddp_start_pending(struct drm_crtc *crtc); int mtk_crtc_create(struct drm_device *drm_dev, const unsigned int *path, unsigned int path_len, int priv_data_index, const struct mtk_drm_route *conn_routes, diff --git a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c index 9672ea1f91a2b5..8d0d31ba8eaa0b 100644 --- a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c +++ b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c @@ -12,8 +12,12 @@ #include #include #include +#include +#include +#include #include +#include "../../../pwm/mt6589-bls-ddp.h" #include "mtk_crtc.h" #include "mtk_ddp_comp.h" #include "mtk_disp_drv.h" @@ -340,6 +344,19 @@ static const struct mtk_ddp_comp_funcs ddp_merge = { .config = mtk_merge_config, }; +/* + * MT6589 BLS. The merge funcs above drive the MT8195 merge unit, a + * different register block, so BLS needs its own. The block is shared + * with the backlight PWM driver, which owns the mapping and the clock. + */ +static const struct mtk_ddp_comp_funcs ddp_bls = { + .clk_enable = mt6589_bls_ddp_clk_enable, + .clk_disable = mt6589_bls_ddp_clk_disable, + .config = mt6589_bls_ddp_config, + .start = mt6589_bls_ddp_start, + .stop = mt6589_bls_ddp_stop, +}; + static const struct mtk_ddp_comp_funcs ddp_od = { .clk_enable = mtk_ddp_clk_enable, .clk_disable = mtk_ddp_clk_disable, @@ -377,6 +394,25 @@ static const struct mtk_ddp_comp_funcs ddp_postmask = { .stop = mtk_postmask_stop, }; +/* + * The RDMA funcs are shared by every SoC in this driver, so they carry + * .layer_config (and .layer_nr) for RDMA0/1/2/4 alike. A plane is only + * ever attached to path index 0 or 1 (see mtk_crtc_num_comp_planes() and + * mtk_ddp_comp_for_plane() in mtk_crtc.c), and index 1 only gets planes if + * the component supplies .bgclr_in_on, which only ddp_ovl does. So an RDMA + * receives a plane only when it is the FIRST element of a path array. On + * MT6589 the main path is OVL0, BLS, RDMA0, DSI0 (mtk_drm_drv.c: + * mt6589_mtk_ddp_main), so RDMA0 sits at index 2 and mtk_rdma_layer_config() + * is dead code there. + * + * It is not dead everywhere, though: mt2701_mtk_ddp_ext is RDMA1, DPI0 and + * mt7623_mtk_ddp_ext is RDMA1, DSI0, so on those SoCs RDMA1 is index 0 and + * does get a plane. The hook is kept because an RDMA-first display path is + * plausible future hardware. Note that mtk_ddp_comp_supports_plane() + * reports true for RDMA, since it only asks whether the layer hooks exist - + * the path array, not the funcs table, is what decides whether RDMA + * receives a plane. + */ static const struct mtk_ddp_comp_funcs ddp_rdma = { .clk_enable = mtk_rdma_clk_enable, .clk_disable = mtk_rdma_clk_disable, @@ -393,12 +429,59 @@ static const struct mtk_ddp_comp_funcs ddp_rdma = { .get_num_formats = mtk_rdma_get_num_formats, }; +static const struct mtk_ddp_comp_funcs ddp_tdshp = { + .clk_enable = mtk_tdshp_clk_enable, + .clk_disable = mtk_tdshp_clk_disable, + .config = mtk_tdshp_config, + .start = mtk_tdshp_start, + .stop = mtk_tdshp_stop, +}; + static const struct mtk_ddp_comp_funcs ddp_ufoe = { .clk_enable = mtk_ddp_clk_enable, .clk_disable = mtk_ddp_clk_disable, .start = mtk_ufoe_start, }; +/* + * DISP_WDMA. Wired to WDMA0 and WDMA1 below, which previously carried a + * NULL funcs pointer - so every mtk_ddp_comp_* accessor on those two + * components fell through to its default. The defaults were safe (all of + * them guard on !comp->funcs) but not free: mtk_ddp_comp_power_on() and + * _power_off() fall back to pm_runtime_resume_and_get()/pm_runtime_put() + * rather than returning 0, and mtk_ddp_comp_supported_rotations() falls back + * to DRM_MODE_ROTATE_0. Those defaults were the right behaviour for a block + * with no driver attached, and they are now the driver's own behaviour. + * + * Note what is absent: power_on/power_off, because WDMA needs no power + * sequence beyond pm_runtime, which mtk_ddp_comp_power_on() already does when + * funcs->power_on is NULL - and adding an empty wrapper would only remove + * that fallback. Also absent: layer_nr and layer_config, and that is the + * point rather than an omission. WDMA is the write-only output leg of the + * pipeline; the pixel source is upstream of it, so WDMA never owns a plane. + * mtk_ddp_comp_supports_plane() requires BOTH hooks to report true, so with + * these two absent WDMA reports false and mtk_crtc_num_comp_planes() will + * never allocate a plane for it. That is what keeps it out of any path + * array by construction, and it is why adding a funcs pointer here is not + * the same risk as adding a component to mt6589_mtk_ddp_main[] - see + * commits c9e543f22025 and 2d59d9693be8. + * + * supported_rotations is DRM_MODE_ROTATE_0, the same value the NULL-funcs + * fallback returned, so nothing observable changes there either. + */ +static const struct mtk_ddp_comp_funcs ddp_wdma = { + .clk_enable = mtk_wdma_clk_enable, + .clk_disable = mtk_wdma_clk_disable, + .config = mtk_wdma_config, + .start = mtk_wdma_start, + .stop = mtk_wdma_stop, + .register_vblank_cb = mtk_wdma_register_vblank_cb, + .unregister_vblank_cb = mtk_wdma_unregister_vblank_cb, + .enable_vblank = mtk_wdma_enable_vblank, + .disable_vblank = mtk_wdma_disable_vblank, + .supported_rotations = mtk_wdma_supported_rotations, +}; + static const struct mtk_ddp_comp_funcs ddp_ovl_adaptor = { .power_on = mtk_ovl_adaptor_power_on, .power_off = mtk_ovl_adaptor_power_off, @@ -441,6 +524,7 @@ static const char * const mtk_ddp_comp_stem[MTK_DDP_COMP_TYPE_MAX] = { [MTK_DISP_POSTMASK] = "postmask", [MTK_DISP_PWM] = "pwm", [MTK_DISP_RDMA] = "rdma", + [MTK_DISP_TDSHP] = "tdshp", [MTK_DISP_UFOE] = "ufoe", [MTK_DISP_WDMA] = "wdma", [MTK_DP_INTF] = "dp-intf", @@ -457,7 +541,7 @@ struct mtk_ddp_comp_match { static const struct mtk_ddp_comp_match mtk_ddp_matches[DDP_COMPONENT_DRM_ID_MAX] = { [DDP_COMPONENT_AAL0] = { MTK_DISP_AAL, 0, &ddp_aal }, [DDP_COMPONENT_AAL1] = { MTK_DISP_AAL, 1, &ddp_aal }, - [DDP_COMPONENT_BLS] = { MTK_DISP_BLS, 0, NULL }, + [DDP_COMPONENT_BLS] = { MTK_DISP_BLS, 0, &ddp_bls }, [DDP_COMPONENT_CCORR] = { MTK_DISP_CCORR, 0, &ddp_ccorr }, [DDP_COMPONENT_COLOR0] = { MTK_DISP_COLOR, 0, &ddp_color }, [DDP_COMPONENT_COLOR1] = { MTK_DISP_COLOR, 1, &ddp_color }, @@ -495,9 +579,10 @@ static const struct mtk_ddp_comp_match mtk_ddp_matches[DDP_COMPONENT_DRM_ID_MAX] [DDP_COMPONENT_RDMA1] = { MTK_DISP_RDMA, 1, &ddp_rdma }, [DDP_COMPONENT_RDMA2] = { MTK_DISP_RDMA, 2, &ddp_rdma }, [DDP_COMPONENT_RDMA4] = { MTK_DISP_RDMA, 4, &ddp_rdma }, + [DDP_COMPONENT_TDSHP] = { MTK_DISP_TDSHP, 0, &ddp_tdshp }, [DDP_COMPONENT_UFOE] = { MTK_DISP_UFOE, 0, &ddp_ufoe }, - [DDP_COMPONENT_WDMA0] = { MTK_DISP_WDMA, 0, NULL }, - [DDP_COMPONENT_WDMA1] = { MTK_DISP_WDMA, 1, NULL }, + [DDP_COMPONENT_WDMA0] = { MTK_DISP_WDMA, 0, &ddp_wdma }, + [DDP_COMPONENT_WDMA1] = { MTK_DISP_WDMA, 1, &ddp_wdma }, }; static bool mtk_ddp_comp_find(struct device *dev, @@ -708,3 +793,56 @@ int mtk_ddp_comp_init(struct device *dev, struct device_node *node, struct mtk_d return 0; } + +/** + * mtk_ddp_comp_fb_dma_addr - fetch the DMA address of a single-plane framebuffer + * @fb: the framebuffer to inspect + * @addr: output, the DMA address of the backing buffer on success + * + * Every DDP block programs a single starting address per layer, and this + * driver only ever creates single-plane framebuffers (mtk_drm_mode_fb_create() + * rejects anything else). Callers - the plane code that fills + * &mtk_plane_pending_state, and any offload component that copies from or to a + * plane - should use this instead of indexing @fb->obj[] and calling + * to_drm_gem_dma_obj() directly, which turns a bad framebuffer into a NULL + * dereference inside container_of(). + * + * The GEM object must be a DMA-helper object, because that is the only layout + * this driver allocates and imports (the driver sets DRM_GEM_DMA_DRIVER_OPS). + * Reading dma_addr out of any other object layout would be garbage. + * + * Return: 0 and the address in @addr on success, or a negative errno if @fb is + * NULL, is not a single-plane framebuffer, has no GEM object, or is not backed + * by a DMA-helper GEM object. + */ +int mtk_ddp_comp_fb_dma_addr(struct drm_framebuffer *fb, dma_addr_t *addr) +{ + struct drm_gem_object *gem; + + if (!fb || !addr) + return -EINVAL; + + if (fb->format->num_planes != 1) + return -EINVAL; + + /* + * drm_gem_fb_get_obj() is the accessor that validates the plane index + * and warns on a missing obj[]; using it keeps a bad framebuffer from + * turning into a NULL dereference inside container_of() below. + */ + gem = drm_gem_fb_get_obj(fb, 0); + if (!gem) + return -EINVAL; + + /* + * This driver only ever creates GEM objects through the DMA helper, so + * every object it can hand us must be a struct drm_gem_dma_object. The + * core has no generic "is this a DMA object" predicate, so that contract + * is what makes the container_of() below safe: DRM_GEM_DMA_DRIVER_OPS in + * mtk_drm_drv.c allocates and imports only DMA-helper objects, and + * mtk_drm_mode_fb_create() only accepts single-plane formats. + */ + *addr = to_drm_gem_dma_obj(gem)->dma_addr; + + return 0; +} diff --git a/drivers/gpu/drm/mediatek/mtk_ddp_comp.h b/drivers/gpu/drm/mediatek/mtk_ddp_comp.h index 3f3d43f4330da4..86a3a0150baa23 100644 --- a/drivers/gpu/drm/mediatek/mtk_ddp_comp.h +++ b/drivers/gpu/drm/mediatek/mtk_ddp_comp.h @@ -18,6 +18,7 @@ struct device; struct device_node; struct drm_crtc; struct drm_device; +struct drm_framebuffer; struct mtk_plane_state; struct drm_crtc_state; @@ -38,6 +39,7 @@ enum mtk_ddp_comp_type { MTK_DISP_POSTMASK, MTK_DISP_PWM, MTK_DISP_RDMA, + MTK_DISP_TDSHP, MTK_DISP_UFOE, MTK_DISP_WDMA, MTK_DPI, @@ -48,6 +50,34 @@ enum mtk_ddp_comp_type { struct mtk_ddp_comp; struct cmdq_pkt; + +/* + * DISP_WDMA hooks, implemented in mtk_disp_wdma.c and wired into + * ddp_wdma in mtk_ddp_comp.c. + * + * WDMA is the display pipeline's write-only output leg: it takes a pixel + * stream from an upstream block and writes it to memory. It carries no + * display plane and is not a CRTC path component on any SoC this tree + * supports, so there is deliberately no layer_nr/layer_config here. The + * probes in mtk_ddp_comp_supports_plane() treat a component with no funcs + * pointer that way already, and WDMA now has one, so the hooks it does + * provide are what mtk_ddp_comp_supports_plane() will find. + */ +int mtk_wdma_clk_enable(struct device *dev); +void mtk_wdma_clk_disable(struct device *dev); +void mtk_wdma_config(struct device *dev, unsigned int w, unsigned int h, + unsigned int vrefresh, unsigned int bpc, + struct cmdq_pkt *cmdq_pkt); +void mtk_wdma_start(struct device *dev); +void mtk_wdma_stop(struct device *dev); +void mtk_wdma_register_vblank_cb(struct device *dev, + void (*vblank_cb)(void *), + void *vblank_cb_data); +void mtk_wdma_unregister_vblank_cb(struct device *dev); +void mtk_wdma_enable_vblank(struct device *dev); +void mtk_wdma_disable_vblank(struct device *dev); +unsigned int mtk_wdma_supported_rotations(struct device *dev); + struct mtk_ddp_comp_funcs { int (*power_on)(struct device *dev); void (*power_off)(struct device *dev); @@ -100,18 +130,51 @@ struct mtk_ddp_comp { const struct mtk_ddp_comp_funcs *funcs; }; +/** + * mtk_ddp_comp_supports_plane - can a DRM plane be attached to this component? + * @comp: the DDP component + * + * A component owns a plane if and only if it implements the layer hooks that + * the CRTC drives: &mtk_ddp_comp_funcs.layer_nr to advertise how many planes it + * can take, and &mtk_ddp_comp_funcs.layer_config to program them. Both are + * required: @layer_nr alone only claims the planes, and @layer_config alone + * claims to program planes the CRTC would never have allocated. + * + * This answers "does this component implement the plane hooks at all?", which + * is what distinguishes a component that can never take a plane (COLOR, DSI, + * BLS, ... and PWM, which have no funcs pointer at all) from one that + * does. WDMA is the interesting case now that it has a funcs table: it + * implements several hooks but neither layer hook, so it still reports + * false, which is correct - WDMA is the write-only output leg and the + * pixel source is upstream of it, so it never owns a plane. It deliberately says nothing about *where* in a pipeline the component + * sits: that is the CRTC's business, and no SoC currently wires a plane to + * anything but path index 0 or 1. + * + * Note that RDMA reports true here even though no SoC currently routes a + * plane to it - see the comment on ddp_rdma in mtk_ddp_comp.c. + * + * Return: true if @comp implements both layer hooks, false otherwise + * (including @comp being NULL or having no funcs at all). + */ +static inline bool mtk_ddp_comp_supports_plane(struct mtk_ddp_comp *comp) +{ + if (!comp || !comp->funcs) + return false; + + return comp->funcs->layer_nr && comp->funcs->layer_config; +} + static inline int mtk_ddp_comp_power_on(struct mtk_ddp_comp *comp) { - if (comp->funcs && comp->funcs->power_on) + if (comp && comp->funcs && comp->funcs->power_on) return comp->funcs->power_on(comp->dev); else return pm_runtime_resume_and_get(comp->dev); - return 0; } static inline void mtk_ddp_comp_power_off(struct mtk_ddp_comp *comp) { - if (comp->funcs && comp->funcs->power_off) + if (comp && comp->funcs && comp->funcs->power_off) comp->funcs->power_off(comp->dev); else pm_runtime_put(comp->dev); @@ -345,6 +408,16 @@ static inline void mtk_ddp_comp_encoder_index_set(struct mtk_ddp_comp *comp) { if (comp->funcs && comp->funcs->encoder_index) comp->encoder_index = (int)comp->funcs->encoder_index(comp->dev); + else + /* + * Without the hook this component cannot be an encoder. Say so + * explicitly: mtk_crtc_connector_change() only skips a route + * component whose encoder_index is negative, so leaving the + * zero-initialised default in place would make a hook-less + * component spuriously match encoder 0 and be appended to the + * CRTC path. + */ + comp->encoder_index = -1; } int mtk_ddp_comp_get_id(struct device_node *node, @@ -352,7 +425,7 @@ int mtk_ddp_comp_get_id(struct device_node *node, int mtk_find_possible_crtcs(struct drm_device *drm, struct device *dev); int mtk_ddp_comp_init(struct device *dev, struct device_node *comp_node, struct mtk_ddp_comp *comp, unsigned int comp_id); -enum mtk_ddp_comp_type mtk_ddp_comp_get_type(unsigned int comp_id); +int mtk_ddp_comp_fb_dma_addr(struct drm_framebuffer *fb, dma_addr_t *addr); void mtk_ddp_write(struct cmdq_pkt *cmdq_pkt, unsigned int value, struct cmdq_client_reg *cmdq_reg, void __iomem *regs, unsigned int offset); diff --git a/drivers/gpu/drm/mediatek/mtk_disp_color.c b/drivers/gpu/drm/mediatek/mtk_disp_color.c index 39c7de4cdcc16b..4374119c9f149b 100644 --- a/drivers/gpu/drm/mediatek/mtk_disp_color.c +++ b/drivers/gpu/drm/mediatek/mtk_disp_color.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2017 MediaTek Inc. + * Copyright (c) 2026 MT6589 adaptation */ #include @@ -8,6 +9,7 @@ #include #include #include +#include #include #include "mtk_crtc.h" @@ -16,6 +18,7 @@ #include "mtk_drm_drv.h" #define DISP_COLOR_CFG_MAIN 0x0400 +#define DISP_COLOR_WIN_X_MAIN 0x040c #define DISP_COLOR_START_MT2701 0x0f00 #define DISP_COLOR_START_MT8167 0x0400 #define DISP_COLOR_START_MT8173 0x0c00 @@ -25,16 +28,27 @@ #define COLOR_BYPASS_ALL BIT(7) #define COLOR_SEQ_SEL BIT(13) +/* MT6589 specific: main control bit to enable the color module */ +#define COLOR_MAIN_EN BIT(29) + +/* Additional registers needed by MT6589 */ +#define DISP_COLOR_R2Y_EN (DISP_COLOR_START_MT2701 + 0x60) /* 0xf60 */ +#define DISP_COLOR_R2Y_MATRIX_BASE (DISP_COLOR_START_MT2701 + 0x64) /* 0xf64 */ + +struct mtk_disp_color *color; struct mtk_disp_color_data { unsigned int color_offset; + bool enable_main_bit29; /* set BIT(29) in CFG_MAIN */ + /* + * Set CFG_MAIN.all_bypass (bit 7) instead of only BIT(29), i.e. leave + * COLOR as a pass-through. See the MT6589 entry for why. + */ + bool bypass_all; + void (*init)(struct mtk_disp_color *color); /* extra init sequence */ + bool has_reset; }; -/* - * struct mtk_disp_color - DISP_COLOR driver structure - * @crtc: associated crtc to report irq events to - * @data: platform colour driver data - */ struct mtk_disp_color { struct drm_crtc *crtc; struct clk *clk; @@ -58,22 +72,113 @@ void mtk_color_clk_disable(struct device *dev) } void mtk_color_config(struct device *dev, unsigned int w, - unsigned int h, unsigned int vrefresh, - unsigned int bpc, struct cmdq_pkt *cmdq_pkt) + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) { struct mtk_disp_color *color = dev_get_drvdata(dev); mtk_ddp_write(cmdq_pkt, w, &color->cmdq_reg, color->regs, DISP_COLOR_WIDTH(color)); mtk_ddp_write(cmdq_pkt, h, &color->cmdq_reg, color->regs, DISP_COLOR_HEIGHT(color)); + + /* The MT6589 COLOR wrapper processes from x=0 to x=0xffff. */ + if (color->data->enable_main_bit29) + mtk_ddp_write(cmdq_pkt, 0xffff0000, &color->cmdq_reg, + color->regs, DISP_COLOR_WIN_X_MAIN); +} + +/* + * MT6589 specific initialization: + * - Enable the color module via BIT(29) in CFG_MAIN + * - Setup the color space conversion matrix (BT.601 YUV->RGB) + * - Enable R2Y and CCOR stages + */ +static void mt6589_color_init(struct mtk_disp_color *color) +{ + void __iomem *regs = color->regs; + unsigned int i; + + /* + * Register sequence of the downstream DpEngine_COLORonInit(): one + * contiguous coefficient table at 0xf64..0xfdc holding the R2Y + * matrix, its RGB gains and input offsets (+128), the Y2R enable + * bit at 0xfa0 and the Y2R matrix with U/V offsets of -128 at + * 0xfcc/0xfd0. + */ + static const u32 coef[] = { + 306, 601, 117, -173, -339, 512, /* 0xf64: R2Y */ + 512, -429, -83, + 0, 0, 0, 0, /* reserved */ + 128, 128, /* R/G offsets */ + 1, /* 0xfa0: Y2R_EN */ + 1024, -1, 1436, /* 0xfa4: Y2R */ + 1024, + -353, -731, + 1024, 1814, + 1, 0, /* gains tail */ + -128, -128, /* U/V offsets */ + 0, 0, 0, + }; + + for (i = 0; i < ARRAY_SIZE(coef); i++) + writel(coef[i], regs + DISP_COLOR_R2Y_MATRIX_BASE + i * 4); + + /* Enable the R2Y stage (0xf60) */ + writel(1, regs + DISP_COLOR_R2Y_EN); + + /* + * Use the same 10-bit to 8-bit rounding pattern as the downstream + * kernel. The interrupt mask at 0xf04 is intentionally left alone: + * this driver does not service COLOR interrupts. + */ + writel(0x333, regs + DISP_COLOR_START_MT2701 + 0x0c); + /* Match DpEngine_COLORonInit(): this driver has no PQ demo mode. */ + writel(0, regs + 0x420); } void mtk_color_start(struct device *dev) { struct mtk_disp_color *color = dev_get_drvdata(dev); + u32 cfg_main = COLOR_BYPASS_ALL | COLOR_SEQ_SEL; + + /* + * BIT(29) is color_dbuf_en ("Enables vs update"), which every path + * wants. BIT(7) is all_bypass, "All engine bypass". + * + * Which one MT6589 should use is settled by the one configuration + * known to light this panel: the bootloader's (LK) display path, whose + * live CFG_MAIN reads 0x20002080 - BIT(29) | BIT(7), i.e. COLOR + * bypassed and not even in the data path (LK routes OVL -> BLS with + * DISP_OVL_MOUT_EN = 0x2 and BLS_SEL = 0). + * + * The Android kernel driver does enable COLOR (DpEngine_COLORonInit() + * writes BIT(29) alone unless pq_debug_flag == 1), so both are used + * somewhere. But with COLOR enabled on this board we have a full, + * self-consistent pipe - OVL run=1, RDMA0 frame-end interrupts + * arriving, DSI transmitting - and no picture, so the additional + * R2Y colour-space conversion that COLOR performs on an RGB888 stream + * is worth ruling out before trusting it. Follow LK. + */ + if (color->data->bypass_all) + cfg_main = COLOR_MAIN_EN | COLOR_BYPASS_ALL; + else if (color->data->enable_main_bit29) + cfg_main = COLOR_MAIN_EN; + + writel(cfg_main, color->regs + DISP_COLOR_CFG_MAIN); + + /* SoC-specific extra initialization */ + if (color->data->init) + color->data->init(color); - writel(COLOR_BYPASS_ALL | COLOR_SEQ_SEL, - color->regs + DISP_COLOR_CFG_MAIN); writel(0x1, color->regs + DISP_COLOR_START(color)); + if (color->data->enable_main_bit29) + dev_dbg(dev, + "MT6589 COLOR start: cfg_main=%#x start=%#x width=%#x height=%#x win_x=%#x r2y_en=%#x\n", + readl(color->regs + DISP_COLOR_CFG_MAIN), + readl(color->regs + DISP_COLOR_START(color)), + readl(color->regs + DISP_COLOR_WIDTH(color)), + readl(color->regs + DISP_COLOR_HEIGHT(color)), + readl(color->regs + DISP_COLOR_WIN_X_MAIN), + readl(color->regs + DISP_COLOR_R2Y_EN)); } static int mtk_disp_color_bind(struct device *dev, struct device *master, @@ -96,6 +201,7 @@ static int mtk_disp_color_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mtk_disp_color *priv; + struct reset_control *rstc; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -118,6 +224,32 @@ static int mtk_disp_color_probe(struct platform_device *pdev) #endif priv->data = of_device_get_match_data(dev); + if (!priv->data) + return -ENODEV; + + /* + * MT6589 declares a COLOR reset in the Device Tree. Pulse it before + * registering the component, while its functional clock is running, + * so stale COLOR state cannot break the OVL -> COLOR -> RDMA0 path. + * Other compatibles do not declare a reset and must not look one up. + */ + if (priv->data->has_reset) { + rstc = devm_reset_control_get(dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(dev, PTR_ERR(rstc), + "failed to get color reset\n"); + + ret = clk_prepare_enable(priv->clk); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable color clock for reset\n"); + + ret = reset_control_reset(rstc); + clk_disable_unprepare(priv->clk); + if (ret) + return dev_err_probe(dev, ret, "failed to reset color\n"); + } + platform_set_drvdata(pdev, priv); ret = component_add(dev, &mtk_disp_color_component_ops); @@ -144,6 +276,18 @@ static const struct mtk_disp_color_data mt8173_color_driver_data = { .color_offset = DISP_COLOR_START_MT8173, }; +static const struct mtk_disp_color_data mt6589_color_driver_data = { + .color_offset = DISP_COLOR_START_MT2701, /* same as MT2701: 0x0f00 */ + .enable_main_bit29 = true, + /* + * Match the bootloader, which is the only configuration observed to + * light this panel: CFG_MAIN = BIT(29) | BIT(7). See mtk_color_start(). + */ + .bypass_all = true, + .init = mt6589_color_init, + .has_reset = true, +}; + static const struct of_device_id mtk_disp_color_driver_dt_match[] = { { .compatible = "mediatek,mt2701-disp-color", .data = &mt2701_color_driver_data}, @@ -151,6 +295,8 @@ static const struct of_device_id mtk_disp_color_driver_dt_match[] = { .data = &mt8167_color_driver_data}, { .compatible = "mediatek,mt8173-disp-color", .data = &mt8173_color_driver_data}, + { .compatible = "mediatek,mt6589-disp-color", + .data = &mt6589_color_driver_data}, {}, }; MODULE_DEVICE_TABLE(of, mtk_disp_color_driver_dt_match); diff --git a/drivers/gpu/drm/mediatek/mtk_disp_drv.h b/drivers/gpu/drm/mediatek/mtk_disp_drv.h index 679d413bf10be1..b5482cf4223423 100644 --- a/drivers/gpu/drm/mediatek/mtk_disp_drv.h +++ b/drivers/gpu/drm/mediatek/mtk_disp_drv.h @@ -161,6 +161,14 @@ void mtk_rdma_disable_vblank(struct device *dev); const u32 *mtk_rdma_get_formats(struct device *dev); size_t mtk_rdma_get_num_formats(struct device *dev); +int mtk_tdshp_clk_enable(struct device *dev); +void mtk_tdshp_clk_disable(struct device *dev); +void mtk_tdshp_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt); +void mtk_tdshp_start(struct device *dev); +void mtk_tdshp_stop(struct device *dev); + int mtk_mdp_rdma_power_on(struct device *dev); void mtk_mdp_rdma_power_off(struct device *dev); int mtk_mdp_rdma_clk_enable(struct device *dev); diff --git a/drivers/gpu/drm/mediatek/mtk_disp_ovl.c b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c index 97a899e4bd99ca..cc2a34ad9076f7 100644 --- a/drivers/gpu/drm/mediatek/mtk_disp_ovl.c +++ b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c @@ -9,10 +9,17 @@ #include #include +#include +#include +#include #include #include +#include #include +#include #include +#include +#include #include #include "mtk_crtc.h" @@ -20,11 +27,99 @@ #include "mtk_disp_drv.h" #include "mtk_drm_drv.h" -#define DISP_REG_OVL_INTEN 0x0004 -#define OVL_FME_CPL_INT BIT(1) +#define DISP_REG_OVL_STA 0x0000 + +/* + * DISPSYS_CONFIG clock-gate control, and the OVL gate within it. The stock + * driver treats a non-zero OVL gate in here as a fault: ddp_path.c:247-252 + * reports "ovl abnormal, en=%d, clk=%#x" when + * DISP_REG_CONFIG_CG_CON0 & DDP_OVL_POWER_BIT is non-zero, with + * DDP_OVL_POWER_BIT = 0x30 (ddp_path.c:212). A set gate bit means the + * clock is OFF, which is why the overlay can hold OVL_EN == 1 and still + * never leave its reset state of OVL_RUN == 0. + */ +/* + * DISP_CG_CON0 is at 0x14000100, NOT inside the OVL block. It lives in + * DISPSYS_CONFIG, which both vendor trees define as DISPSYS_BASE = + * IO_PHYS + 0x04000000 (lk/include/platform/mt_reg_base.h:163, + * kernel/drivers/met/platform/mt6589/mt_reg_base.h:167); the vendor's + * DISP_REG_CONFIG_CG_CON0 is that base plus 0x100 (ddp_reg.h:133), and the + * data sheet puts DISP_CG_CON0 at 0x14000100 (draft/ds/ovl.txt:193960). + * + * An offset of 0x100 taken from the OVL base instead lands on 0x14003100, + * which is OVL_RDMA2_CTRL - an unrelated engine register that reads back + * 0x3ff0001. Reading that and calling the result a clock gate cannot + * report a gate, which is why this diagnostic had never produced a verdict. + * drivers/soc/mediatek/mt6589-dispsys.h documents the same block and base. + * + * The bit map is per the data sheet (draft/ds/ovl.txt:193985): bit 4 = OVL + * engine, bit 5 = OVL SMI. clk-mt6589-disp.c gates those two through + * DISP_CG_SET0/CLR0 at that very 0x100/0x104/0x108, so this readback looks + * at exactly the bits clk_bulk_prepare_enable() manipulates. + */ +#define MT6589_DISPSYS_CONFIG_CG_CON0 0x0100 +#define DISP_CG_CON0_OVL_ENGINE BIT(4) +#define DISP_CG_CON0_OVL_SMI BIT(5) +#define DISP_REG_OVL_INTEN 0x0004 +#define DISP_REG_OVL_ADDCON_DBG 0x0244 +/* + * OVL_INTEN (0x14003004) and OVL_INTSTA (0x14003008) share one bit map. + * The data sheet's "Bit 15..0" row for both registers names exactly + * these twelve mnemonics and leaves 12..31 unnamed, so nothing above + * BIT(11) may ever appear in a mask derived from it: + * + * bit 0 OVL_REG_CMT_INT shadow -> working register commit done + * bit 1 OVL_FME_CPL_INT frame complete + * bit 2 OVL_FME_UND_INT frame underflow + * bit 3 OVL_FME_SWRST_DONE_INT SW reset done + * bit 4 OVL_RDMA0_EOF_ABNORMAL_INT + * bit 5 OVL_RDMA1_EOF_ABNORMAL_INT + * bit 6 OVL_RDMA2_EOF_ABNORMAL_INT + * bit 7 OVL_RDMA3_EOF_ABNORMAL_INT + * bit 8 OVL_RDMA0_FIFO_UND_INT + * bit 9 OVL_RDMA1_FIFO_UND_INT + * bit 10 OVL_RDMA2_FIFO_UND_INT + * bit 11 OVL_RDMA3_FIFO_UND_INT + * + * Every status bit is write-0-to-clear ("Cleared by writing 0 to it; + * writing 1 is useless"), and INTEN mirrors INTSTA one-for-one, so a + * driver that enables a bit is obliged to acknowledge it again in the + * ISR. OVL_INT_ALL below is the union of all twelve. +*/ +#define OVL_REG_CMT_INT BIT(0) +#define OVL_FME_CPL_INT BIT(1) +#define OVL_FME_UND_INT BIT(2) +#define OVL_FME_SWRST_DONE_INT BIT(3) +#define OVL_RDMA0_EOF_ABNORMAL_INT BIT(4) +#define OVL_RDMA1_EOF_ABNORMAL_INT BIT(5) +#define OVL_RDMA2_EOF_ABNORMAL_INT BIT(6) +#define OVL_RDMA3_EOF_ABNORMAL_INT BIT(7) +#define OVL_RDMA0_FIFO_UND_INT BIT(8) +#define OVL_RDMA1_FIFO_UND_INT BIT(9) +#define OVL_RDMA2_FIFO_UND_INT BIT(10) +#define OVL_RDMA3_FIFO_UND_INT BIT(11) + +/* + * Every OVL_INTSTA bit this block can raise. The ISR acknowledges + * reg & int_all_mask so that whatever mtk_ovl_enable_vblank() switched + * on in OVL_INTEN is also deasserted again - see + * mtk_disp_ovl_irq_handler(). +*/ +#define OVL_INT_ALL \ +(OVL_REG_CMT_INT | OVL_FME_CPL_INT | OVL_FME_UND_INT | \ +OVL_FME_SWRST_DONE_INT | \ +OVL_RDMA0_EOF_ABNORMAL_INT | OVL_RDMA1_EOF_ABNORMAL_INT | \ +OVL_RDMA2_EOF_ABNORMAL_INT | OVL_RDMA3_EOF_ABNORMAL_INT | \ +OVL_RDMA0_FIFO_UND_INT | OVL_RDMA1_FIFO_UND_INT | \ +OVL_RDMA2_FIFO_UND_INT | OVL_RDMA3_FIFO_UND_INT) + #define DISP_REG_OVL_INTSTA 0x0008 +#define DISP_REG_OVL_OUTMUX_DBG 0x0248 + #define DISP_REG_OVL_EN 0x000c +#define DISP_REG_OVL_TRIG 0x0010 #define DISP_REG_OVL_RST 0x0014 + #define DISP_REG_OVL_ROI_SIZE 0x0020 #define DISP_REG_OVL_DATAPATH_CON 0x0024 #define OVL_LAYER_SMI_ID_EN BIT(0) @@ -41,17 +136,45 @@ #define OVL_CONST_BLEND BIT(28) #define DISP_REG_OVL_RDMA_CTRL(n) (0x00c0 + 0x20 * (n)) #define DISP_REG_OVL_RDMA_GMC(n) (0x00c8 + 0x20 * (n)) -#define DISP_REG_OVL_ADDR_MT2701 0x0040 +#define DISP_REG_OVL_RDMA_FIFO_CTRL(n) (0x00d0 + 0x20 * (n)) +#define DISP_REG_OVL_RDMA0_DBG 0x024c #define DISP_REG_OVL_CLRFMT_EXT 0x02d0 +/* + * OVL_FLOW_CTRL_DBG, the overlay's flow-control debug port + * (draft/ds/ovl.txt:220209; the vendor names the same register + * DISP_REG_OVL_FLOW_CTRL_DBG at DISPSYS_OVL_BASE + 0x0240, + * kernel ddp_reg.h:306). Read-only, and the only register that says + * *why* the engine is or is not running - see mtk_ovl_start(). + */ +#define DISP_REG_OVL_FLOW_CTRL_DBG 0x0240 #define OVL_CON_CLRFMT_BIT_DEPTH_MASK(n) (GENMASK(1, 0) << (4 * (n))) #define OVL_CON_CLRFMT_BIT_DEPTH(depth, n) ((depth) << (4 * (n))) #define OVL_CON_CLRFMT_8_BIT (0) #define OVL_CON_CLRFMT_10_BIT (1) +/* + * data->addr is the byte offset of layer 0's ADDR register within the OVL + * block, not the block base. The stock header has DISP_REG_OVL_L0_ADDR at + * DISPSYS_OVL_BASE + 0x0040 and the other layers at +0x0060, +0x0080 and + * +0x00a0, i.e. 0x20 apart - which is why DISP_REG_OVL_ADDR() below adds + * 0x20 * n to this. MT2701 and MT6589 share the layout, so both use the + * same 0x40; MT8173's OVL sits later in the block and uses 0x0f40. + */ +#define DISP_REG_OVL_ADDR_MT2701 0x0040 #define DISP_REG_OVL_ADDR_MT8173 0x0f40 #define DISP_REG_OVL_ADDR(ovl, n) ((ovl)->data->addr + 0x20 * (n)) #define DISP_REG_OVL_HDR_ADDR(ovl, n) ((ovl)->data->addr + 0x20 * (n) + 0x04) #define DISP_REG_OVL_HDR_PITCH(ovl, n) ((ovl)->data->addr + 0x20 * (n) + 0x08) +#define MT6589_ROUTE_OVL_MOUT_EN 0x024 +#define MT6589_ROUTE_COLOR_MOUT_EN 0x028 +#define MT6589_ROUTE_RDMA0_OUT_SEL 0x034 +#define MT6589_ROUTE_COLOR_SEL 0x04c +#define MT6589_ROUTE_BLS_SEL 0x054 +#define MT6589_ROUTE_PATH_DEBUG0 0x070 +#define MT6589_ROUTE_PATH_DEBUG1 0x074 +#define MT6589_ROUTE_PATH_DEBUG2 0x078 +#define MT6589_ROUTE_PATH_DEBUG3 0x07c + #define GMC_THRESHOLD_BITS 16 #define GMC_THRESHOLD_HIGH ((1 << GMC_THRESHOLD_BITS) / 4) #define GMC_THRESHOLD_LOW ((1 << GMC_THRESHOLD_BITS) / 8) @@ -69,7 +192,6 @@ #define OVL_CON_CLRFMT_BGRA8888 (OVL_CON_CLRFMT_RGBA8888 | OVL_CON_BYTE_SWAP) #define OVL_CON_CLRFMT_UYVY (4 << 12) #define OVL_CON_CLRFMT_YUYV (5 << 12) -#define OVL_CON_MTX_YUV_TO_RGB (6 << 16) #define OVL_CON_CLRFMT_PARGB8888 ((3 << 12) | OVL_CON_CLRFMT_MAN) #define OVL_CON_CLRFMT_PABGR8888 (OVL_CON_CLRFMT_PARGB8888 | OVL_CON_RGB_SWAP) #define OVL_CON_CLRFMT_PBGRA8888 (OVL_CON_CLRFMT_PARGB8888 | OVL_CON_BYTE_SWAP) @@ -85,6 +207,36 @@ #define OVL_COLOR_ALPHA GENMASK(31, 24) +/* MT6589 specific registers for YUV-to-RGB conversion matrix */ +#define DISP_REG_OVL_Y2R_BASE(n) (0x0134 + 0x28 * (n)) +#define Y2R_R0 0x00 +#define Y2R_R1 0x04 +#define Y2R_G0 0x08 +#define Y2R_G1 0x0C +#define Y2R_B0 0x10 +#define Y2R_B1 0x14 +#define Y2R_YUV_A0 0x18 +#define Y2R_YUV_A1 0x1C +#define Y2R_RGB_A0 0x20 +#define Y2R_RGB_A1 0x24 + +/* + * BT.601 YUV2RGB conversion matrix for the OVL Y2R engine. + * + * The hardware takes a 5x3 coefficient table in the same layout as the + * downstream kernel's ddp_matrix_para.h: rows R/G/B hold {MY, MU, MV} + * multipliers (13-bit sign+2.10 fixed point), row 3 the {YA, UA, VA} + * offsets and row 4 the {RA, GA, BA} output gains. This is + * yuv2rgb_601_0_0 (full-range input, full-range output). + */ +static const s16 mt6589_yuv2rgb_coef[5][3] = { + { 0x0400, 0x0000, 0x059b }, /* 1, 0, 1.402 */ + { 0x0400, 0x1ea0, 0x1d25 }, /* 1, -0.3341, -0.7141 */ + { 0x0400, 0x0716, 0x0000 }, /* 1, 1.772, 0 */ + { 0x0000, 0x0180, 0x0180 }, /* 0, -128, -128 */ + { 0x0000, 0x0000, 0x0000 }, /* gains are written separately */ +}; + static inline bool is_10bit_rgb(u32 fmt) { switch (fmt) { @@ -142,16 +294,96 @@ static const u32 mt8195_formats[] = { struct mtk_disp_ovl_data { unsigned int addr; unsigned int gmc_bits; + /* OVL RDMA FIFO capacity, in 16-byte units; zero if unspecified. */ + unsigned int gmc_fifo_size; + /* + * Fixed OVL RDMA GMC_SETTING value for SoCs whose normal vendor + * path programs one explicitly. Zero uses the generic threshold + * calculation below. MT6589's downstream display path writes + * 0x0101a06b to each OVL RDMA GMC_SETTING register. + */ + unsigned int rdma_gmc_setting; unsigned int layer_nr; bool fmt_rgb565_is_0; bool smi_id_en; bool supports_afbc; + /* + * Whether OVL_PITCH bit 28 (OVL_CONST_BLEND) exists on this SoC. + * + * Upstream wrote OVL_CONST_BLEND unconditionally; it is a bit in + * OVL_L_PITCH, and MT6589's OVL_L0_PITCH is bits 15:0 + * L0_SRC_PITCH only - the data sheet names no other field, and the + * vendor header agrees (ddp_ovl.h L0_PITCH_FLD_L0_SRC_PITCH is + * REG_FLD(16, 0)). So this is true for the eight other SoCs and + * false for MT6589. With CONST_BLD unavailable there, an XRGB8888 + * layer has no constant alpha to fall back on and must use the + * CLRFMT that drops the top byte instead - see mt6589_fmt_convert(). + */ + bool has_const_blend; + bool set_layer_src; + unsigned int vblank_en_mask; + /* + * Whether this SoC's OVL node declares a "resets" phandle for this + * block, and so whether probe may take the warm reset at all. + * + * The lookup is not optional: devm_reset_control_get(dev, NULL) on a + * node with no "resets" property returns -ENOENT, not NULL, because + * the reset was not requested with RESET_CONTROL_FLAGS_BIT_OPTIONAL. + * Only the MT6589 node declares one; the other eight SoCs would fail + * probe outright, which is why this is stated per SoC rather than + * probed for. + */ + bool has_reset; + /* + * Whether probe may borrow a mapping of DISPSYS_CONFIG so that + * mtk_ovl_start() can read DISP_CG_CON0 and report whether the OVL + * engine and SMI gates are actually open. Only MT6589 names a + * dispsys syscon parent in DT; the other nine OVL nodes sit under + * their own top-level (or soc) parent, so no such mapping exists + * for them and the readback is skipped. + */ + bool has_dispsys_cfg; + /* + * int_all_mask is the whole set of OVL_INTSTA bits this block can + * raise. It is ANDed with the latched status to decide both what + * to acknowledge and whether the line is ours at all, so it must + * cover every bit vblank_en_mask is able to enable. fme_cpl_bit + * is the frame complete status bit - the only one that means a + * frame was actually produced, and therefore the only one + * allowed to fire vblank_cb. + * + * Both are required and every SoC below states both explicitly. + * A zero is a real, deliberate value, not "unset" - it says this + * block raises no status bit that this driver can acknowledge, + * which is true of the eight SoCs that never write a non-zero + * value to OVL_INTEN (see mtk_disp_ovl_check_data()). + */ + unsigned int int_all_mask; + unsigned int fme_cpl_bit; + unsigned int fme_und_bit; + unsigned int rdma0_eof_abn_bit; + unsigned int rdma1_eof_abn_bit; + unsigned int rdma0_fifo_und_bit; + unsigned int rdma1_fifo_und_bit; + unsigned int (*fmt_convert)(unsigned int fmt, unsigned int blend_mode); const u32 blend_modes; const u32 *formats; size_t num_formats; bool supports_clrfmt_ext; }; +/* + * The vblank trigger for a SoC whose OVL_INTSTA bit map this driver does + * not describe. Such a SoC cannot say which bit means "a frame was + * produced", so every status bit the block latches counts as a frame + * boundary - the behaviour this driver has always had for them, and the + * only choice that cannot silently stop a display that is working today. + * + * This is spelled out per SoC rather than defaulted at run time so that + * the value in the data is the value the ISR uses. + */ +#define OVL_FME_CPL_ANY GENMASK(31, 0) + /* * struct mtk_disp_ovl - DISP_OVL driver structure * @crtc: associated crtc to report vblank events to @@ -159,8 +391,17 @@ struct mtk_disp_ovl_data { */ struct mtk_disp_ovl { struct drm_crtc *crtc; - struct clk *clk; + struct clk_bulk_data *clks; + struct reset_control *rstc; + int num_clks; void __iomem *regs; + /* + * DISPSYS_CONFIG (0x14000000), mapped only so the clock-gate state can + * be read back. NULL on every SoC except MT6589, the only one whose + * node names a dispsys syscon parent to borrow the mapping from. + */ + struct regmap *cfg_regmap; + void __iomem *cfg_regs; struct cmdq_client_reg cmdq_reg; const struct mtk_disp_ovl_data *data; void (*vblank_cb)(void *data); @@ -170,15 +411,67 @@ struct mtk_disp_ovl { static irqreturn_t mtk_disp_ovl_irq_handler(int irq, void *dev_id) { struct mtk_disp_ovl *priv = dev_id; + u32 reg = readl(priv->regs + DISP_REG_OVL_INTSTA); + u32 handled; - /* Clear frame completion interrupt */ - writel(0x0, priv->regs + DISP_REG_OVL_INTSTA); - - if (!priv->vblank_cb) + /* + * A SoC that owns at least one status bit (int_all_mask non-zero) + * must show one of them asserted for the line to be ours. Only + * then can we return IRQ_NONE without having acknowledged anything, + * because there is nothing of ours in reg to clear. + * + * int_all_mask == 0 is not a sentinel for "map unknown" - it is the + * explicit statement that this block raises nothing this driver can + * acknowledge, and so there is nothing to reject it on. Testing the + * mask alone, as "if (!(reg & int_all_mask))", would read that as + * "no bit matches" and eject every such SoC from the handler on + * every invocation - the regression that came with int_all_mask in + * the first place, since it stopped those SoCs clearing their + * status and stopped their vblank callback from ever running. Hence + * the mask has to be known non-zero before the intersection is + * allowed to veto. + * + * The invariant that lets this be written this way is checked at + * probe: int_all_mask covers every bit vblank_en_mask can enable, + * so a zero mask implies a zero enable mask, so those SoCs never + * raise an OVL interrupt through this driver at all. + */ + if (priv->data->int_all_mask && !(reg & priv->data->int_all_mask)) return IRQ_NONE; - priv->vblank_cb(priv->vblank_cb_data); + if (reg & (priv->data->rdma0_eof_abn_bit | + priv->data->rdma1_eof_abn_bit | + priv->data->fme_und_bit | + priv->data->rdma0_fifo_und_bit | + priv->data->rdma1_fifo_und_bit)) + pr_debug_ratelimited("OVL fault: intsta=%#x sta=%#x flow=%#x src_con=%#x\n", + reg, readl(priv->regs + DISP_REG_OVL_STA), + readl(priv->regs + DISP_REG_OVL_FLOW_CTRL_DBG), + readl(priv->regs + DISP_REG_OVL_SRC_CON)); + + handled = reg & priv->data->int_all_mask; + writel(~handled, priv->regs + DISP_REG_OVL_INTSTA); + /* + * On a SoC whose interrupt map is known (MT6589) fme_cpl_bit names + * one bit and only a completed frame is a vblank: underflow and the + * RDMA faults are errors, already reported above, and firing the + * vblank callback for them would hand the CRTC a frame that never + * arrived. The eight SoCs whose map this driver does not describe + * state OVL_FME_CPL_ANY, which counts every status bit as a frame + * boundary - the behaviour they have always had. Either way a + * frame complete with no callback registered is still acknowledged + * by the clear above and cannot re-trigger. + */ + if (priv->vblank_cb && (reg & priv->data->fme_cpl_bit)) + priv->vblank_cb(priv->vblank_cb_data); + + /* + * The line is ours and we have just deasserted its status bits, so + * returning IRQ_NONE here would wrongly hand an already + * acknowledged interrupt to another handler. IRQ_HANDLED even + * with no callback registered - the clear is the handling. + */ return IRQ_HANDLED; } @@ -205,7 +498,8 @@ void mtk_ovl_enable_vblank(struct device *dev) struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); writel(0x0, ovl->regs + DISP_REG_OVL_INTSTA); - writel_relaxed(OVL_FME_CPL_INT, ovl->regs + DISP_REG_OVL_INTEN); + writel_relaxed(ovl->data->vblank_en_mask, + ovl->regs + DISP_REG_OVL_INTEN); } void mtk_ovl_disable_vblank(struct device *dev) @@ -247,14 +541,14 @@ int mtk_ovl_clk_enable(struct device *dev) { struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); - return clk_prepare_enable(ovl->clk); + return clk_bulk_prepare_enable(ovl->num_clks, ovl->clks); } void mtk_ovl_clk_disable(struct device *dev) { struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); - clk_disable_unprepare(ovl->clk); + clk_bulk_disable_unprepare(ovl->num_clks, ovl->clks); } void mtk_ovl_start(struct device *dev) @@ -268,14 +562,135 @@ void mtk_ovl_start(struct device *dev) reg = reg | OVL_LAYER_SMI_ID_EN; writel_relaxed(reg, ovl->regs + DISP_REG_OVL_DATAPATH_CON); } + + /* + * Enable the interrupts before the engine, as OVLStart() does: + * DISP_REG_SET_FIELD(OVL_INTEN, 0x0f) then OVL_EN = 1. + * + * 0x0f covers the four low interrupt-enable bits - REG_CMT_INTEN, + * FME_CPL_INTEN, FME_UND_INTEN and OVL_SWRS_INTEN - and that map is + * documented for MT6589 only, which is where the value lives. It is + * read from ->data here rather than written as a literal so that this + * shared path cannot enable MT6589's interrupt bits on an SoC whose + * map is different or unknown. Nothing in this driver ever set + * OVL_INTEN outside the reset and stop paths, so on MT6589 the engine + * used to run with every interrupt masked and the underflow condition + * was reported without the completion that should follow it. + * + * A SoC that does not set vblank_en_mask keeps the interrupts + * masked here, which is exactly what it did before this write + * existed - mtk_ovl_enable_vblank() is what turns them on for it. + */ + writel_relaxed(ovl->data->vblank_en_mask, + ovl->regs + DISP_REG_OVL_INTEN); writel_relaxed(0x1, ovl->regs + DISP_REG_OVL_EN); + + /* + * Report what the engine was actually left holding, plus the state + * of the flow-control FSM that decides whether it ever started. + * + * OVL_TRIG bit0 is OVL_SW_TRIG: 0 means "use the hardware sof", 1 + * means "software control enables the engine". Nothing sets it here + * or in the stock driver, so the OVL depends on the MUTEX emitting + * sof. That must stay 0 - setting it would fake a start and hide + * the fault - so it is only ever reported. + * + * flow is OVL_FLOW_CTRL_DBG (0x14003240). Its reset value 0x000f8c01 + * (draft/ds/ovl.txt:220209) decodes as FSM_STATE == 1, and the stock + * driver says exactly what 1 and 2 mean: "OVL at IDLE state(0x1), if + * en=0; OVL at WAIT state(0x2), if en=1" (kernel ddp_path.c:262). + * So after OVL_EN = 1 a healthy, merely not-yet-triggered overlay + * must read FSM_STATE 2. Any other value is the state the stock + * driver calls "ovl abnormal" and resets the block over + * (ddp_path.c:265-273), and it names the register to dump. Bit 21 + * (TRIG) is the overlay's own trigger seen arriving, and bit 28 + * (OVL_START) latches that it started, so those two separate "no sof + * arrived" from "sof arrived but the engine refused". + */ + u32 en, flow, fsm_state, cg; + + en = readl(ovl->regs + DISP_REG_OVL_EN); + flow = readl(ovl->regs + DISP_REG_OVL_FLOW_CTRL_DBG); + fsm_state = flow & GENMASK(9, 0); + /* + * ~0 rather than 0 when there is no mapping, so a missing mapping is + * visible in the line instead of silently reading as "no gate set". + */ + if (!ovl->cfg_regmap || + regmap_read(ovl->cfg_regmap, + MT6589_DISPSYS_CONFIG_CG_CON0, &cg)) + cg = ~0U; + + dev_dbg(dev, + "ovl: en=%#x trig=%#x roi=%#x src_con=%#x sta=%#x cg_con0=%#x fsm=%#x fsm_state=%#x\n", + en, + readl(ovl->regs + DISP_REG_OVL_TRIG), + readl(ovl->regs + DISP_REG_OVL_ROI_SIZE), + readl(ovl->regs + DISP_REG_OVL_SRC_CON), + readl(ovl->regs + DISP_REG_OVL_STA), + cg, flow, fsm_state); + + /* The crossbar select for this path, for the same diagnostic. */ + if (ovl->cfg_regmap) { + u32 ovl_mout = ~0U, color_mout = ~0U, rdma0_sel = ~0U; + u32 color_sel = ~0U, bls_sel = ~0U; + + regmap_read(ovl->cfg_regmap, MT6589_ROUTE_OVL_MOUT_EN, + &ovl_mout); + regmap_read(ovl->cfg_regmap, MT6589_ROUTE_COLOR_MOUT_EN, + &color_mout); + regmap_read(ovl->cfg_regmap, MT6589_ROUTE_RDMA0_OUT_SEL, + &rdma0_sel); + regmap_read(ovl->cfg_regmap, MT6589_ROUTE_COLOR_SEL, + &color_sel); + regmap_read(ovl->cfg_regmap, MT6589_ROUTE_BLS_SEL, + &bls_sel); + dev_dbg(dev, + "MT6589 route: ovl_mout=%#x color_mout=%#x color_sel=%#x bls_sel=%#x rdma0_out=%#x\n", + ovl_mout, color_mout, color_sel, bls_sel, rdma0_sel); + } + + /* + * The same three conditions the stock driver checks before it + * releases the mutex (kernel ddp_path.c:247-252 and :265-273): + * OVL_EN clear, the OVL clock gates still set, or the flow-control + * FSM parked in a state that is neither IDLE (1) nor WAIT (2). Each + * leaves the overlay unable to run while nothing else in the log + * says why. The clock gate term is guarded on the mapping rather + * than on has_dispsys_cfg, so a SoC whose parent has no memory + * resource is simply not judged on it. + */ + if (!(en & 0x1) || + (ovl->cfg_regmap && + (cg & (DISP_CG_CON0_OVL_ENGINE | DISP_CG_CON0_OVL_SMI))) || + (fsm_state != 0x1 && fsm_state != 0x2)) + dev_err(dev, + "ovl abnormal: en=%#x cg_con0=%#x fsm_state=%#x - clock gated, engine disabled, or the FSM is parked\n", + en, cg, fsm_state); } void mtk_ovl_stop(struct device *dev) { struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + /* + * Mask the interrupts before stopping the engine, as OVLStop() does + * (INTEN = 0, EN = 0). Stopping the engine with a latched status + * still enabled makes the level-triggered interrupt fire again + * immediately and keeps re-entering the handler. + */ + writel_relaxed(0x0, ovl->regs + DISP_REG_OVL_INTEN); writel_relaxed(0x0, ovl->regs + DISP_REG_OVL_EN); + + /* + * MT6589's OVL_INTSTA is write-zero-to-clear. The downstream + * OVLStop() clears it when handing the engine back to setup; + * do the same when this SoC's documented interrupt map is + * available. Keep the generic path untouched on other SoCs. + */ + if (ovl->data->int_all_mask) + writel_relaxed(0x0, ovl->regs + DISP_REG_OVL_INTSTA); + if (ovl->data->smi_id_en) { unsigned int reg; @@ -310,25 +725,89 @@ static void mtk_ovl_set_bit_depth(struct device *dev, int idx, u32 format, OVL_CON_CLRFMT_BIT_DEPTH_MASK(idx)); } +static void mt6589_ovl_write_yuv_matrix(struct mtk_disp_ovl *ovl, + unsigned int idx, struct cmdq_pkt *cmdq_pkt) +{ + void __iomem *base = ovl->regs; + unsigned int reg_base = DISP_REG_OVL_Y2R_BASE(idx); + + mtk_ddp_write(cmdq_pkt, + (mt6589_yuv2rgb_coef[0][1] << 16) | (mt6589_yuv2rgb_coef[0][0] & 0x1FFF), + &ovl->cmdq_reg, base, reg_base + Y2R_R0); + mtk_ddp_write(cmdq_pkt, mt6589_yuv2rgb_coef[0][2] & 0x1FFF, + &ovl->cmdq_reg, base, reg_base + Y2R_R1); + mtk_ddp_write(cmdq_pkt, + (mt6589_yuv2rgb_coef[1][1] << 16) | (mt6589_yuv2rgb_coef[1][0] & 0x1FFF), + &ovl->cmdq_reg, base, reg_base + Y2R_G0); + mtk_ddp_write(cmdq_pkt, mt6589_yuv2rgb_coef[1][2] & 0x1FFF, + &ovl->cmdq_reg, base, reg_base + Y2R_G1); + mtk_ddp_write(cmdq_pkt, + (mt6589_yuv2rgb_coef[2][1] << 16) | (mt6589_yuv2rgb_coef[2][0] & 0x1FFF), + &ovl->cmdq_reg, base, reg_base + Y2R_B0); + mtk_ddp_write(cmdq_pkt, mt6589_yuv2rgb_coef[2][2] & 0x1FFF, + &ovl->cmdq_reg, base, reg_base + Y2R_B1); + /* + * Offset fields are 9-bit signed (sign + 8.0): the U/V offsets of + * -128 must be programmed as 0x180 like the downstream kernel does. + */ + mtk_ddp_write(cmdq_pkt, + (mt6589_yuv2rgb_coef[3][1] << 16) | mt6589_yuv2rgb_coef[3][0], + &ovl->cmdq_reg, base, reg_base + Y2R_YUV_A0); + mtk_ddp_write(cmdq_pkt, mt6589_yuv2rgb_coef[3][2], + &ovl->cmdq_reg, base, reg_base + Y2R_YUV_A1); + mtk_ddp_write(cmdq_pkt, mt6589_yuv2rgb_coef[4][0], + &ovl->cmdq_reg, base, reg_base + Y2R_RGB_A0); + mtk_ddp_write(cmdq_pkt, 0, &ovl->cmdq_reg, base, reg_base + Y2R_RGB_A1); +} + void mtk_ovl_config(struct device *dev, unsigned int w, unsigned int h, unsigned int vrefresh, unsigned int bpc, struct cmdq_pkt *cmdq_pkt) { struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + /* + * Soft reset first, wait for it to take effect, then release it, and + * only then program the mode. The order matters: the reset clears the + * engine's configuration, so writing OVL_ROI_SIZE and OVL_ROI_BGCLR + * before asserting it left the block with no frame size at all, and + * the stock driver has the same order - OVLReset() runs and only then + * OVLConfig() writes OVL_ROI_SIZE (ddp_ovl.c). + * + * The data sheet requires polling the engine's run bit until it reads + * 0 before the reset is released. + */ + mtk_ddp_write(cmdq_pkt, 0x1, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_RST); + + if (!cmdq_pkt) { + unsigned int i; + + for (i = 0; i < 10000; i++) { + /* + * Wait for OVL_STA bit0 (OVL_RUN) to drop. This used + * to poll OVL_INTSTA bit0, but that is OVL_REG_CMT - + * a latched event, not the run state - so the loop + * could exit while the engine was still running and + * the reset had not taken effect. + */ + if (!(readl(ovl->regs + DISP_REG_OVL_STA) & BIT(0))) + break; + cpu_relax(); + } + } + + mtk_ddp_write(cmdq_pkt, 0x0, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_RST); + if (w != 0 && h != 0) mtk_ddp_write_relaxed(cmdq_pkt, h << 16 | w, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_ROI_SIZE); /* - * The background color must be opaque black (ARGB), - * otherwise the alpha blending will have no effect + * The background color must be opaque black (ARGB), + * otherwise the alpha blending will have no effect */ mtk_ddp_write_relaxed(cmdq_pkt, OVL_COLOR_ALPHA, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_ROI_BGCLR); - - mtk_ddp_write(cmdq_pkt, 0x1, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_RST); - mtk_ddp_write(cmdq_pkt, 0x0, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_RST); } unsigned int mtk_ovl_layer_nr(struct device *dev) @@ -354,10 +833,10 @@ int mtk_ovl_layer_check(struct device *dev, unsigned int idx, return -EINVAL; /* - * TODO: Rotating/reflecting YUV buffers is not supported at this time. - * Only RGB[AX] variants are supported. - * Since DRM_MODE_ROTATE_0 means "no rotation", we should not - * reject layers with this property. + * TODO: Rotating/reflecting YUV buffers is not supported at this time. + * Only RGB[AX] variants are supported. + * Since DRM_MODE_ROTATE_0 means "no rotation", we should not + * reject layers with this property. */ if (state->fb->format->is_yuv && (state->rotation & ~DRM_MODE_ROTATE_0)) return -EINVAL; @@ -368,24 +847,52 @@ int mtk_ovl_layer_check(struct device *dev, unsigned int idx, void mtk_ovl_layer_on(struct device *dev, unsigned int idx, struct cmdq_pkt *cmdq_pkt) { - unsigned int gmc_thrshd_l; - unsigned int gmc_thrshd_h; - unsigned int gmc_value; struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); mtk_ddp_write(cmdq_pkt, 0x1, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_RDMA_CTRL(idx)); - gmc_thrshd_l = GMC_THRESHOLD_LOW >> - (GMC_THRESHOLD_BITS - ovl->data->gmc_bits); - gmc_thrshd_h = GMC_THRESHOLD_HIGH >> - (GMC_THRESHOLD_BITS - ovl->data->gmc_bits); - if (ovl->data->gmc_bits == 10) - gmc_value = gmc_thrshd_h | gmc_thrshd_h << 16; - else - gmc_value = gmc_thrshd_l | gmc_thrshd_l << 8 | - gmc_thrshd_h << 16 | gmc_thrshd_h << 24; - mtk_ddp_write(cmdq_pkt, gmc_value, - &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_RDMA_GMC(idx)); + if (ovl->data->rdma_gmc_setting) { + /* + * Program the SoC-specific value during layer setup, + * before OVL_EN is asserted. MT6589's downstream path + * writes this setting for every OVL internal RDMA. + */ + mtk_ddp_write(cmdq_pkt, ovl->data->rdma_gmc_setting, + &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_RDMA_GMC(idx)); + } else { + unsigned int gmc_thrshd_l; + unsigned int gmc_thrshd_h; + unsigned int gmc_value; + + gmc_thrshd_l = GMC_THRESHOLD_LOW >> + (GMC_THRESHOLD_BITS - ovl->data->gmc_bits); + gmc_thrshd_h = GMC_THRESHOLD_HIGH >> + (GMC_THRESHOLD_BITS - ovl->data->gmc_bits); + /* + * The GMC thresholds are 10-bit fields in units of 16 bytes. + * DISEN_THRD must be greater than EN_THRD but smaller than + * FIFO_SIZE. On MT6589 the default FIFO_SIZE is 256, while + * the generic high threshold calculation also produces 256. + * Clamp it to 255 to keep the programmed threshold within + * the hardware's documented limits. + */ + if (ovl->data->gmc_fifo_size && + gmc_thrshd_h >= ovl->data->gmc_fifo_size) + gmc_thrshd_h = ovl->data->gmc_fifo_size - 1; + + gmc_value = (gmc_thrshd_l & GENMASK(9, 0)) | + ((gmc_thrshd_h & GENMASK(9, 0)) << 16); + mtk_ddp_write(cmdq_pkt, gmc_value, + &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_RDMA_GMC(idx)); + } + + /* For MT6589, explicitly set layer source to memory (0) */ + if (ovl->data->set_layer_src) + mtk_ddp_write_mask(cmdq_pkt, 0, &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_CON(idx), GENMASK(29, 28)); + mtk_ddp_write_mask(cmdq_pkt, BIT(idx), &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_SRC_CON, BIT(idx)); } @@ -401,23 +908,22 @@ void mtk_ovl_layer_off(struct device *dev, unsigned int idx, DISP_REG_OVL_RDMA_CTRL(idx)); } -static unsigned int mtk_ovl_fmt_convert(struct mtk_disp_ovl *ovl, - struct mtk_plane_state *state) +static unsigned int mtk_ovl_fmt_convert(struct mtk_disp_ovl *ovl, struct mtk_plane_state *state) { unsigned int fmt = state->pending.format; unsigned int blend_mode = DRM_MODE_BLEND_COVERAGE; /* - * For the platforms where OVL_CON_CLRFMT_MAN is defined in the hardware data sheet - * and supports premultiplied color formats, such as OVL_CON_CLRFMT_PARGB8888. - * - * Check blend_modes in the driver data to see if premultiplied mode is supported. - * If not, use coverage mode instead to set it to the supported color formats. - * - * Current DRM assumption is that alpha is default premultiplied, so the bitmask of - * blend_modes must include BIT(DRM_MODE_BLEND_PREMULTI). Otherwise, mtk_plane_init() - * will get an error return from drm_plane_create_blend_mode_property() and - * state->base.pixel_blend_mode should not be used. + * For the platforms where OVL_CON_CLRFMT_MAN is defined in the hardware data sheet + * and supports premultiplied color formats, such as OVL_CON_CLRFMT_PARGB8888. + * + * Check blend_modes in the driver data to see if premultiplied mode is supported. + * If not, use coverage mode instead to set it to the supported color formats. + * + * Current DRM assumption is that alpha is default premultiplied, so the bitmask of + * blend_modes must include BIT(DRM_MODE_BLEND_PREMULTI). Otherwise, mtk_plane_init() + * will get an error return from drm_plane_create_blend_mode_property() and + * state->base.pixel_blend_mode should not be used. */ if (ovl->data->blend_modes & BIT(DRM_MODE_BLEND_PREMULTI)) blend_mode = state->base.pixel_blend_mode; @@ -461,12 +967,54 @@ static unsigned int mtk_ovl_fmt_convert(struct mtk_disp_ovl *ovl, OVL_CON_CLRFMT_ABGR8888 : OVL_CON_CLRFMT_PABGR8888; case DRM_FORMAT_UYVY: - return OVL_CON_CLRFMT_UYVY | OVL_CON_MTX_YUV_TO_RGB; + return OVL_CON_CLRFMT_UYVY; case DRM_FORMAT_YUYV: - return OVL_CON_CLRFMT_YUYV | OVL_CON_MTX_YUV_TO_RGB; + return OVL_CON_CLRFMT_YUYV; } } +/* + * OVL_CON[15:12] CLRFMT, per the MT6589 data sheet: + * 0000 RGB888, 0001 RGB565, 0010 ARGB888, 0011 PARGB8888, + * 0100 xARGB8888, 1000 YUYV, 1001 UYVY. + * + * YUV to RGB conversion is implied purely by selecting a YUV CLRFMT; the + * coefficients are programmed separately by mt6589_ovl_write_yuv_matrix(). + * There is no matrix enable bit in OVL_CON on this SoC, and OVL_CON[23:16] + * is HORI_BLOCK_NUM, which nothing here sets. + */ +static unsigned int mt6589_fmt_convert(unsigned int fmt, unsigned int blend_mode) +{ + switch (fmt) { + case DRM_FORMAT_RGB565: return (1 << 12); + case DRM_FORMAT_BGR565: return (1 << 12) | OVL_CON_BYTE_SWAP; + case DRM_FORMAT_RGB888: return (0 << 12); + case DRM_FORMAT_BGR888: return (0 << 12) | OVL_CON_BYTE_SWAP; + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: return (3 << 12); + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRX8888: return (3 << 12) | OVL_CON_BYTE_SWAP; + case DRM_FORMAT_ARGB8888: + return (2 << 12); + case DRM_FORMAT_ABGR8888: + return (2 << 12) | OVL_CON_BYTE_SWAP; + /* + * 0100 is xARGB8888 on this SoC: the top byte is dropped rather + * than used as alpha. MT6589 has no CONST_BLD bit in OVL_PITCH to + * supply a constant alpha, so an XRGB framebuffer simply ignores + * whatever the buffer carries there. Mapping XRGB8888 onto 0010 + * (ARGB8888) instead would make the ignored byte modulate blending. + */ + case DRM_FORMAT_XRGB8888: + return (4 << 12); + case DRM_FORMAT_XBGR8888: + return (4 << 12) | OVL_CON_BYTE_SWAP; + case DRM_FORMAT_UYVY: return (9 << 12); + case DRM_FORMAT_YUYV: return (8 << 12); + } + return 0; +} + static void mtk_ovl_afbc_layer_config(struct mtk_disp_ovl *ovl, unsigned int idx, struct mtk_plane_pending_state *pending, @@ -503,7 +1051,7 @@ void mtk_ovl_layer_config(struct device *dev, unsigned int idx, unsigned int offset = (pending->y << 16) | pending->x; unsigned int src_size = (pending->height << 16) | pending->width; unsigned int blend_mode = state->base.pixel_blend_mode; - unsigned int ignore_pixel_alpha = 0; + unsigned int ignore_pixel_alpha = 0, const_blend = 0; unsigned int con; if (!pending->enable) { @@ -511,30 +1059,37 @@ void mtk_ovl_layer_config(struct device *dev, unsigned int idx, return; } - con = mtk_ovl_fmt_convert(ovl, state); + if (ovl->data->fmt_convert) + con = ovl->data->fmt_convert(fmt, blend_mode); + else + con = mtk_ovl_fmt_convert(ovl, state); + + if (ovl->data->has_const_blend) + const_blend = OVL_CONST_BLEND; + if (state->base.fb) { con |= state->base.alpha & OVL_CON_ALPHA; /* - * For blend_modes supported SoCs, always enable alpha blending. - * For blend_modes unsupported SoCs, enable alpha blending when has_alpha is set. + * For blend_modes supported SoCs, always enable alpha blending. + * For blend_modes unsupported SoCs, enable alpha blending when has_alpha is set. */ if (blend_mode || state->base.fb->format->has_alpha) con |= OVL_CON_AEN; /* - * Although the alpha channel can be ignored, CONST_BLD must be enabled - * for XRGB format, otherwise OVL will still read the value from memory. - * For RGB888 related formats, whether CONST_BLD is enabled or not won't - * affect the result. Therefore we use !has_alpha as the condition. + * Although the alpha channel can be ignored, CONST_BLD must be enabled + * for XRGB format, otherwise OVL will still read the value from memory. + * For RGB888 related formats, whether CONST_BLD is enabled or not won't + * affect the result. Therefore we use !has_alpha as the condition. */ if (blend_mode == DRM_MODE_BLEND_PIXEL_NONE || !state->base.fb->format->has_alpha) - ignore_pixel_alpha = OVL_CONST_BLEND; + ignore_pixel_alpha = const_blend; } /* - * Treat rotate 180 as flip x + flip y, and XOR the original rotation value - * to flip x + flip y to support both in the same time. + * Treat rotate 180 as flip x + flip y, and XOR the original rotation value + * to flip x + flip y to support both in the same time. */ if (rotation & DRM_MODE_ROTATE_180) rotation ^= DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y; @@ -555,6 +1110,19 @@ void mtk_ovl_layer_config(struct device *dev, unsigned int idx, mtk_ddp_write_relaxed(cmdq_pkt, con, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_CON(idx)); + /* + * OVL_PITCH is the source pitch in bytes on MT6589. The Lenovo BSP + * passes src_pitch directly to DISP_REG_OVL_L_PITCH, while the + * layer address uses src_y * src_pitch together with src_x * bpp. + * DRM's pending->pitch is already the byte pitch from fb->pitches[0], + * so dividing it by cpp would make every line advance too little. + * + * For example, a 1280x800 RGB565 framebuffer has a DRM pitch of + * 2560 bytes; programming 1280 here shears the image after the + * first line. + */ + pitch_lsb = pending->pitch & GENMASK(15, 0); + mtk_ddp_write_relaxed(cmdq_pkt, pitch_lsb | ignore_pixel_alpha, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_PITCH(idx)); mtk_ddp_write_relaxed(cmdq_pkt, src_size, &ovl->cmdq_reg, ovl->regs, @@ -569,6 +1137,23 @@ void mtk_ovl_layer_config(struct device *dev, unsigned int idx, mtk_ovl_set_bit_depth(dev, idx, fmt, cmdq_pkt); mtk_ovl_layer_on(dev, idx, cmdq_pkt); + + /* For MT6589, write YUV conversion matrix if needed */ + if (ovl->data->fmt_convert == mt6589_fmt_convert && + (fmt == DRM_FORMAT_UYVY || fmt == DRM_FORMAT_YUYV)) + mt6589_ovl_write_yuv_matrix(ovl, idx, cmdq_pkt); + + /* + * Read the layer back once it has actually been programmed. The + * readback in mtk_ovl_start() runs before any layer_config(), so it + * cannot show whether the framebuffer reached the hardware: it + * reported src_con = 0 simply because no layer had been set up yet. + * + * This is the one that matters while RDMA0_EOF_ABNORMAL is still + * being raised alongside FME_CPL - the frame completes, but the + * overlay's own RDMA does not finish by EOF. If L0_ADDR is zero or + * the pitch is wrong, that is the reason. + */ } void mtk_ovl_bgclr_in_on(struct device *dev) @@ -594,6 +1179,37 @@ void mtk_ovl_bgclr_in_off(struct device *dev) static int mtk_disp_ovl_bind(struct device *dev, struct device *master, void *data) { + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + int ret; + + /* + * On MT6589, OVL's initial runtime-PM reference is taken in probe, + * before the M4U driver necessarily registers the IOMMU and adds + * the device link to LARB2. Resuming OVL at that point cannot + * activate the supplier through the link. + * + * This component bind runs after the M4U component master has bound + * the LARBs. Cycle the existing reference now, so runtime resume + * sees the IOMMU link and LARB2's larb->mmu mapping is ready. + * Do this before the CRTC starts the display pipeline. + */ + if (!ovl->data->has_dispsys_cfg) + return 0; + + ret = pm_runtime_put_sync(dev); + if (ret < 0) { + pm_runtime_get_noresume(dev); + return dev_err_probe(dev, ret, + "Failed to suspend OVL before LARB setup\n"); + } + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + pm_runtime_get_noresume(dev); + return dev_err_probe(dev, ret, + "Failed to resume OVL after LARB setup\n"); + } + return 0; } @@ -607,10 +1223,49 @@ static const struct component_ops mtk_disp_ovl_component_ops = { .unbind = mtk_disp_ovl_unbind, }; +/* + * Reject SoC data that would leave the interrupt path self-inconsistent. + * + * int_all_mask exists for one reason: whatever mtk_ovl_enable_vblank() + * switches on in OVL_INTEN has to be acknowledged again by the ISR, or a + * level-triggered line stays asserted and re-enters the handler forever. + * So the enable mask must be a subset of the acknowledge mask. Checking + * it here is what lets the ISR treat a zero int_all_mask as a deliberate + * value instead of "unknown": the check proves that a zero acknowledge mask + * can only be paired with a zero enable mask, i.e. a SoC that never raises + * an OVL interrupt through this driver at all. + * + * The second condition keeps a status bit that could fire vblank_cb out of + * a SoC with no enable mask: nothing would ever enable it, so gating + * vblank on it would be a lie, while claiming it does fire means "any + * status bit counts", which cannot be true if there are no status bits. + */ +static int mtk_disp_ovl_check_data(struct device *dev, + const struct mtk_disp_ovl_data *data) +{ + if (data->vblank_en_mask & ~data->int_all_mask) { + dev_err(dev, + "OVL_INTEN bits %#x are not acknowledged by int_all_mask %#x\n", + data->vblank_en_mask, data->int_all_mask); + return -EINVAL; + } + + if (!data->int_all_mask && data->fme_cpl_bit) { + dev_err(dev, + "fme_cpl_bit %#x cannot fire vblank with int_all_mask 0\n", + data->fme_cpl_bit); + return -EINVAL; + } + + return 0; +} + static int mtk_disp_ovl_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mtk_disp_ovl *priv; + struct iommu_domain *domain; + struct iommu_fwspec *fwspec; int irq; int ret; @@ -618,26 +1273,172 @@ static int mtk_disp_ovl_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; + priv->data = of_device_get_match_data(dev); + if (!priv->data) + return -ENODEV; + + /* + * MT6589 allocates display buffers against the OVL device. + * Do not start the display pipeline without the IOMMU domain + * needed to interpret its DMA addresses. + * + * The IOMMU provider may not have been ready when the bus first + * configured DMA. Deferring this probe lets the driver core retry + * DMA configuration after the provider becomes available. + */ + if (priv->data->has_dispsys_cfg) { + fwspec = dev_iommu_fwspec_get(dev); + domain = iommu_get_domain_for_dev(dev); + if (!fwspec || !fwspec->num_ids || + !dev->iommu_group || !domain) + return dev_err_probe(dev, -EPROBE_DEFER, + "waiting for M4U domain " + "(fwspec=%u group=%u domain=%u)\n", + !!fwspec, !!dev->iommu_group, + !!domain); + } + irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; - priv->clk = devm_clk_get(dev, NULL); - if (IS_ERR(priv->clk)) - return dev_err_probe(dev, PTR_ERR(priv->clk), - "failed to get ovl clk\n"); + /* + * The node declares both the engine and the SMI clock. Taking only + * index 0 left SMI gated, and without it the GMC and M4U writes this + * driver performs may not reach the hardware. + */ + /* + * Take every clock the node declares. devm_clk_get() can only return + * one of them: with no clock-names property it always resolves index + * 0, so the SMI clock stayed gated. This helper walks the whole + * clocks property by index instead. + */ + ret = devm_clk_bulk_get_all(dev, &priv->clks); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get ovl clks\n"); + priv->num_clks = ret; priv->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->regs)) return dev_err_probe(dev, PTR_ERR(priv->regs), "failed to ioremap ovl\n"); + + /* + * Resolve and validate the per-SoC data before anything else: the + * reset below is gated on it, so it has to be known first, and a + * node whose data fails the invariant check must not be left with + * its clocks on and its registers half-written. + */ + ret = mtk_disp_ovl_check_data(dev, priv->data); + if (ret) + return ret; + + /* + * OVL and DISPSYS_CONFIG are sibling nodes in the device tree. + * Obtain the syscon through the clock-provider phandle rather than + * assuming the OVL node's parent is DISPSYS_CONFIG. + * + * The register is at offset 0x100 within the syscon. This is + * diagnostic-only, so failure to obtain the regmap is non-fatal. + */ + if (priv->data->has_dispsys_cfg) { + struct device_node *np; + struct resource cfg_res; + + np = of_parse_phandle(dev->of_node, "clocks", 0); + if (np) { + priv->cfg_regmap = syscon_node_to_regmap(np); + if (!of_address_to_resource(np, 0, &cfg_res)) { + priv->cfg_regs = devm_ioremap(dev, cfg_res.start, + resource_size(&cfg_res)); + if (!priv->cfg_regs) + dev_warn(dev, + "failed to map DISPSYS path-debug registers\n"); + } + of_node_put(np); + if (IS_ERR(priv->cfg_regmap)) + priv->cfg_regmap = NULL; + } + } + + /* + * Reset the engine before touching it. Writing the registers + * directly needs the block's clock running, but the bootloader can + * leave it in a state where the engine is still fetching, so assert + * the reset first and release it with the clocks enabled. + */ + ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks); + if (ret) + return dev_err_probe(dev, ret, "failed to enable ovl clks\n"); + + /* + * Look the reset up by index. Passing a name would make the core + * search the "reset-names" property first, and the display nodes + * carry a bare "resets = <&dispsys MT6589_DISP_OVL_RST>" with no + * reset-names, so a named lookup fails with -ENOENT before any reset + * controller is ever consulted. Every other MediaTek display driver + * here looks its reset up this way for the same reason. + * + * Only a SoC whose node actually declares the phandle may do this. + * The lookup is not optional, so on a node with no "resets" it + * returns -ENOENT and probe would fail - which is what the other + * eight SoCs' nodes would get, since only the MT6589 node declares + * one. Gating on data->has_reset keeps their existing behaviour (no + * warm reset) while MT6589 gets the reset it needs. + */ + if (priv->data->has_reset) { + priv->rstc = devm_reset_control_get(dev, NULL); + if (IS_ERR(priv->rstc)) { + ret = PTR_ERR(priv->rstc); + clk_bulk_disable_unprepare(priv->num_clks, priv->clks); + return dev_err_probe(dev, ret, + "failed to get reset control\n"); + } + + ret = reset_control_reset(priv->rstc); + if (ret) { + clk_bulk_disable_unprepare(priv->num_clks, priv->clks); + return dev_err_probe(dev, ret, "failed to reset ovl\n"); + } + } + + /* Stop any leftover OVL activity from bootloader */ + writel(0x0, priv->regs + DISP_REG_OVL_EN); + writel(0x0, priv->regs + DISP_REG_OVL_INTEN); + writel(0x0, priv->regs + DISP_REG_OVL_INTSTA); + + /* + * Downstream OVLReset() also zeros SRC_CON and every layer's + * address/pitch path. The warm reset line alone does not clear + * those registers on MT6589, so a bootloader that left L0 live + * would keep the internal RDMA fetching until the first DRM + * plane config. Only SoCs that take the reset (has_reset) need + * this; the others never asserted the line in the first place. + */ + if (priv->data->has_reset) { + unsigned int i, layers = priv->data->layer_nr; + + writel(0x0, priv->regs + DISP_REG_OVL_SRC_CON); + writel(0x0, priv->regs + DISP_REG_OVL_ROI_SIZE); + writel(0xffffffff, priv->regs + DISP_REG_OVL_ROI_BGCLR); + + for (i = 0; i < layers; i++) { + writel(0x0, priv->regs + DISP_REG_OVL_CON(i)); + writel(0x0, priv->regs + DISP_REG_OVL_SRC_SIZE(i)); + writel(0x0, priv->regs + DISP_REG_OVL_OFFSET(i)); + writel(0x0, priv->regs + DISP_REG_OVL_ADDR(priv, i)); + writel(0x0, priv->regs + DISP_REG_OVL_PITCH(i)); + writel(0x0, priv->regs + DISP_REG_OVL_RDMA_CTRL(i)); + writel(0x0, priv->regs + DISP_REG_OVL_RDMA_GMC(i)); + } + } + #if IS_REACHABLE(CONFIG_MTK_CMDQ) ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0); if (ret) dev_dbg(dev, "get mediatek,gce-client-reg fail!\n"); #endif - priv->data = of_device_get_match_data(dev); platform_set_drvdata(pdev, priv); ret = devm_request_irq(dev, irq, mtk_disp_ovl_irq_handler, @@ -647,8 +1448,28 @@ static int mtk_disp_ovl_probe(struct platform_device *pdev) pm_runtime_enable(dev); + /* + * Not for power management - the clocks are already held above - but + * for the device link the IOMMU driver created from this device to + * its SMI LARB. The LARB programs SMI_SECUR_CON (the per-port + * physical/virtual select) only on a runtime resume that happens + * *after* the IOMMU has attached this device to its domain, and that + * attach already ran before this probe function. Without the get, + * the port-mode update relies on another LARB2 client (RDMA0) + * probing later and runtime-resuming the LARB in between - i.e. on + * probe ordering - and if that never happens the overlay keeps + * reading its IOVA framebuffers through ports still in physical + * mode. + */ + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + pm_runtime_disable(dev); + return dev_err_probe(dev, ret, "Failed to enable power\n"); + } + ret = component_add(dev, &mtk_disp_ovl_component_ops); if (ret) { + pm_runtime_put_sync(dev); pm_runtime_disable(dev); return dev_err_probe(dev, ret, "Failed to add component\n"); } @@ -659,6 +1480,7 @@ static int mtk_disp_ovl_probe(struct platform_device *pdev) static void mtk_disp_ovl_remove(struct platform_device *pdev) { component_del(&pdev->dev, &mtk_disp_ovl_component_ops); + pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); } @@ -667,6 +1489,9 @@ static const struct mtk_disp_ovl_data mt2701_ovl_driver_data = { .gmc_bits = 8, .layer_nr = 4, .fmt_rgb565_is_0 = false, + .has_const_blend = true, + .int_all_mask = 0, /* see the note above the match table */ + .fme_cpl_bit = OVL_FME_CPL_ANY, .formats = mt8173_formats, .num_formats = ARRAY_SIZE(mt8173_formats), }; @@ -677,6 +1502,55 @@ static const struct mtk_disp_ovl_data mt8167_ovl_driver_data = { .layer_nr = 4, .fmt_rgb565_is_0 = true, .smi_id_en = true, + .has_const_blend = true, + .int_all_mask = 0, /* see the note above the match table */ + .fme_cpl_bit = OVL_FME_CPL_ANY, + .formats = mt8173_formats, + .num_formats = ARRAY_SIZE(mt8173_formats), +}; + +static const struct mtk_disp_ovl_data mt6589_ovl_driver_data = { + .addr = DISP_REG_OVL_ADDR_MT2701, + .gmc_bits = 10, + .gmc_fifo_size = 256, + .rdma_gmc_setting = 0x0101a06b, + .layer_nr = 4, + .fmt_rgb565_is_0 = false, + .blend_modes = BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE) | + BIT(DRM_MODE_BLEND_PIXEL_NONE), + .has_const_blend = false, + .set_layer_src = true, + /* + * The only OVL node in the tree that declares + * "resets = <&dispsys MT6589_DISP_OVL_RST>" (mt6589.dtsi), and so + * the only one probe may take a warm reset on. devm_reset_control_get() + * returns -ENOENT for the other eight, so this also keeps their + * probe from failing outright. + */ + .has_reset = true, + /* + * The only OVL node that sits beside the dispsys syscon + * (arch/arm/boot/dts/mediatek/mt6589.dtsi:960) rather than under a + * top-level parent, so the only one whose DISP_CG_CON0 clock gates + * can be read back in mtk_ovl_start(). + */ + .has_dispsys_cfg = true, + .vblank_en_mask = 0xF, /* Reg update, frame done, underflow, sw reset done */ + /* + * This is the only SoC here with a documented OVL_INTEN/OVL_INTSTA + * bit map, so it is the only one that can name its status bits. + * Bits 0, 1, 2 and 3 are enabled above, so all of them - and the + * whole documented map - must be acknowledged in the ISR. + */ + .int_all_mask = OVL_INT_ALL, + .fme_cpl_bit = OVL_FME_CPL_INT, + .fme_und_bit = BIT(2), + .rdma0_eof_abn_bit = BIT(4), + .rdma1_eof_abn_bit = BIT(5), + .rdma0_fifo_und_bit = BIT(8), + .rdma1_fifo_und_bit = BIT(9), + .fmt_convert = mt6589_fmt_convert, .formats = mt8173_formats, .num_formats = ARRAY_SIZE(mt8173_formats), }; @@ -686,6 +1560,9 @@ static const struct mtk_disp_ovl_data mt8173_ovl_driver_data = { .gmc_bits = 8, .layer_nr = 4, .fmt_rgb565_is_0 = true, + .has_const_blend = true, + .int_all_mask = 0, /* see the note above the match table */ + .fme_cpl_bit = OVL_FME_CPL_ANY, .formats = mt8173_formats, .num_formats = ARRAY_SIZE(mt8173_formats), }; @@ -695,6 +1572,9 @@ static const struct mtk_disp_ovl_data mt8183_ovl_driver_data = { .gmc_bits = 10, .layer_nr = 4, .fmt_rgb565_is_0 = true, + .has_const_blend = true, + .int_all_mask = 0, /* see the note above the match table */ + .fme_cpl_bit = OVL_FME_CPL_ANY, .formats = mt8173_formats, .num_formats = ARRAY_SIZE(mt8173_formats), }; @@ -704,6 +1584,9 @@ static const struct mtk_disp_ovl_data mt8183_ovl_2l_driver_data = { .gmc_bits = 10, .layer_nr = 2, .fmt_rgb565_is_0 = true, + .has_const_blend = true, + .int_all_mask = 0, /* see the note above the match table */ + .fme_cpl_bit = OVL_FME_CPL_ANY, .formats = mt8173_formats, .num_formats = ARRAY_SIZE(mt8173_formats), }; @@ -717,6 +1600,9 @@ static const struct mtk_disp_ovl_data mt8192_ovl_driver_data = { .blend_modes = BIT(DRM_MODE_BLEND_PREMULTI) | BIT(DRM_MODE_BLEND_COVERAGE) | BIT(DRM_MODE_BLEND_PIXEL_NONE), + .has_const_blend = true, + .int_all_mask = 0, /* see the note above the match table */ + .fme_cpl_bit = OVL_FME_CPL_ANY, .formats = mt8173_formats, .num_formats = ARRAY_SIZE(mt8173_formats), }; @@ -730,6 +1616,9 @@ static const struct mtk_disp_ovl_data mt8192_ovl_2l_driver_data = { .blend_modes = BIT(DRM_MODE_BLEND_PREMULTI) | BIT(DRM_MODE_BLEND_COVERAGE) | BIT(DRM_MODE_BLEND_PIXEL_NONE), + .has_const_blend = true, + .int_all_mask = 0, /* see the note above the match table */ + .fme_cpl_bit = OVL_FME_CPL_ANY, .formats = mt8173_formats, .num_formats = ARRAY_SIZE(mt8173_formats), }; @@ -744,14 +1633,48 @@ static const struct mtk_disp_ovl_data mt8195_ovl_driver_data = { .blend_modes = BIT(DRM_MODE_BLEND_PREMULTI) | BIT(DRM_MODE_BLEND_COVERAGE) | BIT(DRM_MODE_BLEND_PIXEL_NONE), + .has_const_blend = true, + .int_all_mask = 0, /* see the note above the match table */ + .fme_cpl_bit = OVL_FME_CPL_ANY, .formats = mt8195_formats, .num_formats = ARRAY_SIZE(mt8195_formats), .supports_clrfmt_ext = true, }; +/* + * The eight SoCs other than MT6589 enable no OVL interrupt at all. + * + * Nothing this driver does gives them one. Every write to OVL_INTEN is + * either data->vblank_en_mask, which is 0 for all eight and so programs + * an empty enable mask, or a literal 0 (mtk_ovl_stop(), and the leftover + * bootloader scrub in mtk_disp_ovl_probe()). Their DT nodes wire the + * OVL interrupt as a dedicated GIC line - MT8173 SPI 180, MT8183 SPI 225, + * MT8192 SPI 254, MT8195 SPI 636 - not a shared disp_ovl interrupt, so + * nothing else in the display stack raises it either. The status + * register may therefore latch bits, but nothing can raise the line and + * the handler below is unreachable from this driver. + * + * That is why their values are int_all_mask 0 and fme_cpl_bit + * OVL_FME_CPL_ANY, stated rather than defaulted: + * + * - int_all_mask 0 says "there is no status bit for this driver to + * acknowledge", which is true, and makes the acknowledge write + * leave the status register untouched - which is what these SoCs + * have always done, and is the only behaviour their unused status + * bits permit. + * - fme_cpl_bit OVL_FME_CPL_ANY keeps the vblank gate at "some status + * bit was latched" rather than "never". Were it 0 no interrupt + * could ever fire a vblank on these SoCs, which would silently stop + * any pipeline that does reach the handler for some other reason. + * + * Neither value is a guess about undocumented hardware: both are the + * values that keep these SoCs doing exactly what they do today. + */ static const struct of_device_id mtk_disp_ovl_driver_dt_match[] = { { .compatible = "mediatek,mt2701-disp-ovl", .data = &mt2701_ovl_driver_data}, + { .compatible = "mediatek,mt6589-disp-ovl", + .data = &mt6589_ovl_driver_data}, { .compatible = "mediatek,mt8167-disp-ovl", .data = &mt8167_ovl_driver_data}, { .compatible = "mediatek,mt8173-disp-ovl", diff --git a/drivers/gpu/drm/mediatek/mtk_disp_rdma.c b/drivers/gpu/drm/mediatek/mtk_disp_rdma.c index c9d41d75e7f2a3..037e69338e15cd 100644 --- a/drivers/gpu/drm/mediatek/mtk_disp_rdma.c +++ b/drivers/gpu/drm/mediatek/mtk_disp_rdma.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "mtk_crtc.h" @@ -19,6 +20,7 @@ #include "mtk_drm_drv.h" #define DISP_REG_RDMA_INT_ENABLE 0x0000 + #define DISP_REG_RDMA_INT_STATUS 0x0004 #define RDMA_TARGET_LINE_INT BIT(5) #define RDMA_FIFO_UNDERFLOW_INT BIT(4) @@ -28,14 +30,17 @@ #define RDMA_REG_UPDATE_INT BIT(0) #define DISP_REG_RDMA_GLOBAL_CON 0x0010 #define RDMA_ENGINE_EN BIT(0) -#define RDMA_MODE_MEMORY BIT(1) +/* MODE_SEL is one bit at position 1: 0 = direct link, 1 = memory + * (REG_FLD(1, 1) in the stock ddp_rdma.h, RDMA_MODE_DIRECT_LINK = 0). + * This is the field mask, so writing 0 through it selects direct link. + */ +#define RDMA_MODE_SEL BIT(1) #define DISP_REG_RDMA_SIZE_CON_0 0x0014 -#define RDMA_MATRIX_ENABLE BIT(17) -#define RDMA_MATRIX_INT_MTX_SEL GENMASK(23, 20) -#define RDMA_MATRIX_INT_MTX_BT601_to_RGB (6 << 20) #define DISP_REG_RDMA_SIZE_CON_1 0x0018 #define DISP_REG_RDMA_TARGET_LINE 0x001c #define DISP_RDMA_MEM_CON 0x0024 +#define DISP_REG_RDMA_MEM_SRC_PITCH 0x002c + #define MEM_MODE_INPUT_FORMAT_RGB565 (0x000 << 4) #define MEM_MODE_INPUT_FORMAT_RGB888 (0x001 << 4) #define MEM_MODE_INPUT_FORMAT_RGBA8888 (0x002 << 4) @@ -45,6 +50,7 @@ #define MEM_MODE_INPUT_SWAP BIT(8) #define DISP_RDMA_MEM_SRC_PITCH 0x002c #define DISP_RDMA_MEM_GMC_SETTING_0 0x0030 +#define DISP_REG_RDMA_MEM_GMC_SETTING_1 0x0038 #define DISP_REG_RDMA_FIFO_CON 0x0040 #define RDMA_FIFO_UNDERFLOW_EN BIT(31) #define RDMA_FIFO_PSEUDO_SIZE(bytes) (((bytes) / 16) << 16) @@ -52,8 +58,33 @@ #define RDMA_FIFO_SIZE(rdma) ((rdma)->data->fifo_size) #define DISP_RDMA_MEM_START_ADDR 0x0f00 +/* + * Every status bit this block can raise, and therefore the whole of what + * RDMAStart() enables. Only meaningful where data->int_enable_mask says so + * - see mtk_rdma_start(). + */ +#define RDMA_INT_ALL \ +(RDMA_REG_UPDATE_INT | RDMA_FRAME_START_INT | RDMA_FRAME_END_INT | \ + RDMA_EOF_ABNORMAL_INT | RDMA_FIFO_UNDERFLOW_INT | RDMA_TARGET_LINE_INT) + #define RDMA_MEM_GMC 0x40402020 +/* + * ARGB8888 but not XRGB8888: the RDMA block's input-format field has only + * VYUY, RGB565, RGB888 and ARGB8888 encodings (data sheet, RDMA SIZE_CON_0 + * OUTPUT_FORMAT), so there is nothing to map XRGB8888 to. Advertising it + * here made DRM pick it for the usual XRGB8888 framebuffer and then hand + * 4-byte pixels to the block as if they were ARGB. The OVL side, which + * does have an xARGB8888 encoding, is where XRGB content is handled. + */ +static const u32 mt6589_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB565, + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, +}; + static const u32 mt8173_formats[] = { DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, @@ -68,10 +99,52 @@ static const u32 mt8173_formats[] = { DRM_FORMAT_YUYV, }; +struct mtk_disp_rdma; + struct mtk_disp_rdma_data { unsigned int fifo_size; const u32 *formats; size_t num_formats; + u32 size_con0; + u32 size_con1; + unsigned int (*fmt_convert)(unsigned int fmt); + u32 mem_start_addr_reg; + u32 mem_gmc_val; + void (*reset)(struct mtk_disp_rdma *rdma); + /* + * int_enable_mask is what mtk_rdma_start() writes to INT_ENABLE + * before enabling the engine. Zero - the value on every SoC whose + * INT_STATUS bit map this driver has not checked - leaves the + * register alone, exactly as this driver did before the MT6589 work, + * and lets mtk_rdma_enable_vblank() be the only thing that enables + * an interrupt. A non-zero mask is only set where the reset value + * of the block is known and that mask is the complete set of status + * bits, so the write cannot enable a bit this driver cannot name. + */ + unsigned int int_enable_mask; + /* + * Some old RDMA blocks have a target-specific FIFO configuration rather + * than the generic output-valid threshold used by the common driver. + * Zero means use the generic calculation below. + */ + u32 fifo_con; + /* + * direct_link_no_plane says this RDMA is chained behind a layer + * block that feeds it, and no plane is ever attached to it, so + * mtk_rdma_layer_config() never runs for it. mtk_rdma_config() then + * has to program what the layer hook would otherwise have written: + * MODE_SEL is left clear so the block passes its input straight + * through instead of fetching from a memory ring, and the input + * format, source pitch and ring start address are programmed here. + * + * The block needs no plane in that arrangement: the stock driver + * selects RDMA_MODE_DIRECT_LINK with address 0 (ddp_path.c), and + * the layer block in front is what has the plane. On the other + * SoCs the plane *is* attached to the RDMA, so layer_config does + * run and doing any of this again from the config hook would be + * both redundant and, for the mode bit, wrong. + */ + bool direct_link_no_plane; }; /* @@ -79,26 +152,48 @@ struct mtk_disp_rdma_data { * @data: local driver data */ struct mtk_disp_rdma { - struct clk *clk; + struct clk_bulk_data *clks; + struct reset_control *rstc; + int num_clks; void __iomem *regs; struct cmdq_client_reg cmdq_reg; const struct mtk_disp_rdma_data *data; void (*vblank_cb)(void *data); void *vblank_cb_data; u32 fifo_size; + bool dbg_done; + bool start_dbg_done; }; static irqreturn_t mtk_disp_rdma_irq_handler(int irq, void *dev_id) { struct mtk_disp_rdma *priv = dev_id; + u32 status, handled; - /* Clear frame completion interrupt */ - writel(0x0, priv->regs + DISP_REG_RDMA_INT_STATUS); - - if (!priv->vblank_cb) + status = readl(priv->regs + DISP_REG_RDMA_INT_STATUS); + handled = status & RDMA_INT_ALL; + if (!handled) return IRQ_NONE; - priv->vblank_cb(priv->vblank_cb_data); + /* + * MT6589 downstream acknowledges RDMA interrupt status by + * writing the inverse of the captured status value. + * See ddp_drv.c: DISP_REG_RDMA_INT_STATUS, ~reg_val. + */ + writel(~handled, priv->regs + DISP_REG_RDMA_INT_STATUS); + + if (handled & (RDMA_EOF_ABNORMAL_INT | RDMA_FIFO_UNDERFLOW_INT)) + pr_debug_ratelimited("RDMA0 fault: status=%#x global_con=%#x size0=%#x size1=%#x mem_con=%#x pitch=%#x fifo=%#x\n", + status, + readl(priv->regs + DISP_REG_RDMA_GLOBAL_CON), + readl(priv->regs + DISP_REG_RDMA_SIZE_CON_0), + readl(priv->regs + DISP_REG_RDMA_SIZE_CON_1), + readl(priv->regs + DISP_RDMA_MEM_CON), + readl(priv->regs + DISP_REG_RDMA_MEM_SRC_PITCH), + readl(priv->regs + DISP_REG_RDMA_FIFO_CON)); + + if (priv->vblank_cb && (status & RDMA_FRAME_END_INT)) + priv->vblank_cb(priv->vblank_cb_data); return IRQ_HANDLED; } @@ -160,20 +255,63 @@ int mtk_rdma_clk_enable(struct device *dev) { struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); - return clk_prepare_enable(rdma->clk); + return clk_bulk_prepare_enable(rdma->num_clks, rdma->clks); } void mtk_rdma_clk_disable(struct device *dev) { struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); - clk_disable_unprepare(rdma->clk); + clk_bulk_disable_unprepare(rdma->num_clks, rdma->clks); } +static void mtk_rdma_reset_mt6589(struct mtk_disp_rdma *rdma); + void mtk_rdma_start(struct device *dev) { + struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); + + /* + * Enable the engine's interrupts before the engine itself, as the + * stock driver does (RDMAStart() writes INT_ENABLE = 0x3F, then + * ENGINE_EN; RDMAConfig() writes 0x1F). Starting the engine first + * leaves any status latched from before running - the reset path + * zeroes INT_STATUS, but nothing clears EOF_ABNORMAL raised while + * the engine was idle - and a latched status keeps the OVL reporting + * "RDMA0 didn't complete frame" on every frame. + * + * That reasoning is MT6589's: 0x3F is only correct if all six + * interrupt bits are the ones named above, which is a fact about + * this block's INT_STATUS layout and not something the stock + * driver's constant can be assumed to mean on a different SoC. So + * the write is per-SoC data, and a SoC that has not stated a mask + * gets no write here at all - which is what this function did for + * every SoC before the MT6589 work. Those blocks are left with the + * interrupts probe() cleared, and mtk_rdma_enable_vblank() still + * turns on frame-end for them. + */ + if (rdma->data->int_enable_mask) + writel(rdma->data->int_enable_mask, + rdma->regs + DISP_REG_RDMA_INT_ENABLE); + rdma_update_bits(dev, DISP_REG_RDMA_GLOBAL_CON, RDMA_ENGINE_EN, RDMA_ENGINE_EN); + + if (rdma->data->direct_link_no_plane && !rdma->start_dbg_done) { + rdma->start_dbg_done = true; + dev_dbg(dev, + "RDMA0 post-start: global_con=%#x int_enable=%#x int_status=%#x size0=%#x size1=%#x mem_con=%#x pitch=%#x start=%#x gmc1=%#x fifo=%#x\n", + readl(rdma->regs + DISP_REG_RDMA_GLOBAL_CON), + readl(rdma->regs + DISP_REG_RDMA_INT_ENABLE), + readl(rdma->regs + DISP_REG_RDMA_INT_STATUS), + readl(rdma->regs + DISP_REG_RDMA_SIZE_CON_0), + readl(rdma->regs + DISP_REG_RDMA_SIZE_CON_1), + readl(rdma->regs + DISP_RDMA_MEM_CON), + readl(rdma->regs + DISP_REG_RDMA_MEM_SRC_PITCH), + readl(rdma->regs + rdma->data->mem_start_addr_reg), + readl(rdma->regs + DISP_REG_RDMA_MEM_GMC_SETTING_1), + readl(rdma->regs + DISP_REG_RDMA_FIFO_CON)); + } } void mtk_rdma_stop(struct device *dev) @@ -185,15 +323,97 @@ void mtk_rdma_config(struct device *dev, unsigned int width, unsigned int height, unsigned int vrefresh, unsigned int bpc, struct cmdq_pkt *cmdq_pkt) { + struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); unsigned int threshold; unsigned int reg; - struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); u32 rdma_fifo_size; + /* + * Clear the engine before programming it, as the stock path does: + * ddp_path.c calls RDMAStop(0) and RDMAReset(0) immediately before + * RDMAConfig(). mtk_rdma_reset_mt6589() matches that RDMAReset() + * but was never called from anywhere, so the block kept whatever the + * bootloader and the previous mode left in its registers. OVL + * resets itself from its own config hook; RDMA0 had no such step. + * + * This has to run here rather than from mtk_rdma_start(), because the + * CRTC configures each component before starting it - a reset in + * start() would clear the values written a moment earlier. + * + * It is also the only SoC-specific step here, and it is reached only + * through data->reset, which only the MT6589 data sets. The other + * SoCs never had a reset in this hook and must not grow one: the + * register values above are this block's, and the reset sequence + * they came from is written against the MT6589 reset state. + */ + if (rdma->data->reset) + rdma->data->reset(rdma); + + /* + * direct_link_no_plane says no plane is ever attached to this RDMA, + * so mtk_rdma_layer_config() - which programs the mode, the input + * format, the pitch and the ring address - never runs for it, and + * everything below has to be programmed here instead. + */ + if (rdma->data->direct_link_no_plane) { + /* + * Leave MODE_SEL clear so RDMA0 passes the OVL's output + * straight through instead of fetching from a memory ring. + * + * This has to be set here rather than in layer_config: no + * plane is ever attached to RDMA0 on this path, so + * layer_config never runs. It was the only place MODE_SEL + * was cleared, which meant the reset above left RDMA0 in + * memory mode and it then waited forever on a ring start + * address of zero. + */ + mtk_ddp_write_mask(cmdq_pkt, 0, &rdma->cmdq_reg, rdma->regs, + DISP_REG_RDMA_GLOBAL_CON, RDMA_MODE_SEL); + + /* + * The main path is RGB888 (the panel's format), and the stock + * driver sets RDMA_INPUT_FORMAT_RGB888 even when it selects + * direct-link mode, so the input format is not a + * memory-mode-only field. Set it here: the .config hook is + * the only place it can be written, since no plane is ever + * attached to RDMA0 on this path and so layer_config never + * runs. + */ + mtk_ddp_write_relaxed(cmdq_pkt, + rdma->data->fmt_convert(DRM_FORMAT_RGB888), + &rdma->cmdq_reg, rdma->regs, + DISP_RDMA_MEM_CON); + + /* + * Source pitch, in bytes per line. RDMA_MAX_WIDTH is a pixel + * count but this register holds bytes, and the stock driver + * programs the pitch from RDMAConfig() in direct-link mode + * too - ddp_path.c passes the real pitch with address 0 when + * it selects RDMA_MODE_DIRECT_LINK. The main path is RGB888, + * which is three bytes per pixel. + * + * This has to be written here and not in layer_config: the + * layer hook never runs on this path, since no plane is ever + * attached to RDMA0. Leaving the pitch at reset is what left + * RDMA0 raising EOF_ABNORMAL while the overlay completed + * frames around it. + * + * The start address is zero in direct-link mode for the same + * reason the stock driver passes 0: there is no memory ring + * to read from. + */ + mtk_ddp_write_relaxed(cmdq_pkt, (width * 3) & GENMASK(15, 0), + &rdma->cmdq_reg, rdma->regs, + DISP_REG_RDMA_MEM_SRC_PITCH); + mtk_ddp_write_relaxed(cmdq_pkt, 0, &rdma->cmdq_reg, + rdma->regs, + rdma->data->mem_start_addr_reg); + } + mtk_ddp_write_mask(cmdq_pkt, width, &rdma->cmdq_reg, rdma->regs, - DISP_REG_RDMA_SIZE_CON_0, 0xfff); + DISP_REG_RDMA_SIZE_CON_0, rdma->data->size_con0); mtk_ddp_write_mask(cmdq_pkt, height, &rdma->cmdq_reg, rdma->regs, - DISP_REG_RDMA_SIZE_CON_1, 0xfffff); + DISP_REG_RDMA_SIZE_CON_1, rdma->data->size_con1); if (rdma->fifo_size) rdma_fifo_size = rdma->fifo_size; @@ -210,11 +430,63 @@ void mtk_rdma_config(struct device *dev, unsigned int width, reg = RDMA_FIFO_UNDERFLOW_EN | RDMA_FIFO_PSEUDO_SIZE(rdma_fifo_size) | RDMA_OUTPUT_VALID_FIFO_THRESHOLD(threshold); + if (rdma->data->fifo_con) + reg = rdma->data->fifo_con; mtk_ddp_write(cmdq_pkt, reg, &rdma->cmdq_reg, rdma->regs, DISP_REG_RDMA_FIFO_CON); + + /* + * One-shot readback after programming, because RDMA0 raising no + * interrupt is indistinguishable from it being wedged: the OVL's + * OVL_STA only reports RDMA0_IDLE, not why. Log what the engine + * actually latched so a stuck RDMA0 can be placed rather than + * guessed at. Guarded by a flag so a per-frame failure cannot flood. + */ + /* + * Only when the writes above went straight to the hardware. + * With a command packet they are still queued in the GCE + * buffer and have not been executed, so the shadow registers + * read back here are the previous mode's values - exactly the + * misleading output this dump is meant to rule out. MT6589 + * sets shadow_register and uses CMDQ, so the interesting case + * is the one where this would be wrong; the first config runs + * with cmdq_pkt == NULL from mtk_crtc_ddp_hw_init(), which is + * where a real dump is wanted anyway. + */ + if (!cmdq_pkt && !rdma->dbg_done) { + rdma->dbg_done = true; + dev_dbg(dev, + "rdma0: global_con=%#x int_status=%#x size_con0=%#x size_con1=%#x mem_con=%#x src_pitch=%#x start_addr=%#x gmc1=%#x fifo_con=%#x\n", + readl(rdma->regs + DISP_REG_RDMA_GLOBAL_CON), + readl(rdma->regs + DISP_REG_RDMA_INT_STATUS), + readl(rdma->regs + DISP_REG_RDMA_SIZE_CON_0), + readl(rdma->regs + DISP_REG_RDMA_SIZE_CON_1), + readl(rdma->regs + DISP_RDMA_MEM_CON), + readl(rdma->regs + DISP_REG_RDMA_MEM_SRC_PITCH), + readl(rdma->regs + rdma->data->mem_start_addr_reg), + readl(rdma->regs + DISP_REG_RDMA_MEM_GMC_SETTING_1), + readl(rdma->regs + DISP_REG_RDMA_FIFO_CON)); + } } -static unsigned int rdma_fmt_convert(struct mtk_disp_rdma *rdma, - unsigned int fmt) +static unsigned int rdma_fmt_convert_mt6589(unsigned int fmt) +{ + switch (fmt) { + case DRM_FORMAT_YUYV: + return (0 << 4); + case DRM_FORMAT_UYVY: + return (1 << 4); + case DRM_FORMAT_RGB565: + return (4 << 4); + case DRM_FORMAT_RGB888: + return (8 << 4); + case DRM_FORMAT_ARGB8888: + return (16 << 4); + default: + return (16 << 4); + } +} + +static unsigned int rdma_fmt_convert(unsigned int fmt) { /* The return value in switch "MEM_MODE_INPUT_FORMAT_XXX" * is defined in mediatek HW data sheet. @@ -250,6 +522,37 @@ static unsigned int rdma_fmt_convert(struct mtk_disp_rdma *rdma, } } +static void mtk_rdma_reset_mt6589(struct mtk_disp_rdma *rdma) +{ + void __iomem *regs = rdma->regs; + unsigned int delay_cnt = 0; + + writel(0x10, regs + DISP_REG_RDMA_GLOBAL_CON); + while ((readl(regs + DISP_REG_RDMA_GLOBAL_CON) & 0x700) == 0x100) { + if (++delay_cnt > 10000) { + pr_warn("RDMA reset stage 1 timeout\n"); + break; + } + } + + writel(0x00, regs + DISP_REG_RDMA_GLOBAL_CON); + delay_cnt = 0; + while ((readl(regs + DISP_REG_RDMA_GLOBAL_CON) & 0x700) != 0x100) { + if (++delay_cnt > 10000) { + pr_warn("RDMA reset stage 2 timeout\n"); + break; + } + } + + writel(0x00, regs + DISP_REG_RDMA_SIZE_CON_0); + writel(0x00, regs + DISP_REG_RDMA_SIZE_CON_1); + writel(0x00, regs + DISP_RDMA_MEM_CON); + writel(0x00, regs + rdma->data->mem_start_addr_reg); + writel(0x00, regs + DISP_RDMA_MEM_SRC_PITCH); + writel(0x20, regs + DISP_REG_RDMA_MEM_GMC_SETTING_1); + writel(0x80f00008, regs + DISP_REG_RDMA_FIFO_CON); +} + unsigned int mtk_rdma_layer_nr(struct device *dev) { return 1; @@ -262,33 +565,70 @@ void mtk_rdma_layer_config(struct device *dev, unsigned int idx, struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); struct mtk_plane_pending_state *pending = &state->pending; unsigned int addr = pending->addr; + /* + * MEM_MODE_SRC_PITCH counts *bytes* per line, not pixels. The field + * is 16 bits wide, so an aligned 1080p frame at four bytes per pixel + * fits (8640), but a wider one would not - which is why this stays + * the byte pitch that mainline hands us. pending->pitch is already + * drm's fb->pitches[0] in bytes; do not divide it by cpp here. + * + * Evidence that the register takes bytes, despite ddp_path.c's + * "pitch, pixel number" comment being used for the *OVL* config + * struct: every stock call site that feeds RDMAConfig() scales by + * the pixel size, while every site that feeds the OVL layer config + * does not. hdmitx.c:539 passes src_pitch * 4 for ARGB and + * disp_drv_dsi.c:404 passes width * 2 for RGB565; disp_drv.c:1807 + * passes width * 3 for the RGB888 OVL dump, where the OVL path takes + * the value unscaled. RDMAConfig() itself prints width*bpp in the + * same argument list it is handed the unscaled "pitch" in. + * + * Dividing by cpp here (as this driver briefly did) made RDMA fetch + * every line a third to a quarter of the way too early and sheared + * the frame diagonally. + */ unsigned int pitch = pending->pitch & 0xffff; unsigned int fmt = pending->format; unsigned int con; - con = rdma_fmt_convert(rdma, fmt); + con = rdma->data->fmt_convert(fmt); mtk_ddp_write_relaxed(cmdq_pkt, con, &rdma->cmdq_reg, rdma->regs, DISP_RDMA_MEM_CON); - if (fmt == DRM_FORMAT_UYVY || fmt == DRM_FORMAT_YUYV) { - mtk_ddp_write_mask(cmdq_pkt, RDMA_MATRIX_ENABLE, &rdma->cmdq_reg, rdma->regs, - DISP_REG_RDMA_SIZE_CON_0, - RDMA_MATRIX_ENABLE); - mtk_ddp_write_mask(cmdq_pkt, RDMA_MATRIX_INT_MTX_BT601_to_RGB, - &rdma->cmdq_reg, rdma->regs, DISP_REG_RDMA_SIZE_CON_0, - RDMA_MATRIX_INT_MTX_SEL); - } else { - mtk_ddp_write_mask(cmdq_pkt, 0, &rdma->cmdq_reg, rdma->regs, - DISP_REG_RDMA_SIZE_CON_0, - RDMA_MATRIX_ENABLE); - } + /* + * No YUV matrix is programmed here. MT6589's DISP_RDMA_SIZE_CON_0 has + * only OUTPUT_FORMAT in bit 29 and OUTPUT_FRAME_WIDTH in bits [11:0] + * - there is no matrix enable or matrix select field, so those bits + * are not where they are on MT8192. Writing them set bit 17 of the + * frame width and smeared BT601 coefficients across bits [23:20], + * corrupting the width for every UYVY or YUYV frame. The stock + * driver converts YUV in the OVL instead. + */ mtk_ddp_write_relaxed(cmdq_pkt, addr, &rdma->cmdq_reg, rdma->regs, - DISP_RDMA_MEM_START_ADDR); + rdma->data->mem_start_addr_reg); mtk_ddp_write_relaxed(cmdq_pkt, pitch, &rdma->cmdq_reg, rdma->regs, DISP_RDMA_MEM_SRC_PITCH); - mtk_ddp_write(cmdq_pkt, RDMA_MEM_GMC, &rdma->cmdq_reg, rdma->regs, + mtk_ddp_write(cmdq_pkt, rdma->data->mem_gmc_val, &rdma->cmdq_reg, rdma->regs, DISP_RDMA_MEM_GMC_SETTING_0); - mtk_ddp_write_mask(cmdq_pkt, RDMA_MODE_MEMORY, &rdma->cmdq_reg, rdma->regs, - DISP_REG_RDMA_GLOBAL_CON, RDMA_MODE_MEMORY); + /* + * MODE_SEL in GLOBAL_CON selects how RDMA0 gets its pixels: + * + * 0: Direct link mode + * 1: Memory mode + * + * This path is OVL -> RDMA0 -> DSI, and the stock driver programs + * RDMA_MODE_DIRECT_LINK (ddp_path.c: RDMAConfig(0, + * RDMA_MODE_DIRECT_LINK, ...)). Memory mode instead points RDMA0 at + * its own MEM_MODE ring, and on this configuration no plane is ever + * attached to RDMA0 - mtk_crtc_num_comp_planes() only creates planes + * for components 0 and 1, and MT6589's COLOR claims none, so every + * plane lands on OVL. RDMA0 would then be enabled with an + * address nobody programmed and fetch from garbage: + * + * OVL: underflow intsta=0x35 sta=0x1d (run=1 rdma0_idle=0) + * + * The ring registers above stay programmed: RDMAConfig() writes + * START_ADDR, SRC_PITCH and the GMC settings either way. MODE_SEL is + * handled in mtk_rdma_config(), which is the hook that actually runs. + */ } @@ -324,10 +664,20 @@ static int mtk_disp_rdma_probe(struct platform_device *pdev) if (irq < 0) return irq; - priv->clk = devm_clk_get(dev, NULL); - if (IS_ERR(priv->clk)) - return dev_err_probe(dev, PTR_ERR(priv->clk), - "failed to get rdma clk\n"); + /* + * The node declares engine, SMI and output clocks. Taking only index + * 0 left the other two gated, so the register writes issued through + * the shadow registers may not have reached the hardware. + */ + /* + * Take every clock the node declares: engine, SMI and output. + * devm_clk_get() can only return one of them, so with no clock-names + * property the other two stayed gated. + */ + ret = devm_clk_bulk_get_all(dev, &priv->clks); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get rdma clks\n"); + priv->num_clks = ret; priv->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->regs)) @@ -345,9 +695,24 @@ static int mtk_disp_rdma_probe(struct platform_device *pdev) if (ret && (ret != -EINVAL)) return dev_err_probe(dev, ret, "Failed to get rdma fifo size\n"); - /* Disable and clear pending interrupts */ + /* + * Disable and clear pending interrupts. + * + * INT_ENABLE is a plain RW register of interrupt-enable bits, so 0 + * masks everything. INT_STATUS is write-1-to-clear (see + * mtk_disp_rdma_irq_handler()), so 0 does *not* clear it: writing 0 + * leaves every latched status bit standing. Since the enable is + * masked here the status cannot raise the line, but it survives + * into mtk_rdma_start(), which unmasks all six bits - and the first + * status bit the block finds already set is delivered as a + * "fresh" interrupt the moment the engine is enabled. Acknowledge + * by writing all ones, which is what the stock RDMAStop() relies on + * being harmless (ddp_rdma.c:54 writes 0 and does not clear either, + * which is why the stock driver only ever gets away with it because + * RDMAReset() follows immediately). + */ writel(0x0, priv->regs + DISP_REG_RDMA_INT_ENABLE); - writel(0x0, priv->regs + DISP_REG_RDMA_INT_STATUS); + writel(~0u, priv->regs + DISP_REG_RDMA_INT_STATUS); ret = devm_request_irq(dev, irq, mtk_disp_rdma_irq_handler, IRQF_TRIGGER_NONE, dev_name(dev), priv); @@ -360,6 +725,66 @@ static int mtk_disp_rdma_probe(struct platform_device *pdev) pm_runtime_enable(dev); + /* + * resume_and_get(), not get_sync(): get_sync() leaves the usage + * counter incremented when the resume fails, and the pm_runtime_disable() + * below does not undo that reference, so returning an error would + * hand the device back with a reference nobody will ever drop. + * The reference taken here is kept for the lifetime of the bound + * device - pm_runtime_put_sync() below is balanced against the reset + * setup below it, not against this one. + */ + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + pm_runtime_disable(dev); + return dev_err_probe(dev, ret, "Failed to enable power\n"); + } + + if (priv->data->reset) { + /* Look the reset up by index; see the note in mtk_disp_ovl.c + * about the missing "reset-names" on the display nodes. + */ + priv->rstc = devm_reset_control_get(dev, NULL); + if (IS_ERR(priv->rstc)) { + ret = PTR_ERR(priv->rstc); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + return dev_err_probe(dev, ret, + "Failed to get reset control\n"); + } + + ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks); + if (ret) { + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + return dev_err_probe(dev, ret, + "Failed to enable RDMA clocks\n"); + } + + /* + * Reset the engine before programming it. The bootloader + * can leave the RDMA running with stale state, which shows up + * as frames that never complete and OVL underflow. + */ + ret = reset_control_reset(priv->rstc); + if (ret) { + clk_bulk_disable_unprepare(priv->num_clks, priv->clks); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + return dev_err_probe(dev, ret, + "Failed to reset RDMA\n"); + } + + /* Clear anything the reset left latched. */ + writel(0x0, priv->regs + DISP_REG_RDMA_INT_ENABLE); + writel(~0u, priv->regs + DISP_REG_RDMA_INT_STATUS); + + priv->data->reset(priv); + clk_bulk_disable_unprepare(priv->num_clks, priv->clks); + } + + pm_runtime_put_sync(dev); + ret = component_add(dev, &mtk_disp_rdma_component_ops); if (ret) { pm_runtime_disable(dev); @@ -380,29 +805,91 @@ static const struct mtk_disp_rdma_data mt2701_rdma_driver_data = { .fifo_size = SZ_4K, .formats = mt8173_formats, .num_formats = ARRAY_SIZE(mt8173_formats), + .size_con0 = 0xfff, + .size_con1 = 0xfffff, + .fmt_convert = rdma_fmt_convert, + .mem_start_addr_reg = DISP_RDMA_MEM_START_ADDR, + .mem_gmc_val = RDMA_MEM_GMC, + .reset = NULL, +}; + +static const struct mtk_disp_rdma_data mt6589_rdma_driver_data = { + .fifo_size = 3840, + .formats = mt6589_formats, + .num_formats = ARRAY_SIZE(mt6589_formats), + .size_con0 = 0xfff, + .size_con1 = 0xfffff, + .fmt_convert = rdma_fmt_convert_mt6589, + .mem_start_addr_reg = 0x0028, + /* ddp_rdma.c:95 / RDMAReset(). */ + .fifo_con = 0x80f00008, + /* + * Zero: MEM_GMC_SETTING_0 stays at its reset value. The stock + * driver never writes this register (data sheet reset 0x0a0a0a0a, + * upstream generic 0x40402020; 0x20402040 matched neither). On + * this SoC layer_config never runs anyway (direct_link_no_plane) - + * this only makes the intent explicit. + */ + .mem_gmc_val = 0, + .reset = mtk_rdma_reset_mt6589, + /* + * This block's INT_STATUS has exactly the six bits named above, and + * the stock RDMAStart() enables all six, so the whole set is safe to + * write here. Stated per SoC rather than assumed, because on the + * other SoCs 0x3F would be an unverified guess at their bit maps. + */ + .int_enable_mask = RDMA_INT_ALL, + /* + * MT6589: mtk_crtc_hw_init() disables every plane and routes them + * all to OVL - MT6589's COLOR claims no layers and OVL claims all + * four - so mtk_ddp_comp_for_plane() never returns this RDMA and + * mtk_rdma_layer_config() never runs for it. The mode, format and + * ring registers therefore have to be written from the config hook. + */ + .direct_link_no_plane = true, }; static const struct mtk_disp_rdma_data mt8173_rdma_driver_data = { .fifo_size = SZ_8K, .formats = mt8173_formats, .num_formats = ARRAY_SIZE(mt8173_formats), + .size_con0 = 0xfff, + .size_con1 = 0xfffff, + .fmt_convert = rdma_fmt_convert, + .mem_start_addr_reg = DISP_RDMA_MEM_START_ADDR, + .mem_gmc_val = RDMA_MEM_GMC, + .reset = NULL, }; static const struct mtk_disp_rdma_data mt8183_rdma_driver_data = { .fifo_size = 5 * SZ_1K, .formats = mt8173_formats, .num_formats = ARRAY_SIZE(mt8173_formats), + .size_con0 = 0xfff, + .size_con1 = 0xfffff, + .fmt_convert = rdma_fmt_convert, + .mem_start_addr_reg = DISP_RDMA_MEM_START_ADDR, + .mem_gmc_val = RDMA_MEM_GMC, + .reset = NULL, }; static const struct mtk_disp_rdma_data mt8195_rdma_driver_data = { .fifo_size = 1920, .formats = mt8173_formats, .num_formats = ARRAY_SIZE(mt8173_formats), + .size_con0 = 0xfff, + .size_con1 = 0xfffff, + .fmt_convert = rdma_fmt_convert, + .mem_start_addr_reg = DISP_RDMA_MEM_START_ADDR, + .mem_gmc_val = RDMA_MEM_GMC, + .reset = NULL, }; static const struct of_device_id mtk_disp_rdma_driver_dt_match[] = { { .compatible = "mediatek,mt2701-disp-rdma", .data = &mt2701_rdma_driver_data}, + { .compatible = "mediatek,mt6589-disp-rdma", + .data = &mt6589_rdma_driver_data }, { .compatible = "mediatek,mt8173-disp-rdma", .data = &mt8173_rdma_driver_data}, { .compatible = "mediatek,mt8183-disp-rdma", diff --git a/drivers/gpu/drm/mediatek/mtk_disp_tdshp.c b/drivers/gpu/drm/mediatek/mtk_disp_tdshp.c new file mode 100644 index 00000000000000..4578e7215a4de6 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_tdshp.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MediaTek Two-Dimension Sharpness Processor (TDSHP) + * + * Copyright (c) 2025 MediaTek Inc. + * Copyright (c) 2026 Collabora Ltd. + * AngeloGioacchino Del Regno + * + * Ported to the MT6589 display pipeline. The MT6589 TDSHP sits + * between COLOR and BLS/DBI and is kept in relay (bypass) mode: the + * downstream kernel programs the sharpness engine but ships with + * sharpGain = 0, effectively passing the frame through unchanged. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtk_disp_drv.h" +#include "mtk_drm_drv.h" + +#define DISP_REG_TDSHP_EN 0x0000 +#define DISP_TDSHP_TDS_EN BIT(31) +/* + * 0x0350: PBC/misc bypass register. The downstream kernel clears this + * to zero ("bypass off") during init; it is NOT DTDS_CONFIG. + */ +#define DISP_REG_TDSHP_PBC_BYPASS 0x0350 +/* + * 0x0f10: DTDS_CONFIG -- enables the RGB->YUV (R2Y) and YUV->RGB (Y2R) + * colour-matrix wrappers inside the TDSHP block. bit[2]=R2Y_EN, + * bit[1]=Y2R_EN; the downstream kernel writes 0x6 = both enabled. + */ +#define DISP_REG_TDSHP_DTDS_CONFIG 0x0f10 +#define DISP_TDSHP_R2Y_EN BIT(2) +#define DISP_TDSHP_Y2R_EN BIT(1) +#define DISP_REG_TDSHP_INPUT_SIZE 0x0f40 +#define DISP_REG_TDSHP_OUTPUT_SIZE 0x0f44 +#define DISP_REG_TDSHP_START 0x0f00 + +struct mtk_disp_tdshp { + void __iomem *regs; + struct clk *clk; + struct cmdq_client_reg cmdq_reg; +}; + +void mtk_tdshp_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_tdshp *tdshp = dev_get_drvdata(dev); + + mtk_ddp_write(cmdq_pkt, w, &tdshp->cmdq_reg, + tdshp->regs, DISP_REG_TDSHP_INPUT_SIZE); + mtk_ddp_write(cmdq_pkt, h, &tdshp->cmdq_reg, + tdshp->regs, DISP_REG_TDSHP_OUTPUT_SIZE); + + /* + * Clear the PBC/misc bypass register (0x350) as the downstream does. + * Then enable the R2Y and Y2R colour-matrix wrappers via DTDS_CONFIG. + */ + mtk_ddp_write(cmdq_pkt, 0, + &tdshp->cmdq_reg, tdshp->regs, DISP_REG_TDSHP_PBC_BYPASS); + mtk_ddp_write(cmdq_pkt, DISP_TDSHP_R2Y_EN | DISP_TDSHP_Y2R_EN, + &tdshp->cmdq_reg, tdshp->regs, DISP_REG_TDSHP_DTDS_CONFIG); +} + +static void mtk_tdshp_matrix_init(struct mtk_disp_tdshp *tdshp) +{ + static const u32 in_matrix[] = { + 0x132, 0x259, 0x075, /* c00..c02 */ + 0x1f53, 0x1ead, 0x200, /* c10..c12 */ + 0x200, 0x1e53, 0x1fad, /* c20..c22 */ + 0, 0, 0, /* input offsets */ + 0, 0x80, 0x80, /* output offsets */ + }; + static const u32 out_matrix[] = { + 0x400, 0x1fff, 0x59c, + 0x400, 0x1e9f, 0x1d25, + 0x400, 0x716, 0x001, + 0, 0x180, 0x180, /* input offsets */ + 0, 0, 0, /* output offsets */ + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(in_matrix); i++) + writel(in_matrix[i], tdshp->regs + 0xf50 + i * 4); + + for (i = 0; i < ARRAY_SIZE(out_matrix); i++) + writel(out_matrix[i], tdshp->regs + 0xf90 + i * 4); + + writel(0x40, tdshp->regs + 0xf24); /* TDS_HSYNC_WIDTH */ + writel(0x40, tdshp->regs + 0xf3c); /* ACTIVE_WIDTH_IN_VBLANK */ +} + +int mtk_tdshp_clk_enable(struct device *dev) +{ + struct mtk_disp_tdshp *tdshp = dev_get_drvdata(dev); + + return clk_prepare_enable(tdshp->clk); +} + +void mtk_tdshp_clk_disable(struct device *dev) +{ + struct mtk_disp_tdshp *tdshp = dev_get_drvdata(dev); + + clk_disable_unprepare(tdshp->clk); +} + +void mtk_tdshp_start(struct device *dev) +{ + struct mtk_disp_tdshp *tdshp = dev_get_drvdata(dev); + + writel(1, tdshp->regs + DISP_REG_TDSHP_START); + mtk_tdshp_matrix_init(tdshp); +} + +void mtk_tdshp_stop(struct device *dev) +{ + struct mtk_disp_tdshp *tdshp = dev_get_drvdata(dev); + + writel(0, tdshp->regs + DISP_REG_TDSHP_EN); +} + +static int mtk_tdshp_bind(struct device *dev, struct device *master, void *data) +{ + return 0; +} + +static void mtk_tdshp_unbind(struct device *dev, struct device *master, void *data) +{ +} + +static const struct component_ops mtk_disp_tdshp_component_ops = { + .bind = mtk_tdshp_bind, + .unbind = mtk_tdshp_unbind, +}; + +static int mtk_disp_tdshp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_disp_tdshp *tdshp; + int ret; + + tdshp = devm_kzalloc(dev, sizeof(*tdshp), GFP_KERNEL); + if (!tdshp) + return -ENOMEM; + + tdshp->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(tdshp->regs)) + return dev_err_probe(dev, PTR_ERR(tdshp->regs), + "Cannot get reg resource\n"); + + tdshp->clk = devm_clk_get(dev, NULL); + if (IS_ERR(tdshp->clk)) + return dev_err_probe(dev, PTR_ERR(tdshp->clk), + "Cannot get clocks\n"); + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + ret = cmdq_dev_get_client_reg(dev, &tdshp->cmdq_reg, 0); + if (ret) + dev_dbg(dev, "No mediatek,gce-client-reg\n"); +#endif + + platform_set_drvdata(pdev, tdshp); + + ret = component_add(dev, &mtk_disp_tdshp_component_ops); + if (ret) + return dev_err_probe(dev, ret, "Failed to add component\n"); + + return 0; +} + +static void mtk_disp_tdshp_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_disp_tdshp_component_ops); +} + +static const struct of_device_id mtk_disp_tdshp_driver_dt_match[] = { + { .compatible = "mediatek,mt6589-disp-tdshp", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mtk_disp_tdshp_driver_dt_match); + +struct platform_driver mtk_disp_tdshp_driver = { + .probe = mtk_disp_tdshp_probe, + .remove = mtk_disp_tdshp_remove, + .driver = { + .name = "mediatek-disp-tdshp", + .owner = THIS_MODULE, + .of_match_table = mtk_disp_tdshp_driver_dt_match, + }, +}; + +MODULE_AUTHOR("AngeloGioacchino Del Regno "); +MODULE_DESCRIPTION("MediaTek Display Controller 2D Sharpness Processor Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/mediatek/mtk_disp_wdma.c b/drivers/gpu/drm/mediatek/mtk_disp_wdma.c new file mode 100644 index 00000000000000..833e1ff8c4b2c4 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_wdma.c @@ -0,0 +1,1032 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026 MediaTek Inc. + * + * DISP_WDMA - the display subsystem's write-DMA engine. + * + * WDMA is the write-only output leg of the display pipeline. Where RDMA + * reads pixels from memory and pushes them into the pipeline, WDMA takes a + * pixel stream from an upstream block and writes it out to memory. It is + * never a source for a display output, and it is never a CRTC path component + * that carries a plane. + * + * MT6589 has two instances, WDMA0 at 0x14004000 and WDMA1 at 0x14005000 + * (data sheet ch. 41, p. 1540: "Module name: DISP_WDMA base address: + * (+14004000h)"). The instances are identical; the vendor driver selects + * one by adding DISP_INDEX_OFFSET (0x1000, ddp_wdma.c:32) to every register + * address, which is the same 4 KiB stride this driver gets for free from + * having a separate platform device per instance. + * + * Upstream mainline has no DISP_WDMA DRM driver. What it has is the + * dt-bindings (Documentation/devicetree/bindings/display/mediatek/ + * mediatek,wdma.yaml), DT nodes for MT8173/MT6795/MT8167/MT7623, and a + * matching entry in mtk_drm_drv.c:853 - all binding to no driver at all. + * This file is written from the vendor source + * (aquaris-5/mediatek/platform/mt6589/kernel/drivers/dispsys/ddp_wdma.c) + * with every register offset and bit position checked against data sheet + * chapter 41. It deliberately does NOT take its structure from the MDP3 + * media driver (drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c), which + * is a different block family for a different SoC generation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtk_ddp_comp.h" +#include "mtk_disp_drv.h" +#include "mtk_drm_drv.h" + +/* + * Register offsets. Every one of these was read out of data sheet ch. 41's + * register definition table (pp. 1540-1546) and cross-checked against + * ddp_reg.h:442-474; the two agree exactly. The table also confirms the + * block is 0xAC bytes long at most, so the 4 KiB reg resource below is not + * lying about anything. + */ +#define WDMA_INTEN 0x0000 +#define WDMA_INTSTA 0x0004 +#define WDMA_EN 0x0008 +#define WDMA_RST 0x000c +#define WDMA_SMI_CON 0x0010 +#define WDMA_CFG 0x0014 +#define WDMA_SRC_SIZE 0x0018 +#define WDMA_CLIP_SIZE 0x001c +#define WDMA_CLIP_COORD 0x0020 +#define WDMA_DST_ADDR 0x0024 +#define WDMA_DST_W_IN_BYTE 0x0028 +#define WDMA_ALPHA 0x002c +#define WDMA_STA 0x0034 +#define WDMA_BUF_CON1 0x0038 +#define WDMA_BUF_CON2 0x003c +#define WDMA_DST_U_ADDR 0x0070 +#define WDMA_DST_V_ADDR 0x0074 +#define WDMA_DST_UV_PITCH 0x0078 + +/* + * WDMA_INTEN (0x00). Both bits, and they are the complete set: data sheet + * p. 1540 gives WDMA_INTEN exactly two named bits, FUE (bit 1, frame + * underrun) and FCE (bit 0, frame complete), both reset 0. Matches + * WDMA_INTEN_FLD_Frame_Underrun/Frame_Complete, REG_FLD(1, 1) and REG_FLD(1, + * 0), in ddp_wdma.h. + */ +#define WDMA_INTEN_FRAME_UNDERRUN BIT(1) +#define WDMA_INTEN_FRAME_COMPLETE BIT(0) +#define WDMA_INT_ALL (WDMA_INTEN_FRAME_UNDERRUN | \ + WDMA_INTEN_FRAME_COMPLETE) + +/* + * WDMA_EN (0x08). Bit 0 only: "EN, Enable, Enables WDMA or not / + * 0: Disable WDMA / 1: Enable WDMA", reset 0 (data sheet p. 1541). The + * vendor WDMAStart() writes 0x01 and WDMAStop() writes 0x00 to the whole + * register (ddp_wdma.c:42-44, :49), which is the same thing because bits + * 31..1 are unnamed and reset to 0. + */ +#define WDMA_EN_ENABLE BIT(0) + +/* + * WDMA_RST (0x0c). Bit 0, Soft_Reset. The data sheet is explicit about the + * protocol (p. 1542): "Write this bit to trigger soft reset. WDMA will + * reset itself when bus transaction is done and then auto clear this bit to + * 0. SW can poll this bit to see if reset is done or not." So 1 = trigger, + * and reading it back as 0 is how software learns the reset finished - the + * bit is self-clearing, so the poll below waits for it to drop rather than + * for a value software has to push. "Note: HW will not assert IRQ during SW + * reset", which is why the interrupt is masked around this. + */ +#define WDMA_RST_SOFT_RESET BIT(0) + +/* + * WDMA_SMI_CON (0x10). Fields, from data sheet p. 1543: SE[4], SL[7:5], + * SC[15:8], TH[3:0]. Matches WDMA_SMI_CON_FLD_* in ddp_wdma.h. Reset value + * is 0x03, i.e. SE=0 and TH=3, so the burst threshold already defaults to 4. + * + * Nothing here programs SMI_CON. The vendor driver's WDMASlowMode() + * (ddp_wdma.c) is the only writer and no caller in this tree reaches it; the + * slow-down feature exists to relieve memory bandwidth pressure on + * particular board layouts, and guessing at it would be inventing behaviour. + * The reset value is left in place deliberately. + */ + +/* + * WDMA_CFG (0x14). Field positions confirmed against data sheet p. 1543 + * (IF[3:0] In_Format, OF[7:4] Out_Format, BYTES[8] byte swap, RGBS[9] rgb + * swap, UVS[10] uv swap, V_AVG[12], DNSP_SEL[15]) and against the + * WDMA_CFG_FLD_* macros in ddp_wdma.h. ERR_DIF_EN is bit 25 and DITHER_EN + * bit 24 per ddp_wdma.h; the data sheet page for WDMA_CFG does not name + * those two bits in its text, so they are defined here only as part of the + * configuration mask and are never set by this driver. + */ +#define WDMA_CFG_IN_FORMAT GENMASK(3, 0) +#define WDMA_CFG_OUT_FORMAT GENMASK(7, 4) +#define WDMA_CFG_BYTE_SWAP BIT(8) +#define WDMA_CFG_RGB_SWAP BIT(9) +#define WDMA_CFG_UV_SWAP BIT(10) +#define WDMA_CFG_DNSP_SEL BIT(15) +#define WDMA_CFG_KNOWN_MASK (WDMA_CFG_IN_FORMAT | \ + WDMA_CFG_OUT_FORMAT | \ + WDMA_CFG_BYTE_SWAP | \ + WDMA_CFG_RGB_SWAP | \ + WDMA_CFG_UV_SWAP | \ + WDMA_CFG_DNSP_SEL) + +/* + * Shift count of WDMA_CFG_OUT_FORMAT, so a format value can be placed in it. + * + * __ffs() is 1-based: it returns the one-based index of the lowest set bit, so + * for bits [7:4] it yields 5 where the shift count is 4. Using it directly + * shifted the format one bit too far, which encoded Out_Format=4 (UYVY) for + * the ARGB value 2 - while the pitch and bytes-per-line programmed alongside it + * described a 4-bytes-per-pixel copy. Subtract one. + */ +#define WDMA_CFG_OUT_FORMAT_SHIFT (__ffs(WDMA_CFG_OUT_FORMAT) - 1) + +/* + * The one output format this driver programs, WDMA_OUTPUT_FORMAT_ARGB from + * the vendor enumeration in ddp_wdma.h. That header's full list is + * WDMA_INPUT_FORMAT_* / WDMA_OUTPUT_FORMAT_*, and these values are the + * encoding the data sheet's Out_Format field takes (bits [7:4] of WDMA_CFG), + * not a guess. The input format is not a parameter at all: see + * mtk_wdma_config() for why it is fixed. + */ +#define WDMA_OUTPUT_FORMAT_ARGB 0x2 + +/* + * WDMA_ALPHA (0x2c). ASEL[31] selects between the OVL's per-pixel alpha and + * the constant in A[7:0] (data sheet p. 1546, and WDMA_ALPHA_FLD_A_Sel / + * WDMA_ALPHA_FLD_A_Value in ddp_wdma.h). Register reset is 0x000000ff: A_Sel + * is 0, so the constant is ignored by default and per-pixel alpha passes. + */ +#define WDMA_ALPHA_SEL BIT(31) +#define WDMA_ALPHA_VALUE GENMASK(7, 0) + +/* + * WDMA_DST_ADDR (0x24) and WDMA_DST_W_IN_BYTE (0x28). + * + * DST_ADDR is 32 bits and the data sheet states outright "There is no + * alignment restriction" (p. 1546). The device tree gives this block an + * iommus property and the reset controller maps the hardware M4U, so the + * address reaching this register is an M4U-translated IOVA, which on this + * SoC is a 32-bit quantity. + * + * DWB, the width in bytes, is bits [13:0] only (data sheet p. 1546: the + * bit-number row is 13..0 and the mnemonic is DWB). That is worth stating + * plainly because ddp_wdma.h declares WDMA_DST_W_IN_BYTE_FLD_Dst_W_in_Byte + * as REG_FLD(32, 0) - a 32-bit field. Following the vendor header would let + * a pitch up to 16 KiB be written, of which only the low 14 bits survive, so + * a 4096-pixel ARGB line (16384 bytes) would silently become width 0 and + * every line would be written back to back. This driver masks to the field + * the hardware actually has, and its width check below rejects anything that + * would not fit instead of letting it be truncated. + */ +#define WDMA_DST_W_IN_BYTE_MASK GENMASK(13, 0) + +/* + * WDMA_SRC_SIZE / CLIP_SIZE (0x18 / 0x1c) and WDMA_CLIP_COORD (0x20) all + * split into a 16-bit WTH low half and a 16-bit HGT high half, and the data + * sheet gives the same range for all of them: 0 ~ 2047 pixels + * (pp. 1544-1545). 2047 is therefore a hardware limit here, not a policy + * choice, and WDMA_MAX_DIM below encodes it as the mask of the usable range. + */ +#define WDMA_DIM_MAX 2047 +#define WDMA_SIZE_WIDTH GENMASK(15, 0) +#define WDMA_SIZE_HEIGHT GENMASK(31, 16) + +/* + * WDMA_DST_UV_PITCH (0x78) is bits [13:0] per WDMA_BUF_ADDR_FLD_UV_Pitch, + * REG_FLD(14, 0), in ddp_wdma.h - the same 14-bit pitch field as DWB, and for + * the same reason it is deliberately not programmed here. It belongs to the + * planar YUV output mode, which this driver does not offer; see + * mtk_wdma_config_output() for why. The register offset itself is not + * defined above because nothing reads or writes it. + */ + +/* + * Bounded poll budget for the soft-reset's self-clearing bit. The vendor + * driver spins 10000 times with no delay at all (ddp_wdma.c:60-70) and gives + * up, then carries on and writes a reset configuration over the top of + * whatever the block was doing. That is the failure mode this driver exists + * to avoid: the registers are programmed blind while the engine may still be + * draining a previous frame into a destination buffer that the caller has + * already been told is finished with. Here the reset is given a real time + * budget and a failure is an error, not a shrug. + */ +#define WDMA_RESET_TIMEOUT_US 100000 + +/* + * How many times mtk_wdma_soft_reset() will retrigger the reset and re-poll + * WDMA_RST looking for the block to come back. See mtk_wdma_recover(). + */ +#define WDMA_RESET_TRIES 3 + +/* + * WDMA_STA (0x34) is a 32-bit RU (read-only) "WDMA status" word with no + * named fields in the data sheet - the register table lists a single STA[31:0] + * field described only as "WDMA status" (p. 1546). Its reset value is + * 0x00000001. Because there is no documented way to decode it, nothing in + * this driver reads it or interprets it, and it is deliberately absent from the + * reset sequence too: writing a value the data sheet does not define the + * behaviour of would be worse than leaving it. + * + * That absence is also why this driver has no "is the engine busy" poll to + * hang a timeout on. The two completion facts available are WDMA_INTSTA's + * Frame_Complete bit and the fact that a fresh WDMA_EN=1 with a new + * configuration is what the vendor driver actually does. See + * mtk_wdma_start() for what is done about that. + */ + +/* + * The vendor header caps WDMA at 1920x1080 (WDMA_MAX_WIDTH / WDMA_MAX_HEIGHT + * in ddp_wdma.h). The hardware fields are 16 bits wide with a stated range + * of 0 ~ 2047 for every one of width, height, clip width, clip height and + * both clip coordinates. This driver checks against the hardware limit and + * not against the vendor constant: the larger of the two (2047) is what the + * registers can represent, and inventing a tighter limit would be a policy + * claim with no source. Where they differ, the 16-bit field wins and the + * difference is reported rather than silently applied. + */ +#define WDMA_MAX_WIDTH WDMA_DIM_MAX +#define WDMA_MAX_HEIGHT WDMA_DIM_MAX + +struct mtk_disp_wdma; + +/** + * struct mtk_wdma_driver_data - per-SoC facts about a DISP_WDMA block + * @instance: 0 for WDMA0, 1 for WDMA1 + * @reset: do the data sheet's soft reset, or NULL for an SoC where this + * driver has not established that the block needs it + * @reset_timeout_us: poll budget for one reset attempt + * + * An empty struct body on purpose: there is no per-SoC register offset + * difference to carry, because this driver has only ever been written for + * MT6589. Upstream has no DRM WDMA driver to carry offsets in from, and the + * MDP3 media driver's block (mdp_reg_wdma.h) is a different part - its + * WDMA_BUF_CON2 is written with 0x10101050 by config_wdma_frame(), a value + * with no counterpart anywhere in data sheet ch. 41. Adding a field here for + * a hypothetical other SoC would be inventing a difference; adding the SoCs + * that upstream's YAML names but no driver supports would be claiming support + * this tree has not verified. + */ +struct mtk_wdma_driver_data { + unsigned int instance; + void (*reset)(struct mtk_disp_wdma *wdma); + unsigned int reset_timeout_us; +}; + +/* + * struct mtk_disp_wdma - DISP_WDMA driver structure + * @data: local driver data + */ +struct mtk_disp_wdma { + struct device *dev; + struct clk_bulk_data *clks; + struct reset_control *rstc; + int num_clks; + void __iomem *regs; + struct cmdq_client_reg cmdq_reg; + const struct mtk_wdma_driver_data *data; + void (*vblank_cb)(void *data); + void *vblank_cb_data; + /* + * Set once the soft reset has failed WDMA_RESET_TRIES times and the + * block has been left unprogrammed. See mtk_wdma_recover(): the + * hardware may still be writing to whatever address is in + * WDMA_DST_ADDR, so no further configuration or start is programmed + * and mtk_wdma_start() refuses. + */ + bool wedged; +}; + +static irqreturn_t mtk_disp_wdma_irq_handler(int irq, void *dev_id) +{ + struct mtk_disp_wdma *priv = dev_id; + u32 status; + + /* + * WDMA_INTSTA (0x04) is write-1-to-clear. The data sheet gives both of + * its bits the same treatment it gives RDMA's (p. 1541): "SW writes + * this bit to clear IRQ", for FU at bit 1 and FC at bit 0. The vendor + * driver agrees: WDMAStart() sets INTEN to 0x03 (ddp_wdma.c:41) and + * WDMAStop() clears it, and WDMAWait() polls bit 0 of the same + * register (ddp_wdma.c:307). + * + * So the acknowledge is the value that was read, not its complement + * and not a blanket 0xffffffff: on a write-1-to-clear register writing + * 0 to a bit has no effect, and writing all ones would also strike + * bits 31..2, which this block does not name. Writing the read value + * clears exactly what was latched, and leaves alone any condition that + * arrived between the read and the write - which arrives as a 0 and is + * correctly left standing for the next pass. + */ + status = readl(priv->regs + WDMA_INTSTA); + writel(status, priv->regs + WDMA_INTSTA); + + if (!WDMA_INT_ALL || !(status & WDMA_INT_ALL)) + return IRQ_NONE; + + if (!priv->vblank_cb) + return IRQ_NONE; + + priv->vblank_cb(priv->vblank_cb_data); + + return IRQ_HANDLED; +} + +static void wdma_update_bits(struct device *dev, unsigned int reg, + unsigned int mask, unsigned int val) +{ + struct mtk_disp_wdma *wdma = dev_get_drvdata(dev); + unsigned int tmp = readl(wdma->regs + reg); + + tmp = (tmp & ~mask) | (val & mask); + writel(tmp, wdma->regs + reg); +} + +void mtk_wdma_register_vblank_cb(struct device *dev, + void (*vblank_cb)(void *), + void *vblank_cb_data) +{ + struct mtk_disp_wdma *wdma = dev_get_drvdata(dev); + + wdma->vblank_cb = vblank_cb; + wdma->vblank_cb_data = vblank_cb_data; +} + +void mtk_wdma_unregister_vblank_cb(struct device *dev) +{ + struct mtk_disp_wdma *wdma = dev_get_drvdata(dev); + + wdma->vblank_cb = NULL; + wdma->vblank_cb_data = NULL; +} + +void mtk_wdma_enable_vblank(struct device *dev) +{ + wdma_update_bits(dev, WDMA_INTEN, WDMA_INTEN_FRAME_COMPLETE, + WDMA_INTEN_FRAME_COMPLETE); +} + +void mtk_wdma_disable_vblank(struct device *dev) +{ + wdma_update_bits(dev, WDMA_INTEN, WDMA_INTEN_FRAME_COMPLETE, 0); +} + +int mtk_wdma_clk_enable(struct device *dev) +{ + struct mtk_disp_wdma *wdma = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(wdma->num_clks, wdma->clks); +} + +void mtk_wdma_clk_disable(struct device *dev) +{ + struct mtk_disp_wdma *wdma = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(wdma->num_clks, wdma->clks); +} + +/** + * mtk_wdma_soft_reset - drive the data sheet's WDMA_RST protocol once + * @wdma: device + * + * The sequence is the data sheet's, and the reason it is followed exactly is + * that it is not a formality: "WDMA will reset itself when bus transaction is + * done and then auto clear this bit to 0" (p. 1542). Writing 1 arms the + * reset; the block clears the bit when it has finished draining whatever + * transaction it had outstanding. Polling the bit back to 0 is therefore the + * only way to know the engine has stopped issuing bus traffic. + * + * Returns -ETIMEDOUT if the bit has not self-cleared within the budget. It + * is deliberately not ignored the way the vendor loop's `break` ignores it + * (ddp_wdma.c:66-68): on timeout the block may still be writing to the + * destination address currently in WDMA_DST_ADDR, and the caller must not + * program a new configuration or release a buffer on that basis. + * + * The reset is de-asserted on every path, success or failure. Leaving + * WDMA_RST bit 0 asserted would hold the block in reset for every later + * operation, so a failed reset that was not cleaned up would turn a transient + * stall into a permanently dead engine. + * + * Must be called with the block's clock enabled. + */ +static int mtk_wdma_soft_reset(struct mtk_disp_wdma *wdma) +{ + void __iomem *regs = wdma->regs; + u32 status; + int ret; + + /* Write 1 to trigger; the block owns the bit from here. */ + writel(WDMA_RST_SOFT_RESET, regs + WDMA_RST); + + ret = readl_poll_timeout(regs + WDMA_RST, status, + !(status & WDMA_RST_SOFT_RESET), 20, + wdma->data->reset_timeout_us); + + /* + * Step back to 0 either way. On success the bit has already cleared + * itself and this write is a no-op; on timeout it is what stops the + * block being held in reset forever. + */ + writel(0, regs + WDMA_RST); + + return ret ? -ETIMEDOUT : 0; +} + +/** + * mtk_wdma_recover - put the block back into a usable state after a reset + * that did not complete + * @wdma: device + * + * One soft reset that hits its timeout is not proof that the engine is gone + * for good: the vendor loop's 10000 spins carry no time budget at all + * (ddp_wdma.c:60-70), so it can give up in well under a millisecond on a + * block that merely needed longer. Repeating the sequence gives the engine + * several real budgets. + * + * When the retries are exhausted the block is wedged rather than released. + * The reason is the same one that governs the G2D engine in this tree + * (mtk-g2d.c, g2d_wedge()): the hardware may still be writing to whatever + * address is in WDMA_DST_ADDR, with no register write this driver can make + * that is documented to stop it. Returning an error and letting the caller + * release the buffer would hand memory back to the system while the engine + * was still writing into it. So instead every entry point below refuses on a + * wedged block, and nothing is programmed or released. + * + * Must be called with the block's clock enabled. + */ +static void mtk_wdma_recover(struct mtk_disp_wdma *wdma) +{ + unsigned int i; + + for (i = 0; i < WDMA_RESET_TRIES; i++) { + if (!mtk_wdma_soft_reset(wdma)) + return; + + /* Rate limited: a caller looping on config would otherwise + * produce one line per attempt. The wedge is reported once, + * unratelimited, in probe(). + */ + dev_warn_ratelimited(wdma->dev, + "WDMA reset did not complete, attempt %u/%u\n", + i + 1, WDMA_RESET_TRIES); + } + + /* + * Reported from the single place the failure becomes visible, and + * deliberately not acted on further: the block stays wedged, the + * funcs hooks below refuse, and no buffer is released. Resetting + * the hardware out of this from software would mean a register write + * documented not to stop the engine. + */ + if (wdma->wedged) + return; + + wdma->wedged = true; + + dev_err(wdma->dev, + "WDMA did not reset after %u attempts, wedging the block: it will be left unprogrammed\n", + WDMA_RESET_TRIES); +} + +/** + * mtk_wdma_reset - soft reset and return to the documented default state + * @wdma: device + * + * Follows WDMAReset() (ddp_wdma.c:56-86): reset, then clear the configuration + * registers the vendor driver clears. The list is taken from that function + * exactly - CFG, SRC_SIZE, CLIP_SIZE, CLIP_COORD, DST_ADDR, DST_W_IN_BYTE, + * ALPHA - and nothing more. + * + * Registers deliberately not cleared: WDMA_STA and WDMA_BUF_CON1/2 are left + * at their reset values. WDMA_STA is a read-only status word with no + * documented decode, and BUF_CON1's reset value (0x001000ff, data sheet + * p. 1546) is a FIFO threshold configuration that the vendor driver never + * writes and never needs to clear; writing 0 there would disable the ultra + * high request entirely, which is a behaviour change with no source behind + * it. + * + * The reset goes through mtk_wdma_recover(), so a block that will not come + * back is wedged rather than having these clears written over the top of it. + * That matters most here, at probe: the vendor WDMAReset() ignores its own + * timeout and writes the configuration clears anyway (ddp_wdma.c:66-68 breaks + * out of the poll loop and carries on), which programs a block that may still + * be draining a transaction. + * + * Must be called with the block's clock enabled. + */ +static void mtk_wdma_reset(struct mtk_disp_wdma *wdma) +{ + void __iomem *regs = wdma->regs; + + mtk_wdma_recover(wdma); + + /* A wedged block is not programmed any further. */ + if (wdma->wedged) + return; + + writel(0x00, regs + WDMA_CFG); + writel(0x00, regs + WDMA_SRC_SIZE); + writel(0x00, regs + WDMA_CLIP_SIZE); + writel(0x00, regs + WDMA_CLIP_COORD); + writel(0x00, regs + WDMA_DST_ADDR); + writel(0x00, regs + WDMA_DST_W_IN_BYTE); + writel(0x00, regs + WDMA_ALPHA); +} + +/** + * mtk_wdma_validate_frame - reject a frame the hardware cannot be told to do + * @wdma: device + * @width: source width in pixels + * @height: source height in pixels + * @bpp: bytes per pixel of the output format + * @dst_addr: destination M4U-translated IOVA + * + * Every limit here comes from a register field, not from the vendor driver's + * WDMA_MAX_WIDTH/HEIGHT constants, which are tighter (1920x1080) than the + * hardware (16-bit fields, range 0 ~ 2047, data sheet pp. 1544-1545). + * + * The pitch check is the one that has to be careful with arithmetic width. + * WDMA_DST_W_IN_BYTE holds the destination line pitch in bytes, in 14 bits. + * The product is computed with the width widened to u64 *before* the + * multiply, as (u64)width * bpp: writing ((u64)(width + 1) * bpp) would add + * first in u32 and only then widen, which is the same class of bug the G2D + * driver documents against at mtk-g2d.c:505 and mtk-g2d-uapi.c:280. Here + * width is bounded by WDMA_MAX_WIDTH before the multiply, so the product + * cannot exceed 2047 * 4 = 8188 and would in fact fit in u16; the u64 is + * kept so the check does not silently depend on that bound holding. + */ +static int mtk_wdma_validate_frame(struct mtk_disp_wdma *wdma, + unsigned int width, unsigned int height, + unsigned int bpp, u32 dst_addr) +{ + u64 pitch; + + if (!width || !height) + return -EINVAL; + + if (width > WDMA_MAX_WIDTH || height > WDMA_MAX_HEIGHT) + return -EINVAL; + + /* Widen before the multiply, never after. */ + pitch = (u64)width * bpp; + + /* + * WDMA_DST_W_IN_BYTE.DWB is 14 bits. Reject rather than mask: a line + * that does not fit is a programming error, and truncating the pitch + * would make the hardware write every line at the wrong offset with + * no indication that anything went wrong. + */ + if (pitch > WDMA_DST_W_IN_BYTE_MASK) + return -EINVAL; + + /* + * WDMA_DST_ADDR is 32 bits with no alignment restriction (data sheet + * p. 1546). The destination is an IOVA from the M4U, so this is a + * check that software is not about to hand the block an address it + * cannot express, not a check that the IOVA is inside any particular + * memory region - the IOMMU owns that, and the DT node's iommus + * property is what makes it happen (MT6589_M4U_PORT_WDMA{0,1} at + * mt6589-larb-port.h:50-51). + * + * The final byte written is dst_addr + pitch * height. Widened before + * the multiply and the add for the same reason as above. + */ + if ((u64)dst_addr + pitch * height > (u64)(dma_addr_t)~0ULL) + return -EINVAL; + + return 0; +} + +/** + * mtk_wdma_config_output - program WDMA_CFG's format and swap fields + * @wdma: device + * @out_format: WDMA_OUTPUT_FORMAT_* + * + * The output format encodings are the vendor WDMA_OUTPUT_FORMAT_* values from + * ddp_wdma.h, which is the encoding the data sheet's Out_Format field takes + * (bits [7:4] of WDMA_CFG). The byte/rgb/uv swap bits exist because the + * field names a small set of formats and the rest of the colour-space + * arrangements are reached by swapping - that is what ddp_wdma.c:130-176 is + * doing, mapping BGR888 onto RGB888 with rgb_swap and so on. + * + * Only RGB565 and ARGB are programmed here, and for both the vendor driver + * takes the same branch: output_format = outputFormat with byte_swap, + * rgb_swap and uv_swap all left 0 (ddp_wdma.c:132-140). The swap bits + * therefore need no value from this function at all, and the format table + * below carries them as zero rather than pretending to encode formats this + * driver cannot reach. + * + * The planar YUV420 and UYVY modes the vendor driver supports are + * deliberately not offered. YUV420_P needs DST_U_ADDR, DST_V_ADDR and + * DST_UV_PITCH programmed with a second and third plane of addresses, and + * the destination byte pitch of WDMAConfigUV() is computed in the vendor + * driver as `dstWidth * bpp / 2` from a caller-supplied address arithmetic + * (ddp_wdma.c:88-97, :608-614) with no bounds check anywhere - that is + * exactly the "narrow arithmetic into an unbounded buffer" shape the G2D + * driver in this tree exists to refuse. Exposing a mode whose address + * computation has no checked version of it would be shipping the bug, so + * the mode is left out and said here rather than left to be discovered. + * + * Must be called with the block's clock enabled. + */ +static void mtk_wdma_config_output(struct mtk_disp_wdma *wdma, + unsigned int out_format) +{ + wdma_update_bits(wdma->dev, WDMA_CFG, WDMA_CFG_KNOWN_MASK, + (out_format << WDMA_CFG_OUT_FORMAT_SHIFT) & + WDMA_CFG_KNOWN_MASK); +} + +/** + * mtk_wdma_config - program a frame for WDMA to write out + * @dev: device + * @width: source width in pixels + * @height: source height in pixels + * @vrefresh: vertical refresh rate, unused + * @bpc: bits per colour component, unused + * @cmdq_pkt: command packet, or NULL for a direct register write + * + * Mirrors WDMAConfig() (ddp_wdma.c:98-306) for the subset this driver + * supports. The clip rectangle is programmed as the full frame at (0,0): + * that is what every in-tree caller of the vendor function does, passing + * clipX = clipY = 0 and clipWidth/Height equal to the source size + * (ddp_path.c:596-603 for the OVL -> WDMA1 memory-out path, and :680-686 for + * the suspend-mode capture path). There is no consumer on this tree that + * clips, and exposing a clip path that nothing uses would be untested code. + * + * The destination width in bytes is the source width times the output + * format's bytes per pixel, exactly as WDMAConfig() computes it from its own + * bpp table (ddp_wdma.c:280-298, :301). For ARGB that is 4. + * + * This hook exists so the funcs table is complete and so the block can be + * brought to a known, idle state. It is NOT a way to aim WDMA at memory: + * no buffer is allocated for this block anywhere on this tree, so + * WDMA_DST_ADDR is deliberately left at 0 rather than at whatever a previous + * caller left there. See mtk_wdma_start() for why that makes start() refuse. + * + * On a wedged block, or on a frame the hardware cannot be told to do, this + * returns without programming anything. Returning without a register write is + * deliberate in both cases: a partial program followed by WDMA_EN=1 would + * start the engine on a half-written destination. + */ +void mtk_wdma_config(struct device *dev, unsigned int width, + unsigned int height, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_wdma *wdma = dev_get_drvdata(dev); + const unsigned int out_format = WDMA_OUTPUT_FORMAT_ARGB; + const unsigned int bpp = 4; + + /* Never program a wedged block. */ + if (wdma->wedged) + return; + + /* + * Validated against the destination this would program, which is 0. + * The address is the one thing mtk_wdma_validate_frame() is here to + * range-check, and it is checked here rather than skipped because + * "it happens to be zero": the check is what would catch a future + * caller that supplies a real address, and it must not be the path + * that is untested. + */ + if (mtk_wdma_validate_frame(wdma, width, height, bpp, 0)) { + /* + * Say so. Silently leaving the previous WDMA_SRC_SIZE and + * WDMA_DST_ADDR in place is how the engine would end up started + * against stale configuration for a frame nobody configured, + * and the caller has no way to tell that from a successful + * config. Rate limited so a caller looping on a too-large mode + * cannot flood the console. + */ + dev_warn_ratelimited(wdma->dev, + "refusing a %ux%u frame: WDMA accepts at most %ux%u\n", + width, height, WDMA_MAX_WIDTH, WDMA_MAX_HEIGHT); + return; + } + + /* + * The input format is fixed, not a parameter. On this tree the only + * consumer of a WDMA is the overlay: the OVL -> WDMA1 route is the + * memory-out path (ddp_path.c:566, disp_path_config_mem_out()) and it + * passes WDMA_INPUT_FORMAT_ARGB (ddp_path.c:597). The YUV444 direct + * link the vendor header names has no producer on this tree. + */ + mtk_wdma_config_output(wdma, out_format); + + /* SRC_SIZE: height in [31:16], width in [15:0]. */ + mtk_ddp_write_mask(cmdq_pkt, (height << 16) | width, &wdma->cmdq_reg, + wdma->regs, WDMA_SRC_SIZE, + WDMA_SIZE_HEIGHT | WDMA_SIZE_WIDTH); + + /* CLIP_COORD at the origin, CLIP_SIZE the full frame. */ + mtk_ddp_write_mask(cmdq_pkt, 0, &wdma->cmdq_reg, wdma->regs, + WDMA_CLIP_COORD, WDMA_SIZE_HEIGHT | WDMA_SIZE_WIDTH); + mtk_ddp_write_mask(cmdq_pkt, (height << 16) | width, &wdma->cmdq_reg, + wdma->regs, WDMA_CLIP_SIZE, + WDMA_SIZE_HEIGHT | WDMA_SIZE_WIDTH); + + /* + * ALPHA: A_Sel = 0 takes the per-pixel alpha from the overlay. The + * register resets to 0xff with A_Sel already 0, so this writes the + * reset state explicitly rather than relying on it surviving - a + * previous configuration could have selected the constant-alpha mode. + */ + mtk_ddp_write_mask(cmdq_pkt, 0, &wdma->cmdq_reg, wdma->regs, + WDMA_ALPHA, WDMA_ALPHA_SEL | WDMA_ALPHA_VALUE); + + /* + * DST_ADDR is written as 0, deliberately and on every config, not + * skipped: no buffer is allocated for this block anywhere on this tree + * and no component supplies a destination address, so the alternative is + * leaving whatever the bootloader or a previous mode put there. With + * the register pinned at 0 there is no address for this engine to be + * pointed at, which is what makes the refusal in mtk_wdma_start() + * honest rather than advisory. + */ + mtk_ddp_write_mask(cmdq_pkt, 0, &wdma->cmdq_reg, wdma->regs, + WDMA_DST_ADDR, GENMASK(31, 0)); + mtk_ddp_write_mask(cmdq_pkt, width * bpp, &wdma->cmdq_reg, + wdma->regs, WDMA_DST_W_IN_BYTE, + WDMA_DST_W_IN_BYTE_MASK); +} + +/** + * mtk_wdma_start - enable the engine + * @dev: device + * + * This is the vendor WDMAStart() (ddp_wdma.c:40-45): unmask both interrupts, + * then set WDMA_EN. + * + * Interrupts are enabled before the engine, not after. Any status latched + * while the block was idle would otherwise be delivered as if it were fresh + * the moment EN went high, which is the same reasoning the RDMA driver gives + * for mtk_rdma_start() in this tree. + * + * Note what this does NOT do: it does not return a completion, and it does not + * wait for one. WDMA is not a CRTC path component on any SoC this driver + * claims - it appears in no mtk_ddp_ext array - so there is no atomic commit + * boundary for it to complete against, and no buffer whose release this + * driver could safely gate on a completion it is not tracking. The vblank + * hooks above exist so a caller that does have such a boundary can use it, + * but nothing in this driver owns a destination buffer, so nothing calls + * them. Writing a "wait for Frame_Complete and return" here would be + * inventing a lifetime this block does not have on this tree; the vendor + * WDMAWait() (ddp_wdma.c:301-320) exists and is not called by any in-tree + * consumer either. + * + * Must be called with the block's clock enabled. + */ +void mtk_wdma_start(struct device *dev) +{ + struct mtk_disp_wdma *wdma = dev_get_drvdata(dev); + + /* + * Refuse on a wedged block. WDMA_EN is what makes the engine fetch + * and write the address in WDMA_DST_ADDR, and after a failed reset that + * register still describes an operation that never finished, so this is + * the one place where "submit" happens and therefore the one place the + * wedge has to be refused. Nothing has been released on the way here, + * so returning also means no buffer is handed back. + */ + if (wdma->wedged) + return; + + /* + * The whole set of INTEN bits, which is complete: the data sheet names + * exactly two (FUE, FCE), so writing WDMA_INT_ALL cannot enable a bit + * this driver is unable to name or acknowledge. + */ + writel(WDMA_INT_ALL, wdma->regs + WDMA_INTEN); + writel(WDMA_EN_ENABLE, wdma->regs + WDMA_EN); +} + +/** + * mtk_wdma_stop - disable the engine + * @dev: device + * + * Mirrors WDMAStop() (ddp_wdma.c:47-54): mask the interrupts, clear the + * enable, and acknowledge the status. The acknowledge is the full mask, not + * the 0x00 the vendor function writes, because WDMA_INTSTA is + * write-1-to-clear (see the IRQ handler): the vendor write of 0 clears + * nothing, which is why the vendor driver can only get away with it because + * WDMAReset() happens to follow in most call paths. + */ +void mtk_wdma_stop(struct device *dev) +{ + struct mtk_disp_wdma *wdma = dev_get_drvdata(dev); + + writel(0x00, wdma->regs + WDMA_INTEN); + writel(0x00, wdma->regs + WDMA_EN); + writel(WDMA_INT_ALL, wdma->regs + WDMA_INTSTA); +} + +unsigned int mtk_wdma_supported_rotations(struct device *dev) +{ + /* + * DRM_MODE_ROTATE_0 and nothing else. WDMA has no rotation of its + * own: the display pipeline's scaling and rotation lives in SCL, and + * the vendor driver passes no rotation parameter to WDMAConfig() + * at all (ddp_wdma.c:98-306). Returning ROTATE_0 is also what + * mtk_ddp_comp_supported_rotations() falls back to, so this matches + * that default rather than overriding it with a claim. + * + * Advertising anything else would be a lie with a visible consequence: + * the CRTC would accept a rotated mode and neither WDMA nor anything + * else on this path would perform the rotation. + */ + return DRM_MODE_ROTATE_0; +} + +static int mtk_disp_wdma_bind(struct device *dev, struct device *master, + void *data) +{ + return 0; +} + +static void mtk_disp_wdma_unbind(struct device *dev, struct device *master, + void *data) +{ +} + +static const struct component_ops mtk_disp_wdma_component_ops = { + .bind = mtk_disp_wdma_bind, + .unbind = mtk_disp_wdma_unbind, +}; + +static int mtk_disp_wdma_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_disp_wdma *priv; + int irq; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_clk_bulk_get_all(dev, &priv->clks); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get WDMA clks\n"); + priv->num_clks = ret; + + priv->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->regs)) + return dev_err_probe(dev, PTR_ERR(priv->regs), + "failed to ioremap WDMA\n"); + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0); + if (ret) + dev_dbg(dev, "get mediatek,gce-client-reg fail!\n"); +#endif + + /* + * Disable and clear pending interrupts before anything else. INTEN=0 + * masks everything; INTSTA is write-1-to-clear so it is acknowledged + * with the full mask, not with 0. + */ + writel(0x0, priv->regs + WDMA_INTEN); + writel(WDMA_INT_ALL, priv->regs + WDMA_INTSTA); + + ret = devm_request_irq(dev, irq, mtk_disp_wdma_irq_handler, + IRQF_TRIGGER_NONE, dev_name(dev), priv); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to request irq %d\n", irq); + + priv->data = of_device_get_match_data(dev); + if (!priv->data) + return dev_err_probe(dev, -ENODEV, "no driver data\n"); + + platform_set_drvdata(pdev, priv); + + pm_runtime_enable(dev); + + /* + * resume_and_get(), not get_sync(): get_sync() leaves the usage counter + * incremented when the resume fails and pm_runtime_disable() does not + * undo that reference, so an error return would hand the device back + * with a reference nobody will ever drop. The reference taken here is + * kept for the lifetime of the bound device - the put_sync() below is + * balanced against the reset setup below it, not against this one. + */ + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + pm_runtime_disable(dev); + return dev_err_probe(dev, ret, "Failed to enable power\n"); + } + + if (priv->data->reset) { + /* Look the reset up by index; see the note in mtk_disp_ovl.c + * about the missing "reset-names" on the display nodes. + */ + priv->rstc = devm_reset_control_get(dev, NULL); + if (IS_ERR(priv->rstc)) { + ret = PTR_ERR(priv->rstc); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + return dev_err_probe(dev, ret, + "Failed to get reset control\n"); + } + + ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks); + if (ret) { + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + return dev_err_probe(dev, ret, + "Failed to enable WDMA clocks\n"); + } + + /* + * Assert the block-level reset before touching it. WDMA_RST is + * the block's own soft reset and is a separate mechanism; this + * is MT6589_DISP_WDMA{0,1}_RST (7 and 8, + * include/dt-bindings/reset/mt6589-resets.h:82-83), which + * resets the whole block. + */ + ret = reset_control_reset(priv->rstc); + if (ret) { + clk_bulk_disable_unprepare(priv->num_clks, priv->clks); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + return dev_err_probe(dev, ret, + "Failed to reset WDMA\n"); + } + + /* Clear anything the reset left latched. */ + writel(0x0, priv->regs + WDMA_INTEN); + writel(WDMA_INT_ALL, priv->regs + WDMA_INTSTA); + + priv->data->reset(priv); + + clk_bulk_disable_unprepare(priv->num_clks, priv->clks); + } + + pm_runtime_put_sync(dev); + + ret = component_add(dev, &mtk_disp_wdma_component_ops); + if (ret) { + pm_runtime_disable(dev); + return dev_err_probe(dev, ret, "Failed to add component\n"); + } + + return 0; +} + +static void mtk_disp_wdma_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_disp_wdma_component_ops); + + pm_runtime_disable(&pdev->dev); +} + +static const struct mtk_wdma_driver_data mt6589_wdma0_driver_data = { + .instance = 0, + .reset = mtk_wdma_reset, + .reset_timeout_us = WDMA_RESET_TIMEOUT_US, +}; + +static const struct mtk_wdma_driver_data mt6589_wdma1_driver_data = { + .instance = 1, + .reset = mtk_wdma_reset, + .reset_timeout_us = WDMA_RESET_TIMEOUT_US, +}; + +static const struct of_device_id mtk_disp_wdma_driver_dt_match[] = { + { .compatible = "mediatek,mt6589-disp-wdma0", + .data = &mt6589_wdma0_driver_data }, + { .compatible = "mediatek,mt6589-disp-wdma1", + .data = &mt6589_wdma1_driver_data }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_disp_wdma_driver_dt_match); + +struct platform_driver mtk_disp_wdma_driver = { + .probe = mtk_disp_wdma_probe, + .remove = mtk_disp_wdma_remove, + .driver = { + .name = "mediatek-disp-wdma", + .of_match_table = mtk_disp_wdma_driver_dt_match, + }, +}; +module_platform_driver(mtk_disp_wdma_driver); + +MODULE_AUTHOR("MediaTek Inc."); +MODULE_DESCRIPTION("MediaTek DISP_WDMA driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c index c86a3f54f35bcc..a5492f4cbbf592 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -240,6 +242,17 @@ static const struct mtk_mmsys_driver_data mt2701_mmsys_driver_data = { .mmsys_dev_num = 1, }; +/* + * MT6589 discovers its selected display pipeline by walking the OF graph + * starting at the MMSYS output port. Crossbar register selection remains in + * the SoC-specific mt6589_dispsys_routing_table. + */ +static const struct mtk_mmsys_driver_data mt6589_mmsys_driver_data = { + .shadow_register = true, + .defer_start_until_bridge = true, + .mmsys_dev_num = 1, +}; + static const struct mtk_mmsys_driver_data mt7623_mmsys_driver_data = { .main_path = mt7623_mtk_ddp_main, .main_len = ARRAY_SIZE(mt7623_mtk_ddp_main), @@ -334,6 +347,8 @@ static const struct mtk_mmsys_driver_data mt8365_mmsys_driver_data = { static const struct of_device_id mtk_drm_of_ids[] = { { .compatible = "mediatek,mt2701-mmsys", .data = &mt2701_mmsys_driver_data}, + { .compatible = "mediatek,mt6589-dispsys", + .data = &mt6589_mmsys_driver_data}, { .compatible = "mediatek,mt7623-mmsys", .data = &mt7623_mmsys_driver_data}, { .compatible = "mediatek,mt2712-mmsys", @@ -718,6 +733,10 @@ static const struct of_device_id mtk_ddp_comp_dt_ids[] = { .data = (void *)MTK_DISP_CCORR }, { .compatible = "mediatek,mt2701-disp-color", .data = (void *)MTK_DISP_COLOR }, + { .compatible = "mediatek,mt6589-disp-color", + .data = (void *)MTK_DISP_COLOR }, + { .compatible = "mediatek,mt6589-disp-tdshp", + .data = (void *)MTK_DISP_TDSHP }, { .compatible = "mediatek,mt8167-disp-color", .data = (void *)MTK_DISP_COLOR }, { .compatible = "mediatek,mt8173-disp-color", @@ -742,6 +761,8 @@ static const struct of_device_id mtk_ddp_comp_dt_ids[] = { .data = (void *)MTK_DISP_MUTEX }, { .compatible = "mediatek,mt2712-disp-mutex", .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt6589-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, { .compatible = "mediatek,mt8167-disp-mutex", .data = (void *)MTK_DISP_MUTEX }, { .compatible = "mediatek,mt8173-disp-mutex", @@ -762,6 +783,8 @@ static const struct of_device_id mtk_ddp_comp_dt_ids[] = { .data = (void *)MTK_DISP_OD }, { .compatible = "mediatek,mt2701-disp-ovl", .data = (void *)MTK_DISP_OVL }, + { .compatible = "mediatek,mt6589-disp-ovl", + .data = (void *)MTK_DISP_OVL }, { .compatible = "mediatek,mt8167-disp-ovl", .data = (void *)MTK_DISP_OVL }, { .compatible = "mediatek,mt8173-disp-ovl", @@ -780,12 +803,16 @@ static const struct of_device_id mtk_ddp_comp_dt_ids[] = { .data = (void *)MTK_DISP_POSTMASK }, { .compatible = "mediatek,mt2701-disp-pwm", .data = (void *)MTK_DISP_BLS }, + { .compatible = "mediatek,mt6589-disp-pwm", + .data = (void *)MTK_DISP_BLS }, { .compatible = "mediatek,mt8167-disp-pwm", .data = (void *)MTK_DISP_PWM }, { .compatible = "mediatek,mt8173-disp-pwm", .data = (void *)MTK_DISP_PWM }, { .compatible = "mediatek,mt2701-disp-rdma", .data = (void *)MTK_DISP_RDMA }, + { .compatible = "mediatek,mt6589-disp-rdma", + .data = (void *)MTK_DISP_RDMA }, { .compatible = "mediatek,mt8167-disp-rdma", .data = (void *)MTK_DISP_RDMA }, { .compatible = "mediatek,mt8173-disp-rdma", @@ -818,6 +845,8 @@ static const struct of_device_id mtk_ddp_comp_dt_ids[] = { .data = (void *)MTK_DPI }, { .compatible = "mediatek,mt2701-dsi", .data = (void *)MTK_DSI }, + { .compatible = "mediatek,mt6589-dsi", + .data = (void *)MTK_DSI }, { .compatible = "mediatek,mt8173-dsi", .data = (void *)MTK_DSI }, { .compatible = "mediatek,mt8183-dsi", @@ -1095,8 +1124,7 @@ static int mtk_drm_probe(struct platform_device *pdev) if (ret) return ret; } else { - /* No devicetree graphs support: go with hardcoded paths if present */ - dev_dbg(dev, "Using hardcoded paths for MMSYS %u\n", mtk_drm_data->mmsys_id); + dev_dbg(dev, "Using SoC-specific static display path"); private->data = mtk_drm_data; } @@ -1277,6 +1305,7 @@ static struct platform_driver * const mtk_drm_drivers[] = { &mtk_disp_ovl_adaptor_driver, &mtk_disp_ovl_driver, &mtk_disp_rdma_driver, + &mtk_disp_tdshp_driver, &mtk_dpi_driver, &mtk_drm_platform_driver, &mtk_dsi_driver, diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h index 1fc3df4b548580..e13055853bb600 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.h +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h @@ -6,6 +6,7 @@ #ifndef MTK_DRM_DRV_H #define MTK_DRM_DRV_H +#include #include #include "mtk_ddp_comp.h" @@ -25,6 +26,7 @@ struct device_node; struct drm_crtc; struct drm_device; struct drm_fb_helper; +struct drm_framebuffer; struct drm_property; struct regmap; @@ -44,6 +46,7 @@ struct mtk_mmsys_driver_data { unsigned int num_conn_routes; bool shadow_register; + bool defer_start_until_bridge; unsigned int mmsys_id; unsigned int mmsys_dev_num; @@ -76,9 +79,11 @@ extern struct platform_driver mtk_disp_merge_driver; extern struct platform_driver mtk_disp_ovl_adaptor_driver; extern struct platform_driver mtk_disp_ovl_driver; extern struct platform_driver mtk_disp_rdma_driver; +extern struct platform_driver mtk_disp_tdshp_driver; extern struct platform_driver mtk_dpi_driver; extern struct platform_driver mtk_dsi_driver; extern struct platform_driver mtk_ethdr_driver; extern struct platform_driver mtk_mdp_rdma_driver; extern struct platform_driver mtk_padding_driver; + #endif /* MTK_DRM_DRV_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c index 5aa71fcdcfab78..4f891acb3431e2 100644 --- a/drivers/gpu/drm/mediatek/mtk_dsi.c +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c @@ -30,10 +30,29 @@ #include "mtk_ddp_comp.h" #include "mtk_disp_drv.h" +#include "mtk_crtc.h" #include "mtk_drm_drv.h" #define DSI_START 0x00 +/* + * DSI_STA / DSI_STATUS (0x004). The MT6589 datasheet's DSI chapter confirms + * these status bit positions; the downstream bootloader header uses the same + * bit names (lk/include/platform/dsi_reg.h:266-274): + * + * rsv_0:1 BUF_UNDERRUN:1 rsv_2:2 ESC_ENTRY_ERR:1 + * LPDT_SYNC_ERR:1 CTRL_ERR:1 CONTENT_ERR:1 + * + * All six error bits are sticky status, cleared by writing the DSI_RACK + * handshake; they are read-only for diagnostics here. + */ +#define DSI_STA 0x04 +#define BUF_UNDERRUN BIT(1) +#define ESC_ENTRY_ERR BIT(4) +#define LPDT_SYNC_ERR BIT(5) +#define CTRL_ERR BIT(6) +#define CONTENT_ERR BIT(7) + #define DSI_INTEN 0x08 #define DSI_INTSTA 0x0c @@ -85,6 +104,9 @@ #define DSI_SIZE_CON 0x38 #define DSI_HEIGHT GENMASK(30, 16) #define DSI_WIDTH GENMASK(14, 0) +/* Only present on parts that set has_mem_conti; upstream programs none. */ +#define DSI_MEM_CONTI 0x90 +#define DSI_WMEM_CONTI 0x3c #define DSI_HSA_WC 0x50 #define DSI_HBP_WC 0x54 #define DSI_HFP_WC 0x58 @@ -106,12 +128,52 @@ #define DSI_RACK 0x84 #define RACK BIT(0) +/* + * DSI_TRIG_STA (0088 in the vendor header's numbering). dsi_reg.h:450-458 + * names each bit; TRIG2 is the LPRX acknowledgement, i.e. the panel + * acknowledging a turnaround. + */ +#define DSI_TRIG_STA 0x88 +#define TRIG_ACK BIT(2) + +/* + * DSI_STATE_DBG0 (0148) holds the controller/D-PHY state machines; + * dsi_reg.h:551-561 and datasheet p. 1854 place CTL_STATE_C at [8:0] and + * HX_TX_STATE_C at [15:12]; bits [11:9] are reserved. These are diagnostic + * only. + */ +#define DSI_STATE_DBG0 0x148 +#define DSI_STATE_DBG1 0x14c +#define DSI_STATE_DBG2 0x150 +#define DSI_STATE_DBG3 0x154 +#define DSI_STATE_DBG4 0x158 +#define DSI_STATE_DBG6 0x160 +#define DSI_STATE_DBG7 0x164 +#define DSI_STATE_DBG8 0x168 +#define DSI_STATE_DBG9 0x16c +/* + * Field masks for DSI_STATE_DBG0. These are spelled as u32 constants rather + * than GENMASK() because GENMASK() expands to an unsigned long expression, + * which FIELD_GET() rejects as a non-constant mask on this kernel. + */ +#define CTL_STATE_C 0x000001ff +#define HX_TX_STATE_C 0x0000f000 + #define DSI_PHY_LCCON 0x104 #define LC_HS_TX_EN BIT(0) #define LC_ULPM_EN BIT(1) #define LC_WAKEUP_EN BIT(2) #define DSI_PHY_LD0CON 0x108 +/* + * Bit 0: the vendor header calls this L0_HS_TX_EN (dsi_reg.h:426) while the + * data sheet's field table calls bit 0 of 0x108 L0_RM_TRIG_EN, "Sends + * application trigger to slave side". Either way it is not what puts the + * lane into high speed: downstream's DSI_lane0_ULP_mode() only ever *clears* + * it, and never sets it on the resume path, so a healthy link reads + * LD0CON == 0. The actual HS state is + * DSI_STATE_DBG0.DPHY_HS_TX_STATE_C. Do not "fix" a zero here. + */ #define LD0_HS_TX_EN BIT(0) #define LD0_ULPM_EN BIT(1) #define LD0_WAKEUP_EN BIT(2) @@ -130,6 +192,12 @@ #define DSI_PHY_TIMECON2 0x118 #define CONT_DET GENMASK(7, 0) +/* + * DSI_PHY_TIMCON2[15:8]. On MT6589 this byte is reserved: the register has + * only CLK_HS_TRAIL[31:24], CLK_HS_ZERO[23:16] and CONT_DET[7:0], and the + * downstream register struct names this byte RSV8. So the field is written + * only on the parts where it exists. + */ #define DA_HS_SYNC GENMASK(15, 8) #define CLK_ZERO GENMASK(23, 16) #define CLK_TRAIL GENMASK(31, 24) @@ -190,10 +258,38 @@ struct mtk_dsi_driver_data { const u32 reg_cmdq_off; const u32 reg_vm_cmd_off; const u32 reg_shadow_dbg_off; + /* + * Optional control bits in DSI_CON_CTRL. + * + * MT6589 calls register 0x10 DSI_COM_CTRL and implements only + * DSI_RESET at bit 0. DSI_EN and DPHY_RESET are not defined + * there, so leave these masks zero on MT6589. + */ + const u32 dsi_en_mask; + const u32 dphy_reset_mask; bool has_shadow_ctl; bool has_size_ctl; bool cmdq_long_packet_ctl; bool support_per_frame_lp; + bool use_downstream_vdo_porch_timing; + /* + * Use the bootloader's D-PHY timing words instead of the generic + * formulas; see mtk_dsi_phy_timconfig(). + */ + bool use_downstream_phy_timing; + /* + * On this SoC DSI_PHY_TIMCON2[15:8] is reserved, not DA_HS_SYNC. + * The real CONT_DET field is at [7:0]. + */ + bool timcon2_no_da_hs_sync; + /* + * Whether DSI_MEM_CONTI (0x90) exists and holds the read/write memory + * continue command. Upstream never programs this register on any + * SoC, so it must stay off unless a part is known to have it. + */ + bool has_mem_conti; + bool has_lane_clk; + bool has_reset; }; struct mtk_dsi { @@ -209,6 +305,7 @@ struct mtk_dsi { struct clk *engine_clk; struct clk *digital_clk; + struct clk *lane_clk; struct clk *hs_clk; u32 data_rate; @@ -236,6 +333,8 @@ static inline struct mtk_dsi *host_to_dsi(struct mipi_dsi_host *h) return container_of(h, struct mtk_dsi, host); } +static void mtk_dsi_wait_for_idle(struct mtk_dsi *dsi); + static void mtk_dsi_mask(struct mtk_dsi *dsi, u32 offset, u32 mask, u32 data) { u32 temp = readl(dsi->regs + offset); @@ -249,6 +348,94 @@ static void mtk_dsi_phy_timconfig(struct mtk_dsi *dsi) u32 data_rate_mhz = DIV_ROUND_UP(dsi->data_rate, HZ_PER_MHZ); struct mtk_phy_timing *timing = &dsi->phy_timing; + /* + * MT6589: the vendor's D-PHY timing computation, which differs from + * the generic one above in both its constants and its inputs. + * + * This is not cosmetic. The generic formulas produce a set that + * differs from the bootloader's on 9 of the 13 fields - HS_TRAIL 5 + * against 16, CLK_TRAIL 3 against 16, CLK_ZERO 12 against 18 - and + * HS_TRAIL/CLK_TRAIL are the high-speed trailer periods, i.e. how long + * a lane stays in HS after the last bit before releasing to low power. + * With those wrong the link reports itself as transmitting (START set, + * BUSY high, state machine advancing) while the panel receives + * something it cannot decode. That was this board's black screen. + * + * All of it derives from two numbers, per DSI_PHY_TIMCONFIG() + * (dsi_drv.c:1453-1495): + * + * cycle_time = 8000 / lane_rate_mhz [ns per PLL cycle] + * ui = 1000 / lane_rate_mhz + 1 [unit interval] + * + * lane_rate is the *achieved* rate the MIPI TX PLL produces, not the + * requested one: the PLL table is coarse, and dsi->data_rate is read + * back from the PHY for exactly that reason. For this panel that is + * 416 MHz, giving cycle_time 19 and ui 3, and the fields below then + * come out at the values the bootloader runs - which is the check + * that this is a derivation and not a table copied from one dump. + */ + if (dsi->driver_data->use_downstream_phy_timing) { + unsigned int lane_mhz = data_rate_mhz; + unsigned int cycle_time, ui; + + /* NS_TO_CYCLE(): integer division, rounded up. */ +#define MTK_DSI_NS_TO_CYCLE(n, c) ((n) / (c) + (((n) % (c)) ? 1 : 0)) + + cycle_time = 8000 / lane_mhz; + ui = 1000 / lane_mhz + 1; + + if (!cycle_time) + cycle_time = 1; + + /* HS_TRAIL: max(lanes, time for 4 UI per lane + 60 ns) + 0x0a. + * The +0x0a is the vendor's "HW latency" margin. + */ + timing->da_hs_trail = + max((unsigned int)dsi->lanes, + MTK_DSI_NS_TO_CYCLE(dsi->lanes * 4 * ui + 60, + cycle_time)) + 0x0a; + + timing->da_hs_prepare = MTK_DSI_NS_TO_CYCLE(60 + 5 * ui, + cycle_time); + if (!timing->da_hs_prepare) + timing->da_hs_prepare = 1; + + timing->da_hs_zero = MTK_DSI_NS_TO_CYCLE( + 0xC8 + 0x0a * ui - timing->da_hs_prepare * cycle_time, + cycle_time); + + timing->lpx = MTK_DSI_NS_TO_CYCLE(65, cycle_time); + if (!timing->lpx) + timing->lpx = 1; + + /* Note these are not the generic path's multiples of LPX. */ + timing->ta_get = 5 * timing->lpx; + timing->ta_sure = 3 * timing->lpx / 2; + timing->ta_go = 4 * timing->lpx; + timing->da_hs_exit = 2 * timing->lpx; + + /* CLK_TRAIL carries the same +0x0a margin, floor of 2. */ + timing->clk_hs_trail = + MTK_DSI_NS_TO_CYCLE(0x64, cycle_time) + 0x0a; + if (timing->clk_hs_trail < 2) + timing->clk_hs_trail = 2; + + timing->clk_hs_prepare = MTK_DSI_NS_TO_CYCLE(0x40, + cycle_time); + if (!timing->clk_hs_prepare) + timing->clk_hs_prepare = 1; + + /* CLK_ZERO comes from 0x190 ns minus the prepare time. */ + timing->clk_hs_zero = MTK_DSI_NS_TO_CYCLE( + 0x190 - timing->clk_hs_prepare * cycle_time, + cycle_time); + + timing->clk_hs_exit = 2 * timing->lpx; + timing->clk_hs_post = MTK_DSI_NS_TO_CYCLE(80 + 52 * ui, + cycle_time); + +#undef MTK_DSI_NS_TO_CYCLE + } else { timing->lpx = (60 * data_rate_mhz / (8 * 1000)) + 1; timing->da_hs_prepare = (80 * data_rate_mhz + 4 * 1000) / 8000; timing->da_hs_zero = (170 * data_rate_mhz + 10 * 1000) / 8000 + 1 - @@ -265,6 +452,7 @@ static void mtk_dsi_phy_timconfig(struct mtk_dsi *dsi) timing->clk_hs_trail = timing->clk_hs_prepare; timing->clk_hs_zero = timing->clk_hs_trail * 4; timing->clk_hs_exit = 2 * timing->clk_hs_trail; + } timcon0 = FIELD_PREP(LPX, timing->lpx) | FIELD_PREP(HS_PREP, timing->da_hs_prepare) | @@ -276,9 +464,13 @@ static void mtk_dsi_phy_timconfig(struct mtk_dsi *dsi) FIELD_PREP(TA_GET, timing->ta_get) | FIELD_PREP(DA_HS_EXIT, timing->da_hs_exit); - timcon2 = FIELD_PREP(DA_HS_SYNC, 1) | - FIELD_PREP(CLK_ZERO, timing->clk_hs_zero) | - FIELD_PREP(CLK_TRAIL, timing->clk_hs_trail); + if (dsi->driver_data->timcon2_no_da_hs_sync) + timcon2 = FIELD_PREP(CLK_ZERO, timing->clk_hs_zero) | + FIELD_PREP(CLK_TRAIL, timing->clk_hs_trail); + else + timcon2 = FIELD_PREP(DA_HS_SYNC, 1) | + FIELD_PREP(CLK_ZERO, timing->clk_hs_zero) | + FIELD_PREP(CLK_TRAIL, timing->clk_hs_trail); timcon3 = FIELD_PREP(CLK_HS_PREP, timing->clk_hs_prepare) | FIELD_PREP(CLK_HS_POST, timing->clk_hs_post) | @@ -292,12 +484,18 @@ static void mtk_dsi_phy_timconfig(struct mtk_dsi *dsi) static void mtk_dsi_enable(struct mtk_dsi *dsi) { - mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, DSI_EN); + u32 mask = dsi->driver_data->dsi_en_mask; + + if (mask) + mtk_dsi_mask(dsi, DSI_CON_CTRL, mask, mask); } static void mtk_dsi_disable(struct mtk_dsi *dsi) { - mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, 0); + u32 mask = dsi->driver_data->dsi_en_mask; + + if (mask) + mtk_dsi_mask(dsi, DSI_CON_CTRL, mask, 0); } static void mtk_dsi_reset_engine(struct mtk_dsi *dsi) @@ -308,8 +506,13 @@ static void mtk_dsi_reset_engine(struct mtk_dsi *dsi) static void mtk_dsi_reset_dphy(struct mtk_dsi *dsi) { - mtk_dsi_mask(dsi, DSI_CON_CTRL, DPHY_RESET, DPHY_RESET); - mtk_dsi_mask(dsi, DSI_CON_CTRL, DPHY_RESET, 0); + u32 mask = dsi->driver_data->dphy_reset_mask; + + if (!mask) + return; + + mtk_dsi_mask(dsi, DSI_CON_CTRL, mask, mask); + mtk_dsi_mask(dsi, DSI_CON_CTRL, mask, 0); } static void mtk_dsi_clk_ulp_mode_enter(struct mtk_dsi *dsi) @@ -535,7 +738,31 @@ static void mtk_dsi_config_vdo_timing_per_line_lp(struct mtk_dsi *dsi) horizontal_front_back_byte = horizontal_frontporch_byte + horizontal_backporch_byte; data_phy_cycles_byte = data_phy_cycles * dsi->lanes + delta; - if (horizontal_front_back_byte > data_phy_cycles_byte) { + if (dsi->driver_data->use_downstream_vdo_porch_timing) { + /* + * Match downstream DSI_Config_VDO_Timing() + * (dsi_drv.c:1360-1381) exactly. Three things differ from the + * generic path above, and all three are load-bearing: + * + * - HSA_WC is ZERO in sync-event mode. Downstream declares + * horizontal_sync_active_byte = 0 and assigns it only in the + * sync-pulse/else branch, so for SYNC_EVENT (which this panel + * is) it writes ALIGN_TO(0, 4) == 0. The generic formula + * above yields (hsync_len * bpp - 10), which is 2 for this + * panel, and a non-zero HSA on a sync-event link puts a sync + * active packet where the panel expects none. + * - HFP subtracts 12 before aligning, not the per-line D-PHY + * transition budget. Without it HBP/HFP come out as + * 0x44/0xd1 instead of the known-good 0x64/0x120. + * - All three word counts are aligned to 4 bytes. + * + * Keep this MT6589-specific: no other SoC here shares the + * vendor sequence this is transcribed from. + */ + horizontal_sync_active_byte = 0; + horizontal_backporch_byte = roundup(horizontal_backporch_byte, 4); + horizontal_frontporch_byte = roundup(horizontal_frontporch_byte - 12, 4); + } else if (horizontal_front_back_byte > data_phy_cycles_byte) { horizontal_frontporch_byte -= data_phy_cycles_byte * horizontal_frontporch_byte / horizontal_front_back_byte; @@ -609,6 +836,32 @@ static void mtk_dsi_set_interrupt_enable(struct mtk_dsi *dsi) writel(inten, dsi->regs + DSI_INTEN); } +/* + * Log the link state at stream on. Kept because a dark panel is otherwise + * indistinguishable between "the engine never started" and "the link came up + * and the panel is not showing it", and DSI_STA reads 0 in both cases. One + * line, at debug level: the boot log has no room for more, and the values are + * only wanted while something is wrong. + */ +static void mtk_dsi_dump_status(struct mtk_dsi *dsi, const char *when) +{ + u32 dbg0 = readl(dsi->regs + DSI_STATE_DBG0); + + dev_dbg(dsi->host.dev, + "DSI %s: mode=%#x txrx=%#x start=%#x sta=%#x intsta=%#x " + "dbg0=%#x ctl_state=%#x hx_state=%#x lccon=%#x ld0con=%#x " + "ps=%#x hsa=%#x hbp=%#x hfp=%#x\n", + when, readl(dsi->regs + DSI_MODE_CTRL), + readl(dsi->regs + DSI_TXRX_CTRL), readl(dsi->regs + DSI_START), + readl(dsi->regs + DSI_STA), readl(dsi->regs + DSI_INTSTA), + dbg0, FIELD_GET(CTL_STATE_C, dbg0), + FIELD_GET(HX_TX_STATE_C, dbg0), + readl(dsi->regs + DSI_PHY_LCCON), + readl(dsi->regs + DSI_PHY_LD0CON), + readl(dsi->regs + DSI_PSCTRL), readl(dsi->regs + DSI_HSA_WC), + readl(dsi->regs + DSI_HBP_WC), readl(dsi->regs + DSI_HFP_WC)); +} + static void mtk_dsi_irq_data_set(struct mtk_dsi *dsi, u32 irq_bit) { dsi->irq_data |= irq_bit; @@ -628,33 +881,49 @@ static s32 mtk_dsi_wait_for_irq_done(struct mtk_dsi *dsi, u32 irq_flag, ret = wait_event_interruptible_timeout(dsi->irq_wait_queue, dsi->irq_data & irq_flag, jiffies); + if (ret < 0) + return ret; + if (ret == 0) { DRM_WARN("Wait DSI IRQ(0x%08x) Timeout\n", irq_flag); mtk_dsi_enable(dsi); mtk_dsi_reset_engine(dsi); + + return -ETIME; } - return ret; + return 0; } static irqreturn_t mtk_dsi_irq(int irq, void *dev_id) { struct mtk_dsi *dsi = dev_id; - u32 status, tmp; + u32 status, tmp = 0; + int ret; u32 flag = LPRX_RD_RDY_INT_FLAG | CMD_DONE_INT_FLAG | VM_DONE_INT_FLAG; status = readl(dsi->regs + DSI_INTSTA) & flag; if (status) { - do { - mtk_dsi_mask(dsi, DSI_RACK, RACK, RACK); - tmp = readl(dsi->regs + DSI_INTSTA); - } while (tmp & DSI_BUSY); + /* + * DSI_BUSY is a status bit. Never spin on it indefinitely from + * hard-IRQ context: a wedged engine would otherwise keep a CPU in + * this handler forever and take the system down with it. + */ + mtk_dsi_mask(dsi, DSI_RACK, RACK, RACK); + ret = readl_poll_timeout_atomic(dsi->regs + DSI_INTSTA, tmp, + !(tmp & DSI_BUSY), 1, 1000); + if (ret) + dev_warn_ratelimited(dsi->host.dev, + "DSI IRQ: engine stayed busy for 1 ms (INTSTA=0x%08x)\n", + tmp); mtk_dsi_mask(dsi, DSI_INTSTA, status, 0); - mtk_dsi_irq_data_set(dsi, status); - wake_up_interruptible(&dsi->irq_wait_queue); + if (!ret) { + mtk_dsi_irq_data_set(dsi, status); + wake_up_interruptible(&dsi->irq_wait_queue); + } } return IRQ_HANDLED; @@ -662,15 +931,17 @@ static irqreturn_t mtk_dsi_irq(int irq, void *dev_id) static s32 mtk_dsi_switch_to_cmd_mode(struct mtk_dsi *dsi, u8 irq_flag, u32 t) { + s32 ret; + mtk_dsi_irq_data_clear(dsi, irq_flag); mtk_dsi_set_cmd_mode(dsi); - if (!mtk_dsi_wait_for_irq_done(dsi, irq_flag, t)) { + ret = mtk_dsi_wait_for_irq_done(dsi, irq_flag, t); + if (ret) { DRM_ERROR("failed to switch cmd mode\n"); - return -ETIME; - } else { - return 0; } + + return ret; } static void mtk_dsi_lane_ready(struct mtk_dsi *dsi) @@ -691,6 +962,7 @@ static void mtk_dsi_lane_ready(struct mtk_dsi *dsi) static int mtk_dsi_poweron(struct mtk_dsi *dsi) { struct device *dev = dsi->host.dev; + struct reset_control *rstc; int ret; u32 bit_per_pixel; @@ -700,7 +972,7 @@ static int mtk_dsi_poweron(struct mtk_dsi *dsi) ret = mipi_dsi_pixel_format_to_bpp(dsi->format); if (ret < 0) { dev_err(dev, "Unknown MIPI DSI format %d\n", dsi->format); - return ret; + goto err_refcount; } bit_per_pixel = ret; @@ -713,7 +985,25 @@ static int mtk_dsi_poweron(struct mtk_dsi *dsi) goto err_refcount; } - phy_power_on(dsi->phy); + /* + * Replace the requested rate with the one the PLL actually produces. + * + * The MIPI TX PLL table is coarse - 50 discrete entries - so a + * requested lane rate is almost never reachable exactly: this panel + * asks for 419.022 Mbps and gets 416. Everything downstream of here + * that computes a time from the rate must use the achieved value, and + * mtk_dsi_phy_timconfig() is exactly that consumer: its cycle_time is + * 8000/rate, so a 3 Mbps error moves every D-PHY timing field. The + * PHY's set_rate() stores the achieved rate, so reading it back here + * is what makes the timing match the hardware. + */ + dsi->data_rate = clk_get_rate(dsi->hs_clk); + + ret = phy_power_on(dsi->phy); + if (ret) { + dev_err(dev, "Failed to power on DPHY: %d\n", ret); + goto err_refcount; + } ret = clk_prepare_enable(dsi->engine_clk); if (ret < 0) { @@ -727,8 +1017,63 @@ static int mtk_dsi_poweron(struct mtk_dsi *dsi) goto err_disable_engine_clk; } + /* MT6589 has a separate gate for the digital DSI data-lane logic. */ + if (dsi->lane_clk) { + ret = clk_prepare_enable(dsi->lane_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable lane clock: %d\n", ret); + goto err_disable_digital_clk; + } + } + + /* + * MT6589 has a separate DISPSYS reset for the DSI controller. + * Resetting DSI_COM_CTRL alone does not release this external + * reset. Pulse it with the engine clocks enabled before + * programming the controller registers. Acquire the handle here, + * rather than in probe, so it does not conflict with device_reset() + * during component binding. + */ + if (dsi->driver_data->has_reset) { + rstc = reset_control_get_exclusive(dev, NULL); + if (IS_ERR(rstc)) { + ret = PTR_ERR(rstc); + dev_err(dev, "Failed to get DSI reset control: %d\n", + ret); + goto err_disable_lane_clk; + } + + ret = reset_control_acquire(rstc); + if (ret) { + dev_err(dev, "Failed to acquire DSI reset control: %d\n", + ret); + reset_control_put(rstc); + goto err_disable_lane_clk; + } + + ret = reset_control_reset(rstc); + reset_control_release(rstc); + reset_control_put(rstc); + if (ret) { + dev_err(dev, "Failed to reset DSI controller: %d\n", + ret); + goto err_disable_lane_clk; + } + } + mtk_dsi_enable(dsi); + /* + * Set the lane count and the packet flags in DSI_TXRX_CTRL before the + * engine is restarted. mtk_dsi_rxtx_control() was only ever called + * from mtk_dsi_stop(), so on enable LANE_NUM stayed at its reset + * value of 0 and the DSI drove no lanes at all - the overlay and RDMA + * completed frames without underflow and the panel stayed dark. + * The bootloader writes the same field the same way (dsi_drv.c: + * LANE_NUM = 0xF for four lanes) before starting video. + */ + mtk_dsi_rxtx_control(dsi); + if (dsi->driver_data->has_shadow_ctl) writel(FORCE_COMMIT | BYPASS_SHADOW, dsi->regs + dsi->driver_data->reg_shadow_dbg_off); @@ -736,14 +1081,20 @@ static int mtk_dsi_poweron(struct mtk_dsi *dsi) mtk_dsi_reset_engine(dsi); mtk_dsi_phy_timconfig(dsi); + if (dsi->driver_data->has_mem_conti) + writel(DSI_WMEM_CONTI, dsi->regs + DSI_MEM_CONTI); + mtk_dsi_ps_control(dsi, true); mtk_dsi_set_vm_cmd(dsi); mtk_dsi_config_vdo_timing(dsi); mtk_dsi_set_interrupt_enable(dsi); - mtk_dsi_lane_ready(dsi); - mtk_dsi_clk_hs_mode(dsi, 1); return 0; +err_disable_lane_clk: + if (dsi->lane_clk) + clk_disable_unprepare(dsi->lane_clk); +err_disable_digital_clk: + clk_disable_unprepare(dsi->digital_clk); err_disable_engine_clk: clk_disable_unprepare(dsi->engine_clk); err_phy_power_off: @@ -781,6 +1132,8 @@ static void mtk_dsi_poweroff(struct mtk_dsi *dsi) clk_disable_unprepare(dsi->engine_clk); clk_disable_unprepare(dsi->digital_clk); + if (dsi->lane_clk) + clk_disable_unprepare(dsi->lane_clk); phy_power_off(dsi->phy); @@ -792,6 +1145,21 @@ static void mtk_output_dsi_enable(struct mtk_dsi *dsi) if (dsi->enabled) return; + /* + * Make sure the last transmission has drained before programming a + * new mode and pulsing START. Downstream does exactly this before + * every start (DSI_StartTransfer() -> _WaitForEngineNotBusy(), and + * dsi_drv.c:942 "while(DSI_REG->DSI_INTSTA.BUSY);"), and for good + * reason: DSI_START is a level the engine samples, so a start issued + * while the controller is still busy is dropped and the engine then + * sits with DSI_BUSY asserted and no interrupt ever following. That + * is the observed failure - INTSTA reading 0x80000000 for the rest of + * the boot with the DSI interrupt count at zero. + * + * mtk_dsi_wait_for_idle() was written for this and never called. + */ + mtk_dsi_wait_for_idle(dsi); + mtk_dsi_set_mode(dsi); mtk_dsi_start(dsi); @@ -842,7 +1210,24 @@ static void mtk_dsi_bridge_atomic_enable(struct drm_bridge *bridge, if (dsi->refcount == 0) return; + /* + * MT6589 defers the complete DDP start until this point, after the panel + * bridge's atomic_pre_enable has enabled its supply/GPIO and the DSI PHY + * is configured. The CRTC then starts the components in OF graph order, + * source to sink, so DSI - the last element of the path - is started + * last, exactly as the downstream path does it. + * + * mtk_output_dsi_enable() below is therefore the first START pulse for + * this enable: the DDP hook has not run for DSI yet. + */ + mtk_crtc_ddp_start_pending(dsi->encoder.crtc); mtk_output_dsi_enable(dsi); + /* + * Keep a final snapshot here to compare the link after every component + * has started. mtk_output_dsi_enable() is idempotent, so this call is + * a no-op for the case where the DDP hook already got here first. + */ + mtk_dsi_dump_status(dsi, "post-start"); } static void mtk_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge, @@ -852,8 +1237,32 @@ static void mtk_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge, int ret; ret = mtk_dsi_poweron(dsi); - if (ret < 0) - DRM_ERROR("failed to power on dsi\n"); + if (ret < 0) { + /* + * Abort the enable here. mtk_dsi_poweron() has already undone + * whatever it managed to do (it drops the refcount and unwinds + * clocks/phy on every error path), so the link is not powered and + * must not be poked further: putting the lanes into HS mode and + * dumping registers of an unpowered DSI only produces a second, + * more confusing set of secondary errors on top of the real one. + * + * atomic_pre_enable() has no error return - drm_bridge has no way + * to abort an atomic commit from it - so the failure is recorded + * by leaving dsi->refcount at 0. mtk_dsi_bridge_atomic_enable() + * already skips mtk_output_dsi_enable() in that case, so the + * engine is never started and the display pipe comes up dark but + * intact, exactly as mtk_dsi_poweroff()'s WARN_ON(refcount == 0) + * guard expects. Nothing calls mtk_dsi_poweron() speculatively, + * and refcount was 0 coming in, so the counter is back to where it + * started either way. + */ + DRM_ERROR("failed to power on dsi: %d\n", ret); + return; + } + + mtk_dsi_lane_ready(dsi); + mtk_dsi_clk_hs_mode(dsi, 1); + mtk_dsi_dump_status(dsi, "pre-start"); } static void mtk_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge, @@ -861,6 +1270,17 @@ static void mtk_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge, { struct mtk_dsi *dsi = bridge_to_dsi(bridge); + /* + * drm_atomic_bridge_chain_post_disable() calls this unconditionally, + * including for the enable that atomic_pre_enable() failed to power on + * - there is no way to tell the core to skip it. mtk_dsi_poweroff() + * would correctly WARN_ON(refcount == 0) and return, but a WARN splat + * for a failure already reported is pure noise, so check here and stay + * silent: nothing was powered on, so there is nothing to power off. + */ + if (dsi->refcount == 0) + return; + mtk_dsi_poweroff(dsi); } @@ -898,8 +1318,32 @@ static const struct drm_bridge_funcs mtk_dsi_bridge_funcs = { void mtk_dsi_ddp_start(struct device *dev) { struct mtk_dsi *dsi = dev_get_drvdata(dev); + int ret; + + ret = mtk_dsi_poweron(dsi); + if (ret < 0) { + DRM_ERROR("failed to power on DSI display path: %d\n", ret); + return; + } - mtk_dsi_poweron(dsi); + /* + * The CRTC starts the MT6589 DDP components in OF graph order, so this + * is the sink-side hook and runs last, after OVL, COLOR and RDMA0 have + * been configured and started. Powering DSI on alone is not enough: + * the DSI_START pulse is what makes the controller begin pulling frames + * out of RDMA0, and it belongs at the end of the sequence. It used to + * wait for the bridge atomic_enable callback, which runs after the same + * point but cannot be relied on to come first. + * + * atomic_pre_enable normally did the lane setup already. Keep it + * idempotent here because the DDP hook can also handle a retried + * power-on. + */ + mtk_dsi_lane_ready(dsi); + mtk_dsi_clk_hs_mode(dsi, true); + mtk_dsi_dump_status(dsi, "ddp-pre-start"); + mtk_output_dsi_enable(dsi); + mtk_dsi_dump_status(dsi, "ddp-post-start"); } void mtk_dsi_ddp_stop(struct device *dev) @@ -1105,15 +1549,18 @@ static void mtk_dsi_cmdq(struct mtk_dsi *dsi, const struct mipi_dsi_msg *msg) static ssize_t mtk_dsi_host_send_cmd(struct mtk_dsi *dsi, const struct mipi_dsi_msg *msg, u8 flag) { + s32 ret; + mtk_dsi_wait_for_idle(dsi); mtk_dsi_irq_data_clear(dsi, flag); mtk_dsi_cmdq(dsi, msg); mtk_dsi_start(dsi); - if (!mtk_dsi_wait_for_irq_done(dsi, flag, 2000)) - return -ETIME; - else - return 0; + ret = mtk_dsi_wait_for_irq_done(dsi, flag, 2000); + if (ret) + return ret; + + return 0; } static ssize_t mtk_dsi_host_transfer(struct mipi_dsi_host *host, @@ -1145,7 +1592,7 @@ static ssize_t mtk_dsi_host_transfer(struct mipi_dsi_host *host, goto restore_dsi_mode; if (!MTK_DSI_HOST_IS_READ(msg->type)) { - recv_cnt = 0; + recv_cnt = msg->tx_len; goto restore_dsi_mode; } @@ -1217,6 +1664,13 @@ static int mtk_dsi_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(dsi->digital_clk), "Failed to get digital clock\n"); + if (dsi->driver_data->has_lane_clk) { + dsi->lane_clk = devm_clk_get(dev, "lane"); + if (IS_ERR(dsi->lane_clk)) + return dev_err_probe(dev, PTR_ERR(dsi->lane_clk), + "Failed to get lane clock\n"); + } + dsi->hs_clk = devm_clk_get(dev, "hs"); if (IS_ERR(dsi->hs_clk)) return dev_err_probe(dev, PTR_ERR(dsi->hs_clk), "Failed to get hs clock\n"); @@ -1268,19 +1722,40 @@ static void mtk_dsi_remove(struct platform_device *pdev) static const struct mtk_dsi_driver_data mt8173_dsi_driver_data = { .reg_cmdq_off = 0x200, .reg_vm_cmd_off = 0x130, - .reg_shadow_dbg_off = 0x190 + .reg_shadow_dbg_off = 0x190, + .dsi_en_mask = DSI_EN, + .dphy_reset_mask = DPHY_RESET, }; static const struct mtk_dsi_driver_data mt2701_dsi_driver_data = { .reg_cmdq_off = 0x180, .reg_vm_cmd_off = 0x130, - .reg_shadow_dbg_off = 0x190 + .reg_shadow_dbg_off = 0x190, + .dsi_en_mask = DSI_EN, + .dphy_reset_mask = DPHY_RESET, +}; + +static const struct mtk_dsi_driver_data mt6589_dsi_driver_data = { + .reg_cmdq_off = 0x180, + .reg_vm_cmd_off = 0x130, + /* + * DSI_COM_CTRL at 0x10 only implements DSI_RESET on MT6589. + * Do not access the generic DSI_EN or DPHY_RESET bits here. + */ + .timcon2_no_da_hs_sync = true, + .has_mem_conti = true, + .has_lane_clk = true, + .has_reset = true, + .use_downstream_vdo_porch_timing = true, + .use_downstream_phy_timing = true, }; static const struct mtk_dsi_driver_data mt8183_dsi_driver_data = { .reg_cmdq_off = 0x200, .reg_vm_cmd_off = 0x130, .reg_shadow_dbg_off = 0x190, + .dsi_en_mask = DSI_EN, + .dphy_reset_mask = DPHY_RESET, .has_shadow_ctl = true, .has_size_ctl = true, }; @@ -1289,6 +1764,8 @@ static const struct mtk_dsi_driver_data mt8186_dsi_driver_data = { .reg_cmdq_off = 0xd00, .reg_vm_cmd_off = 0x200, .reg_shadow_dbg_off = 0xc00, + .dsi_en_mask = DSI_EN, + .dphy_reset_mask = DPHY_RESET, .has_shadow_ctl = true, .has_size_ctl = true, }; @@ -1297,6 +1774,8 @@ static const struct mtk_dsi_driver_data mt8188_dsi_driver_data = { .reg_cmdq_off = 0xd00, .reg_vm_cmd_off = 0x200, .reg_shadow_dbg_off = 0xc00, + .dsi_en_mask = DSI_EN, + .dphy_reset_mask = DPHY_RESET, .has_shadow_ctl = true, .has_size_ctl = true, .cmdq_long_packet_ctl = true, @@ -1305,6 +1784,8 @@ static const struct mtk_dsi_driver_data mt8188_dsi_driver_data = { static const struct of_device_id mtk_dsi_of_match[] = { { .compatible = "mediatek,mt2701-dsi", .data = &mt2701_dsi_driver_data }, + { .compatible = "mediatek,mt6589-dsi", .data = &mt6589_dsi_driver_data }, + { .compatible = "mediatek,mt8167-dsi", .data = &mt2701_dsi_driver_data }, { .compatible = "mediatek,mt8173-dsi", .data = &mt8173_dsi_driver_data }, { .compatible = "mediatek,mt8183-dsi", .data = &mt8183_dsi_driver_data }, { .compatible = "mediatek,mt8186-dsi", .data = &mt8186_dsi_driver_data }, diff --git a/drivers/gpu/drm/mediatek/mtk_plane.c b/drivers/gpu/drm/mediatek/mtk_plane.c index 86c6f60eadb32c..238dfff624ceff 100644 --- a/drivers/gpu/drm/mediatek/mtk_plane.c +++ b/drivers/gpu/drm/mediatek/mtk_plane.c @@ -45,6 +45,16 @@ static void mtk_plane_reset(struct drm_plane *plane) state->base.plane = plane; state->pending.format = DRM_FORMAT_RGB565; state->pending.modifier = DRM_FORMAT_MOD_LINEAR; + /* + * pending->pitch is in bytes while the OVL and RDMA pitch registers + * count pixels, so mtk_disp_ovl.c and mtk_disp_rdma.c divide by cpp to + * convert. A freshly reset plane has cpp == 0 until the first + * mtk_plane_update_new_state(), and mtk_crtc_ddp_hw_init() calls + * layer_config() for every layer on its way up. Keep the divisor + * non-zero; 1 is the neutral value, since a plane with no framebuffer + * has no meaningful pitch to convert anyway. + */ + state->pending.cpp = 1; } static struct drm_plane_state *mtk_plane_duplicate_state(struct drm_plane *plane) @@ -110,8 +120,8 @@ static int mtk_plane_atomic_async_check(struct drm_plane *plane, true, true); } -static void mtk_plane_update_new_state(struct drm_plane_state *new_state, - struct mtk_plane_state *mtk_plane_state) +void mtk_plane_update_new_state(struct drm_plane_state *new_state, + struct mtk_plane_state *mtk_plane_state) { struct drm_framebuffer *fb = new_state->fb; struct drm_gem_dma_object *dma_obj; @@ -179,6 +189,12 @@ static void mtk_plane_update_new_state(struct drm_plane_state *new_state, mtk_plane_state->pending.enable = true; mtk_plane_state->pending.pitch = pitch; + /* + * The OVL and RDMA pitch registers count pixels per line. Carry the + * bytes-per-pixel so each engine can convert without having to look + * the format up again. + */ + mtk_plane_state->pending.cpp = fb->format->cpp[0]; mtk_plane_state->pending.hdr_pitch = hdr_pitch; mtk_plane_state->pending.format = format; mtk_plane_state->pending.modifier = modifier; diff --git a/drivers/gpu/drm/mediatek/mtk_plane.h b/drivers/gpu/drm/mediatek/mtk_plane.h index 95c5fa5295d8ac..e53999a36708ad 100644 --- a/drivers/gpu/drm/mediatek/mtk_plane.h +++ b/drivers/gpu/drm/mediatek/mtk_plane.h @@ -20,9 +20,10 @@ struct mtk_plane_pending_state { bool enable; dma_addr_t addr; dma_addr_t hdr_addr; - unsigned int pitch; + unsigned int pitch; /* bytes per line */ unsigned int hdr_pitch; unsigned int format; + unsigned int cpp; /* bytes per pixel */ unsigned long long modifier; unsigned int x; unsigned int y; @@ -40,6 +41,9 @@ struct mtk_plane_state { struct mtk_plane_pending_state pending; }; +void mtk_plane_update_new_state(struct drm_plane_state *new_state, + struct mtk_plane_state *mtk_plane_state); + static inline struct mtk_plane_state * to_mtk_plane_state(struct drm_plane_state *state) { diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index d592f4f4b939a9..b05ebfbd88251e 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -1269,6 +1269,18 @@ config DRM_PANEL_VISIONOX_RM692E5 display panels, such as the one found in the Nothing Phone (1) smartphone. +config DRM_PANEL_LENOVO_B6000 + tristate "Lenovo YOGA Tablet 8 (B6000) panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here to enable support for the 800x1280 DSI panels fitted to + the Lenovo YOGA Tablet 8 (B6000): the Novatek NT35521 + (nt35521_cmi_080) and the Samsung S6D7AA0 (s6d7aa0x04_cpt_080). + Both need a manufacturer init sequence, so they are not handled by + panel-simple. + config DRM_PANEL_VISIONOX_VTDR6130 tristate "Visionox VTDR6130" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index a4291dc3905bed..cbcc334ce68add 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -124,6 +124,7 @@ obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o obj-$(CONFIG_DRM_PANEL_VISIONOX_G2647FB105) += panel-visionox-g2647fb105.o obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o obj-$(CONFIG_DRM_PANEL_VISIONOX_RM692E5) += panel-visionox-rm692e5.o +obj-$(CONFIG_DRM_PANEL_LENOVO_B6000) += panel-lenovo-b6000.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_WIDECHIPS_WS2401) += panel-widechips-ws2401.o diff --git a/drivers/gpu/drm/panel/panel-lenovo-b6000-seq.h b/drivers/gpu/drm/panel/panel-lenovo-b6000-seq.h new file mode 100644 index 00000000000000..2bb4be6f9c0572 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-lenovo-b6000-seq.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Init sequences for the Lenovo B6000 panels. + * + * Decoded from kernel_blade8_row_3gdata-jb.elf; see panel-lenovo-b6000.c for + * the word-packing derivation and the cross-checks that validate it. + */ +#ifndef _PANEL_LENOVO_B6000_SEQ_H +#define _PANEL_LENOVO_B6000_SEQ_H + +#include + +/* + * One entry of a panel init sequence. A delay entry has delay_ms set and no + * command; otherwise cmd/len/data describe a DCS write. + */ +struct b6000_init_cmd { + u8 cmd; + u8 len; + const char *data; + u16 delay_ms; +}; + +static const struct b6000_init_cmd b6000_cmi_init[] = { + { .cmd = 0xff, .len = 4, .data = "\xaa\x55\xa5\x80" }, + { .cmd = 0x6f, .len = 1, .data = "\x07" }, + { .cmd = 0xf7, .len = 1, .data = "\x50" }, + { .cmd = 0x6f, .len = 2, .data = "\x11\x00" }, + { .cmd = 0xf7, .len = 2, .data = "\x20\x00" }, + { .cmd = 0x6f, .len = 1, .data = "\x06" }, + { .cmd = 0xf7, .len = 1, .data = "\xa0" }, + { .cmd = 0x6f, .len = 1, .data = "\x19" }, + { .cmd = 0xf7, .len = 1, .data = "\x12" }, + { .cmd = 0x6f, .len = 1, .data = "\x08" }, + { .cmd = 0xfa, .len = 1, .data = "\x40" }, + { .cmd = 0x6f, .len = 1, .data = "\x11" }, + { .cmd = 0xf3, .len = 1, .data = "\x01" }, + { .cmd = 0xf0, .len = 5, .data = "\x55\xaa\x52\x08\x00" }, + { .cmd = 0xc8, .len = 1, .data = "\x00" }, + { .cmd = 0xb1, .len = 2, .data = "\x68\x01" }, + { .cmd = 0xb6, .len = 1, .data = "\x08" }, + { .cmd = 0x6f, .len = 1, .data = "\x02" }, + { .cmd = 0xb8, .len = 1, .data = "\x0c" }, + { .cmd = 0xbb, .len = 2, .data = "\x74\x44" }, + { .cmd = 0xbc, .len = 2, .data = "\x00\x00" }, + { .cmd = 0xbd, .len = 5, .data = "\x02\xb0\x0c\x0a\x00" }, + { .cmd = 0xf0, .len = 5, .data = "\x55\xaa\x52\x08\x01" }, + { .cmd = 0xb0, .len = 2, .data = "\x05\x05" }, + { .cmd = 0xb1, .len = 2, .data = "\x05\x05" }, + { .cmd = 0xbc, .len = 2, .data = "\x90\x01" }, + { .cmd = 0xbd, .len = 2, .data = "\x90\x01" }, + { .cmd = 0xca, .len = 1, .data = "\x00" }, + { .cmd = 0xc0, .len = 1, .data = "\x04" }, + { .cmd = 0xbe, .len = 1, .data = "\x29" }, + { .cmd = 0xb3, .len = 2, .data = "\x37\x37" }, + { .cmd = 0xb4, .len = 2, .data = "\x19\x19" }, + { .cmd = 0xb9, .len = 2, .data = "\x46\x46" }, + { .cmd = 0xba, .len = 2, .data = "\x24\x24" }, + { .cmd = 0xf0, .len = 5, .data = "\x55\xaa\x52\x08\x02" }, + { .cmd = 0xee, .len = 1, .data = "\x01" }, + { .cmd = 0xef, .len = 4, .data = "\x09\x06\x15\x18" }, + { .cmd = 0x24, .len = 6, .data = "\x00\x55\x00\xb0\x00\x00" }, + { .cmd = 0x6f, .len = 1, .data = "\x06" }, + { .cmd = 0x94, .len = 6, .data = "\x00\xc0\x00\xb0\x00\x77" }, + { .cmd = 0x6f, .len = 1, .data = "\x0c" }, + { .cmd = 0xb0, .len = 4, .data = "\x00\xe3\x01\x1a" }, + { .cmd = 0x88, .len = 6, .data = "\x01\xbc\x00\xb1\x01\x46" }, + { .cmd = 0x6f, .len = 1, .data = "\x06" }, + { .cmd = 0x4b, .len = 6, .data = "\x02\x4d\x00\xb1\x02\x0b" }, + { .cmd = 0x6f, .len = 1, .data = "\x0c" }, + { .cmd = 0xb1, .len = 4, .data = "\x02\x88\x02\xc9" }, + { .cmd = 0x29, .len = 6, .data = "\x03\x4e\x00\xb2\x02\xf3" }, + { .cmd = 0x6f, .len = 1, .data = "\x06" }, + { .cmd = 0x9b, .len = 6, .data = "\x03\xbe\x00\xb2\x03\x7d" }, + { .cmd = 0x6f, .len = 1, .data = "\x0c" }, + { .cmd = 0xb2, .len = 4, .data = "\x03\xd3\x03\xe9" }, + { .cmd = 0xb3, .len = 4, .data = "\x03\xfb\x03\xff" }, + { .cmd = 0xf0, .len = 5, .data = "\x55\xaa\x52\x08\x06" }, + { .cmd = 0xb0, .len = 2, .data = "\x00\x10" }, + { .cmd = 0xb1, .len = 2, .data = "\x12\x14" }, + { .cmd = 0xb2, .len = 2, .data = "\x16\x18" }, + { .cmd = 0xb3, .len = 2, .data = "\x1a\x29" }, + { .cmd = 0xb4, .len = 2, .data = "\x2a\x08" }, + { .cmd = 0xb5, .len = 2, .data = "\x31\x31" }, + { .cmd = 0xb6, .len = 2, .data = "\x31\x31" }, + { .cmd = 0xb7, .len = 2, .data = "\x31\x31" }, + { .cmd = 0xb8, .len = 2, .data = "\x31\x0a" }, + { .cmd = 0xb9, .len = 2, .data = "\x31\x31" }, + { .cmd = 0xba, .len = 2, .data = "\x31\x31" }, +}; +static const struct b6000_init_cmd b6000_cpt_init[] = { + { .delay_ms = 10 }, + { .delay_ms = 10 }, + { .delay_ms = 120 }, + { .cmd = 0xf0, .len = 2, .data = "\x5a\x5a" }, + { .cmd = 0xf1, .len = 2, .data = "\x5a\x5a" }, + { .cmd = 0xfc, .len = 2, .data = "\xa5\xa5" }, + { .cmd = 0xd0, .len = 2, .data = "\x00\x10" }, + { .cmd = 0xb1, .len = 1, .data = "\x10" }, + { .cmd = 0xb2, .len = 4, .data = "\x14\x22\x2f\x04" }, + { .cmd = 0xb5, .len = 1, .data = "\x01" }, + { .cmd = 0xee, .len = 8, .data = "\x00\x00\x1f\x00\x00\x00\x1f\x00" }, + { .cmd = 0xef, .len = 13, .data = "\x56\x34\x43\x65\x90\x80\x24\x80\x00\x91\x11\x11\x11" }, + { .cmd = 0xf7, .len = 32, .data = "\x04\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x16\x17\x10\x01\x01\x01\x01\x04\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x16\x17\x10\x01\x01\x01\x01" }, + { .cmd = 0xf2, .len = 5, .data = "\x02\x08\x08\x40\x10" }, + { .cmd = 0xf6, .len = 6, .data = "\x60\x25\x26\x00\x00\x00" }, + { .cmd = 0xfa, .len = 17, .data = "\x04\x35\x07\x0b\x12\x0b\x10\x16\x1a\x24\x2c\x33\x3b\x3b\x33\x34\x33" }, + { .cmd = 0xfb, .len = 17, .data = "\x04\x35\x07\x0b\x12\x0b\x10\x16\x1a\x24\x2c\x33\x3b\x3b\x33\x34\x33" }, + { .cmd = 0xf3, .len = 10, .data = "\x01\xc4\xe0\x62\xd4\x83\x37\x3c\x24\x00" }, + { .cmd = 0xf4, .len = 35, .data = "\x00\x02\x03\x26\x03\x02\x09\x00\x07\x16\x16\x03\x00\x08\x08\x03\x19\x1c\x12\x1c\x1d\x1e\x1a\x09\x01\x04\x02\x61\x74\x75\x72\x83\x80\x80\xf0" }, + { .cmd = 0xb0, .len = 1, .data = "\x01" }, + { .cmd = 0xf5, .len = 19, .data = "\x2f\x2f\x5f\xab\x98\x52\x0f\x33\x43\x04\x59\x54\x52\x05\x40\x40\x5d\x59\x40" }, + { .cmd = 0xbc, .len = 3, .data = "\x01\x4e\x08" }, + { .cmd = 0xe1, .len = 5, .data = "\x03\x10\x1c\xa0\x10" }, + { .cmd = 0xfd, .len = 5, .data = "\x16\x10\x11\x20\x09" }, + { .delay_ms = 120 }, +}; +#endif /* _PANEL_LENOVO_B6000_SEQ_H */ diff --git a/drivers/gpu/drm/panel/panel-lenovo-b6000.c b/drivers/gpu/drm/panel/panel-lenovo-b6000.c new file mode 100644 index 00000000000000..8fc97a63167417 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-lenovo-b6000.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Lenovo YOGA Tablet 8 (B6000) DSI panel driver. + * + * The B6000 ships with one of two panels, chosen at build time by the vendor + * and distinguished at runtime by a board ID strap: + * + * nt35521_cmi_080 Novatek NT35521, 800x1280 + * s6d7aa0x04_cpt_080 Samsung S6D7AA0, 800x1280 + * + * Neither is a "simple panel": both need a long manufacturer-command init + * sequence before they will display anything, which is why they cannot live + * in panel-simple.c. + * + * SOURCE OF THE INIT SEQUENCES + * + * Both sequences are decoded from the vendor kernel's own LCM drivers in + * kernel_blade8_row_3gdata-jb.elf (decompiled in Ghidra): + * + * nt35521_cmi_080 lcm_init_2() @ c03a0f98 + * lcm_compare_id_2() @ c03a25a8 + * lcm_get_params_2() @ c03a0e10 + * s6d7aa0x04_cpt_080 lcm_init_3() @ c03a2f64 + * init_cptlcm_registers() @ c03a26b0 + * + * The vendor pushes commands with dsi_set_cmdq(), which takes an array of + * 32-bit words rather than a command/data pair. The packing was recovered + * from DSI_set_cmdq_V2 @ c04193fc in the same image and then validated by + * round-tripping a real vendor table row; it is byte-oriented: + * + * bytes : 02 39 00 ... long write + * 02 15 00 00 00 00 short write, 1 param + * 02 05 00 short write, no param + * + * so the extracted commands below are ordinary DCS writes and are issued + * here through mipi_dsi_dcs_write(). + * + * CROSS-CHECK + * + * The NT35521 sequence was independently confirmed against the public + * nt35521 driver in DoomFlex/Kernel_6737m + * (drivers/misc/mediatek/lcm/nt35521_cmi55_rixin_hd): its page-unlock + * entries {0xF0,5,{0x55,0xAA,0x52,0x08,0x00}} match what this decoder + * produces byte for byte. The S6D7AA0 register payloads likewise match + * carlitros900/CUBE-kernel-U1005 (c805_hx_s6d7aa0x04_cpt_wxga_ips_8), which + * carries the same dsi_set_cmdq word stream. + * + * That CUBE driver is a *different* 800x1280 s6d7aa0x04 variant with + * different timings (four lanes, PLL 200, vsync 5/back 3/front 8), so only + * its register payloads were used; the timings below come from this board's + * own lcm_get_params_2()/lcm_get_params_3(). + * + * WHAT IS NOT KNOWN + * + * The vendor's lcd_power_en()/lcd_reset() helpers are static and were not + * recovered, so the regulator/GPIO ordering is modelled on the Devicetree + * (power-supply + reset-gpios) rather than copied. The panel ID strap is + * GPIO 0x9a (154) read as an input: lcm_compare_id_2() returns true when it + * reads 1. That GPIO is optional here - if the board does not describe it, + * the panel named by the compatible string is used as-is. + */ +#include +#include +#include +#include +#include +#include + +#include