diff --git a/Documentation/admin-guide/media/amdisp4-1.rst b/Documentation/admin-guide/media/amdisp4-1.rst new file mode 100644 index 00000000000000..878141154f962f --- /dev/null +++ b/Documentation/admin-guide/media/amdisp4-1.rst @@ -0,0 +1,63 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: + +==================================== +AMD Image Signal Processor (amdisp4) +==================================== + +Introduction +============ + +This file documents the driver for the AMD ISP4 that is part of +AMD Ryzen AI Max 300 Series. + +The driver is located under drivers/media/platform/amd/isp4 and uses +the Media-Controller API. + +The driver exposes one video capture device to userspace and provide +web camera like interface. Internally the video device is connected +to the isp4 sub-device responsible for communication with the CCPU FW. + +Topology +======== + +.. _amdisp4_topology_graph: + +.. kernel-figure:: amdisp4.dot + :alt: Diagram of the media pipeline topology + :align: center + + + +The driver has 1 sub-device: Representing isp4 image signal processor. +The driver has 1 video device: Capture device for retrieving images. + +- ISP4 Image Signal Processing Subdevice Node + +--------------------------------------------- + +The isp4 is represented as a single V4L2 subdev, the sub-device does not +provide interface to the user space. The sub-device is connected to one video node +(isp4_capture) with immutable active link. The sub-device represents ISP with +connected sensor similar to smart cameras (sensors with integrated ISP). +sub-device has only one link to the video device for capturing the frames. +The sub-device communicates with CCPU FW for streaming configuration and +buffer management. + + +- isp4_capture - Frames Capture Video Node + +------------------------------------------ + +Isp4_capture is a capture device to capture frames to memory. +The entity is connected to isp4 sub-device. The video device +provides web camera like interface to userspace. It supports +mmap and dma buf types of memory. + +Capturing Video Frames Example +============================== + +.. code-block:: bash + + v4l2-ctl "-d" "/dev/video0" "--set-fmt-video=width=1920,height=1080,pixelformat=NV12" "--stream-mmap" "--stream-count=10" diff --git a/Documentation/admin-guide/media/amdisp4.dot b/Documentation/admin-guide/media/amdisp4.dot new file mode 100644 index 00000000000000..978f30c1a31a4c --- /dev/null +++ b/Documentation/admin-guide/media/amdisp4.dot @@ -0,0 +1,6 @@ +digraph board { + rankdir=TB + n00000001 [label="{{} | amd isp4\n | { 0}}", shape=Mrecord, style=filled, fillcolor=green] + n00000001:port0 -> n00000003 [style=bold] + n00000003 [label="Preview\n/dev/video0", shape=box, style=filled, fillcolor=yellow] +} diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst index d31da8e0a54f27..4621eae9fa1edf 100644 --- a/Documentation/admin-guide/media/v4l-drivers.rst +++ b/Documentation/admin-guide/media/v4l-drivers.rst @@ -9,6 +9,7 @@ Video4Linux (V4L) driver-specific documentation .. toctree:: :maxdepth: 2 + amdisp4-1 bttv c3-isp cafe_ccic diff --git a/Documentation/devicetree/bindings/clock/thead,th1520-clk-ap.yaml b/Documentation/devicetree/bindings/clock/thead,th1520-clk-ap.yaml index 9d058c00ab3d59..d46d13597466fe 100644 --- a/Documentation/devicetree/bindings/clock/thead,th1520-clk-ap.yaml +++ b/Documentation/devicetree/bindings/clock/thead,th1520-clk-ap.yaml @@ -23,6 +23,7 @@ properties: compatible: enum: - thead,th1520-clk-ap + - thead,th1520-clk-misc - thead,th1520-clk-vo reg: @@ -32,8 +33,8 @@ properties: items: - description: | One input clock: - - For "thead,th1520-clk-ap": the clock input must be the 24 MHz - main oscillator. + - For "thead,th1520-clk-ap" and "thead,th1520-clk-misc": the clock + input must be the 24 MHz main oscillator. - For "thead,th1520-clk-vo": the clock input must be the VIDEO_PLL, which is configured by the AP clock controller. According to the TH1520 manual, VIDEO_PLL is a Silicon Creations Sigma-Delta PLL diff --git a/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml b/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml index ec06235baf5ca3..62ce6d81526b44 100644 --- a/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml +++ b/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml @@ -14,7 +14,9 @@ allOf: properties: compatible: - const: spacemit,k1-pdma + enum: + - spacemit,k1-pdma + - spacemit,k3-pdma reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml b/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml index bba6f5b6606fdf..55069533f6d912 100644 --- a/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml @@ -95,6 +95,12 @@ patternProperties: '#interrupt-cells': const: 2 + patternProperties: + "^.+-hog(-[0-9]+)?$": + type: object + required: + - gpio-hog + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml b/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml index 67882ec6e06afc..0beb7f2515c8cb 100644 --- a/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml @@ -16,6 +16,7 @@ properties: compatible: enum: - loongson,ls2k-i2c + - loongson,ls2k0300-i2c - loongson,ls7a-i2c reg: @@ -24,6 +25,9 @@ properties: interrupts: maxItems: 1 + clocks: + maxItems: 1 + required: - compatible - reg @@ -33,11 +37,14 @@ unevaluatedProperties: false examples: - | + #include #include i2c0: i2c@1fe21000 { compatible = "loongson,ls2k-i2c"; reg = <0x1fe21000 0x8>; + clock-frequency = <100000>; + clocks = <&clk LOONGSON2_APB_CLK>; interrupt-parent = <&extioiic>; interrupts = <22 IRQ_TYPE_LEVEL_LOW>; #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/mmc/spacemit,sdhci.yaml b/Documentation/devicetree/bindings/mmc/spacemit,sdhci.yaml index 9a055d963a7f0c..ce1edb11893471 100644 --- a/Documentation/devicetree/bindings/mmc/spacemit,sdhci.yaml +++ b/Documentation/devicetree/bindings/mmc/spacemit,sdhci.yaml @@ -44,12 +44,26 @@ properties: - const: axi - const: sdh + pinctrl-names: + minItems: 1 + items: + - const: default + - const: uhs + + pinctrl-0: + description: Default pinctrl state for 3.3V operation + + pinctrl-1: + description: Optional pinctrl state for 1.8V UHS operation with "uhs" name + required: - compatible - reg - interrupts - clocks - clock-names + - resets + - reset-names unevaluatedProperties: false @@ -62,4 +76,9 @@ examples: interrupt-parent = <&plic>; clocks = <&clk_apmu 10>, <&clk_apmu 13>; clock-names = "core", "io"; + pinctrl-names = "default", "uhs"; + pinctrl-0 = <&sdhci_default_cfg>; + pinctrl-1 = <&sdhci_uhs_cfg>; + resets = <&syscon_apmu 2>, <&syscon_apmu 5>; + reset-names = "axi", "sdh"; }; diff --git a/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml b/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml index b3216141881c9c..91bbbc8924f608 100644 --- a/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml @@ -27,8 +27,11 @@ allOf: - $ref: /schemas/pci/snps,dw-pcie-common.yaml# - if: not: - required: - - msi-map + anyOf: + - required: + - msi-map + - required: + - msi-parent then: properties: interrupt-names: diff --git a/Documentation/devicetree/bindings/pci/sophgo,sg2042-pcie-host.yaml b/Documentation/devicetree/bindings/pci/sophgo,sg2042-pcie-host.yaml index f8b7ca57fff14c..ab482488b04753 100644 --- a/Documentation/devicetree/bindings/pci/sophgo,sg2042-pcie-host.yaml +++ b/Documentation/devicetree/bindings/pci/sophgo,sg2042-pcie-host.yaml @@ -30,6 +30,8 @@ properties: device-id: const: 0x2042 + dma-coherent: true + msi-parent: true allOf: @@ -60,5 +62,6 @@ examples: vendor-id = <0x1f1c>; device-id = <0x2042>; cdns,no-bar-match-nbits = <48>; + dma-coherent; msi-parent = <&msi>; }; diff --git a/Documentation/devicetree/bindings/pci/spacemit,k1-pcie-host.yaml b/Documentation/devicetree/bindings/pci/spacemit,k1-pcie-host.yaml index c4c00b5fcdc0c4..54817d6fd9afe0 100644 --- a/Documentation/devicetree/bindings/pci/spacemit,k1-pcie-host.yaml +++ b/Documentation/devicetree/bindings/pci/spacemit,k1-pcie-host.yaml @@ -14,26 +14,29 @@ description: > PCIe IP. The controller uses the DesignWare built-in MSI interrupt controller, and supports 256 MSIs. -allOf: - - $ref: /schemas/pci/snps,dw-pcie.yaml# - properties: compatible: - const: spacemit,k1-pcie + enum: + - spacemit,k1-pcie + - spacemit,k3-pcie reg: + minItems: 4 items: - description: DesignWare PCIe registers - description: ATU address space - description: PCIe configuration space - description: Link control registers + - description: Data Bus Interface (DBI) shadow registers. reg-names: + minItems: 4 items: - const: dbi - const: atu - const: config - const: link + - const: dbi2 clocks: items: @@ -66,6 +69,8 @@ properties: interrupt-names: const: msi + msi-parent: true + spacemit,apmu: $ref: /schemas/types.yaml#/definitions/phandle-array description: @@ -84,7 +89,8 @@ patternProperties: properties: phys: - maxItems: 1 + minItems: 1 + maxItems: 6 vpcie3v3-supply: description: @@ -96,13 +102,43 @@ patternProperties: unevaluatedProperties: false +allOf: + - $ref: /schemas/pci/snps,dw-pcie.yaml# + - if: + properties: + compatible: + contains: + const: spacemit,k1-pcie + then: + properties: + reg: + maxItems: 4 + + reg-names: + maxItems: 4 + + patternProperties: + '^pcie@': + properties: + phys: + maxItems: 1 + + required: + - interrupts + - interrupt-names + else: + properties: + reg: + minItems: 5 + + reg-names: + minItems: 5 + required: - clocks - clock-names - resets - reset-names - - interrupts - - interrupt-names - spacemit,apmu unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/phy/spacemit,k3-combo-phy.yaml b/Documentation/devicetree/bindings/phy/spacemit,k3-combo-phy.yaml new file mode 100644 index 00000000000000..ed820cf697b822 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/spacemit,k3-combo-phy.yaml @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/spacemit,k3-combo-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Spacemit K3 PCIE/USB3 Comb PHY + +maintainers: + - Inochi Amaoto + +properties: + compatible: + const: spacemit,k3-combo-phy + + reg: + maxItems: 1 + + "#phy-cells": + const: 2 + description: + The first one is phy id, the second one is phy type. This + only supports types are PCIe and USB . + + spacemit,apb-spare: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle to APB SPARE system controller interface, used for + PHY calibration. + + spacemit,apmu: + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - items: + - description: phandle of APMU syscon + - description: configuration of the PHY lanes + description: | + Phandle to control PHY mux configuration. The configuration + is described as follows: + bit 4: 0 - PCIe A x8 mode, 1 - PCIe lane share mode + bit 3: 0 - PCIe A x4 mode, 1 - PCIe A x2 and PCIe B x2 mode + bit 2: 0 - PCIe C lane 0 is PCIe mode , 1 - USB mode + bit 1: 0 - PCIe C lane 1 is PCIe mode , 1 - USB mode + bit 0: 0 - PCIe D lane is PCIe mode , 1 - USB mode + + The bit[3:0] is only valid when bit 4 is 1. + + This mux control does not affect the PHY for PCIe E, it is always + in PCIe mode. + +required: + - compatible + - reg + - "#phy-cells" + - spacemit,apb-spare + - spacemit,apmu + +additionalProperties: false + +examples: + - | + phy@81d00000 { + compatible = "spacemit,k3-combo-phy"; + reg = <0x81d00000 0x600000>; + #phy-cells = <2>; + spacemit,apb-spare = <&apb_spare>; + spacemit,apmu = <&apmu 0x00>; + }; diff --git a/Documentation/devicetree/bindings/phy/spacemit,usb2-phy.yaml b/Documentation/devicetree/bindings/phy/spacemit,usb2-phy.yaml index 43eaca90d88c02..18025e5f60d691 100644 --- a/Documentation/devicetree/bindings/phy/spacemit,usb2-phy.yaml +++ b/Documentation/devicetree/bindings/phy/spacemit,usb2-phy.yaml @@ -4,14 +4,16 @@ $id: http://devicetree.org/schemas/phy/spacemit,usb2-phy.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: SpacemiT K1 SoC USB 2.0 PHY +title: SpacemiT K1/K3 SoC USB 2.0 PHY maintainers: - Ze Huang properties: compatible: - const: spacemit,k1-usb2-phy + enum: + - spacemit,k1-usb2-phy + - spacemit,k3-usb2-phy reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/phy/thead,th1520-usb-phy.yaml b/Documentation/devicetree/bindings/phy/thead,th1520-usb-phy.yaml new file mode 100644 index 00000000000000..37f5cfb95bad01 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/thead,th1520-usb-phy.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/thead,th1520-usb-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: T-Head TH1520 USB PHY + +description: | + The T-HEAD TH1520 USB PHY is a Synopsys USB 3.0 FemtoPHY glued with some + custom logic to configure PHY parameters. + +maintainers: + - Icenowy Zheng + - Wei Fu + - Drew Fustini + +properties: + compatible: + const: thead,th1520-usb-phy + + reg: + maxItems: 1 + + "#phy-cells": + const: 0 + + clocks: + items: + - description: PHY bus clock + - description: PHY reference clock + + clock-names: + items: + - const: bus + - const: ref + + resets: + items: + - description: PHY bus reset + - description: PHY reset + + reset-names: + items: + - const: bus + - const: phy + + avdd33-usb3-supply: + description: | + 3.3V power supply for the PHY, named AVDD33_USB3 in the SoC pin list. + +required: + - compatible + - "#phy-cells" + - clocks + - clock-names + - resets + - reset-names + - avdd33-usb3-supply + +additionalProperties: false + +examples: + - | + phy@ec030000 { + compatible = "thead,th1520-usb-phy"; + reg = <0xec030000 0x10000>; + #phy-cells = <0>; + clocks = <&clk_misc 1>, <&clk_misc 3>; + clock-names = "bus", "ref"; + resets = <&rst_misc 6>, <&rst_misc 7>; + reset-names = "bus", "phy"; + avdd33-usb3-supply = <&avdd33_usb3>; + }; diff --git a/Documentation/devicetree/bindings/pinctrl/mediatek,mt8188-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/mediatek,mt8188-pinctrl.yaml index e994b0c70dbfd2..1cf06e46f7bb0f 100644 --- a/Documentation/devicetree/bindings/pinctrl/mediatek,mt8188-pinctrl.yaml +++ b/Documentation/devicetree/bindings/pinctrl/mediatek,mt8188-pinctrl.yaml @@ -67,6 +67,11 @@ properties: # PIN CONFIGURATION NODES patternProperties: + "-hog(-[0-9]+)?$": + type: object + required: + - gpio-hog + '-pins$': type: object additionalProperties: false diff --git a/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml b/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml index 8df327e5281075..f1422a401b6ba7 100644 --- a/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml @@ -15,7 +15,9 @@ allOf: properties: compatible: contains: - const: spacemit,k1-pwm + enum: + - spacemit,k1-pwm + - spacemit,k3-pwm then: properties: "#pwm-cells": @@ -26,6 +28,26 @@ allOf: const: 1 description: | Used for specifying the period length in nanoseconds. + - if: + properties: + compatible: + contains: + enum: + - spacemit,k3-pwm + then: + required: + - clock-names + properties: + clocks: + minItems: 2 + clock-names: + minItems: 2 + else: + properties: + clocks: + maxItems: 1 + clock-names: + maxItems: 1 properties: compatible: @@ -36,7 +58,9 @@ properties: - marvell,pxa168-pwm - marvell,pxa910-pwm - items: - - const: spacemit,k1-pwm + - enum: + - spacemit,k1-pwm + - spacemit,k3-pwm - const: marvell,pxa910-pwm reg: @@ -47,7 +71,18 @@ properties: description: Number of cells in a pwm specifier. clocks: - maxItems: 1 + minItems: 1 + items: + - description: The function clock + - description: An optional bus clock + + clock-names: + minItems: 1 + maxItems: 2 + oneOf: + - items: + - const: func + - const: bus resets: maxItems: 1 diff --git a/Documentation/devicetree/bindings/riscv/extensions.yaml b/Documentation/devicetree/bindings/riscv/extensions.yaml index 2b0a8a93bb2144..b09888e9988de6 100644 --- a/Documentation/devicetree/bindings/riscv/extensions.yaml +++ b/Documentation/devicetree/bindings/riscv/extensions.yaml @@ -294,10 +294,10 @@ properties: of the PTE A/D bits or page faults when they need updated. 2) Only Svade present in DT => Supervisor must assume Svade to be always enabled. - 3) Only Svadu present in DT => Supervisor must assume Svadu to be - always enabled. + 3) Only Svadu present in DT => Supervisor must assume Svadu is + enabled at boot. 4) Both Svade and Svadu present in DT => Supervisor must assume - Svadu turned-off at boot time. To use Svadu, supervisor must + Svadu is disabled at boot time. To use Svadu, supervisor must explicitly enable it using the SBI FWFT extension. - const: svadu diff --git a/Documentation/devicetree/bindings/riscv/spacemit.yaml b/Documentation/devicetree/bindings/riscv/spacemit.yaml index b958b94a924dee..311bbccba0baa2 100644 --- a/Documentation/devicetree/bindings/riscv/spacemit.yaml +++ b/Documentation/devicetree/bindings/riscv/spacemit.yaml @@ -29,8 +29,14 @@ properties: - const: spacemit,k1 - items: - enum: + - deepcomputing,fml13v05 - spacemit,k3-pico-itx - const: spacemit,k3 + - items: + - enum: + - spacemit,k3-com260-ifx + - const: spacemit,k3-com260 + - const: spacemit,k3 additionalProperties: true diff --git a/Documentation/devicetree/bindings/sound/spacemit,k1-i2s.yaml b/Documentation/devicetree/bindings/sound/spacemit,k1-i2s.yaml index 55bd0b307d22b3..240d90402e4f95 100644 --- a/Documentation/devicetree/bindings/sound/spacemit,k1-i2s.yaml +++ b/Documentation/devicetree/bindings/sound/spacemit,k1-i2s.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/sound/spacemit,k1-i2s.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: K1 I2S controller +title: SpacemiT K1/K3 I2S controller description: The I2S bus (Inter-IC sound bus) is a serial link for digital @@ -15,27 +15,54 @@ maintainers: allOf: - $ref: dai-common.yaml# + - if: + properties: + compatible: + contains: + const: spacemit,k3-i2s + then: + properties: + clocks: + minItems: 7 + clock-names: + minItems: 7 + else: + properties: + clocks: + maxItems: 4 + clock-names: + maxItems: 4 properties: compatible: - const: spacemit,k1-i2s + enum: + - spacemit,k1-i2s + - spacemit,k3-i2s reg: maxItems: 1 clocks: + minItems: 4 items: - description: clock for I2S sysclk - description: clock for I2S bclk - description: clock for I2S bus - description: clock for I2S controller + - description: clock for I2S sysclk divider + - description: clock for I2S common sysclk + - description: clock for I2S common bclk clock-names: + minItems: 4 items: - const: sysclk - const: bclk - const: bus - const: func + - const: sysclk_div + - const: c_sysclk + - const: c_bclk dmas: minItems: 1 diff --git a/Documentation/devicetree/bindings/spi/spacemit,k1-spi.yaml b/Documentation/devicetree/bindings/spi/spacemit,k1-spi.yaml new file mode 100644 index 00000000000000..e82c7f8d0b981f --- /dev/null +++ b/Documentation/devicetree/bindings/spi/spacemit,k1-spi.yaml @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/spacemit,k1-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SpacemiT K1 SoC Serial Peripheral Interface (SPI) + +maintainers: + - Alex Elder + +description: + The SpacemiT K1 SoC implements a SPI controller that has two 32-entry + FIFOs, for transmit and receive. Details are currently available in + section 18.2.1 of the K1 User Manual, found in the SpacemiT Keystone + K1 Documentation[1]. The controller transfers words using PIO. DMA + transfers are supported as well, if both TX and RX DMA channels are + specified, + + [1] https://developer.spacemit.com/documentation + +allOf: + - $ref: /schemas/spi/spi-controller.yaml# + +properties: + compatible: + const: spacemit,k1-spi + + reg: + maxItems: 1 + + clocks: + items: + - description: Core clock + - description: Bus clock + + clock-names: + items: + - const: core + - const: bus + + resets: + maxItems: 1 + + interrupts: + maxItems: 1 + + dmas: + items: + - description: RX DMA channel + - description: TX DMA channel + + dma-names: + items: + - const: rx + - const: tx + +required: + - compatible + - reg + - clocks + - clock-names + - resets + - interrupts + +unevaluatedProperties: false + +examples: + - | + + #include + spi@d401c000 { + compatible = "spacemit,k1-spi"; + reg = <0xd401c000 0x30>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&syscon_apbc CLK_SSP3>, + <&syscon_apbc CLK_SSP3_BUS>; + clock-names = "core", "bus"; + resets = <&syscon_apbc RESET_SSP3>; + interrupts = <55>; + dmas = <&pdma 20>, <&pdma 19>; + dma-names = "rx", "tx"; + }; diff --git a/Documentation/devicetree/bindings/thermal/spacemit,k1-tsensor.yaml b/Documentation/devicetree/bindings/thermal/spacemit,k1-tsensor.yaml new file mode 100644 index 00000000000000..6dad76a7dd3613 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/spacemit,k1-tsensor.yaml @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/spacemit,k1-tsensor.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SpacemiT K1 Thermal Sensor + +description: + The SpacemiT K1 Thermal Sensor monitors the temperature of the SoC + using multiple internal sensors (e.g., soc, package, gpu, clusters). + +maintainers: + - Shuwei Wu + +$ref: thermal-sensor.yaml# + +properties: + compatible: + const: spacemit,k1-tsensor + + reg: + maxItems: 1 + + clocks: + items: + - description: Core clock for thermal sensor + - description: Bus clock for thermal sensor + + clock-names: + items: + - const: core + - const: bus + + interrupts: + maxItems: 1 + + resets: + items: + - description: Reset for the thermal sensor + + "#thermal-sensor-cells": + const: 1 + description: + The first cell indicates the sensor ID. + 0 = soc + 1 = package + 2 = gpu + 3 = cluster0 + 4 = cluster1 + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - resets + - "#thermal-sensor-cells" + +additionalProperties: false + +examples: + - | + #include + + thermal@d4018000 { + compatible = "spacemit,k1-tsensor"; + reg = <0xd4018000 0x100>; + clocks = <&syscon_apbc CLK_TSEN>, + <&syscon_apbc CLK_TSEN_BUS>; + clock-names = "core", "bus"; + interrupts = <61>; + resets = <&syscon_apbc RESET_TSEN>; + #thermal-sensor-cells = <1>; + }; diff --git a/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml b/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml new file mode 100644 index 00000000000000..e6cb6fb2496c85 --- /dev/null +++ b/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/ufs/spacemit,k3-ufshc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SpacemiT K3 SoC UFS Host Controller + +maintainers: + - Yixun Lan + +properties: + compatible: + const: spacemit,k3-ufshc + + reg: + maxItems: 1 + + clocks: + maxItems: 2 + + clock-names: + items: + - const: aclk + - const: ref_clk + + resets: + maxItems: 1 + +required: + - reg + - clocks + - clock-names + - resets + +allOf: + - $ref: ufs-common.yaml + +unevaluatedProperties: false + +examples: + - | + #include + #include + ufs@c0e00000 { + compatible = "spacemit,k3-ufshc"; + reg = <0xc0e00000 0x40000>; + clocks = <&syscon_apmu CLK_APMU_UFS_ACLK>, + <&syscon_apmu CLK_APMU_UFS_REFCLK>; + clock-names = "aclk", "ref_clk"; + resets = <&syscon_apmu RESET_APMU_UFS_ACLK>; + freq-table-hz = <491520000 491520000 19200000 19200000>; + lanes-per-direction = <2>; + }; diff --git a/Documentation/devicetree/bindings/usb/vialab,vl817.yaml b/Documentation/devicetree/bindings/usb/vialab,vl817.yaml index c815010ba9c2e8..7387f4fae54d1e 100644 --- a/Documentation/devicetree/bindings/usb/vialab,vl817.yaml +++ b/Documentation/devicetree/bindings/usb/vialab,vl817.yaml @@ -10,7 +10,7 @@ maintainers: - Anand Moon allOf: - - $ref: usb-device.yaml# + - $ref: usb-hub.yaml# properties: compatible: @@ -34,6 +34,8 @@ properties: description: phandle to the peer hub on the controller. + ports: true + required: - compatible - reg diff --git a/Documentation/rust/arch-support.rst b/Documentation/rust/arch-support.rst index 6e6a515d08991a..5282e0e174e8de 100644 --- a/Documentation/rust/arch-support.rst +++ b/Documentation/rust/arch-support.rst @@ -18,7 +18,7 @@ Architecture Level of support Constraints ``arm`` Maintained ARMv7 Little Endian only. ``arm64`` Maintained Little Endian only. ``loongarch`` Maintained \- -``riscv`` Maintained ``riscv64`` and LLVM/Clang only. +``riscv`` Maintained ``riscv64`` only. ``um`` Maintained \- ``x86`` Maintained ``x86_64`` only. ============= ================ ============================================== diff --git a/MAINTAINERS b/MAINTAINERS index f74da642290aa6..3a1f16a36f055b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -200,6 +200,18 @@ M: Linus Walleij F: Documentation/devicetree/bindings/power/supply/*ab8500* F: drivers/power/supply/*ab8500* +AAEON DEVICE DRIVER WITH WMI INTERFACE +M: Edward Lin +M: Kunyang Fan +M: Frank Hsieh +M: Jacob Wu +S: Supported +F: drivers/gpio/gpio-aaeon.c +F: drivers/hwmon/hwmon-aaeon.c +F: drivers/leds/leds-aaeon.c +F: drivers/mfd/mfd-aaeon.c +F: drivers/watchdog/wdt_aaeon.c + ABI/API L: linux-api@vger.kernel.org F: include/linux/syscalls.h @@ -1159,6 +1171,31 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git F: drivers/iommu/amd/ F: include/linux/amd-iommu.h +AMD ISP4 DRIVER +M: Bin Du +M: Nirujogi Pratap +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media.git +F: Documentation/admin-guide/media/amdisp4-1.rst +F: Documentation/admin-guide/media/amdisp4.dot +F: drivers/media/platform/amd/Kconfig +F: drivers/media/platform/amd/Makefile +F: drivers/media/platform/amd/isp4/Kconfig +F: drivers/media/platform/amd/isp4/Makefile +F: drivers/media/platform/amd/isp4/isp4.c +F: drivers/media/platform/amd/isp4/isp4.h +F: drivers/media/platform/amd/isp4/isp4_debug.c +F: drivers/media/platform/amd/isp4/isp4_debug.h +F: drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h +F: drivers/media/platform/amd/isp4/isp4_hw_reg.h +F: drivers/media/platform/amd/isp4/isp4_interface.c +F: drivers/media/platform/amd/isp4/isp4_interface.h +F: drivers/media/platform/amd/isp4/isp4_subdev.c +F: drivers/media/platform/amd/isp4/isp4_subdev.h +F: drivers/media/platform/amd/isp4/isp4_video.c +F: drivers/media/platform/amd/isp4/isp4_video.h + AMD KFD M: Felix Kuehling L: amd-gfx@lists.freedesktop.org @@ -15052,6 +15089,13 @@ F: arch/loongarch/ F: drivers/*/*loongarch* F: drivers/cpufreq/loongson3_cpufreq.c +LOONGSON CAN FD DRIVER +M: Bingxiong Li +M: Binbin Zhou +L: linux-can@vger.kernel.org +S: Maintained +F: drivers/net/can/loongson_canfd/ + LOONGSON GPIO DRIVER M: Yinbo Zhu L: linux-gpio@vger.kernel.org @@ -15073,6 +15117,7 @@ M: Binbin Zhou L: linux-i2c@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml +F: drivers/i2c/busses/i2c-ls2x-v2.c F: drivers/i2c/busses/i2c-ls2x.c LOONGSON PWM DRIVER @@ -21622,6 +21667,15 @@ F: include/sound/pxa2xx-lib.h F: sound/arm/pxa* F: sound/soc/pxa/ +ARM/PHYTIUM SOC SUPPORT +M: Wang Yinfeng +S: Maintained +W: https://www.phytium.com.cn +F: drivers/net/ethernet/Kconfig +F: drivers/net/ethernet/Makefile +F: drivers/net/ethernet/phytium/* +F: drivers/net/ethernet/stmicro/stmmac/dwmac-phytium.c + QAT DRIVER M: Giovanni Cabiddu L: qat-linux@intel.com diff --git a/Makefile b/Makefile index b3496dd21312c7..8b6fb4e6878013 100644 --- a/Makefile +++ b/Makefile @@ -734,9 +734,10 @@ ifneq ($(findstring clang,$(CC_VERSION_TEXT)),) include $(srctree)/scripts/Makefile.clang endif +ifdef need-compiler +include $(srctree)/scripts/Makefile.rust # Include this also for config targets because some architectures need # cc-cross-prefix to determine CROSS_COMPILE. -ifdef need-compiler include $(srctree)/scripts/Makefile.compiler endif diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 72c812e76b0b11..cff15501771701 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -318,6 +318,14 @@ config ARCH_PENSANDO cores for a minimal latency/jitter datapath, and network interfaces up to 200 Gb/s. +config ARCH_PHYTIUM + bool "Phytium SoC Family" + help + This enables support for Phytium ARMv8 SoC family, including: + - Phytium Server SoC Family + - Phytium Desktop SoC Family + - Phytium Embedded SoC Family + config ARCH_QCOM bool "Qualcomm Platforms" select GPIOLIB diff --git a/arch/arm64/boot/dts/mediatek/mt8188-geralt.dtsi b/arch/arm64/boot/dts/mediatek/mt8188-geralt.dtsi index 8e423504ec052c..57cd2deb882fe3 100644 --- a/arch/arm64/boot/dts/mediatek/mt8188-geralt.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8188-geralt.dtsi @@ -460,6 +460,18 @@ pinctrl-0 = <&i2c4_pins>; clock-frequency = <400000>; status = "okay"; + + /* The touchpad HID device is emulated by EC so it's always present */ + touchpad: touchpad@56 { + compatible = "hid-over-i2c"; + reg = <0x56>; + hid-descr-addr = <0x0001>; + interrupt-parent = <&pio>; + interrupts = <148 IRQ_TYPE_LEVEL_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&touchpad_int>; + wakeup-source; + }; }; &i2c5 { @@ -544,6 +556,11 @@ mediatek,mic-type-2 = <2>; /* DMIC */ }; +&mt6359_vcn18_ldo_reg { + /* Used by WLAN */ + regulator-always-on; +}; + &mt6359_vcore_buck_reg { regulator-always-on; }; @@ -1131,6 +1148,14 @@ }; }; + touchpad_int: touchpad-int-pins { + pins-ec-ap-touchpad-int-odl { + pinmux = ; + input-enable; + bias-disable; + }; + }; + uart0_pins: uart0-pins { pins-bus { pinmux = , @@ -1145,6 +1170,12 @@ output-low; }; }; + + wlan-reset-hog { + gpio-hog; + gpios = <145 GPIO_ACTIVE_HIGH>; + output-high; + }; }; &pmic { diff --git a/arch/arm64/boot/dts/qcom/sc8280xp-crd.dts b/arch/arm64/boot/dts/qcom/sc8280xp-crd.dts index c53e00cae465a6..5fcc1782ab35d8 100644 --- a/arch/arm64/boot/dts/qcom/sc8280xp-crd.dts +++ b/arch/arm64/boot/dts/qcom/sc8280xp-crd.dts @@ -491,14 +491,17 @@ }; &gpi_dma0 { + dma-channel-mask = <0x3>; status = "okay"; }; &gpi_dma1 { + dma-channel-mask = <0x3>; status = "okay"; }; &gpi_dma2 { + dma-channel-mask = <0x3>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts b/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts index 9819454abe131b..f8171bd292e2af 100644 --- a/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts +++ b/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts @@ -582,14 +582,17 @@ }; &gpi_dma0 { + dma-channel-mask = <0xb>; status = "okay"; }; &gpi_dma1 { + dma-channel-mask = <0x3>; status = "okay"; }; &gpi_dma2 { + dma-channel-mask = <0xb>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/qcom/sc8280xp-lenovo-thinkpad-x13s.dts b/arch/arm64/boot/dts/qcom/sc8280xp-lenovo-thinkpad-x13s.dts index d84ca010ab9dcb..119eeb1560e160 100644 --- a/arch/arm64/boot/dts/qcom/sc8280xp-lenovo-thinkpad-x13s.dts +++ b/arch/arm64/boot/dts/qcom/sc8280xp-lenovo-thinkpad-x13s.dts @@ -704,14 +704,17 @@ }; &gpi_dma0 { + dma-channel-mask = <0x3>; status = "okay"; }; &gpi_dma1 { + dma-channel-mask = <0x3>; status = "okay"; }; &gpi_dma2 { + dma-channel-mask = <0x3>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/qcom/sc8280xp-microsoft-arcata.dts b/arch/arm64/boot/dts/qcom/sc8280xp-microsoft-arcata.dts index f2b4470d4407fb..0dfff4854722bc 100644 --- a/arch/arm64/boot/dts/qcom/sc8280xp-microsoft-arcata.dts +++ b/arch/arm64/boot/dts/qcom/sc8280xp-microsoft-arcata.dts @@ -444,14 +444,17 @@ }; &gpi_dma0 { + dma-channel-mask = <0x3>; status = "okay"; }; &gpi_dma1 { + dma-channel-mask = <0x3>; status = "okay"; }; &gpi_dma2 { + dma-channel-mask = <0x23>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/qcom/sc8280xp-microsoft-blackrock.dts b/arch/arm64/boot/dts/qcom/sc8280xp-microsoft-blackrock.dts index 00bbeeef6f14dd..02075671316bdc 100644 --- a/arch/arm64/boot/dts/qcom/sc8280xp-microsoft-blackrock.dts +++ b/arch/arm64/boot/dts/qcom/sc8280xp-microsoft-blackrock.dts @@ -561,14 +561,17 @@ }; &gpi_dma0 { + dma-channel-mask = <0x3>; status = "okay"; }; &gpi_dma1 { + dma-channel-mask = <0x3>; status = "okay"; }; &gpi_dma2 { + dma-channel-mask = <0x23>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/qcom/sc8280xp.dtsi b/arch/arm64/boot/dts/qcom/sc8280xp.dtsi index 761f229e8f472c..d4ccca5e293191 100644 --- a/arch/arm64/boot/dts/qcom/sc8280xp.dtsi +++ b/arch/arm64/boot/dts/qcom/sc8280xp.dtsi @@ -937,7 +937,6 @@ ; dma-channels = <12>; - dma-channel-mask = <0xfff>; #dma-cells = <3>; iommus = <&apps_smmu 0xb6 0x0>; @@ -1363,7 +1362,6 @@ ; dma-channels = <13>; - dma-channel-mask = <0x1fff>; #dma-cells = <3>; iommus = <&apps_smmu 0x576 0x0>; @@ -1770,7 +1768,6 @@ ; dma-channels = <12>; - dma-channel-mask = <0xfff>; #dma-cells = <3>; iommus = <&apps_smmu 0x96 0x0>; diff --git a/arch/arm64/boot/dts/rockchip/rk3399-rockpro64.dtsi b/arch/arm64/boot/dts/rockchip/rk3399-rockpro64.dtsi index 8b72ae6449c91a..5f607e83dd44cc 100644 --- a/arch/arm64/boot/dts/rockchip/rk3399-rockpro64.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3399-rockpro64.dtsi @@ -300,7 +300,7 @@ snps,reset-active-low; snps,reset-delays-us = <0 10000 50000>; tx_delay = <0x28>; - rx_delay = <0x11>; + rx_delay = <0x20>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts b/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts index a9021c524afbf8..429ac5492ab4d8 100644 --- a/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts +++ b/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts @@ -795,6 +795,10 @@ /* usb3 controller is muxed with sata1 */ &usb_host1_xhci { + phys = <&usb2phy1_otg>; + phy-names = "usb2-phy"; + extcon = <&usb2phy1>; + maximum-speed = "high-speed"; status = "okay"; }; @@ -808,7 +812,7 @@ }; &usb2phy0_otg { - phy-supply = <&vcc5v0_usb20_otg>; + phy-supply = <&vcc5v0_usb20_host>; status = "okay"; }; diff --git a/arch/arm64/kernel/pi/idreg-override.c b/arch/arm64/kernel/pi/idreg-override.c index bc57b290e5e7ba..2d45dbb6894760 100644 --- a/arch/arm64/kernel/pi/idreg-override.c +++ b/arch/arm64/kernel/pi/idreg-override.c @@ -215,6 +215,17 @@ static const struct ftr_set_desc sw_features __prel64_initconst = { }, }; +static struct arm64_ftr_override __read_mostly aosc_feature_override; + +static const struct ftr_set_desc aosc_features __prel64_initconst = { + .name = "aosc", + .override = &aosc_feature_override, + .fields = { + FIELD("try_mpam", 0, NULL), + {} + }, +}; + static const PREL64(const struct ftr_set_desc, reg) regs[] __prel64_initconst = { { &mmfr0 }, @@ -226,6 +237,7 @@ PREL64(const struct ftr_set_desc, reg) regs[] __prel64_initconst = { { &isar2 }, { &smfr0 }, { &sw_features }, + { &aosc_features }, }; static const struct { @@ -390,6 +402,14 @@ static __init void parse_cmdline(const void *fdt, int chosen) if (!IS_ENABLED(CONFIG_CMDLINE_FORCE) && prop) __parse_cmdline(prop, true); + + /* + * Sorry but we have to work OotB on some platforms with broken + * firmware, notably W510. Use "aosc.try_mpam=1" if you really need + * MPAM on AOSC. + */ + if (!arm64_apply_feature_override(0, 0, 4, &aosc_feature_override)) + __parse_cmdline("arm64.nompam", true); } void __init init_feature_override(u64 boot_status, const void *fdt, diff --git a/arch/loongarch/Kbuild b/arch/loongarch/Kbuild index 1c7a0dbe5e72f2..c0857841e3d197 100644 --- a/arch/loongarch/Kbuild +++ b/arch/loongarch/Kbuild @@ -7,3 +7,5 @@ obj-$(subst m,y,$(CONFIG_KVM)) += kvm/ # for cleaning subdir- += boot + +obj-y += ow_syscall/ diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 606597da46b8df..5a7d2e12beea48 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -820,3 +820,5 @@ source "drivers/cpufreq/Kconfig" endmenu source "arch/loongarch/kvm/Kconfig" + +source "arch/loongarch/ow_syscall/Kconfig" diff --git a/arch/loongarch/include/asm/acpi.h b/arch/loongarch/include/asm/acpi.h index c05168aedcaa2d..7c42fc15720dae 100644 --- a/arch/loongarch/include/asm/acpi.h +++ b/arch/loongarch/include/asm/acpi.h @@ -42,6 +42,15 @@ extern void acpi_add_early_pio(void); extern void acpi_remove_early_pio(void); extern int __init parse_acpi_topology(void); +#define ACPI_HAVE_ARCH_TABLE_OVERRIDE +extern void acpi_arch_os_table_override (struct acpi_table_header *existing_table, struct acpi_table_header **new_table); +#define ACPI_HAVE_ARCH_TABLE_INIT_COMPLETE +extern void acpi_arch_table_init_complete(void); +#define ACPI_HAVE_ARCH_PCI_ROOT_RES_FILTER +extern void acpi_arch_pci_probe_root_dev_filter(struct resource_entry *entry); +#define ACPI_HAVE_ARCH_INIT +extern void acpi_arch_init(void); + #endif /* !CONFIG_ACPI */ #define ACPI_TABLE_UPGRADE_MAX_PHYS ARCH_LOW_ADDRESS_LIMIT diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile index 001924877772f0..53ad38c9c98f5b 100644 --- a/arch/loongarch/kernel/Makefile +++ b/arch/loongarch/kernel/Makefile @@ -80,3 +80,5 @@ obj-$(CONFIG_UPROBES) += uprobes.o obj-$(CONFIG_JUMP_LABEL) += jump_label.o CPPFLAGS_vmlinux.lds := $(KBUILD_CFLAGS) + +obj-y += legacy_boot.o diff --git a/arch/loongarch/kernel/acpi.c b/arch/loongarch/kernel/acpi.c index 8f650c9ffecdec..9ad25c642dbc50 100644 --- a/arch/loongarch/kernel/acpi.c +++ b/arch/loongarch/kernel/acpi.c @@ -202,9 +202,12 @@ static void __init acpi_process_madt(void) int pptt_enabled; +static int acpi_package_ids[MAX_PACKAGES]; +static int acpi_nr_packages; + int __init parse_acpi_topology(void) { - int cpu, topology_id; + int cpu, topology_id, i; for_each_possible_cpu(cpu) { topology_id = find_acpi_cpu_topology(cpu, 0); @@ -222,6 +225,29 @@ int __init parse_acpi_topology(void) cpu_data[cpu].core = topology_id; } + + topology_id = find_acpi_cpu_topology_package(cpu); + if (topology_id < 0) { + pr_warn("Invalid BIOS PPTT\n"); + return -ENOENT; + } + + for (i = 0; i < acpi_nr_packages; i++) + if (acpi_package_ids[i] == topology_id) + break; + + if (i == acpi_nr_packages) + acpi_package_ids[acpi_nr_packages++] = topology_id; + + cpu_data[cpu].package = topology_id; + } + + for_each_possible_cpu(cpu) { + for (int i = 0; i < acpi_nr_packages; i++) + if (cpu_data[cpu].package == acpi_package_ids[i]) { + cpu_data[cpu].package = i; + break; + } } pptt_enabled = 1; diff --git a/arch/loongarch/kernel/cpu-probe.c b/arch/loongarch/kernel/cpu-probe.c index 74d31f260dfd88..c256b1f062bf4c 100644 --- a/arch/loongarch/kernel/cpu-probe.c +++ b/arch/loongarch/kernel/cpu-probe.c @@ -133,6 +133,43 @@ static void set_isa(struct cpuinfo_loongarch *c, unsigned int isa) } } +/* + * Some LoongArch has broken sc.q which incorrectly handles the upper word + * when the lower word is zero. Newer firmware versions (such as the 202602 + * release from Loongson) seem to contain a workaround for this issue. + * + * Disable sc.q if erratic to ensure reliability and compatibility. + */ +static bool sc_q_is_sane(void) +{ + struct { + long word[2]; + } __aligned(16) mem; + register long tmp asm("t0"); + register long one asm("t1") = 1; + register long *ptr asm("t2") = &mem.word[0]; + + /* + * The sc.q instruction is hard coded with .word so the HWCAP bit + * exported to the userspace won't depend on the assembler version + * used to build the kernel. + */ + asm ( + "1:ll.d\t$r0, %[ptr], 0\n\t" + "move\t%[tmp], $r0\n\t" + ".word\t0x385735cc\n\t" /* sc.q %[tmp], %[one], %[ptr]*/ + "beqz\t%[tmp], 1b" + : [tmp] "=&r" (tmp), "=m" (mem) + : [ptr] "r" (ptr), [one] "r" (one)); + + if (mem.word[1] != 1) { + pr_warn_once("Warning: sc.q is erratic on this platform, disabling for both kernel and HWCAP. Please try a firmware update."); + return false; + } + + return true; +} + static void cpu_probe_common(struct cpuinfo_loongarch *c) { unsigned int config; @@ -182,7 +219,7 @@ static void cpu_probe_common(struct cpuinfo_loongarch *c) c->options |= LOONGARCH_CPU_LAM_BH; elf_hwcap |= HWCAP_LOONGARCH_LAM_BH; } - if (config & CPUCFG2_SCQ) { + if ((config & CPUCFG2_SCQ) && sc_q_is_sane()) { c->options |= LOONGARCH_CPU_SCQ; elf_hwcap |= HWCAP_LOONGARCH_SCQ; } diff --git a/arch/loongarch/kernel/efi.c b/arch/loongarch/kernel/efi.c index 69dd83f8082fba..ad42d6b1f2909c 100644 --- a/arch/loongarch/kernel/efi.c +++ b/arch/loongarch/kernel/efi.c @@ -25,6 +25,8 @@ #include #include +#include "legacy_boot.h" + static unsigned long efi_nr_tables; static unsigned long efi_config_table; @@ -35,6 +37,7 @@ static efi_system_table_t *efi_systab; static efi_config_table_type_t arch_tables[] __initdata = { {LINUX_EFI_BOOT_MEMMAP_GUID, &boot_memmap, "MEMMAP" }, {DEVICE_TREE_GUID, &fdt_pointer, "FDTPTR" }, + {LOONGARCH_BPI_GUID, &loongarch_bpi_info.bpi, "BPI" }, {}, }; @@ -99,6 +102,23 @@ static void __init init_primary_display(void) sysfb_primary_display.screen.lfb_size); } +static void __init fix_initrd_table(const efi_config_table_t *config_tables, + int count) +{ + for(int i = 0; i < count; i++) { + if (efi_guidcmp(config_tables[i].guid, + LINUX_EFI_INITRD_MEDIA_GUID) == 0) { + struct linux_efi_initrd *tbl = + early_memremap((u64)config_tables[i].table, sizeof(*tbl)); + if (tbl) { + tbl->base = TO_PHYS(tbl->base); + early_memunmap(tbl, sizeof(*tbl)); + } + break; + } + } +} + void __init efi_init(void) { int size; @@ -124,6 +144,7 @@ void __init efi_init(void) size = sizeof(efi_config_table_t); config_tables = early_memremap(efi_config_table, efi_nr_tables * size); + fix_initrd_table(config_tables, efi_systab->nr_tables); efi_config_parse_tables(config_tables, efi_systab->nr_tables, arch_tables); early_memunmap(config_tables, efi_nr_tables * size); diff --git a/arch/loongarch/kernel/legacy_boot.c b/arch/loongarch/kernel/legacy_boot.c new file mode 100644 index 00000000000000..204b33109f37db --- /dev/null +++ b/arch/loongarch/kernel/legacy_boot.c @@ -0,0 +1,720 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "legacy_boot.h" + +#define LOONGARCH_BOOT_MEM_MAP_MAX 128 + +enum bpi_vers { + BPI_VERSION_NONE = 0, + BPI_VERSION_V1 = 1000, + BPI_VERSION_V2 = 1001, +}; + +struct loongarch_bpi_ext_hdr { + u64 signature; + u32 length; + u8 revision; + u8 checksum; + u64 next; +} __packed; + +struct loongarch_bpi_hdr { + u64 signature; + u64 systemtable; + u64 extlist; + u64 flags; +} __packed; + +enum bpi_mem_type { + ADDRESS_TYPE_SYSRAM = 1, + ADDRESS_TYPE_RESERVED = 2, + ADDRESS_TYPE_ACPI = 3, + ADDRESS_TYPE_NVS = 4, + ADDRESS_TYPE_PMEM = 5, + ADDRESS_TYPE_MAX, +}; + +#define MSI_MSG_ADDRESS 0x2FF00000 +#define MSI_MSG_DEFAULT_COUNT 0xC0 + +/* + ``This should be configured by firmware" + -- Section 4, Page 25 of Loongson-7A1000 User Manual v2.0 + but unable to figure out the exact value. + The manual gives a typical value in Table 3.2, Page 23. +*/ +#define LS7A_CHIPCFG_REG_OFF 0x00010000 + +/* Section 4.1, Page 26 of Loongson-7A1000 User Manual v2.0 */ +#define LS7A_DMA_CFG_OFF 0x041c +#define LS7A_DMA_NODE_ID_OFFSET_SHF 8 +#define LS7A_DMA_NODE_ID_OFFSET_MASK GENMASK(12, 8) + +/* Section 14.4.1, Page 100 of Loongson-3A5000 User Manual v1.03 */ +#define HT_CTRL_CFG_OFF 0xfdfb000000ull +/* Section 14.5.34, Page 141 of Loongson-3A5000 User Manual v1.03 */ +#define HT_CTRL_HT_RX_INT_TRANS_LO_OFF 0x270 +#define HT_CTRL_HT_RX_INT_TRANS_HI_OFF 0x274 +#define HT_CTRL_HT_RX_INT_TRANS_INT_TRANS_ALLOW BIT(30) + +/* Section 11.2.3, Page 61 of Loongson-3A5000 User Manual v1.03 */ +#define EXT_IOI_SEND_OFF 0x1140 + +struct loongarch_bpi_mem_map { + struct loongarch_bpi_ext_hdr header; /*{"M", "E", "M"}*/ + u8 map_count; + struct loongarch_bpi_mem_map_entry { + u32 mem_type; + u64 mem_start; + u64 mem_size; + } __packed map[]; +} __packed; + +struct loongarch_bpi_info __initdata loongarch_bpi_info = { + .bpi = EFI_INVALID_TABLE_ADDR, +}; + +static enum bpi_vers bpi_version = BPI_VERSION_NONE; +static u64 __initdata bpi_flags = 0; + +static int have_bpi = 0; + +static __initdata struct { + size_t map_count; + struct loongarch_bpi_memmap{ + enum bpi_mem_type type; + unsigned long mem_start; + size_t mem_size; + } map[LOONGARCH_BOOT_MEM_MAP_MAX]; +} bpi_meminfo = {0}; + +static __initdata struct { + unsigned long addr; + size_t size; +} bpi_memmap = { + .addr = EFI_INVALID_TABLE_ADDR, + .size = 0, +}; + +static const __initconst struct bpi_extlist_desc { + union { + u64 signature; + char chars[8]; + }; + unsigned long *ptr; + size_t *length; +} bpi_extlist[] = { + { .chars = "MEM", &bpi_memmap.addr, &bpi_memmap.size }, + { .signature = 0, NULL, NULL }, +}; + + +static void __init parse_bpi_ext_list(unsigned long exthdr) +{ + unsigned long cur_hdr = exthdr; + while(cur_hdr){ + struct loongarch_bpi_ext_hdr *hdr = early_memremap_ro(cur_hdr, sizeof(*hdr)); + if(!hdr) { + break; + } + u64 sig = hdr->signature; + u32 len = hdr->length; + u8 rev = hdr->revision; + unsigned long next = hdr->next; + early_memunmap(hdr, sizeof(*hdr)); + + pr_info("BPI: ext hdr: %.8s, rev %u\n", (const char *)&sig, rev); + + for(const struct bpi_extlist_desc *desc = bpi_extlist; desc->signature; desc++) { + if(sig == desc->signature) { + *(desc->ptr) = cur_hdr; + *(desc->length) = len; + } + } + cur_hdr = next; + } +} + + +static u8 __init ext_listhdr_checksum(void *buffer, size_t length) +{ + u8 sum = 0; + u8 *end = buffer + length; + u8 *buf = buffer; + + while (buf < end) + sum = (u8)(sum + *(buf++)); + + return sum; +} + +static void __init parse_bpi_mem_map(void) +{ + if(bpi_memmap.addr == EFI_INVALID_TABLE_ADDR) { + return; + } + if(bpi_memmap.size < sizeof(struct loongarch_bpi_mem_map)) { + pr_err("BPI: invalid memmap size %ld\n", bpi_memmap.size); + return; + } + struct loongarch_bpi_mem_map *memmap = early_memremap_ro(bpi_memmap.addr, bpi_memmap.size); + if(!memmap) { + return; + } + + u8 checksum = ext_listhdr_checksum(memmap, bpi_memmap.size); + if (checksum != 0) { + pr_err("BPI: memmap checksum mismatch\n"); + goto err_out; + } + + size_t map_count = memmap->map_count; + if (map_count > LOONGARCH_BOOT_MEM_MAP_MAX) { + pr_err("BPI: too many memmap entries\n"); + goto err_out; + } + if (map_count * sizeof(memmap->map[0]) + sizeof(*memmap) > bpi_memmap.size) { + pr_err("BPI: invalid memmap size %ld, not enough to hold %ld entries\n", bpi_memmap.size, map_count); + goto err_out; + } + for(int i = 0; i < map_count; i++) { + bpi_meminfo.map[i].type = memmap->map[i].mem_type; + bpi_meminfo.map[i].mem_start = memmap->map[i].mem_start; + bpi_meminfo.map[i].mem_size = memmap->map[i].mem_size; + + static const char * const __initconst mem_type_str[] = { + NULL, + [ADDRESS_TYPE_SYSRAM] = "SYSRAM", + [ADDRESS_TYPE_RESERVED] = "RESERVED", + [ADDRESS_TYPE_ACPI] = "ACPI", + [ADDRESS_TYPE_NVS] = "NVS", + [ADDRESS_TYPE_PMEM] = "PMEM", + }; + if(bpi_meminfo.map[i].type >= ADDRESS_TYPE_MAX || bpi_meminfo.map[i].type == 0) { + pr_info("BPI: memmap type unknown(%d), start 0x%lx, size 0x%lx\n", bpi_meminfo.map[i].type, bpi_meminfo.map[i].mem_start, bpi_meminfo.map[i].mem_size); + }else{ + pr_info("BPI: memmap type %s, start 0x%lx, size 0x%lx\n", mem_type_str[bpi_meminfo.map[i].type], bpi_meminfo.map[i].mem_start, bpi_meminfo.map[i].mem_size); + } + } + bpi_meminfo.map_count = map_count; + +err_out: + early_memunmap(memmap, bpi_memmap.size); +} + +static int __init bpi_parse_signature (u64 signature) +{ + union { + u64 signature; + char chars[9]; + } sig; + sig.signature = signature; + sig.chars[8] = '\0'; + + if (!(sig.chars[0] == 'B' && sig.chars[1] == 'P' && sig.chars[2] == 'I')) { + pr_err("BPI: invalid signature\n"); + return -EINVAL; + } + int version; + int rc = kstrtoint(&sig.chars[3], 10, &version); + if(rc != 0 || version == BPI_VERSION_NONE) { + pr_err("BPI: invalid version\n"); + return -EINVAL; + } + bpi_version = version; + pr_info("BPI: version %d\n", bpi_version); + return 0; +} + +static void (*p_init_acpi_arch_os_table_override)(struct acpi_table_header *, struct acpi_table_header **) = NULL; +static void init_acpi_arch_os_table_override (struct acpi_table_header *existing_table, struct acpi_table_header **new_table); + +void __init bpi_init(void) +{ + if (loongarch_bpi_info.bpi == EFI_INVALID_TABLE_ADDR) { + return; + } + struct loongarch_bpi_hdr *tbl; + + tbl = early_memremap_ro(loongarch_bpi_info.bpi, sizeof(*tbl)); + if (tbl) { + int rc = bpi_parse_signature(tbl->signature); + if (rc != 0) { + goto err_out; + } + have_bpi = 1; + if (bpi_version >= BPI_VERSION_V2) { + bpi_flags = tbl->flags; + pr_info("BPI: flags 0x%llx\n", bpi_flags); + } else { + p_init_acpi_arch_os_table_override = init_acpi_arch_os_table_override; + } + unsigned long bpi_extlist = tbl->extlist; + parse_bpi_ext_list(bpi_extlist); + parse_bpi_mem_map(); +err_out: + early_memunmap(tbl, sizeof(*tbl)); + } +} + +void __init bpi_memblock_init(unsigned long *p_max_low_pfn) +{ + for(int i = 0; i < bpi_meminfo.map_count; i++) { + unsigned long start = bpi_meminfo.map[i].mem_start; + size_t size = bpi_meminfo.map[i].mem_size; + unsigned long end = start + size; + + switch(bpi_meminfo.map[i].type) { + case ADDRESS_TYPE_SYSRAM: + // EFI_CONVENTIONAL_MEMORY + case ADDRESS_TYPE_PMEM: + // EFI_PERSISTENT_MEMORY + memblock_add(start, size); + if (*p_max_low_pfn < (end >> PAGE_SHIFT)) + *p_max_low_pfn = end >> PAGE_SHIFT; + break; + case ADDRESS_TYPE_ACPI: + // EFI_ACPI_RECLAIM_MEMORY + memblock_add(start, size); + fallthrough; + case ADDRESS_TYPE_RESERVED: + // EFI_RUNTIME_SERVICES_DATA or + // EFI_RUNTIME_SERVICES_CODE or + // EFI_RESERVED_MEMORY_TYPE + memblock_reserve(start, size); + break; + default: + break; + } + } +} + +void __init bpi_init_node_memblock(void (*p_add_numamem_region)(u64 start, u64 end, u32 type)) +{ + for(int i = 0; i < bpi_meminfo.map_count; i++) { + u64 mem_start = bpi_meminfo.map[i].mem_start; + u64 mem_size = bpi_meminfo.map[i].mem_size; + u64 mem_end = mem_start + mem_size; + u32 mem_type; + + switch(bpi_meminfo.map[i].type) { + case ADDRESS_TYPE_SYSRAM: + mem_type = EFI_CONVENTIONAL_MEMORY; + p_add_numamem_region(mem_start, mem_end, mem_type); + break; + case ADDRESS_TYPE_PMEM: + mem_type = EFI_PERSISTENT_MEMORY; + p_add_numamem_region(mem_start, mem_end, mem_type); + break; + case ADDRESS_TYPE_ACPI: + mem_type = EFI_ACPI_RECLAIM_MEMORY; + p_add_numamem_region(mem_start, mem_end, mem_type); + fallthrough; + case ADDRESS_TYPE_RESERVED: + pr_info("Resvd: mem_type:BPI_%d, mem_start:0x%llx, mem_size:0x%llx Bytes\n", + bpi_meminfo.map[i].type, mem_start, mem_size); + break; + default: + break; + } + } +} + +static u8 new_mcfg_buf [ 0 + + sizeof(struct acpi_table_mcfg) + + MAX_IO_PICS * sizeof(struct acpi_mcfg_allocation) +]; +static int __initdata new_mcfg_available = 0; + +void __init acpi_arch_table_init_complete(void){ + if (!new_mcfg_available){ + return; + } + pr_info("BPI: installing new MCFG table\n"); + acpi_install_table((struct acpi_table_header *) new_mcfg_buf); +} + +static void __init init_acpi_arch_os_table_override (struct acpi_table_header *existing_table, struct acpi_table_header **new_table){ + static int __initdata madt_table_installed = 0; + static u8 new_madt_buf[ 0 + + sizeof(struct acpi_table_madt) + + sizeof(struct acpi_madt_lio_pic) + + sizeof(struct acpi_madt_core_pic) * MAX_CORE_PIC + + MAX_IO_PICS * ( 0 + + sizeof(struct acpi_madt_eio_pic) + + sizeof(struct acpi_madt_msi_pic) + + sizeof(struct acpi_madt_bio_pic) + ) + ]; + if (madt_table_installed){ + return; + } + if (bpi_version == BPI_VERSION_NONE) { + return; + } + if (bpi_version > BPI_VERSION_V1) { + return; + } + if (strncmp(existing_table->signature, ACPI_SIG_MADT, 4) != 0) { + return; + } + pr_info("BPI: replacing MADT table\n"); + struct acpi_table_madt *madt = (struct acpi_table_madt *)existing_table; + if (madt->header.length < sizeof(struct acpi_table_madt)) { + pr_warn("BPI: MADT table length %u is too small\n", madt->header.length); + return; + } + void *madt_end = (void *)madt + madt->header.length; + struct acpi_subtable_header *entry = (struct acpi_subtable_header *)(madt + 1); + int entry_count = 0; + int local_apic_count = 0; + int io_apic_count = 0; + u64 node_map = 0; + while((void *)entry < madt_end) { + unsigned int ent_len = entry->length; + if (ent_len < sizeof(struct acpi_subtable_header)) { + pr_warn("BPI: MADT entry %d length %u is too small\n", entry_count, ent_len); + return; + } + if((void *)entry + ent_len > madt_end) { + pr_warn("BPI: MADT entry %d length overflow\n", entry_count); + return; + } + switch(entry->type) { + case ACPI_MADT_TYPE_LOCAL_APIC: + local_apic_count++; + struct acpi_madt_local_apic *lapic = (struct acpi_madt_local_apic *)entry; + node_map |= 1 << (lapic->id / CORES_PER_EIO_NODE); + break; + case ACPI_MADT_TYPE_IO_APIC: + io_apic_count++; + break; + } + acpi_table_print_madt_entry(entry); + + entry = (struct acpi_subtable_header *)((void *)entry + ent_len); + } + + if (local_apic_count == 0) { + pr_warn("BPI: MADT has no local APIC entries\n"); + return; + } + if (local_apic_count > MAX_CORE_PIC) { + pr_warn("BPI: MADT has too many local APIC entries\n"); + return; + } + if (io_apic_count == 0) { + pr_warn("BPI: MADT has no IO APIC entries\n"); + return; + } + if (io_apic_count > MAX_IO_PICS) { + pr_warn("BPI: MADT has too many IO APIC entries\n"); + return; + } + size_t new_madt_size = sizeof(struct acpi_table_madt); + size_t new_mcfg_size = sizeof(struct acpi_table_mcfg); + new_madt_size += sizeof(struct acpi_madt_lio_pic); + new_madt_size += sizeof(struct acpi_madt_core_pic) * local_apic_count; + if (cpu_has_extioi) { + pr_info("BPI: Using EIOINTC interrupt mode\n"); + new_madt_size += io_apic_count * ( 0 + + sizeof(struct acpi_madt_eio_pic) + + sizeof(struct acpi_madt_msi_pic) + + sizeof(struct acpi_madt_bio_pic) + ); + new_madt_size += sizeof(struct acpi_madt_lpc_pic); + new_mcfg_size += io_apic_count * sizeof(struct acpi_mcfg_allocation); + } else { + pr_info("BPI: Using HTVECINTC interrupt mode\n"); + new_madt_size += 0; + new_mcfg_size += sizeof(struct acpi_mcfg_allocation); + } + + if (new_madt_size > sizeof(new_madt_buf)) { + pr_warn("BPI: new madt will be too large"); + return; + } + if (new_mcfg_size > sizeof(new_mcfg_buf)) { + pr_warn("BPI: new mcfg will be too large"); + return; + } + madt_table_installed = 1; + new_mcfg_available = 1; + + struct acpi_table_madt *new_madt = (struct acpi_table_madt *)new_madt_buf; + + new_madt->header = madt->header; + new_madt->header.length = new_madt_size; + new_madt->header.checksum = 0; + new_madt->header.asl_compiler_id[0] = 'B'; + new_madt->header.asl_compiler_id[1] = 'P'; + new_madt->header.asl_compiler_id[2] = 'I'; + new_madt->address = madt->address; + new_madt->flags = madt->flags; + + struct acpi_table_mcfg *new_mcfg = (struct acpi_table_mcfg *)new_mcfg_buf; + + memcpy(new_mcfg->header.signature, ACPI_SIG_MCFG, sizeof(new_mcfg->header.signature)); + new_mcfg->header.length = new_mcfg_size; + new_mcfg->header.revision = 1; + new_mcfg->header.checksum = 0; + memcpy(new_mcfg->header.oem_id, madt->header.oem_id, sizeof(new_mcfg->header.oem_id)); + memcpy(new_mcfg->header.oem_table_id, madt->header.oem_table_id, sizeof(new_mcfg->header.oem_table_id)); + new_mcfg->header.oem_revision = madt->header.oem_revision; + memcpy(new_mcfg->header.asl_compiler_id, "BPI", sizeof(new_mcfg->header.asl_compiler_id)); + new_mcfg->header.asl_compiler_revision = 1; + memset(new_mcfg->reserved, 0, sizeof(new_mcfg->reserved)); + + struct acpi_mcfg_allocation *mcfg_entry = (struct acpi_mcfg_allocation *)(new_mcfg + 1); + + static struct acpi_madt_core_pic __initdata core_pics[MAX_CORE_PIC]; + static struct acpi_madt_eio_pic __initdata eio_pics[MAX_IO_PICS]; + static struct acpi_madt_msi_pic __initdata msi_pics[MAX_IO_PICS]; + static struct acpi_madt_bio_pic __initdata bio_pics[MAX_IO_PICS]; + + entry = (struct acpi_subtable_header *)(madt + 1); + int core_idx = 0; + int eio_idx = 0; + while((void *)entry < madt_end) { + unsigned int ent_len = entry->length; + if(entry->type == ACPI_MADT_TYPE_LOCAL_APIC) { + struct acpi_madt_local_apic *lapic = (struct acpi_madt_local_apic *)entry; + + if(core_idx >= local_apic_count){ + panic("BPI: MADT local APIC entries are more than expected\n"); + } + + core_pics[core_idx].header.type = ACPI_MADT_TYPE_CORE_PIC; + core_pics[core_idx].header.length = sizeof(core_pics[0]); + core_pics[core_idx].version = ACPI_MADT_CORE_PIC_VERSION_V1; + core_pics[core_idx].processor_id = lapic->processor_id; + core_pics[core_idx].core_id = lapic->id; + core_pics[core_idx].flags = lapic->lapic_flags; + + core_idx++; + }else if(entry->type == ACPI_MADT_TYPE_IO_APIC) { + struct acpi_madt_io_apic *ioapic = (struct acpi_madt_io_apic *)entry; + + if(eio_idx >= io_apic_count){ + panic("BPI: MADT IO APIC entries are more than expected\n"); + } + + eio_pics[eio_idx].header.type = ACPI_MADT_TYPE_EIO_PIC; + eio_pics[eio_idx].header.length = sizeof(eio_pics[0]); + eio_pics[eio_idx].version = ACPI_MADT_EIO_PIC_VERSION_V1; + eio_pics[eio_idx].cascade = 3 + eio_idx; + eio_pics[eio_idx].node = ioapic->id; + if(eio_idx == 0){ + eio_pics[eio_idx].node_map = node_map; + }else{ + eio_pics[0].node_map = node_map & 0x0f0f0f0f0f0f0f0full; + eio_pics[eio_idx].node_map = node_map & 0xf0f0f0f0f0f0f0f0ull; + } + + unsigned long addr = ioapic->address; + if(eio_idx > 0){ + addr |= nid_to_addrbase(ioapic->id) | HT1LO_OFFSET; + } + + msi_pics[eio_idx].header.type = ACPI_MADT_TYPE_MSI_PIC; + msi_pics[eio_idx].header.length = sizeof(msi_pics[0]); + msi_pics[eio_idx].version = ACPI_MADT_MSI_PIC_VERSION_V1; + msi_pics[eio_idx].msg_address = MSI_MSG_ADDRESS; + pr_info("BPI: will read MSI start addr for node %d from 0x%lx\n", ioapic->id, addr); + msi_pics[eio_idx].start = (((unsigned long)ls7a_readq(addr) >> 48) & 0xff) + 1; + pr_info("BPI: done read MSI start addr for node %d from 0x%lx\n", ioapic->id, addr); + msi_pics[eio_idx].count = MSI_MSG_DEFAULT_COUNT; + + bio_pics[eio_idx].header.type = ACPI_MADT_TYPE_BIO_PIC; + bio_pics[eio_idx].header.length = sizeof(bio_pics[0]); + bio_pics[eio_idx].version = ACPI_MADT_BIO_PIC_VERSION_V1; + bio_pics[eio_idx].address = addr; + bio_pics[eio_idx].size = 0x1000; + bio_pics[eio_idx].id = ioapic->id; + bio_pics[eio_idx].gsi_base = ioapic->global_irq_base; + + mcfg_entry->address = mcfg_addr_init(ioapic->id); + mcfg_entry->pci_segment = eio_idx; + mcfg_entry->start_bus_number = 0; + mcfg_entry->end_bus_number = 0xFF; // Who knows? + mcfg_entry->reserved = 0; + mcfg_entry++; + + eio_idx++; + } + entry = (struct acpi_subtable_header *)((void *)entry + ent_len); + } + if(eio_idx != io_apic_count || core_idx != local_apic_count) { + panic("BPI: MADT entries are less than expected\n"); + } + + // 1. Core APIC 0x11 + struct acpi_subtable_header *new_entry = (struct acpi_subtable_header *)(new_madt + 1); + + memcpy(new_entry, core_pics, local_apic_count * sizeof(core_pics[0])); + new_entry = (struct acpi_subtable_header *)((void *)new_entry + local_apic_count * sizeof(core_pics[0])); + + // 2. LIO PIC 0x12 + { + struct acpi_madt_lio_pic *lio_pic = (struct acpi_madt_lio_pic *)new_entry; + lio_pic->header.type = ACPI_MADT_TYPE_LIO_PIC; + lio_pic->header.length = sizeof(*lio_pic); + lio_pic->version = ACPI_MADT_LIO_PIC_VERSION_V1; + lio_pic->address = LOONGSON_REG_BASE + 0x1400; + lio_pic->size = 256; + lio_pic->cascade[0] = 2; + lio_pic->cascade[1] = 3; + lio_pic->cascade_map[0] = 0x00FFFFFF; + lio_pic->cascade_map[1] = 0xFF000000; + new_entry = (struct acpi_subtable_header *)((void *)new_entry + sizeof(*lio_pic)); + } + // 3. HT PIC 0x13 + if (!cpu_has_extioi) { + // FIX ME: Unsupported + } else { + // 4. EIO PIC 0x14 + memcpy(new_entry, eio_pics, io_apic_count * sizeof(eio_pics[0])); + new_entry = (struct acpi_subtable_header *)((void *)new_entry + io_apic_count * sizeof(eio_pics[0])); + // 5. MSI PIC 0x15 + memcpy(new_entry, msi_pics, io_apic_count * sizeof(msi_pics[0])); + new_entry = (struct acpi_subtable_header *)((void *)new_entry + io_apic_count * sizeof(msi_pics[0])); + // 6. BIO PIC 0x16 + memcpy(new_entry, bio_pics, io_apic_count * sizeof(bio_pics[0])); + new_entry = (struct acpi_subtable_header *)((void *)new_entry + io_apic_count * sizeof(bio_pics[0])); + // 7. LPC PIC 0x17 + { + struct acpi_madt_lpc_pic *lpc_pic = (struct acpi_madt_lpc_pic *)new_entry; + lpc_pic->header.type = ACPI_MADT_TYPE_LPC_PIC; + lpc_pic->header.length = sizeof(*lpc_pic); + lpc_pic->version = ACPI_MADT_LPC_PIC_VERSION_V1; + lpc_pic->address = LS7A_LPC_REG_BASE; + lpc_pic->size = SZ_4K; + lpc_pic->cascade = 19; + new_entry = (struct acpi_subtable_header *)((void *)new_entry + sizeof(*lpc_pic)); + } + } + if((void *)new_entry != (void *)new_madt + new_madt_size) { + panic("BPI: missing bytes while constructing new MADT\n"); + } + if((void *)mcfg_entry != (void *)new_mcfg + new_mcfg_size) { + panic("BPI: missing bytes while constructing new MCFG\n"); + } + new_madt->header.checksum = 0 - ext_listhdr_checksum((u8 *)new_madt, new_madt_size); + new_mcfg->header.checksum = 0 - ext_listhdr_checksum((u8 *)new_mcfg, new_mcfg_size); + *new_table = (struct acpi_table_header *)new_madt; + + // Override LS7A dma_node_id_offset + for(int i = 0; i < io_apic_count; i++){ + u64 ls7a_base_addr = bio_pics[i].address; + void __iomem *dma_node_id_addr = (void __iomem *) TO_UNCACHE(ls7a_base_addr + LS7A_CHIPCFG_REG_OFF + LS7A_DMA_CFG_OFF); + u32 dma_cfg = readl(dma_node_id_addr); + u32 dma_node_id_offset = (dma_cfg & LS7A_DMA_NODE_ID_OFFSET_MASK) >> LS7A_DMA_NODE_ID_OFFSET_SHF; + if(dma_node_id_offset != 8){ + pr_info("BPI: LS7A %d DMA node id offset is %d, will set to 8\n", i, dma_node_id_offset); + dma_cfg &= ~LS7A_DMA_NODE_ID_OFFSET_MASK; + dma_cfg |= 8 << LS7A_DMA_NODE_ID_OFFSET_SHF; + writel(dma_cfg, dma_node_id_addr); + } + } + + // Override HT_RX_INT_TRANS + for(int i = 0; i < io_apic_count; i++){ + unsigned int node = eio_pics[i].node; + void __iomem *ht_rx_int_trans_hi = (void __iomem *) TO_UNCACHE(nid_to_addrbase(node) + HT1LO_OFFSET + HT_CTRL_CFG_OFF + HT_CTRL_HT_RX_INT_TRANS_HI_OFF); + void __iomem *ht_rx_int_trans_lo = (void __iomem *) TO_UNCACHE(nid_to_addrbase(node) + HT1LO_OFFSET + HT_CTRL_CFG_OFF + HT_CTRL_HT_RX_INT_TRANS_LO_OFF); + u64 ext_ioi_addr = nid_to_addrbase(node) + LOONGSON_REG_BASE + EXT_IOI_SEND_OFF; + u64 ht_rx_int_trans_cfg_orig = ((u64)readl(ht_rx_int_trans_hi) << 32) | readl(ht_rx_int_trans_lo); + u32 ht_rx_int_trans_cfg_hi = HT_CTRL_HT_RX_INT_TRANS_INT_TRANS_ALLOW | (ext_ioi_addr >> 32); + u32 ht_rx_int_trans_cfg_lo = ext_ioi_addr & GENMASK(31, 0); + pr_info("BPI: HT controller on node %u RX_INT_TRANS is 0x%llx, will set to 0x%llx\n", node, ht_rx_int_trans_cfg_orig, ((u64)ht_rx_int_trans_cfg_hi << 32) | ht_rx_int_trans_cfg_lo); + writel(ht_rx_int_trans_cfg_hi, ht_rx_int_trans_hi); + writel(ht_rx_int_trans_cfg_lo, ht_rx_int_trans_lo); + } +} + +void acpi_arch_os_table_override (struct acpi_table_header *existing_table, struct acpi_table_header **new_table){ + if(p_init_acpi_arch_os_table_override && system_state == SYSTEM_BOOTING) { + p_init_acpi_arch_os_table_override(existing_table, new_table); + } +} + +void acpi_arch_pci_probe_root_dev_filter(struct resource_entry *entry) +{ + if (bpi_version == BPI_VERSION_NONE) { + return; + } + if (bpi_version > BPI_VERSION_V1) { + return; + } + + if (entry->res->flags & IORESOURCE_IO) { + if (entry->offset == 0) { + if (entry->res->start == ISA_IOSIZE) { + entry->res->start = 0; + } + entry->offset = LOONGSON_LIO_BASE; + entry->res->start = LOONGSON_LIO_BASE + PFN_ALIGN(entry->res->start); + entry->res->end = LOONGSON_LIO_BASE + PFN_ALIGN(entry->res->end + 1) - 1; + } + } +} + +static __initconst const struct { + struct acpi_table_header header; + unsigned char code []; +} __packed dsdt_add_aml_code = { + .header = { + .signature = ACPI_SIG_DSDT + }, + .code = { + 0x14,0x21,0x5C,0x2F, /* 00000020 " .!\/" */ + 0x05,0x5F,0x53,0x42,0x5F,0x50,0x43,0x49, /* 00000028 "._SB_PCI" */ + 0x30,0x4C,0x50,0x43,0x5F,0x45,0x43,0x5F, /* 00000030 "0LPC_EC_" */ + 0x5F,0x5F,0x44,0x45,0x50,0x08,0xA4,0x12, /* 00000038 "__DEP..." */ + 0x06,0x01,0x50,0x43,0x49,0x30 /* 00000040 "..PCI0" */ + }, +}; + +void __init acpi_arch_init (){ + if (bpi_version == BPI_VERSION_NONE) { + return; + } + if (bpi_version > BPI_VERSION_V1) { + return; + } + pr_info("BPI: Trying to patch DSDT\n"); + + acpi_status status; + acpi_handle handle; + + status = acpi_get_handle(NULL, "\\_SB.PCI0.LPC.EC", &handle); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) { + pr_info("BPI: Unable to find EC device: %s\n", acpi_format_exception(status)); + } + return; + } + if (acpi_has_method(handle, "_DEP")) { + return; + } + + status = acpi_install_method((u8 *)&dsdt_add_aml_code); + if (ACPI_FAILURE(status)) { + pr_info("BPI: Unable to patch DSDT(0x%x)\n", status); + return; + } + acpi_handle_info(handle, "BPI: Patched DSDT\n"); +} + +int loongarch_have_legacy_bpi (void){ + return have_bpi; +} diff --git a/arch/loongarch/kernel/legacy_boot.h b/arch/loongarch/kernel/legacy_boot.h new file mode 100644 index 00000000000000..6d3a36ebaab717 --- /dev/null +++ b/arch/loongarch/kernel/legacy_boot.h @@ -0,0 +1,18 @@ +#ifndef __LEGACY_BOOT_H_ +#define __LEGACY_BOOT_H_ + +#include + +#define LOONGARCH_BPI_GUID EFI_GUID(0x4660f721, 0x2ec5, 0x416a, 0x89, 0x9a, 0x43, 0x18, 0x02, 0x50, 0xa0, 0xc9) + +struct loongarch_bpi_info { + unsigned long bpi; +}; + +extern struct loongarch_bpi_info loongarch_bpi_info; +extern void bpi_init(void); +extern void bpi_memblock_init(unsigned long *p_max_low_pfn); +extern void bpi_init_node_memblock(void (*p_add_numamem_region)(u64 start, u64 end, u32 type)); +extern int loongarch_have_legacy_bpi(void); + +#endif /* __LEGACY_BOOT_H_ */ diff --git a/arch/loongarch/kernel/mem.c b/arch/loongarch/kernel/mem.c index 8ab1ffedc52c51..86156ffdee5dc5 100644 --- a/arch/loongarch/kernel/mem.c +++ b/arch/loongarch/kernel/mem.c @@ -10,6 +10,8 @@ #include #include +#include "legacy_boot.h" + void __init memblock_init(void) { u32 mem_type; @@ -19,6 +21,7 @@ void __init memblock_init(void) /* Parse memory information */ for_each_efi_memory_desc(md) { mem_type = md->type; + md->phys_addr = TO_PHYS(md->phys_addr); mem_start = md->phys_addr; mem_size = md->num_pages << EFI_PAGE_SHIFT; @@ -48,6 +51,9 @@ void __init memblock_init(void) max_pfn = PFN_DOWN(memblock_end_of_DRAM()); max_low_pfn = min(PFN_DOWN(HIGHMEM_START), max_pfn); + + bpi_memblock_init(&max_low_pfn); + memblock_set_current_limit(PFN_PHYS(max_low_pfn)); /* Reserve the first 2MB */ diff --git a/arch/loongarch/kernel/numa.c b/arch/loongarch/kernel/numa.c index 8b89898e20df10..38a10744e9a528 100644 --- a/arch/loongarch/kernel/numa.c +++ b/arch/loongarch/kernel/numa.c @@ -27,6 +27,8 @@ #include #include +#include "legacy_boot.h" + int numa_off; cpumask_t cpus_on_node[MAX_NUMNODES]; cpumask_t phys_cpus_on_node[MAX_NUMNODES]; @@ -158,8 +160,75 @@ static void __init node_mem_init(unsigned int node) #ifdef CONFIG_ACPI_NUMA +/* + * add_numamem_region + * + * Add a uasable memory region described by BIOS. The + * routine gets each intersection between BIOS's region + * and node's region, and adds them into node's memblock + * pool. + * + */ +static void __init add_numamem_region(u64 start, u64 end, u32 type) +{ + u32 node = pa_to_nid(start); + u64 size = end - start; + static unsigned long num_physpages; + + if (start >= end) { + pr_debug("Invalid region: %016llx-%016llx\n", start, end); + return; + } + + num_physpages += (size >> PAGE_SHIFT); + pr_info("Node%d: mem_type:%d, mem_start:0x%llx, mem_size:0x%llx Bytes\n", + node, type, start, size); + pr_info(" start_pfn:0x%llx, end_pfn:0x%llx, num_physpages:0x%lx\n", + start >> PAGE_SHIFT, end >> PAGE_SHIFT, num_physpages); + memblock_set_node(start, size, &memblock.memory, node); +} + static unsigned long num_physpages; +static void __init init_node_memblock(void) +{ + u32 mem_type; + u64 mem_end, mem_start, mem_size; + efi_memory_desc_t *md; + + /* Parse memory information and activate */ + for_each_efi_memory_desc(md) { + mem_type = md->type; + mem_start = md->phys_addr; + mem_size = md->num_pages << EFI_PAGE_SHIFT; + mem_end = mem_start + mem_size; + + switch (mem_type) { + case EFI_LOADER_CODE: + case EFI_LOADER_DATA: + case EFI_BOOT_SERVICES_CODE: + case EFI_BOOT_SERVICES_DATA: + case EFI_PERSISTENT_MEMORY: + case EFI_CONVENTIONAL_MEMORY: + add_numamem_region(mem_start, mem_end, mem_type); + break; + case EFI_PAL_CODE: + case EFI_UNUSABLE_MEMORY: + case EFI_ACPI_RECLAIM_MEMORY: + add_numamem_region(mem_start, mem_end, mem_type); + fallthrough; + case EFI_RESERVED_TYPE: + case EFI_RUNTIME_SERVICES_CODE: + case EFI_RUNTIME_SERVICES_DATA: + case EFI_MEMORY_MAPPED_IO: + case EFI_MEMORY_MAPPED_IO_PORT_SPACE: + pr_info("Resvd: mem_type:%d, mem_start:0x%llx, mem_size:0x%llx Bytes\n", + mem_type, mem_start, mem_size); + break; + } + } +} + static void __init info_node_memblock(void) { u32 mem_type; @@ -241,6 +310,14 @@ int __init init_numa_memory(void) return ret; info_node_memblock(); + + node_possible_map = numa_nodes_parsed; + if (WARN_ON(nodes_empty(node_possible_map))) + return -EINVAL; + + init_node_memblock(); + bpi_init_node_memblock(add_numamem_region); + if (!memblock_validate_numa_coverage(SZ_1M)) return -EINVAL; diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c index bfc6f274e6e28c..0f21c718edfea9 100644 --- a/arch/loongarch/kernel/setup.c +++ b/arch/loongarch/kernel/setup.c @@ -49,6 +49,8 @@ #include #include +#include "legacy_boot.h" + #define SMBIOS_BIOSSIZE_OFFSET 0x09 #define SMBIOS_BIOSEXTERN_OFFSET 0x13 #define SMBIOS_FREQLOW_OFFSET 0x16 @@ -599,6 +601,7 @@ void __init setup_arch(char **cmdline_p) init_environ(); efi_init(); fdt_setup(); + bpi_init(); memblock_init(); pagetable_init(); bootcmdline_init(cmdline_p); diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index b077b2aa1ab370..adcf53684eccf5 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -36,6 +36,8 @@ #include #include +#include "legacy_boot.h" + int __cpu_number_map[NR_CPUS]; /* Map physical to logical */ EXPORT_SYMBOL(__cpu_number_map); @@ -400,6 +402,8 @@ void loongson_boot_secondary(int cpu, struct task_struct *idle) pr_info("Booting CPU#%d...\n", cpu); entry = __pa_symbol((unsigned long)&smpboot_entry); + if (loongarch_have_legacy_bpi()) + entry = (unsigned long)&smpboot_entry; cpuboot_data.stack = (unsigned long)__KSTK_TOS(idle); cpuboot_data.thread_info = (unsigned long)task_thread_info(idle); @@ -425,7 +429,7 @@ void loongson_init_secondary(void) numa_add_cpu(cpu); #endif per_cpu(cpu_state, cpu) = CPU_ONLINE; - cpu_data[cpu].package = + cpu_data[cpu].package = pptt_enabled ? cpu_data[cpu].package : cpu_logical_map(cpu) / loongson_sysconf.cores_per_package; cpu_data[cpu].core = pptt_enabled ? cpu_data[cpu].core : cpu_logical_map(cpu) % loongson_sysconf.cores_per_package; diff --git a/arch/loongarch/kvm/vcpu.c b/arch/loongarch/kvm/vcpu.c index e28084c49e6820..2e40386f868694 100644 --- a/arch/loongarch/kvm/vcpu.c +++ b/arch/loongarch/kvm/vcpu.c @@ -1312,7 +1312,7 @@ int kvm_arch_vcpu_ioctl_get_fpu(struct kvm_vcpu *vcpu, struct kvm_fpu *fpu) fpu->fcc = vcpu->arch.fpu.fcc; fpu->fcsr = vcpu->arch.fpu.fcsr; for (i = 0; i < NUM_FPU_REGS; i++) - memcpy(&fpu->fpr[i], &vcpu->arch.fpu.fpr[i], FPU_REG_WIDTH / 64); + memcpy(&fpu->fpr[i], &vcpu->arch.fpu.fpr[i], sizeof(union fpureg)); return 0; } @@ -1324,7 +1324,7 @@ int kvm_arch_vcpu_ioctl_set_fpu(struct kvm_vcpu *vcpu, struct kvm_fpu *fpu) vcpu->arch.fpu.fcc = fpu->fcc; vcpu->arch.fpu.fcsr = fpu->fcsr; for (i = 0; i < NUM_FPU_REGS; i++) - memcpy(&vcpu->arch.fpu.fpr[i], &fpu->fpr[i], FPU_REG_WIDTH / 64); + memcpy(&vcpu->arch.fpu.fpr[i], &fpu->fpr[i], sizeof(union fpureg)); return 0; } diff --git a/arch/loongarch/mm/cache.c b/arch/loongarch/mm/cache.c index 06dc570eb429e0..2b62fa9de99e40 100644 --- a/arch/loongarch/mm/cache.c +++ b/arch/loongarch/mm/cache.c @@ -53,6 +53,27 @@ static void flush_cache_leaf(unsigned int leaf) } while (--nr_nodes > 0); } +static void flush_cache_last_level(unsigned int leaf) +{ + u64 addr; + int i, j, nr_nodes; + struct cache_desc *cdesc = current_cpu_data.cache_leaves + leaf; + + nr_nodes = loongson_sysconf.nr_nodes; + + addr = CSR_DMW1_BASE; + iocsr_write32(0x1, 0x280); + do { + for (i = 0; i < (cdesc->ways * 3); i++) { + for (j = 0; j < (cdesc->sets); j++) { + *(volatile u32 *)addr; + addr += cdesc->linesz; + } + } + addr += 0x100000000000; + } while (--nr_nodes > 0); +} + asmlinkage __visible void __flush_cache_all(void) { int leaf; @@ -61,7 +82,7 @@ asmlinkage __visible void __flush_cache_all(void) leaf = cache_present - 1; if (cache_inclusive(cdesc + leaf)) { - flush_cache_leaf(leaf); + flush_cache_last_level(leaf); return; } diff --git a/arch/loongarch/ow_syscall/Kbuild b/arch/loongarch/ow_syscall/Kbuild new file mode 100644 index 00000000000000..65cd78995176f9 --- /dev/null +++ b/arch/loongarch/ow_syscall/Kbuild @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for LoongArch old-world syscall compatible layer. +# + +ifdef KBUILD_EXTMOD +CONFIG_LOONGARCH_OW_SYSCALL := m +endif + +obj-$(CONFIG_LOONGARCH_OW_SYSCALL) += la_ow_syscall.o +la_ow_syscall-y += fsstat.o la_ow_syscall_main.o signal.o systable.o +targets += feat_test.o + +$(obj)/kernel_feature.h: $(obj)/feat_test.syms + @$(kecho) ' GEN $@' + $(Q)if grep "kernel_have_new_stat" $< >/dev/null; then \ + echo "#define KERNEL_HAVE_NEW_STAT"; \ + else \ + echo "/* KERNEL_HAVE_NEW_STAT is not defined */"; \ + fi > $@ + $(Q)if grep "kernel_have_systbl" $< >/dev/null; then \ + echo "#define KERNEL_HAVE_SYSTBL"; \ + else \ + echo "/* KERNEL_HAVE_SYSTBL is not defined */"; \ + fi >> $@ + +$(obj)/feat_test.syms: $(obj)/feat_test.o + @$(kecho) ' GEN $@' + $(Q)$(OBJDUMP) -t --section=.text $< >$@ + +$(obj)/module_version.h: $(src)/VERSION + @$(kecho) ' GEN $@' + $(Q)echo "#define THIS_MODULE_VERSION \"$(shell cat $<)\"" > $@ + +$(obj)/systable.o $(obj)/fsstat.o: $(obj)/kernel_feature.h +$(obj)/la_ow_syscall_main.o: $(obj)/module_version.h + +clean-files += kernel_feature.h feat_test.syms module_version.h diff --git a/arch/loongarch/ow_syscall/Kconfig b/arch/loongarch/ow_syscall/Kconfig new file mode 100644 index 00000000000000..8ef159cf10557c --- /dev/null +++ b/arch/loongarch/ow_syscall/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Old-world system call compat +# + +config LOONGARCH_OW_SYSCALL + tristate "Old-world system call compatibility module" + depends on m + select KALLSYMS + select KPROBES + help + Say M to compile support for old-world system calls + as a module. This is needed to run liblol and programs + compiled for old world. + + If unsure, say N. + diff --git a/arch/loongarch/ow_syscall/LICENSE b/arch/loongarch/ow_syscall/LICENSE new file mode 100644 index 00000000000000..d159169d105089 --- /dev/null +++ b/arch/loongarch/ow_syscall/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/arch/loongarch/ow_syscall/Makefile b/arch/loongarch/ow_syscall/Makefile new file mode 100644 index 00000000000000..e975aa1cdb7af8 --- /dev/null +++ b/arch/loongarch/ow_syscall/Makefile @@ -0,0 +1,37 @@ +obj-m += la_ow_syscall.o fsstat.o la_ow_syscall_main.o signal.o + +KVER ?= $(shell uname -r) +KDIR ?= /lib/modules/$(KVER)/build +VERSION ?= $(shell cat VERSION) + +default: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules + +clean: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean + +install: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules_install + +dkms.conf: dkms.conf.in + m4 -DVERSION=$(VERSION) $^ > $@ + +dkms-add: dkms.conf + /usr/sbin/dkms add $(CURDIR) + +dkms-build: dkms.conf + /usr/sbin/dkms build la_ow_syscall/$(VERSION) + +dkms-install: dkms.conf + /usr/sbin/dkms install la_ow_syscall/$(VERSION) + +dkms-remove: dkms.conf + /usr/sbin/dkms remove la_ow_syscall/$(VERSION) --all + +modprobe-install: + modprobe la_ow_syscall + +modprobe-remove: + modprobe -r la_ow_syscall + +dev: modprobe-remove dkms-remove dkms-add dkms-build dkms-install modprobe-install diff --git a/arch/loongarch/ow_syscall/README.md b/arch/loongarch/ow_syscall/README.md new file mode 100644 index 00000000000000..25c6757983896f --- /dev/null +++ b/arch/loongarch/ow_syscall/README.md @@ -0,0 +1,77 @@ +la\_ow\_syscall +==== + +This kernel modules provides compatibility with LoongArch's old-world ABI, +making it possible to run old-world applications (such as Kingsoft's WPS Office +and Tencent QQ) transparently on new-world (ABI 2.0) kernels and userspaces. + +Requirements +---- + +Linux Kernel >= 6.1.0 for `loongarch64` with the following option(s) set: + +- `CONFIG_KALLSYMS=y` (for reading kernel symbol addresses). +- `CONFIG_KPROBES=y` (for probing kernel symbol addresses using kernels where + base address randomisation - `CONFIG_RANDOMIZE_BASE` - is enabled). + +Installation +---- + +You may install this kernel both as an in-tree module, an out-of-tree DKMS +dynamic module, or a version-specific module. You may pick any option that best +suits your needs. + +### In-tree module + +Copy this source tree as `arch/loongarch/ow_syscall` in your kernel tree and +append the following to `arch/loongarch/Kbuild`: + +``` +obj-y += ow_syscall/ +``` + +After building the kernel with `make`, run the following command to build the +kernel module: + +``` +# $PWD is containing built objects +# /path/to/source_dir is containing Linux source code +make \ + -C /path/to/source_dir \ + ARCH=loongarch \ + O="$PWD" \ + arch/loongarch/ow_syscall/la_ow_syscall.ko \ + CONFIG_LOONGARCH_OW_SYSCALL=m +``` + +Upon completion, copy the kernel module in place +(`/lib/modules/.../arch/loongarch/ow_syscall/la_ow_syscall.ko`) and +re-generate modules.dep and map files: + +``` +depmod +``` + +### DKMS dynamic module + +Generate a `dkms.conf`: + +``` +make dkms.conf +``` + +For installation and version management, refer to dkms(8) for details. + +### Version-specific module + +Build the kernel module: + +``` +make +``` + +Load the module with super user or root privilege: + +``` +insmod la_ow_syscall.ko +``` diff --git a/arch/loongarch/ow_syscall/VERSION b/arch/loongarch/ow_syscall/VERSION new file mode 100644 index 00000000000000..17e51c385ea382 --- /dev/null +++ b/arch/loongarch/ow_syscall/VERSION @@ -0,0 +1 @@ +0.1.1 diff --git a/arch/loongarch/ow_syscall/dkms.conf.in b/arch/loongarch/ow_syscall/dkms.conf.in new file mode 100644 index 00000000000000..ad9aec128e6f8e --- /dev/null +++ b/arch/loongarch/ow_syscall/dkms.conf.in @@ -0,0 +1,11 @@ +PACKAGE_NAME="la_ow_syscall" +PACKAGE_VERSION="VERSION" + +BUILD_EXCLUSIVE_KERNEL_MIN="5.19" +BUILD_EXCLUSIVE_CONFIG="CONFIG_KALLSYMS" + +MAKE="KDIR=/lib/modules/${kernelver}/build make" +CLEAN="make clean" +BUILT_MODULE_NAME[0]="$PACKAGE_NAME" +AUTOINSTALL="yes" +DEST_MODULE_LOCATION[0]="/extra" diff --git a/arch/loongarch/ow_syscall/feat_test.c b/arch/loongarch/ow_syscall/feat_test.c new file mode 100644 index 00000000000000..5fc3b8709364d4 --- /dev/null +++ b/arch/loongarch/ow_syscall/feat_test.c @@ -0,0 +1,16 @@ +/* + This file is used to test if the coresponding macro is defined in unistd.h + If a macro is defined, the coresponding function will be defined, and + the symbol will be shown in the .o file. Later, the Kbuild script will + generate kernel_feature.h according to the existence of the symbols. +*/ + +#include +#ifdef __ARCH_WANT_NEW_STAT +void kernel_have_new_stat(void); +void kernel_have_new_stat(void){ } +#endif +#ifndef __SYSCALL +void kernel_have_systbl(void); +void kernel_have_systbl(void){ } +#endif diff --git a/arch/loongarch/ow_syscall/fsstat.c b/arch/loongarch/ow_syscall/fsstat.c new file mode 100644 index 00000000000000..1f5c336f52ec15 --- /dev/null +++ b/arch/loongarch/ow_syscall/fsstat.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include "fsstat.h" +#include "kernel_feature.h" + +#ifndef KERNEL_HAVE_NEW_STAT +#define INIT_STRUCT_STAT_PADDING(st) memset(&st, 0, sizeof(st)) + +struct __old_kernel_stat { + unsigned short st_dev; + unsigned short st_ino; + unsigned short st_mode; + unsigned short st_nlink; + unsigned short st_uid; + unsigned short st_gid; + unsigned short st_rdev; + unsigned int st_size; + unsigned int st_atime; + unsigned int st_mtime; + unsigned int st_ctime; +}; + +static int cp_new_stat(struct kstat *stat, struct stat __user *statbuf) +{ + struct stat tmp; + + if (sizeof(tmp.st_dev) < 4 && !old_valid_dev(stat->dev)) + return -EOVERFLOW; + if (sizeof(tmp.st_rdev) < 4 && !old_valid_dev(stat->rdev)) + return -EOVERFLOW; +#if BITS_PER_LONG == 32 + if (stat->size > MAX_NON_LFS) + return -EOVERFLOW; +#endif + + INIT_STRUCT_STAT_PADDING(tmp); + tmp.st_dev = new_encode_dev(stat->dev); + tmp.st_ino = stat->ino; + if (sizeof(tmp.st_ino) < sizeof(stat->ino) && tmp.st_ino != stat->ino) + return -EOVERFLOW; + tmp.st_mode = stat->mode; + tmp.st_nlink = stat->nlink; + if (tmp.st_nlink != stat->nlink) + return -EOVERFLOW; + SET_UID(tmp.st_uid, from_kuid_munged(current_user_ns(), stat->uid)); + SET_GID(tmp.st_gid, from_kgid_munged(current_user_ns(), stat->gid)); + tmp.st_rdev = new_encode_dev(stat->rdev); + tmp.st_size = stat->size; + tmp.st_atime = stat->atime.tv_sec; + tmp.st_mtime = stat->mtime.tv_sec; + tmp.st_ctime = stat->ctime.tv_sec; +#ifdef STAT_HAVE_NSEC + tmp.st_atime_nsec = stat->atime.tv_nsec; + tmp.st_mtime_nsec = stat->mtime.tv_nsec; + tmp.st_ctime_nsec = stat->ctime.tv_nsec; +#endif + tmp.st_blocks = stat->blocks; + tmp.st_blksize = stat->blksize; + return copy_to_user(statbuf, &tmp, sizeof(tmp)) ? -EFAULT : 0; +} + +__SYSCALL_DEFINEx(2, _newfstat, unsigned int, fd, struct stat __user *, statbuf) +{ + struct kstat stat; + int error = p_vfs_fstat(fd, &stat); + + if (!error) + error = cp_new_stat(&stat, statbuf); + + return error; +} + +__SYSCALL_DEFINEx(4, _newfstatat, int, dfd, const char __user *, filename, + struct stat __user *, statbuf, int, flag) +{ + struct kstat stat; + int error; + + error = p_vfs_fstatat(dfd, filename, &stat, flag); + if (error) + return error; + return cp_new_stat(&stat, statbuf); +} +#endif diff --git a/arch/loongarch/ow_syscall/fsstat.h b/arch/loongarch/ow_syscall/fsstat.h new file mode 100644 index 00000000000000..fa40914afa4141 --- /dev/null +++ b/arch/loongarch/ow_syscall/fsstat.h @@ -0,0 +1,4 @@ +#include +extern int (*p_vfs_fstatat)(int dfd, const char __user *filename, + struct kstat *stat, int flags); +extern int (*p_vfs_fstat)(int fd, struct kstat *stat); diff --git a/arch/loongarch/ow_syscall/la_ow_syscall_main.c b/arch/loongarch/ow_syscall/la_ow_syscall_main.c new file mode 100644 index 00000000000000..4ae3fc6d707851 --- /dev/null +++ b/arch/loongarch/ow_syscall/la_ow_syscall_main.c @@ -0,0 +1,274 @@ +#include /* Needed by all modules */ +#include /* Needed for KERN_INFO */ +#include /* Needed for the macros */ +#include /* Needed for kprobe calls */ +#include "module_version.h" + +///< The license type -- this affects runtime behavior +MODULE_LICENSE("GPL"); + +///< The author -- visible when you use modinfo +MODULE_AUTHOR("Miao Wang"); + +///< The description -- see modinfo +MODULE_DESCRIPTION("LoongArch old-world syscall compatibility module"); + +///< The version of the module +MODULE_VERSION(THIS_MODULE_VERSION); + +#include +#include + +#include "systable.h" + +#define __EXTERN +#include "fsstat.h" +#include "signal.h" + +#ifndef __loongarch64 +#error This Linux kernel module is only supported on LoongArch +#endif + +static unsigned long kallsyms_lookup_name_addr = 0; +static unsigned int allow_mod_unreg = 0; + +#include + +// Taken from https://github.com/zizzu0/LinuxKernelModules/blob/main/FindKallsymsLookupName.c +#define KPROBE_PRE_HANDLER(fname) \ + static int __kprobes fname(struct kprobe *p, struct pt_regs *regs) + +static struct kprobe kp0, kp1; + +KPROBE_PRE_HANDLER(handler_pre0) +{ + kallsyms_lookup_name_addr = regs->csr_era; + + return 0; +} + +KPROBE_PRE_HANDLER(handler_pre1) +{ + return 0; +} + +#undef KPROBE_PRE_HANDLER + +static int do_register_kprobe(struct kprobe *kp, char *symbol_name, + void *handler) +{ + int ret = 0; + + kp->symbol_name = symbol_name; + kp->pre_handler = handler; + + ret = register_kprobe(kp); + if (ret < 0) { + pr_err("register_probe() for symbol %s failed, returned %d\n", + symbol_name, ret); + return ret; + } + + pr_debug("planted kprobe for symbol %s at %p\n", symbol_name, kp->addr); + + return ret; +} + +static int __init kprobe_kallsyms_lookup_name(void) +{ + int ret = 0; + + ret = do_register_kprobe(&kp0, "kallsyms_lookup_name", handler_pre0); + if (ret < 0) + return ret; + + ret = do_register_kprobe(&kp1, "kallsyms_lookup_name", handler_pre1); + if (ret < 0) { + unregister_kprobe(&kp0); + return ret; + } + + unregister_kprobe(&kp0); + unregister_kprobe(&kp1); + + return ret; +} + + +static int __init find_kallsyms_lookup_name(void) +{ + char fn_name[KSYM_SYMBOL_LEN]; + + int ret = 0; + + if (kallsyms_lookup_name_addr == 0 || + kallsyms_lookup_name_addr == (unsigned long)-1) { + ret = kprobe_kallsyms_lookup_name(); + if ( ret < 0 ) { + return ret; + } + if (kallsyms_lookup_name_addr == 0 || + kallsyms_lookup_name_addr == (unsigned long)-1) { + return -EINVAL; + } + } + sprint_symbol(fn_name, kallsyms_lookup_name_addr); + if (strncmp(fn_name, "kallsyms_lookup_name+0x0", + strlen("kallsyms_lookup_name+0x0")) == 0) { + pr_debug("got kallsyms_lookup_name = %lx\n", + kallsyms_lookup_name_addr); + return 0; + } else { + pr_debug("got %s at %lx, not kallsyms_lookup_name\n", fn_name, + kallsyms_lookup_name_addr); + return -EINVAL; + } +} + +int (*p_vfs_fstatat)(int dfd, const char __user *filename, struct kstat *stat, + int flags); +int (*p_vfs_fstat)(int fd, struct kstat *stat); + +void *p_sys_setxattr, *p_sys_close, *p_sys_clone; + +static struct { + const char *func_name; + void **stor; +} relocation_table[] = { +#define __rel(func) \ + { \ + (#func), ((void **)&(p_##func)) \ + } + __rel(vfs_fstatat), __rel(vfs_fstat), + __rel(sys_setxattr), __rel(sys_close), + __rel(sys_clone), __rel(sys_rt_sigprocmask), + __rel(sys_rt_sigpending), __rel(sys_rt_sigtimedwait), + __rel(sys_rt_sigaction), __rel(sys_rt_sigsuspend), + __rel(sys_pselect6), __rel(sys_ppoll), +#ifdef CONFIG_SIGNALFD + __rel(sys_signalfd4), +#endif +#ifdef CONFIG_EPOLL + __rel(sys_epoll_pwait), __rel(sys_epoll_pwait2), +#endif +}; +#define nr_rel_tab (sizeof(relocation_table) / sizeof(relocation_table[0])) + +static void **p_sys_call_table; + +#include +#include +static int __init find_sys_call_table(void) +{ + unsigned long (*p_kallsyms_lookup_name)(const char *name) = + (void *)kallsyms_lookup_name_addr; + unsigned long *sys_table; + + if (kallsyms_lookup_name_addr == 0) { + return -ENOSYS; + } + + if ((sys_table = (unsigned long *)p_kallsyms_lookup_name( + "sys_call_table"))) { + p_sys_call_table = (void **)sys_table; + pr_debug("found sys_call_table=%px\n", p_sys_call_table); + return 0; + } + + pr_info("failed to find sys_call_table using kallsyms_lookup_name()\n"); + pr_info("trying to find sys_call_table using memory scanning\n"); + + for (sys_table = (void *)&jiffies; + (void *)sys_table < (void *)&reboot_mode; sys_table++) { + if (sys_table[__NR_setxattr] == (unsigned long)p_sys_setxattr && + sys_table[__NR_close] == (unsigned long)p_sys_close && + sys_table[__NR_clone] == (unsigned long)p_sys_clone) { + p_sys_call_table = (void **)sys_table; + pr_debug("found sys_call_table=%px\n", + p_sys_call_table); + return 0; + } + } + + return -ENOSYS; +} + +static int __init oldsyscall_start(void) +{ + unsigned long (*p_kallsyms_lookup_name)(const char *name); + int rc = find_kallsyms_lookup_name(); + if (rc < 0) { + return rc; + } + p_kallsyms_lookup_name = (void *)kallsyms_lookup_name_addr; + + for (int i = 0; i < nr_rel_tab; i++) { + unsigned long p = + p_kallsyms_lookup_name(relocation_table[i].func_name); + if (p == 0) { + pr_warn("cannot find symbol %s\n", + relocation_table[i].func_name); + return -EINVAL; + } + pr_debug("found symbol %s at %px\n", + relocation_table[i].func_name, (void *)p); + *relocation_table[i].stor = (void *)p; + } + rc = find_sys_call_table(); + if (rc < 0) { + return rc; + } + for (int i = 0; syscall_to_replace[i].syscall_num != -1; i++) { + if (syscall_to_replace[i].symbol_addr) { + continue; + } + const char *symbol_name = + sys_call_table_name[syscall_to_replace[i].syscall_num]; + unsigned long symbol_addr = p_kallsyms_lookup_name(symbol_name); + if (symbol_addr) { + pr_debug("found %s at %px\n", symbol_name, + (void *)symbol_addr); + } else { + pr_warn("cannot find symbol %s\n", symbol_name); + return -EINVAL; + } + syscall_to_replace[i].symbol_addr = (void *)symbol_addr; + } + if (!allow_mod_unreg) { + bool succ = try_module_get(THIS_MODULE); + if (!succ) { + return -EINVAL; + } + } + for (int i = 0; syscall_to_replace[i].syscall_num != -1; i++) { + pr_debug("will replace syscall_%ld with %px, orig %px\n", + syscall_to_replace[i].syscall_num, + syscall_to_replace[i].symbol_addr, + p_sys_call_table[syscall_to_replace[i].syscall_num]); + syscall_to_replace[i].orig = + p_sys_call_table[syscall_to_replace[i].syscall_num]; + p_sys_call_table[syscall_to_replace[i].syscall_num] = + syscall_to_replace[i].symbol_addr; + } + pr_info("la_ow_syscall module successfully initialized\n"); + return 0; +} + +static void __exit oldsyscall_end(void) +{ + for (int i = 0; syscall_to_replace[i].syscall_num != -1; i++) { + pr_debug("will restore syscall_%ld to %px\n", + syscall_to_replace[i].syscall_num, + syscall_to_replace[i].orig); + p_sys_call_table[syscall_to_replace[i].syscall_num] = + syscall_to_replace[i].orig; + } +} + +module_init(oldsyscall_start); +module_exit(oldsyscall_end); +module_param(allow_mod_unreg, uint, 0000); +MODULE_PARM_DESC(allow_mod_unreg, + "Allow this module to be unload (Danger! Debug use only)"); +module_param(kallsyms_lookup_name_addr, ulong, 0000); +MODULE_PARM_DESC(kallsyms_lookup_name_addr, "Address for kallsyms_lookup_name, provide this when unable to find using kprobe"); diff --git a/arch/loongarch/ow_syscall/signal.c b/arch/loongarch/ow_syscall/signal.c new file mode 100644 index 00000000000000..90eb34727680dc --- /dev/null +++ b/arch/loongarch/ow_syscall/signal.c @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include "signal.h" + +#define _LA_OW_NSIG 128 +#define _LA_OW_NSIG_WORDS (_LA_OW_NSIG / _NSIG_BPW) + +typedef struct { + unsigned long sig[_LA_OW_NSIG_WORDS]; +} _la_ow_sigset_t; + +static inline int clear_user_sigset_extension(sigset_t __user *to) +{ + char __user *expansion = (char __user *)to + sizeof(sigset_t); + int rc = clear_user(expansion, + sizeof(_la_ow_sigset_t) - sizeof(sigset_t)); + if (rc < 0) { + return -EFAULT; + } + return 0; +} + +__SYSCALL_DEFINEx(4, _rt_sigprocmask, int, how, sigset_t __user *, nset, + sigset_t __user *, oset, size_t, sigsetsize) +{ + if (sigsetsize == sizeof(sigset_t)) { + return p_sys_rt_sigprocmask(how, nset, oset, sigsetsize); + } else if (sigsetsize == sizeof(_la_ow_sigset_t)) { + int rc = + p_sys_rt_sigprocmask(how, nset, oset, sizeof(sigset_t)); + if (rc < 0) { + return rc; + } + if (oset) { + int rc2 = clear_user_sigset_extension(oset); + if (rc2 < 0) { + return rc2; + } + } + return 0; + } else { + return -EINVAL; + } +} + +__SYSCALL_DEFINEx(2, _rt_sigpending, sigset_t __user *, uset, size_t, + sigsetsize) +{ + if (sigsetsize == sizeof(sigset_t)) { + return p_sys_rt_sigpending(uset, sigsetsize); + } else if (sigsetsize == sizeof(_la_ow_sigset_t)) { + int rc = p_sys_rt_sigpending(uset, sizeof(sigset_t)); + if (rc < 0) { + return rc; + } + int rc2 = clear_user_sigset_extension(uset); + if (rc2 < 0) { + return rc2; + } + return 0; + } else { + return -EINVAL; + } +} + +__SYSCALL_DEFINEx(4, _rt_sigtimedwait, const sigset_t __user *, uthese, + siginfo_t __user *, uinfo, + const struct __kernel_timespec __user *, uts, size_t, + sigsetsize) +{ + if (sigsetsize == sizeof(sigset_t)) { + return p_sys_rt_sigtimedwait(uthese, uinfo, uts, sigsetsize); + } else if (sigsetsize == sizeof(_la_ow_sigset_t)) { + int rc = p_sys_rt_sigtimedwait(uthese, uinfo, uts, + sizeof(sigset_t)); + if (rc < 0) { + return rc; + } + return rc; + } else { + return -EINVAL; + } +} + +__SYSCALL_DEFINEx(4, _rt_sigaction, int, sig, const struct sigaction __user *, + act, struct sigaction __user *, oact, size_t, sigsetsize) +{ + if (sigsetsize == sizeof(sigset_t)) { + return p_sys_rt_sigaction(sig, act, oact, sigsetsize); + } else if (sigsetsize == sizeof(_la_ow_sigset_t)) { + int rc = p_sys_rt_sigaction(sig, act, oact, sizeof(sigset_t)); + if (rc < 0) { + return rc; + } + if (oact) { + int rc2 = clear_user_sigset_extension(&oact->sa_mask); + if (rc2 < 0) { + return rc2; + } + } + return rc; + } else { + return -EINVAL; + } +} + +__SYSCALL_DEFINEx(2, _rt_sigsuspend, sigset_t __user *, unewset, size_t, + sigsetsize) +{ + if (sigsetsize == sizeof(sigset_t)) { + return p_sys_rt_sigsuspend(unewset, sigsetsize); + } else if (sigsetsize == sizeof(_la_ow_sigset_t)) { + int rc = p_sys_rt_sigsuspend(unewset, sizeof(sigset_t)); + if (rc < 0) { + return rc; + } + return rc; + } else { + return -EINVAL; + } +} + +__SYSCALL_DEFINEx(6, _pselect6, int, n, fd_set __user *, inp, fd_set __user *, + outp, fd_set __user *, exp, struct __kernel_timespec __user *, + tsp, void __user *, sig) +{ + struct sigset_argpack { + sigset_t __user *p; + size_t size; + } x = { NULL, 0 }; + struct sigset_argpack __user *siginfo = + (struct sigset_argpack __user *)sig; + + if (siginfo) { + int rc = get_user(x.size, &siginfo->size); + if (rc < 0) { + return -EFAULT; + } + } + if (siginfo == NULL || x.size == sizeof(sigset_t)) { + return p_sys_pselect6(n, inp, outp, exp, tsp, sig); + } else if (x.size == sizeof(_la_ow_sigset_t)) { + int rc = put_user(sizeof(sigset_t), &siginfo->size); + if (rc < 0) { + return -EFAULT; + } + rc = p_sys_pselect6(n, inp, outp, exp, tsp, sig); + int rc2 = put_user(sizeof(_la_ow_sigset_t), &siginfo->size); + if (rc2 < 0) { + return -EFAULT; + } + return rc; + } else { + return -EINVAL; + } +} + +__SYSCALL_DEFINEx(5, _ppoll, struct pollfd __user *, ufds, unsigned int, nfds, + struct __kernel_timespec __user *, tsp, const sigset_t __user *, sigmask, + size_t, sigsetsize) +{ + if (sigmask == NULL || sigsetsize == sizeof(sigset_t)) { + return p_sys_ppoll(ufds, nfds, tsp, sigmask, sigsetsize); + } else if (sigsetsize == sizeof(_la_ow_sigset_t)) { + int rc = p_sys_ppoll(ufds, nfds, tsp, sigmask, sizeof(sigset_t)); + if (rc < 0) { + return rc; + } + return rc; + } else { + return -EINVAL; + } +} + +#ifdef CONFIG_EPOLL + +__SYSCALL_DEFINEx(6, _epoll_pwait, int, epfd, struct epoll_event __user *, + events, int, maxevents, int, timeout, const sigset_t __user *, + sigmask, size_t, sigsetsize) +{ + if (sigmask == NULL || sigsetsize == sizeof(sigset_t)) { + return p_sys_epoll_pwait(epfd, events, maxevents, timeout, + sigmask, sigsetsize); + } else if (sigsetsize == sizeof(_la_ow_sigset_t)) { + int rc = p_sys_epoll_pwait(epfd, events, maxevents, timeout, + sigmask, sizeof(sigset_t)); + if (rc < 0) { + return rc; + } + return rc; + } else { + return -EINVAL; + } +} + +__SYSCALL_DEFINEx(6, _epoll_pwait2, int, epfd, struct epoll_event __user *, + events, int, maxevents, + const struct __kernel_timespec __user *, timeout, + const sigset_t __user *, sigmask, size_t, sigsetsize) +{ + if (sigmask == NULL || sigsetsize == sizeof(sigset_t)) { + return p_sys_epoll_pwait2(epfd, events, maxevents, timeout, + sigmask, sigsetsize); + } else if (sigsetsize == sizeof(_la_ow_sigset_t)) { + int rc = p_sys_epoll_pwait2(epfd, events, maxevents, timeout, + sigmask, sizeof(sigset_t)); + if (rc < 0) { + return rc; + } + return rc; + } else { + return -EINVAL; + } +} + +#endif + +#ifdef CONFIG_SIGNALFD + +__SYSCALL_DEFINEx(4, _signalfd4, int, ufd, sigset_t __user *, user_mask, size_t, + sizemask, int, flags) +{ + if (sizemask == sizeof(sigset_t)) { + return p_sys_signalfd4(ufd, user_mask, sizemask, flags); + } else if (sizemask == sizeof(_la_ow_sigset_t)) { + int rc = p_sys_signalfd4(ufd, user_mask, sizeof(sigset_t), + flags); + if (rc < 0) { + return rc; + } + return rc; + } else { + return -EINVAL; + } +} + +#endif diff --git a/arch/loongarch/ow_syscall/signal.h b/arch/loongarch/ow_syscall/signal.h new file mode 100644 index 00000000000000..adcd53bedc662d --- /dev/null +++ b/arch/loongarch/ow_syscall/signal.h @@ -0,0 +1,44 @@ +#include + +#ifndef __EXTERN +#define __EXTERN extern +#endif +#define P__SYSCALL_DEFINEx(x, name, ...) \ + __EXTERN int (*p_sys##name)(__MAP(x, __SC_DECL, __VA_ARGS__)) + +P__SYSCALL_DEFINEx(4, _rt_sigprocmask, int, how, sigset_t __user *, nset, + sigset_t __user *, oset, size_t, sigsetsize); + +P__SYSCALL_DEFINEx(2, _rt_sigpending, sigset_t __user *, uset, size_t, + sigsetsize); + +P__SYSCALL_DEFINEx(4, _rt_sigtimedwait, const sigset_t __user *, uthese, + siginfo_t __user *, uinfo, + const struct __kernel_timespec __user *, uts, size_t, + sigsetsize); + +P__SYSCALL_DEFINEx(4, _rt_sigaction, int, sig, const struct sigaction __user *, + act, struct sigaction __user *, oact, size_t, sigsetsize); + +P__SYSCALL_DEFINEx(2, _rt_sigsuspend, sigset_t __user *, unewset, size_t, + sigsetsize); +P__SYSCALL_DEFINEx(6, _pselect6, int, n, fd_set __user *, inp, fd_set __user *, + outp, fd_set __user *, exp, + struct __kernel_timespec __user *, tsp, void __user *, sig); +P__SYSCALL_DEFINEx(5, _ppoll, struct pollfd __user *, ufds, unsigned int, nfds, + struct __kernel_timespec __user *, tsp, const sigset_t __user *, sigmask, + size_t, sigsetsize); +#ifdef CONFIG_SIGNALFD +P__SYSCALL_DEFINEx(4, _signalfd4, int, ufd, sigset_t __user *, user_mask, + size_t, sizemask, int, flags); +#endif +#ifdef CONFIG_EPOLL +P__SYSCALL_DEFINEx(6, _epoll_pwait, int, epfd, struct epoll_event __user *, + events, int, maxevents, int, timeout, + const sigset_t __user *, sigmask, size_t, sigsetsize); +P__SYSCALL_DEFINEx(6, _epoll_pwait2, int, epfd, struct epoll_event __user *, + events, int, maxevents, + const struct __kernel_timespec __user *, timeout, + const sigset_t __user *, sigmask, size_t, sigsetsize); +#undef P__SYSCALL_DEFINEx +#endif diff --git a/arch/loongarch/ow_syscall/systable.c b/arch/loongarch/ow_syscall/systable.c new file mode 100644 index 00000000000000..8b311954d62ef1 --- /dev/null +++ b/arch/loongarch/ow_syscall/systable.c @@ -0,0 +1,65 @@ +#include "systable.h" +#include "kernel_feature.h" + +/* + In 6.11 or newer, the asm-generic/unistd.h is not used by the loongarch + asm/unistd.h. Instead, the definition of the syscall table and the syscall + numbers is generated after commit 26a3b85bac08 ("loongarch: convert to + generic syscall table"). As a result, we can no more get the definition of + those syscalls that are not available on Loongarch. + + To solve this problem, we still use asm-generic/unistd.h to retrive the + definitions. + + To avoid any possible conflicts, only syscall related data are defined here. + No functions are implemented here, so the above hacks will not affect other + parts of our module and will not introduce unexpected changes related to the + syscall numbers, ensuring the consistence of our module with the kernel. +*/ + +#include + +/* + And we also need those hacks to prevent including asm/unistd.h, which will + cause redefinition of the syscall numbers. +*/ + +#define _LINUX_UNISTD_H_ /* prevent loading uapi/linux/unistd.h */ +#define __ASM_VDSO_H /* prevent loading asm/vdso.h */ +#define _ASM_SECCOMP_H /* prevent loading asm/seccomp.h */ +#include + +#define __ARCH_WANT_SET_GET_RLIMIT +#define __ARCH_WANT_NEW_STAT + +#undef __SYSCALL +#define __SYSCALL(nr, call) [nr] = (#call), + +const char *sys_call_table_name[__NR_syscalls] = { + [0 ... __NR_syscalls - 1] = "sys_ni_syscall", +#include +}; + +struct syscall_replace_table syscall_to_replace[] = { +#ifndef KERNEL_HAVE_NEW_STAT + { __NR_fstat, sys_newfstat }, + { __NR_newfstatat, sys_newfstatat }, +#endif + { __NR_getrlimit, NULL }, + { __NR_setrlimit, NULL }, + { __NR_rt_sigprocmask, sys_rt_sigprocmask }, + { __NR_rt_sigpending, sys_rt_sigpending }, + { __NR_rt_sigtimedwait, sys_rt_sigtimedwait }, + { __NR_rt_sigaction, sys_rt_sigaction }, + { __NR_rt_sigsuspend, sys_rt_sigsuspend }, + { __NR_pselect6, sys_pselect6 }, + { __NR_ppoll, sys_ppoll }, +#ifdef CONFIG_SIGNALFD + { __NR_signalfd4, sys_signalfd4 }, +#endif +#ifdef CONFIG_EPOLL + { __NR_epoll_pwait, sys_epoll_pwait }, + { __NR_epoll_pwait2, sys_epoll_pwait2 }, +#endif + { -1, NULL }, +}; diff --git a/arch/loongarch/ow_syscall/systable.h b/arch/loongarch/ow_syscall/systable.h new file mode 100644 index 00000000000000..f2b6602cc7ef4f --- /dev/null +++ b/arch/loongarch/ow_syscall/systable.h @@ -0,0 +1,8 @@ +struct syscall_replace_table { + long syscall_num; + void *symbol_addr; + void *orig; +}; + +extern const char *sys_call_table_name[]; +extern struct syscall_replace_table syscall_to_replace[]; diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 4364f3dba688fe..3ff89b82e2d7be 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -19,16 +19,18 @@ config MIPS select ARCH_HAS_TICK_BROADCAST if GENERIC_CLOCKEVENTS_BROADCAST select ARCH_HAS_UBSAN select ARCH_HAS_GCOV_PROFILE_ALL + select ARCH_SUPPORTS_NUMA_BALANCING if 64BIT select ARCH_KEEP_MEMBLOCK select ARCH_USE_BUILTIN_BSWAP select ARCH_USE_CMPXCHG_LOCKREF if 64BIT select ARCH_USE_MEMTEST select ARCH_USE_QUEUED_RWLOCKS select ARCH_USE_QUEUED_SPINLOCKS - select ARCH_SUPPORTS_HUGETLBFS if CPU_SUPPORTS_HUGEPAGES + select ARCH_SUPPORTS_HUGETLBFS if CPU_SUPPORTS_HUGEPAGES && !CPU_LOONGSON64 select ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT if MMU select ARCH_WANT_IPC_PARSE_VERSION select ARCH_WANT_LD_ORPHAN_WARN + select AUDIT_ARCH select BUILDTIME_TABLE_SORT select BUILTIN_DTB_ALL if BUILTIN_DTB select CLONE_BACKWARDS @@ -54,6 +56,7 @@ config MIPS select GENERIC_TIME_VSYSCALL select GUP_GET_PXX_LOW_HIGH if CPU_MIPS32 && PHYS_ADDR_T_64BIT select HAS_IOPORT if !NO_IOPORT_MAP || ISA + select HAVE_ARCH_AUDITSYSCALL select HAVE_ARCH_COMPILER_H select HAVE_ARCH_JUMP_LABEL select HAVE_ARCH_KGDB if MIPS_FP_SUPPORT @@ -1103,6 +1106,15 @@ config FW_ARC config ARCH_MAY_HAVE_PC_FDC bool +config AUDIT_ARCH + bool + +config AUDITSYSCALL_O32 + bool + +config AUDITSYSCALL_N32 + bool + config BOOT_RAW bool @@ -1373,6 +1385,7 @@ config CPU_LOONGSON64 select MIPS_FP_SUPPORT select GPIOLIB select SWIOTLB + select HAVE_PLAT_MEMCPY help The Loongson GSx64(GS264/GS464/GS464E/GS464V) series of processor cores implements the MIPS64R2 instruction set with many extensions, @@ -2126,9 +2139,9 @@ config ZBOOT_LOAD_ADDRESS config ARCH_FORCE_MAX_ORDER int "Maximum zone order" - default "13" if MIPS_HUGE_TLB_SUPPORT && PAGE_SIZE_64KB - default "12" if MIPS_HUGE_TLB_SUPPORT && PAGE_SIZE_32KB - default "11" if MIPS_HUGE_TLB_SUPPORT && PAGE_SIZE_16KB + default "13" if (NUMA_BALANCING || MIPS_HUGE_TLB_SUPPORT) && PAGE_SIZE_64KB + default "12" if (NUMA_BALANCING || MIPS_HUGE_TLB_SUPPORT) && PAGE_SIZE_32KB + default "11" if (NUMA_BALANCING || MIPS_HUGE_TLB_SUPPORT) && PAGE_SIZE_16KB default "10" help The kernel memory allocator divides physically contiguous memory @@ -2891,9 +2904,6 @@ config ARCH_SUPPORTS_KEXEC config ARCH_SUPPORTS_CRASH_DUMP def_bool y -config ARCH_DEFAULT_CRASH_DUMP - def_bool y - config PHYSICAL_START hex "Physical address where the kernel is loaded" default "0xffffffff84000000" @@ -3097,6 +3107,7 @@ config MIPS32_O32 select ARCH_WANT_OLD_COMPAT_IPC select COMPAT select MIPS32_COMPAT + select AUDITSYSCALL_O32 if AUDITSYSCALL help Select this option if you want to run o32 binaries. These are pure 32-bit binaries as used by the 32-bit Linux/MIPS port. Most of @@ -3110,6 +3121,7 @@ config MIPS32_N32 select ARCH_WANT_COMPAT_IPC_PARSE_VERSION select COMPAT select MIPS32_COMPAT + select AUDITSYSCALL_N32 if AUDITSYSCALL help Select this option if you want to run n32 binaries. These are 64-bit binaries using 32-bit quantities for addressing and certain diff --git a/arch/mips/Makefile.postlink b/arch/mips/Makefile.postlink index ea0add7d56b23a..2543679da14924 100644 --- a/arch/mips/Makefile.postlink +++ b/arch/mips/Makefile.postlink @@ -24,9 +24,6 @@ quiet_cmd_relocs = RELOCS $@ vmlinux vmlinux.unstripped: FORCE @true -ifeq ($(CONFIG_CPU_LOONGSON3_WORKAROUNDS),y) - $(call if_changed,ls3_llsc) -endif ifeq ($(CONFIG_RELOCATABLE),y) $(call if_changed,relocs) endif diff --git a/arch/mips/boot/dts/loongson/ls7a-pch.dtsi b/arch/mips/boot/dts/loongson/ls7a-pch.dtsi index 6dee85909f5a61..d41ca0b3d6511b 100644 --- a/arch/mips/boot/dts/loongson/ls7a-pch.dtsi +++ b/arch/mips/boot/dts/loongson/ls7a-pch.dtsi @@ -19,11 +19,13 @@ #interrupt-cells = <2>; }; - rtc0: rtc@100d0100 { - compatible = "loongson,ls7a-rtc"; - reg = <0 0x100d0100 0 0x78>; + lpc: interrupt-controller@10002000 { + compatible = "loongson,pch-lpc-1.0"; + reg = <0 0x10002000 0 0x1000>; + interrupt-controller; interrupt-parent = <&pic>; - interrupts = <52 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <19 IRQ_TYPE_LEVEL_HIGH>; + #interrupt-cells = <2>; }; ls7a_uart0: serial@10080000 { @@ -65,6 +67,13 @@ no-loopback-test; }; + rtc0: rtc@100d0100 { + compatible = "loongson,ls7a-rtc"; + reg = <0 0x100d0100 0 0x78>; + interrupt-parent = <&pic>; + interrupts = <52 IRQ_TYPE_LEVEL_HIGH>; + }; + pci@1a000000 { compatible = "loongson,ls7a-pci"; device_type = "pci"; diff --git a/arch/mips/include/asm/abi.h b/arch/mips/include/asm/abi.h index dba7f4b6bebfae..6e717a4a13ceb7 100644 --- a/arch/mips/include/asm/abi.h +++ b/arch/mips/include/asm/abi.h @@ -21,6 +21,7 @@ struct mips_abi { int (* const setup_rt_frame)(void *sig_return, struct ksignal *ksig, struct pt_regs *regs, sigset_t *set); const unsigned long restart; + const int audit_arch; unsigned off_sc_fpregs; unsigned off_sc_fpc_csr; diff --git a/arch/mips/include/asm/cpu-features.h b/arch/mips/include/asm/cpu-features.h index 3f11e5218e6c65..cbc1795e204779 100644 --- a/arch/mips/include/asm/cpu-features.h +++ b/arch/mips/include/asm/cpu-features.h @@ -658,6 +658,10 @@ # define cpu_has_mm_full __opt(MIPS_CPU_MM_FULL) #endif +#ifndef cpu_has_constant_timer +# define cpu_has_constant_timer __opt(MIPS_CPU_CONST_TIMER) +#endif + /* * Guest capabilities */ diff --git a/arch/mips/include/asm/cpu-info.h b/arch/mips/include/asm/cpu-info.h index 211b578af6aa0a..b7b60a84beb056 100644 --- a/arch/mips/include/asm/cpu-info.h +++ b/arch/mips/include/asm/cpu-info.h @@ -124,7 +124,9 @@ extern void cpu_report(void); extern void cpu_disable_mmid(void); extern const char *__cpu_name[]; +extern const char *__cpu_full_name[]; #define cpu_name_string() __cpu_name[raw_smp_processor_id()] +#define cpu_full_name_string() __cpu_full_name[raw_smp_processor_id()] struct seq_file; struct notifier_block; diff --git a/arch/mips/include/asm/cpu.h b/arch/mips/include/asm/cpu.h index 0fd9f9bbd21f1d..05e6ce0bb3fb75 100644 --- a/arch/mips/include/asm/cpu.h +++ b/arch/mips/include/asm/cpu.h @@ -420,6 +420,7 @@ enum cpu_type_enum { #define MIPS_CPU_MAC_2008_ONLY BIT_ULL(60) /* CPU Only support MAC2008 Fused multiply-add instruction */ #define MIPS_CPU_FTLBPAREX BIT_ULL(61) /* CPU has FTLB parity exception */ #define MIPS_CPU_GSEXCEX BIT_ULL(62) /* CPU has GSExc exception */ +#define MIPS_CPU_CONST_TIMER BIT_ULL(63) /* CPU has constant timer */ /* * CPU ASE encodings diff --git a/arch/mips/include/asm/mach-loongson64/boot_param.h b/arch/mips/include/asm/mach-loongson64/boot_param.h index 3a11ce85762be6..943c5762207324 100644 --- a/arch/mips/include/asm/mach-loongson64/boot_param.h +++ b/arch/mips/include/asm/mach-loongson64/boot_param.h @@ -66,6 +66,7 @@ struct efi_cpuinfo_loongson { u16 reserved_cores_mask; u32 cpu_clock_freq; /* cpu_clock */ u32 nr_cpus; + char cpuname[64]; } __packed; #define MAX_UARTS 64 diff --git a/arch/mips/include/asm/mach-loongson64/loongson_regs.h b/arch/mips/include/asm/mach-loongson64/loongson_regs.h index fec76750760492..47294ab78a2cd3 100644 --- a/arch/mips/include/asm/mach-loongson64/loongson_regs.h +++ b/arch/mips/include/asm/mach-loongson64/loongson_regs.h @@ -131,6 +131,9 @@ static inline u32 read_cpucfg(u32 reg) #define LOONGSON_CFG7_GCCAEQRP BIT(0) #define LOONGSON_CFG7_UCAWINP BIT(1) +#define CSR_TO_RAW_ADDR(cpu, csr) ((0x900000003ff00000 | csr | \ + (((u64)cpu & 0xc) << 42)) | (((u64)cpu & 0x3) << 8)) + static inline bool cpu_has_csr(void) { if (cpu_has_cfg()) @@ -247,6 +250,12 @@ static inline void csr_writeq(u64 val, u32 reg) #define CSR_MAIL_SEND_BUF_SHIFT 32 #define CSR_MAIL_SEND_H32_MASK 0xFFFFFFFF00000000ULL +#define LOONGSON_CSR_TIMER_CFG 0x1060 +#define LOONGSON_CSR_TIMER_TICK 0x1070 +#define CONSTANT_TIMER_CFG_PERIODIC (_ULCAST_(1) << 62) +#define CONSTANT_TIMER_CFG_EN (_ULCAST_(1) << 61) +#define CONSTANT_TIMER_INITVAL_RESET (_ULCAST_(0xffff) << 48) + static inline u64 drdtime(void) { int rID = 0; @@ -265,4 +274,25 @@ static inline u64 drdtime(void) return val; } +static inline unsigned int calc_const_freq(void) +{ + unsigned int res; + unsigned int base_freq; + unsigned int cfm, cfd; + + res = read_cpucfg(LOONGSON_CFG2); + if (!(res & LOONGSON_CFG2_LLFTP)) + return 0; + + res = read_cpucfg(LOONGSON_CFG5); + cfm = res & 0xffff; + cfd = (res >> 16) & 0xffff; + base_freq = read_cpucfg(LOONGSON_CFG4); + + if (!base_freq || !cfm || !cfd) + return 0; + else + return (base_freq * cfm / cfd); +} + #endif diff --git a/arch/mips/include/asm/mipsregs.h b/arch/mips/include/asm/mipsregs.h index 12a095dbf9e2a9..805efd3ea9697d 100644 --- a/arch/mips/include/asm/mipsregs.h +++ b/arch/mips/include/asm/mipsregs.h @@ -838,6 +838,7 @@ #define MTI_CONF6_SYND (_ULCAST_(1) << 13) /* Sleep state performance counter disable */ #define MTI_CONF6_SPCD (_ULCAST_(1) << 14) + /* proAptiv FTLB on/off bit */ #define MTI_CONF6_FTLBEN (_ULCAST_(1) << 15) /* Disable load/store bonding */ diff --git a/arch/mips/include/asm/pgtable-64.h b/arch/mips/include/asm/pgtable-64.h index 6e854bb11f37de..1f2c3e5d0ad436 100644 --- a/arch/mips/include/asm/pgtable-64.h +++ b/arch/mips/include/asm/pgtable-64.h @@ -260,7 +260,7 @@ static inline int pmd_present(pmd_t pmd) { #ifdef CONFIG_MIPS_HUGE_TLB_SUPPORT if (unlikely(pmd_val(pmd) & _PAGE_HUGE)) - return pmd_val(pmd) & _PAGE_PRESENT; + return pmd_val(pmd) & (_PAGE_PRESENT | _PAGE_PROTNONE); #endif return pmd_val(pmd) != (unsigned long) invalid_pte_table; diff --git a/arch/mips/include/asm/pgtable-bits.h b/arch/mips/include/asm/pgtable-bits.h index 088623ba7b8b17..9ee6e189d329fa 100644 --- a/arch/mips/include/asm/pgtable-bits.h +++ b/arch/mips/include/asm/pgtable-bits.h @@ -52,6 +52,9 @@ enum pgtable_bits { _PAGE_WRITE_SHIFT, _PAGE_ACCESSED_SHIFT, _PAGE_MODIFIED_SHIFT, +#if defined(CONFIG_ARCH_SUPPORTS_NUMA_BALANCING) + _PAGE_PROTNONE_SHIFT, +#endif #if defined(CONFIG_ARCH_HAS_PTE_SPECIAL) _PAGE_SPECIAL_SHIFT, #endif @@ -84,6 +87,9 @@ enum pgtable_bits { _PAGE_WRITE_SHIFT, _PAGE_ACCESSED_SHIFT, _PAGE_MODIFIED_SHIFT, +#if defined(CONFIG_ARCH_SUPPORTS_NUMA_BALANCING) + _PAGE_PROTNONE_SHIFT, +#endif #if defined(CONFIG_ARCH_HAS_PTE_SPECIAL) _PAGE_SPECIAL_SHIFT, #endif @@ -102,6 +108,9 @@ enum pgtable_bits { _PAGE_WRITE_SHIFT, _PAGE_ACCESSED_SHIFT, _PAGE_MODIFIED_SHIFT, +#if defined(CONFIG_ARCH_SUPPORTS_NUMA_BALANCING) + _PAGE_PROTNONE_SHIFT, +#endif #if defined(CONFIG_ARCH_HAS_PTE_SPECIAL) _PAGE_SPECIAL_SHIFT, #endif @@ -131,6 +140,9 @@ enum pgtable_bits { #if defined(CONFIG_MIPS_HUGE_TLB_SUPPORT) _PAGE_HUGE_SHIFT, #endif +#if defined(CONFIG_ARCH_SUPPORTS_NUMA_BALANCING) + _PAGE_PROTNONE_SHIFT, +#endif #if defined(CONFIG_ARCH_HAS_PTE_SPECIAL) _PAGE_SPECIAL_SHIFT, #endif @@ -158,6 +170,11 @@ enum pgtable_bits { #if defined(CONFIG_MIPS_HUGE_TLB_SUPPORT) # define _PAGE_HUGE (1 << _PAGE_HUGE_SHIFT) #endif +#if defined(CONFIG_ARCH_SUPPORTS_NUMA_BALANCING) +# define _PAGE_PROTNONE (1 <<_PAGE_PROTNONE_SHIFT) +#else +# define _PAGE_PROTNONE 0 +#endif #if defined(CONFIG_ARCH_HAS_PTE_SPECIAL) # define _PAGE_SPECIAL (1 << _PAGE_SPECIAL_SHIFT) #else diff --git a/arch/mips/include/asm/pgtable.h b/arch/mips/include/asm/pgtable.h index fa7b935f947ca0..66497e063fce1e 100644 --- a/arch/mips/include/asm/pgtable.h +++ b/arch/mips/include/asm/pgtable.h @@ -25,6 +25,9 @@ struct mm_struct; struct vm_area_struct; +#define PAGE_NONE __pgprot(_PAGE_PROTNONE | _PAGE_NO_READ | \ + _page_cachable_default) + #define PAGE_SHARED vm_get_page_prot(VM_READ|VM_WRITE|VM_SHARED) #define PAGE_KERNEL __pgprot(_PAGE_PRESENT | __READABLE | __WRITEABLE | \ @@ -160,7 +163,7 @@ static inline void pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *pt #else #define pte_none(pte) (!(pte_val(pte) & ~_PAGE_GLOBAL)) -#define pte_present(pte) (pte_val(pte) & _PAGE_PRESENT) +#define pte_present(pte) (pte_val(pte) & (_PAGE_PRESENT | _PAGE_PROTNONE)) #define pte_no_exec(pte) (pte_val(pte) & _PAGE_NO_EXEC) /* @@ -721,7 +724,7 @@ static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot) static inline pmd_t pmd_mkinvalid(pmd_t pmd) { - pmd_val(pmd) &= ~(_PAGE_PRESENT | _PAGE_VALID | _PAGE_DIRTY); + pmd_val(pmd) &= ~(_PAGE_PRESENT | _PAGE_VALID | _PAGE_PROTNONE | _PAGE_DIRTY); return pmd; } @@ -743,6 +746,18 @@ static inline pmd_t pmdp_huge_get_and_clear(struct mm_struct *mm, #endif /* CONFIG_TRANSPARENT_HUGEPAGE */ +#ifdef CONFIG_NUMA_BALANCING +static inline long pte_protnone(pte_t pte) +{ + return (pte_val(pte) & _PAGE_PROTNONE); +} + +static inline long pmd_protnone(pmd_t pmd) +{ + return (pmd_val(pmd) & _PAGE_PROTNONE); +} +#endif /* CONFIG_NUMA_BALANCING */ + #ifdef _PAGE_HUGE #define pmd_leaf(pmd) ((pmd_val(pmd) & _PAGE_HUGE) != 0) #define pud_leaf(pud) ((pud_val(pud) & _PAGE_HUGE) != 0) diff --git a/arch/mips/include/asm/time.h b/arch/mips/include/asm/time.h index 5e7193b759f337..72fec943a9e442 100644 --- a/arch/mips/include/asm/time.h +++ b/arch/mips/include/asm/time.h @@ -22,6 +22,8 @@ extern spinlock_t rtc_lock; */ extern void plat_time_init(void); +extern unsigned int mips_cpu_frequency; + /* * mips_hpt_frequency - must be set if you intend to use an R4k-compatible * counter as a timer interrupt source. @@ -40,11 +42,19 @@ extern int __weak get_c0_perfcount_int(void); */ extern unsigned int get_c0_compare_int(void); extern int r4k_clockevent_init(void); +#ifdef CONFIG_CPU_LOONGSON64 +extern int constant_clockevent_init(void); +#else +static inline int constant_clockevent_init(void) { return 0; } +#endif static inline int mips_clockevent_init(void) { #ifdef CONFIG_CEVT_R4K - return r4k_clockevent_init(); + if (!cpu_has_constant_timer) + return r4k_clockevent_init(); + else + return constant_clockevent_init(); #else return -ENXIO; #endif @@ -54,11 +64,19 @@ static inline int mips_clockevent_init(void) * Initialize the count register as a clocksource */ extern int init_r4k_clocksource(void); +#ifdef CONFIG_CPU_LOONGSON64 +extern int init_constant_clocksource(void); +#else +static inline int init_constant_clocksource(void) { return 0; } +#endif static inline __init int init_mips_clocksource(void) { #ifdef CONFIG_CSRC_R4K - return init_r4k_clocksource(); + if (!cpu_has_constant_timer) + return init_r4k_clocksource(); + else + return init_constant_clocksource(); #else return 0; #endif diff --git a/arch/mips/include/asm/uasm.h b/arch/mips/include/asm/uasm.h index b43bfd44525213..2573d559320e63 100644 --- a/arch/mips/include/asm/uasm.h +++ b/arch/mips/include/asm/uasm.h @@ -42,6 +42,10 @@ void uasm_i##op(u32 **buf, unsigned int a, signed int b, unsigned int c) #define Ip_s3s1s2(op) \ void uasm_i##op(u32 **buf, int a, int b, int c) +#define Ip_u4u2u1s3(op) \ +void uasm_i##op(u32 **buf, unsigned int a, unsigned int b, signed int c, \ + unsigned int d) + #define Ip_u2u1s3(op) \ void uasm_i##op(u32 **buf, unsigned int a, unsigned int b, signed int c) @@ -159,6 +163,7 @@ Ip_u2s3u1(_sd); Ip_u3u1u2(_seleqz); Ip_u3u1u2(_selnez); Ip_u2s3u1(_sh); +Ip_u4u2u1s3(_gssq); Ip_u2u1u3(_sll); Ip_u3u2u1(_sllv); Ip_s3s1s2(_slt); diff --git a/arch/mips/include/asm/unistd.h b/arch/mips/include/asm/unistd.h index 6a974b990f4b21..91d0c7fd6d4b05 100644 --- a/arch/mips/include/asm/unistd.h +++ b/arch/mips/include/asm/unistd.h @@ -64,4 +64,14 @@ #endif /* !__ASSEMBLER__ */ +#ifdef CONFIG_MIPS32_N32 +#define NR_syscalls (__NR_N32_Linux + __NR_N32_Linux_syscalls) +#elif defined(CONFIG_64BIT) +#define NR_syscalls (__NR_64_Linux + __NR_64_Linux_syscalls) +#elif defined(CONFIG_32BIT) +#define NR_syscalls (__NR_O32_Linux + __NR_O32_Linux_syscalls) +#else +#error Must know ABIs in use to define NR_syscalls +#endif + #endif /* _ASM_UNISTD_H */ diff --git a/arch/mips/include/asm/vdso/clocksource.h b/arch/mips/include/asm/vdso/clocksource.h index 510e1671d89851..7fd43ca06eb117 100644 --- a/arch/mips/include/asm/vdso/clocksource.h +++ b/arch/mips/include/asm/vdso/clocksource.h @@ -4,6 +4,7 @@ #define VDSO_ARCH_CLOCKMODES \ VDSO_CLOCKMODE_R4K, \ - VDSO_CLOCKMODE_GIC + VDSO_CLOCKMODE_GIC, \ + VDSO_CLOCKMODE_CONST #endif /* __ASM_VDSOCLOCKSOURCE_H */ diff --git a/arch/mips/include/asm/vdso/gettimeofday.h b/arch/mips/include/asm/vdso/gettimeofday.h index 32d2d173fdc0be..2dba88c691edd9 100644 --- a/arch/mips/include/asm/vdso/gettimeofday.h +++ b/arch/mips/include/asm/vdso/gettimeofday.h @@ -183,6 +183,45 @@ static __always_inline u64 read_gic_count(const struct vdso_time_data *data) #endif +/* + * HWR $30 (Loongson64 constant timer) is 64 bits wide. RDHWR never sign-extends + * the timer count before copying it into a GPR, as the kernel always sets + * Status.UX=1 even in O32 userspace. If it's kept as is, the "shadow" on the + * upper half breaks subsequent logical/branch instructions relying on the GPR. + * + * Thankfully, Status.UX==1 also means that we have the full timer count + * available and can use 64-bit instructions to salvage it into a pair of GPR + * to comply with O32 ABI. + */ +#ifdef CONFIG_CPU_LOONGSON64 +static __always_inline u64 read_const_count(void) +{ + u64 count; + +/* N32 ABI stores 64-bit value in a single register. */ +#if _MIPS_SIM == _MIPS_SIM_ABI64 || _MIPS_SIM == _MIPS_SIM_NABI32 + __asm__ __volatile__( + " .set push\n" + " .set mips32r2\n" + " rdhwr %0, $30\n" + " .set pop\n" + : "=r" (count)); +#else /* _MIPS_SIM == _MIPS_SIM_ABI32 */ + __asm__ __volatile__( + " .set push\n" + " .set mips64r2\n" /* Enable 64-bit instructions. */ + " .set noreorder\n" + " rdhwr %0, $30\n" /* Now %0 holds the full 64-bit count. */ + " dsra %D0, %0, 32\n" /* Salvage and sign-extend the upper half. */ + " sll %0, %0, 0\n" /* Sign-extend the lower half. */ + " .set pop\n" + : "=r" (count)); +#endif + + return count; +} +#endif + static __always_inline u64 __arch_get_hw_counter(s32 clock_mode, const struct vdso_time_data *vd) { @@ -193,6 +232,10 @@ static __always_inline u64 __arch_get_hw_counter(s32 clock_mode, #ifdef CONFIG_CLKSRC_MIPS_GIC if (clock_mode == VDSO_CLOCKMODE_GIC) return read_gic_count(vd); +#endif +#ifdef CONFIG_CPU_LOONGSON64 + if (clock_mode == VDSO_CLOCKMODE_CONST) + return read_const_count(); #endif /* * Core checks mode already. So this raced against a concurrent diff --git a/arch/mips/include/uapi/asm/inst.h b/arch/mips/include/uapi/asm/inst.h index c29dbc8c1d491e..6d41c8bbbdec2c 100644 --- a/arch/mips/include/uapi/asm/inst.h +++ b/arch/mips/include/uapi/asm/inst.h @@ -137,6 +137,13 @@ enum ddivu_op { ddivu_dmodu_op = 0x3, }; +/* + * func field of spec opcode. + */ +enum swc2_op { + gssq_op = 0x20, +}; + /* * rt field of bcond opcodes. */ diff --git a/arch/mips/include/uapi/asm/unistd.h b/arch/mips/include/uapi/asm/unistd.h index 4abe387549ad25..b501ea16ccd403 100644 --- a/arch/mips/include/uapi/asm/unistd.h +++ b/arch/mips/include/uapi/asm/unistd.h @@ -6,34 +6,37 @@ * * Copyright (C) 1995, 96, 97, 98, 99, 2000 by Ralf Baechle * Copyright (C) 1999, 2000 Silicon Graphics, Inc. - * - * Changed system calls macros _syscall5 - _syscall7 to push args 5 to 7 onto - * the stack. Robin Farine for ACN S.A, Copyright (C) 1996 by ACN S.A */ #ifndef _UAPI_ASM_UNISTD_H #define _UAPI_ASM_UNISTD_H #include -#if _MIPS_SIM == _MIPS_SIM_ABI32 +#if (defined(__WANT_SYSCALL_NUMBERS) && \ + (__WANT_SYSCALL_NUMBERS == _MIPS_SIM_ABI32)) || \ + (!defined(__WANT_SYSCALL_NUMBERS) && _MIPS_SIM == _MIPS_SIM_ABI32) #define __NR_Linux 4000 #include -#endif /* _MIPS_SIM == _MIPS_SIM_ABI32 */ +#endif /* Want O32 || _MIPS_SIM == _MIPS_SIM_ABI32 */ -#if _MIPS_SIM == _MIPS_SIM_ABI64 +#if (defined(__WANT_SYSCALL_NUMBERS) && \ + (__WANT_SYSCALL_NUMBERS == _MIPS_SIM_ABI64)) || \ + (!defined(__WANT_SYSCALL_NUMBERS) && _MIPS_SIM == _MIPS_SIM_ABI64) #define __NR_Linux 5000 #include -#endif /* _MIPS_SIM == _MIPS_SIM_ABI64 */ +#endif /* Want N64 || _MIPS_SIM == _MIPS_SIM_ABI64 */ -#if _MIPS_SIM == _MIPS_SIM_NABI32 +#if (defined(__WANT_SYSCALL_NUMBERS) && \ + (__WANT_SYSCALL_NUMBERS == _MIPS_SIM_NABI32)) || \ + (!defined(__WANT_SYSCALL_NUMBERS) && _MIPS_SIM == _MIPS_SIM_NABI32) #define __NR_Linux 6000 #include -#endif /* _MIPS_SIM == _MIPS_SIM_NABI32 */ +#endif /* Want N32 || _MIPS_SIM == _MIPS_SIM_NABI32 */ #endif /* _UAPI_ASM_UNISTD_H */ diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile index 95a1e674fd6785..b9786a289592cd 100644 --- a/arch/mips/kernel/Makefile +++ b/arch/mips/kernel/Makefile @@ -105,6 +105,10 @@ obj-$(CONFIG_HW_PERF_EVENTS) += perf_event_mipsxx.o obj-$(CONFIG_JUMP_LABEL) += jump_label.o obj-$(CONFIG_UPROBES) += uprobes.o +obj-$(CONFIG_AUDITSYSCALL_O32) += audit-o32.o +obj-$(CONFIG_AUDITSYSCALL_N32) += audit-n32.o +obj-$(CONFIG_AUDITSYSCALL) += audit-native.o + obj-$(CONFIG_MIPS_CM) += mips-cm.o obj-$(CONFIG_MIPS_CPC) += mips-cpc.o diff --git a/arch/mips/kernel/audit-n32.c b/arch/mips/kernel/audit-n32.c new file mode 100644 index 00000000000000..76c95ff924f83b --- /dev/null +++ b/arch/mips/kernel/audit-n32.c @@ -0,0 +1,59 @@ +#define __WANT_SYSCALL_NUMBERS _MIPS_SIM_NABI32 + +#include +#include +#include +#include +#include "audit-n32.h" + +static unsigned dir_class_n32[] = { +#include +~0U +}; + +static unsigned read_class_n32[] = { +#include +~0U +}; + +static unsigned write_class_n32[] = { +#include +~0U +}; + +static unsigned chattr_class_n32[] = { +#include +~0U +}; + +static unsigned signal_class_n32[] = { +#include +~0U +}; + +int audit_classify_syscall_n32(int abi, unsigned syscall) +{ + switch (syscall) { + case __NR_open: + return AUDITSC_OPEN; + case __NR_openat: + return AUDITSC_OPENAT; + case __NR_execve: + return AUDITSC_EXECVE; + default: + return AUDITSC_NATIVE; + } +} + +static int __init audit_classes_n32_init(void) +{ + audit_register_class(AUDIT_CLASS_WRITE_N32, write_class_n32); + audit_register_class(AUDIT_CLASS_READ_N32, read_class_n32); + audit_register_class(AUDIT_CLASS_DIR_WRITE_N32, dir_class_n32); + audit_register_class(AUDIT_CLASS_CHATTR_N32, chattr_class_n32); + audit_register_class(AUDIT_CLASS_SIGNAL_N32, signal_class_n32); + + return 0; +} + +__initcall(audit_classes_n32_init); diff --git a/arch/mips/kernel/audit-n32.h b/arch/mips/kernel/audit-n32.h new file mode 100644 index 00000000000000..781de2e9ad66be --- /dev/null +++ b/arch/mips/kernel/audit-n32.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifdef CONFIG_AUDITSYSCALL_N32 +int audit_classify_syscall_n32(int abi, unsigned syscall); +#endif /* CONFIG_AUDITSYSCALL_N32 */ diff --git a/arch/mips/kernel/audit-native.c b/arch/mips/kernel/audit-native.c new file mode 100644 index 00000000000000..763f0e6b3636aa --- /dev/null +++ b/arch/mips/kernel/audit-native.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include "audit-n32.h" +#include "audit-o32.h" + +static unsigned dir_class[] = { +#include +~0U +}; + +static unsigned read_class[] = { +#include +~0U +}; + +static unsigned write_class[] = { +#include +~0U +}; + +static unsigned chattr_class[] = { +#include +~0U +}; + +static unsigned signal_class[] = { +#include +~0U +}; + + +/* + * Pretend to be a single architecture + */ +int audit_classify_arch(int arch) +{ + return 0; +} + +int audit_classify_syscall(int abi, unsigned syscall) +{ + switch (syscall) { + case __NR_open: + return AUDITSC_OPEN; + case __NR_openat: + return AUDITSC_OPEN; +#ifdef __NR_socketcall /* Only exists on O32 */ + case __NR_socketcall: + return AUDITSC_SOCKETCALL; +#endif + case __NR_execve: + return AUDITSC_EXECVE; + default: +#ifdef CONFIG_AUDITSYSCALL_O32 + return audit_classify_syscall_o32(abi, syscall); +#endif +#ifdef CONFIG_AUDITSYSCALL_N32 + return audit_classify_syscall_n32(abi, syscall); +#endif + return AUDITSC_NATIVE; + } +} + +static int __init audit_classes_init(void) +{ + audit_register_class(AUDIT_CLASS_WRITE, write_class); + audit_register_class(AUDIT_CLASS_READ, read_class); + audit_register_class(AUDIT_CLASS_DIR_WRITE, dir_class); + audit_register_class(AUDIT_CLASS_CHATTR, chattr_class); + audit_register_class(AUDIT_CLASS_SIGNAL, signal_class); + + return 0; +} + +__initcall(audit_classes_init); diff --git a/arch/mips/kernel/audit-o32.c b/arch/mips/kernel/audit-o32.c new file mode 100644 index 00000000000000..818f91ce14acf3 --- /dev/null +++ b/arch/mips/kernel/audit-o32.c @@ -0,0 +1,61 @@ +#define __WANT_SYSCALL_NUMBERS _MIPS_SIM_ABI32 + +#include +#include +#include +#include +#include "audit-o32.h" + +static unsigned dir_class_o32[] = { +#include +~0U +}; + +static unsigned read_class_o32[] = { +#include +~0U +}; + +static unsigned write_class_o32[] = { +#include +~0U +}; + +static unsigned chattr_class_o32[] = { +#include +~0U +}; + +static unsigned signal_class_o32[] = { +#include +~0U +}; + +int audit_classify_syscall_o32(int abi, unsigned syscall) +{ + switch (syscall) { + case __NR_open: + return AUDITSC_OPEN; + case __NR_openat: + return AUDITSC_OPENAT; + case __NR_socketcall: + return AUDITSC_SOCKETCALL; + case __NR_execve: + return AUDITSC_EXECVE; + default: + return AUDITSC_NATIVE; + } +} + +static int __init audit_classes_o32_init(void) +{ + audit_register_class(AUDIT_CLASS_WRITE_32, write_class_o32); + audit_register_class(AUDIT_CLASS_READ_32, read_class_o32); + audit_register_class(AUDIT_CLASS_DIR_WRITE_32, dir_class_o32); + audit_register_class(AUDIT_CLASS_CHATTR_32, chattr_class_o32); + audit_register_class(AUDIT_CLASS_SIGNAL_32, signal_class_o32); + + return 0; +} + +__initcall(audit_classes_o32_init); diff --git a/arch/mips/kernel/audit-o32.h b/arch/mips/kernel/audit-o32.h new file mode 100644 index 00000000000000..8a782fdd1c6e78 --- /dev/null +++ b/arch/mips/kernel/audit-o32.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifdef CONFIG_AUDITSYSCALL_O32 +int audit_classify_syscall_o32(int abi, unsigned syscall); +#endif /* CONFIG_AUDITSYSCALL_O32 */ diff --git a/arch/mips/kernel/cpu-probe.c b/arch/mips/kernel/cpu-probe.c index 489612ed9d4987..3647315658b752 100644 --- a/arch/mips/kernel/cpu-probe.c +++ b/arch/mips/kernel/cpu-probe.c @@ -34,6 +34,10 @@ #include +#ifdef CONFIG_CPU_LOONGSON64 +#include +#endif + /* Hardware capabilities */ unsigned int elf_hwcap __read_mostly; EXPORT_SYMBOL_GPL(elf_hwcap); @@ -1253,32 +1257,44 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu) switch (c->processor_id & PRID_REV_MASK) { case PRID_REV_LOONGSON2E: c->cputype = CPU_LOONGSON2EF; - __cpu_name[cpu] = "ICT Loongson-2"; + __cpu_name[cpu] = "Loongson-2"; set_elf_platform(cpu, "loongson2e"); set_isa(c, MIPS_CPU_ISA_III); c->fpu_msk31 |= FPU_CSR_CONDX; + __cpu_full_name[cpu] = "Loongson-2E"; break; case PRID_REV_LOONGSON2F: c->cputype = CPU_LOONGSON2EF; - __cpu_name[cpu] = "ICT Loongson-2"; + __cpu_name[cpu] = "Loongson-2"; set_elf_platform(cpu, "loongson2f"); set_isa(c, MIPS_CPU_ISA_III); c->fpu_msk31 |= FPU_CSR_CONDX; + __cpu_full_name[cpu] = "Loongson-2F"; break; case PRID_REV_LOONGSON3A_R1: c->cputype = CPU_LOONGSON64; - __cpu_name[cpu] = "ICT Loongson-3"; + __cpu_name[cpu] = "Loongson-3"; set_elf_platform(cpu, "loongson3a"); set_isa(c, MIPS_CPU_ISA_M64R1); + __cpu_full_name[cpu] = "Loongson-3A R1 (Loongson-3A1000)"; c->ases |= (MIPS_ASE_LOONGSON_MMI | MIPS_ASE_LOONGSON_CAM | MIPS_ASE_LOONGSON_EXT); break; case PRID_REV_LOONGSON3B_R1: + c->cputype = CPU_LOONGSON64; + __cpu_name[cpu] = "Loongson-3"; + set_elf_platform(cpu, "loongson3b"); + set_isa(c, MIPS_CPU_ISA_M64R1); + __cpu_full_name[cpu] = "Loongson-3B R1 (Loongson-3B1000)"; + c->ases |= (MIPS_ASE_LOONGSON_MMI | MIPS_ASE_LOONGSON_CAM | + MIPS_ASE_LOONGSON_EXT); + break; case PRID_REV_LOONGSON3B_R2: c->cputype = CPU_LOONGSON64; - __cpu_name[cpu] = "ICT Loongson-3"; + __cpu_name[cpu] = "Loongson-3"; set_elf_platform(cpu, "loongson3b"); set_isa(c, MIPS_CPU_ISA_M64R1); + __cpu_full_name[cpu] = "Loongson-3B R2 (Loongson-3B1500)"; c->ases |= (MIPS_ASE_LOONGSON_MMI | MIPS_ASE_LOONGSON_CAM | MIPS_ASE_LOONGSON_EXT); break; @@ -1679,6 +1695,17 @@ static inline void decode_cpucfg(struct cpuinfo_mips *c) c->ases |= MIPS_ASE_LOONGSON_CAM; } +static void decode_loongson_cpucfg(struct cpuinfo_mips *c) +{ +#ifdef CONFIG_CPU_LOONGSON64 + unsigned int cpucfg; + + cpucfg = read_cpucfg(LOONGSON_CFG2); + if (cpucfg & LOONGSON_CFG2_LLFTP) + c->options |= MIPS_CPU_CONST_TIMER; +#endif +} + static inline void cpu_probe_loongson(struct cpuinfo_mips *c, unsigned int cpu) { c->cputype = CPU_LOONGSON64; @@ -1706,15 +1733,17 @@ static inline void cpu_probe_loongson(struct cpuinfo_mips *c, unsigned int cpu) switch (c->processor_id & PRID_REV_MASK) { case PRID_REV_LOONGSON3A_R2_0: case PRID_REV_LOONGSON3A_R2_1: - __cpu_name[cpu] = "ICT Loongson-3"; + __cpu_name[cpu] = "Loongson-3"; set_elf_platform(cpu, "loongson3a"); set_isa(c, MIPS_CPU_ISA_M64R2); + __cpu_full_name[cpu] = "Loongson-3A R2 (Loongson-3A2000)"; break; case PRID_REV_LOONGSON3A_R3_0: case PRID_REV_LOONGSON3A_R3_1: - __cpu_name[cpu] = "ICT Loongson-3"; + __cpu_name[cpu] = "Loongson-3"; set_elf_platform(cpu, "loongson3a"); set_isa(c, MIPS_CPU_ISA_M64R2); + __cpu_full_name[cpu] = "Loongson-3A R3 (Loongson-3A3000)"; break; } /* @@ -1733,10 +1762,12 @@ static inline void cpu_probe_loongson(struct cpuinfo_mips *c, unsigned int cpu) LOONGSON_CONF6_INTIMER); break; case PRID_IMP_LOONGSON_64G: - __cpu_name[cpu] = "ICT Loongson-3"; + __cpu_name[cpu] = "Loongson-3"; set_elf_platform(cpu, "loongson3a"); set_isa(c, MIPS_CPU_ISA_M64R2); + __cpu_full_name[cpu] = "Loongson-3A R4 (Loongson-3A4000)"; decode_cpucfg(c); + decode_loongson_cpucfg(c); change_c0_config6(LOONGSON_CONF6_EXTIMER | LOONGSON_CONF6_INTIMER, LOONGSON_CONF6_INTIMER); break; @@ -1841,6 +1872,7 @@ EXPORT_SYMBOL(__ua_limit); #endif const char *__cpu_name[NR_CPUS]; +const char *__cpu_full_name[NR_CPUS]; const char *__elf_platform; const char *__elf_base_platform; diff --git a/arch/mips/kernel/fpu-probe.c b/arch/mips/kernel/fpu-probe.c index 6bf3f19b1c335d..404d7531205824 100644 --- a/arch/mips/kernel/fpu-probe.c +++ b/arch/mips/kernel/fpu-probe.c @@ -144,7 +144,12 @@ static void cpu_set_fpu_2008(struct cpuinfo_mips *c) * IEEE 754 conformance mode to use. Affects the NaN encoding and the * ABS.fmt/NEG.fmt execution mode. */ -static enum { STRICT, EMULATED, LEGACY, STD2008, RELAXED } ieee754 = STRICT; +enum ieee754_mode { STRICT, EMULATED, LEGACY, STD2008, RELAXED }; +#ifdef CONFIG_CPU_LOONGSON64 +static enum ieee754_mode ieee754 = RELAXED; +#else +static enum ieee754_mode ieee754 = STRICT; +#endif /* * Set the IEEE 754 NaN encodings and the ABS.fmt/NEG.fmt execution modes diff --git a/arch/mips/kernel/proc.c b/arch/mips/kernel/proc.c index 8f0a0001540c7b..9b2e85a2a932e6 100644 --- a/arch/mips/kernel/proc.c +++ b/arch/mips/kernel/proc.c @@ -15,6 +15,7 @@ #include #include #include +#include unsigned int vced_count, vcei_count; @@ -63,6 +64,11 @@ static int show_cpuinfo(struct seq_file *m, void *v) seq_printf(m, fmt, __cpu_name[n], (version >> 4) & 0x0f, version & 0x0f, (fp_vers >> 4) & 0x0f, fp_vers & 0x0f); + if (__cpu_full_name[n]) + seq_printf(m, "model name\t\t: %s\n", __cpu_full_name[n]); + if (mips_cpu_frequency) + seq_printf(m, "CPU MHz\t\t\t: %u.%02u\n", + mips_cpu_frequency / 1000000, (mips_cpu_frequency / 10000) % 100); seq_printf(m, "BogoMIPS\t\t: %u.%02u\n", cpu_data[n].udelay_val / (500000/HZ), (cpu_data[n].udelay_val / (5000/HZ)) % 100); diff --git a/arch/mips/kernel/signal.c b/arch/mips/kernel/signal.c index 4a10f18a880604..c803c39f5cbe50 100644 --- a/arch/mips/kernel/signal.c +++ b/arch/mips/kernel/signal.c @@ -8,6 +8,7 @@ * Copyright (C) 1999, 2000 Silicon Graphics, Inc. * Copyright (C) 2014, Imagination Technologies Ltd. */ +#include #include #include #include @@ -805,6 +806,23 @@ struct mips_abi mips_abi = { #endif .setup_rt_frame = setup_rt_frame, .restart = __NR_restart_syscall, +#ifdef CONFIG_64BIT +# ifdef __BIG_ENDIAN + .audit_arch = AUDIT_ARCH_MIPS64, +# elif defined(__LITTLE_ENDIAN) + .audit_arch = AUDIT_ARCH_MIPSEL64, +# else +# error "Neither big nor little endian ???" +# endif +#else +# ifdef __BIG_ENDIAN + .audit_arch = AUDIT_ARCH_MIPS, +# elif defined(__LITTLE_ENDIAN) + .audit_arch = AUDIT_ARCH_MIPSEL, +# else +# error "Neither big nor little endian ???" +# endif +#endif .off_sc_fpregs = offsetof(struct sigcontext, sc_fpregs), .off_sc_fpc_csr = offsetof(struct sigcontext, sc_fpc_csr), diff --git a/arch/mips/kernel/signal_n32.c b/arch/mips/kernel/signal_n32.c index 139d2596b0d409..fb9ddefae9648c 100644 --- a/arch/mips/kernel/signal_n32.c +++ b/arch/mips/kernel/signal_n32.c @@ -2,6 +2,7 @@ /* * Copyright (C) 2003 Broadcom Corporation */ +#include #include #include #include @@ -137,6 +138,13 @@ static int setup_rt_frame_n32(void *sig_return, struct ksignal *ksig, struct mips_abi mips_abi_n32 = { .setup_rt_frame = setup_rt_frame_n32, .restart = __NR_N32_restart_syscall, +#ifdef __BIG_ENDIAN + .audit_arch = AUDIT_ARCH_MIPS64N32, +#elif defined(__LITTLE_ENDIAN) + .audit_arch = AUDIT_ARCH_MIPSEL64N32, +#else +# error "Neither big nor little endian ???" +#endif .off_sc_fpregs = offsetof(struct sigcontext, sc_fpregs), .off_sc_fpc_csr = offsetof(struct sigcontext, sc_fpc_csr), diff --git a/arch/mips/kernel/signal_o32.c b/arch/mips/kernel/signal_o32.c index 4f04584596507a..ea3f280a18aa0d 100644 --- a/arch/mips/kernel/signal_o32.c +++ b/arch/mips/kernel/signal_o32.c @@ -8,6 +8,7 @@ * Copyright (C) 1999, 2000 Silicon Graphics, Inc. * Copyright (C) 2016, Imagination Technologies Ltd. */ +#include #include #include #include @@ -245,6 +246,13 @@ struct mips_abi mips_abi_32 = { .setup_frame = setup_frame_32, .setup_rt_frame = setup_rt_frame_32, .restart = __NR_O32_restart_syscall, +#ifdef __BIG_ENDIAN + .audit_arch = AUDIT_ARCH_MIPS, +#elif defined(__LITTLE_ENDIAN) + .audit_arch = AUDIT_ARCH_MIPSEL, +#else +# error "Neither big nor little endian ???" +#endif .off_sc_fpregs = offsetof(struct sigcontext32, sc_fpregs), .off_sc_fpc_csr = offsetof(struct sigcontext32, sc_fpc_csr), diff --git a/arch/mips/kernel/time.c b/arch/mips/kernel/time.c index ed339d7979f3f5..849c6be53b2968 100644 --- a/arch/mips/kernel/time.c +++ b/arch/mips/kernel/time.c @@ -120,6 +120,8 @@ EXPORT_SYMBOL(perf_irq); * 2) calculate a couple of cached variables for later usage */ +unsigned int mips_cpu_frequency; +EXPORT_SYMBOL_GPL(mips_cpu_frequency); unsigned int mips_hpt_frequency; EXPORT_SYMBOL_GPL(mips_hpt_frequency); diff --git a/arch/mips/kernel/unaligned.c b/arch/mips/kernel/unaligned.c index db652c99b72e32..4f2055afbd7e83 100644 --- a/arch/mips/kernel/unaligned.c +++ b/arch/mips/kernel/unaligned.c @@ -1514,14 +1514,37 @@ static void emulate_load_store_MIPS16e(struct pt_regs *regs, void __user * addr) force_sig(SIGILL); } +static inline bool addr_legal(unsigned long addr, bool user) +{ + if (addr < TASK_SIZE) + return true; + + if (user) + return false; + + if (addr >= CKSEG3) + return true; + +#ifdef CONFIG_64BIT + if (addr >= XKPHYS && addr < CKSSEG) + return true; +#else + if (addr >= CKSEG0 && addr < CKSEG2) + return true; +#endif + + return false; +} + asmlinkage void do_ade(struct pt_regs *regs) { enum ctx_state prev_state; unsigned int *pc; prev_state = exception_enter(); - perf_sw_event(PERF_COUNT_SW_ALIGNMENT_FAULTS, - 1, regs, regs->cp0_badvaddr); + + if (!addr_legal(regs->cp0_badvaddr, user_mode(regs))) + goto sigbus; #ifdef CONFIG_64BIT /* @@ -1545,6 +1568,9 @@ asmlinkage void do_ade(struct pt_regs *regs) if (regs->cp0_badvaddr == regs->cp0_epc) goto sigbus; + perf_sw_event(PERF_COUNT_SW_ALIGNMENT_FAULTS, + 1, regs, regs->cp0_badvaddr); + if (user_mode(regs) && !test_thread_flag(TIF_FIXADE)) goto sigbus; if (unaligned_action == UNALIGNED_ACTION_SIGNAL) diff --git a/arch/mips/lib/Makefile b/arch/mips/lib/Makefile index 5d5b993cbc2bff..b5532bab9337bd 100644 --- a/arch/mips/lib/Makefile +++ b/arch/mips/lib/Makefile @@ -10,6 +10,7 @@ lib-y += bitops.o csum_partial.o delay.o memcpy.o memset.o \ obj-y += iomap_copy.o obj-$(CONFIG_PCI) += iomap-pci.o lib-$(CONFIG_GENERIC_CSUM) := $(filter-out csum_partial.o, $(lib-y)) +lib-$(CONFIG_CPU_LOONGSON64) := $(filter-out memset.o, $(lib-y)) obj-$(CONFIG_CPU_GENERIC_DUMP_TLB) += dump_tlb.o obj-$(CONFIG_CPU_R3000) += r3k_dump_tlb.o diff --git a/arch/mips/loongson64/Makefile b/arch/mips/loongson64/Makefile index cbba30dfddf5d0..438828f554a259 100644 --- a/arch/mips/loongson64/Makefile +++ b/arch/mips/loongson64/Makefile @@ -4,6 +4,8 @@ # obj-$(CONFIG_MACH_LOONGSON64) += cop2-ex.o dma.o \ setup.o init.o env.o time.o reset.o \ + constant_timer.o \ + loongson3-memset.o loongson3-memcpy.o obj-$(CONFIG_SMP) += smp.o obj-$(CONFIG_NUMA) += numa.o diff --git a/arch/mips/loongson64/constant_timer.c b/arch/mips/loongson64/constant_timer.c new file mode 100644 index 00000000000000..c1fda4a3ae1657 --- /dev/null +++ b/arch/mips/loongson64/constant_timer.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int constant_timer_irq_installed; + +static DEFINE_SPINLOCK(state_lock); +DEFINE_PER_CPU(struct clock_event_device, constant_clockevent_device); + +static void constant_event_handler(struct clock_event_device *dev) +{ +} + +static irqreturn_t constant_timer_interrupt(int irq, void *data) +{ + int cpu = smp_processor_id(); + const int r2 = cpu_has_mips_r2_r6; + struct clock_event_device *cd; + + if ((cp0_perfcount_irq < 0) && perf_irq() == IRQ_HANDLED && !r2) + return IRQ_HANDLED; + + /* + * The same applies to performance counter interrupts. But with the + * above we now know that the reason we got here must be a timer + * interrupt. Being the paranoiacs we are we check anyway. + */ + if (!r2 || (read_c0_cause() & CAUSEF_TI)) { + /* Clear Count/Compare Interrupt */ + write_c0_compare(read_c0_compare()); + cd = &per_cpu(constant_clockevent_device, cpu); + cd->event_handler(cd); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int constant_set_state_oneshot(struct clock_event_device *evt) +{ + unsigned int raw_cpuid; + unsigned long cfg, addr; + + spin_lock(&state_lock); + + raw_cpuid = cpu_logical_map(smp_processor_id()); + addr = CSR_TO_RAW_ADDR(raw_cpuid, LOONGSON_CSR_TIMER_CFG); + + if (!cpu_has_csr()) + cfg = readq((void *)addr); + else + cfg = csr_readq(LOONGSON_CSR_TIMER_CFG); + + /* set timer type + * 1 : periodic interrupt + * 0 : non-periodic(oneshot) interrupt + */ + cfg |= CONSTANT_TIMER_CFG_EN; + cfg &= ~CONSTANT_TIMER_CFG_PERIODIC; + + if (!cpu_has_csr()) + writeq(cfg, (void *)addr); + else + csr_writeq(cfg, LOONGSON_CSR_TIMER_CFG); + + spin_unlock(&state_lock); + + return 0; +} + +static int constant_set_state_oneshot_stopped(struct clock_event_device *evt) +{ + return 0; +} + +static int constant_set_state_periodic(struct clock_event_device *evt) +{ + unsigned int period; + unsigned int raw_cpuid; + unsigned long cfg, addr; + + spin_lock(&state_lock); + + raw_cpuid = cpu_logical_map(smp_processor_id()); + addr = CSR_TO_RAW_ADDR(raw_cpuid, LOONGSON_CSR_TIMER_CFG); + + if (!cpu_has_csr()) + cfg = readq((void *)addr); + else + cfg = csr_readq(LOONGSON_CSR_TIMER_CFG); + + cfg &= CONSTANT_TIMER_INITVAL_RESET; + cfg |= (CONSTANT_TIMER_CFG_PERIODIC | CONSTANT_TIMER_CFG_EN); + + period = calc_const_freq(); + if (!period) + period = cpu_clock_freq; + + period = period / HZ; + + if (!cpu_has_csr()) + writeq(cfg | period, (void *)addr); + else + csr_writeq(cfg | period, LOONGSON_CSR_TIMER_CFG); + + spin_unlock(&state_lock); + + return 0; +} + +static int constant_set_state_shutdown(struct clock_event_device *evt) +{ + return 0; +} + +static int constant_next_event(unsigned long delta, + struct clock_event_device *evt) +{ + unsigned long addr; + unsigned int raw_cpuid; + + raw_cpuid = cpu_logical_map(smp_processor_id()); + addr = CSR_TO_RAW_ADDR(raw_cpuid, LOONGSON_CSR_TIMER_CFG); + + writeq(delta | CONSTANT_TIMER_CFG_EN, (void *)addr); + + return 0; +} + +static int csr_constant_next_event(unsigned long delta, + struct clock_event_device *evt) +{ + csr_writeq(delta | CONSTANT_TIMER_CFG_EN, LOONGSON_CSR_TIMER_CFG); + return 0; +} + +int constant_clockevent_init(void) +{ + unsigned int irq; + unsigned int config; + unsigned int const_freq; + unsigned long min_delta = 0x600; + unsigned long max_delta = (1UL << 48) - 1; + unsigned int cpu = smp_processor_id(); + struct clock_event_device *cd; + int err; + + config = read_c0_config6(); + config |= LOONGSON_CONF6_EXTIMER; + write_c0_config6(config); + + const_freq = calc_const_freq(); + if (!const_freq) + const_freq = cpu_clock_freq; + + irq = get_c0_compare_int(); + + cd = &per_cpu(constant_clockevent_device, cpu); + + cd->name = "Constant"; + cd->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC | + CLOCK_EVT_FEAT_PERCPU; + + cd->rating = 320; + cd->irq = irq; + cd->cpumask = cpumask_of(cpu); + cd->set_state_oneshot = constant_set_state_oneshot; + cd->set_state_oneshot_stopped = constant_set_state_oneshot_stopped; + cd->set_state_periodic = constant_set_state_periodic; + cd->set_state_shutdown = constant_set_state_shutdown; + if (!cpu_has_csr()) + cd->set_next_event = constant_next_event; + else + cd->set_next_event = csr_constant_next_event; + + cd->event_handler = constant_event_handler; + + clockevents_config_and_register(cd, const_freq, min_delta, max_delta); + + if (constant_timer_irq_installed) + return 0; + + constant_timer_irq_installed = 1; + + err = request_irq(irq, constant_timer_interrupt, + IRQF_PERCPU | IRQF_TIMER | IRQF_SHARED, + "timer", cd); + if (err) { + pr_err("loongson64: setup irq for constant_clock_event failed: %d\n", err); + return err; + } + + return 0; +} + +static u64 read_const_counter(struct clocksource *clk) +{ + u64 count; + + __asm__ __volatile__( + " .set push\n" + " .set mips32r2\n" + " rdhwr %0, $30\n" + " .set pop\n" + : "=r" (count)); + + return count; +} + +static struct clocksource clocksource_const = { + .name = "Constant", + .rating = 400, + .read = read_const_counter, + .mask = CLOCKSOURCE_MASK(64), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .mult = 0, + .shift = 10, +}; + +static u64 read_sched_clock(void) +{ + u64 count; + + __asm__ __volatile__( + " .set push\n" + " .set mips32r2\n" + " rdhwr %0, $30\n" + " .set pop\n" + : "=r" (count)); + + return count; +} + +int __init init_constant_clocksource(void) +{ + int res; + unsigned long freq; + + freq = calc_const_freq(); + if (freq) + freq = cpu_clock_freq; + + clocksource_const.mult = + clocksource_hz2mult(freq, clocksource_const.shift); + + res = clocksource_register_hz(&clocksource_const, freq); + + sched_clock_register(read_sched_clock, 64, freq); + clocksource_const.vdso_clock_mode = VDSO_CLOCKMODE_CONST; + + return res; +} diff --git a/arch/mips/loongson64/env.c b/arch/mips/loongson64/env.c index 7abcca7ab4ed44..46bc2ffac652c0 100644 --- a/arch/mips/loongson64/env.c +++ b/arch/mips/loongson64/env.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ u32 cpu_clock_freq; EXPORT_SYMBOL(cpu_clock_freq); +char cpu_full_name[64]; struct efi_memory_map_loongson *loongson_memmap; struct loongson_system_configuration loongson_sysconf; @@ -171,6 +173,7 @@ static void __init lefi_fixup_fdt(struct system_loongson *system) void __init prom_lefi_init_env(void) { + char freq[12]; struct boot_params *boot_p; struct loongson_params *loongson_p; struct system_loongson *esys; @@ -265,6 +268,10 @@ void __init prom_lefi_init_env(void) loongson_sysconf.nr_nodes = (loongson_sysconf.nr_cpus + loongson_sysconf.cores_per_node - 1) / loongson_sysconf.cores_per_node; + if (!strncmp(ecpu->cpuname, "Loongson", 8)) + strncpy(cpu_full_name, ecpu->cpuname, sizeof(cpu_full_name)); + if (cpu_full_name[0] == 0) + strncpy(cpu_full_name, __cpu_full_name[0], sizeof(cpu_full_name)); loongson_sysconf.dma_mask_bits = eirq_source->dma_mask_bits; if (loongson_sysconf.dma_mask_bits < 32 || @@ -288,8 +295,14 @@ void __init prom_lefi_init_env(void) loongson_sysconf.workarounds |= esys->workarounds; + mips_cpu_frequency = cpu_clock_freq; pr_info("CpuClock = %u\n", cpu_clock_freq); + /* Append default cpu frequency with round-off */ + sprintf(freq, " @ %uMHz", (cpu_clock_freq + 500000) / 1000000); + strncat(cpu_full_name, freq, sizeof(cpu_full_name)); + __cpu_full_name[0] = cpu_full_name; + /* Read the ID of PCI host bridge to detect bridge type */ id = readl(HOST_BRIDGE_CONFIG_ADDR); vendor = id & 0xffff; diff --git a/arch/mips/loongson64/init.c b/arch/mips/loongson64/init.c index c7cc5a3d7817f1..9205195acca0b2 100644 --- a/arch/mips/loongson64/init.c +++ b/arch/mips/loongson64/init.c @@ -23,6 +23,16 @@ u32 node_id_offset; +static void __init mips_ebase_setup(void) +{ + ebase = CKSEG0; + + if (cpu_has_ebase_wg) + write_c0_ebase(ebase | MIPS_EBASE_WG); + + write_c0_ebase(ebase); +} + static void __init mips_nmi_setup(void) { void *base; @@ -147,6 +157,7 @@ void __init prom_init(void) setup_8250_early_printk_port(TO_UNCAC(LOONGSON_REG_BASE + 0x1e0), 0, 1024); register_smp_ops(&loongson3_smp_ops); + board_ebase_setup = mips_ebase_setup; board_nmi_handler_setup = mips_nmi_setup; } diff --git a/arch/mips/loongson64/loongson3-memcpy.S b/arch/mips/loongson64/loongson3-memcpy.S new file mode 100644 index 00000000000000..c5dd2e2866c3c7 --- /dev/null +++ b/arch/mips/loongson64/loongson3-memcpy.S @@ -0,0 +1,508 @@ +/* + ============================================================================ + Name : memcpy.S + Author : Heiher + Chen Jie + Version : 20140307 + Copyright : GPLv2 + Description : The memcpy for Loongson 3. + ============================================================================ + */ + +#define LINUX_KERNEL +#ifndef LINUX_KERNEL +#include +#include + +#define EXC(inst_reg,addr,handler) \ + inst_reg, addr; +#define EXCQ(inst,reg1,reg2,addr,handler) \ + inst reg1, reg2, addr; + +#if _MIPS_SIM == _ABI64 +#define CONFIG_64BIT +#else +#define CONFIG_32BIT +#endif + +#define FEXPORT(symbol) + +#else /* LINUX_KERNEL */ + +#include +#include +#include +#include + +#define EXC(inst_reg,addr,handler) \ +9: inst_reg, addr; \ + .section __ex_table,"a"; \ + PTR_WD 9b, handler; \ + .previous + +#define EXCQ(inst,reg1,reg2,addr,handler) \ +9: inst reg1, reg2, addr; \ + .section __ex_table,"a"; \ + PTR_WD 9b, handler; \ + .previous + +#endif + +#define dst a0 +#define src a1 +#define len a2 +#define rem t8 + +/* + * 64bit ABI vs 32bit ABI + */ +#ifdef CONFIG_64BIT +#define ADDU daddu +#define ADDI daddi +#define SUBU dsubu +#define SLL dsll +#define SRL dsrl +#define PTR_LA dla + +#define LOAD ld + +/* + * As we are sharing code base with the mips32 tree (which use the o32 ABI + * register definitions). We need to redefine the register definitions from + * the n64 ABI register naming to the o32 ABI register naming. + */ +#undef t0 +#undef t1 +#undef t2 +#undef t3 +#define t0 $8 +#define t1 $9 +#define t2 $10 +#define t3 $11 +#define t4 $12 +#define t5 $13 +#define t6 $14 +#define t7 $15 + +#else + +#define ADDU addu +#define ADDI addi +#define SUBU subu +#define SLL sll +#define SRL srl +#define PTR_LA la + +#define LOAD lw +#endif /* CONFIG_64BIT */ + +#define LDFIRST ldr +#define LDREST ldl + +#define SDFIRST sdr +#define SDREST sdl + +#define LWFIRST lwr +#define LWREST lwl + +#define SWFIRST swr +#define SWREST swl + +/* void * memcpy (void *s1, const void *s2, size_t n); */ + .text + .align 5 + .set noreorder + .set noat + .set arch=loongson3a + +LEAF(memcpy) /* a0=dst a1=src a2=len */ +EXPORT_SYMBOL(memcpy) + move v0, dst +.L__memcpy: +FEXPORT(__raw_copy_from_user) +EXPORT_SYMBOL(__raw_copy_from_user) +FEXPORT(__raw_copy_to_user) +EXPORT_SYMBOL(__raw_copy_to_user) + /* if less then 0x28 bytes */ + sltu t2, a2, 0x28 + andi t0, dst, 0xf + bnez t2, .L_memcpy_less + andi t1, src, 0xf + + beqz t0, 1f + ADDI rem, t0, -0x10 + + /* upgrade */ +EXC( LDFIRST t3, 0(src), .Ll_exc) + sltu t4, t0, 0x8 +EXC( LDREST t3, 7(src), .Ll_exc_copy) + SUBU src, rem +EXC( SDFIRST t3, 0(dst), .Ls_exc) + SUBU dst, rem + beqz t4, 1f + ADDU len, rem +EXC( LDFIRST t3, -8(src), .Ll_exc_a8) +EXC( LDREST t3, -1(src), .Ll_exc_copy_a8) +EXC( sd t3, -8(dst), .Ls_exc_p8) + +1: andi t7, src, 0x7 + beq t0, t1, .L_memcpy_16_16 + nop + bnez t7, .L_memcpy_16_4_2_1 + +.L_memcpy_16_8: + SRL t0, len, 6 # 64B per iteration + beqz t0, 2f + and rem, len, 0x3f + .align 4 +1: +EXC( ld t4, (16 * 0)(src), .Ll_exc) +EXC( ld t7, (8 + 16 * 0)(src), .Ll_exc_copy) +EXC( ld t2, (16 * 1)(src), .Ll_exc_copy) +EXC( ld t3, (8 + 16 * 1)(src), .Ll_exc_copy) +EXC( ld t0, (16 * 2)(src), .Ll_exc_copy) +EXC( ld t1, (8 + 16 * 2)(src), .Ll_exc_copy) + ADDI len, -16 * 4 +EXCQ( gssq, t7, t4, (16 * 0)(dst), .Ls_exc_p64) +EXC( ld t4, (16 * 3)(src), .Ll_exc_copy) +EXC( ld t7, (8 + 16 * 3)(src), .Ll_exc_copy) + ADDU src, 16 * 4 + ADDU dst, 16 * 4 +EXCQ( gssq, t3, t2, (-16 * 3)(dst), .Ls_exc_p48) +EXCQ( gssq, t1, t0, (-16 * 2)(dst), .Ls_exc_p32) +EXCQ( gssq, t7, t4, (-16 * 1)(dst), .Ls_exc_p16) + bne len, rem, 1b + nop + beqz len, .Ldone +2: sltu t0, len, 32 + bnez t0, 3f + and rem, len, 0xf +EXC( ld t2, (16 * 0)(src), .Ll_exc) +EXC( ld t3, (8 + 16 * 0)(src), .Ll_exc_copy) +EXC( ld t0, (16 * 1)(src), .Ll_exc_copy) +EXC( ld t1, (8 + 16 * 1)(src), .Ll_exc_copy) + ADDI len, -16 * 2 + ADDU src, 16 * 2 +EXCQ( gssq, t3, t2, (16 * 0)(dst), .Ls_exc_p32) +EXCQ( gssq, t1, t0, (16 * 1)(dst), .Ls_exc_p16) + beqz len, .Ldone + ADDU dst, 32 +3: /* copy less than 32B */ + beq rem, len, .L_memcpy_1_15B_8B_aligned + nop +EXC( ld t2, 0(src), .Ll_exc) +EXC( ld t3, 8(src), .Ll_exc_copy) + ADDI len, -16 + ADDU src, 16 +EXCQ( gssq, t3, t2, 0(dst), .Ls_exc_p16) + bnez len, .L_memcpy_1_15B_8B_aligned + ADDU dst, 16 + + jr ra + nop + +.L_memcpy_16_16: + SRL t0, len, 6 # 64B per iteration + beqz t0, 2f + and rem, len, 0x3f + .align 4 +1: +EXCQ( gslq, t7, t4, (16 * 0)(src), .Ll_exc) +EXCQ( gslq, t3, t2, (16 * 1)(src), .Ll_exc_copy) +EXCQ( gslq, t1, t0, (16 * 2)(src), .Ll_exc_copy) + ADDI len, -16 * 4 +EXCQ( gssq, t7, t4, (16 * 0)(dst), .Ls_exc_p64) +EXCQ( gslq, t7, t4, (16 * 3)(src), .Ll_exc_copy) + ADDU src, 16 * 4 + ADDU dst, 16 * 4 +EXCQ( gssq, t3, t2, (-16 * 3)(dst), .Ls_exc_p48) +EXCQ( gssq, t1, t0, (-16 * 2)(dst), .Ls_exc_p32) +EXCQ( gssq, t7, t4, (-16 * 1)(dst), .Ls_exc_p16) + bne len, rem, 1b + nop + beqz len, .Ldone +2: sltu t0, len, 32 + bnez t0, 3f + and rem, len, 0xf +EXCQ( gslq, t3, t2, (16 * 0)(src), .Ll_exc) +EXCQ( gslq, t1, t0, (16 * 1)(src), .Ll_exc_copy) + ADDI len, -16 * 2 + ADDU src, 32 +EXCQ( gssq, t3, t2, (16 * 0)(dst), .Ls_exc_p32) +EXCQ( gssq, t1, t0, (16 * 1)(dst), .Ls_exc_p16) + beqz len, .Ldone + ADDU dst, 32 +3: /* copy less than 32B */ + beq rem, len, .L_memcpy_1_15B_8B_aligned + nop +EXCQ( gslq, t3, t2, 0(src), .Ll_exc) + ADDI len, -16 + ADDU src, 16 +EXCQ( gssq, t3, t2, 0(dst), .Ls_exc_p16) + beqz len, .Ldone + ADDU dst, 16 +/* + * copy 1 - 15B, src & dst are 8B aligned + */ +.L_memcpy_1_15B_8B_aligned: + sltu t0, len, 0x9 + bnez t0, 1f + nop +EXC( ld t1, (src), .Ll_exc) +EXC( sd t1, (dst), .Ls_exc) +1: ADDU src, len + ADDU dst, len +EXC( LDREST t1, -1(src), .Ll_exc_copy_len) +EXC( SDREST t1, -1(dst), .Ls_exc) +.Ldone: + jr ra + move len, zero + +.L_memcpy_16_4_2_1: + SRL t0, len, 5 # 32B per iteration + beqz t0, 2f + and rem, len, 0x1f +1: +EXC( LDFIRST t4, 0(src), .Ll_exc) +EXC( LDFIRST t7, 8(src), .Ll_exc_copy) + ADDI len, -16 * 2 +EXC( LDREST t4, 7(src), .Ll_exc_copy) +EXC( LDREST t7, 15(src), .Ll_exc_copy) +EXC( LDFIRST t2, 16(src), .Ll_exc_copy) +EXC( LDFIRST t3, 24(src), .Ll_exc_copy) +EXC( LDREST t2, 23(src), .Ll_exc_copy) +EXC( LDREST t3, 31(src), .Ll_exc_copy) + ADDU src, 16 * 2 +EXCQ( gssq, t7, t4, (16 * 0)(dst), .Ls_exc_p32) +EXCQ( gssq, t3, t2, (16 * 1)(dst), .Ls_exc_p16) + bne len, rem, 1b + ADDU dst, 16 * 2 + beqz len, .Ldone +2: and rem, len, 0xf + beq rem, len, .L_memcpy_less + nop +EXC( LDFIRST t0, 0(src), .Ll_exc) +EXC( LDFIRST t1, 8(src), .Ll_exc_copy) + ADDI len, -16 +EXC( LDREST t0, 7(src), .Ll_exc_copy) +EXC( LDREST t1, 15(src), .Ll_exc_copy) + ADDU src, 16 +EXCQ( gssq, t1, t0, 0(dst), .Ls_exc_p16) + beqz len, .Ldone + ADDU dst, 16 + +.L_memcpy_less: + andi t0, len, 0x7 + beq t0, len, 2f + andi t4, len, 0x3 + + .set reorder + SUBU t1, len, t0 + ADDU dst, t1 + ADDU src, t1 + .set at=t2 + PTR_LA t3, 1f + .set noat + SLL t2, t1, 0x1 /* 4 * 4B instructions move 8B data*/ + SUBU t3, t2 + jr t3 + .set noreorder + +EXC( LDFIRST t2, (-8 * 6)(src), .Ll_exc_a48) +EXC( LDREST t2, (-8 * 6 + 7)(src), .Ll_exc_copy_a48) +EXC( SDFIRST t2, (-8 * 6)(dst), .Ls_exc) +EXC( SDREST t2, (-8 * 6 + 7)(dst), .Ls_exc_a8) + +EXC( LDFIRST t3, (-8 * 5)(src), .Ll_exc_a40) +EXC( LDREST t3, (-8 * 5 + 7)(src), .Ll_exc_copy_a40) +EXC( SDFIRST t3, (-8 * 5)(dst), .Ls_exc_a8) +EXC( SDREST t3, (-8 * 5 + 7)(dst), .Ls_exc_a16) + +EXC( LDFIRST t1, (-8 * 4)(src), .Ll_exc_a32) +EXC( LDREST t1, (-8 * 4 + 7)(src), .Ll_exc_copy_a32) +EXC( SDFIRST t1, (-8 * 4)(dst), .Ls_exc_a16) +EXC( SDREST t1, (-8 * 4 + 7)(dst), .Ls_exc_a24) + +EXC( LDFIRST t2, (-8 * 3)(src), .Ll_exc_a24) +EXC( LDREST t2, (-8 * 3 + 7)(src), .Ll_exc_copy_a24) +EXC( SDFIRST t2, (-8 * 3)(dst), .Ls_exc_a24) +EXC( SDREST t2, (-8 * 3 + 7)(dst), .Ls_exc_a32) + +EXC( LDFIRST t3, (-8 * 2)(src), .Ll_exc_a16) +EXC( LDREST t3, (-8 * 2 + 7)(src), .Ll_exc_copy_a16) +EXC( SDFIRST t3, (-8 * 2)(dst), .Ls_exc_a32) +EXC( SDREST t3, (-8 * 2 + 7)(dst), .Ls_exc_a40) + +EXC( LDFIRST t1, (-8 * 1)(src), .Ll_exc_a8) +EXC( LDREST t1, (-8 * 1 + 7)(src), .Ll_exc_copy_a8) +EXC( SDFIRST t1, (-8 * 1)(dst), .Ls_exc_a40) +EXC( SDREST t1, (-8 * 1 + 7)(dst), .Ls_exc_a48) +1: beqz t0, .Ldone + ADDU src, t0 + ADDU dst, t0 +EXC( LDFIRST t2, -8(src), .Ll_exc_a8) +EXC( LDREST t2, -1(src), .Ll_exc_copy_a8) +EXC( SDFIRST t2, -8(dst), .Ls_exc) +EXC( SDREST t2, -1(dst), .Ls_exc) + jr ra + move len, zero + +2: + beq t4, len, 3f + nop +EXC( LWFIRST t2, (src), .Ll_exc) +EXC( LWREST t2, 3(src), .Ll_exc_copy) + ADDU src, len +EXC( SWFIRST t2, (dst), .Ls_exc) +EXC( SWREST t2, 3(dst), .Ls_exc) + beqz t4, .Ldone + ADDU dst, len + +EXC( LWFIRST t1, -4(src), .Ll_exc_a4) +EXC( LWREST t1, -1(src), .Ll_exc_copy_a4) +EXC( SWFIRST t1, -4(dst), .Ls_exc) +EXC( SWREST t1, -1(dst), .Ls_exc) + jr ra + move len, zero + +3: + beqz len, .Ldone + ADDU t0, src, len +1: +EXC( lb t2, (src), .Ll_exc) + ADDU src, 1 +EXC( sb t2, (dst), .Ls_exc_p1) + bne t0, src, 1b + ADDU dst, 1 + + jr ra + move len, zero + + END(memcpy) + +#ifdef LINUX_KERNEL + +#define LEXC_a(n) \ +.Ll_exc_copy_a ## n: \ + ADDI src, -n; \ + b .Ll_exc_copy; \ + ADDI dst, -n; \ +.Ll_exc_a ## n: \ + ADDI src, -n; \ + b .Ll_exc; \ + ADDI dst, -n; + +.Ll_exc_copy_len: + SUBU src, len + b .Ll_exc_copy + SUBU dst, len + +LEXC_a(4) +LEXC_a(8) +LEXC_a(16) +LEXC_a(24) +LEXC_a(32) +LEXC_a(40) +LEXC_a(48) + +.Ll_exc_copy: + /* + * Copy bytes from src until faulting load address (or until a + * lb faults) + * + * When reached by a faulting LDFIRST/LDREST, THREAD_BUADDR($28) + * may be more than a byte beyond the last address. + * Hence, the lb below may get an exception. + * + * Assumes src < THREAD_BUADDR($28) + */ + LOAD t0, TI_TASK($28) + nop + LOAD t0, THREAD_BUADDR(t0) +1: +EXC( lb t1, 0(src), .Ll_exc) + ADDU src, 1 + sb t1, 0(dst) # can't fault -- we're copy_from_user + bne src, t0, 1b + ADDU dst, 1 +.Ll_exc: + LOAD t0, TI_TASK($28) + nop + LOAD t0, THREAD_BUADDR(t0) # t0 is just past last good address + nop + SUBU len, AT, t0 # len number of uncopied bytes + jr ra + nop + +#define SEXC_p(n) \ +.Ls_exc_p ## n: \ + jr ra; \ + ADDU len, n; + +#define SEXC_a(n) \ +.Ls_exc_a ## n: \ + jr ra; \ + ADDI len, -n; + +SEXC_p(1) +SEXC_p(8) +SEXC_p(16) +SEXC_p(32) +SEXC_p(48) +SEXC_p(64) +SEXC_a(48) +SEXC_a(40) +SEXC_a(32) +SEXC_a(24) +SEXC_a(16) +SEXC_a(8) + +.Ls_exc: + jr ra + nop + + .align 5 +LEAF(memmove) +EXPORT_SYMBOL(memmove) + ADDU t0, a0, a2 + ADDU t1, a1, a2 + sltu t0, a1, t0 # dst + len <= src -> memcpy + sltu t1, a0, t1 # dst >= src + len -> memcpy + and t0, t1 + beqz t0, .L__memcpy + move v0, a0 /* return value */ + beqz a2, .Lr_out + END(memmove) + + /* fall through to __rmemcpy */ +LEAF(__rmemcpy) /* a0=dst a1=src a2=len */ + sltu t0, a1, a0 + beqz t0, .Lr_end_bytes_up # src >= dst + nop + ADDU a0, a2 # dst = dst + len + ADDU a1, a2 # src = src + len + +.Lr_end_bytes: + lb t0, -1(a1) + ADDI a2, -0x1 + sb t0, -1(a0) + ADDI a1, -0x1 + bnez a2, .Lr_end_bytes + ADDI a0, -0x1 + +.Lr_out: + jr ra + move a2, zero + +.Lr_end_bytes_up: + lb t0, (a1) + ADDI a2, -0x1 + sb t0, (a0) + ADDU a1, 0x1 + bnez a2, .Lr_end_bytes_up + ADDU a0, 0x1 + + jr ra + move a2, zero + END(__rmemcpy) +#endif diff --git a/arch/mips/loongson64/loongson3-memset.S b/arch/mips/loongson64/loongson3-memset.S new file mode 100644 index 00000000000000..55c3b8d7bc458e --- /dev/null +++ b/arch/mips/loongson64/loongson3-memset.S @@ -0,0 +1,206 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 1998, 1999, 2000 by Ralf Baechle + * Copyright (C) 1999, 2000 Silicon Graphics, Inc. + * Copyright (C) 2007 by Maciej W. Rozycki + * Copyright (C) 2011, 2012 MIPS Technologies, Inc. + */ +#include +#include +#include +#include + +#define LONG_S_L sdl +#define LONG_S_R sdr + +#define STORSIZE 16 +#define STORMASK 15 + +#define EX(insn,reg,addr,handler) \ +9: insn reg, addr; \ + .section __ex_table,"a"; \ + PTR_WD 9b, handler; \ + .previous + +#define EX_GSSQ(reg, addr, handler) \ + .set push; \ + .set arch=loongson3a; \ +9: gssq reg, reg, addr; \ + .set pop; \ + .section __ex_table,"a"; \ + PTR_WD 9b, handler; \ + .previous + + .macro f_fill128 dst, offset, val, fixup + EX_GSSQ(\val, (\offset + 0 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 1 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 2 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 3 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 4 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 5 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 6 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 7 * STORSIZE)(\dst), \fixup) + .endm + +/* + * memset(void *s, int c, size_t n) + * + * a0: start of area to clear + * a1: char to fill with + * a2: size of area to clear + */ + .set noreorder + .align 5 +LEAF(memset) +EXPORT_SYMBOL(memset) + beqz a1, 1f + move v0, a0 /* result */ + + andi a1, 0xff /* spread fillword */ + LONG_SLL t1, a1, 8 + or a1, t1 + LONG_SLL t1, a1, 16 + or a1, t1 + LONG_SLL t1, a1, 32 + or a1, t1 +1: + +FEXPORT(__bzero) +EXPORT_SYMBOL(__bzero) + sltiu t0, a2, STORSIZE /* very small region? */ + bnez t0, .Lsmall_memset + andi t0, a0, STORMASK /* aligned? */ + + .set noat + li AT, STORSIZE + beqz t0, 1f + PTR_SUBU t0, AT /* alignment in bytes */ + .set at + + EX(LONG_S_R, a1, (a0), .Lfirst_fixup) /* make word/dword 8B aligned */ + .set push + .set arch=mips64r2 + PTR_ADDIU t1, a0, 8 + dins t1, zero, 0, 3 + .set pop + EX(LONG_S, a1, (t1), .Lsecond_fixup) /* May double copy 8B */ + + PTR_SUBU a0, t0 /* long align ptr */ + PTR_ADDU a2, t0 /* correct size */ + +1: ori t1, a2, 0x7f /* # of full blocks */ + xori t1, 0x7f + beqz t1, .Lmemset_partial /* no block to fill */ + andi t0, a2, 0x80-STORSIZE + + PTR_ADDU t1, a0 /* end address */ + .set reorder +1: PTR_ADDIU a0, 128 + f_fill128 a0, -128, a1, .Lfwd_fixup + bne t1, a0, 1b + .set noreorder + +.Lmemset_partial: + PTR_LA t1, 2f /* where to start */ + .set noat + LONG_SRL AT, t0, 2 + PTR_SUBU t1, AT + .set at + jr t1 + PTR_ADDU a0, t0 /* dest ptr */ + + .set push + .set noreorder + .set nomacro + f_fill128 a0, -128, a1, .Lpartial_fixup /* ... but first do 16Bs ... */ +2: .set pop + andi a2, STORMASK /* At most 15B to go */ + + beqz a2, 1f + PTR_ADDU a0, a2 /* What's left */ + .set push + .set arch=mips64r2 + PTR_ADDI t1, a0, -8 + dins t1, zero, 0, 3 + .set pop + EX(LONG_S, a1, (t1), .Lnotlast_fixup) /* May double copy 8B */ + EX(LONG_S_L, a1, -1(a0), .Llast_fixup) +1: jr ra + move a2, zero + +.Lsmall_memset: + andi t1, a2, 7 + beq t1, a2, 1f + LONG_SLL t1, 2 + + EX(LONG_S_R, a1, (a0), .Lfirst_fixup) + EX(LONG_S_L, a1, 7(a0), .Lsmall_memset_fixup) + +1: PTR_LA t0, 2f + PTR_SUBU t1, t0, t1 + jr t1 + PTR_ADDU a0, a2 + + EX(sb, a1, -7(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -6(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -5(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -4(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -3(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -2(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -1(a0), .Lsmall_memset_partial_fixup) + +2: jr ra /* done */ + move a2, zero + END(memset) + +.Lsmall_memset_fixup: + PTR_ADDIU t0, a0, 8 + .set push + .set arch=mips64r2 + dins t0, zero, 0, 3 + .set pop + LONG_ADDU a2, a0 + jr ra + LONG_SUBU a2, t0 + +.Lsmall_memset_partial_fixup: + PTR_L t0, TI_TASK($28) + LONG_L t0, THREAD_BUADDR(t0) + jr ra + LONG_SUBU a2, a0, t0 + +.Lfirst_fixup: + jr ra + nop + +.Lsecond_fixup: + LONG_ADDU a2, a0 + jr ra + LONG_SUBU a2, t1 + +.Lfwd_fixup: + PTR_L t0, TI_TASK($28) + andi a2, 0x7f + LONG_L t0, THREAD_BUADDR(t0) + LONG_ADDU a2, t1 + jr ra + LONG_SUBU a2, t0 + +.Lpartial_fixup: + PTR_L t0, TI_TASK($28) + andi a2, STORMASK + LONG_L t0, THREAD_BUADDR(t0) + LONG_ADDU a2, a0 + jr ra + LONG_SUBU a2, t0 + +.Llast_fixup: + jr ra + andi a2, 0x7 + +.Lnotlast_fixup: + jr ra + PTR_SUBU a2, a0, t1 diff --git a/arch/mips/loongson64/numa.c b/arch/mips/loongson64/numa.c index 16ffb32cca5082..ae8f3758a27207 100644 --- a/arch/mips/loongson64/numa.c +++ b/arch/mips/loongson64/numa.c @@ -60,7 +60,7 @@ static int __init compute_node_distance(int row, int col) else if (package_row == package_col) return 40; else - return 100; + return 200; } static void __init init_topology_matrix(void) diff --git a/arch/mips/loongson64/smp.c b/arch/mips/loongson64/smp.c index e584299d0fde8e..c30f83d58b37b9 100644 --- a/arch/mips/loongson64/smp.c +++ b/arch/mips/loongson64/smp.c @@ -428,13 +428,16 @@ static void loongson3_init_secondary(void) cpu_logical_map(cpu) % loongson_sysconf.cores_per_package); cpu_data[cpu].package = cpu_logical_map(cpu) / loongson_sysconf.cores_per_package; + __cpu_full_name[cpu] = cpu_full_name; } static void loongson3_smp_finish(void) { int cpu = smp_processor_id(); - write_c0_compare(read_c0_count() + mips_hpt_frequency/HZ); + if (!cpu_has_constant_timer) + write_c0_compare(read_c0_count() + mips_hpt_frequency/HZ); + local_irq_enable(); ipi_clear_buf(cpu); diff --git a/arch/mips/loongson64/smp.h b/arch/mips/loongson64/smp.h index 957bde81e0e4e5..6112d9d7dd2268 100644 --- a/arch/mips/loongson64/smp.h +++ b/arch/mips/loongson64/smp.h @@ -3,6 +3,7 @@ #define __LOONGSON_SMP_H_ /* for Loongson-3 smp support */ +extern char cpu_full_name[64]; extern unsigned long long smp_group[4]; /* 4 groups(nodes) in maximum in numa case */ diff --git a/arch/mips/math-emu/cp1emu.c b/arch/mips/math-emu/cp1emu.c index c89e70df43d82b..fc1d0c83b554aa 100644 --- a/arch/mips/math-emu/cp1emu.c +++ b/arch/mips/math-emu/cp1emu.c @@ -1451,6 +1451,7 @@ static union ieee754sp fpemu_sp_rsqrt(union ieee754sp s) return ieee754sp_div(ieee754sp_one(0), ieee754sp_sqrt(s)); } +#ifndef CONFIG_CPU_LOONGSON64 DEF3OP(madd, sp, ieee754sp_mul, ieee754sp_add, ); DEF3OP(msub, sp, ieee754sp_mul, ieee754sp_sub, ); DEF3OP(nmadd, sp, ieee754sp_mul, ieee754sp_add, ieee754sp_neg); @@ -1459,6 +1460,7 @@ DEF3OP(madd, dp, ieee754dp_mul, ieee754dp_add, ); DEF3OP(msub, dp, ieee754dp_mul, ieee754dp_sub, ); DEF3OP(nmadd, dp, ieee754dp_mul, ieee754dp_add, ieee754dp_neg); DEF3OP(nmsub, dp, ieee754dp_mul, ieee754dp_sub, ieee754dp_neg); +#endif static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx, mips_instruction ir, void __user **fault_addr) @@ -1513,6 +1515,20 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx, } break; +#ifdef CONFIG_CPU_LOONGSON64 + case madd_s_op: + handler = ieee754sp_madd; + goto scoptop; + case msub_s_op: + handler = ieee754sp_msub; + goto scoptop; + case nmadd_s_op: + handler = ieee754sp_nmadd; + goto scoptop; + case nmsub_s_op: + handler = ieee754sp_nmsub; + goto scoptop; +#else case madd_s_op: if (cpu_has_mac2008_only) handler = ieee754sp_madd; @@ -1537,6 +1553,7 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx, else handler = fpemu_sp_nmsub; goto scoptop; +#endif scoptop: SPFROMREG(fr, MIPSInst_FR(ir)); @@ -1621,6 +1638,20 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx, } break; +#ifdef CONFIG_CPU_LOONGSON64 + case madd_d_op: + handler = ieee754dp_madd; + goto dcoptop; + case msub_d_op: + handler = ieee754dp_msub; + goto dcoptop; + case nmadd_d_op: + handler = ieee754dp_nmadd; + goto dcoptop; + case nmsub_d_op: + handler = ieee754dp_nmsub; + goto dcoptop; +#else case madd_d_op: if (cpu_has_mac2008_only) handler = ieee754dp_madd; @@ -1645,6 +1676,7 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx, else handler = fpemu_dp_nmsub; goto dcoptop; +#endif dcoptop: DPFROMREG(fr, MIPSInst_FR(ir)); diff --git a/arch/mips/mm/page.c b/arch/mips/mm/page.c index 1df237bd4a72bd..21c9d35ec25e86 100644 --- a/arch/mips/mm/page.c +++ b/arch/mips/mm/page.c @@ -206,6 +206,11 @@ static void set_prefetch_parameters(void) else if (cpu_has_cache_cdex_p) cache_line_size = cpu_dcache_line_size(); } + +#ifdef CONFIG_CPU_LOONGSON3 + clear_word_size = 16; +#endif + /* * Too much unrolling will overflow the available space in * clear_space_array / copy_page_array. @@ -220,11 +225,15 @@ static void set_prefetch_parameters(void) static void build_clear_store(u32 **buf, int off) { +#ifdef CONFIG_CPU_LOONGSON3 + uasm_i_gssq(buf, ZERO, ZERO, off, A0); +#else if (cpu_has_64bit_gp_regs || cpu_has_64bit_zero_reg) { uasm_i_sd(buf, GPR_ZERO, off, GPR_A0); } else { uasm_i_sw(buf, GPR_ZERO, off, GPR_A0); } +#endif } static inline void build_clear_pref(u32 **buf, int off) diff --git a/arch/mips/mm/tlbex.c b/arch/mips/mm/tlbex.c index 69ea54bdc0c361..33f613d6665b8b 100644 --- a/arch/mips/mm/tlbex.c +++ b/arch/mips/mm/tlbex.c @@ -676,13 +676,12 @@ static void build_huge_tlb_write_entry(u32 **p, struct uasm_label **l, */ static void build_is_huge_pte(u32 **p, struct uasm_reloc **r, unsigned int tmp, - unsigned int pmd, int lid) + unsigned int pmde, int lid) { - UASM_i_LW(p, tmp, 0, pmd); if (use_bbit_insns()) { - uasm_il_bbit1(p, r, tmp, ilog2(_PAGE_HUGE), lid); + uasm_il_bbit1(p, r, pmde, ilog2(_PAGE_HUGE), lid); } else { - uasm_i_andi(p, tmp, tmp, _PAGE_HUGE); + uasm_i_andi(p, tmp, pmde, _PAGE_HUGE); uasm_il_bnez(p, r, tmp, lid); } } @@ -1042,7 +1041,6 @@ EXPORT_SYMBOL_GPL(build_update_entries); struct mips_huge_tlb_info { int huge_pte; int restore_scratch; - bool need_reload_pte; }; static struct mips_huge_tlb_info @@ -1057,7 +1055,6 @@ build_fast_tlb_refill_handler (u32 **p, struct uasm_label **l, rv.huge_pte = scratch; rv.restore_scratch = 0; - rv.need_reload_pte = false; if (check_for_high_segbits) { UASM_i_MFC0(p, tmp, C0_BADVADDR); @@ -1263,7 +1260,6 @@ static void build_r4000_tlb_refill_handler(void) } else { htlb_info.huge_pte = GPR_K0; htlb_info.restore_scratch = 0; - htlb_info.need_reload_pte = true; vmalloc_mode = refill_noscratch; /* * create the plain linear handler @@ -1288,11 +1284,14 @@ static void build_r4000_tlb_refill_handler(void) build_get_pgde32(&p, GPR_K0, GPR_K1); /* get pgd in GPR_K1 */ #endif + UASM_i_LW(&p, GPR_K0, 0, GPR_K1); /* get pmd entry in GPR_K0 */ #ifdef CONFIG_MIPS_HUGE_TLB_SUPPORT - build_is_huge_pte(&p, &r, GPR_K0, GPR_K1, label_tlb_huge_update); + build_is_huge_pte(&p, &r, GPR_K1, GPR_K0, label_tlb_huge_update); #endif - build_get_ptep(&p, GPR_K0, GPR_K1); + GET_CONTEXT(&p, GPR_K1); /* get context reg */ + build_adjust_context(&p, GPR_K1); + UASM_i_ADDU(&p, GPR_K1, GPR_K0, GPR_K1); /* add in offset */ build_update_entries(&p, GPR_K0, GPR_K1); build_tlb_write_entry(&p, &l, &r, tlb_random); uasm_l_leave(&l, p); @@ -1300,8 +1299,6 @@ static void build_r4000_tlb_refill_handler(void) } #ifdef CONFIG_MIPS_HUGE_TLB_SUPPORT uasm_l_tlb_huge_update(&l, p); - if (htlb_info.need_reload_pte) - UASM_i_LW(&p, htlb_info.huge_pte, 0, GPR_K1); build_huge_update_entries(&p, htlb_info.huge_pte, GPR_K1); build_huge_tlb_write_entry(&p, &l, &r, GPR_K0, tlb_random, htlb_info.restore_scratch); @@ -2001,20 +1998,20 @@ build_r4000_tlbchange_handler_head(u32 **p, struct uasm_label **l, build_get_pgde32(p, wr.r1, wr.r2); /* get pgd in ptr */ #endif + UASM_i_LW(p, wr.r3, 0, wr.r2); /* get pmd entry in wr.r3 */ #ifdef CONFIG_MIPS_HUGE_TLB_SUPPORT /* - * For huge tlb entries, pmd doesn't contain an address but + * For huge tlb entries, pmde doesn't contain an address but * instead contains the tlb pte. Check the PAGE_HUGE bit and * see if we need to jump to huge tlb processing. */ - build_is_huge_pte(p, r, wr.r1, wr.r2, label_tlb_huge_update); + build_is_huge_pte(p, r, wr.r1, wr.r3, label_tlb_huge_update); #endif UASM_i_MFC0(p, wr.r1, C0_BADVADDR); - UASM_i_LW(p, wr.r2, 0, wr.r2); UASM_i_SRL(p, wr.r1, wr.r1, PAGE_SHIFT - PTE_T_LOG2); uasm_i_andi(p, wr.r1, wr.r1, (PTRS_PER_PTE - 1) << PTE_T_LOG2); - UASM_i_ADDU(p, wr.r2, wr.r2, wr.r1); + UASM_i_ADDU(p, wr.r2, wr.r3, wr.r1); #ifdef CONFIG_SMP uasm_l_smp_pgtable_change(l, *p); diff --git a/arch/mips/mm/uasm-mips.c b/arch/mips/mm/uasm-mips.c index e15c6700cd0884..7e44ea365795b1 100644 --- a/arch/mips/mm/uasm-mips.c +++ b/arch/mips/mm/uasm-mips.c @@ -27,6 +27,10 @@ #define RT_SH 16 #define SCIMM_MASK 0xfffff #define SCIMM_SH 6 +#define RZ_MASK 0x1f +#define RZ_SH 0 +#define RC_MASK 0x1ff +#define RC_SH 6 /* This macro sets the non-variable bits of an instruction. */ #define M(a, b, c, d, e, f) \ @@ -203,6 +207,7 @@ static const struct insn insn_table[insn_invalid] = { [insn_xor] = {M(spec_op, 0, 0, 0, 0, xor_op), RS | RT | RD}, [insn_xori] = {M(xori_op, 0, 0, 0, 0, 0), RS | RT | UIMM}, [insn_yield] = {M(spec3_op, 0, 0, 0, 0, yield_op), RS | RD}, + [insn_gssq] = {M(swc2_op, 0, 0, 0, 0, gssq_op), RT | RS | RZ | RC}, }; #undef M @@ -225,6 +230,21 @@ static inline u32 build_jimm(u32 arg) return (arg >> 2) & JIMM_MASK; } +static inline u32 build_rz(u32 arg) +{ + WARN(arg & ~RZ_MASK, KERN_WARNING "Micro-assembler field overflow\n"); + + return (arg & RZ_MASK) << RZ_SH; +} + +static inline u32 build_rc(s32 arg) +{ + WARN((arg >> 4) > 0xff || + (arg >> 4) < -0x100, KERN_WARNING "Micro-assembler field overflow\n"); + + return ((arg >> 4) & RC_MASK) << RC_SH; +} + /* * The order of opcode arguments is implicitly left to right, * starting with RS and ending with FUNC or IMM. @@ -252,6 +272,10 @@ static void build_insn(u32 **buf, enum opcode opc, ...) op |= build_rd(va_arg(ap, u32)); if (ip->fields & RE) op |= build_re(va_arg(ap, u32)); + if (ip->fields & RZ) + op |= build_rz(va_arg(ap, u32)); + if (ip->fields & RC) + op |= build_rc(va_arg(ap, s32)); if (ip->fields & SIMM) op |= build_simm(va_arg(ap, s32)); if (ip->fields & UIMM) diff --git a/arch/mips/mm/uasm.c b/arch/mips/mm/uasm.c index 125140979d62c5..2a9ca7de437603 100644 --- a/arch/mips/mm/uasm.c +++ b/arch/mips/mm/uasm.c @@ -26,6 +26,8 @@ enum fields { SET = 0x200, SCIMM = 0x400, SIMM9 = 0x800, + RZ = 0x1000, + RC = 0x2000 }; #define OP_MASK 0x3f @@ -65,7 +67,7 @@ enum opcode { insn_sllv, insn_slt, insn_slti, insn_sltiu, insn_sltu, insn_sra, insn_srav, insn_srl, insn_srlv, insn_subu, insn_sw, insn_sync, insn_syscall, insn_tlbp, insn_tlbr, insn_tlbwi, insn_tlbwr, insn_wait, - insn_wsbh, insn_xor, insn_xori, insn_yield, + insn_wsbh, insn_xor, insn_xori, insn_yield, insn_gssq, insn_invalid /* insn_invalid must be last */ }; @@ -198,6 +200,13 @@ Ip_u2s3u1(op) \ } \ UASM_EXPORT_SYMBOL(uasm_i##op); +#define I_u4u2u1s3(op) \ +Ip_u4u2u1s3(op) \ +{ \ + build_insn(buf, insn##op, d, b, a, c); \ +} \ +UASM_EXPORT_SYMBOL(uasm_i##op); + #define I_u2u1s3(op) \ Ip_u2u1s3(op) \ { \ @@ -356,6 +365,7 @@ I_u2s3u1(_sd) I_u3u1u2(_seleqz) I_u3u1u2(_selnez) I_u2s3u1(_sh) +I_u4u2u1s3(_gssq) I_u2u1u3(_sll) I_u3u2u1(_sllv) I_s3s1s2(_slt) diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index e93df95b79e750..592aac33cdca36 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -287,6 +287,7 @@ config PPC select HAVE_RSEQ select HAVE_SAMPLE_FTRACE_DIRECT if HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS select HAVE_SAMPLE_FTRACE_DIRECT_MULTI if HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS + select HAVE_RUST if PPC64 select HAVE_SETUP_PER_CPU_AREA if PPC64 select HAVE_SOFTIRQ_ON_OWN_STACK select HAVE_STACKPROTECTOR if PPC32 && $(cc-option,$(m32-flag) -mstack-protector-guard=tls -mstack-protector-guard-reg=r2 -mstack-protector-guard-offset=0) diff --git a/arch/powerpc/Makefile b/arch/powerpc/Makefile index a58b1029592ce2..7ad28cf0fd3558 100644 --- a/arch/powerpc/Makefile +++ b/arch/powerpc/Makefile @@ -172,10 +172,11 @@ CFLAGS-y += $(CONFIG_TUNE_CPU) asinstr := $(call as-instr,lis 9$(comma)foo@high,-DHAVE_AS_ATHIGH=1) -KBUILD_CPPFLAGS += -I $(srctree)/arch/powerpc $(asinstr) -KBUILD_AFLAGS += $(AFLAGS-y) -KBUILD_CFLAGS += $(CC_FLAGS_NO_FPU) -KBUILD_CFLAGS += $(CFLAGS-y) +KBUILD_CPPFLAGS += -I $(srctree)/arch/powerpc $(asinstr) +KBUILD_AFLAGS += $(AFLAGS-y) +KBUILD_CFLAGS += $(CC_FLAGS_NO_FPU) +KBUILD_CFLAGS += $(CFLAGS-y) +KBUILD_RUSTFLAGS += -Ctarget-feature=-mma,-vsx,-hard-float,-altivec CHECKFLAGS += -m$(BITS) -D__powerpc__ -D__powerpc$(BITS)__ ifdef CONFIG_CPU_BIG_ENDIAN diff --git a/arch/powerpc/include/asm/asm-compat.h b/arch/powerpc/include/asm/asm-compat.h index f48e644900a26e..34f8740909a945 100644 --- a/arch/powerpc/include/asm/asm-compat.h +++ b/arch/powerpc/include/asm/asm-compat.h @@ -37,7 +37,7 @@ #define STDX_BE stringify_in_c(stdbrx) #endif -#ifdef CONFIG_CC_IS_CLANG +#ifdef __clang__ #define DS_FORM_CONSTRAINT "Z<>" #else #define DS_FORM_CONSTRAINT "YZ<>" diff --git a/arch/powerpc/include/asm/jump_label.h b/arch/powerpc/include/asm/jump_label.h index d4eaba459a0ed5..a6b211502bfe79 100644 --- a/arch/powerpc/include/asm/jump_label.h +++ b/arch/powerpc/include/asm/jump_label.h @@ -15,14 +15,20 @@ #define JUMP_ENTRY_TYPE stringify_in_c(FTR_ENTRY_LONG) #define JUMP_LABEL_NOP_SIZE 4 +#define JUMP_TABLE_ENTRY(key, label) \ + ".pushsection __jump_table, \"aw\" \n\t" \ + ".long 1b - ., " label " - . \n\t" \ + JUMP_ENTRY_TYPE key " - . \n\t" \ + ".popsection \n\t" + +#define ARCH_STATIC_BRANCH_ASM(key, label) \ + "1: nop \n\t" \ + JUMP_TABLE_ENTRY(key,label) + static __always_inline bool arch_static_branch(struct static_key *key, bool branch) { - asm goto("1:\n\t" - "nop # arch_static_branch\n\t" - ".pushsection __jump_table, \"aw\"\n\t" - ".long 1b - ., %l[l_yes] - .\n\t" - JUMP_ENTRY_TYPE "%c0 - .\n\t" - ".popsection \n\t" + asm goto( + ARCH_STATIC_BRANCH_ASM("%c0", "%l[l_yes]") : : "i" (&((char *)key)[branch]) : : l_yes); return false; @@ -34,10 +40,7 @@ static __always_inline bool arch_static_branch_jump(struct static_key *key, bool { asm goto("1:\n\t" "b %l[l_yes] # arch_static_branch_jump\n\t" - ".pushsection __jump_table, \"aw\"\n\t" - ".long 1b - ., %l[l_yes] - .\n\t" - JUMP_ENTRY_TYPE "%c0 - .\n\t" - ".popsection \n\t" + JUMP_TABLE_ENTRY("%c0", "%l[l_yes]") : : "i" (&((char *)key)[branch]) : : l_yes); return false; diff --git a/arch/powerpc/include/asm/sfp-machine.h b/arch/powerpc/include/asm/sfp-machine.h index 8b957aabb826d3..db8525605c026d 100644 --- a/arch/powerpc/include/asm/sfp-machine.h +++ b/arch/powerpc/include/asm/sfp-machine.h @@ -319,10 +319,12 @@ #define abort() \ return 0 -#ifdef __BIG_ENDIAN +#ifdef __BIG_ENDIAN__ #define __BYTE_ORDER __BIG_ENDIAN +#define __LITTLE_ENDIAN 0 #else #define __BYTE_ORDER __LITTLE_ENDIAN +#define __BIG_ENDIAN 0 #endif /* Exception flags. */ diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index c5754942cf85a4..ab610e340c9e8a 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -23,7 +23,9 @@ config RISCV select ARCH_ENABLE_MEMORY_HOTPLUG if SPARSEMEM_VMEMMAP select ARCH_ENABLE_SPLIT_PMD_PTLOCK if PGTABLE_LEVELS > 2 select ARCH_ENABLE_THP_MIGRATION if TRANSPARENT_HUGEPAGE + select ARCH_HAS_ACPI_TABLE_UPGRADE if ACPI select ARCH_HAS_BINFMT_FLAT + select ARCH_HAS_CC_CAN_LINK select ARCH_HAS_CURRENT_STACK_POINTER select ARCH_HAS_DEBUG_VIRTUAL if MMU select ARCH_HAS_DEBUG_VM_PGTABLE @@ -55,7 +57,7 @@ config RISCV select ARCH_HAS_UBSAN select ARCH_HAS_VDSO_ARCH_DATA if HAVE_GENERIC_VDSO select ARCH_HAVE_NMI_SAFE_CMPXCHG - select ARCH_KEEP_MEMBLOCK if ACPI + select ARCH_KEEP_MEMBLOCK if ACPI || KEXEC select ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE if 64BIT && MMU select ARCH_OPTIONAL_KERNEL_RWX if ARCH_HAS_STRICT_KERNEL_RWX select ARCH_OPTIONAL_KERNEL_RWX_DEFAULT @@ -151,6 +153,7 @@ config RISCV select HAVE_ARCH_USERFAULTFD_WP if 64BIT && MMU && USERFAULTFD && RISCV_ISA_SVRSW60T59B select HAVE_ARCH_VMAP_STACK if MMU && 64BIT select HAVE_ASM_MODVERSIONS + select HAVE_BUILDTIME_MCOUNT_SORT select HAVE_CONTEXT_TRACKING_USER select HAVE_DEBUG_KMEMLEAK select HAVE_DMA_CONTIGUOUS if MMU @@ -195,7 +198,7 @@ config RISCV select HAVE_REGS_AND_STACK_ACCESS_API select HAVE_RETHOOK select HAVE_RSEQ - select HAVE_RUST if RUSTC_SUPPORTS_RISCV && CC_IS_CLANG + select HAVE_RUST if RUSTC_SUPPORTS_RISCV && TOOLCHAIN_MATCHES_ZICSR_ZIFENCEI select HAVE_SAMPLE_FTRACE_DIRECT select HAVE_SAMPLE_FTRACE_DIRECT_MULTI select HAVE_STACKPROTECTOR @@ -481,7 +484,7 @@ endchoice config NUMA bool "NUMA Memory Allocation and Scheduler Support" depends on SMP && MMU - select ARCH_SUPPORTS_NUMA_BALANCING + select ARCH_SUPPORTS_NUMA_BALANCING if SPARSEMEM_VMEMMAP select GENERIC_ARCH_NUMA select HAVE_SETUP_PER_CPU_AREA select NEED_PER_CPU_EMBED_FIRST_CHUNK @@ -615,6 +618,8 @@ config TOOLCHAIN_HAS_V depends on !32BIT || $(cc-option,-mabi=ilp32 -march=rv32imv) depends on LD_IS_LLD || LD_VERSION >= 23800 depends on AS_HAS_OPTION_ARCH + depends on !RUST || !64BIT || $(bindgen-backend-option,-mabi=lp64 -march=rv64imv) + depends on !RUST || !32BIT || $(bindgen-backend-option,-mabi=ilp32 -march=rv32imv) config RISCV_ISA_V bool "Vector extension support" @@ -679,6 +684,8 @@ config TOOLCHAIN_HAS_ZABHA depends on !64BIT || $(cc-option,-mabi=lp64 -march=rv64ima_zabha) depends on !32BIT || $(cc-option,-mabi=ilp32 -march=rv32ima_zabha) depends on AS_HAS_OPTION_ARCH + depends on !RUST || !64BIT || $(bindgen-backend-option,-mabi=lp64 -march=rv64ima_zabha) + depends on !RUST || !32BIT || $(bindgen-backend-option,-mabi=ilp32 -march=rv32ima_zabha) config RISCV_ISA_ZABHA bool "Zabha extension support for atomic byte/halfword operations" @@ -697,6 +704,8 @@ config TOOLCHAIN_HAS_ZACAS depends on !64BIT || $(cc-option,-mabi=lp64 -march=rv64ima_zacas) depends on !32BIT || $(cc-option,-mabi=ilp32 -march=rv32ima_zacas) depends on AS_HAS_OPTION_ARCH + depends on !RUST || !64BIT || $(bindgen-backend-option,-mabi=lp64 -march=rv64ima_zacas) + depends on !RUST || !32BIT || $(bindgen-backend-option,-mabi=ilp32 -march=rv32ima_zacas) config RISCV_ISA_ZACAS bool "Zacas extension support for atomic CAS" @@ -715,6 +724,8 @@ config TOOLCHAIN_HAS_ZBB depends on !32BIT || $(cc-option,-mabi=ilp32 -march=rv32ima_zbb) depends on LD_IS_LLD || LD_VERSION >= 23900 depends on AS_HAS_OPTION_ARCH + depends on !RUST || !64BIT || $(bindgen-backend-option,-mabi=lp64 -march=rv64ima_zbb) + depends on !RUST || !32BIT || $(bindgen-backend-option,-mabi=ilp32 -march=rv32ima_zbb) # This symbol indicates that the toolchain supports all v1.0 vector crypto # extensions, including Zvk*, Zvbb, and Zvbc. LLVM added all of these at once. @@ -730,6 +741,8 @@ config TOOLCHAIN_HAS_ZBA depends on !32BIT || $(cc-option,-mabi=ilp32 -march=rv32ima_zba) depends on LD_IS_LLD || LD_VERSION >= 23900 depends on AS_HAS_OPTION_ARCH + depends on !RUST || !64BIT || $(bindgen-backend-option,-mabi=lp64 -march=rv64ima_zba) + depends on !RUST || !32BIT || $(bindgen-backend-option,-mabi=ilp32 -march=rv32ima_zba) config RISCV_ISA_ZBA bool "Zba extension support for bit manipulation instructions" @@ -765,6 +778,8 @@ config TOOLCHAIN_HAS_ZBC depends on !32BIT || $(cc-option,-mabi=ilp32 -march=rv32ima_zbc) depends on LD_IS_LLD || LD_VERSION >= 23900 depends on AS_HAS_OPTION_ARCH + depends on !RUST || !64BIT || $(bindgen-backend-option,-mabi=lp64 -march=rv64ima_zbc) + depends on !RUST || !32BIT || $(bindgen-backend-option,-mabi=ilp32 -march=rv32ima_zbc) config RISCV_ISA_ZBC bool "Zbc extension support for carry-less multiplication instructions" @@ -788,6 +803,8 @@ config TOOLCHAIN_HAS_ZBKB depends on !32BIT || $(cc-option,-mabi=ilp32 -march=rv32ima_zbkb) depends on LD_IS_LLD || LD_VERSION >= 23900 depends on AS_HAS_OPTION_ARCH + depends on !RUST || !64BIT || $(bindgen-backend-option,-mabi=lp64 -march=rv64ima_zbkb) + depends on !RUST || !32BIT || $(bindgen-backend-option,-mabi=ilp32 -march=rv32ima_zbkb) config RISCV_ISA_ZBKB bool "Zbkb extension support for bit manipulation instructions" @@ -889,6 +906,25 @@ config TOOLCHAIN_NEEDS_OLD_ISA_SPEC versions of clang and GCC to be passed to GAS, which has the same result as passing zicsr and zifencei to -march. +config RUST_BINDGEN_HAS_ZICSR_ZIFENCEI + def_bool y + depends on !64BIT || $(bindgen-backend-option,-mabi=lp64 -march=rv64ima_zicsr_zifencei) + depends on !32BIT || $(bindgen-backend-option,-mabi=ilp32 -march=rv32ima_zicsr_zifencei) + +config TOOLCHAIN_MATCHES_ZICSR_ZIFENCEI + def_bool y + # https://github.com/llvm/llvm-project/commit/22e199e6afb1263c943c0c0d4498694e15bf8a16 + depends on TOOLCHAIN_NEEDS_OLD_ISA_SPEC || !TOOLCHAIN_NEEDS_EXPLICIT_ZICSR_ZIFENCEI || RUST_BINDGEN_HAS_ZICSR_ZIFENCEI + help + LLVM/Clang >= 17.0.0 starts recognizing Zicsr/Zifencei in -march, passing + them to -march doesn't generate an error anymore, and passing them or not + doesn't have any real difference, it still follows ISA before version + 20190608 - Zicsr/Zifencei are included in base ISA. + + The current latest version of LLVM/Clang still does not require explicit + Zicsr/Zifencei to enable these two extensions, Clang just accepts them in + -march and then silently ignores them. + config FPU bool "FPU support" default y @@ -1353,6 +1389,20 @@ config PORTABLE config ARCH_PROC_KCORE_TEXT def_bool y +config ARCH_CC_CAN_LINK + bool + default $(cc_can_link_user,-march=rv64g -mabi=lp64d) if 64BIT && FPU + default $(cc_can_link_user,-march=rv64g -mabi=lp64) if 64BIT + default $(cc_can_link_user,-march=rv32g -mabi=ilp32d) if FPU + default $(cc_can_link_user,-march=rv32g -mabi=ilp32) + +config ARCH_USERFLAGS + string + default "-march=rv64g -mabi=lp64d" if 64BIT && FPU + default "-march=rv64g -mabi=lp64" if 64BIT + default "-march=rv32g -mabi=ilp32d" if FPU + default "-march=rv32g -mabi=ilp32" + menu "Power management options" source "kernel/power/Kconfig" diff --git a/arch/riscv/Kconfig.errata b/arch/riscv/Kconfig.errata index 3c945d086c7d02..4d3f13522da6d0 100644 --- a/arch/riscv/Kconfig.errata +++ b/arch/riscv/Kconfig.errata @@ -154,4 +154,21 @@ config ERRATA_THEAD_GHOSTWRITE If you don't know what to do here, say "Y". +config ERRATA_THEAD_WRITE_ONCE + bool "Apply T-Head WRITE_ONCE errata" + depends on ERRATA_THEAD + default y + help + The early version of T-Head C9xx cores of sg2042 & th1520 have a store + merge buffer delay problem. The store merge buffer could improve the + store queue performance by merging multi-store requests, but when there + are no continued store requests, the prior single store request would be + waiting in the store queue for a long time. That would cause signifi- + cant problems for communication between multi-cores. Appending a + fence w.o could immediately flush the store merge buffer and let other + cores see the write result. + + This will apply the WRITE_ONCE errata to handle the non-standard beh- + avior via appending a fence w.o instruction for WRITE_ONCE(). + endmenu # "CPU errata selection" diff --git a/arch/riscv/Makefile b/arch/riscv/Makefile index ce0cc737f87096..3592f9788da32e 100644 --- a/arch/riscv/Makefile +++ b/arch/riscv/Makefile @@ -14,11 +14,19 @@ endif ifeq ($(CONFIG_DYNAMIC_FTRACE),y) LDFLAGS_vmlinux += --no-relax KBUILD_CPPFLAGS += -DCC_USING_PATCHABLE_FUNCTION_ENTRY +ifeq ($(CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS),y) ifeq ($(CONFIG_RISCV_ISA_C),y) CC_FLAGS_FTRACE := -fpatchable-function-entry=8,4 else CC_FLAGS_FTRACE := -fpatchable-function-entry=4,2 endif +else +ifeq ($(CONFIG_RISCV_ISA_C),y) + CC_FLAGS_FTRACE := -fpatchable-function-entry=4 +else + CC_FLAGS_FTRACE := -fpatchable-function-entry=2 +endif +endif endif ifeq ($(CONFIG_CMODEL_MEDLOW),y) @@ -75,12 +83,6 @@ else riscv-march-$(CONFIG_TOOLCHAIN_NEEDS_EXPLICIT_ZICSR_ZIFENCEI) := $(riscv-march-y)_zicsr_zifencei endif -# Check if the toolchain supports Zacas -riscv-march-$(CONFIG_TOOLCHAIN_HAS_ZACAS) := $(riscv-march-y)_zacas - -# Check if the toolchain supports Zabha -riscv-march-$(CONFIG_TOOLCHAIN_HAS_ZABHA) := $(riscv-march-y)_zabha - KBUILD_BASE_ISA = -march=$(shell echo $(riscv-march-y) | sed -E 's/(rv32ima|rv64ima)fd([^v_]*)v?/\1\2/') export KBUILD_BASE_ISA @@ -168,7 +170,7 @@ vdso_prepare: prepare0 endif endif -vdso-install-y += arch/riscv/kernel/vdso/vdso.so.dbg +vdso-install-$(CONFIG_MMU) += arch/riscv/kernel/vdso/vdso.so.dbg vdso-install-$(CONFIG_RISCV_USER_CFI) += arch/riscv/kernel/vdso_cfi/vdso-cfi.so.dbg vdso-install-$(CONFIG_COMPAT) += arch/riscv/kernel/compat_vdso/compat_vdso.so.dbg diff --git a/arch/riscv/boot/dts/sophgo/sg2042-cpus.dtsi b/arch/riscv/boot/dts/sophgo/sg2042-cpus.dtsi index 509488eee4321c..fd8906b313d2eb 100644 --- a/arch/riscv/boot/dts/sophgo/sg2042-cpus.dtsi +++ b/arch/riscv/boot/dts/sophgo/sg2042-cpus.dtsi @@ -263,7 +263,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <0>; + reg = <0x0>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -291,7 +291,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <1>; + reg = <0x1>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -319,7 +319,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <2>; + reg = <0x2>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -347,7 +347,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <3>; + reg = <0x3>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -375,7 +375,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <4>; + reg = <0x4>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -403,7 +403,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <5>; + reg = <0x5>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -431,7 +431,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <6>; + reg = <0x6>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -459,7 +459,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <7>; + reg = <0x7>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -487,7 +487,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <8>; + reg = <0x8>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -515,7 +515,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <9>; + reg = <0x9>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -533,7 +533,7 @@ }; }; - cpu10: cpu@10 { + cpu10: cpu@a { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -543,7 +543,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <10>; + reg = <0xa>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -561,7 +561,7 @@ }; }; - cpu11: cpu@11 { + cpu11: cpu@b { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -571,7 +571,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <11>; + reg = <0xb>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -589,7 +589,7 @@ }; }; - cpu12: cpu@12 { + cpu12: cpu@c { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -599,7 +599,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <12>; + reg = <0xc>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -617,7 +617,7 @@ }; }; - cpu13: cpu@13 { + cpu13: cpu@d { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -627,7 +627,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <13>; + reg = <0xd>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -645,7 +645,7 @@ }; }; - cpu14: cpu@14 { + cpu14: cpu@e { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -655,7 +655,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <14>; + reg = <0xe>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -673,7 +673,7 @@ }; }; - cpu15: cpu@15 { + cpu15: cpu@f { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -683,7 +683,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <15>; + reg = <0xf>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -701,7 +701,7 @@ }; }; - cpu16: cpu@16 { + cpu16: cpu@10 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -711,7 +711,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <16>; + reg = <0x10>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -729,7 +729,7 @@ }; }; - cpu17: cpu@17 { + cpu17: cpu@11 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -739,7 +739,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <17>; + reg = <0x11>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -757,7 +757,7 @@ }; }; - cpu18: cpu@18 { + cpu18: cpu@12 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -767,7 +767,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <18>; + reg = <0x12>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -785,7 +785,7 @@ }; }; - cpu19: cpu@19 { + cpu19: cpu@13 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -795,7 +795,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <19>; + reg = <0x13>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -813,7 +813,7 @@ }; }; - cpu20: cpu@20 { + cpu20: cpu@14 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -823,7 +823,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <20>; + reg = <0x14>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -841,7 +841,7 @@ }; }; - cpu21: cpu@21 { + cpu21: cpu@15 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -851,7 +851,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <21>; + reg = <0x15>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -869,7 +869,7 @@ }; }; - cpu22: cpu@22 { + cpu22: cpu@16 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -879,7 +879,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <22>; + reg = <0x16>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -897,7 +897,7 @@ }; }; - cpu23: cpu@23 { + cpu23: cpu@17 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -907,7 +907,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <23>; + reg = <0x17>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -925,7 +925,7 @@ }; }; - cpu24: cpu@24 { + cpu24: cpu@18 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -935,7 +935,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <24>; + reg = <0x18>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -953,7 +953,7 @@ }; }; - cpu25: cpu@25 { + cpu25: cpu@19 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -963,7 +963,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <25>; + reg = <0x19>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -981,7 +981,7 @@ }; }; - cpu26: cpu@26 { + cpu26: cpu@1a { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -991,7 +991,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <26>; + reg = <0x1a>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1009,7 +1009,7 @@ }; }; - cpu27: cpu@27 { + cpu27: cpu@1b { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1019,7 +1019,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <27>; + reg = <0x1b>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1037,7 +1037,7 @@ }; }; - cpu28: cpu@28 { + cpu28: cpu@1c { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1047,7 +1047,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <28>; + reg = <0x1c>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1065,7 +1065,7 @@ }; }; - cpu29: cpu@29 { + cpu29: cpu@1d { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1075,7 +1075,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <29>; + reg = <0x1d>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1093,7 +1093,7 @@ }; }; - cpu30: cpu@30 { + cpu30: cpu@1e { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1103,7 +1103,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <30>; + reg = <0x1e>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1121,7 +1121,7 @@ }; }; - cpu31: cpu@31 { + cpu31: cpu@1f { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1131,7 +1131,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <31>; + reg = <0x1f>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1149,7 +1149,7 @@ }; }; - cpu32: cpu@32 { + cpu32: cpu@20 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1159,7 +1159,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <32>; + reg = <0x20>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1177,7 +1177,7 @@ }; }; - cpu33: cpu@33 { + cpu33: cpu@21 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1187,7 +1187,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <33>; + reg = <0x21>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1205,7 +1205,7 @@ }; }; - cpu34: cpu@34 { + cpu34: cpu@22 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1215,7 +1215,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <34>; + reg = <0x22>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1233,7 +1233,7 @@ }; }; - cpu35: cpu@35 { + cpu35: cpu@23 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1243,7 +1243,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <35>; + reg = <0x23>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1261,7 +1261,7 @@ }; }; - cpu36: cpu@36 { + cpu36: cpu@24 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1271,7 +1271,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <36>; + reg = <0x24>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1289,7 +1289,7 @@ }; }; - cpu37: cpu@37 { + cpu37: cpu@25 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1299,7 +1299,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <37>; + reg = <0x25>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1317,7 +1317,7 @@ }; }; - cpu38: cpu@38 { + cpu38: cpu@26 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1327,7 +1327,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <38>; + reg = <0x26>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1345,7 +1345,7 @@ }; }; - cpu39: cpu@39 { + cpu39: cpu@27 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1355,7 +1355,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <39>; + reg = <0x27>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1373,7 +1373,7 @@ }; }; - cpu40: cpu@40 { + cpu40: cpu@28 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1383,7 +1383,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <40>; + reg = <0x28>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1401,7 +1401,7 @@ }; }; - cpu41: cpu@41 { + cpu41: cpu@29 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1411,7 +1411,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <41>; + reg = <0x29>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1429,7 +1429,7 @@ }; }; - cpu42: cpu@42 { + cpu42: cpu@2a { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1439,7 +1439,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <42>; + reg = <0x2a>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1457,7 +1457,7 @@ }; }; - cpu43: cpu@43 { + cpu43: cpu@2b { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1467,7 +1467,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <43>; + reg = <0x2b>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1485,7 +1485,7 @@ }; }; - cpu44: cpu@44 { + cpu44: cpu@2c { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1495,7 +1495,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <44>; + reg = <0x2c>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1513,7 +1513,7 @@ }; }; - cpu45: cpu@45 { + cpu45: cpu@2d { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1523,7 +1523,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <45>; + reg = <0x2d>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1541,7 +1541,7 @@ }; }; - cpu46: cpu@46 { + cpu46: cpu@2e { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1551,7 +1551,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <46>; + reg = <0x2e>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1569,7 +1569,7 @@ }; }; - cpu47: cpu@47 { + cpu47: cpu@2f { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1579,7 +1579,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <47>; + reg = <0x2f>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1597,7 +1597,7 @@ }; }; - cpu48: cpu@48 { + cpu48: cpu@30 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1607,7 +1607,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <48>; + reg = <0x30>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1625,7 +1625,7 @@ }; }; - cpu49: cpu@49 { + cpu49: cpu@31 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1635,7 +1635,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <49>; + reg = <0x31>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1653,7 +1653,7 @@ }; }; - cpu50: cpu@50 { + cpu50: cpu@32 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1663,7 +1663,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <50>; + reg = <0x32>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1681,7 +1681,7 @@ }; }; - cpu51: cpu@51 { + cpu51: cpu@33 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1691,7 +1691,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <51>; + reg = <0x33>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1709,7 +1709,7 @@ }; }; - cpu52: cpu@52 { + cpu52: cpu@34 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1719,7 +1719,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <52>; + reg = <0x34>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1737,7 +1737,7 @@ }; }; - cpu53: cpu@53 { + cpu53: cpu@35 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1747,7 +1747,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <53>; + reg = <0x35>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1765,7 +1765,7 @@ }; }; - cpu54: cpu@54 { + cpu54: cpu@36 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1775,7 +1775,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <54>; + reg = <0x36>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1793,7 +1793,7 @@ }; }; - cpu55: cpu@55 { + cpu55: cpu@37 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1803,7 +1803,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <55>; + reg = <0x37>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1821,7 +1821,7 @@ }; }; - cpu56: cpu@56 { + cpu56: cpu@38 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1831,7 +1831,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <56>; + reg = <0x38>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1849,7 +1849,7 @@ }; }; - cpu57: cpu@57 { + cpu57: cpu@39 { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1859,7 +1859,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <57>; + reg = <0x39>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1877,7 +1877,7 @@ }; }; - cpu58: cpu@58 { + cpu58: cpu@3a { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1887,7 +1887,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <58>; + reg = <0x3a>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1905,7 +1905,7 @@ }; }; - cpu59: cpu@59 { + cpu59: cpu@3b { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1915,7 +1915,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <59>; + reg = <0x3b>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1933,7 +1933,7 @@ }; }; - cpu60: cpu@60 { + cpu60: cpu@3c { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1943,7 +1943,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <60>; + reg = <0x3c>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1961,7 +1961,7 @@ }; }; - cpu61: cpu@61 { + cpu61: cpu@3d { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1971,7 +1971,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <61>; + reg = <0x3d>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1989,7 +1989,7 @@ }; }; - cpu62: cpu@62 { + cpu62: cpu@3e { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -1999,7 +1999,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <62>; + reg = <0x3e>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -2017,7 +2017,7 @@ }; }; - cpu63: cpu@63 { + cpu63: cpu@3f { compatible = "thead,c920", "riscv"; device_type = "cpu"; riscv,isa = "rv64imafdc"; @@ -2027,7 +2027,7 @@ "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; - reg = <63>; + reg = <0x3f>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; diff --git a/arch/riscv/boot/dts/sophgo/sg2042.dtsi b/arch/riscv/boot/dts/sophgo/sg2042.dtsi index 3af77054974262..7eab0655f15002 100644 --- a/arch/riscv/boot/dts/sophgo/sg2042.dtsi +++ b/arch/riscv/boot/dts/sophgo/sg2042.dtsi @@ -234,7 +234,7 @@ reg-names = "clr", "doorbell"; msi-controller; #msi-cells = <0>; - msi-ranges = <&intc 64 IRQ_TYPE_EDGE_RISING 32>; + msi-ranges = <&intc 64 IRQ_TYPE_EDGE_RISING 16>; }; rpgate: clock-controller@7030010368 { diff --git a/arch/riscv/boot/dts/sophgo/sg2044-cpus.dtsi b/arch/riscv/boot/dts/sophgo/sg2044-cpus.dtsi index 3135409c21492f..f66a382c95bd9c 100644 --- a/arch/riscv/boot/dts/sophgo/sg2044-cpus.dtsi +++ b/arch/riscv/boot/dts/sophgo/sg2044-cpus.dtsi @@ -14,7 +14,7 @@ cpu0: cpu@0 { compatible = "thead,c920", "riscv"; - reg = <0>; + reg = <0x0>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -50,7 +50,7 @@ cpu1: cpu@1 { compatible = "thead,c920", "riscv"; - reg = <1>; + reg = <0x1>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -86,7 +86,7 @@ cpu2: cpu@2 { compatible = "thead,c920", "riscv"; - reg = <2>; + reg = <0x2>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -122,7 +122,7 @@ cpu3: cpu@3 { compatible = "thead,c920", "riscv"; - reg = <3>; + reg = <0x3>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -158,7 +158,7 @@ cpu4: cpu@4 { compatible = "thead,c920", "riscv"; - reg = <4>; + reg = <0x4>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -194,7 +194,7 @@ cpu5: cpu@5 { compatible = "thead,c920", "riscv"; - reg = <5>; + reg = <0x5>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -230,7 +230,7 @@ cpu6: cpu@6 { compatible = "thead,c920", "riscv"; - reg = <6>; + reg = <0x6>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -266,7 +266,7 @@ cpu7: cpu@7 { compatible = "thead,c920", "riscv"; - reg = <7>; + reg = <0x7>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -302,7 +302,7 @@ cpu8: cpu@8 { compatible = "thead,c920", "riscv"; - reg = <8>; + reg = <0x8>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -338,7 +338,7 @@ cpu9: cpu@9 { compatible = "thead,c920", "riscv"; - reg = <9>; + reg = <0x9>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -372,9 +372,9 @@ }; }; - cpu10: cpu@10 { + cpu10: cpu@a { compatible = "thead,c920", "riscv"; - reg = <10>; + reg = <0xa>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -408,9 +408,9 @@ }; }; - cpu11: cpu@11 { + cpu11: cpu@b { compatible = "thead,c920", "riscv"; - reg = <11>; + reg = <0xb>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -444,9 +444,9 @@ }; }; - cpu12: cpu@12 { + cpu12: cpu@c { compatible = "thead,c920", "riscv"; - reg = <12>; + reg = <0xc>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -480,9 +480,9 @@ }; }; - cpu13: cpu@13 { + cpu13: cpu@d { compatible = "thead,c920", "riscv"; - reg = <13>; + reg = <0xd>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -516,9 +516,9 @@ }; }; - cpu14: cpu@14 { + cpu14: cpu@e { compatible = "thead,c920", "riscv"; - reg = <14>; + reg = <0xe>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -552,9 +552,9 @@ }; }; - cpu15: cpu@15 { + cpu15: cpu@f { compatible = "thead,c920", "riscv"; - reg = <15>; + reg = <0xf>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -588,9 +588,9 @@ }; }; - cpu16: cpu@16 { + cpu16: cpu@10 { compatible = "thead,c920", "riscv"; - reg = <16>; + reg = <0x10>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -624,9 +624,9 @@ }; }; - cpu17: cpu@17 { + cpu17: cpu@11 { compatible = "thead,c920", "riscv"; - reg = <17>; + reg = <0x11>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -660,9 +660,9 @@ }; }; - cpu18: cpu@18 { + cpu18: cpu@12 { compatible = "thead,c920", "riscv"; - reg = <18>; + reg = <0x12>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -696,9 +696,9 @@ }; }; - cpu19: cpu@19 { + cpu19: cpu@13 { compatible = "thead,c920", "riscv"; - reg = <19>; + reg = <0x13>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -732,9 +732,9 @@ }; }; - cpu20: cpu@20 { + cpu20: cpu@14 { compatible = "thead,c920", "riscv"; - reg = <20>; + reg = <0x14>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -768,9 +768,9 @@ }; }; - cpu21: cpu@21 { + cpu21: cpu@15 { compatible = "thead,c920", "riscv"; - reg = <21>; + reg = <0x15>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -804,9 +804,9 @@ }; }; - cpu22: cpu@22 { + cpu22: cpu@16 { compatible = "thead,c920", "riscv"; - reg = <22>; + reg = <0x16>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -840,9 +840,9 @@ }; }; - cpu23: cpu@23 { + cpu23: cpu@17 { compatible = "thead,c920", "riscv"; - reg = <23>; + reg = <0x17>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -876,9 +876,9 @@ }; }; - cpu24: cpu@24 { + cpu24: cpu@18 { compatible = "thead,c920", "riscv"; - reg = <24>; + reg = <0x18>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -912,9 +912,9 @@ }; }; - cpu25: cpu@25 { + cpu25: cpu@19 { compatible = "thead,c920", "riscv"; - reg = <25>; + reg = <0x19>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -948,9 +948,9 @@ }; }; - cpu26: cpu@26 { + cpu26: cpu@1a { compatible = "thead,c920", "riscv"; - reg = <26>; + reg = <0x1a>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -984,9 +984,9 @@ }; }; - cpu27: cpu@27 { + cpu27: cpu@1b { compatible = "thead,c920", "riscv"; - reg = <27>; + reg = <0x1b>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1020,9 +1020,9 @@ }; }; - cpu28: cpu@28 { + cpu28: cpu@1c { compatible = "thead,c920", "riscv"; - reg = <28>; + reg = <0x1c>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1056,9 +1056,9 @@ }; }; - cpu29: cpu@29 { + cpu29: cpu@1d { compatible = "thead,c920", "riscv"; - reg = <29>; + reg = <0x1d>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1092,9 +1092,9 @@ }; }; - cpu30: cpu@30 { + cpu30: cpu@1e { compatible = "thead,c920", "riscv"; - reg = <30>; + reg = <0x1e>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1128,9 +1128,9 @@ }; }; - cpu31: cpu@31 { + cpu31: cpu@1f { compatible = "thead,c920", "riscv"; - reg = <31>; + reg = <0x1f>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1164,9 +1164,9 @@ }; }; - cpu32: cpu@32 { + cpu32: cpu@20 { compatible = "thead,c920", "riscv"; - reg = <32>; + reg = <0x20>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1200,9 +1200,9 @@ }; }; - cpu33: cpu@33 { + cpu33: cpu@21 { compatible = "thead,c920", "riscv"; - reg = <33>; + reg = <0x21>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1236,9 +1236,9 @@ }; }; - cpu34: cpu@34 { + cpu34: cpu@22 { compatible = "thead,c920", "riscv"; - reg = <34>; + reg = <0x22>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1272,9 +1272,9 @@ }; }; - cpu35: cpu@35 { + cpu35: cpu@23 { compatible = "thead,c920", "riscv"; - reg = <35>; + reg = <0x23>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1308,9 +1308,9 @@ }; }; - cpu36: cpu@36 { + cpu36: cpu@24 { compatible = "thead,c920", "riscv"; - reg = <36>; + reg = <0x24>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1344,9 +1344,9 @@ }; }; - cpu37: cpu@37 { + cpu37: cpu@25 { compatible = "thead,c920", "riscv"; - reg = <37>; + reg = <0x25>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1380,9 +1380,9 @@ }; }; - cpu38: cpu@38 { + cpu38: cpu@26 { compatible = "thead,c920", "riscv"; - reg = <38>; + reg = <0x26>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1416,9 +1416,9 @@ }; }; - cpu39: cpu@39 { + cpu39: cpu@27 { compatible = "thead,c920", "riscv"; - reg = <39>; + reg = <0x27>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1452,9 +1452,9 @@ }; }; - cpu40: cpu@40 { + cpu40: cpu@28 { compatible = "thead,c920", "riscv"; - reg = <40>; + reg = <0x28>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1488,9 +1488,9 @@ }; }; - cpu41: cpu@41 { + cpu41: cpu@29 { compatible = "thead,c920", "riscv"; - reg = <41>; + reg = <0x29>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1524,9 +1524,9 @@ }; }; - cpu42: cpu@42 { + cpu42: cpu@2a { compatible = "thead,c920", "riscv"; - reg = <42>; + reg = <0x2a>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1560,9 +1560,9 @@ }; }; - cpu43: cpu@43 { + cpu43: cpu@2b { compatible = "thead,c920", "riscv"; - reg = <43>; + reg = <0x2b>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1596,9 +1596,9 @@ }; }; - cpu44: cpu@44 { + cpu44: cpu@2c { compatible = "thead,c920", "riscv"; - reg = <44>; + reg = <0x2c>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1632,9 +1632,9 @@ }; }; - cpu45: cpu@45 { + cpu45: cpu@2d { compatible = "thead,c920", "riscv"; - reg = <45>; + reg = <0x2d>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1668,9 +1668,9 @@ }; }; - cpu46: cpu@46 { + cpu46: cpu@2e { compatible = "thead,c920", "riscv"; - reg = <46>; + reg = <0x2e>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1704,9 +1704,9 @@ }; }; - cpu47: cpu@47 { + cpu47: cpu@2f { compatible = "thead,c920", "riscv"; - reg = <47>; + reg = <0x2f>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1740,9 +1740,9 @@ }; }; - cpu48: cpu@48 { + cpu48: cpu@30 { compatible = "thead,c920", "riscv"; - reg = <48>; + reg = <0x30>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1776,9 +1776,9 @@ }; }; - cpu49: cpu@49 { + cpu49: cpu@31 { compatible = "thead,c920", "riscv"; - reg = <49>; + reg = <0x31>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1812,9 +1812,9 @@ }; }; - cpu50: cpu@50 { + cpu50: cpu@32 { compatible = "thead,c920", "riscv"; - reg = <50>; + reg = <0x32>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1848,9 +1848,9 @@ }; }; - cpu51: cpu@51 { + cpu51: cpu@33 { compatible = "thead,c920", "riscv"; - reg = <51>; + reg = <0x33>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1884,9 +1884,9 @@ }; }; - cpu52: cpu@52 { + cpu52: cpu@34 { compatible = "thead,c920", "riscv"; - reg = <52>; + reg = <0x34>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1920,9 +1920,9 @@ }; }; - cpu53: cpu@53 { + cpu53: cpu@35 { compatible = "thead,c920", "riscv"; - reg = <53>; + reg = <0x35>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1956,9 +1956,9 @@ }; }; - cpu54: cpu@54 { + cpu54: cpu@36 { compatible = "thead,c920", "riscv"; - reg = <54>; + reg = <0x36>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -1992,9 +1992,9 @@ }; }; - cpu55: cpu@55 { + cpu55: cpu@37 { compatible = "thead,c920", "riscv"; - reg = <55>; + reg = <0x37>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -2028,9 +2028,9 @@ }; }; - cpu56: cpu@56 { + cpu56: cpu@38 { compatible = "thead,c920", "riscv"; - reg = <56>; + reg = <0x38>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -2064,9 +2064,9 @@ }; }; - cpu57: cpu@57 { + cpu57: cpu@39 { compatible = "thead,c920", "riscv"; - reg = <57>; + reg = <0x39>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -2100,9 +2100,9 @@ }; }; - cpu58: cpu@58 { + cpu58: cpu@3a { compatible = "thead,c920", "riscv"; - reg = <58>; + reg = <0x3a>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -2136,9 +2136,9 @@ }; }; - cpu59: cpu@59 { + cpu59: cpu@3b { compatible = "thead,c920", "riscv"; - reg = <59>; + reg = <0x3b>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -2172,9 +2172,9 @@ }; }; - cpu60: cpu@60 { + cpu60: cpu@3c { compatible = "thead,c920", "riscv"; - reg = <60>; + reg = <0x3c>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -2208,9 +2208,9 @@ }; }; - cpu61: cpu@61 { + cpu61: cpu@3d { compatible = "thead,c920", "riscv"; - reg = <61>; + reg = <0x3d>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -2244,9 +2244,9 @@ }; }; - cpu62: cpu@62 { + cpu62: cpu@3e { compatible = "thead,c920", "riscv"; - reg = <62>; + reg = <0x3e>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; @@ -2280,9 +2280,9 @@ }; }; - cpu63: cpu@63 { + cpu63: cpu@3f { compatible = "thead,c920", "riscv"; - reg = <63>; + reg = <0x3f>; i-cache-block-size = <64>; i-cache-size = <65536>; i-cache-sets = <512>; diff --git a/arch/riscv/boot/dts/spacemit/Makefile b/arch/riscv/boot/dts/spacemit/Makefile index 7e2b8770257181..28ec9e1e0a625c 100644 --- a/arch/riscv/boot/dts/spacemit/Makefile +++ b/arch/riscv/boot/dts/spacemit/Makefile @@ -4,4 +4,6 @@ dtb-$(CONFIG_ARCH_SPACEMIT) += k1-milkv-jupiter.dtb dtb-$(CONFIG_ARCH_SPACEMIT) += k1-musepi-pro.dtb dtb-$(CONFIG_ARCH_SPACEMIT) += k1-orangepi-r2s.dtb dtb-$(CONFIG_ARCH_SPACEMIT) += k1-orangepi-rv2.dtb +dtb-$(CONFIG_ARCH_SPACEMIT) += k3-com260-ifx.dtb +dtb-$(CONFIG_ARCH_SPACEMIT) += k3-deepcomputing-fml13v05.dtb dtb-$(CONFIG_ARCH_SPACEMIT) += k3-pico-itx.dtb diff --git a/arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts b/arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts index 5790d927b93db3..8d2c161f5b356b 100644 --- a/arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts +++ b/arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts @@ -4,6 +4,7 @@ */ #include "k1.dtsi" +#include "k1-opp.dtsi" #include "k1-pinctrl.dtsi" / { @@ -14,12 +15,15 @@ ethernet0 = ð0; ethernet1 = ð1; serial0 = &uart0; + spi3 = &spi3; i2c2 = &i2c2; i2c8 = &i2c8; + mmc0 = &emmc; + mmc1 = &sdhci0; }; chosen { - stdout-path = "serial0"; + stdout-path = "serial0:115200n8"; }; leds { @@ -50,6 +54,16 @@ regulator-always-on; }; + reg_vcc5v0_sys: regulator-vcc5v0-sys { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_sys"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + regulator-always-on; + vin-supply = <®_dc_in>; + }; + reg_vcc_4v: regulator-vcc-4v { compatible = "regulator-fixed"; regulator-name = "vcc_4v"; @@ -60,21 +74,24 @@ vin-supply = <®_dc_in>; }; - regulator-usb3-vbus-5v { + reg_5v_vbus: regulator-usb3-vbus-5v { compatible = "regulator-fixed"; - regulator-name = "USB30_VBUS"; + regulator-name = "5V_VBUS"; regulator-min-microvolt = <5000000>; regulator-max-microvolt = <5000000>; regulator-always-on; + vin-supply = <®_vcc5v0_sys>; gpio = <&gpio K1_GPIO(97) GPIO_ACTIVE_HIGH>; enable-active-high; }; usb3_hub_5v: regulator-usb3-hub-5v { compatible = "regulator-fixed"; - regulator-name = "USB30_HUB"; + regulator-name = "VCC5V0_HUB"; regulator-min-microvolt = <5000000>; regulator-max-microvolt = <5000000>; + regulator-always-on; + vin-supply = <®_vcc5v0_sys>; gpio = <&gpio K1_GPIO(123) GPIO_ACTIVE_HIGH>; enable-active-high; }; @@ -84,6 +101,38 @@ status = "okay"; }; +&cpu_0 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_1 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_2 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_3 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_4 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_5 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_6 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_7 { + cpu-supply = <&buck1_0v9>; +}; + &emmc { bus-width = <8>; mmc-hs400-1_8v; @@ -199,7 +248,7 @@ dldoin2-supply = <&buck5>; regulators { - buck1 { + buck1_0v9: buck1 { regulator-min-microvolt = <500000>; regulator-max-microvolt = <3450000>; regulator-ramp-delay = <5000>; @@ -220,7 +269,7 @@ regulator-always-on; }; - buck4 { + buck4: buck4 { regulator-min-microvolt = <500000>; regulator-max-microvolt = <3300000>; regulator-ramp-delay = <5000>; @@ -241,7 +290,7 @@ regulator-always-on; }; - aldo1 { + aldo1: aldo1 { regulator-min-microvolt = <500000>; regulator-max-microvolt = <3400000>; regulator-boot-on; @@ -335,6 +384,12 @@ status = "okay"; }; +&spi3 { + pinctrl-0 = <&ssp3_0_cfg>; + pinctrl-names = "default"; + status = "okay"; +}; + &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_2_cfg>; @@ -349,12 +404,13 @@ dr_mode = "host"; #address-cells = <1>; #size-cells = <0>; + vbus-supply = <&usb3_hub_5v>; status = "okay"; hub_2_0: hub@1 { compatible = "usb2109,2817"; reg = <0x1>; - vdd-supply = <&usb3_hub_5v>; + vdd-supply = <®_5v_vbus>; peer-hub = <&hub_3_0>; reset-gpios = <&gpio K1_GPIO(124) GPIO_ACTIVE_LOW>; }; @@ -362,8 +418,26 @@ hub_3_0: hub@2 { compatible = "usb2109,817"; reg = <0x2>; - vdd-supply = <&usb3_hub_5v>; + vdd-supply = <®_5v_vbus>; peer-hub = <&hub_2_0>; reset-gpios = <&gpio K1_GPIO(124) GPIO_ACTIVE_LOW>; }; }; + +&sdhci0 { + pinctrl-names = "default", "uhs"; + pinctrl-0 = <&mmc1_cfg>; + pinctrl-1 = <&mmc1_uhs_cfg>; + bus-width = <4>; + cd-gpios = <&gpio K1_GPIO(80) (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; + no-mmc; + no-sdio; + disable-wp; + cap-sd-highspeed; + vmmc-supply = <&buck4>; + vqmmc-supply = <&aldo1>; + sd-uhs-sdr25; + sd-uhs-sdr50; + sd-uhs-sdr104; + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/spacemit/k1-milkv-jupiter.dts b/arch/riscv/boot/dts/spacemit/k1-milkv-jupiter.dts index afaad59e6bce22..2fc8d653378637 100644 --- a/arch/riscv/boot/dts/spacemit/k1-milkv-jupiter.dts +++ b/arch/riscv/boot/dts/spacemit/k1-milkv-jupiter.dts @@ -14,13 +14,15 @@ aliases { ethernet0 = ð0; ethernet1 = ð1; - serial0 = &uart0; i2c2 = &i2c2; i2c8 = &i2c8; + mmc0 = &emmc; + mmc1 = &sdhci0; + serial0 = &uart0; }; chosen { - stdout-path = "serial0"; + stdout-path = "serial0:115200n8"; }; leds { @@ -105,6 +107,16 @@ status = "okay"; }; +&emmc { + bus-width = <8>; + mmc-hs400-1_8v; + mmc-hs400-enhanced-strobe; + non-removable; + no-sd; + no-sdio; + status = "okay"; +}; + ð0 { phy-handle = <&rgmii0>; phy-mode = "rgmii-id"; @@ -239,7 +251,7 @@ regulator-always-on; }; - aldo1 { + aldo1: aldo1 { regulator-min-microvolt = <500000>; regulator-max-microvolt = <3400000>; regulator-boot-on; @@ -367,13 +379,31 @@ opensbi@70000 { reg = <0x70000 0x30000>; }; - uboot@a00000 { + uboot@a0000 { reg = <0xa0000 0x760000>; }; }; }; }; +&sdhci0 { + pinctrl-names = "default", "uhs"; + pinctrl-0 = <&mmc1_cfg>; + pinctrl-1 = <&mmc1_uhs_cfg>; + bus-width = <4>; + cd-gpios = <&gpio K1_GPIO(80) (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; + no-mmc; + no-sdio; + disable-wp; + cap-sd-highspeed; + vmmc-supply = <&buck4_3v3>; + vqmmc-supply = <&aldo1>; + sd-uhs-sdr25; + sd-uhs-sdr50; + sd-uhs-sdr104; + status = "okay"; +}; + &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_2_cfg>; diff --git a/arch/riscv/boot/dts/spacemit/k1-musepi-pro.dts b/arch/riscv/boot/dts/spacemit/k1-musepi-pro.dts index 29e333b670cf0a..1b425011f1231d 100644 --- a/arch/riscv/boot/dts/spacemit/k1-musepi-pro.dts +++ b/arch/riscv/boot/dts/spacemit/k1-musepi-pro.dts @@ -9,6 +9,7 @@ #include "k1.dtsi" #include "k1-pinctrl.dtsi" +#include "k1-opp.dtsi" / { model = "SpacemiT MusePi Pro"; @@ -17,10 +18,14 @@ aliases { ethernet0 = ð0; serial0 = &uart0; + i2c2 = &i2c2; + i2c8 = &i2c8; + mmc0 = &emmc; + mmc1 = &sdhci0; }; chosen { - stdout-path = "serial0"; + stdout-path = "serial0:115200n8"; }; leds { @@ -33,6 +38,52 @@ default-state = "on"; }; }; + + reg_pcie_vcc_3v3: regulator-pcie-vcc3v3 { + compatible = "regulator-fixed"; + regulator-name = "PCIE_VCC3V3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + reg_vcc_4v: regulator-vcc-4v { + compatible = "regulator-fixed"; + regulator-name = "VCC4V0"; + regulator-min-microvolt = <4000000>; + regulator-max-microvolt = <4000000>; + regulator-boot-on; + regulator-always-on; + }; + + reg_qspi_vcc1v833: regulator-qspi-vcc1v833 { + compatible = "regulator-fixed"; + regulator-name = "QSPI_VCC1833"; + regulator-min-microvolt = <1833000>; + regulator-max-microvolt = <1833000>; + regulator-always-on; + vin-supply = <&buck4_3v3>; + }; + + reg_5v_vbus: regulator-5v-vbus { + compatible = "regulator-fixed"; + regulator-name = "5V_VBUS"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + gpio = <&gpio K1_GPIO(79) GPIO_ACTIVE_HIGH>; /* USB3_PWREN */ + enable-active-high; + }; + + reg_vcc5v_hub: regulator-vcc5v-hub { + compatible = "regulator-fixed"; + regulator-name = "VCC5V0_HUB"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + gpio = <&gpio K1_GPIO(127) GPIO_ACTIVE_HIGH>; /* HUB_PWREN */ + enable-active-high; + }; }; &emmc { @@ -45,6 +96,42 @@ status = "okay"; }; +&combo_phy { + status = "okay"; +}; + +&cpu_0 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_1 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_2 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_3 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_4 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_5 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_6 { + cpu-supply = <&buck1_0v9>; +}; + +&cpu_7 { + cpu-supply = <&buck1_0v9>; +}; + ð0 { phy-handle = <&rgmii0>; phy-mode = "rgmii-id"; @@ -72,8 +159,281 @@ status = "okay"; }; +&i2c2 { + pinctrl-0 = <&i2c2_0_cfg>; + pinctrl-names = "default"; + status = "okay"; + + eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + vcc-supply = <&buck3_1v8>; /* EEPROM_VCC1V8 */ + pagesize = <8>; + read-only; + size = <256>; + + nvmem-layout { + compatible = "onie,tlv-layout"; + + product-name { + }; + + serial-number { + }; + + mac-address { + #nvmem-cell-cells = <1>; + }; + + num-macs { + }; + }; + }; +}; + +&i2c8 { + pinctrl-0 = <&i2c8_cfg>; + pinctrl-names = "default"; + status = "okay"; + + pmic@41 { + compatible = "spacemit,p1"; + reg = <0x41>; + interrupts = <64>; + vin1-supply = <®_vcc_4v>; + vin2-supply = <®_vcc_4v>; + vin3-supply = <®_vcc_4v>; + vin4-supply = <®_vcc_4v>; + vin5-supply = <®_vcc_4v>; + vin6-supply = <®_vcc_4v>; + aldoin-supply = <®_vcc_4v>; + dldoin1-supply = <&buck5>; + dldoin2-supply = <&buck5>; + + regulators { + buck1_0v9: buck1 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck2 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck3_1v8: buck3 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1800000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck4_3v3: buck4 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3300000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck5: buck5 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck6 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + aldo1: aldo1 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-boot-on; + }; + + aldo2 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + aldo3 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + aldo4 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + dldo1 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-boot-on; + }; + + dldo2 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + dldo3 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + dldo4 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-always-on; + }; + + dldo5 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + dldo6 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-always-on; + }; + + dldo7 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + }; + }; +}; + +&pcie1_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pcie1_3_cfg>; + status = "okay"; +}; + +&pcie1_port { + phys = <&pcie1_phy>; + vpcie3v3-supply = <®_pcie_vcc_3v3>; +}; + +&pcie1 { + status = "okay"; +}; + +&pcie2_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pcie2_4_cfg>; + status = "okay"; +}; + +&pcie2_port { + phys = <&pcie2_phy>; + vpcie3v3-supply = <®_pcie_vcc_3v3>; +}; + +&pcie2 { + status = "okay"; +}; + +&qspi { + pinctrl-names = "default"; + pinctrl-0 = <&qspi_cfg>; + status = "okay"; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <26500000>; + spi-rx-bus-width = <4>; + spi-tx-bus-width = <4>; + vcc-supply = <®_qspi_vcc1v833>; + m25p,fast-read; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + bootinfo@0 { + reg = <0x0 0x10000>; + }; + private@10000 { + reg = <0x10000 0x10000>; + }; + fsbl@20000 { + reg = <0x20000 0x40000>; + }; + env@60000 { + reg = <0x60000 0x10000>; + }; + opensbi@70000 { + reg = <0x70000 0x30000>; + }; + uboot@a0000 { + reg = <0xa0000 0x760000>; + }; + }; + }; +}; + &uart0 { pinctrl-0 = <&uart0_2_cfg>; pinctrl-names = "default"; status = "okay"; }; + +&usbphy2 { + status = "okay"; +}; + +&usb_dwc3 { + dr_mode = "host"; + #address-cells = <1>; + #size-cells = <0>; + vbus-supply = <®_5v_vbus>; + status = "okay"; + + hub_2_0: hub@1 { + compatible = "usb2109,2817"; + reg = <0x1>; + vdd-supply = <®_vcc5v_hub>; + peer-hub = <&hub_3_0>; + reset-gpios = <&gpio K1_GPIO(123) GPIO_ACTIVE_LOW>; /* HUB_RST */ + }; + + hub_3_0: hub@2 { + compatible = "usb2109,817"; + reg = <0x2>; + vdd-supply = <®_vcc5v_hub>; + peer-hub = <&hub_2_0>; + reset-gpios = <&gpio K1_GPIO(123) GPIO_ACTIVE_LOW>; /* HUB_RST */ + }; +}; + +&sdhci0 { + pinctrl-names = "default", "uhs"; + pinctrl-0 = <&mmc1_cfg>; + pinctrl-1 = <&mmc1_uhs_cfg>; + bus-width = <4>; + cd-gpios = <&gpio K1_GPIO(80) (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>; + no-mmc; + no-sdio; + disable-wp; + cap-sd-highspeed; + vmmc-supply = <&buck4_3v3>; + vqmmc-supply = <&aldo1>; + sd-uhs-sdr25; + sd-uhs-sdr50; + sd-uhs-sdr104; + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/spacemit/k1-opp.dtsi b/arch/riscv/boot/dts/spacemit/k1-opp.dtsi new file mode 100644 index 00000000000000..4cebfcd87485f2 --- /dev/null +++ b/arch/riscv/boot/dts/spacemit/k1-opp.dtsi @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) + +/ { + cluster0_opp_table: opp-table-cluster0 { + compatible = "operating-points-v2"; + opp-shared; + + opp-614400000 { + opp-hz = /bits/ 64 <614400000>; + opp-microvolt = <950000 950000 1050000>; + clock-latency-ns = <200000>; + }; + + opp-819000000 { + opp-hz = /bits/ 64 <819000000>; + opp-microvolt = <950000 950000 1050000>; + clock-latency-ns = <200000>; + }; + + opp-1000000000 { + opp-hz = /bits/ 64 <1000000000>; + opp-microvolt = <950000 950000 1050000>; + clock-latency-ns = <200000>; + }; + + opp-1228800000 { + opp-hz = /bits/ 64 <1228800000>; + opp-microvolt = <950000 950000 1050000>; + clock-latency-ns = <200000>; + }; + + opp-1600000000 { + opp-hz = /bits/ 64 <1600000000>; + opp-microvolt = <1050000 1050000 1050000>; + clock-latency-ns = <200000>; + }; + }; + + cluster1_opp_table: opp-table-cluster1 { + compatible = "operating-points-v2"; + opp-shared; + + opp-614400000 { + opp-hz = /bits/ 64 <614400000>; + opp-microvolt = <950000 950000 1050000>; + clock-latency-ns = <200000>; + }; + + opp-819000000 { + opp-hz = /bits/ 64 <819000000>; + opp-microvolt = <950000 950000 1050000>; + clock-latency-ns = <200000>; + }; + + opp-1000000000 { + opp-hz = /bits/ 64 <1000000000>; + opp-microvolt = <950000 950000 1050000>; + clock-latency-ns = <200000>; + }; + + opp-1228800000 { + opp-hz = /bits/ 64 <1228800000>; + opp-microvolt = <950000 950000 1050000>; + clock-latency-ns = <200000>; + }; + + opp-1600000000 { + opp-hz = /bits/ 64 <1600000000>; + opp-microvolt = <1050000 1050000 1050000>; + clock-latency-ns = <200000>; + }; + }; +}; + +&cpu_0 { + operating-points-v2 = <&cluster0_opp_table>; +}; + +&cpu_1 { + operating-points-v2 = <&cluster0_opp_table>; +}; + +&cpu_2 { + operating-points-v2 = <&cluster0_opp_table>; +}; + +&cpu_3 { + operating-points-v2 = <&cluster0_opp_table>; +}; + +&cpu_4 { + operating-points-v2 = <&cluster1_opp_table>; +}; + +&cpu_5 { + operating-points-v2 = <&cluster1_opp_table>; +}; + +&cpu_6 { + operating-points-v2 = <&cluster1_opp_table>; +}; + +&cpu_7 { + operating-points-v2 = <&cluster1_opp_table>; +}; diff --git a/arch/riscv/boot/dts/spacemit/k1-orangepi-r2s.dts b/arch/riscv/boot/dts/spacemit/k1-orangepi-r2s.dts index de75f6aac74055..919e5b45110961 100644 --- a/arch/riscv/boot/dts/spacemit/k1-orangepi-r2s.dts +++ b/arch/riscv/boot/dts/spacemit/k1-orangepi-r2s.dts @@ -13,6 +13,7 @@ compatible = "xunlong,orangepi-r2s", "spacemit,k1"; aliases { + i2c8 = &i2c8; serial0 = &uart0; ethernet0 = ð0; ethernet1 = ð1; @@ -21,6 +22,36 @@ chosen { stdout-path = "serial0"; }; + + pcie_vcc3v3: regulator-pcie-vcc3v3 { + compatible = "regulator-fixed"; + regulator-name = "pcie_vcc3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + vcc4v0: regulator-vcc4v0 { + compatible = "regulator-fixed"; + regulator-name = "vcc4v0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <4000000>; + regulator-max-microvolt = <4000000>; + }; + + vcc5v0_usb: regulator-vcc5v0-usb { + compatible = "regulator-fixed"; + enable-active-high; + gpios = <&gpio K1_GPIO(126) GPIO_ACTIVE_HIGH>; + regulator-name = "vcc5v0_usb"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + }; +}; + +&combo_phy { + status = "okay"; }; &emmc { @@ -81,6 +112,160 @@ }; }; +&i2c8 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c8_cfg>; + status = "okay"; + + pmic@41 { + compatible = "spacemit,p1"; + reg = <0x41>; + interrupts = <64>; + vin1-supply = <&vcc4v0>; + vin2-supply = <&vcc4v0>; + vin3-supply = <&vcc4v0>; + vin4-supply = <&vcc4v0>; + vin5-supply = <&vcc4v0>; + vin6-supply = <&vcc4v0>; + aldoin-supply = <&vcc4v0>; + dldoin1-supply = <&buck5>; + dldoin2-supply = <&buck5>; + + regulators { + buck1 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck2 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck3_1v8: buck3 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1800000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck4_3v3: buck4 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3300000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck5: buck5 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck6 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + aldo1_3v3: aldo1 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-boot-on; + }; + + aldo2 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + aldo3 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + aldo4 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + dldo1 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-boot-on; + }; + + dldo2 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + dldo3 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + dldo4 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-always-on; + }; + + dldo5 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + dldo6 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-always-on; + }; + + dldo7 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + }; + }; +}; + +&pcie1_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pcie1_3_cfg>; + status = "okay"; +}; + +&pcie1_port { + phys = <&pcie1_phy>; + vpcie3v3-supply = <&pcie_vcc3v3>; +}; + +&pcie1 { + status = "okay"; +}; + +&pcie2_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pcie2_4_cfg>; + status = "okay"; +}; + +&pcie2_port { + phys = <&pcie2_phy>; + vpcie3v3-supply = <&pcie_vcc3v3>; +}; + +&pcie2 { + status = "okay"; +}; + &pdma { status = "okay"; }; @@ -90,3 +275,13 @@ pinctrl-0 = <&uart0_2_cfg>; status = "okay"; }; + +&usbphy2 { + status = "okay"; +}; + +&usb_dwc3 { + dr_mode = "host"; + vbus-supply = <&vcc5v0_usb>; + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts b/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts index 7b7331cb3c726f..07dbd292eee33e 100644 --- a/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts +++ b/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts @@ -17,10 +17,51 @@ serial0 = &uart0; ethernet0 = ð0; ethernet1 = ð1; + mmc0 = &emmc; + mmc1 = &sdhci0; }; chosen { - stdout-path = "serial0"; + stdout-path = "serial0:115200n8"; + }; + + pcie_vcc3v3: regulator-pcie-vcc3v3 { + compatible = "regulator-fixed"; + enable-active-high; + gpios = <&gpio K1_GPIO(116) GPIO_ACTIVE_HIGH>; + regulator-name = "pcie_vcc3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vcc_5v0>; + }; + + vcc_5v0: regulator-vcc-5v0 { + compatible = "regulator-fixed"; + regulator-name = "vcc_5v0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + }; + + vcc4v0: regulator-vcc4v0 { + compatible = "regulator-fixed"; + regulator-name = "vcc4v0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <4000000>; + regulator-max-microvolt = <4000000>; + vin-supply = <&vcc_5v0>; + }; + + vcc5v0_usb30: regulator-vcc5v0-usb30 { + compatible = "regulator-fixed"; + enable-active-high; + gpios = <&gpio K1_GPIO(123) GPIO_ACTIVE_HIGH>; + regulator-name = "vcc5v0_usb30"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + vin-supply = <&vcc_5v0>; }; leds { @@ -35,6 +76,20 @@ }; }; +&combo_phy { + status = "okay"; +}; + +&emmc { + bus-width = <8>; + mmc-hs400-1_8v; + mmc-hs400-enhanced-strobe; + no-sd; + no-sdio; + non-removable; + status = "okay"; +}; + ð0 { phy-handle = <&rgmii0>; phy-mode = "rgmii-id"; @@ -87,8 +142,245 @@ status = "okay"; }; +&i2c8 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c8_cfg>; + status = "okay"; + + pmic@41 { + compatible = "spacemit,p1"; + reg = <0x41>; + interrupts = <64>; + vin1-supply = <&vcc4v0>; + vin2-supply = <&vcc4v0>; + vin3-supply = <&vcc4v0>; + vin4-supply = <&vcc4v0>; + vin5-supply = <&vcc4v0>; + vin6-supply = <&vcc4v0>; + aldoin-supply = <&vcc4v0>; + dldoin1-supply = <&buck5>; + dldoin2-supply = <&buck5>; + + regulators { + buck1 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck2 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck3_1v8: buck3 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1800000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck4_3v3: buck4 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3300000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck5: buck5 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck6 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3450000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + aldo1: aldo1 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-boot-on; + }; + + aldo2 { + /* not connected */ + }; + + aldo3 { + /* not connected */ + }; + + aldo4 { + /* not connected */ + }; + + dldo1 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-boot-on; + }; + + dldo2 { + /* not connected */ + }; + + dldo3 { + /* not connected */ + }; + + dldo4 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-always-on; + }; + + dldo5 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + dldo6 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + regulator-always-on; + }; + + dldo7 { + /* not connected */ + }; + }; + }; +}; + +&pcie1_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pcie1_3_cfg>; + status = "okay"; +}; + +&pcie1_port { + phys = <&pcie1_phy>; + vpcie3v3-supply = <&pcie_vcc3v3>; +}; + +&pcie1 { + status = "okay"; +}; + +&pcie2_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pcie2_4_cfg>; + status = "okay"; +}; + +&pcie2_port { + phys = <&pcie2_phy>; + vpcie3v3-supply = <&pcie_vcc3v3>; +}; + +&pcie2 { + status = "okay"; +}; + +&qspi { + pinctrl-names = "default"; + pinctrl-0 = <&qspi_cfg>; + status = "okay"; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <26500000>; + spi-rx-bus-width = <4>; + spi-tx-bus-width = <4>; + vcc-supply = <&buck3_1v8>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + bootinfo@0 { + reg = <0x00000 0x010000>; + }; + + private@10000 { + reg = <0x10000 0x010000>; + }; + + fsbl@20000 { + reg = <0x20000 0x040000>; + }; + + env@60000 { + reg = <0x60000 0x010000>; + }; + + opensbi@70000 { + reg = <0x70000 0x030000>; + }; + + uboot@a0000 { + reg = <0xa0000 0x760000>; + }; + }; + }; +}; + &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_2_cfg>; status = "okay"; }; + +&usbphy2 { + status = "okay"; +}; + +&usb_dwc3 { + dr_mode = "host"; + #address-cells = <1>; + #size-cells = <0>; + vbus-supply = <&vcc5v0_usb30>; + status = "okay"; + + hub_2_0: hub@1 { + compatible = "usb5e3,610"; + reg = <0x1>; + peer-hub = <&hub_3_0>; + vdd-supply = <&vcc_5v0>; + }; + + hub_3_0: hub@2 { + compatible = "usb5e3,620"; + reg = <0x2>; + peer-hub = <&hub_2_0>; + vdd-supply = <&vcc_5v0>; + }; +}; + +&sdhci0 { + pinctrl-names = "default", "uhs"; + pinctrl-0 = <&mmc1_cfg>; + pinctrl-1 = <&mmc1_uhs_cfg>; + bus-width = <4>; + cd-gpios = <&gpio K1_GPIO(80) GPIO_ACTIVE_LOW>; + no-mmc; + no-sdio; + disable-wp; + cap-sd-highspeed; + vmmc-supply = <&buck4_3v3>; + vqmmc-supply = <&aldo1>; + sd-uhs-sdr25; + sd-uhs-sdr50; + sd-uhs-sdr104; + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/spacemit/k1-pdma.h b/arch/riscv/boot/dts/spacemit/k1-pdma.h new file mode 100644 index 00000000000000..7e5ad3d7111d46 --- /dev/null +++ b/arch/riscv/boot/dts/spacemit/k1-pdma.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * DMA request number (DRQ) definitions for non-secure peripherals of + * the SpacemiT K1 PDMA. + * + * Copyright (c) 2026 Guodong Xu + */ + +#ifndef _DTS_SPACEMIT_K1_PDMA_H +#define _DTS_SPACEMIT_K1_PDMA_H + +#define K1_PDMA_UART0_TX 3 +#define K1_PDMA_UART0_RX 4 +#define K1_PDMA_UART2_TX 5 +#define K1_PDMA_UART2_RX 6 +#define K1_PDMA_UART3_TX 7 +#define K1_PDMA_UART3_RX 8 +#define K1_PDMA_UART4_TX 9 +#define K1_PDMA_UART4_RX 10 +#define K1_PDMA_I2C0_TX 11 +#define K1_PDMA_I2C0_RX 12 +#define K1_PDMA_I2C1_TX 13 +#define K1_PDMA_I2C1_RX 14 +#define K1_PDMA_I2C2_TX 15 +#define K1_PDMA_I2C2_RX 16 +#define K1_PDMA_I2C4_TX 17 +#define K1_PDMA_I2C4_RX 18 +#define K1_PDMA_SPI3_TX 19 +#define K1_PDMA_SPI3_RX 20 +#define K1_PDMA_I2S0_TX 21 +#define K1_PDMA_I2S0_RX 22 +#define K1_PDMA_I2S1_TX 23 +#define K1_PDMA_I2S1_RX 24 +#define K1_PDMA_UART5_TX 25 +#define K1_PDMA_UART5_RX 26 +#define K1_PDMA_UART6_TX 27 +#define K1_PDMA_UART6_RX 28 +#define K1_PDMA_UART7_TX 29 +#define K1_PDMA_UART7_RX 30 +#define K1_PDMA_UART8_TX 31 +#define K1_PDMA_UART8_RX 32 +#define K1_PDMA_UART9_TX 33 +#define K1_PDMA_UART9_RX 34 +#define K1_PDMA_I2C5_TX 35 +#define K1_PDMA_I2C5_RX 36 +#define K1_PDMA_I2C6_TX 37 +#define K1_PDMA_I2C6_RX 38 +#define K1_PDMA_I2C7_TX 39 +#define K1_PDMA_I2C7_RX 40 +#define K1_PDMA_I2C8_TX 41 +#define K1_PDMA_I2C8_RX 42 +#define K1_PDMA_CAN0_RX 43 +#define K1_PDMA_QSPI_RX 44 +#define K1_PDMA_QSPI_TX 45 + +#endif /* _DTS_SPACEMIT_K1_PDMA_H */ diff --git a/arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi b/arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi index b13dcb10f4d660..4e9a62d0e85b5d 100644 --- a/arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi +++ b/arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi @@ -570,4 +570,64 @@ drive-strength = <32>; }; }; + + ssp3_0_cfg: ssp3-0-cfg { + ssp3-0-pins { + pinmux = , /* SCLK */ + , /* MOSI */ + ; /* MISO */ + + bias-disable; + drive-strength = <19>; + power-source = <3300>; + }; + + ssp3-0-frm-pins { + pinmux = ; /* FRM (frame) */ + + bias-pull-up = <0>; + drive-strength = <19>; + power-source = <3300>; + }; + }; + + mmc1_cfg: mmc1-cfg { + mmc1-data-cmd-pins { + pinmux = , /* mmc1_d3 */ + , /* mmc1_d2 */ + , /* mmc1_d1 */ + , /* mmc1_d0 */ + ; /* mmc1_cmd */ + bias-pull-up = <1>; + drive-strength = <19>; + power-source = <3300>; + }; + + mmc1-clk-pins { + pinmux = ; /* mmc1_clk */ + bias-pull-down = <1>; + drive-strength = <19>; + power-source = <3300>; + }; + }; + + mmc1_uhs_cfg: mmc1-uhs-cfg { + mmc1-data-cmd-pins { + pinmux = , /* mmc1_d3 */ + , /* mmc1_d2 */ + , /* mmc1_d1 */ + , /* mmc1_d0 */ + ; /* mmc1_cmd */ + bias-pull-up = <1>; + drive-strength = <42>; + power-source = <1800>; + }; + + mmc1-clk-pins { + pinmux = ; /* mmc1_clk */ + bias-pull-down = <1>; + drive-strength = <42>; + power-source = <1800>; + }; + }; }; diff --git a/arch/riscv/boot/dts/spacemit/k1.dtsi b/arch/riscv/boot/dts/spacemit/k1.dtsi index f0bad6855c970a..3fabfd34114b23 100644 --- a/arch/riscv/boot/dts/spacemit/k1.dtsi +++ b/arch/riscv/boot/dts/spacemit/k1.dtsi @@ -6,6 +6,8 @@ #include #include +#include "k1-pdma.h" + /dts-v1/; / { #address-cells = <2>; @@ -54,6 +56,7 @@ compatible = "spacemit,x60", "riscv"; device_type = "cpu"; reg = <0>; + clocks = <&syscon_apmu CLK_CPU_C0_CORE>; riscv,isa = "rv64imafdcbv_zicbom_zicbop_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zihpm_zfh_zba_zbb_zbc_zbs_zkt_zvfh_zvkt_sscofpmf_sstc_svinval_svnapot_svpbmt"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "b", "v", "zicbom", @@ -84,6 +87,7 @@ compatible = "spacemit,x60", "riscv"; device_type = "cpu"; reg = <1>; + clocks = <&syscon_apmu CLK_CPU_C0_CORE>; riscv,isa = "rv64imafdcbv_zicbom_zicbop_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zihpm_zfh_zba_zbb_zbc_zbs_zkt_zvfh_zvkt_sscofpmf_sstc_svinval_svnapot_svpbmt"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "b", "v", "zicbom", @@ -114,6 +118,7 @@ compatible = "spacemit,x60", "riscv"; device_type = "cpu"; reg = <2>; + clocks = <&syscon_apmu CLK_CPU_C0_CORE>; riscv,isa = "rv64imafdcbv_zicbom_zicbop_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zihpm_zfh_zba_zbb_zbc_zbs_zkt_zvfh_zvkt_sscofpmf_sstc_svinval_svnapot_svpbmt"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "b", "v", "zicbom", @@ -144,6 +149,7 @@ compatible = "spacemit,x60", "riscv"; device_type = "cpu"; reg = <3>; + clocks = <&syscon_apmu CLK_CPU_C0_CORE>; riscv,isa = "rv64imafdcbv_zicbom_zicbop_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zihpm_zfh_zba_zbb_zbc_zbs_zkt_zvfh_zvkt_sscofpmf_sstc_svinval_svnapot_svpbmt"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "b", "v", "zicbom", @@ -174,6 +180,7 @@ compatible = "spacemit,x60", "riscv"; device_type = "cpu"; reg = <4>; + clocks = <&syscon_apmu CLK_CPU_C1_CORE>; riscv,isa = "rv64imafdcbv_zicbom_zicbop_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zihpm_zfh_zba_zbb_zbc_zbs_zkt_zvfh_zvkt_sscofpmf_sstc_svinval_svnapot_svpbmt"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "b", "v", "zicbom", @@ -204,6 +211,7 @@ compatible = "spacemit,x60", "riscv"; device_type = "cpu"; reg = <5>; + clocks = <&syscon_apmu CLK_CPU_C1_CORE>; riscv,isa = "rv64imafdcbv_zicbom_zicbop_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zihpm_zfh_zba_zbb_zbc_zbs_zkt_zvfh_zvkt_sscofpmf_sstc_svinval_svnapot_svpbmt"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "b", "v", "zicbom", @@ -234,6 +242,7 @@ compatible = "spacemit,x60", "riscv"; device_type = "cpu"; reg = <6>; + clocks = <&syscon_apmu CLK_CPU_C1_CORE>; riscv,isa = "rv64imafdcbv_zicbom_zicbop_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zihpm_zfh_zba_zbb_zbc_zbs_zkt_zvfh_zvkt_sscofpmf_sstc_svinval_svnapot_svpbmt"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "b", "v", "zicbom", @@ -264,6 +273,7 @@ compatible = "spacemit,x60", "riscv"; device_type = "cpu"; reg = <7>; + clocks = <&syscon_apmu CLK_CPU_C1_CORE>; riscv,isa = "rv64imafdcbv_zicbom_zicbop_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zihpm_zfh_zba_zbb_zbc_zbs_zkt_zvfh_zvkt_sscofpmf_sstc_svinval_svnapot_svpbmt"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "b", "v", "zicbom", @@ -333,12 +343,102 @@ osc_32k: clock-32k { compatible = "fixed-clock"; - clock-frequency = <32000>; + clock-frequency = <32768>; clock-output-names = "osc_32k"; #clock-cells = <0>; }; }; + thermal-zones { + soc-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&thermal 0>; + + trips { + soc-crit { + temperature = <115000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; + + package-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&thermal 1>; + + trips { + package-crit { + temperature = <115000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; + + gpu-thermal { + polling-delay-passive = <100>; + polling-delay = <0>; + thermal-sensors = <&thermal 2>; + + trips { + gpu-alert { + temperature = <85000>; + hysteresis = <2000>; + type = "passive"; + }; + + gpu-crit { + temperature = <115000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; + + cluster0-thermal { + polling-delay-passive = <100>; + polling-delay = <0>; + thermal-sensors = <&thermal 3>; + + trips { + cluster0-alert { + temperature = <85000>; + hysteresis = <2000>; + type = "passive"; + }; + + cluster0-crit { + temperature = <115000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; + + cluster1-thermal { + polling-delay-passive = <100>; + polling-delay = <0>; + thermal-sensors = <&thermal 4>; + + trips { + cluster1-alert { + temperature = <85000>; + hysteresis = <2000>; + type = "passive"; + }; + + cluster1-crit { + temperature = <115000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; + }; + soc { compatible = "simple-bus"; interrupt-parent = <&plic>; @@ -494,6 +594,17 @@ #reset-cells = <1>; }; + thermal: thermal@d4018000 { + compatible = "spacemit,k1-tsensor"; + reg = <0x0 0xd4018000 0x0 0x100>; + clocks = <&syscon_apbc CLK_TSEN>, + <&syscon_apbc CLK_TSEN_BUS>; + clock-names = "core", "bus"; + interrupts = <61>; + resets = <&syscon_apbc RESET_TSEN>; + #thermal-sensor-cells = <1>; + }; + i2c6: i2c@d4018800 { compatible = "spacemit,k1-i2c"; reg = <0x0 0xd4018800 0x0 0x38>; @@ -983,6 +1094,21 @@ status = "disabled"; }; + spi3: spi@d401c000 { + compatible = "spacemit,k1-spi"; + reg = <0x0 0xd401c000 0x0 0x30>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&syscon_apbc CLK_SSP3>, + <&syscon_apbc CLK_SSP3_BUS>; + clock-names = "core", "bus"; + resets = <&syscon_apbc RESET_SSP3>; + interrupts = <55>; + dmas = <&pdma K1_PDMA_SPI3_RX>, <&pdma K1_PDMA_SPI3_TX>; + dma-names = "rx", "tx"; + status = "disabled"; + }; + /* sec_uart1: 0xf0612000, not available from Linux */ }; @@ -1199,6 +1325,19 @@ status = "disabled"; }; + sdhci0: mmc@d4280000 { + compatible = "spacemit,k1-sdhci"; + reg = <0x0 0xd4280000 0x0 0x200>; + clocks = <&syscon_apmu CLK_SDH_AXI>, + <&syscon_apmu CLK_SDH0>; + clock-names = "core", "io"; + resets = <&syscon_apmu RESET_SDH_AXI>, + <&syscon_apmu RESET_SDH0>; + reset-names = "axi", "sdh"; + interrupts = <99>; + status = "disabled"; + }; + emmc: mmc@d4281000 { compatible = "spacemit,k1-sdhci"; reg = <0x0 0xd4281000 0x0 0x200>; diff --git a/arch/riscv/boot/dts/spacemit/k3-com260-ifx.dts b/arch/riscv/boot/dts/spacemit/k3-com260-ifx.dts new file mode 100644 index 00000000000000..91bc8c0a2ed9d0 --- /dev/null +++ b/arch/riscv/boot/dts/spacemit/k3-com260-ifx.dts @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Copyright (c) 2026 SpacemiT (Hangzhou) Technology Co. Ltd + * Copyright (c) 2026 Yixun Lan + */ + +#include "k3-com260.dtsi" + +/ { + model = "SpacemiT K3 CoM260 IFX"; + compatible = "spacemit,k3-com260-ifx", "spacemit,k3-com260", "spacemit,k3"; + + aliases { + serial0 = &uart0; + ethernet0 = ð1; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; +}; + +&sdhci0 { + pinctrl-names = "default","uhs"; + pinctrl-0 = <&mmc1_cfg>; + pinctrl-1 = <&mmc1_uhs_cfg>; + bus-width = <4>; + cd-gpios = <&gpio K3_GPIO(4) GPIO_ACTIVE_HIGH>; + no-mmc; + no-sdio; + disable-wp; + cap-sd-highspeed; + vmmc-supply = <&aldo4>; + vqmmc-supply = <&aldo1>; + sd-uhs-sdr25; + sd-uhs-sdr50; + sd-uhs-sdr104; + status = "okay"; +}; + +&ufshc { + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/spacemit/k3-com260.dtsi b/arch/riscv/boot/dts/spacemit/k3-com260.dtsi new file mode 100644 index 00000000000000..b704b537385c7c --- /dev/null +++ b/arch/riscv/boot/dts/spacemit/k3-com260.dtsi @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Copyright (c) 2026 SpacemiT (Hangzhou) Technology Co. Ltd + * Copyright (c) 2026 Yixun Lan + */ +#include + +#include "k3.dtsi" +#include "k3-pinctrl.dtsi" + +/ { + model = "SpacemiT K3 CoM260 Module"; + compatible = "spacemit,k3-com260", "spacemit,k3"; + + memory@100000000 { + device_type = "memory"; + reg = <0x1 0x00000000 0x4 0x00000000>; + }; + + reg_5v_sys: regulator-5v-sys { + compatible = "regulator-fixed"; + regulator-name = "P5V0_SYS"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + }; +}; + +&i2c8 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c8_cfg>; + status = "okay"; + + pmic@41 { + compatible = "spacemit,p1"; + reg = <0x41>; + interrupts = <64 IRQ_TYPE_LEVEL_HIGH>; + vin1-supply = <®_5v_sys>; + vin2-supply = <®_5v_sys>; + vin3-supply = <®_5v_sys>; + vin4-supply = <®_5v_sys>; + vin5-supply = <®_5v_sys>; + vin6-supply = <®_5v_sys>; + aldoin-supply = <®_5v_sys>; + dldoin1-supply = <&buck4>; + dldoin2-supply = <&buck4>; + + regulators { + buck1: buck1 { + regulator-min-microvolt = <1050000>; + regulator-max-microvolt = <1050000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck2: buck2 { + regulator-min-microvolt = <1050000>; + regulator-max-microvolt = <1050000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck3: buck3 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <800000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck4: buck4 { + regulator-min-microvolt = <2100000>; + regulator-max-microvolt = <2100000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck5: buck5 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + buck6: buck6 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <500000>; + regulator-ramp-delay = <5000>; + regulator-always-on; + }; + + aldo1: aldo1 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + regulator-boot-on; + }; + + aldo2: aldo2 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-boot-on; + }; + + aldo3: aldo3 { + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3400000>; + }; + + aldo4: aldo4 { + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + regulator-boot-on; + }; + + dldo1: dldo1 { + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; + regulator-boot-on; + }; + + dldo2: dldo2 { + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <900000>; + regulator-always-on; + regulator-boot-on; + }; + + dldo3: dldo3 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <800000>; + regulator-always-on; + regulator-boot-on; + }; + + dldo4: dldo4 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-boot-on; + }; + + dldo5: dldo5 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-boot-on; + }; + + dldo6: dldo6 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-boot-on; + }; + + dldo7: dldo7 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-boot-on; + }; + }; + }; +}; + +ð1 { + pinctrl-names = "default"; + pinctrl-0 = <&gmac1_rgmii_0_cfg>, <&gmac1_phy_0_cfg>; + phy-mode = "rgmii-id"; + phy-handle = <&phy1>; + status = "okay"; + + mdio { + phy1: phy@1 { + compatible = "ethernet-phy-ieee802.3-c22"; + reg = <1>; + reset-gpios = <&gpio 1 5 GPIO_ACTIVE_LOW>; + reset-assert-us = <10000>; + reset-deassert-us = <50000>; + }; + }; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_0_cfg>; + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/spacemit/k3-deepcomputing-fml13v05.dts b/arch/riscv/boot/dts/spacemit/k3-deepcomputing-fml13v05.dts new file mode 100644 index 00000000000000..b5d5112de35813 --- /dev/null +++ b/arch/riscv/boot/dts/spacemit/k3-deepcomputing-fml13v05.dts @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Copyright (C) 2026 DeepComputing (HK) Limited + */ + +#include "k3.dtsi" +#include "k3-pinctrl.dtsi" + +/ { + model = "DeepComputing FML13V05"; + compatible = "deepcomputing,fml13v05", "spacemit,k3"; + + aliases { + serial0 = &uart0; + }; + + chosen { + stdout-path = "serial0"; + }; + + memory@100000000 { + device_type = "memory"; + reg = <0x1 0x00000000 0x4 0x00000000>; + }; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_0_cfg>; + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts b/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts index 4486dc1fe114da..9dd60d1acb340e 100644 --- a/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts +++ b/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts @@ -14,11 +14,13 @@ aliases { ethernet0 = ð0; + i2c2 = &i2c2; + i2c8 = &i2c8; serial0 = &uart0; }; chosen { - stdout-path = "serial0"; + stdout-path = "serial0:115200n8"; }; memory@100000000 { @@ -26,6 +28,14 @@ reg = <0x1 0x00000000 0x4 0x00000000>; }; + reg_aux_vcc3v3: regulator-aux-vcc3v3 { + compatible = "regulator-fixed"; + regulator-name = "AUX_VCC3V3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + reg_aux_vcc5v: regulator-aux-vcc5v { compatible = "regulator-fixed"; regulator-name = "AUX_VCC5V"; @@ -33,6 +43,56 @@ regulator-max-microvolt = <5000000>; regulator-always-on; }; + + rfkill-pcie-wlan { + compatible = "rfkill-gpio"; + label = "rfkill-pcie-wlan"; + radio-type = "wlan"; + shutdown-gpios = <&gpio 1 3 GPIO_ACTIVE_HIGH>; + }; + + rfkill-usb-bt { + compatible = "rfkill-gpio"; + label = "rfkill-usb-bt"; + radio-type = "bluetooth"; + shutdown-gpios = <&gpio 0 30 GPIO_ACTIVE_HIGH>; + }; +}; + +&i2c2 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c2_1_cfg>; + status = "okay"; + + eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + pagesize = <8>; + read-only; + size = <256>; + status = "okay"; + + nvmem-layout { + compatible = "onie,tlv-layout"; + + mac-address { + #nvmem-cell-cells = <1>; + }; + + num-macs { + }; + + part-number { + }; + + product-name { + }; + + serial-number { + }; + + }; + }; }; &i2c8 { @@ -174,6 +234,11 @@ }; }; +&combophy { + spacemit,apmu = <&syscon_apmu 0x11>; + status = "okay"; +}; + ð0 { pinctrl-names = "default"; pinctrl-0 = <&gmac0_rgmii_0_cfg>, <&gmac0_phy_0_cfg>; @@ -183,17 +248,160 @@ mdio { phy0: phy@1 { - compatible = "ethernet-phy-ieee802.3-c22"; + compatible = "ethernet-phy-id001c.c916", + "ethernet-phy-ieee802.3-c22"; reg = <1>; reset-gpios = <&gpio 0 15 GPIO_ACTIVE_LOW>; reset-assert-us = <10000>; - reset-deassert-us = <10000>; + reset-deassert-us = <50000>; + realtek,aldps-enable; + realtek,clkout-disable; + }; + }; +}; + +&qspi_cfg { + qspi-pins { + power-source = <1800>; + }; + + qspi-cs0-pins { + power-source = <1800>; + }; +}; + +&qspi { + pinctrl-names = "default"; + pinctrl-0 = <&qspi_cfg>; + status = "okay"; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <26500000>; + spi-rx-bus-width = <4>; + spi-tx-bus-width = <4>; + vcc-supply = <&aldo2>; /* PMIC_VCC1V8_QSPI */ + m25p,fast-read; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + bootinfo@0 { + reg = <0x0 0x20000>; + }; + + fsbl@20000 { + reg = <0x20000 0x80000>; + }; + + env@a0000 { + reg = <0xa0000 0x10000>; + }; + + esos@b0000 { + reg = <0xb0000 0x100000>; + }; + + opensbi@1b0000 { + reg = <0x1b0000 0x60000>; + }; + + uboot@210000 { + reg = <0x210000 0x5f0000>; + }; }; }; }; +&pcie0_rc { + pinctrl-names = "default"; + pinctrl-0 = <&pcie0_0_cfg>; + num-lanes = <4>; + status = "okay"; + + pcie@0 { + vpcie3v3-supply = <®_aux_vcc3v3>; + phys = <&combophy 0 PHY_TYPE_PCIE>, + <&combophy 1 PHY_TYPE_PCIE>; + }; +}; + +&pcie2_rc { + pinctrl-names = "default"; + pinctrl-0 = <&pcie2_0_cfg>; + num-lanes = <2>; + status = "okay"; + + pcie@0 { + vpcie3v3-supply = <®_aux_vcc3v3>; + phys = <&combophy 2 PHY_TYPE_PCIE>, + <&combophy 3 PHY_TYPE_PCIE>; + }; +}; + +&pcie4_rc { + pinctrl-names = "default"; + pinctrl-0 = <&pcie4_0_cfg>; + num-lanes = <1>; + status = "okay"; + + pcie@0 { + vpcie3v3-supply = <®_aux_vcc3v3>; + phys = <&combophy 5 PHY_TYPE_PCIE>; + }; +}; + &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_0_cfg>; status = "okay"; }; + +&usb2_host { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + hub@1 { + compatible = "usb1a40,0101"; + reg = <1>; + vdd-supply = <®_aux_vcc3v3>; + }; +}; + +&usb2_phy { + status = "okay"; +}; + +&usb3b { + maximum-speed = "high-speed"; + status = "okay"; +}; + +&usb3b_u2phy { + status = "okay"; +}; + +&usb3c { + maximum-speed = "high-speed"; + status = "okay"; +}; + +&usb3c_u2phy { + status = "okay"; +}; + +&usb3d_u2phy { + status = "okay"; +}; + +&usb3d { + status = "okay"; +}; + +&ufshc { + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/spacemit/k3-pinctrl.dtsi b/arch/riscv/boot/dts/spacemit/k3-pinctrl.dtsi index 23899d3f308a02..746eb2ee179910 100644 --- a/arch/riscv/boot/dts/spacemit/k3-pinctrl.dtsi +++ b/arch/riscv/boot/dts/spacemit/k3-pinctrl.dtsi @@ -45,6 +45,50 @@ }; }; + gmac1_rgmii_0_cfg: gmac1-rgmii-0-cfg { + gmac1-rgmii-0-pins { + pinmux = , /* gmac1_rxdv */ + , /* gmac1 rx d0 */ + , /* gmac1 rx d1 */ + , /* gmac1 rx_clk */ + , /* gmac1 rx d2 */ + , /* gmac1 rx d3 */ + , /* gmac1 tx d0 */ + , /* gmac1 tx d1 */ + , /* gmac1 tx clk */ + , /* gmac1 tx d2 */ + , /* gmac1 tx d3 */ + , /* gmac1 tx_en */ + , /* gmac1 mdc */ + ; /* gmac1 mdio */ + + bias-disable; + drive-strength = <25>; + power-source = <1800>; + }; + }; + + gmac1_phy_0_cfg: gmac1-phy-0-cfg { + gmac1-phy-0-pins { + pinmux = ; /* gmac1 int */ + + bias-disable; + drive-strength = <25>; + power-source = <1800>; + }; + }; + + /omit-if-no-ref/ + i2c2_1_cfg: i2c2-1-cfg { + i2c2-1-pins { + pinmux = , /* i2c2 scl */ + ; /* i2c2 sda */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + /omit-if-no-ref/ i2c8_cfg: i2c8-cfg { i2c8-pins { @@ -56,6 +100,617 @@ }; }; + /omit-if-no-ref/ + pwm0_0_cfg: pwm0-0-cfg { + pwm0-0-pins { + pinmux = ; /* pwm0 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm0_1_cfg: pwm0-1-cfg { + pwm0-1-pins { + pinmux = ; /* pwm0 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm1_0_cfg: pwm1-0-cfg { + pwm1-0-pins { + pinmux = ; /* pwm1 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm1_1_cfg: pwm1-1-cfg { + pwm1-1-pins { + pinmux = ; /* pwm1 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm1_2_cfg: pwm1-2-cfg { + pwm1-2-pins { + pinmux = ; /* pwm1 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm2_0_cfg: pwm2-0-cfg { + pwm2-0-pins { + pinmux = ; /* pwm2 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm2_1_cfg: pwm2-1-cfg { + pwm2-1-pins { + pinmux = ; /* pwm2 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm2_2_cfg: pwm2-2-cfg { + pwm2-2-pins { + pinmux = ; /* pwm2 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm2_3_cfg: pwm2-3-cfg { + pwm2-3-pins { + pinmux = ; /* pwm2 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm3_0_cfg: pwm3-0-cfg { + pwm3-0-pins { + pinmux = ; /* pwm3 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm3_1_cfg: pwm3-1-cfg { + pwm3-1-pins { + pinmux = ; /* pwm3 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm3_2_cfg: pwm3-2-cfg { + pwm3-2-pins { + pinmux = ; /* pwm3 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm3_3_cfg: pwm3-3-cfg { + pwm3-3-pins { + pinmux = ; /* pwm3 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm4_0_cfg: pwm4-0-cfg { + pwm4-0-pins { + pinmux = ; /* pwm4 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm4_1_cfg: pwm4-1-cfg { + pwm4-1-pins { + pinmux = ; /* pwm4 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm4_2_cfg: pwm4-2-cfg { + pwm4-2-pins { + pinmux = ; /* pwm4 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm5_0_cfg: pwm5-0-cfg { + pwm5-0-pins { + pinmux = ; /* pwm5 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm5_1_cfg: pwm5-1-cfg { + pwm5-1-pins { + pinmux = ; /* pwm5 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm5_2_cfg: pwm5-2-cfg { + pwm5-2-pins { + pinmux = ; /* pwm5 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm6_0_cfg: pwm6-0-cfg { + pwm6-0-pins { + pinmux = ; /* pwm6 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm6_1_cfg: pwm6-1-cfg { + pwm6-1-pins { + pinmux = ; /* pwm6 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm6_2_cfg: pwm6-2-cfg { + pwm6-2-pins { + pinmux = ; /* pwm6 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm7_0_cfg: pwm7-0-cfg { + pwm7-0-pins { + pinmux = ; /* pwm7 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm7_1_cfg: pwm7-1-cfg { + pwm7-1-pins { + pinmux = ; /* pwm7 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm7_2_cfg: pwm7-2-cfg { + pwm7-2-pins { + pinmux = ; /* pwm7 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm8_0_cfg: pwm8-0-cfg { + pwm8-0-pins { + pinmux = ; /* pwm8 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm8_1_cfg: pwm8-1-cfg { + pwm8-1-pins { + pinmux = ; /* pwm8 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm8_2_cfg: pwm8-2-cfg { + pwm8-2-pins { + pinmux = ; /* pwm8 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm9_0_cfg: pwm9-0-cfg { + pwm9-0-pins { + pinmux = ; /* pwm9 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm9_1_cfg: pwm9-1-cfg { + pwm9-1-pins { + pinmux = ; /* pwm9 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm9_2_cfg: pwm9-2-cfg { + pwm9-2-pins { + pinmux = ; /* pwm9 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm10_0_cfg: pwm10-0-cfg { + pwm10-0-pins { + pinmux = ; /* pwm10 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm10_1_cfg: pwm10-1-cfg { + pwm10-1-pins { + pinmux = ; /* pwm10 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm10_2_cfg: pwm10-2-cfg { + pwm10-2-pins { + pinmux = ; /* pwm10 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm11_0_cfg: pwm11-0-cfg { + pwm11-0-pins { + pinmux = ; /* pwm11 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm11_1_cfg: pwm11-1-cfg { + pwm11-1-pins { + pinmux = ; /* pwm11 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm12_0_cfg: pwm12-0-cfg { + pwm12-0-pins { + pinmux = ; /* pwm12 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm12_1_cfg: pwm12-1-cfg { + pwm12-1-pins { + pinmux = ; /* pwm12 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm13_0_cfg: pwm13-0-cfg { + pwm13-0-pins { + pinmux = ; /* pwm13 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm13_1_cfg: pwm13-1-cfg { + pwm13-1-pins { + pinmux = ; /* pwm13 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm13_2_cfg: pwm13-2-cfg { + pwm13-2-pins { + pinmux = ; /* pwm13 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm14_0_cfg: pwm14-0-cfg { + pwm14-0-pins { + pinmux = ; /* pwm14 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm14_1_cfg: pwm14-1-cfg { + pwm14-1-pins { + pinmux = ; /* pwm14 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm14_2_cfg: pwm14-2-cfg { + pwm14-2-pins { + pinmux = ; /* pwm14 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm15_0_cfg: pwm15-0-cfg { + pwm15-0-pins { + pinmux = ; /* pwm15 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm15_1_cfg: pwm15-1-cfg { + pwm15-1-pins { + pinmux = ; /* pwm15 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm15_2_cfg: pwm15-2-cfg { + pwm15-2-pins { + pinmux = ; /* pwm15 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm16_0_cfg: pwm16-0-cfg { + pwm16-0-pins { + pinmux = ; /* pwm16 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm16_1_cfg: pwm16-1-cfg { + pwm16-1-pins { + pinmux = ; /* pwm16 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm16_2_cfg: pwm16-2-cfg { + pwm16-2-pins { + pinmux = ; /* pwm16 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm17_0_cfg: pwm17-0-cfg { + pwm17-0-pins { + pinmux = ; /* pwm17 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm17_1_cfg: pwm17-1-cfg { + pwm17-1-pins { + pinmux = ; /* pwm17 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm17_2_cfg: pwm17-2-cfg { + pwm17-2-pins { + pinmux = ; /* pwm17 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm18_0_cfg: pwm18-0-cfg { + pwm18-0-pins { + pinmux = ; /* pwm18 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm18_1_cfg: pwm18-1-cfg { + pwm18-1-pins { + pinmux = ; /* pwm18 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm18_2_cfg: pwm18-2-cfg { + pwm18-2-pins { + pinmux = ; /* pwm18 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm19_0_cfg: pwm19-0-cfg { + pwm19-0-pins { + pinmux = ; /* pwm19 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm19_1_cfg: pwm19-1-cfg { + pwm19-1-pins { + pinmux = ; /* pwm19 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + pwm19_2_cfg: pwm19-2-cfg { + pwm19-2-pins { + pinmux = ; /* pwm19 */ + + bias-pull-up = <0>; + drive-strength = <25>; + }; + }; + + /omit-if-no-ref/ + qspi_cfg: qspi-cfg { + qspi-pins { + pinmux = , /* qspi dat0 */ + , /* qspi dat1 */ + , /* qspi dat2 */ + , /* qspi dat3 */ + ; /* qspi clk */ + + bias-disable; + drive-strength = <25>; + }; + + qspi-cs0-pins { + pinmux = ; /* qspi cs0 */ + + bias-disable; + drive-strength = <25>; + }; + }; + /omit-if-no-ref/ uart0_0_cfg: uart0-0-cfg { uart0-0-pins { @@ -66,4 +721,81 @@ drive-strength = <25>; }; }; + + pcie0_0_cfg: pcie0-0-cfg { + pcie0-0-pins { + pinmux = , /* pcie0 perst */ + ; /* pcie0 clkreq */ + + bias-pull-up = <1>; + drive-strength = <33>; + power-source = <1800>; + }; + }; + + pcie2_0_cfg: pcie2-0-cfg { + pcie2-0-pins { + pinmux = , /* pcie2 perst */ + ; /* pcie2 clkreq */ + + drive-strength = <38>; + power-source = <3300>; + }; + }; + + pcie4_0_cfg: pcie4-0-cfg { + pcie4-0-pins { + pinmux = , /* pcie4 perst */ + , /* pcie4 wake */ + ; /* pcie4 clkreq */ + + bias-pull-up = <1>; + drive-strength = <33>; + power-source = <1800>; + }; + }; + + mmc1_cfg: mmc1-cfg { + mmc1-data-cmd-pins { + pinmux = , /* mmc1 dat3 */ + , /* mmc1 dat2 */ + , /* mmc1 dat1 */ + , /* mmc1 dat0 */ + ; /* mmc1 cmd */ + + bias-pull-up = <0>; + drive-strength = <25>; + power-source = <3300>; + }; + + mmc1-clk-pins { + pinmux = ; /* mmc1 clk */ + + bias-pull-down; + drive-strength = <25>; + power-source = <3300>; + }; + }; + + mmc1_uhs_cfg: mmc1-uhs-cfg { + mmc1-uhs-data-cmd-pins { + pinmux = , /* mmc1 dat3 */ + , /* mmc1 dat2 */ + , /* mmc1 dat1 */ + , /* mmc1 dat0 */ + ; /* mmc1 cmd */ + + bias-pull-up = <0>; + drive-strength = <21>; + power-source = <1800>; + }; + + mmc1-uhs-clk-pins { + pinmux = ; /* mmc1 clk */ + + bias-pull-down; + drive-strength = <21>; + power-source = <1800>; + }; + }; }; diff --git a/arch/riscv/boot/dts/spacemit/k3.dtsi b/arch/riscv/boot/dts/spacemit/k3.dtsi index 815debd16409be..ab3d71282da03c 100644 --- a/arch/riscv/boot/dts/spacemit/k3.dtsi +++ b/arch/riscv/boot/dts/spacemit/k3.dtsi @@ -7,6 +7,7 @@ #include #include #include +#include /dts-v1/; @@ -36,7 +37,7 @@ "zawrs", "zba", "zbb", "zbc", "zbs", "zca", "zcb", "zcd", "zcmop", "zfa", "zfbfmin", "zfh", "zfhmin", "zicbom", "zicbop", "zicboz", - "ziccamoa", "ziccif", "zicclsm", "zicntr", + "ziccamoa", "ziccif", "zicclsm", "ziccrse", "zicntr", "zicond", "zicsr", "zifencei", "zihintntl", "zihintpause", "zihpm", "zimop", "zkt", "zvbb", "zvbc", "zvfbfmin", "zvfbfwma", "zvfh", @@ -77,7 +78,7 @@ "zawrs", "zba", "zbb", "zbc", "zbs", "zca", "zcb", "zcd", "zcmop", "zfa", "zfbfmin", "zfh", "zfhmin", "zicbom", "zicbop", "zicboz", - "ziccamoa", "ziccif", "zicclsm", "zicntr", + "ziccamoa", "ziccif", "zicclsm", "ziccrse", "zicntr", "zicond", "zicsr", "zifencei", "zihintntl", "zihintpause", "zihpm", "zimop", "zkt", "zvbb", "zvbc", "zvfbfmin", "zvfbfwma", "zvfh", @@ -118,7 +119,7 @@ "zawrs", "zba", "zbb", "zbc", "zbs", "zca", "zcb", "zcd", "zcmop", "zfa", "zfbfmin", "zfh", "zfhmin", "zicbom", "zicbop", "zicboz", - "ziccamoa", "ziccif", "zicclsm", "zicntr", + "ziccamoa", "ziccif", "zicclsm", "ziccrse", "zicntr", "zicond", "zicsr", "zifencei", "zihintntl", "zihintpause", "zihpm", "zimop", "zkt", "zvbb", "zvbc", "zvfbfmin", "zvfbfwma", "zvfh", @@ -159,7 +160,7 @@ "zawrs", "zba", "zbb", "zbc", "zbs", "zca", "zcb", "zcd", "zcmop", "zfa", "zfbfmin", "zfh", "zfhmin", "zicbom", "zicbop", "zicboz", - "ziccamoa", "ziccif", "zicclsm", "zicntr", + "ziccamoa", "ziccif", "zicclsm", "ziccrse", "zicntr", "zicond", "zicsr", "zifencei", "zihintntl", "zihintpause", "zihpm", "zimop", "zkt", "zvbb", "zvbc", "zvfbfmin", "zvfbfwma", "zvfh", @@ -200,7 +201,7 @@ "zawrs", "zba", "zbb", "zbc", "zbs", "zca", "zcb", "zcd", "zcmop", "zfa", "zfbfmin", "zfh", "zfhmin", "zicbom", "zicbop", "zicboz", - "ziccamoa", "ziccif", "zicclsm", "zicntr", + "ziccamoa", "ziccif", "zicclsm", "ziccrse", "zicntr", "zicond", "zicsr", "zifencei", "zihintntl", "zihintpause", "zihpm", "zimop", "zkt", "zvbb", "zvbc", "zvfbfmin", "zvfbfwma", "zvfh", @@ -241,7 +242,7 @@ "zawrs", "zba", "zbb", "zbc", "zbs", "zca", "zcb", "zcd", "zcmop", "zfa", "zfbfmin", "zfh", "zfhmin", "zicbom", "zicbop", "zicboz", - "ziccamoa", "ziccif", "zicclsm", "zicntr", + "ziccamoa", "ziccif", "zicclsm", "ziccrse", "zicntr", "zicond", "zicsr", "zifencei", "zihintntl", "zihintpause", "zihpm", "zimop", "zkt", "zvbb", "zvbc", "zvfbfmin", "zvfbfwma", "zvfh", @@ -282,7 +283,7 @@ "zawrs", "zba", "zbb", "zbc", "zbs", "zca", "zcb", "zcd", "zcmop", "zfa", "zfbfmin", "zfh", "zfhmin", "zicbom", "zicbop", "zicboz", - "ziccamoa", "ziccif", "zicclsm", "zicntr", + "ziccamoa", "ziccif", "zicclsm", "ziccrse", "zicntr", "zicond", "zicsr", "zifencei", "zihintntl", "zihintpause", "zihpm", "zimop", "zkt", "zvbb", "zvbc", "zvfbfmin", "zvfbfwma", "zvfh", @@ -323,7 +324,7 @@ "zawrs", "zba", "zbb", "zbc", "zbs", "zca", "zcb", "zcd", "zcmop", "zfa", "zfbfmin", "zfh", "zfhmin", "zicbom", "zicbop", "zicboz", - "ziccamoa", "ziccif", "zicclsm", "zicntr", + "ziccamoa", "ziccif", "zicclsm", "ziccrse", "zicntr", "zicond", "zicsr", "zifencei", "zihintntl", "zihintpause", "zihpm", "zimop", "zkt", "zvbb", "zvbc", "zvfbfmin", "zvfbfwma", "zvfh", @@ -424,7 +425,7 @@ osc_32k: clock-32k { compatible = "fixed-clock"; - clock-frequency = <32000>; + clock-frequency = <32768>; clock-output-names = "osc_32k"; #clock-cells = <0>; }; @@ -438,6 +439,337 @@ dma-noncoherent; ranges; + pcie0_rc: pcie@80000000 { + compatible = "spacemit,k3-pcie"; + reg = <0x0 0x80000000 0x0 0x00001000>, + <0x0 0x80300000 0x0 0x00003f20>, + <0x11 0x00000000 0x0 0x00010000>, + <0x0 0x82900000 0x0 0x00001000>, + <0x0 0x80100000 0x0 0x00001000>; + reg-names = "dbi", "atu", "config", "link", "dbi2"; + device_type = "pci"; + #address-cells = <3>; + #size-cells = <2>; + clocks = <&syscon_apmu CLK_APMU_PCIE_PORTA_DBI>, + <&syscon_apmu CLK_APMU_PCIE_PORTA_MSTE>, + <&syscon_apmu CLK_APMU_PCIE_PORTA_SLV>; + clock-names = "dbi", "mstr", "slv"; + msi-parent = <&simsic>; + ranges = <0x01000000 0x00 0x00010000 0x11 0x00010000 0x0 0x00100000>, + <0x02000000 0x0 0x00110000 0x11 0x00110000 0x0 0x7fef0000>, + <0x43000000 0x18 0x00000000 0x18 0x00000000 0x1 0x00000000>; + resets = <&syscon_apmu RESET_APMU_PCIE_A_DBI>, + <&syscon_apmu RESET_APMU_PCIE_A_MASTER>, + <&syscon_apmu RESET_APMU_PCIE_A_SLAVE>; + reset-names = "dbi", "mstr", "slv"; + max-link-speed = <3>; + linux,pci-domain = <0>; + spacemit,apmu = <&syscon_apmu 0x1f0>; + status = "disabled"; + + pcie0_port: pcie@0 { + device_type = "pci"; + compatible = "pciclass,0604"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + }; + }; + + pcie1_rc: pcie@80400000 { + compatible = "spacemit,k3-pcie"; + reg = <0x0 0x80400000 0x0 0x00001000>, + <0x0 0x80700000 0x0 0x00003f20>, + <0x11 0x80000000 0x0 0x00010000>, + <0x0 0x82c00000 0x0 0x00001000>, + <0x0 0x80500000 0x0 0x00001000>; + reg-names = "dbi", "atu", "config", "link", "dbi2"; + device_type = "pci"; + #address-cells = <3>; + #size-cells = <2>; + clocks = <&syscon_apmu CLK_APMU_PCIE_PORTB_DBI>, + <&syscon_apmu CLK_APMU_PCIE_PORTB_MSTE>, + <&syscon_apmu CLK_APMU_PCIE_PORTB_SLV>; + clock-names = "dbi", "mstr", "slv"; + msi-parent = <&simsic>; + ranges = <0x01000000 0x0 0x00010000 0x11 0x80010000 0x0 0x00100000>, + <0x02000000 0x0 0x80110000 0x11 0x80110000 0x0 0x7fef0000>, + <0x43000000 0x16 0x00000000 0x16 0x00000000 0x1 0x00000000>; + resets = <&syscon_apmu RESET_APMU_PCIE_B_DBI>, + <&syscon_apmu RESET_APMU_PCIE_B_MASTER>, + <&syscon_apmu RESET_APMU_PCIE_B_SLAVE>; + reset-names = "dbi", "mstr", "slv"; + max-link-speed = <3>; + linux,pci-domain = <1>; + spacemit,apmu = <&syscon_apmu 0x1d0>; + status = "disabled"; + + pcie1_port: pcie@0 { + device_type = "pci"; + compatible = "pciclass,0604"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + }; + }; + + pcie2_rc: pcie@80800000 { + compatible = "spacemit,k3-pcie"; + reg = <0x0 0x80800000 0x0 0x00001000>, + <0x0 0x80b00000 0x0 0x00003f20>, + <0x12 0x00000000 0x0 0x00010000>, + <0x0 0x82d00000 0x0 0x00001000>, + <0x0 0x80900000 0x0 0x00001000>; + reg-names = "dbi", "atu", "config", "link", "dbi2"; + device_type = "pci"; + #address-cells = <3>; + #size-cells = <2>; + clocks = <&syscon_apmu CLK_APMU_PCIE_PORTC_DBI>, + <&syscon_apmu CLK_APMU_PCIE_PORTC_MSTE>, + <&syscon_apmu CLK_APMU_PCIE_PORTC_SLV>; + clock-names = "dbi", "mstr", "slv"; + msi-parent = <&simsic>; + ranges = <0x01000000 0x00 0x00000000 0x12 0x00010000 0x0 0x00100000>, + <0x02000000 0x0 0x00110000 0x12 0x00110000 0x0 0x7fef0000>, + <0x43000000 0x15 0x00000000 0x15 0x00000000 0x1 0x00000000>; + resets = <&syscon_apmu RESET_APMU_PCIE_C_DBI>, + <&syscon_apmu RESET_APMU_PCIE_C_MASTER>, + <&syscon_apmu RESET_APMU_PCIE_C_SLAVE>; + reset-names = "dbi", "mstr", "slv"; + linux,pci-domain = <2>; + max-link-speed = <3>; + spacemit,apmu = <&syscon_apmu 0x1c8>; + status = "disabled"; + + pcie2_port: pcie@0 { + device_type = "pci"; + compatible = "pciclass,0604"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + }; + }; + + pcie3_rc: pcie@80c00000 { + compatible = "spacemit,k3-pcie"; + reg = <0x0 0x80c00000 0x0 0x00001000>, + <0x0 0x80f00000 0x0 0x00003f20>, + <0x12 0x80000000 0x0 0x00010000>, + <0x0 0x82a00000 0x0 0x00001000>, + <0x0 0x80d00000 0x0 0x00001000>; + reg-names = "dbi", "atu", "config", "link", "dbi2"; + device_type = "pci"; + #address-cells = <3>; + #size-cells = <2>; + clocks = <&syscon_apmu CLK_APMU_PCIE_PORTD_DBI>, + <&syscon_apmu CLK_APMU_PCIE_PORTD_MSTE>, + <&syscon_apmu CLK_APMU_PCIE_PORTD_SLV>; + clock-names = "dbi", "mstr", "slv"; + msi-parent = <&simsic>; + ranges = <0x01000000 0x0 0x00010000 0x12 0x80010000 0x0 0x100000>, + <0x02000000 0x0 0x80110000 0x12 0x80110000 0x0 0x3fef0000>, + <0x43000000 0x14 0x00000000 0x14 0x00000000 0x1 0x00000000>; + resets = <&syscon_apmu RESET_APMU_PCIE_D_DBI>, + <&syscon_apmu RESET_APMU_PCIE_D_MASTER>, + <&syscon_apmu RESET_APMU_PCIE_D_SLAVE>; + reset-names = "dbi", "mstr", "slv"; + linux,pci-domain = <3>; + bus-range = <0x00 0xff>; + max-link-speed = <3>; + spacemit,apmu = <&syscon_apmu 0x1e0>; + status = "disabled"; + + pcie3_port: pcie@0 { + device_type = "pci"; + compatible = "pciclass,0604"; + reg = <0x0 0x0 0x0 0x0 0x0>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + }; + }; + + pcie4_rc: pcie@81000000 { + compatible = "spacemit,k3-pcie"; + reg = <0x0 0x81000000 0x0 0x00001000>, + <0x0 0x81300000 0x0 0x00003f20>, + <0x12 0xc0000000 0x0 0x00010000>, + <0x0 0x82b00000 0x0 0x00001000>, + <0x0 0x81100000 0x0 0x00001000>; + reg-names = "dbi", "atu", "config", "link", "dbi2"; + device_type = "pci"; + #address-cells = <3>; + #size-cells = <2>; + clocks = <&syscon_apmu CLK_APMU_PCIE_PORTE_DBI>, + <&syscon_apmu CLK_APMU_PCIE_PORTE_MSTE>, + <&syscon_apmu CLK_APMU_PCIE_PORTE_SLV>; + clock-names = "dbi", "mstr", "slv"; + msi-parent = <&simsic>; + ranges = <0x01000000 0x0 0x00000000 0x12 0xc0010000 0x0 0x100000>, + <0x02000000 0x0 0xc0110000 0x12 0xc0110000 0x0 0x3fef0000>, + <0x43000000 0x13 0x00000000 0x13 0x00000000 0x1 0x00000000>; + resets = <&syscon_apmu RESET_APMU_PCIE_E_DBI>, + <&syscon_apmu RESET_APMU_PCIE_E_MASTER>, + <&syscon_apmu RESET_APMU_PCIE_E_SLAVE>; + reset-names = "dbi", "mstr", "slv"; + linux,pci-domain = <4>; + max-link-speed = <3>; + spacemit,apmu = <&syscon_apmu 0x1e8>; + status = "disabled"; + + pcie4_port: pcie@0 { + device_type = "pci"; + compatible = "pciclass,0604"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + }; + }; + + usb3b: usb@81400000 { + compatible = "spacemit,k3-dwc3"; + reg = <0x0 0x81400000 0x0 0x10000>; + interrupts = <125 IRQ_TYPE_LEVEL_HIGH>; + interrupt-parent = <&saplic>; + clocks = <&syscon_apmu CLK_APMU_USB3_PORTB_BUS>; + clock-names = "usbdrd30"; + resets = <&syscon_apmu RESET_APMU_USB3_B_AHB>, + <&syscon_apmu RESET_APMU_USB3_B_VCC>, + <&syscon_apmu RESET_APMU_USB3_B_PHY>; + reset-names = "ahb", "vcc", "phy"; + phys = <&usb3b_u2phy>; + phy-names = "usb2-phy"; + phy_type = "utmi"; + snps,dis_enblslpm_quirk; + snps,dis_u2_susphy_quirk; + snps,dis_u3_susphy_quirk; + snps,dis-del-phy-power-chg-quirk; + snps,dis-tx-ipgap-linecheck-quirk; + snps,parkmode-disable-ss-quirk; + dr_mode = "host"; + status = "disabled"; + }; + + usb3b_u2phy: phy@81500000 { + compatible = "spacemit,k3-usb2-phy"; + reg = <0x0 0x81500000 0x0 0x200>; + clocks = <&syscon_apmu CLK_APMU_USB3_PORTB_BUS>; + #phy-cells = <0>; + status = "disabled"; + }; + + usb3c: usb@81700000 { + compatible = "spacemit,k3-dwc3"; + reg = <0x0 0x81700000 0x0 0x10000>; + interrupts = <148 IRQ_TYPE_LEVEL_HIGH>; + interrupt-parent = <&saplic>; + clocks = <&syscon_apmu CLK_APMU_USB3_PORTC_BUS>; + clock-names = "usbdrd30"; + resets = <&syscon_apmu RESET_APMU_USB3_C_AHB>, + <&syscon_apmu RESET_APMU_USB3_C_VCC>, + <&syscon_apmu RESET_APMU_USB3_C_PHY>; + reset-names = "ahb", "vcc", "phy"; + phys = <&usb3c_u2phy>; + phy-names = "usb2-phy"; + phy_type = "utmi"; + snps,dis_enblslpm_quirk; + snps,dis_u2_susphy_quirk; + snps,dis_u3_susphy_quirk; + snps,dis-del-phy-power-chg-quirk; + snps,dis-tx-ipgap-linecheck-quirk; + snps,parkmode-disable-ss-quirk; + dr_mode = "host"; + status = "disabled"; + }; + + usb3c_u2phy: phy@81800000 { + compatible = "spacemit,k3-usb2-phy"; + reg = <0x0 0x81800000 0x0 0x200>; + clocks = <&syscon_apmu CLK_APMU_USB3_PORTC_BUS>; + #phy-cells = <0>; + status = "disabled"; + }; + + usb3d: usb@81a00000 { + compatible = "spacemit,k3-dwc3"; + reg = <0x0 0x81a00000 0x0 0x10000>; + interrupts = <149 IRQ_TYPE_LEVEL_HIGH>; + interrupt-parent = <&saplic>; + clocks = <&syscon_apmu CLK_APMU_USB3_PORTD_BUS>; + clock-names = "usbdrd30"; + resets = <&syscon_apmu RESET_APMU_USB3_D_AHB>, + <&syscon_apmu RESET_APMU_USB3_D_VCC>, + <&syscon_apmu RESET_APMU_USB3_D_PHY>; + reset-names = "ahb", "vcc", "phy"; + phys = <&usb3d_u2phy>, + <&combophy 4 PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + phy_type = "utmi"; + snps,dis_enblslpm_quirk; + snps,dis_u2_susphy_quirk; + snps,dis_u3_susphy_quirk; + snps,dis-del-phy-power-chg-quirk; + snps,dis-tx-ipgap-linecheck-quirk; + snps,parkmode-disable-ss-quirk; + dr_mode = "host"; + status = "disabled"; + }; + + usb3d_u2phy: phy@81b00000 { + compatible = "spacemit,k3-usb2-phy"; + reg = <0x0 0x81b00000 0x0 0x200>; + clocks = <&syscon_apmu CLK_APMU_USB2_BUS>; + #phy-cells = <0>; + status = "disabled"; + }; + + combophy: phy@81d00000 { + compatible = "spacemit,k3-combo-phy"; + reg = <0x0 0x81d00000 0x0 0x600000>; + #phy-cells = <2>; + spacemit,apb-spare = <&pll>; + status = "disabled"; + }; + + usb2_host: usb@c0a00000 { + compatible = "spacemit,k3-dwc3"; + reg = <0x0 0xc0a00000 0x0 0x10000>; + clocks = <&syscon_apmu CLK_APMU_USB2_BUS>; + clock-names = "usbdrd30"; + resets = <&syscon_apmu RESET_APMU_USB2_AHB>, + <&syscon_apmu RESET_APMU_USB2_VCC>, + <&syscon_apmu RESET_APMU_USB2_PHY>; + reset-names = "ahb", "vcc", "phy"; + interrupts = <105 IRQ_TYPE_LEVEL_HIGH>; + interrupt-parent = <&saplic>; + phys = <&usb2_phy>; + phy-names = "usb2-phy"; + phy_type = "utmi"; + snps,dis_enblslpm_quirk; + snps,dis_u2_susphy_quirk; + snps,dis-del-phy-power-chg-quirk; + snps,dis-tx-ipgap-linecheck-quirk; + dr_mode = "host"; + maximum-speed = "high-speed"; + status = "disabled"; + }; + + usb2_phy: phy@c0a20000 { + compatible = "spacemit,k3-usb2-phy"; + reg = <0x0 0xc0a20000 0x0 0x200>; + clocks = <&syscon_apmu CLK_APMU_USB2_BUS>; + #phy-cells = <0>; + status = "disabled"; + }; + eth0: ethernet@cac80000 { compatible = "spacemit,k3-dwmac", "snps,dwmac-5.40a"; reg = <0x0 0xcac80000 0x0 0x2000>; @@ -555,6 +887,17 @@ }; }; + pdma: dma-controller@d4000000 { + compatible = "spacemit,k3-pdma"; + reg = <0x0 0xd4000000 0x0 0x4000>; + clocks = <&syscon_apmu CLK_APMU_DMA>; + resets = <&syscon_apmu RESET_APMU_DMA>; + interrupts = <72 IRQ_TYPE_LEVEL_HIGH>; + dma-channels = <16>; + #dma-cells = <1>; + status = "disabled"; + }; + i2c0: i2c@d4010800 { compatible = "spacemit,k3-i2c", "spacemit,k1-i2c"; reg = <0x0 0xd4010800 0x0 0x38>; @@ -797,12 +1140,233 @@ status = "disabled"; }; + pwm0: pwm@d401a000 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd401a000 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM0>, + <&syscon_apbc CLK_APBC_PWM0_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM0>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1: pwm@d401a400 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd401a400 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM1>, + <&syscon_apbc CLK_APBC_PWM1_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM1>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2: pwm@d401a800 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd401a800 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM2>, + <&syscon_apbc CLK_APBC_PWM2_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM2>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm3: pwm@d401ac00 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd401ac00 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM3>, + <&syscon_apbc CLK_APBC_PWM3_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM3>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm4: pwm@d401b000 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd401b000 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM4>, + <&syscon_apbc CLK_APBC_PWM4_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM4>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm5: pwm@d401b400 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd401b400 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM5>, + <&syscon_apbc CLK_APBC_PWM5_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM5>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm6: pwm@d401b800 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd401b800 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM6>, + <&syscon_apbc CLK_APBC_PWM6_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM6>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm7: pwm@d401bc00 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd401bc00 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM7>, + <&syscon_apbc CLK_APBC_PWM7_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM7>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm8: pwm@d4020000 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4020000 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM8>, + <&syscon_apbc CLK_APBC_PWM8_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM8>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm9: pwm@d4020400 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4020400 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM9>, + <&syscon_apbc CLK_APBC_PWM9_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM9>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm10: pwm@d4020800 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4020800 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM10>, + <&syscon_apbc CLK_APBC_PWM10_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM10>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm11: pwm@d4020c00 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4020c00 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM11>, + <&syscon_apbc CLK_APBC_PWM11_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM11>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm12: pwm@d4021000 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4021000 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM12>, + <&syscon_apbc CLK_APBC_PWM12_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM12>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm13: pwm@d4021400 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4021400 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM13>, + <&syscon_apbc CLK_APBC_PWM13_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM13>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm14: pwm@d4021800 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4021800 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM14>, + <&syscon_apbc CLK_APBC_PWM14_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM14>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm15: pwm@d4021c00 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4021c00 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM15>, + <&syscon_apbc CLK_APBC_PWM15_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM15>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm16: pwm@d4022000 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4022000 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM16>, + <&syscon_apbc CLK_APBC_PWM16_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM16>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm17: pwm@d4022400 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4022400 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM17>, + <&syscon_apbc CLK_APBC_PWM17_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM17>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm18: pwm@d4022800 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4022800 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM18>, + <&syscon_apbc CLK_APBC_PWM18_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM18>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm19: pwm@d4022c00 { + compatible = "spacemit,k3-pwm", "marvell,pxa910-pwm"; + reg = <0x0 0xd4022c00 0x0 0x10>; + clocks = <&syscon_apbc CLK_APBC_PWM19>, + <&syscon_apbc CLK_APBC_PWM19_BUS>; + clock-names = "func", "bus"; + resets = <&syscon_apbc RESET_APBC_PWM19>; + #pwm-cells = <3>; + status = "disabled"; + }; + pinctrl: pinctrl@d401e000 { compatible = "spacemit,k3-pinctrl"; reg = <0x0 0xd401e000 0x0 0x1000>; clocks = <&syscon_apbc CLK_APBC_AIB>, <&syscon_apbc CLK_APBC_AIB_BUS>; clock-names = "func", "bus"; + spacemit,apbc = <&syscon_apbc>; }; uart10: serial@d401f000 { @@ -836,6 +1400,36 @@ #clock-cells = <1>; }; + qspi: spi@d420c000 { + compatible = "spacemit,k3-qspi", + "spacemit,k1-qspi"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x0 0xd420c000 0x0 0x1000>, + <0x0 0xb8000000 0x0 0xc00000>; + reg-names = "QuadSPI", "QuadSPI-memory"; + clocks = <&syscon_apmu CLK_APMU_QSPI_BUS>, + <&syscon_apmu CLK_APMU_QSPI>; + clock-names = "qspi_en", "qspi"; + resets = <&syscon_apmu RESET_APMU_QSPI>, + <&syscon_apmu RESET_APMU_QSPI_BUS>; + interrupts = <117 IRQ_TYPE_LEVEL_HIGH>; + status = "disabled"; + }; + + sdhci0: mmc@d4280000 { + compatible = "spacemit,k3-sdhci"; + reg = <0x0 0xd4280000 0x0 0x200>; + clocks = <&syscon_apmu CLK_APMU_SDH_AXI>, + <&syscon_apmu CLK_APMU_SDH0>; + clock-names = "core", "io"; + resets = <&syscon_apmu RESET_APMU_SDH_AXI>, + <&syscon_apmu RESET_APMU_SDH0>; + reset-names = "axi", "sdh"; + interrupts = <99 IRQ_TYPE_LEVEL_HIGH>; + status = "disabled"; + }; + syscon_apmu: system-controller@d4282800 { compatible = "spacemit,k3-syscon-apmu"; reg = <0x0 0xd4282800 0x0 0x400>; @@ -923,5 +1517,18 @@ riscv,num-sources = <512>; status = "reserved"; }; + + ufshc: ufshc@c0e00000 { + compatible = "spacemit,k3-ufshc"; + reg = <0x0 0xc0e00000 0x0 0x40000>; + clocks = <&syscon_apmu CLK_APMU_UFS_ACLK>, + <&syscon_apmu CLK_APMU_UFS_REFCLK>; + clock-names = "aclk", "ref_clk"; + resets = <&syscon_apmu RESET_APMU_UFS_ACLK>; + interrupts = <135 IRQ_TYPE_LEVEL_HIGH>; + freq-table-hz = <491520000 491520000 19200000 19200000>; + lanes-per-direction = <2>; + status = "disabled"; + }; }; }; diff --git a/arch/riscv/boot/dts/thead/Makefile b/arch/riscv/boot/dts/thead/Makefile index b55a17127c2bc0..281849e71ccb84 100644 --- a/arch/riscv/boot/dts/thead/Makefile +++ b/arch/riscv/boot/dts/thead/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 dtb-$(CONFIG_ARCH_THEAD) += th1520-lichee-pi-4a.dtb th1520-beaglev-ahead.dtb +dtb-$(CONFIG_ARCH_THEAD) += th1520-lichee-pi-4a-16g.dtb diff --git a/arch/riscv/boot/dts/thead/th1520-beaglev-ahead.dts b/arch/riscv/boot/dts/thead/th1520-beaglev-ahead.dts index 91f3f9b987bc38..e16484a476534c 100644 --- a/arch/riscv/boot/dts/thead/th1520-beaglev-ahead.dts +++ b/arch/riscv/boot/dts/thead/th1520-beaglev-ahead.dts @@ -86,6 +86,11 @@ }; }; }; + + wifi_pwrseq: wifi-pwrseq { + compatible = "mmc-pwrseq-simple"; + reset-gpios = <&gpio2 31 GPIO_ACTIVE_LOW>; /* WL-REG-ON */ + }; }; &osc { @@ -239,6 +244,28 @@ slew-rate = <0>; }; }; + + wifi_pins: wifi-0 { + host-wake-pins { + pins = "GPIO2_25"; + function = "gpio"; + bias-disable; + drive-strength = <1>; + input-enable; + input-schmitt-disable; + slew-rate = <0>; + }; + + reg-on-pins { + pins = "GPIO2_31"; + function = "gpio"; + bias-disable; + drive-strength = <3>; + input-disable; + input-schmitt-disable; + slew-rate = <0>; + }; + }; }; &sdio0 { @@ -247,6 +274,27 @@ status = "okay"; }; +&sdio1 { + bus-width = <4>; + max-frequency = <198000000>; + pinctrl-names = "default"; + pinctrl-0 = <&wifi_pins>; + #address-cells = <1>; + #size-cells = <0>; + non-removable; + keep-power-in-suspend; + mmc-pwrseq = <&wifi_pwrseq>; + status = "okay"; + + wifi@1 { + compatible = "cypress,cyw43012-fmac", "brcm,bcm4329-fmac"; + reg = <1>; + interrupt-parent = <&gpio2>; + interrupts = <25 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "host-wake"; + }; +}; + &dpu { status = "okay"; }; diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-module-4a.dtsi b/arch/riscv/boot/dts/thead/th1520-lichee-module-4a.dtsi index 8e76b63e0100aa..bfda5a6b56b8f8 100644 --- a/arch/riscv/boot/dts/thead/th1520-lichee-module-4a.dtsi +++ b/arch/riscv/boot/dts/thead/th1520-lichee-module-4a.dtsi @@ -20,6 +20,16 @@ device_type = "memory"; reg = <0x0 0x00000000 0x2 0x00000000>; }; + + /* TODO: Switch to AON regulator when it's available. */ + avdd33_usb3: regulator-avdd33-usb3 { + compatible = "regulator-fixed"; + regulator-name = "AVDD33_USB3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + /* Marked as always on on the schematics */ + regulator-always-on; + }; }; &osc { @@ -202,3 +212,8 @@ max-frequency = <198000000>; status = "okay"; }; + +&usb_phy { + avdd33-usb3-supply = <&avdd33_usb3>; + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a-16g.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a-16g.dts new file mode 100644 index 00000000000000..a3a991baf716be --- /dev/null +++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a-16g.dts @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Copyright (C) 2023 Han Gao + */ + +/dts-v1/; + +#include "th1520-lichee-pi-4a.dts" + +/ { + model = "Sipeed Lichee Pi 4A 16G"; + compatible = "sipeed,lichee-pi-4a", "sipeed,lichee-module-4a", "thead,th1520"; + + memory@0 { + device_type = "memory"; + reg = <0x0 0x00000000 0x4 0x00000000>; + }; +}; diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts index 7cb7d28683bce7..da3425a44f63e9 100644 --- a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts +++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts @@ -4,6 +4,7 @@ */ #include "th1520-lichee-module-4a.dtsi" +#include / { model = "Sipeed Lichee Pi 4A"; @@ -16,6 +17,9 @@ gpio3 = &gpio3; gpio4 = &gpio4; gpio5 = &aogpio; + i2c0 = &i2c0; + i2c1 = &i2c1; + i2c3 = &i2c3; serial0 = &uart0; serial1 = &uart1; serial2 = &uart2; @@ -40,6 +44,12 @@ }; }; + wifi_pwrseq: wifi-pwrseq { + compatible = "mmc-pwrseq-simple"; + reset-gpios = <&ioexp2 4 GPIO_ACTIVE_LOW>; + post-power-on-delay-ms = <200>; + }; + thermal-zones { cpu-thermal { polling-delay = <1000>; @@ -94,6 +104,239 @@ cooling-levels = <0 66 196 255>; }; + hub_5v: regulator-hub-5v { + compatible = "regulator-fixed"; + regulator-name = "HUB_5V"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&ioexp3 3 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; + + vcc5v_usb: regulator-vcc5v-usb { + compatible = "regulator-fixed"; + regulator-name = "VCC5V_USB"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&gpio1 22 GPIO_ACTIVE_HIGH>; + enable-active-high; + /* + * Workaround for Linux currently being not able to power on + * Vbus for USB Type-A connectors. + */ + regulator-always-on; + }; + + connector-usb-a-1 { + compatible = "usb-a-connector"; + vbus-supply = <&vcc5v_usb>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + usb_a_1_hs_ep: endpoint { + remote-endpoint = <&hub_hs_port1_ep>; + }; + }; + + port@1 { + reg = <1>; + + usb_a_1_ss_ep: endpoint { + remote-endpoint = <&hub_ss_port1_ep>; + }; + }; + }; + }; + + connector-usb-a-2 { + compatible = "usb-a-connector"; + vbus-supply = <&vcc5v_usb>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + usb_a_2_hs_ep: endpoint { + remote-endpoint = <&hub_hs_port2_ep>; + }; + }; + + port@1 { + reg = <1>; + + usb_a_2_ss_ep: endpoint { + remote-endpoint = <&hub_ss_port2_ep>; + }; + }; + }; + }; + + connector-usb-a-3 { + compatible = "usb-a-connector"; + vbus-supply = <&vcc5v_usb>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + usb_a_3_hs_ep: endpoint { + remote-endpoint = <&hub_hs_port3_ep>; + }; + }; + + port@1 { + reg = <1>; + + usb_a_3_ss_ep: endpoint { + remote-endpoint = <&hub_ss_port3_ep>; + }; + }; + }; + }; + + connector-usb-a-4 { + compatible = "usb-a-connector"; + vbus-supply = <&vcc5v_usb>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + usb_a_4_hs_ep: endpoint { + remote-endpoint = <&hub_hs_port4_ep>; + }; + }; + + port@1 { + reg = <1>; + + usb_a_4_ss_ep: endpoint { + remote-endpoint = <&hub_ss_port4_ep>; + }; + }; + }; + }; +}; + +&aogpio { + /* Route USB2 to the onboard hub for normal operation */ + sel-usb-hub-hog { + gpio-hog; + gpios = <4 GPIO_ACTIVE_HIGH>; + output-high; + }; +}; + +&padctrl1_apsys { + i2c1_pins: i2c1-0 { + i2c-pins { + pins = "I2C1_SCL", "I2C1_SDA"; + function = "i2c"; + bias-pull-up = <2100>; + drive-strength = <7>; + input-enable; + input-schmitt-enable; + slew-rate = <0>; + }; + }; +}; + +&dpu { + status = "okay"; +}; + +&hdmi { + status = "okay"; +}; + +&hdmi_out_port { + hdmi_out_con: endpoint { + remote-endpoint = <&hdmi_con_in>; + }; +}; + +&i2c0 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c0_pins>; + clock-frequency = <100000>; + i2c-sda-hold-time-ns = <300>; + i2c-sda-falling-time-ns = <510>; + i2c-scl-falling-time-ns = <510>; + status = "okay"; + + ioexp1: gpio@18 { + compatible = "nxp,pca9557"; + reg = <0x18>; + gpio-controller; + #gpio-cells = <2>; + gpio-line-names = "cam0_dvdd12", + "cam0_avdd28", + "cam0_dovdd18"; + }; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <400000>; + i2c-sda-hold-time-ns = <300>; + i2c-sda-falling-time-ns = <510>; + i2c-scl-falling-time-ns = <510>; + status = "okay"; + + ioexp2: gpio@18 { + compatible = "nxp,pca9557"; + reg = <0x18>; + gpio-controller; + #gpio-cells = <2>; + gpio-line-names = "", + "cam0_reset", + "cam1_reset", + "cam2_reset", + "wl_host_wake", + "bt_resetn", + "", + "bt_host_wake"; + }; +}; + +&i2c3 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c3_pins>; + clock-frequency = <100000>; + i2c-sda-hold-time-ns = <300>; + i2c-sda-falling-time-ns = <510>; + i2c-scl-falling-time-ns = <510>; + status = "okay"; + + ioexp3: gpio@18 { + compatible = "nxp,pca9557"; + reg = <0x18>; + gpio-controller; + #gpio-cells = <2>; + gpio-line-names = "tp0_rst", + "", + "", + "vcc5v_usb", + "vdd28_tp0", + "vdd33_lcd0", + "vdd18_lcd0", + "lcd0_reset"; + }; }; &padctrl0_apsys { @@ -109,6 +352,18 @@ }; }; + i2c3_pins: i2c3-0 { + i2c-pins { + pins = "I2C3_SCL", "I2C3_SDA"; + function = "i2c"; + bias-disable; /* external pull-up */ + drive-strength = <7>; + input-enable; + input-schmitt-enable; + slew-rate = <0>; + }; + }; + uart0_pins: uart0-0 { tx-pins { pins = "UART0_TXD"; @@ -132,17 +387,41 @@ }; }; -&dpu { +&sdio1 { + #address-cells = <1>; + #size-cells = <0>; + bus-width = <4>; + max-frequency = <198000000>; + cap-sdio-irq; + keep-power-in-suspend; + mmc-pwrseq = <&wifi_pwrseq>; + non-removable; status = "okay"; }; -&hdmi { - status = "okay"; -}; +&padctrl1_apsys { + i2c0_pins: i2c0-0 { + i2c-pins { + pins = "I2C0_SCL", "I2C0_SDA"; + function = "i2c"; + bias-disable; /* external pull-up */ + drive-strength = <7>; + input-enable; + input-schmitt-enable; + slew-rate = <0>; + }; + }; -&hdmi_out_port { - hdmi_out_con: endpoint { - remote-endpoint = <&hdmi_con_in>; + i2c1_pins: i2c1-0 { + i2c-pins { + pins = "I2C1_SCL", "I2C1_SDA"; + function = "i2c"; + bias-disable; /* external pull-up */ + drive-strength = <7>; + input-enable; + input-schmitt-enable; + slew-rate = <0>; + }; }; }; @@ -151,3 +430,98 @@ pinctrl-0 = <&uart0_pins>; status = "okay"; }; + +&usb { + dr_mode = "host"; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + hub_hs: hub@1 { + compatible = "usb2109,2817"; + reg = <1>; + peer-hub = <&hub_ss>; + vdd-supply = <&hub_5v>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + + hub_hs_port1_ep: endpoint { + remote-endpoint = <&usb_a_1_hs_ep>; + }; + }; + + port@2 { + reg = <2>; + + hub_hs_port2_ep: endpoint { + remote-endpoint = <&usb_a_2_hs_ep>; + }; + }; + + port@3 { + reg = <3>; + + hub_hs_port3_ep: endpoint { + remote-endpoint = <&usb_a_3_hs_ep>; + }; + }; + + port@4 { + reg = <4>; + + hub_hs_port4_ep: endpoint { + remote-endpoint = <&usb_a_4_hs_ep>; + }; + }; + }; + }; + + hub_ss: hub@2 { + compatible = "usb2109,817"; + reg = <2>; + peer-hub = <&hub_hs>; + vdd-supply = <&hub_5v>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + + hub_ss_port1_ep: endpoint { + remote-endpoint = <&usb_a_1_ss_ep>; + }; + }; + + port@2 { + reg = <2>; + + hub_ss_port2_ep: endpoint { + remote-endpoint = <&usb_a_2_ss_ep>; + }; + }; + + port@3 { + reg = <3>; + + hub_ss_port3_ep: endpoint { + remote-endpoint = <&usb_a_3_ss_ep>; + }; + }; + + port@4 { + reg = <4>; + + hub_ss_port4_ep: endpoint { + remote-endpoint = <&usb_a_4_ss_ep>; + }; + }; + }; + }; +}; diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index 5e91dc1d2b9b7e..fdef5fb82b451c 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -38,6 +38,8 @@ d-cache-sets = <512>; next-level-cache = <&l2_cache>; mmu-type = "riscv,sv39"; + operating-points-v2 = <&cpu_opp>; + clocks = <&clk CLK_C910>; cpu0_intc: interrupt-controller { compatible = "riscv,cpu-intc"; @@ -65,6 +67,8 @@ d-cache-sets = <512>; next-level-cache = <&l2_cache>; mmu-type = "riscv,sv39"; + operating-points-v2 = <&cpu_opp>; + clocks = <&clk CLK_C910>; cpu1_intc: interrupt-controller { compatible = "riscv,cpu-intc"; @@ -92,6 +96,8 @@ d-cache-sets = <512>; next-level-cache = <&l2_cache>; mmu-type = "riscv,sv39"; + operating-points-v2 = <&cpu_opp>; + clocks = <&clk CLK_C910>; cpu2_intc: interrupt-controller { compatible = "riscv,cpu-intc"; @@ -119,6 +125,8 @@ d-cache-sets = <512>; next-level-cache = <&l2_cache>; mmu-type = "riscv,sv39"; + operating-points-v2 = <&cpu_opp>; + clocks = <&clk CLK_C910>; cpu3_intc: interrupt-controller { compatible = "riscv,cpu-intc"; @@ -137,6 +145,33 @@ }; }; + cpu_opp: opp-table-cpu { + compatible = "operating-points-v2"; + opp-shared; + + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + opp-microvolt = <600000>; + }; + + opp-800000000 { + opp-hz = /bits/ 64 <800000000>; + opp-microvolt = <700000>; + }; + + opp-1500000000 { + opp-hz = /bits/ 64 <1500000000>; + opp-microvolt = <800000>; + }; + +/* + opp-1848000000 { + opp-hz = /bits/ 64 <1848000000>; + opp-microvolt = <1000000>; + }; + */ + }; + pmu { compatible = "riscv,pmu"; riscv,event-to-mhpmcounters = @@ -251,12 +286,30 @@ }; aon: aon { - compatible = "thead,th1520-aon"; + compatible = "xuantie,th1520-aon", "thead,th1520-aon"; mboxes = <&mbox_910t 1>; mbox-names = "aon"; resets = <&rst TH1520_RESET_ID_GPU_CLKGEN>; reset-names = "gpu-clkgen"; #power-domain-cells = <1>; + opensbi-mboxes = <&mbox_910r>; + status = "okay"; + }; + + mbox_910r: mbox@ffefc53000 { + compatible = "xuantie,th1520-mbox-r"; + reg = <0xff 0xefc53000 0x0 0x4000>, + <0xff 0xefc3f000 0x0 0x1000>, + <0xff 0xefc47000 0x0 0x1000>, + <0xff 0xefc4f000 0x0 0x1000>; + reg-names = "local_base", + "remote_icu0", + "remote_icu1", + "remote_icu2"; + clocks = <&clk CLK_PERI_APB_PCLK>; + clock-names = "ipg"; + icu_cpu_id = <3>; + #mbox-cells = <2>; }; soc { @@ -316,6 +369,20 @@ status = "disabled"; }; + usb: usb@ffe7040000 { + compatible = "snps,dwc3"; + reg = <0xff 0xe7040000 0x0 0x10000>; + interrupts = <68 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk_misc CLK_USB>, + <&clk_misc CLK_USB_CTL_REF>, + <&clk_misc CLK_USB_SUSPEND>; + clock-names = "bus_early", "ref", "suspend"; + resets = <&rst_misc TH1520_RESET_ID_USB3_VCC>; + phys = <&usb_phy>; + phy-names = "usb3-phy"; + status = "disabled"; + }; + gmac1: ethernet@ffe7060000 { compatible = "thead,th1520-gmac", "snps,dwmac-3.70a"; reg = <0xff 0xe7060000 0x0 0x2000>, <0xff 0xec004000 0x0 0x1000>; @@ -366,8 +433,8 @@ compatible = "thead,th1520-dwcmshc"; reg = <0xff 0xe7080000 0x0 0x10000>; interrupts = <62 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&clk CLK_EMMC_SDIO>; - clock-names = "core"; + clocks = <&clk CLK_EMMC_SDIO>, <&clk_misc CLK_EMMC>; + clock-names = "core", "bus"; status = "disabled"; }; @@ -375,8 +442,8 @@ compatible = "thead,th1520-dwcmshc"; reg = <0xff 0xe7090000 0x0 0x10000>; interrupts = <64 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&clk CLK_EMMC_SDIO>; - clock-names = "core"; + clocks = <&clk CLK_EMMC_SDIO>, <&clk_misc CLK_SDIO0>; + clock-names = "core", "bus"; status = "disabled"; }; @@ -384,8 +451,8 @@ compatible = "thead,th1520-dwcmshc"; reg = <0xff 0xe70a0000 0x0 0x10000>; interrupts = <71 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&clk CLK_EMMC_SDIO>; - clock-names = "core"; + clocks = <&clk CLK_EMMC_SDIO>, <&clk_misc CLK_SDIO1>; + clock-names = "core", "bus"; status = "disabled"; }; @@ -411,6 +478,37 @@ status = "disabled"; }; + i2c0: i2c@ffe7f20000 { + compatible = "thead,th1520-i2c", "snps,designware-i2c"; + reg = <0xff 0xe7f20000 0x0 0x4000>; + interrupts = <44 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk CLK_I2C0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + i2c1: i2c@ffe7f24000 { + compatible = "thead,th1520-i2c", "snps,designware-i2c"; + reg = <0xff 0xe7f24000 0x0 0x4000>; + interrupts = <45 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk CLK_I2C1>, <&clk CLK_PERI_APB_PCLK>; + clock-names = "ref", "pclk"; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + i2c4: i2c@ffe7f28000 { + compatible = "thead,th1520-i2c", "snps,designware-i2c"; + reg = <0xff 0xe7f28000 0x0 0x4000>; + interrupts = <48 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk CLK_I2C4>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + gpio@ffe7f34000 { compatible = "snps,dw-apb-gpio"; reg = <0xff 0xe7f34000 0x0 0x1000>; @@ -509,6 +607,16 @@ thead,pad-group = <3>; }; + i2c2: i2c@ffec00c000 { + compatible = "thead,th1520-i2c", "snps,designware-i2c"; + reg = <0xff 0xec00c000 0x0 0x4000>; + interrupts = <46 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk CLK_I2C2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + uart2: serial@ffec010000 { compatible = "snps,dw-apb-uart"; reg = <0xff 0xec010000 0x0 0x4000>; @@ -520,6 +628,16 @@ status = "disabled"; }; + i2c3: i2c@ffec014000 { + compatible = "thead,th1520-i2c", "snps,designware-i2c"; + reg = <0xff 0xec014000 0x0 0x4000>; + interrupts = <47 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk CLK_I2C3>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + pwm: pwm@ffec01c000 { compatible = "thead,th1520-pwm"; reg = <0xff 0xec01c000 0x0 0x4000>; @@ -533,6 +651,26 @@ #reset-cells = <1>; }; + clk_misc: clock-controller@ffec02c100 { + compatible = "thead,th1520-clk-misc"; + reg = <0xff 0xec02c100 0x0 0x100>; + clocks = <&osc>; + #clock-cells = <1>; + }; + + usb_phy: phy@ffec030000 { + compatible = "thead,th1520-usb-phy"; + reg = <0xff 0xec030000 0x0 0x10000>; + clocks = <&clk_misc CLK_USB>, + <&clk_misc CLK_USB_PHY_REF>; + clock-names = "bus", "ref"; + resets = <&rst_misc TH1520_RESET_ID_USB3_APB>, + <&rst_misc TH1520_RESET_ID_USB3_PHY>; + reset-names = "bus", "phy"; + #phy-cells = <0>; + status = "disabled"; + }; + rst_vp: reset-controller@ffecc30000 { compatible = "thead,th1520-reset-vp"; reg = <0xff 0xecc30000 0x0 0x14>; @@ -725,6 +863,16 @@ status = "disabled"; }; + i2c5: i2c@fff7f2c000 { + compatible = "thead,th1520-i2c", "snps,designware-i2c"; + reg = <0xff 0xf7f2c000 0x0 0x4000>; + interrupts = <49 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk CLK_I2C5>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + timer4: timer@ffffc33000 { compatible = "snps,dw-apb-timer"; reg = <0xff 0xffc33000 0x0 0x14>; diff --git a/arch/riscv/configs/defconfig b/arch/riscv/configs/defconfig index c2c37327b9877f..d66b8f27d6e83d 100644 --- a/arch/riscv/configs/defconfig +++ b/arch/riscv/configs/defconfig @@ -98,6 +98,8 @@ CONFIG_NET_CLS_CGROUP=m CONFIG_NETLINK_DIAG=y CONFIG_CGROUP_NET_PRIO=y CONFIG_CAN=m +CONFIG_RFKILL=m +CONFIG_RFKILL_GPIO=m CONFIG_NET_9P=y CONFIG_NET_9P_VIRTIO=y CONFIG_PCI=y diff --git a/arch/riscv/errata/thead/errata.c b/arch/riscv/errata/thead/errata.c index 6f1c683f0ec037..64683882d50b75 100644 --- a/arch/riscv/errata/thead/errata.c +++ b/arch/riscv/errata/thead/errata.c @@ -168,6 +168,23 @@ static bool errata_probe_ghostwrite(unsigned int stage, return true; } +static bool errata_probe_write_once(unsigned int stage, + unsigned long arch_id, unsigned long impid) +{ + if (!IS_ENABLED(CONFIG_ERRATA_THEAD_WRITE_ONCE)) + return false; + + /* target-c9xx cores report arch_id and impid as 0 */ + if (arch_id != 0 || impid != 0) + return false; + + if (stage == RISCV_ALTERNATIVES_BOOT || + stage == RISCV_ALTERNATIVES_MODULE) + return true; + + return false; +} + static u32 thead_errata_probe(unsigned int stage, unsigned long archid, unsigned long impid) { @@ -183,6 +200,9 @@ static u32 thead_errata_probe(unsigned int stage, errata_probe_ghostwrite(stage, archid, impid); + if (errata_probe_write_once(stage, archid, impid)) + cpu_req_errata |= BIT(ERRATA_THEAD_WRITE_ONCE); + return cpu_req_errata; } diff --git a/arch/riscv/include/asm/acpi.h b/arch/riscv/include/asm/acpi.h index 26ab37c171bcf6..d59bd06347cc8d 100644 --- a/arch/riscv/include/asm/acpi.h +++ b/arch/riscv/include/asm/acpi.h @@ -92,4 +92,6 @@ void acpi_map_cpus_to_nodes(void); static inline void acpi_map_cpus_to_nodes(void) { } #endif /* CONFIG_ACPI_NUMA */ +#define ACPI_TABLE_UPGRADE_MAX_PHYS MEMBLOCK_ALLOC_ACCESSIBLE + #endif /*_ASM_ACPI_H*/ diff --git a/arch/riscv/include/asm/cacheflush.h b/arch/riscv/include/asm/cacheflush.h index b1a2ac66579263..c6297a1d26459c 100644 --- a/arch/riscv/include/asm/cacheflush.h +++ b/arch/riscv/include/asm/cacheflush.h @@ -6,7 +6,9 @@ #ifndef _ASM_RISCV_CACHEFLUSH_H #define _ASM_RISCV_CACHEFLUSH_H +#include #include +#include static inline void local_flush_icache_all(void) { @@ -40,25 +42,35 @@ do { \ flush_icache_mm(vma->vm_mm, 0); \ } while (0) -#ifdef CONFIG_64BIT -extern u64 new_vmalloc[NR_CPUS / sizeof(u64) + 1]; +#if defined(CONFIG_64BIT) && defined(CONFIG_MMU) +/* This is accessed in assembly code. cpumask_var_t would be too complex. */ +extern DECLARE_BITMAP(new_valid_map_cpus, NR_CPUS); extern char _end[]; static inline void mark_new_valid_map(void) { - int i; + /* + * Orders any previous page table writes before setting bits in + * new_valid_map_cpus. Pairs with the sfence.vma in + * new_valid_map_cpus_check. + */ + smp_wmb(); /* * We don't care if concurrently a cpu resets this value since * the only place this can happen is in handle_exception() where * an sfence.vma is emitted. + * + * Not memset() or bitmap_fill() to avoid any possible compiler + * shenanigans. */ - for (i = 0; i < ARRAY_SIZE(new_vmalloc); ++i) - new_vmalloc[i] = -1ULL; + for (size_t i = 0; i < ARRAY_SIZE(new_valid_map_cpus); i++) + WRITE_ONCE(new_valid_map_cpus[i], -1UL); } #define flush_cache_vmap flush_cache_vmap static inline void flush_cache_vmap(unsigned long start, unsigned long end) { - if (is_vmalloc_or_module_addr((void *)start)) + if (is_vmalloc_or_module_addr((void *)start) || + (start >= VMEMMAP_START && end <= VMEMMAP_END)) mark_new_valid_map(); } #define flush_cache_vmap_early(start, end) local_flush_tlb_kernel_range(start, end) diff --git a/arch/riscv/include/asm/cmpxchg.h b/arch/riscv/include/asm/cmpxchg.h index 8712cf9c69dcb3..662e160b05229b 100644 --- a/arch/riscv/include/asm/cmpxchg.h +++ b/arch/riscv/include/asm/cmpxchg.h @@ -23,7 +23,10 @@ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) { \ __asm__ __volatile__ ( \ prepend \ + " .option push\n" \ + " .option arch, +zabha\n" \ " amoswap" swap_sfx " %0, %z2, %1\n" \ + " .option pop\n" \ swap_append \ : "=&r" (r), "+A" (*(p)) \ : "rJ" (n) \ @@ -141,7 +144,10 @@ \ __asm__ __volatile__ ( \ cas_prepend \ + " .option push\n" \ + " .option arch, +zacas, +zabha\n" \ " amocas" cas_sfx " %0, %z2, %1\n" \ + " .option pop\n" \ cas_append \ : "+&r" (r), "+A" (*(p)) \ : "rJ" (n) \ @@ -188,7 +194,10 @@ \ __asm__ __volatile__ ( \ cas_prepend \ + " .option push\n" \ + " .option arch, +zacas\n" \ " amocas" cas_sfx " %0, %z2, %1\n" \ + " .option pop\n" \ cas_append \ : "+&r" (r), "+A" (*(p)) \ : "rJ" (n) \ @@ -340,7 +349,10 @@ union __u128_halves { register unsigned long t4 asm ("t4") = __ho.high; \ \ __asm__ __volatile__ ( \ - " amocas.q" cas_sfx " %0, %z3, %2" \ + " .option push\n" \ + " .option arch, +zacas\n" \ + " amocas.q" cas_sfx " %0, %z3, %2\n" \ + " .option pop\n" \ : "+&r" (t3), "+&r" (t4), "+A" (*(p)) \ : "rJ" (t1), "rJ" (t2) \ : "memory"); \ diff --git a/arch/riscv/include/asm/cpufeature.h b/arch/riscv/include/asm/cpufeature.h index 739fcc84bf7b28..ba3d74f6006a65 100644 --- a/arch/riscv/include/asm/cpufeature.h +++ b/arch/riscv/include/asm/cpufeature.h @@ -128,6 +128,14 @@ struct riscv_isa_ext_data { extern const struct riscv_isa_ext_data riscv_isa_ext[]; extern const size_t riscv_isa_ext_count; extern bool riscv_isa_fallback; +DECLARE_STATIC_KEY_FALSE(riscv_hw_pte_ad_updating); + +static __always_inline bool riscv_has_hw_pte_ad_updating(void) +{ + return static_branch_unlikely(&riscv_hw_pte_ad_updating); +} + +int riscv_enable_hw_pte_ad_updating(void); unsigned long riscv_isa_extension_base(const unsigned long *isa_bitmap); static __always_inline bool riscv_cpu_has_extension_likely(int cpu, const unsigned long ext) diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h index 31b8988f4488da..0f69f389c96d37 100644 --- a/arch/riscv/include/asm/csr.h +++ b/arch/riscv/include/asm/csr.h @@ -16,6 +16,7 @@ #define SR_MPIE _AC(0x00000080, UL) /* Previous Machine IE */ #define SR_SPP _AC(0x00000100, UL) /* Previously Supervisor */ #define SR_MPP _AC(0x00001800, UL) /* Previously Machine */ +#define SR_MPP_SHIFT 11 #define SR_SUM _AC(0x00040000, UL) /* Supervisor User Memory Access */ /* zicfilp landing pad status bit */ @@ -606,6 +607,9 @@ : "memory"); \ }) +unsigned long csr_read_num(unsigned long csr_num, int *out_err); +void csr_write_num(unsigned long csr_num, unsigned long val, int *out_err); + #endif /* __ASSEMBLER__ */ #endif /* _ASM_RISCV_CSR_H */ diff --git a/arch/riscv/include/asm/errata_list_vendors.h b/arch/riscv/include/asm/errata_list_vendors.h index ec7eba3734371a..8fd7c36307e2ac 100644 --- a/arch/riscv/include/asm/errata_list_vendors.h +++ b/arch/riscv/include/asm/errata_list_vendors.h @@ -18,7 +18,8 @@ #define ERRATA_THEAD_MAE 0 #define ERRATA_THEAD_PMU 1 #define ERRATA_THEAD_GHOSTWRITE 2 -#define ERRATA_THEAD_NUMBER 3 +#define ERRATA_THEAD_WRITE_ONCE 3 +#define ERRATA_THEAD_NUMBER 4 #endif #ifdef CONFIG_ERRATA_MIPS diff --git a/arch/riscv/include/asm/hwprobe.h b/arch/riscv/include/asm/hwprobe.h index 8b9f5e1cf4cb51..9b04377c0f98fa 100644 --- a/arch/riscv/include/asm/hwprobe.h +++ b/arch/riscv/include/asm/hwprobe.h @@ -43,11 +43,4 @@ static inline bool riscv_hwprobe_pair_cmp(struct riscv_hwprobe *pair, return pair->value == other_pair->value; } -#ifdef CONFIG_MMU -void riscv_hwprobe_register_async_probe(void); -void riscv_hwprobe_complete_async_probe(void); -#else -static inline void riscv_hwprobe_register_async_probe(void) {} -static inline void riscv_hwprobe_complete_async_probe(void) {} -#endif #endif diff --git a/arch/riscv/include/asm/pgtable.h b/arch/riscv/include/asm/pgtable.h index 5d5756bda82e3d..02286b48dc4712 100644 --- a/arch/riscv/include/asm/pgtable.h +++ b/arch/riscv/include/asm/pgtable.h @@ -678,15 +678,21 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm, static inline void ptep_set_wrprotect(struct mm_struct *mm, unsigned long address, pte_t *ptep) { - pte_t read_pte = READ_ONCE(*ptep); + pte_t old_pte; + pte_t pte; /* * ptep_set_wrprotect can be called for shadow stack ranges too. * shadow stack memory is XWR = 010 and thus clearing _PAGE_WRITE will lead to * encoding 000b which is wrong encoding with V = 1. This should lead to page fault * but we dont want this wrong configuration to be set in page tables. */ - atomic_long_set((atomic_long_t *)ptep, - ((pte_val(read_pte) & ~(unsigned long)_PAGE_WRITE) | _PAGE_READ)); + pte = READ_ONCE(*ptep); + do { + old_pte = pte; + pte = pte_wrprotect(pte); + pte_val(pte) = cmpxchg_relaxed(&pte_val(*ptep), pte_val(old_pte), + pte_val(pte)); + } while (pte_val(pte) != pte_val(old_pte)); } #define __HAVE_ARCH_PTEP_CLEAR_YOUNG_FLUSH @@ -742,14 +748,14 @@ static inline pgprot_t pgprot_writecombine(pgprot_t _prot) #define pgprot_dmacoherent pgprot_writecombine /* - * Both Svade and Svadu control the hardware behavior when the PTE A/D bits need to be set. By - * default the M-mode firmware enables the hardware updating scheme when only Svadu is present in - * DT. + * Both Svade and Svadu control the hardware behavior when the PTE A/D bits + * need to be set. The core MM code only cares whether hardware updating of + * the accessed/dirty state is currently active. */ #define arch_has_hw_pte_young arch_has_hw_pte_young static inline bool arch_has_hw_pte_young(void) { - return riscv_has_extension_unlikely(RISCV_ISA_EXT_SVADU); + return riscv_has_hw_pte_ad_updating(); } /* @@ -1040,6 +1046,13 @@ static inline void pmdp_set_wrprotect(struct mm_struct *mm, ptep_set_wrprotect(mm, address, (pte_t *)pmdp); } +#define __HAVE_ARCH_PUDP_SET_WRPROTECT +static inline void pudp_set_wrprotect(struct mm_struct *mm, + unsigned long address, pud_t *pudp) +{ + ptep_set_wrprotect(mm, address, (pte_t *)pudp); +} + #define pmdp_establish pmdp_establish static inline pmd_t pmdp_establish(struct vm_area_struct *vma, unsigned long address, pmd_t *pmdp, pmd_t pmd) diff --git a/arch/riscv/include/asm/rwonce.h b/arch/riscv/include/asm/rwonce.h new file mode 100644 index 00000000000000..081793d4d772da --- /dev/null +++ b/arch/riscv/include/asm/rwonce.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __ASM_RWONCE_H +#define __ASM_RWONCE_H + +#include +#include +#include +#include + +#if defined(CONFIG_ERRATA_THEAD_WRITE_ONCE) && !defined(NO_ALTERNATIVE) + +#define write_once_fence() \ +do { \ + asm volatile(ALTERNATIVE( \ + "nop", \ + "fence w, o", \ + THEAD_VENDOR_ID, \ + ERRATA_THEAD_WRITE_ONCE, \ + CONFIG_ERRATA_THEAD_WRITE_ONCE) \ + : : : "memory"); \ +} while (0) + +#define __WRITE_ONCE(x, val) \ +do { \ + *(volatile typeof(x) *)&(x) = (val); \ + write_once_fence(); \ +} while (0) + +#endif /* defined(CONFIG_ERRATA_THEAD_WRITE_ONCE) && !defined(NO_ALTERNATIVE) */ + +#include + +#endif /* __ASM_RWONCE_H */ diff --git a/arch/riscv/include/asm/thread_info.h b/arch/riscv/include/asm/thread_info.h index 55019fdfa9ecaa..afa9c61d74c603 100644 --- a/arch/riscv/include/asm/thread_info.h +++ b/arch/riscv/include/asm/thread_info.h @@ -73,6 +73,11 @@ struct thread_info { */ unsigned long a0, a1, a2; #endif + /* + * Used in ret_from_exception() and handle_exception() as scratch space + * when CSR_SCRATCH does not reflect the current privilege mode. + */ + unsigned long tp, t0; #ifdef CONFIG_RISCV_USER_CFI struct cfi_state user_cfi_state; #endif diff --git a/arch/riscv/include/asm/timex.h b/arch/riscv/include/asm/timex.h index a06697846e6952..d41acfb3959ddc 100644 --- a/arch/riscv/include/asm/timex.h +++ b/arch/riscv/include/asm/timex.h @@ -22,13 +22,13 @@ static inline cycles_t get_cycles(void) #else /* !CONFIG_64BIT */ static inline u32 get_cycles(void) { - return readl_relaxed(((u32 *)clint_time_val)); + return readl_relaxed(((u32 __iomem *)clint_time_val)); } #define get_cycles get_cycles static inline u32 get_cycles_hi(void) { - return readl_relaxed(((u32 *)clint_time_val) + 1); + return readl_relaxed(((u32 __iomem *)clint_time_val) + 1); } #define get_cycles_hi get_cycles_hi #endif /* CONFIG_64BIT */ diff --git a/arch/riscv/include/asm/uaccess.h b/arch/riscv/include/asm/uaccess.h index 11c9886c3b704b..69bdc070f4204b 100644 --- a/arch/riscv/include/asm/uaccess.h +++ b/arch/riscv/include/asm/uaccess.h @@ -487,6 +487,11 @@ static inline void user_access_restore(unsigned long enabled) { } if (__asm_copy_from_user_sum_enabled(_dst, _src, _len)) \ goto label; +/* Memory cacheability check for vector uaccess optimization */ +#ifdef CONFIG_RISCV_ISA_V +int is_cacheable_safe(const void *addr); +#endif + #else /* CONFIG_MMU */ #include #endif /* CONFIG_MMU */ diff --git a/arch/riscv/include/asm/vdso.h b/arch/riscv/include/asm/vdso.h index 35bf830a557612..0259bbc98e93a1 100644 --- a/arch/riscv/include/asm/vdso.h +++ b/arch/riscv/include/asm/vdso.h @@ -12,14 +12,21 @@ * All systems with an MMU have a VDSO, but systems without an MMU don't * support shared libraries and therefore don't have one. */ -#ifdef CONFIG_MMU #define __VDSO_PAGES 4 #ifndef __ASSEMBLER__ + +#ifdef CONFIG_MMU #include +#else +extern size_t __vdso_alternatives_start_offset, __vdso_alternatives_end_offset; +#endif + #ifdef CONFIG_RISCV_USER_CFI #include +#else +extern size_t __vdso_alternatives_start_cfi_offset, __vdso_alternatives_end_cfi_offset; #endif #ifdef CONFIG_RISCV_USER_CFI @@ -38,15 +45,14 @@ #define COMPAT_VDSO_SYMBOL(base, name) \ (void __user *)((unsigned long)(base) + compat__vdso_##name##_offset) -extern char compat_vdso_start[], compat_vdso_end[]; - +#else +extern size_t compat__vdso_alternatives_start_offset, compat__vdso_alternatives_end_offset; #endif /* CONFIG_COMPAT */ extern char vdso_start[], vdso_end[]; extern char vdso_cfi_start[], vdso_cfi_end[]; +extern char compat_vdso_start[], compat_vdso_end[]; #endif /* !__ASSEMBLER__ */ -#endif /* CONFIG_MMU */ - #endif /* _ASM_RISCV_VDSO_H */ diff --git a/arch/riscv/include/asm/vdso/arch_data.h b/arch/riscv/include/asm/vdso/arch_data.h index 88b37af5517512..da57a3786f7a53 100644 --- a/arch/riscv/include/asm/vdso/arch_data.h +++ b/arch/riscv/include/asm/vdso/arch_data.h @@ -12,12 +12,6 @@ struct vdso_arch_data { /* Boolean indicating all CPUs have the same static hwprobe values. */ __u8 homogeneous_cpus; - - /* - * A gate to check and see if the hwprobe data is actually ready, as - * probing is deferred to avoid boot slowdowns. - */ - __u8 ready; }; #endif /* __RISCV_ASM_VDSO_ARCH_DATA_H */ diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile index cabb99cadfb6d1..cea8c51decae57 100644 --- a/arch/riscv/kernel/Makefile +++ b/arch/riscv/kernel/Makefile @@ -51,6 +51,7 @@ obj-y += soc.o obj-$(CONFIG_RISCV_ALTERNATIVE) += alternative.o obj-y += cpu.o obj-y += cpufeature.o +obj-y += csr.o obj-y += entry.o obj-y += irq.o obj-y += process.o diff --git a/arch/riscv/kernel/acpi.c b/arch/riscv/kernel/acpi.c index 068e0b404b6f4a..efd52a5d05b5c0 100644 --- a/arch/riscv/kernel/acpi.c +++ b/arch/riscv/kernel/acpi.c @@ -353,3 +353,8 @@ int acpi_get_cpu_uid(unsigned int cpu, u32 *uid) return 0; } EXPORT_SYMBOL_GPL(acpi_get_cpu_uid); + +void __init arch_reserve_mem_area(acpi_physical_address addr, size_t size) +{ + memblock_mark_nomap(addr, size); +} diff --git a/arch/riscv/kernel/alternative.c b/arch/riscv/kernel/alternative.c index 7642704c7f1841..c0c9306022c5f8 100644 --- a/arch/riscv/kernel/alternative.c +++ b/arch/riscv/kernel/alternative.c @@ -173,30 +173,16 @@ static void __init_or_module _apply_alternatives(struct alt_entry *begin, stage); } -#ifdef CONFIG_MMU -static void __init apply_vdso_alternatives(void) +static __always_inline +void __init apply_vdso_alternatives(void *base, size_t alternatives_begin, size_t alternatives_end) { - const Elf_Ehdr *hdr; - const Elf_Shdr *shdr; - const Elf_Shdr *alt; - struct alt_entry *begin, *end; - - hdr = (Elf_Ehdr *)vdso_start; - shdr = (void *)hdr + hdr->e_shoff; - alt = find_section(hdr, shdr, ".alternative"); - if (!alt) + if (alternatives_begin == alternatives_end) return; - begin = (void *)hdr + alt->sh_offset, - end = (void *)hdr + alt->sh_offset + alt->sh_size, - - _apply_alternatives((struct alt_entry *)begin, - (struct alt_entry *)end, + _apply_alternatives(base + alternatives_begin, + base + alternatives_end, RISCV_ALTERNATIVES_BOOT); } -#else -static void __init apply_vdso_alternatives(void) { } -#endif void __init apply_boot_alternatives(void) { @@ -207,7 +193,20 @@ void __init apply_boot_alternatives(void) (struct alt_entry *)__alt_end, RISCV_ALTERNATIVES_BOOT); - apply_vdso_alternatives(); + if (IS_ENABLED(CONFIG_MMU)) + apply_vdso_alternatives(vdso_start, + __vdso_alternatives_start_offset, + __vdso_alternatives_end_offset); + + if (IS_ENABLED(CONFIG_RISCV_USER_CFI)) + apply_vdso_alternatives(vdso_cfi_start, + __vdso_alternatives_start_cfi_offset, + __vdso_alternatives_end_cfi_offset); + + if (IS_ENABLED(CONFIG_COMPAT)) + apply_vdso_alternatives(compat_vdso_start, + compat__vdso_alternatives_start_offset, + compat__vdso_alternatives_end_offset); } /* diff --git a/arch/riscv/kernel/asm-offsets.c b/arch/riscv/kernel/asm-offsets.c index af827448a609e6..2d617234bcdb4c 100644 --- a/arch/riscv/kernel/asm-offsets.c +++ b/arch/riscv/kernel/asm-offsets.c @@ -49,6 +49,8 @@ void asm_offsets(void) OFFSET(TASK_TI_A1, task_struct, thread_info.a1); OFFSET(TASK_TI_A2, task_struct, thread_info.a2); #endif + OFFSET(TASK_TI_TP, task_struct, thread_info.tp); + OFFSET(TASK_TI_T0, task_struct, thread_info.t0); OFFSET(TASK_TI_CPU_NUM, task_struct, thread_info.cpu); #ifdef CONFIG_RISCV_USER_CFI diff --git a/arch/riscv/kernel/bugs.c b/arch/riscv/kernel/bugs.c index 3655fe7d678cd1..e5758e3f1c7ef4 100644 --- a/arch/riscv/kernel/bugs.c +++ b/arch/riscv/kernel/bugs.c @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include @@ -46,15 +46,15 @@ ssize_t cpu_show_ghostwrite(struct device *dev, struct device_attribute *attr, c if (IS_ENABLED(CONFIG_RISCV_ISA_XTHEADVECTOR)) { switch (ghostwrite_state) { case UNAFFECTED: - return sprintf(buf, "Not affected\n"); + return sysfs_emit(buf, "Not affected\n"); case MITIGATED: - return sprintf(buf, "Mitigation: xtheadvector disabled\n"); + return sysfs_emit(buf, "Mitigation: xtheadvector disabled\n"); case VULNERABLE: fallthrough; default: - return sprintf(buf, "Vulnerable\n"); + return sysfs_emit(buf, "Vulnerable\n"); } - } else { - return sprintf(buf, "Not affected\n"); } + + return sysfs_emit(buf, "Not affected\n"); } diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c index f46aa5602d74d3..f144797fa2dc84 100644 --- a/arch/riscv/kernel/cpufeature.c +++ b/arch/riscv/kernel/cpufeature.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,9 @@ static bool any_cpu_has_zicboz; static bool any_cpu_has_zicbop; static bool any_cpu_has_zicbom; +DEFINE_STATIC_KEY_FALSE(riscv_hw_pte_ad_updating); +EXPORT_SYMBOL_GPL(riscv_hw_pte_ad_updating); +static bool riscv_hw_pte_ad_updating_requires_fwft __read_mostly; unsigned long elf_hwcap __read_mostly; @@ -287,15 +291,88 @@ static int riscv_ext_zvfbfwma_validate(const struct riscv_isa_ext_data *data, return -EPROBE_DEFER; } -static int riscv_ext_svadu_validate(const struct riscv_isa_ext_data *data, - const unsigned long *isa_bitmap) +static void riscv_set_hw_pte_ad_updating(void) +{ + static_branch_enable(&riscv_hw_pte_ad_updating); +} + +static int riscv_enable_local_hw_pte_ad_updating(void) +{ + return sbi_fwft_set(SBI_FWFT_PTE_AD_HW_UPDATING, 1, 0); +} + +static int riscv_set_online_hw_pte_ad_updating(bool enable) +{ + return sbi_fwft_set_online_cpus(SBI_FWFT_PTE_AD_HW_UPDATING, + enable, 0); +} + +int riscv_enable_hw_pte_ad_updating(void) +{ + unsigned int cpu; + int ret; + + if (!riscv_has_hw_pte_ad_updating() || + !riscv_hw_pte_ad_updating_requires_fwft) + return 0; + + cpu = smp_processor_id(); + ret = riscv_enable_local_hw_pte_ad_updating(); + if (ret) + pr_err("CPU%u failed to enable hardware PTE A/D updating: %d\n", + cpu, ret); + + return ret; +} + +static void riscv_disable_hw_pte_ad_updating(int error) +{ + int ret; + + riscv_hw_pte_ad_updating_requires_fwft = false; + if (error != -EOPNOTSUPP) + pr_err("Failed to enable hardware PTE A/D updating: %d\n", + error); + + ret = riscv_set_online_hw_pte_ad_updating(false); + if (ret && ret != -EOPNOTSUPP) + pr_err("Failed to rollback hardware PTE A/D updating: %d\n", + ret); + + pr_info("riscv: leave PTE A/D updates software-managed (%d)\n", + error); +} + +static int __init riscv_hw_pte_ad_updating_init(void) { - /* SVADE has already been detected, use SVADE only */ - if (__riscv_isa_extension_available(isa_bitmap, RISCV_ISA_EXT_SVADE)) - return -EOPNOTSUPP; + bool has_svade, has_svadu; + int ret; + has_svade = riscv_has_extension_unlikely(RISCV_ISA_EXT_SVADE); + has_svadu = riscv_has_extension_unlikely(RISCV_ISA_EXT_SVADU); + + if (!has_svadu) + return 0; + + if (has_svade) { + riscv_hw_pte_ad_updating_requires_fwft = true; + ret = riscv_set_online_hw_pte_ad_updating(true); + if (ret) { + riscv_disable_hw_pte_ad_updating(ret); + return 0; + } + } + + /* + * At this point hardware PTE A/D updating is active for all online + * harts, either from boot or from the FWFT setup above. Later harts + * must do the same in secondary startup before they are marked online. + */ + riscv_set_hw_pte_ad_updating(); + pr_debug("riscv: hardware PTE A/D updating enabled\n"); return 0; } +arch_initcall(riscv_hw_pte_ad_updating_init); static int riscv_cfilp_validate(const struct riscv_isa_ext_data *data, const unsigned long *isa_bitmap) @@ -584,7 +661,7 @@ const struct riscv_isa_ext_data riscv_isa_ext[] = { __RISCV_ISA_EXT_SUPERSET(ssnpm, RISCV_ISA_EXT_SSNPM, riscv_xlinuxenvcfg_exts), __RISCV_ISA_EXT_DATA(sstc, RISCV_ISA_EXT_SSTC), __RISCV_ISA_EXT_DATA(svade, RISCV_ISA_EXT_SVADE), - __RISCV_ISA_EXT_DATA_VALIDATE(svadu, RISCV_ISA_EXT_SVADU, riscv_ext_svadu_validate), + __RISCV_ISA_EXT_DATA(svadu, RISCV_ISA_EXT_SVADU), __RISCV_ISA_EXT_DATA(svinval, RISCV_ISA_EXT_SVINVAL), __RISCV_ISA_EXT_DATA(svnapot, RISCV_ISA_EXT_SVNAPOT), __RISCV_ISA_EXT_DATA(svpbmt, RISCV_ISA_EXT_SVPBMT), diff --git a/arch/riscv/kernel/csr.c b/arch/riscv/kernel/csr.c new file mode 100644 index 00000000000000..e96b129c1a99ed --- /dev/null +++ b/arch/riscv/kernel/csr.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025 Ventana Micro Systems Inc. + */ + +#include +#include +#include +#include + +#define CSR_CUSTOM0_U_RW_BASE 0x800 +#define CSR_CUSTOM0_U_RW_COUNT 0x100 + +#define CSR_CUSTOM1_U_RO_BASE 0xCC0 +#define CSR_CUSTOM1_U_RO_COUNT 0x040 + +#define CSR_CUSTOM2_S_RW_BASE 0x5C0 +#define CSR_CUSTOM2_S_RW_COUNT 0x040 + +#define CSR_CUSTOM3_S_RW_BASE 0x9C0 +#define CSR_CUSTOM3_S_RW_COUNT 0x040 + +#define CSR_CUSTOM4_S_RO_BASE 0xDC0 +#define CSR_CUSTOM4_S_RO_COUNT 0x040 + +#define CSR_CUSTOM5_HS_RW_BASE 0x6C0 +#define CSR_CUSTOM5_HS_RW_COUNT 0x040 + +#define CSR_CUSTOM6_HS_RW_BASE 0xAC0 +#define CSR_CUSTOM6_HS_RW_COUNT 0x040 + +#define CSR_CUSTOM7_HS_RO_BASE 0xEC0 +#define CSR_CUSTOM7_HS_RO_COUNT 0x040 + +#define CSR_CUSTOM8_M_RW_BASE 0x7C0 +#define CSR_CUSTOM8_M_RW_COUNT 0x040 + +#define CSR_CUSTOM9_M_RW_BASE 0xBC0 +#define CSR_CUSTOM9_M_RW_COUNT 0x040 + +#define CSR_CUSTOM10_M_RO_BASE 0xFC0 +#define CSR_CUSTOM10_M_RO_COUNT 0x040 + +unsigned long csr_read_num(unsigned long csr_num, int *out_err) +{ +#define switchcase_csr_read(__csr_num) \ + case (__csr_num): \ + return csr_read(__csr_num) +#define switchcase_csr_read_2(__csr_num) \ + switchcase_csr_read(__csr_num + 0); \ + switchcase_csr_read(__csr_num + 1) +#define switchcase_csr_read_4(__csr_num) \ + switchcase_csr_read_2(__csr_num + 0); \ + switchcase_csr_read_2(__csr_num + 2) +#define switchcase_csr_read_8(__csr_num) \ + switchcase_csr_read_4(__csr_num + 0); \ + switchcase_csr_read_4(__csr_num + 4) +#define switchcase_csr_read_16(__csr_num) \ + switchcase_csr_read_8(__csr_num + 0); \ + switchcase_csr_read_8(__csr_num + 8) +#define switchcase_csr_read_32(__csr_num) \ + switchcase_csr_read_16(__csr_num + 0); \ + switchcase_csr_read_16(__csr_num + 16) +#define switchcase_csr_read_64(__csr_num) \ + switchcase_csr_read_32(__csr_num + 0); \ + switchcase_csr_read_32(__csr_num + 32) +#define switchcase_csr_read_128(__csr_num) \ + switchcase_csr_read_64(__csr_num + 0); \ + switchcase_csr_read_64(__csr_num + 64) +#define switchcase_csr_read_256(__csr_num) \ + switchcase_csr_read_128(__csr_num + 0); \ + switchcase_csr_read_128(__csr_num + 128) + + *out_err = 0; + switch (csr_num) { + switchcase_csr_read_32(CSR_CYCLE); + switchcase_csr_read_32(CSR_CYCLEH); + switchcase_csr_read_256(CSR_CUSTOM0_U_RW_BASE); + switchcase_csr_read_64(CSR_CUSTOM1_U_RO_BASE); + switchcase_csr_read_64(CSR_CUSTOM2_S_RW_BASE); + switchcase_csr_read_64(CSR_CUSTOM3_S_RW_BASE); + switchcase_csr_read_64(CSR_CUSTOM4_S_RO_BASE); + switchcase_csr_read_64(CSR_CUSTOM5_HS_RW_BASE); + switchcase_csr_read_64(CSR_CUSTOM6_HS_RW_BASE); + switchcase_csr_read_64(CSR_CUSTOM7_HS_RO_BASE); +#ifdef CONFIG_RISCV_M_MODE + switchcase_csr_read_64(CSR_CUSTOM8_M_RW_BASE); + switchcase_csr_read_64(CSR_CUSTOM9_M_RW_BASE); + switchcase_csr_read_64(CSR_CUSTOM10_M_RO_BASE); +#endif + default: + *out_err = -EINVAL; + break; + } + + return 0; +#undef switchcase_csr_read_256 +#undef switchcase_csr_read_128 +#undef switchcase_csr_read_64 +#undef switchcase_csr_read_32 +#undef switchcase_csr_read_16 +#undef switchcase_csr_read_8 +#undef switchcase_csr_read_4 +#undef switchcase_csr_read_2 +#undef switchcase_csr_read +} +EXPORT_SYMBOL_GPL(csr_read_num); + +void csr_write_num(unsigned long csr_num, unsigned long val, int *out_err) +{ +#define switchcase_csr_write(__csr_num, __val) \ + case (__csr_num): \ + csr_write(__csr_num, __val); \ + break +#define switchcase_csr_write_2(__csr_num, __val) \ + switchcase_csr_write(__csr_num + 0, __val); \ + switchcase_csr_write(__csr_num + 1, __val) +#define switchcase_csr_write_4(__csr_num, __val) \ + switchcase_csr_write_2(__csr_num + 0, __val); \ + switchcase_csr_write_2(__csr_num + 2, __val) +#define switchcase_csr_write_8(__csr_num, __val) \ + switchcase_csr_write_4(__csr_num + 0, __val); \ + switchcase_csr_write_4(__csr_num + 4, __val) +#define switchcase_csr_write_16(__csr_num, __val) \ + switchcase_csr_write_8(__csr_num + 0, __val); \ + switchcase_csr_write_8(__csr_num + 8, __val) +#define switchcase_csr_write_32(__csr_num, __val) \ + switchcase_csr_write_16(__csr_num + 0, __val); \ + switchcase_csr_write_16(__csr_num + 16, __val) +#define switchcase_csr_write_64(__csr_num, __val) \ + switchcase_csr_write_32(__csr_num + 0, __val); \ + switchcase_csr_write_32(__csr_num + 32, __val) +#define switchcase_csr_write_128(__csr_num, __val) \ + switchcase_csr_write_64(__csr_num + 0, __val); \ + switchcase_csr_write_64(__csr_num + 64, __val) +#define switchcase_csr_write_256(__csr_num, __val) \ + switchcase_csr_write_128(__csr_num + 0, __val); \ + switchcase_csr_write_128(__csr_num + 128, __val) + + *out_err = 0; + switch (csr_num) { + switchcase_csr_write_256(CSR_CUSTOM0_U_RW_BASE, val); + switchcase_csr_write_64(CSR_CUSTOM2_S_RW_BASE, val); + switchcase_csr_write_64(CSR_CUSTOM3_S_RW_BASE, val); + switchcase_csr_write_64(CSR_CUSTOM5_HS_RW_BASE, val); + switchcase_csr_write_64(CSR_CUSTOM6_HS_RW_BASE, val); +#ifdef CONFIG_RISCV_M_MODE + switchcase_csr_write_64(CSR_CUSTOM8_M_RW_BASE, val); + switchcase_csr_write_64(CSR_CUSTOM9_M_RW_BASE, val); +#endif + default: + *out_err = -EINVAL; + break; + } +#undef switchcase_csr_write_256 +#undef switchcase_csr_write_128 +#undef switchcase_csr_write_64 +#undef switchcase_csr_write_32 +#undef switchcase_csr_write_16 +#undef switchcase_csr_write_8 +#undef switchcase_csr_write_4 +#undef switchcase_csr_write_2 +#undef switchcase_csr_write +} +EXPORT_SYMBOL_GPL(csr_write_num); diff --git a/arch/riscv/kernel/entry.S b/arch/riscv/kernel/entry.S index e189c477c0e23a..da4c9d4e3d01f0 100644 --- a/arch/riscv/kernel/entry.S +++ b/arch/riscv/kernel/entry.S @@ -20,44 +20,44 @@ .section .irqentry.text, "ax" -.macro new_vmalloc_check +.macro new_valid_map_cpus_check REG_S a0, TASK_TI_A0(tp) csrr a0, CSR_CAUSE /* Exclude IRQs */ - blt a0, zero, .Lnew_vmalloc_restore_context_a0 + blt a0, zero, .Lnew_valid_map_cpus_restore_context_a0 REG_S a1, TASK_TI_A1(tp) - /* Only check new_vmalloc if we are in page/protection fault */ + /* Only check new_valid_map_cpus if we are in page/protection fault */ li a1, EXC_LOAD_PAGE_FAULT - beq a0, a1, .Lnew_vmalloc_kernel_address + beq a0, a1, .Lnew_valid_map_cpus_kernel_address li a1, EXC_STORE_PAGE_FAULT - beq a0, a1, .Lnew_vmalloc_kernel_address + beq a0, a1, .Lnew_valid_map_cpus_kernel_address li a1, EXC_INST_PAGE_FAULT - bne a0, a1, .Lnew_vmalloc_restore_context_a1 + bne a0, a1, .Lnew_valid_map_cpus_restore_context_a1 -.Lnew_vmalloc_kernel_address: +.Lnew_valid_map_cpus_kernel_address: /* Is it a kernel address? */ csrr a0, CSR_TVAL - bge a0, zero, .Lnew_vmalloc_restore_context_a1 + bge a0, zero, .Lnew_valid_map_cpus_restore_context_a1 /* Check if a new vmalloc mapping appeared that could explain the trap */ REG_S a2, TASK_TI_A2(tp) /* * Computes: - * a0 = &new_vmalloc[BIT_WORD(cpu)] + * a0 = &new_valid_map_cpus[BIT_WORD(cpu)] * a1 = BIT_MASK(cpu) */ lw a2, TASK_TI_CPU(tp) /* - * Compute the new_vmalloc element position: + * Compute the new_valid_map_cpus element position: * (cpu / 64) * 8 = (cpu >> 6) << 3 */ srli a1, a2, 6 slli a1, a1, 3 - la a0, new_vmalloc + la a0, new_valid_map_cpus add a0, a0, a1 /* - * Compute the bit position in the new_vmalloc element: + * Compute the bit position in the new_valid_map_cpus element: * bit_pos = cpu % 64 = cpu - (cpu / 64) * 64 = cpu - (cpu >> 6) << 6 * = cpu - ((cpu >> 6) << 3) << 3 */ @@ -67,15 +67,17 @@ li a2, 1 sll a1, a2, a1 - /* Check the value of new_vmalloc for this cpu */ + /* Check the value of new_valid_map_cpus for this cpu */ REG_L a2, 0(a0) and a2, a2, a1 - beq a2, zero, .Lnew_vmalloc_restore_context + beq a2, zero, .Lnew_valid_map_cpus_restore_context - /* Atomically reset the current cpu bit in new_vmalloc */ + /* Atomically reset the current cpu bit in new_valid_map_cpus */ amoxor.d a0, a1, (a0) /* + * Pairs with the smp_wmb() in mark_new_valid_map() + * * A sfence.vma is required here. Even if we had Svvptc, there's no * guarantee that after returning we wouldn't just fault again. */ @@ -87,11 +89,11 @@ csrw CSR_SCRATCH, x0 sret -.Lnew_vmalloc_restore_context: +.Lnew_valid_map_cpus_restore_context: REG_L a2, TASK_TI_A2(tp) -.Lnew_vmalloc_restore_context_a1: +.Lnew_valid_map_cpus_restore_context_a1: REG_L a1, TASK_TI_A1(tp) -.Lnew_vmalloc_restore_context_a0: +.Lnew_valid_map_cpus_restore_context_a0: REG_L a0, TASK_TI_A0(tp) .endm @@ -126,6 +128,8 @@ .endm SYM_CODE_START(handle_exception) + /* Caution: CSR_SCARTCH does not reflect privilege mode from now on. */ + /* * If coming from userspace, preserve the user thread pointer and load * the kernel thread pointer. If we came from the kernel, the scratch @@ -137,19 +141,14 @@ SYM_CODE_START(handle_exception) .Lrestore_kernel_tpsp: csrr tp, CSR_SCRATCH -#ifdef CONFIG_64BIT +#if defined(CONFIG_64BIT) && defined(CONFIG_MMU) /* * The RISC-V kernel does not flush TLBs on all CPUS after each new * vmalloc mapping or kfence_unprotect(), which may result in - * exceptions: - * - * - if the uarch caches invalid entries, the new mapping would not be - * observed by the page table walker and an invalidation is needed. - * - if the uarch does not cache invalid entries, a reordered access - * could "miss" the new mapping and traps: in that case, we only need - * to retry the access, no sfence.vma is required. + * exceptions. In that case, we need to sfence.vma to "receive" the new + * mappings and retry, whether or not we have Svvptc. */ - new_vmalloc_check + new_valid_map_cpus_check #endif REG_S sp, TASK_TI_KERNEL_SP(tp) @@ -163,6 +162,19 @@ SYM_CODE_START(handle_exception) #endif .Lsave_context: + REG_S t0, TASK_TI_T0(tp) + + /* + * Grab the saved TP, and also set the scratch register back to 0, so + * that if a recursive exception occurs, the exception vector knows it + * came from the kernel. + * + * Caution: CSR_SCARTCH does not reflect privilege mode before this. + */ + csrrw t0, CSR_SCRATCH, zero + REG_S t0, TASK_TI_TP(tp) + REG_L t0, TASK_TI_T0(tp) + REG_S sp, TASK_TI_USER_SP(tp) REG_L sp, TASK_TI_KERNEL_SP(tp) addi sp, sp, -(PT_SIZE_ON_STACK) @@ -190,7 +202,7 @@ SYM_CODE_START(handle_exception) csrr s2, CSR_EPC csrr s3, CSR_TVAL csrr s4, CSR_CAUSE - csrr s5, CSR_SCRATCH + REG_L s5, TASK_TI_TP(tp) REG_S s0, PT_SP(sp) REG_S s1, PT_STATUS(sp) REG_S s2, PT_EPC(sp) @@ -198,12 +210,6 @@ SYM_CODE_START(handle_exception) REG_S s4, PT_CAUSE(sp) REG_S s5, PT_TP(sp) - /* - * Set the scratch register to 0, so that if a recursive exception - * occurs, the exception vector knows it came from the kernel - */ - csrw CSR_SCRATCH, x0 - /* Load the global pointer */ load_global_pointer @@ -271,11 +277,6 @@ SYM_CODE_START_NOALIGN(ret_from_exception) /* Save the kernel shadow call stack pointer */ scs_save_current - /* - * Save TP into the scratch register , so we can find the kernel data - * structures again. - */ - csrw CSR_SCRATCH, tp 1: #ifdef CONFIG_RISCV_ISA_V_PREEMPTIVE move a0, sp @@ -308,11 +309,42 @@ SYM_CODE_START_NOALIGN(ret_from_exception) REG_L x1, PT_RA(sp) REG_L x3, PT_GP(sp) - REG_L x4, PT_TP(sp) - REG_L x5, PT_T0(sp) restore_from_x6_to_x31 - REG_L x2, PT_SP(sp) + /* + * Stash t0 and tp in thread_info since we can't access the stack after + * switching CSR_SCRATCH. + */ + REG_L t0, PT_T0(sp) + REG_S t0, TASK_TI_T0(tp) + REG_L t0, PT_TP(sp) + REG_S t0, TASK_TI_TP(tp) + + REG_L sp, PT_SP(sp) + + /* If returning to user mode, set CSR_SCRATCH to tp, else 0 */ + csrr t0, CSR_STATUS +#if defined(CONFIG_RISCV_M_MODE) + /* the MPP value is too large to be used as an immediate arg for andi */ + srli t0, t0, SR_MPP_SHIFT + andi t0, t0, SR_MPP >> SR_MPP_SHIFT + /* t0 = (mstatus & SR_MPP) >> SR_MPP_SHIFT */ +#else + andi t0, t0, SR_SPP + /* t0 = sstatus & SR_SPP */ +#endif + seqz t0, t0 + /* t0 = !(status & SR_PP) */ + neg t0, t0 + /* t0 = (status & SR_PP) ? 0 : -1 */ + and t0, t0, tp + /* t0 = (status & SR_PP) ? 0 : tp */ + csrw CSR_SCRATCH, t0 + + /* Caution: CSR_SCARTCH does not reflect privilege mode from now on. */ + + REG_L t0, TASK_TI_T0(tp) + REG_L tp, TASK_TI_TP(tp) #ifdef CONFIG_RISCV_M_MODE mret diff --git a/arch/riscv/kernel/machine_kexec.c b/arch/riscv/kernel/machine_kexec.c index 2306ce3e5f229f..738df176ff6f16 100644 --- a/arch/riscv/kernel/machine_kexec.c +++ b/arch/riscv/kernel/machine_kexec.c @@ -41,6 +41,9 @@ machine_kexec_prepare(struct kimage *image) if (image->segment[i].memsz <= sizeof(fdt)) continue; + if (!image->segment[i].buf) + continue; + if (image->file_mode) memcpy(&fdt, image->segment[i].buf, sizeof(fdt)); else if (copy_from_user(&fdt, image->segment[i].buf, sizeof(fdt))) diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c index 54e2d9552e930d..59d4bbc848a896 100644 --- a/arch/riscv/kernel/machine_kexec_file.c +++ b/arch/riscv/kernel/machine_kexec_file.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -266,7 +267,7 @@ int load_extra_segments(struct kimage *image, unsigned long kernel_start, kbuf.image = image; kbuf.buf_min = kernel_start + kernel_len; - kbuf.buf_max = ULONG_MAX; + kbuf.buf_max = PFN_PHYS(max_low_pfn); #ifdef CONFIG_CRASH_DUMP /* Add elfcorehdr */ diff --git a/arch/riscv/kernel/module-sections.c b/arch/riscv/kernel/module-sections.c index 98eaac6f660604..b3b11b7f7ed958 100644 --- a/arch/riscv/kernel/module-sections.c +++ b/arch/riscv/kernel/module-sections.c @@ -56,17 +56,15 @@ unsigned long module_emit_plt_entry(struct module *mod, unsigned long val) return (unsigned long)&plt[i]; } -#define cmp_3way(a, b) ((a) < (b) ? -1 : (a) > (b)) - static int cmp_rela(const void *a, const void *b) { const Elf_Rela *x = a, *y = b; int i; /* sort by type, symbol index and addend */ - i = cmp_3way(x->r_info, y->r_info); + i = cmp_int(x->r_info, y->r_info); if (i == 0) - i = cmp_3way(x->r_addend, y->r_addend); + i = cmp_int(x->r_addend, y->r_addend); return i; } diff --git a/arch/riscv/kernel/pi/fdt_early.c b/arch/riscv/kernel/pi/fdt_early.c index a12ff8090f1903..7022532875604d 100644 --- a/arch/riscv/kernel/pi/fdt_early.c +++ b/arch/riscv/kernel/pi/fdt_early.c @@ -218,6 +218,8 @@ u64 set_satp_mode_from_fdt(uintptr_t dtb_pa) return SATP_MODE_39; else if (!strcmp(mmu_type, "riscv,sv48")) return SATP_MODE_48; + else if (!strcmp(mmu_type, "riscv,sv57")) + return SATP_MODE_57; break; } diff --git a/arch/riscv/kernel/probes/rethook_trampoline.S b/arch/riscv/kernel/probes/rethook_trampoline.S index f2cd83d9b0f004..c3aa8d8cf5aff1 100644 --- a/arch/riscv/kernel/probes/rethook_trampoline.S +++ b/arch/riscv/kernel/probes/rethook_trampoline.S @@ -41,6 +41,9 @@ REG_S x29, PT_T4(sp) REG_S x30, PT_T5(sp) REG_S x31, PT_T6(sp) + /* save original sp */ + addi a0, sp, PT_SIZE_ON_STACK + REG_S a0, PT_SP(sp) .endm .macro restore_all_base_regs diff --git a/arch/riscv/kernel/reset.c b/arch/riscv/kernel/reset.c index 9122885722265c..8c48466c50e9a0 100644 --- a/arch/riscv/kernel/reset.c +++ b/arch/riscv/kernel/reset.c @@ -5,6 +5,7 @@ #include #include +#include static void default_power_off(void) { @@ -17,18 +18,27 @@ EXPORT_SYMBOL(pm_power_off); void machine_restart(char *cmd) { + local_irq_disable(); + smp_send_stop(); + do_kernel_restart(cmd); while (1); } void machine_halt(void) { + local_irq_disable(); + smp_send_stop(); + do_kernel_power_off(); default_power_off(); } void machine_power_off(void) { + local_irq_disable(); + smp_send_stop(); + do_kernel_power_off(); default_power_off(); } diff --git a/arch/riscv/kernel/setup.c b/arch/riscv/kernel/setup.c index c89cc272440b9f..a32344bb220dff 100644 --- a/arch/riscv/kernel/setup.c +++ b/arch/riscv/kernel/setup.c @@ -71,16 +71,13 @@ static struct resource *standard_resources; static int __init add_resource(struct resource *parent, struct resource *res) { - int ret = 0; + int ret; ret = insert_resource(parent, res); - if (ret < 0) { - pr_err("Failed to add a %s resource at %llx\n", - res->name, (unsigned long long) res->start); - return ret; - } + if (ret < 0) + pr_err("Failed to add resource %s %pR\n", res->name, res); - return 1; + return ret; } static int __init add_kernel_resources(void) @@ -324,6 +321,8 @@ void __init setup_arch(char **cmdline_p) efi_init(); paging_init(); + acpi_table_upgrade(); + /* Parse the ACPI tables for possible boot-time configuration */ acpi_boot_table_init(); diff --git a/arch/riscv/kernel/smpboot.c b/arch/riscv/kernel/smpboot.c index 8b628580fe118e..4fe62f96bcca2a 100644 --- a/arch/riscv/kernel/smpboot.c +++ b/arch/riscv/kernel/smpboot.c @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -221,6 +222,9 @@ asmlinkage __visible void smp_callin(void) struct mm_struct *mm = &init_mm; unsigned int curr_cpuid = smp_processor_id(); + if (riscv_enable_hw_pte_ad_updating()) + return; + if (has_vector()) { /* * Return as early as possible so the hart with a mismatching diff --git a/arch/riscv/kernel/stacktrace.c b/arch/riscv/kernel/stacktrace.c index b41b6255751cb1..694c9c7fde4a44 100644 --- a/arch/riscv/kernel/stacktrace.c +++ b/arch/riscv/kernel/stacktrace.c @@ -52,11 +52,14 @@ void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs, int graph_idx = 0; int level = 0; + if (task == NULL) + task = current; + if (regs) { fp = frame_pointer(regs); sp = user_stack_pointer(regs); pc = instruction_pointer(regs); - } else if (task == NULL || task == current) { + } else if (task == current) { fp = (unsigned long)__builtin_frame_address(0); sp = current_stack_pointer; pc = (unsigned long)walk_stackframe; diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c index 1659d31fd288fc..2ce162f5cf7c5d 100644 --- a/arch/riscv/kernel/sys_hwprobe.c +++ b/arch/riscv/kernel/sys_hwprobe.c @@ -5,9 +5,6 @@ * more details. */ #include -#include -#include -#include #include #include #include @@ -504,32 +501,28 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, return 0; } -#ifdef CONFIG_MMU - -static DECLARE_COMPLETION(boot_probes_done); -static atomic_t pending_boot_probes = ATOMIC_INIT(1); - -void riscv_hwprobe_register_async_probe(void) +static int do_riscv_hwprobe(struct riscv_hwprobe __user *pairs, + size_t pair_count, size_t cpusetsize, + unsigned long __user *cpus_user, + unsigned int flags) { - atomic_inc(&pending_boot_probes); -} + if (flags & RISCV_HWPROBE_WHICH_CPUS) + return hwprobe_get_cpus(pairs, pair_count, cpusetsize, + cpus_user, flags); -void riscv_hwprobe_complete_async_probe(void) -{ - if (atomic_dec_and_test(&pending_boot_probes)) - complete(&boot_probes_done); + return hwprobe_get_values(pairs, pair_count, cpusetsize, + cpus_user, flags); } -static int complete_hwprobe_vdso_data(void) +#ifdef CONFIG_MMU + +static int __init init_hwprobe_vdso_data(void) { struct vdso_arch_data *avd = vdso_k_arch_data; u64 id_bitsmash = 0; struct riscv_hwprobe pair; int key; - if (unlikely(!atomic_dec_and_test(&pending_boot_probes))) - wait_for_completion(&boot_probes_done); - /* * Initialize vDSO data with the answers for the "all CPUs" case, to * save a syscall in the common case. @@ -557,52 +550,13 @@ static int complete_hwprobe_vdso_data(void) * vDSO should defer to the kernel for exotic cpu masks. */ avd->homogeneous_cpus = id_bitsmash != 0 && id_bitsmash != -1; - - /* - * Make sure all the VDSO values are visible before we look at them. - * This pairs with the implicit "no speculativly visible accesses" - * barrier in the VDSO hwprobe code. - */ - smp_wmb(); - avd->ready = true; - return 0; -} - -static int __init init_hwprobe_vdso_data(void) -{ - struct vdso_arch_data *avd = vdso_k_arch_data; - - /* - * Prevent the vDSO cached values from being used, as they're not ready - * yet. - */ - avd->ready = false; return 0; } arch_initcall_sync(init_hwprobe_vdso_data); -#else - -static int complete_hwprobe_vdso_data(void) { return 0; } - #endif /* CONFIG_MMU */ -static int do_riscv_hwprobe(struct riscv_hwprobe __user *pairs, - size_t pair_count, size_t cpusetsize, - unsigned long __user *cpus_user, - unsigned int flags) -{ - DO_ONCE_SLEEPABLE(complete_hwprobe_vdso_data); - - if (flags & RISCV_HWPROBE_WHICH_CPUS) - return hwprobe_get_cpus(pairs, pair_count, cpusetsize, - cpus_user, flags); - - return hwprobe_get_values(pairs, pair_count, cpusetsize, - cpus_user, flags); -} - SYSCALL_DEFINE5(riscv_hwprobe, struct riscv_hwprobe __user *, pairs, size_t, pair_count, size_t, cpusetsize, unsigned long __user *, cpus, unsigned int, flags) diff --git a/arch/riscv/kernel/unaligned_access_speed.c b/arch/riscv/kernel/unaligned_access_speed.c index 11c781a4de733a..cde91b2c8270be 100644 --- a/arch/riscv/kernel/unaligned_access_speed.c +++ b/arch/riscv/kernel/unaligned_access_speed.c @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -313,18 +312,9 @@ static void check_vector_unaligned_access(struct work_struct *work __always_unus __free_pages(page, MISALIGNED_BUFFER_ORDER); } -/* Measure unaligned access speed on all CPUs present at boot in parallel. */ -static int __init vec_check_unaligned_access_speed_all_cpus(void *unused __always_unused) -{ - schedule_on_each_cpu(check_vector_unaligned_access); - riscv_hwprobe_complete_async_probe(); - - return 0; -} #else /* CONFIG_RISCV_PROBE_VECTOR_UNALIGNED_ACCESS */ -static int __init vec_check_unaligned_access_speed_all_cpus(void *unused __always_unused) +static void check_vector_unaligned_access(struct work_struct *work __always_unused) { - return 0; } #endif @@ -412,12 +402,7 @@ static int __init check_unaligned_access_all_cpus(void) per_cpu(vector_misaligned_access, cpu) = unaligned_vector_speed_param; } else if (!check_vector_unaligned_access_emulated_all_cpus() && IS_ENABLED(CONFIG_RISCV_PROBE_VECTOR_UNALIGNED_ACCESS)) { - riscv_hwprobe_register_async_probe(); - if (IS_ERR(kthread_run(vec_check_unaligned_access_speed_all_cpus, - NULL, "vec_check_unaligned_access_speed_all_cpus"))) { - pr_warn("Failed to create vec_unalign_check kthread\n"); - riscv_hwprobe_complete_async_probe(); - } + schedule_on_each_cpu(check_vector_unaligned_access); } /* @@ -436,4 +421,10 @@ static int __init check_unaligned_access_all_cpus(void) return 0; } -late_initcall(check_unaligned_access_all_cpus); +/* + * Run after clocksource_done_booting() so measure_cycles() uses a stable + * clocksource, but before rootfs_initcall() enables usermode helpers. Those + * helpers can reach hwprobe and populate the vDSO cache, so async hwprobe + * probes must be registered first. + */ +fs_initcall_sync(check_unaligned_access_all_cpus); diff --git a/arch/riscv/kernel/vdso/Makefile b/arch/riscv/kernel/vdso/Makefile index a842dc034571da..8dbf2532a57303 100644 --- a/arch/riscv/kernel/vdso/Makefile +++ b/arch/riscv/kernel/vdso/Makefile @@ -41,6 +41,8 @@ ccflags-y += $(CFI_FULL) asflags-y += $(KBUILD_BASE_ISA)$(CFI_MARCH) asflags-y += $(CFI_FULL) +ccflags-remove-y += $(CC_FLAGS_FTRACE) $(CC_FLAGS_SCS) $(CC_FLAGS_LTO) + ifneq ($(c-gettimeofday-y),) CFLAGS_vgettimeofday.o += -fPIC -include $(c-gettimeofday-y) endif @@ -68,11 +70,6 @@ ifneq ($(filter vgettimeofday, $(vdso-syms)),) CPPFLAGS_$(vdso_lds) += -DHAS_VGETTIMEOFDAY endif -# Disable -pg to prevent insert call site -CFLAGS_REMOVE_vgettimeofday.o = $(CC_FLAGS_FTRACE) $(CC_FLAGS_SCS) -CFLAGS_REMOVE_getrandom.o = $(CC_FLAGS_FTRACE) $(CC_FLAGS_SCS) -CFLAGS_REMOVE_hwprobe.o = $(CC_FLAGS_FTRACE) $(CC_FLAGS_SCS) - # Force dependency $(obj)/$(vdso_o): $(obj)/$(vdso_so) diff --git a/arch/riscv/kernel/vdso/hwprobe.c b/arch/riscv/kernel/vdso/hwprobe.c index 8f45500d0a6e76..2ddeba6c68dda0 100644 --- a/arch/riscv/kernel/vdso/hwprobe.c +++ b/arch/riscv/kernel/vdso/hwprobe.c @@ -27,7 +27,7 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, * homogeneous, then this function can handle requests for arbitrary * masks. */ - if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || unlikely(!avd->ready)) + if ((flags != 0) || (!all_cpus && !avd->homogeneous_cpus)) return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags); /* This is something we can handle, fill out the pairs. */ diff --git a/arch/riscv/kernel/vdso/note.S b/arch/riscv/kernel/vdso/note.S index 3d92cc956b95b9..69bfe48be037a2 100644 --- a/arch/riscv/kernel/vdso/note.S +++ b/arch/riscv/kernel/vdso/note.S @@ -4,6 +4,7 @@ * Here we can supply some information useful to userland. */ +#include #include #include #include @@ -12,4 +13,6 @@ ELFNOTE_START(Linux, 0, "a") .long LINUX_VERSION_CODE ELFNOTE_END +BUILD_SALT + emit_riscv_feature_1_and diff --git a/arch/riscv/kernel/vdso/rt_sigreturn.S b/arch/riscv/kernel/vdso/rt_sigreturn.S index e82987dc37394b..d6f96b1abe409b 100644 --- a/arch/riscv/kernel/vdso/rt_sigreturn.S +++ b/arch/riscv/kernel/vdso/rt_sigreturn.S @@ -7,11 +7,19 @@ #include #include +/* + * WARNING: Do NOT add a CFI landing pad at the start of this function. + * Unwinders such as libgcc identify the sigreturn trampoline by matching the + * instruction sequence. Adding a landing pad here would break unwinding from + * signal handlers. + * + * This trampoline is used only for signal return and not via an indirect + * call/jump from userspace, so adding CFI landing pad is unnecessary. + */ .text SYM_FUNC_START(__vdso_rt_sigreturn) .cfi_startproc .cfi_signal_frame - vdso_lpad li a7, __NR_rt_sigreturn ecall .cfi_endproc diff --git a/arch/riscv/kernel/vdso/vdso.lds.S b/arch/riscv/kernel/vdso/vdso.lds.S index c29ef12a63bb43..5e9424df932b70 100644 --- a/arch/riscv/kernel/vdso/vdso.lds.S +++ b/arch/riscv/kernel/vdso/vdso.lds.S @@ -45,9 +45,11 @@ SECTIONS .text : { *(.text .text.*) } :text . = ALIGN(4); + __vdso_alternatives_start = .; .alternative : { *(.alternative) } + __vdso_alternatives_end = .; } /* diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c index 2d3def024270c0..b5873bca9dcec6 100644 --- a/arch/riscv/kvm/mmu.c +++ b/arch/riscv/kvm/mmu.c @@ -142,9 +142,8 @@ void kvm_arch_commit_memory_region(struct kvm *kvm, enum kvm_mr_change change) { /* - * At this point memslot has been committed and there is an - * allocated dirty_bitmap[], dirty pages will be tracked while - * the memory slot is write protected. + * At this point memslot has been committed and dirty pages will be + * tracked while the memory slot is write protected. */ if (change != KVM_MR_DELETE && new->flags & KVM_MEM_LOG_DIRTY_PAGES) { if (kvm_dirty_log_manual_protect_and_init_set(kvm)) @@ -433,8 +432,8 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, struct vm_area_struct *vma; struct kvm *kvm = vcpu->kvm; struct kvm_mmu_memory_cache *pcache = &vcpu->arch.mmu_page_cache; - bool logging = (memslot->dirty_bitmap && - !(memslot->flags & KVM_MEM_READONLY)) ? true : false; + bool logging = kvm_slot_dirty_track_enabled(memslot) && + !(memslot->flags & KVM_MEM_READONLY); unsigned long vma_pagesize, mmu_seq; struct kvm_gstage gstage; struct page *page; diff --git a/arch/riscv/lib/Makefile b/arch/riscv/lib/Makefile index 6f767b2a349d76..01c53576be98dd 100644 --- a/arch/riscv/lib/Makefile +++ b/arch/riscv/lib/Makefile @@ -14,6 +14,7 @@ endif lib-y += csum.o ifeq ($(CONFIG_MMU), y) lib-$(CONFIG_RISCV_ISA_V) += uaccess_vector.o +lib-$(CONFIG_RISCV_ISA_V) += uaccess_cache_check.o endif lib-$(CONFIG_MMU) += uaccess.o lib-$(CONFIG_64BIT) += tishift.o diff --git a/arch/riscv/lib/riscv_v_helpers.c b/arch/riscv/lib/riscv_v_helpers.c index 7bbdfc6d4552d4..7ab2cea280f8bb 100644 --- a/arch/riscv/lib/riscv_v_helpers.c +++ b/arch/riscv/lib/riscv_v_helpers.c @@ -8,6 +8,7 @@ #include #include +#include #ifdef CONFIG_MMU #include @@ -28,6 +29,10 @@ asmlinkage int enter_vector_usercopy(void *dst, void *src, size_t n, if (!may_use_simd()) goto fallback; + /* HACK */ + if (!is_cacheable_safe(dst) || !is_cacheable_safe(src)) + goto fallback; + kernel_vector_begin(); remain = enable_sum ? __asm_vector_usercopy(dst, src, n) : __asm_vector_usercopy_sum_enabled(dst, src, n); diff --git a/arch/riscv/lib/strnlen.S b/arch/riscv/lib/strnlen.S index 53afa7b5b314d9..fc5d578bbffe43 100644 --- a/arch/riscv/lib/strnlen.S +++ b/arch/riscv/lib/strnlen.S @@ -17,6 +17,7 @@ SYM_FUNC_START(strnlen) __ALTERNATIVE_CFG("nop", "j strnlen_zbb", 0, RISCV_ISA_EXT_ZBB, IS_ENABLED(CONFIG_RISCV_ISA_ZBB) && IS_ENABLED(CONFIG_TOOLCHAIN_HAS_ZBB)) +strnlen_generic: /* * Returns @@ -27,20 +28,24 @@ SYM_FUNC_START(strnlen) * a1 - Max length of string * * Clobbers - * t0, t1, t2 + * t0, t1 */ - addi t1, a0, -1 - add t2, a0, a1 + mv t1, a0 + 1: - addi t1, t1, 1 - beq t1, t2, 2f + beqz a1, 2f + addi a1, a1, -1 + lbu t0, 0(t1) - bnez t0, 1b + beqz t0, 2f + + addi t1, t1, 1 + j 1b + 2: sub a0, t1, a0 ret - /* * Variant of strnlen using the ZBB extension if available */ @@ -73,6 +78,16 @@ strnlen_zbb: /* If maxlen is 0, return 0. */ beqz a1, 3f + /* + * Fallback to generic implementation when count is large enough to + * cause address overflow in the ZBB optimized path, or when count + * equals SIZE_MAX where the minu instruction misbehaves. + */ + li t5, -1 + beq a1, t5, strnlen_generic /* count == SIZE_MAX */ + add t4, a0, a1 + bltu t4, a0, strnlen_generic /* a0 + a1 overflow */ + /* Number of irrelevant bytes in the first word. */ andi t2, a0, SZREG-1 @@ -83,8 +98,12 @@ strnlen_zbb: sub t3, t3, t2 slli t2, t2, 3 - /* Aligned boundary. */ - add t4, a0, a1 + /* + * Aligned boundary. Use the address of the last valid byte + * (s + count - 1) to avoid loading a word past the count + * boundary in the loop below. count == 0 is handled above. + */ + addi t4, t4, -1 andi t4, t4, -SZREG /* Get the first word. */ @@ -120,6 +139,9 @@ strnlen_zbb: bgtu t3, a0, 2f + /* All remaining bytes are in the first word, no loop needed. */ + bgeu t0, t4, 2f + /* Prepare for the word comparison loop. */ addi t2, t0, SZREG li t3, -1 diff --git a/arch/riscv/lib/uaccess.S b/arch/riscv/lib/uaccess.S index 4efea1b3326c8d..2d78007f10d015 100644 --- a/arch/riscv/lib/uaccess.S +++ b/arch/riscv/lib/uaccess.S @@ -70,12 +70,27 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled) add t0, a0, a2 /* - * Use byte copy only if too small. - * SZREG holds 4 for RV32 and 8 for RV64 + * For small copies below the large-copy threshold, use word-copy if + * both src and dst are naturally aligned. Unaligned or sub-word copies + * are left to the byte-copy tail. */ li a3, 9*SZREG-1 /* size must >= (word_copy stride + SZREG-1) */ + bgeu a2, a3, .Llarge_copy_user + li a3, SZREG bltu a2, a3, .Lbyte_copy_tail + or a3, a0, a1 + andi a3, a3, SZREG-1 + bnez a3, .Lbyte_copy_tail + andi t1, t0, ~(SZREG-1) +5: + fixup REG_L a5, 0(a1), 10f + addi a1, a1, SZREG + fixup REG_S a5, 0(a0), 10f + addi a0, a0, SZREG + bltu a0, t1, 5b + j .Lbyte_copy_tail +.Llarge_copy_user: /* * Copy first bytes until dst is aligned to word boundary. * a0 - start of dst diff --git a/arch/riscv/lib/uaccess_cache_check.c b/arch/riscv/lib/uaccess_cache_check.c new file mode 100644 index 00000000000000..0b0996fa2d371a --- /dev/null +++ b/arch/riscv/lib/uaccess_cache_check.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Memory cacheability check for RISC-V uaccess optimization + * + * This file provides a C function that can be called from assembly + * to determine if a buffer is cacheable before using vector instructions. + */ + +#include +#include +#include + +/** + * is_cacheable_safe - Check if memory buffer is cacheable + * @addr: Virtual address to check (kernel or user space) + * + * Returns: 1 if cacheable, 0 if non-cacheable + * + * This function is designed to be called from assembly code in uaccess.S + * to determine if vector instructions are safe to use for memory copy. + * + * Non-cacheable memory (device IO, DMA coherent buffers) should not use + * vector instructions as they may cause cache coherency issues. + * + * Handles both kernel and user space addresses safely: + * - Kernel direct mapping: Always cacheable + * - Kernel vmalloc: Check VM flags + * - User space: Check page table (most are cacheable) + * - ioremap/DMA: Non-cacheable + */ +int is_cacheable_safe(const void *addr) +{ + unsigned long vaddr = (unsigned long)addr; + + /* Kernel direct mapped memory - always cacheable */ + if (virt_addr_valid(addr)) + return 1; + + if (vaddr < TASK_SIZE) { + /* + * User space address, Determine it as a cacheable buffer, + * maybe not safe!! + */ + return 1; + } + + /* Check if it's a vmalloc region (kernel virtual address) */ + if (is_vmalloc_addr(addr)) { + struct vm_struct *vm; + + vm = find_vm_area(addr); + if (!vm) + return 0; + + /* Exclude ioremap and DMA coherent buffers */ + if (vm->flags & (VM_IOREMAP | VM_DMA_COHERENT)) + return 0; + + /* Normal vmalloc - cacheable */ + return 1; + } + + /* Unknown kernel region - assume non-cacheable for safety */ + return 0; +} diff --git a/arch/riscv/mm/init.c b/arch/riscv/mm/init.c index fa8d2f6f554b57..86b0527d993e2a 100644 --- a/arch/riscv/mm/init.c +++ b/arch/riscv/mm/init.c @@ -37,7 +37,9 @@ #include "../kernel/head.h" -u64 new_vmalloc[NR_CPUS / sizeof(u64) + 1]; +#if defined(CONFIG_64BIT) && defined(CONFIG_MMU) +DECLARE_BITMAP(new_valid_map_cpus, NR_CPUS); +#endif struct kernel_mapping kernel_map __ro_after_init; EXPORT_SYMBOL(kernel_map); @@ -162,7 +164,9 @@ static void print_vm_layout(void) { } void __init arch_mm_preinit(void) { - bool swiotlb = max_pfn > PFN_DOWN(dma32_phys_limit); + bool swiotlb = max_pfn > PFN_DOWN(dma32_phys_limit) && + memblock_start_of_DRAM() < dma32_phys_limit; + unsigned int swiotlb_flags = SWIOTLB_VERBOSE; #ifdef CONFIG_FLATMEM BUG_ON(!mem_map); #endif /* CONFIG_FLATMEM */ @@ -170,17 +174,21 @@ void __init arch_mm_preinit(void) if (IS_ENABLED(CONFIG_DMA_BOUNCE_UNALIGNED_KMALLOC) && !swiotlb && dma_cache_alignment != 1) { /* - * If no bouncing needed for ZONE_DMA, allocate 1MB swiotlb - * buffer per 1GB of RAM for kmalloc() bouncing on - * non-coherent platforms. + * No 32-bit DMA bouncing needed (either all DRAM is within + * the 32-bit limit, or it all starts above it), but + * non-coherent hardware still requires cache-line-aligned + * bounce buffers for kmalloc(). Use SWIOTLB_ANY so that the + * buffer can be allocated from high memory when DRAM starts + * above dma32_phys_limit. Allocate ~1 MB per 1 GB of RAM. */ unsigned long size = DIV_ROUND_UP(memblock_phys_mem_size(), 1024); swiotlb_adjust_size(min(swiotlb_size_or_default(), size)); swiotlb = true; + swiotlb_flags |= SWIOTLB_ANY; } - swiotlb_init(swiotlb, SWIOTLB_VERBOSE); + swiotlb_init(swiotlb, swiotlb_flags); print_vm_layout(); } @@ -295,8 +303,6 @@ static void __init setup_bootmem(void) */ if (!IS_ENABLED(CONFIG_BUILTIN_DTB)) memblock_reserve(dtb_early_pa, fdt_totalsize(dtb_early_va)); - - dma_contiguous_reserve(dma32_phys_limit); } #ifdef CONFIG_RELOCATABLE @@ -826,8 +832,9 @@ static __init void set_satp_mode(uintptr_t dtb_pa) { u64 identity_satp, hw_satp; uintptr_t set_satp_mode_pmd = ((unsigned long)set_satp_mode) & PMD_MASK; + u64 satp_mode_limit_fdt = __pi_set_satp_mode_from_fdt(dtb_pa); u64 satp_mode_limit = min_not_zero(__pi_set_satp_mode_from_cmdline(dtb_pa), - __pi_set_satp_mode_from_fdt(dtb_pa)); + satp_mode_limit_fdt); kernel_map.page_offset = PAGE_OFFSET_L5; @@ -839,6 +846,10 @@ static __init void set_satp_mode(uintptr_t dtb_pa) return; } + /* Skip probing if max SATP mode known from FDT */ + if (satp_mode_limit_fdt) + return; + create_p4d_mapping(early_p4d, set_satp_mode_pmd, (uintptr_t)early_pud, P4D_SIZE, PAGE_TABLE); @@ -1350,6 +1361,7 @@ void __init misc_mem_init(void) { early_memtest(min_low_pfn << PAGE_SHIFT, max_low_pfn << PAGE_SHIFT); arch_numa_init(); + dma_contiguous_reserve(dma32_phys_limit); #ifdef CONFIG_SPARSEMEM_VMEMMAP /* The entire VMEMMAP region has been populated. Flush TLB for this region */ local_flush_tlb_kernel_range(VMEMMAP_START, VMEMMAP_END); diff --git a/arch/riscv/mm/pgtable.c b/arch/riscv/mm/pgtable.c index 9c4427d0b1874e..98eed19ea70dee 100644 --- a/arch/riscv/mm/pgtable.c +++ b/arch/riscv/mm/pgtable.c @@ -5,23 +5,55 @@ #include #include +#define RISCV_PTE_ACCESS_FLAG_MASK (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC | \ + _PAGE_ACCESSED | _PAGE_DIRTY | \ + _PAGE_SOFT_DIRTY) + +static inline unsigned long riscv_pte_access_flags(unsigned long cur, + unsigned long entry) +{ + unsigned long pteval; + unsigned long preserved_flags; + + preserved_flags = _PAGE_ACCESSED | _PAGE_DIRTY | _PAGE_SOFT_DIRTY; + pteval = cur & ~RISCV_PTE_ACCESS_FLAG_MASK; + pteval |= entry & (RISCV_PTE_ACCESS_FLAG_MASK & ~preserved_flags); + pteval |= (cur | entry) & preserved_flags; + + return pteval; +} + int ptep_set_access_flags(struct vm_area_struct *vma, unsigned long address, pte_t *ptep, pte_t entry, int dirty) { + unsigned long old_pteval; + unsigned long new_pteval; + unsigned long prev_pteval; + bool changed; + + old_pteval = pte_val(ptep_get(ptep)); + do { + new_pteval = riscv_pte_access_flags(old_pteval, pte_val(entry)); + if (new_pteval == old_pteval) + break; + + prev_pteval = cmpxchg_relaxed(&pte_val(*ptep), old_pteval, + new_pteval); + if (prev_pteval == old_pteval) + break; + + old_pteval = prev_pteval; + } while (1); + + changed = old_pteval != new_pteval; if (riscv_has_extension_unlikely(RISCV_ISA_EXT_SVVPTC)) { - if (!pte_same(ptep_get(ptep), entry)) { - __set_pte_at(vma->vm_mm, ptep, entry); - /* Here only not svadu is impacted */ + if (changed) flush_tlb_page(vma, address); - return true; - } - return false; + return changed; } - if (!pte_same(ptep_get(ptep), entry)) - __set_pte_at(vma->vm_mm, ptep, entry); /* * update_mmu_cache will unconditionally execute, handling both * the case that the PTE changed and the spurious fault case. @@ -32,9 +64,23 @@ int ptep_set_access_flags(struct vm_area_struct *vma, bool ptep_test_and_clear_young(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) { - if (!pte_young(ptep_get(ptep))) - return false; - return test_and_clear_bit(_PAGE_ACCESSED_OFFSET, &pte_val(*ptep)); + unsigned long old_pteval; + unsigned long new_pteval; + unsigned long prev_pteval; + + old_pteval = pte_val(ptep_get(ptep)); + do { + if (!(old_pteval & _PAGE_ACCESSED)) + return false; + + new_pteval = pte_val(pte_mkold(__pte(old_pteval))); + prev_pteval = cmpxchg_relaxed(&pte_val(*ptep), old_pteval, + new_pteval); + if (prev_pteval == old_pteval) + return true; + + old_pteval = prev_pteval; + } while (1); } EXPORT_SYMBOL_GPL(ptep_test_and_clear_young); diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index ceba24f65ae3a8..07384e7718e7e1 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -1176,6 +1177,24 @@ static void __init mp_config_acpi_legacy_irqs(void) } } +static const struct dmi_system_id surface_quirk[] __initconst = { + { + .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, + { + .ident = "Microsoft Surface Laptop 4 (AMD 13\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") + }, + }, + {} +}; + /* * Parse IOAPIC related entries in MADT * returns 0 on success, < 0 on error @@ -1232,6 +1251,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, acpi_gbl_FADT.sci_interrupt); + if (dmi_check_system(surface_quirk)) { + pr_warn("Surface hack: Override irq 7\n"); + mp_override_legacy_irq(7, 3, 3, 7); + } + /* Fill in identity legacy mappings where no override */ mp_config_acpi_legacy_irqs(); diff --git a/arch/x86/kernel/cpu/centaur.c b/arch/x86/kernel/cpu/centaur.c index 81695da9c52421..3773784ba6a9d7 100644 --- a/arch/x86/kernel/cpu/centaur.c +++ b/arch/x86/kernel/cpu/centaur.c @@ -108,6 +108,29 @@ static void early_init_centaur(struct cpuinfo_x86 *c) } } +/* + * Zhaoxin C4600 (family 6, model 15) names itself as CentaurHauls, it claims + * X86_FEATURE_FSGSBASE support in CPUID, while executing any fsgsbase-related + * instructions on it results in #UD. + */ +static void fixup_zhaoxin_fsgsbase(struct cpuinfo_x86 *c) +{ + const char *name, *model_names[] = { + "C-QuadCore C4600" + }; + int i; + + for (i = 0; i < ARRAY_SIZE(model_names); i++) { + name = model_names[i]; + + if (!strncmp(c->x86_model_id, name, strlen(name))) { + pr_warn_once("CPU has broken FSGSBASE support\n"); + setup_clear_cpu_cap(X86_FEATURE_FSGSBASE); + return; + } + } +} + static void init_centaur(struct cpuinfo_x86 *c) { #ifdef CONFIG_X86_32 @@ -201,6 +224,8 @@ static void init_centaur(struct cpuinfo_x86 *c) set_cpu_cap(c, X86_FEATURE_LFENCE_RDTSC); #endif + fixup_zhaoxin_fsgsbase(c); + init_ia32_feat_ctl(c); } diff --git a/arch/x86/kernel/fpu/xstate.h b/arch/x86/kernel/fpu/xstate.h index 38a2862f09d35b..dbc20466507e37 100644 --- a/arch/x86/kernel/fpu/xstate.h +++ b/arch/x86/kernel/fpu/xstate.h @@ -93,6 +93,9 @@ static inline int update_pkru_in_sigframe(struct xregs_state __user *buf, u32 pk if (unlikely(!cpu_feature_enabled(X86_FEATURE_OSPKE))) return 0; + if (!cpu_feature_enabled(X86_FEATURE_XGETBV1)) + return 0; + /* Mark PKRU as in-use so that it is restored correctly. */ err = set_xfeature_in_sigframe(buf, XFEATURE_MASK_PKRU); if (err) @@ -306,7 +309,7 @@ static inline int xsave_to_user_sigframe(struct xregs_state __user *buf, u32 pkr int err; /* Optimize away writing unnecessary xfeatures: */ - if (fpu_state_size_dynamic()) + if (cpu_feature_enabled(X86_FEATURE_XGETBV1) && fpu_state_size_dynamic()) mask &= xfeatures_need_sigframe_write(); lmask = mask; diff --git a/arch/x86/kernel/reboot.c b/arch/x86/kernel/reboot.c index 0fed6d0d7e322c..ed427d8bd8f186 100644 --- a/arch/x86/kernel/reboot.c +++ b/arch/x86/kernel/reboot.c @@ -490,7 +490,46 @@ static const struct dmi_system_id reboot_dmi_table[] __initconst = { DMI_MATCH(DMI_PRODUCT_NAME, "VGN-Z540N"), }, }, - + { /* Handle problems with rebooting on the Latitude E6520. */ + .callback = set_pci_reboot, + .ident = "Dell Latitude E6520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude E6520"), + }, + }, + { /* Handle problems with rebooting on the OptiPlex 790. */ + .callback = set_pci_reboot, + .ident = "Dell OptiPlex 790", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex 790"), + }, + }, + { /* Handle problems with rebooting on the OptiPlex 990. */ + .callback = set_pci_reboot, + .ident = "Dell OptiPlex 990", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex 990"), + }, + }, + { /* Handle problems with rebooting on the Latitude E6220. */ + .callback = set_pci_reboot, + .ident = "Dell Latitude E6220", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude E6220"), + }, + }, + { /* Handle problems with rebooting on the OptiPlex 390. */ + .callback = set_pci_reboot, + .ident = "Dell OptiPlex 390", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex 390"), + }, + }, { } }; diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index d1b0affb844f05..2148e6a5ebccae 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -134,3 +134,4 @@ obj-$(CONFIG_ACPI_VIOT) += viot.o obj-$(CONFIG_RISCV) += riscv/ obj-$(CONFIG_X86) += x86/ +obj-$(CONFIG_LOONGARCH) += loongarch/ diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 386fc1abcbdcb5..e2259938e27892 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -570,7 +570,6 @@ static ssize_t dc_status_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(dc_status); static struct attribute *acpi_tad_attrs[] = { - &dev_attr_caps.attr, &dev_attr_ac_alarm.attr, &dev_attr_ac_policy.attr, &dev_attr_ac_status.attr, @@ -835,11 +834,6 @@ static int acpi_tad_probe(struct platform_device *pdev) return -ENODEV; } - if (!acpi_has_method(handle, "_PRW")) { - dev_info(dev, "Missing _PRW\n"); - caps &= ~(ACPI_TAD_AC_WAKE | ACPI_TAD_DC_WAKE); - } - if (!(caps & ACPI_TAD_AC_WAKE)) caps &= ~ACPI_TAD_DC_WAKE; diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 40f875b265a942..c53b0c4777a645 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -75,6 +75,12 @@ void acpi_lpss_init(void); static inline void acpi_lpss_init(void) {} #endif +#if IS_ENABLED(CONFIG_PWM_LOONGSON) +void acpi_ls7a_pwm_init(void); +#else +static inline void acpi_ls7a_pwm_init(void) {} +#endif + void acpi_apd_init(void); acpi_status acpi_hotplug_schedule(struct acpi_device *adev, u32 src); diff --git a/drivers/acpi/loongarch/Makefile b/drivers/acpi/loongarch/Makefile new file mode 100644 index 00000000000000..25f207f841222c --- /dev/null +++ b/drivers/acpi/loongarch/Makefile @@ -0,0 +1,3 @@ +ifdef CONFIG_PWM_LOONGSON +obj-y += acpi-ls7a-backlight.o +endif diff --git a/drivers/acpi/loongarch/acpi-ls7a-backlight.c b/drivers/acpi/loongarch/acpi-ls7a-backlight.c new file mode 100644 index 00000000000000..5363747cd3a7f0 --- /dev/null +++ b/drivers/acpi/loongarch/acpi-ls7a-backlight.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Xi Ruoyao + * + * ACPI support for devices using LG110 discrete graphics + * with LS7A PWM controlling the backlight. + */ + +#include +#include + +#include "../internal.h" + +static struct pwm_lookup ls7a_pwm_lookup[] = { + PWM_LOOKUP_WITH_MODULE("LOON0006:03", 0, NULL, "gsgpu_backlight", 0, + PWM_POLARITY_NORMAL, "pwm-loongson"), +}; + +static int acpi_ls7a_pwm_attach(struct acpi_device *adev, + const struct acpi_device_id *id) +{ + if (acpi_dev_uid_match(adev, 3)) + pwm_add_table(ls7a_pwm_lookup, ARRAY_SIZE(ls7a_pwm_lookup)); + + return 0; +} + +static const struct acpi_device_id acpi_ls7a_pwm_ids[] = { + { "LOON0006", 0 }, + { }, +}; + +static struct acpi_scan_handler ls7a_pwm_handler = { + .ids = acpi_ls7a_pwm_ids, + .attach = acpi_ls7a_pwm_attach, +}; + + +void __init acpi_ls7a_pwm_init(void) +{ + acpi_scan_add_handler(&ls7a_pwm_handler); +} diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index a0ba64e45e8ab3..4fca040a52350f 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -853,7 +853,7 @@ static void acpi_pci_root_validate_resources(struct device *dev, } } -static void acpi_pci_root_remap_iospace(const struct fwnode_handle *fwnode, +static void acpi_pci_root_remap_iospace(struct acpi_device *device, struct resource_entry *entry) { #ifdef PCI_IOBASE @@ -863,7 +863,15 @@ static void acpi_pci_root_remap_iospace(const struct fwnode_handle *fwnode, resource_size_t length = resource_size(res); unsigned long port; - if (pci_register_io_range(fwnode, cpu_addr, length)) + if (!PAGE_ALIGNED(cpu_addr) || !PAGE_ALIGNED(length) || + !PAGE_ALIGNED(pci_addr)) { + dev_err(&device->dev, + FW_BUG "I/O resource %pR or its offset %pa is not page aligned\n", + res, &entry->offset); + goto err; + } + + if (pci_register_io_range(&device->fwnode, cpu_addr, length)) goto err; port = pci_address_to_pio(cpu_addr); @@ -904,8 +912,9 @@ int acpi_pci_probe_root_resources(struct acpi_pci_root_info *info) "no IO and memory resources present in _CRS\n"); else { resource_list_for_each_entry_safe(entry, tmp, list) { + acpi_arch_pci_probe_root_dev_filter(entry); if (entry->res->flags & IORESOURCE_IO) - acpi_pci_root_remap_iospace(&device->fwnode, + acpi_pci_root_remap_iospace(device, entry); if (entry->res->flags & IORESOURCE_DISABLED) diff --git a/drivers/acpi/riscv/cppc.c b/drivers/acpi/riscv/cppc.c index 42c1a90524707f..41c23db0223a20 100644 --- a/drivers/acpi/riscv/cppc.c +++ b/drivers/acpi/riscv/cppc.c @@ -65,24 +65,19 @@ static void sbi_cppc_write(void *write_data) static void cppc_ffh_csr_read(void *read_data) { struct sbi_cppc_data *data = (struct sbi_cppc_data *)read_data; + int err; - switch (data->reg) { - /* Support only TIME CSR for now */ - case CSR_TIME: - data->ret.value = csr_read(CSR_TIME); - data->ret.error = 0; - break; - default: - data->ret.error = -EINVAL; - break; - } + data->ret.value = csr_read_num(data->reg, &err); + data->ret.error = err; } static void cppc_ffh_csr_write(void *write_data) { struct sbi_cppc_data *data = (struct sbi_cppc_data *)write_data; + int err; - data->ret.error = -EINVAL; + csr_write_num(data->reg, data->val, &err); + data->ret.error = err; } /* @@ -98,16 +93,19 @@ int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val) { struct sbi_cppc_data data; - if (WARN_ON_ONCE(irqs_disabled())) - return -EPERM; - if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_SBI) { if (!cppc_ext_present) return -EINVAL; data.reg = FFH_CPPC_SBI_REG(reg->address); - smp_call_function_single(cpu, sbi_cppc_read, &data, 1); + if (irqs_disabled()) { + if (WARN_ON_ONCE(cpu != smp_processor_id())) + return -EPERM; + sbi_cppc_read(&data); + } else { + smp_call_function_single(cpu, sbi_cppc_read, &data, 1); + } *val = data.ret.value; @@ -115,7 +113,13 @@ int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val) } else if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_CSR) { data.reg = FFH_CPPC_CSR_NUM(reg->address); - smp_call_function_single(cpu, cppc_ffh_csr_read, &data, 1); + if (irqs_disabled()) { + if (WARN_ON_ONCE(cpu != smp_processor_id())) + return -EPERM; + cppc_ffh_csr_read(&data); + } else { + smp_call_function_single(cpu, cppc_ffh_csr_read, &data, 1); + } *val = data.ret.value; diff --git a/drivers/acpi/riscv/rimt.c b/drivers/acpi/riscv/rimt.c index 906282b0e63c0c..e4538fa6c2c822 100644 --- a/drivers/acpi/riscv/rimt.c +++ b/drivers/acpi/riscv/rimt.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -257,11 +258,11 @@ static int rimt_iommu_xlate(struct device *dev, struct acpi_rimt_node *node, u32 rimt_fwnode = rimt_get_fwnode(node); /* - * The IOMMU drivers may not be probed yet. - * Defer the IOMMU configuration + * The IOMMU drivers may not be probed yet. Defer the IOMMU + * configuration if it's still in initialization stage. */ if (!rimt_fwnode) - return -EPROBE_DEFER; + return driver_deferred_probe_check_state(dev); /* * EPROBE_DEFER ensures IOMMU is probed before the devices that diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 530547cda8b286..d810aa6137440f 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -861,6 +861,8 @@ static const char * const acpi_honor_dep_ids[] = { "INTC1095", /* IVSC (ADL) driver must be loaded to allow i2c access to camera sensors */ "INTC100A", /* IVSC (RPL) driver must be loaded to allow i2c access to camera sensors */ "INTC10CF", /* IVSC (MTL) driver must be loaded to allow i2c access to camera sensors */ + "INTC10DE", /* IVSC (LNL) driver must be loaded to allow i2c access to camera sensors */ + "INTC10E0", /* IVSC (ARL) driver must be loaded to allow i2c access to camera sensors */ "RSCV0001", /* RISC-V PLIC */ "RSCV0002", /* RISC-V APLIC */ "RSCV0005", /* RISC-V SBI MPXY MBOX */ @@ -2025,7 +2027,7 @@ int acpi_scan_add_dep(acpi_handle handle, struct acpi_handle_list *dep_devices) honor_dep = acpi_info_matches_ids(info, acpi_honor_dep_ids); kfree(info); - if (skip) + if (skip && !honor_dep) continue; dep = kzalloc_obj(*dep); @@ -2244,6 +2246,9 @@ static const char * const acpi_system_dev_ids[] = { static void acpi_default_enumeration(struct acpi_device *device) { + if (!acpi_dev_ready_for_enumeration(device)) + return; + /* * Do not enumerate devices with enumeration_by_parent flag set as * they will be enumerated by their respective parents. @@ -2834,6 +2839,7 @@ void __init acpi_scan_init(void) acpi_pnp_init(); acpi_power_resources_init(); acpi_init_lpit(); + acpi_ls7a_pwm_init(); acpi_scan_add_handler(&generic_device_handler); diff --git a/drivers/acpi/tables.c b/drivers/acpi/tables.c index 4286e4af10924a..50ef860659dfa4 100644 --- a/drivers/acpi/tables.c +++ b/drivers/acpi/tables.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "internal.h" #ifdef CONFIG_ACPI_CUSTOM_DSDT @@ -693,6 +694,7 @@ acpi_status acpi_os_table_override(struct acpi_table_header *existing_table, *new_table = (struct acpi_table_header *)&dsdt_amlcode; } #endif + acpi_arch_os_table_override(existing_table, new_table); if (*new_table != NULL) acpi_table_taint(existing_table); return AE_OK; @@ -752,6 +754,7 @@ void __init acpi_table_init_complete(void) { acpi_table_initrd_scan(); check_multiple_madt(); + acpi_arch_table_init_complete(); } int __init acpi_table_init(void) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index cc6392f8d98cd2..8f6db6c76a248b 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -67,6 +67,7 @@ static struct usb_driver btusb_driver; #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) #define BTUSB_ACTIONS_SEMI BIT(27) #define BTUSB_BARROT BIT(28) +#define BTUSB_LOWER_LESCAN_INTERVAL BIT(29) static const struct usb_device_id btusb_table[] = { /* Generic Bluetooth USB device */ @@ -472,6 +473,7 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, /* Intel Bluetooth devices */ { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, @@ -4248,6 +4250,19 @@ static int btusb_probe(struct usb_interface *intf, if (id->driver_info & BTUSB_MARVELL) hdev->set_bdaddr = btusb_set_bdaddr_marvell; + /* The Marvell 88W8897 combined wifi and bluetooth card is known for + * very bad bt+wifi coexisting performance. + * + * Decrease the passive BT Low Energy scan interval a bit + * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter + * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly + * higher wifi throughput while passively scanning for BT LE devices. + */ + if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { + hdev->le_scan_interval = 0x0190; + hdev->le_scan_window = 0x000a; + } + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && (id->driver_info & BTUSB_MEDIATEK)) { hdev->setup = btusb_mtk_setup; diff --git a/drivers/clk/spacemit/ccu-k3.c b/drivers/clk/spacemit/ccu-k3.c index bb8b75bdbdb30d..972499084d3adf 100644 --- a/drivers/clk/spacemit/ccu-k3.c +++ b/drivers/clk/spacemit/ccu-k3.c @@ -218,11 +218,19 @@ static const struct clk_parent_data i2s_sysclk_src_parents[] = { CCU_PARENT_HW(pll1_d96_25p6), CCU_PARENT_HW(i2s_153p6_base), }; -CCU_MUX_GATE_DEFINE(i2s_sysclk_src, i2s_sysclk_src_parents, MPMU_ISCCR, 30, 1, BIT(31), 0); +CCU_MUX_GATE_DEFINE(i2s_sysclk_src, i2s_sysclk_src_parents, MPMU_ISCCR1, 30, 1, BIT(31), 0); -CCU_DDN_DEFINE(i2s1_sysclk, i2s_sysclk_src, MPMU_ISCCR, 0, 15, 15, 12, 1, 0); +CCU_DDN_DEFINE(i2s_sysclk, i2s_sysclk_src, MPMU_ISCCR1, 0, 15, 15, 12, 1, 0); -CCU_DIV_GATE_DEFINE(i2s_bclk, CCU_PARENT_HW(i2s1_sysclk), MPMU_ISCCR, 27, 2, BIT(29), 0); +CCU_FACTOR_DEFINE(i2s_bclk_factor, CCU_PARENT_HW(i2s_sysclk), 2, 1); +/* + * Divider of i2s_bclk always implies a 1/2 factor, which is + * described by i2s_bclk_factor. + */ +CCU_DIV_GATE_DEFINE(i2s_bclk, CCU_PARENT_HW(i2s_bclk_factor), MPMU_ISCCR1, 27, 2, BIT(29), 0); + +CCU_MUX_GATE_DEFINE(i2s1_sysclk_src, i2s_sysclk_src_parents, MPMU_ISCCR0, 30, 1, BIT(31), 0); +CCU_DDN_DEFINE(i2s1_sysclk, i2s1_sysclk_src, MPMU_ISCCR0, 0, 15, 15, 12, 1, 0); static const struct clk_parent_data i2s_sysclk_parents[] = { CCU_PARENT_HW(pll1_d4_614p4), @@ -236,14 +244,14 @@ CCU_MUX_DEFINE(i2s3_sysclk_sel, i2s_sysclk_parents, MPMU_I2S_SYSCLK_CTRL, 12, 2, CCU_MUX_DEFINE(i2s4_sysclk_sel, i2s_sysclk_parents, MPMU_I2S_SYSCLK_CTRL, 16, 2, 0); CCU_MUX_DEFINE(i2s5_sysclk_sel, i2s_sysclk_parents, MPMU_I2S_SYSCLK_CTRL, 20, 2, 0); -CCU_DDN_DEFINE(i2s0_sysclk_div, i2s0_sysclk_sel, MPMU_I2S0_SYSCLK, 0, 16, 16, 16, 1, 0); -CCU_DDN_DEFINE(i2s2_sysclk_div, i2s2_sysclk_sel, MPMU_I2S2_SYSCLK, 0, 16, 16, 16, 1, 0); -CCU_DDN_DEFINE(i2s3_sysclk_div, i2s3_sysclk_sel, MPMU_I2S3_SYSCLK, 0, 16, 16, 16, 1, 0); -CCU_DDN_DEFINE(i2s4_sysclk_div, i2s4_sysclk_sel, MPMU_I2S4_SYSCLK, 0, 16, 16, 16, 1, 0); -CCU_DDN_DEFINE(i2s5_sysclk_div, i2s5_sysclk_sel, MPMU_I2S5_SYSCLK, 0, 16, 16, 16, 1, 0); +CCU_DDN_DEFINE(i2s0_sysclk_div, i2s0_sysclk_sel, MPMU_I2S0_SYSCLK, 0, 16, 16, 16, 2, 0); +CCU_DDN_DEFINE(i2s2_sysclk_div, i2s2_sysclk_sel, MPMU_I2S2_SYSCLK, 0, 16, 16, 16, 2, 0); +CCU_DDN_DEFINE(i2s3_sysclk_div, i2s3_sysclk_sel, MPMU_I2S3_SYSCLK, 0, 16, 16, 16, 2, 0); +CCU_DDN_DEFINE(i2s4_sysclk_div, i2s4_sysclk_sel, MPMU_I2S4_SYSCLK, 0, 16, 16, 16, 2, 0); +CCU_DDN_DEFINE(i2s5_sysclk_div, i2s5_sysclk_sel, MPMU_I2S5_SYSCLK, 0, 16, 16, 16, 2, 0); static const struct clk_parent_data i2s2_sysclk_parents[] = { - CCU_PARENT_HW(i2s1_sysclk), + CCU_PARENT_HW(i2s_sysclk), CCU_PARENT_HW(i2s2_sysclk_div), }; CCU_GATE_DEFINE(i2s0_sysclk, CCU_PARENT_HW(i2s0_sysclk_div), MPMU_I2S_SYSCLK_CTRL, BIT(2), 0); @@ -777,7 +785,7 @@ static const struct clk_parent_data sdh2_parents[] = { CCU_MUX_DIV_GATE_FC_DEFINE(sdh2_clk, sdh2_parents, APMU_SDH2_CLK_RES_CTRL, 8, 3, BIT(11), 5, 3, BIT(4), 0); -CCU_GATE_DEFINE(usb2_bus_clk, CCU_PARENT_HW(axi_clk), APMU_USB_CLK_RES_CTRL, BIT(0), 0); +CCU_GATE_DEFINE(usb2_bus_clk, CCU_PARENT_HW(axi_clk), APMU_USB_CLK_RES_CTRL, BIT(1), 0); CCU_GATE_DEFINE(usb3_porta_bus_clk, CCU_PARENT_HW(axi_clk), APMU_USB_CLK_RES_CTRL, BIT(4), 0); CCU_GATE_DEFINE(usb3_portb_bus_clk, CCU_PARENT_HW(axi_clk), APMU_USB_CLK_RES_CTRL, BIT(8), 0); CCU_GATE_DEFINE(usb3_portc_bus_clk, CCU_PARENT_HW(axi_clk), APMU_USB_CLK_RES_CTRL, BIT(12), 0); @@ -866,7 +874,7 @@ static const struct clk_parent_data rcpu_clk_parents[] = { CCU_PARENT_HW(pll1_d6_409p6), }; CCU_MUX_DIV_GATE_FC_DEFINE(rcpu_clk, rcpu_clk_parents, APMU_RCPU_CLK_RES_CTRL, - 4, 3, BIT(15), 7, 3, BIT(12), 0); + 4, 3, BIT(15), 7, 3, BIT(12), CLK_IS_CRITICAL); static const struct clk_parent_data dsi4ln2_dsi_esc_parents[] = { CCU_PARENT_HW(pll1_d48_51p2_ap), @@ -926,15 +934,16 @@ CCU_MUX_DIV_GATE_FC_DEFINE(dpu_aclk, dpu_aclk_parents, APMU_LCD_CLK_RES_CTRL5, 1 20, 3, BIT(16), 0); static const struct clk_parent_data ufs_aclk_parents[] = { - CCU_PARENT_HW(pll1_d6_409p6), CCU_PARENT_HW(pll1_d5_491p52), - CCU_PARENT_HW(pll1_d4_614p4), - CCU_PARENT_HW(pll1_d8_307p2), - CCU_PARENT_HW(pll2_d4), + CCU_PARENT_HW(pll1_d6_409p6), + CCU_PARENT_HW(pll2_d6), + CCU_PARENT_HW(pll2_d5), }; CCU_MUX_DIV_GATE_FC_DEFINE(ufs_aclk, ufs_aclk_parents, APMU_UFS_CLK_RES_CTRL, 5, 3, BIT(8), 2, 3, BIT(1), 0); +CCU_FACTOR_DEFINE(ufs_refclk, CCU_PARENT_HW(pll1_d64_38p4), 2, 1); + static const struct clk_parent_data edp0_pclk_parents[] = { CCU_PARENT_HW(lcd_pxclk), CCU_PARENT_NAME(external_clk), @@ -947,16 +956,21 @@ static const struct clk_parent_data edp1_pclk_parents[] = { }; CCU_MUX_GATE_DEFINE(edp1_pxclk, edp1_pclk_parents, APMU_LCD_EDP_CTRL, 18, 1, BIT(17), 0); -CCU_GATE_DEFINE(pciea_mstr_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_A, BIT(2), 0); -CCU_GATE_DEFINE(pciea_slv_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_A, BIT(1), 0); -CCU_GATE_DEFINE(pcieb_mstr_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_B, BIT(2), 0); -CCU_GATE_DEFINE(pcieb_slv_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_B, BIT(1), 0); -CCU_GATE_DEFINE(pciec_mstr_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_C, BIT(2), 0); -CCU_GATE_DEFINE(pciec_slv_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_C, BIT(1), 0); -CCU_GATE_DEFINE(pcied_mstr_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_D, BIT(2), 0); -CCU_GATE_DEFINE(pcied_slv_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_D, BIT(1), 0); -CCU_GATE_DEFINE(pciee_mstr_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_E, BIT(2), 0); -CCU_GATE_DEFINE(pciee_slv_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_E, BIT(1), 0); +CCU_GATE_DEFINE(pciea_mstr_clk, CCU_PARENT_HW(pll2_d6), APMU_PCIE_CLK_RES_CTRL_A, BIT(2), 0); +CCU_GATE_DEFINE(pciea_slv_clk, CCU_PARENT_HW(pll2_d6), APMU_PCIE_CLK_RES_CTRL_A, BIT(1), 0); +CCU_GATE_DEFINE(pciea_dbi_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_A, BIT(0), 0); +CCU_GATE_DEFINE(pcieb_mstr_clk, CCU_PARENT_HW(pll2_d6), APMU_PCIE_CLK_RES_CTRL_B, BIT(2), 0); +CCU_GATE_DEFINE(pcieb_slv_clk, CCU_PARENT_HW(pll2_d6), APMU_PCIE_CLK_RES_CTRL_B, BIT(1), 0); +CCU_GATE_DEFINE(pcieb_dbi_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_B, BIT(0), 0); +CCU_GATE_DEFINE(pciec_mstr_clk, CCU_PARENT_HW(pll2_d6), APMU_PCIE_CLK_RES_CTRL_C, BIT(2), 0); +CCU_GATE_DEFINE(pciec_slv_clk, CCU_PARENT_HW(pll2_d6), APMU_PCIE_CLK_RES_CTRL_C, BIT(1), 0); +CCU_GATE_DEFINE(pciec_dbi_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_C, BIT(0), 0); +CCU_GATE_DEFINE(pcied_mstr_clk, CCU_PARENT_HW(pll2_d6), APMU_PCIE_CLK_RES_CTRL_D, BIT(2), 0); +CCU_GATE_DEFINE(pcied_slv_clk, CCU_PARENT_HW(pll2_d6), APMU_PCIE_CLK_RES_CTRL_D, BIT(1), 0); +CCU_GATE_DEFINE(pcied_dbi_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_D, BIT(0), 0); +CCU_GATE_DEFINE(pciee_mstr_clk, CCU_PARENT_HW(pll2_d6), APMU_PCIE_CLK_RES_CTRL_E, BIT(2), 0); +CCU_GATE_DEFINE(pciee_slv_clk, CCU_PARENT_HW(pll2_d6), APMU_PCIE_CLK_RES_CTRL_E, BIT(1), 0); +CCU_GATE_DEFINE(pciee_dbi_clk, CCU_PARENT_HW(axi_clk), APMU_PCIE_CLK_RES_CTRL_E, BIT(0), 0); static const struct clk_parent_data emac_1588_parents[] = { CCU_PARENT_NAME(vctcxo_24m), @@ -1021,7 +1035,7 @@ CCU_MUX_DIV_GATE_DEFINE(isim_vclk_out3, isim_vclk_parents, APMU_SNR_ISIM_VCLK_CT /* APMU clocks end */ /* DCIU clocks start */ -CCU_GATE_DEFINE(hdma_clk, CCU_PARENT_HW(axi_clk), DCIU_DMASYS_CLK_EN, BIT(0), 0); +CCU_GATE_DEFINE(hdma_clk, CCU_PARENT_HW(axi_clk), DCIU_DMASYS_CLK_EN, BIT(0), CLK_IS_CRITICAL); CCU_GATE_DEFINE(dma350_clk, CCU_PARENT_HW(axi_clk), DCIU_DMASYS_SDMA_CLK_EN, BIT(0), 0); CCU_GATE_DEFINE(c2_tcm_pipe_clk, CCU_PARENT_HW(axi_clk), DCIU_C2_TCM_PIPE_CLK, BIT(0), 0); CCU_GATE_DEFINE(c3_tcm_pipe_clk, CCU_PARENT_HW(axi_clk), DCIU_C3_TCM_PIPE_CLK, BIT(0), 0); @@ -1156,8 +1170,11 @@ static struct clk_hw *k3_ccu_mpmu_hws[] = { [CLK_MPMU_I2S_153P6] = &i2s_153p6.common.hw, [CLK_MPMU_I2S_153P6_BASE] = &i2s_153p6_base.common.hw, [CLK_MPMU_I2S_SYSCLK_SRC] = &i2s_sysclk_src.common.hw, - [CLK_MPMU_I2S1_SYSCLK] = &i2s1_sysclk.common.hw, + [CLK_MPMU_I2S_SYSCLK] = &i2s_sysclk.common.hw, + [CLK_MPMU_I2S_BCLK_FACTOR] = &i2s_bclk_factor.common.hw, [CLK_MPMU_I2S_BCLK] = &i2s_bclk.common.hw, + [CLK_MPMU_I2S1_SYSCLK_SRC] = &i2s1_sysclk_src.common.hw, + [CLK_MPMU_I2S1_SYSCLK] = &i2s1_sysclk.common.hw, [CLK_MPMU_I2S0_SYSCLK_SEL] = &i2s0_sysclk_sel.common.hw, [CLK_MPMU_I2S2_SYSCLK_SEL] = &i2s2_sysclk_sel.common.hw, [CLK_MPMU_I2S3_SYSCLK_SEL] = &i2s3_sysclk_sel.common.hw, @@ -1387,18 +1404,24 @@ static struct clk_hw *k3_ccu_apmu_hws[] = { [CLK_APMU_DSI4LN2_DPU_ACLK] = &dsi4ln2_dpu_aclk.common.hw, [CLK_APMU_DPU_ACLK] = &dpu_aclk.common.hw, [CLK_APMU_UFS_ACLK] = &ufs_aclk.common.hw, + [CLK_APMU_UFS_REFCLK] = &ufs_refclk.common.hw, [CLK_APMU_EDP0_PXCLK] = &edp0_pxclk.common.hw, [CLK_APMU_EDP1_PXCLK] = &edp1_pxclk.common.hw, [CLK_APMU_PCIE_PORTA_MSTE] = &pciea_mstr_clk.common.hw, [CLK_APMU_PCIE_PORTA_SLV] = &pciea_slv_clk.common.hw, + [CLK_APMU_PCIE_PORTA_DBI] = &pciea_dbi_clk.common.hw, [CLK_APMU_PCIE_PORTB_MSTE] = &pcieb_mstr_clk.common.hw, [CLK_APMU_PCIE_PORTB_SLV] = &pcieb_slv_clk.common.hw, + [CLK_APMU_PCIE_PORTB_DBI] = &pcieb_dbi_clk.common.hw, [CLK_APMU_PCIE_PORTC_MSTE] = &pciec_mstr_clk.common.hw, [CLK_APMU_PCIE_PORTC_SLV] = &pciec_slv_clk.common.hw, + [CLK_APMU_PCIE_PORTC_DBI] = &pciec_dbi_clk.common.hw, [CLK_APMU_PCIE_PORTD_MSTE] = &pcied_mstr_clk.common.hw, [CLK_APMU_PCIE_PORTD_SLV] = &pcied_slv_clk.common.hw, + [CLK_APMU_PCIE_PORTD_DBI] = &pcied_dbi_clk.common.hw, [CLK_APMU_PCIE_PORTE_MSTE] = &pciee_mstr_clk.common.hw, [CLK_APMU_PCIE_PORTE_SLV] = &pciee_slv_clk.common.hw, + [CLK_APMU_PCIE_PORTE_DBI] = &pciee_dbi_clk.common.hw, [CLK_APMU_EMAC0_BUS] = &emac0_bus_clk.common.hw, [CLK_APMU_EMAC0_REF] = &emac0_ref_clk.common.hw, [CLK_APMU_EMAC0_1588] = &emac0_1588_clk.common.hw, diff --git a/drivers/clk/thead/clk-th1520-ap.c b/drivers/clk/thead/clk-th1520-ap.c index 3a6847f1c950fc..24f785f0b329a7 100644 --- a/drivers/clk/thead/clk-th1520-ap.c +++ b/drivers/clk/thead/clk-th1520-ap.c @@ -1266,6 +1266,41 @@ static CCU_GATE(CLK_MIPIDSI1_PIXCLK, mipi_dsi1_pixclk, "mipi-dsi1-pixclk", static CCU_GATE(CLK_HDMI_PIXCLK, hdmi_pixclk, "hdmi-pixclk", video_pll_clk_pd, 0x4, 0, 0); +static struct clk_fixed_factor usb_suspend_div_clk = { + .div = 24, + .mult = 1, + .hw.init = CLK_HW_INIT_PARENTS_DATA("usb-suspend-div", + osc_24m_clk, + &clk_fixed_factor_ops, + 0), +}; + +static const struct clk_parent_data usb_suspend_parents[] = { + { .hw = &usb_suspend_div_clk.hw }, +}; + +static CCU_GATE(CLK_MISCSYS_ACLK, miscsys_aclk, "miscsys-aclk", axi_aclk_pd, + 0x0, 0, CLK_IS_CRITICAL); + +static const struct clk_parent_data miscsys_aclk_pd[] = { + { .hw = &miscsys_aclk.gate.hw }, +}; + +static CCU_GATE(CLK_USB, usb_clk, "usb", miscsys_aclk_pd, 0x4, 0, + CLK_IS_CRITICAL); +static CCU_GATE(CLK_USB_CTL_REF, usb_ctl_ref_clk, "usb-ctl-ref", osc_24m_clk, + 0x4, 1, 0); +static CCU_GATE(CLK_USB_PHY_REF, usb_phy_ref_clk, "usb-phy-ref", osc_24m_clk, + 0x4, 2, 0); +static CCU_GATE(CLK_USB_SUSPEND, usb_suspend_clk, "usb-suspend", + usb_suspend_parents, 0x4, 3, 0); +static CCU_GATE(CLK_EMMC, emmc_clk, "emmc", perisys_ahb_hclk_pd, 0x8, 0, + 0); +static CCU_GATE(CLK_SDIO0, sdio0_clk, "sdio0", perisys_ahb_hclk_pd, 0xc, 0, + 0); +static CCU_GATE(CLK_SDIO1, sdio1_clk, "sdio1", perisys_ahb_hclk_pd, 0x10, 0, + 0); + static CLK_FIXED_FACTOR_HW(gmac_pll_clk_100m, "gmac-pll-clk-100m", &gmac_pll_clk.common.hw, 10, 1, 0); @@ -1410,6 +1445,17 @@ static struct ccu_gate *th1520_vo_gate_clks[] = { &hdmi_pixclk }; +static struct ccu_gate *th1520_misc_gate_clks[] = { + &miscsys_aclk, + &usb_clk, + &usb_ctl_ref_clk, + &usb_phy_ref_clk, + &usb_suspend_clk, + &emmc_clk, + &sdio0_clk, + &sdio1_clk +}; + static const struct regmap_config th1520_clk_regmap_config = { .reg_bits = 32, .val_bits = 32, @@ -1451,6 +1497,14 @@ static const struct th1520_plat_data th1520_vo_platdata = { .nr_gate_clks = ARRAY_SIZE(th1520_vo_gate_clks), }; +static const struct th1520_plat_data th1520_misc_platdata = { + .th1520_gate_clks = th1520_misc_gate_clks, + + .nr_clks = CLK_SDIO1 + 1, + + .nr_gate_clks = ARRAY_SIZE(th1520_misc_gate_clks), +}; + /* * Maintain clock rate of c910_bus_clk below TH1520_C910_BUS_MAX_RATE (750MHz) * when its parent, c910_clk, changes the rate. @@ -1609,6 +1663,12 @@ static int th1520_clk_probe(struct platform_device *pdev) return ret; } + if (plat_data == &th1520_ap_platdata) { + ret = devm_clk_hw_register(dev, &usb_suspend_div_clk.hw); + if (ret) + return ret; + } + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, priv); if (ret) return ret; @@ -1625,6 +1685,10 @@ static const struct of_device_id th1520_clk_match[] = { .compatible = "thead,th1520-clk-vo", .data = &th1520_vo_platdata, }, + { + .compatible = "thead,th1520-clk-misc", + .data = &th1520_misc_platdata, + }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, th1520_clk_match); diff --git a/drivers/clocksource/timer-sun4i.c b/drivers/clocksource/timer-sun4i.c index 7bdcc60ad43cb5..c2d04ab7cf2d38 100644 --- a/drivers/clocksource/timer-sun4i.c +++ b/drivers/clocksource/timer-sun4i.c @@ -208,7 +208,7 @@ static int __init sun4i_timer_init(struct device_node *node) sun4i_timer_clear_interrupt(timer_of_base(&to)); clockevents_config_and_register(&to.clkevt, timer_of_rate(&to), - TIMER_SYNC_TICKS, 0xffffffff); + TIMER_SYNC_TICKS + 1, 0xffffffff); /* Enable timer0 interrupt */ val = readl(timer_of_base(&to) + TIMER_IRQ_EN_REG); diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index ff1204c666b197..26f81f4d56df85 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -81,6 +81,8 @@ static const struct of_device_id allowlist[] __initconst = { { .have_governor_per_policy = true, }, }, + { .compatible = "spacemit,k1", }, + { .compatible = "st-ericsson,u8500", }, { .compatible = "st-ericsson,u8540", }, { .compatible = "st-ericsson,u9500", }, diff --git a/drivers/cpufreq/loongson3_cpufreq.c b/drivers/cpufreq/loongson3_cpufreq.c index 1e8715ea1b771d..e5c01eae05db69 100644 --- a/drivers/cpufreq/loongson3_cpufreq.c +++ b/drivers/cpufreq/loongson3_cpufreq.c @@ -21,9 +21,9 @@ union smc_message { u32 value; struct { - u32 id : 4; + u32 id : 8; u32 info : 4; - u32 val : 16; + u32 val : 12; u32 cmd : 6; u32 extra : 1; u32 complete : 1; @@ -220,8 +220,9 @@ static inline int do_service_request(u32 id, u32 info, u32 cmd, u32 val, u32 ext static unsigned int loongson3_cpufreq_get(unsigned int cpu) { int ret; + unsigned int core = cpu_logical_map(cpu); - ret = do_service_request(cpu, FREQ_INFO_TYPE_FREQ, CMD_GET_FREQ_INFO, 0, 0); + ret = do_service_request(core, FREQ_INFO_TYPE_FREQ, CMD_GET_FREQ_INFO, 0, 0); return ret * KILO; } @@ -229,9 +230,9 @@ static unsigned int loongson3_cpufreq_get(unsigned int cpu) static int loongson3_cpufreq_target(struct cpufreq_policy *policy, unsigned int index) { int ret; + unsigned int core = cpu_logical_map(policy->cpu); - ret = do_service_request(cpu_data[policy->cpu].core, - FREQ_INFO_TYPE_LEVEL, CMD_SET_FREQ_INFO, index, 0); + ret = do_service_request(core, FREQ_INFO_TYPE_LEVEL, CMD_SET_FREQ_INFO, index, 0); return (ret >= 0) ? 0 : ret; } diff --git a/drivers/crypto/padlock-sha.c b/drivers/crypto/padlock-sha.c index 9214bbfc868f5d..5104f8adfa3417 100644 --- a/drivers/crypto/padlock-sha.c +++ b/drivers/crypto/padlock-sha.c @@ -329,7 +329,17 @@ static int __init padlock_init(void) struct shash_alg *sha1; struct shash_alg *sha256; - if (!x86_match_cpu(padlock_sha_ids) || !boot_cpu_has(X86_FEATURE_PHE_EN)) + /* On Zhaoxin KaiXian KX-6000/6000G series of processors, this crypto + * module causes a null pointer dereference. + * + * Zhaoxin submitted upstream an implementation update but does not + * specify a fix. Per our testing, blacklisting this module causes the + * null pointer dereference to go away. + * + * Link: https://lore.kernel.org/all/20250114121301.156359-1-TonyWWang-oc%40zhaoxin.com/ */ + if (!x86_match_cpu(padlock_sha_ids) || + !boot_cpu_has(X86_FEATURE_PHE_EN) || + (c->x86 == 7 && c->x86_model == 59)) return -ENODEV; /* diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c index d12e729ee12c56..386e85cd4882a3 100644 --- a/drivers/dma/mmp_pdma.c +++ b/drivers/dma/mmp_pdma.c @@ -51,7 +51,10 @@ #define DCSR_CMPST BIT(10) /* The Descriptor Compare Status */ #define DCSR_EORINTR BIT(9) /* The end of Receive */ -#define DRCMR(n) ((((n) < 64) ? 0x0100 : 0x1100) + (((n) & 0x3f) << 2)) +#define DRCMR_BASE 0x0100 +#define DRCMR_EXT_BASE_K3 0x1000 +#define DRCMR_EXT_BASE_DEFAULT 0x1100 +#define DRCMR_REQ_LIMIT 64 #define DRCMR_MAPVLD BIT(7) /* Map Valid (read / write) */ #define DRCMR_CHLNUM 0x1f /* mask for Channel Number (read / write) */ @@ -154,6 +157,7 @@ struct mmp_pdma_phy { * @run_bits: Control bits in DCSR register for channel start/stop * @dma_width: DMA addressing width in bits (32 or 64). Determines the * DMA mask capability of the controller hardware. + * @drcmr_ext_base: Base DRCMR address for extended requests */ struct mmp_pdma_ops { /* Hardware Register Operations */ @@ -174,6 +178,7 @@ struct mmp_pdma_ops { /* Controller Configuration */ u32 run_bits; u32 dma_width; + u32 drcmr_ext_base; }; struct mmp_pdma_device { @@ -195,6 +200,13 @@ struct mmp_pdma_device { #define to_mmp_pdma_dev(dmadev) \ container_of(dmadev, struct mmp_pdma_device, device) +static u32 mmp_pdma_get_drcmr(struct mmp_pdma_device *pdev, u32 drcmr) +{ + if (drcmr < DRCMR_REQ_LIMIT) + return DRCMR_BASE + (drcmr << 2); + return pdev->ops->drcmr_ext_base + ((drcmr - DRCMR_REQ_LIMIT) << 2); +} + /* For 32-bit PDMA */ static void write_next_addr_32(struct mmp_pdma_phy *phy, dma_addr_t addr) { @@ -301,7 +313,7 @@ static void enable_chan(struct mmp_pdma_phy *phy) pdev = to_mmp_pdma_dev(phy->vchan->chan.device); - reg = DRCMR(phy->vchan->drcmr); + reg = mmp_pdma_get_drcmr(pdev, phy->vchan->drcmr); writel(DRCMR_MAPVLD | phy->idx, phy->base + reg); dalgn = readl(phy->base + DALGN); @@ -437,7 +449,7 @@ static void mmp_pdma_free_phy(struct mmp_pdma_chan *pchan) return; /* clear the channel mapping in DRCMR */ - reg = DRCMR(pchan->drcmr); + reg = mmp_pdma_get_drcmr(pdev, pchan->drcmr); writel(0, pchan->phy->base + reg); spin_lock_irqsave(&pdev->phy_lock, flags); @@ -1179,6 +1191,7 @@ static const struct mmp_pdma_ops marvell_pdma_v1_ops = { .get_desc_dst_addr = get_desc_dst_addr_32, .run_bits = (DCSR_RUN), .dma_width = 32, + .drcmr_ext_base = DRCMR_EXT_BASE_DEFAULT, }; static const struct mmp_pdma_ops spacemit_k1_pdma_ops = { @@ -1192,6 +1205,21 @@ static const struct mmp_pdma_ops spacemit_k1_pdma_ops = { .get_desc_dst_addr = get_desc_dst_addr_64, .run_bits = (DCSR_RUN | DCSR_LPAEEN), .dma_width = 64, + .drcmr_ext_base = DRCMR_EXT_BASE_DEFAULT, +}; + +static const struct mmp_pdma_ops spacemit_k3_pdma_ops = { + .write_next_addr = write_next_addr_64, + .read_src_addr = read_src_addr_64, + .read_dst_addr = read_dst_addr_64, + .set_desc_next_addr = set_desc_next_addr_64, + .set_desc_src_addr = set_desc_src_addr_64, + .set_desc_dst_addr = set_desc_dst_addr_64, + .get_desc_src_addr = get_desc_src_addr_64, + .get_desc_dst_addr = get_desc_dst_addr_64, + .run_bits = (DCSR_RUN | DCSR_LPAEEN | DCSR_EORIRQEN | DCSR_EORSTOPEN), + .dma_width = 64, + .drcmr_ext_base = DRCMR_EXT_BASE_K3, }; static const struct of_device_id mmp_pdma_dt_ids[] = { @@ -1201,6 +1229,9 @@ static const struct of_device_id mmp_pdma_dt_ids[] = { }, { .compatible = "spacemit,k1-pdma", .data = &spacemit_k1_pdma_ops + }, { + .compatible = "spacemit,k3-pdma", + .data = &spacemit_k3_pdma_ops }, { /* sentinel */ } diff --git a/drivers/dma/qcom/gpi.c b/drivers/dma/qcom/gpi.c index c9a6f610ffd9fa..a5055a6273af62 100644 --- a/drivers/dma/qcom/gpi.c +++ b/drivers/dma/qcom/gpi.c @@ -2260,6 +2260,7 @@ static int gpi_probe(struct platform_device *pdev) /* clear and Set capabilities */ dma_cap_zero(gpi_dev->dma_device.cap_mask); dma_cap_set(DMA_SLAVE, gpi_dev->dma_device.cap_mask); + dma_cap_set(DMA_PRIVATE, gpi_dev->dma_device.cap_mask); /* configure dmaengine apis */ gpi_dev->dma_device.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); diff --git a/drivers/firmware/efi/libstub/loongarch.c b/drivers/firmware/efi/libstub/loongarch.c index 2b0c87dc990881..11a93c46a34497 100644 --- a/drivers/firmware/efi/libstub/loongarch.c +++ b/drivers/firmware/efi/libstub/loongarch.c @@ -44,6 +44,8 @@ struct exit_boot_struct { int runtime_entry_count; }; +static int is_oldworld = 0; + static efi_status_t exit_boot_func(struct efi_boot_memmap *map, void *priv) { struct exit_boot_struct *p = priv; @@ -59,6 +61,15 @@ static efi_status_t exit_boot_func(struct efi_boot_memmap *map, void *priv) return EFI_SUCCESS; } +static void detect_oldworld(void) +{ + is_oldworld = !!(csr_read64(LOONGARCH_CSR_DMWIN1) & CSR_DMW1_PLV0); + efi_debug("is_oldworld: %d\n", is_oldworld); + if(is_oldworld) { + efi_info("Booting on OldWorld firmware\n"); + } +} + unsigned long __weak kernel_entry_address(unsigned long kernel_addr, efi_loaded_image_t *image) { @@ -74,6 +85,8 @@ efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image, efi_status_t status; u32 desc_ver; + detect_oldworld(); + status = efi_alloc_virtmap(&priv.runtime_map, &desc_size, &desc_ver); if (status != EFI_SUCCESS) { efi_err("Unable to retrieve UEFI memory map.\n"); @@ -87,11 +100,16 @@ efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image, if (status != EFI_SUCCESS) return status; + if (is_oldworld) { + goto skip_set_virtual_address_map; + } + /* Install the new virtual address map */ efi_rt_call(set_virtual_address_map, priv.runtime_entry_count * desc_size, desc_size, desc_ver, priv.runtime_map); +skip_set_virtual_address_map: /* Config Direct Mapping */ csr_write(CSR_DMW0_INIT, LOONGARCH_CSR_DMWIN0); csr_write(CSR_DMW1_INIT, LOONGARCH_CSR_DMWIN1); diff --git a/drivers/firmware/sysfb.c b/drivers/firmware/sysfb.c index 8833582c188301..7d529d8bed5124 100644 --- a/drivers/firmware/sysfb.c +++ b/drivers/firmware/sysfb.c @@ -222,4 +222,4 @@ static __init int sysfb_init(void) } /* must execute after PCI subsystem for EFI quirks */ -device_initcall(sysfb_init); +fs_initcall(sysfb_init); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 020e51e30317a8..650ee3f44c672c 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1786,6 +1786,17 @@ endmenu menu "PCI GPIO expanders" depends on PCI +config GPIO_AAEON + tristate "AAEON GPIO support" + depends on ASUS_WMI + select MFD_AAEON + help + Say yes here to support GPIO pins on Single Board Computers produced + by AAEON. + + This driver leverages the ASUS WMI interface to access device + resources. + config GPIO_AMD8111 tristate "AMD 8111 GPIO driver" depends on X86 || COMPILE_TEST diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index b267598b517de0..d2ee444bebe31b 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_GPIO_104_IDI_48) += gpio-104-idi-48.o obj-$(CONFIG_GPIO_104_IDIO_16) += gpio-104-idio-16.o obj-$(CONFIG_GPIO_74X164) += gpio-74x164.o obj-$(CONFIG_GPIO_74XX_MMIO) += gpio-74xx-mmio.o +obj-$(CONFIG_GPIO_AAEON) += gpio-aaeon.o obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o obj-$(CONFIG_GPIO_ADP5585) += gpio-adp5585.o diff --git a/drivers/gpio/gpio-aaeon.c b/drivers/gpio/gpio-aaeon.c new file mode 100644 index 00000000000000..e57f41eed3ec0a --- /dev/null +++ b/drivers/gpio/gpio-aaeon.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AAEON GPIO driver + * Copyright (c) 2021, AAEON Ltd. + * + * Author: Edward Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "gpio_aaeon" +#define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C" +#define AAEON_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" + +#define GET_GPIO_NUMBER_ID 0x00010000 +#define GET_LEVEL_METHOD_ID 0x00010001 +#define SET_LEVEL_METHOD_ID 0x00010002 +#define GET_DIRECTION_METHOD_ID 0x00010003 +#define SET_DIRECTION_METHOD_ID 0x00010004 +#define GET_SIO_NUMBER_METHOD_ID 0xF0010 + +struct aaeon_gpio_bank { + struct gpio_chip chip; + unsigned int regbase; + struct aaeon_gpio_data *data; +}; + +struct aaeon_gpio_data { + int nr_bank; + struct aaeon_gpio_bank *bank; +}; + +static int aaeon_gpio_get_number(void); +static int aaeon_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset); +static int aaeon_gpio_output_set_direction(struct gpio_chip *chip, + unsigned int offset, int value); +static int aaeon_gpio_input_set_direction(struct gpio_chip *chip, + unsigned int offset); +static int aaeon_gpio_get(struct gpio_chip *chip, + unsigned int offset); +static int aaeon_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value); + +#define AAEON_GPIO_BANK(_base, _ngpio, _regbase) \ +{ \ + .chip = { \ + .label = DRVNAME, \ + .owner = THIS_MODULE, \ + .get_direction = aaeon_gpio_get_direction, \ + .direction_input = aaeon_gpio_input_set_direction, \ + .direction_output = aaeon_gpio_output_set_direction, \ + .get = aaeon_gpio_get, \ + .set = aaeon_gpio_set, \ + .base = _base, \ + .ngpio = _ngpio, \ + .can_sleep = true, \ + }, \ + .regbase = _regbase, \ +} + +static struct aaeon_gpio_bank aaeon_gpio_bank[] = { + AAEON_GPIO_BANK(0, 0, 0xF0), +}; + +static int aaeon_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + int err, retval; + u32 dev_id = 0x0; + + dev_id |= offset; + err = asus_wmi_evaluate_method(GET_DIRECTION_METHOD_ID, dev_id, + 0, &retval); + if (err) + return err; + + return retval; +} + +static int aaeon_gpio_input_set_direction(struct gpio_chip *chip, + unsigned int offset) +{ + int err, retval; + u32 dev_id; + + dev_id = BIT(16) | offset; + err = asus_wmi_evaluate_method(SET_DIRECTION_METHOD_ID, dev_id, + 0, &retval); + if (err) + return err; + + return retval; +} + +static int aaeon_gpio_output_set_direction(struct gpio_chip *chip, + unsigned int offset, int value) +{ + int err, retval; + u32 dev_id = 0x0; + + dev_id |= offset; + err = asus_wmi_evaluate_method(SET_DIRECTION_METHOD_ID, dev_id, + 0, &retval); + if (err) + return err; + + return retval; +} + +static int aaeon_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + int err, retval; + u32 dev_id = 0x0; + + dev_id |= offset; + err = asus_wmi_evaluate_method(GET_LEVEL_METHOD_ID, dev_id, 0, &retval); + if (err) + return err; + + return retval; +} + +static int aaeon_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + int retval; + u32 dev_id = offset; + + if (value) + dev_id = BIT(16) | dev_id; + + return asus_wmi_evaluate_method(SET_LEVEL_METHOD_ID, dev_id, 0, &retval); +} + +static int aaeon_gpio_get_number(void) +{ + int err, retval; + + err = asus_wmi_evaluate_method(GET_GPIO_NUMBER_ID, + GET_SIO_NUMBER_METHOD_ID, + 0, &retval); + if (err) + return err; + + return retval; +} + +static int __init aaeon_gpio_probe(struct platform_device *pdev) +{ + int err, i; + int dio_number = 0; + struct aaeon_gpio_data *data; + struct aaeon_gpio_bank *bank; + + /* Prevent other drivers adding this platfom device */ + if (!wmi_has_guid(AAEON_WMI_MGMT_GUID)) { + pr_debug("AAEON Management GUID not found\n"); + return -ENODEV; + } + + dio_number = aaeon_gpio_get_number(); + if (dio_number < 0) + return -ENODEV; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->nr_bank = ARRAY_SIZE(aaeon_gpio_bank); + data->bank = aaeon_gpio_bank; + platform_set_drvdata(pdev, data); + bank = &data->bank[0]; + bank->chip.parent = &pdev->dev; + bank->chip.ngpio = dio_number; + bank->data = data; + err = devm_gpiochip_add_data(&pdev->dev, &bank->chip, bank); + if (err) + pr_debug("Failed to register gpiochip %d: %d\n", i, err); + + return err; +} + +static struct platform_driver aaeon_gpio_driver = { + .driver = { + .name = "gpio-aaeon", + }, +}; + +module_platform_driver_probe(aaeon_gpio_driver, aaeon_gpio_probe); + +MODULE_ALIAS("platform:gpio-aaeon"); +MODULE_DESCRIPTION("AAEON GPIO Driver"); +MODULE_AUTHOR("Edward Lin "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("ASUS_WMI"); diff --git a/drivers/gpio/gpio-loongson-64bit.c b/drivers/gpio/gpio-loongson-64bit.c index 0fdf15faa344d2..bff7d70e7470ea 100644 --- a/drivers/gpio/gpio-loongson-64bit.c +++ b/drivers/gpio/gpio-loongson-64bit.c @@ -46,6 +46,8 @@ struct loongson_gpio_chip { spinlock_t lock; void __iomem *reg_base; const struct loongson_gpio_chip_data *chip_data; + u16 *gsi_idx_map; + int mapsize; }; static inline struct loongson_gpio_chip *to_loongson_gpio_chip(struct gpio_chip *chip) @@ -145,6 +147,12 @@ static int loongson_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) writeb(1, lgpio->reg_base + lgpio->chip_data->inten_offset + offset); } + if (lgpio->gsi_idx_map != NULL) { + if (offset >= lgpio->mapsize) + return -EINVAL; + offset = lgpio->gsi_idx_map[offset]; + } + return platform_get_irq(pdev, offset); } @@ -326,6 +334,23 @@ static int loongson_gpio_init(struct platform_device *pdev, struct loongson_gpio lgpio->chip.gc.to_irq = loongson_gpio_to_irq; } + lgpio->mapsize = device_property_read_u16_array(&pdev->dev, "gsi_idx_map", NULL, 0); + if (lgpio->mapsize > 0) { + lgpio->gsi_idx_map = devm_kcalloc(&pdev->dev, lgpio->mapsize, + sizeof(*lgpio->gsi_idx_map), GFP_KERNEL); + if (!lgpio->gsi_idx_map) + return -ENOMEM; + + ret = device_property_read_u16_array(&pdev->dev, "gsi_idx_map", + lgpio->gsi_idx_map, lgpio->mapsize); + if (ret != 0) + return dev_err_probe(&pdev->dev, ret, "failed to read gsi_idx_map\n"); + dev_warn(&pdev->dev, "gsi_idx_map property is deprecated, consider upgrading your firmware\n"); + } else { + lgpio->gsi_idx_map = NULL; + lgpio->mapsize = 0; + } + return devm_gpiochip_add_data(&pdev->dev, &lgpio->chip.gc, lgpio); } diff --git a/drivers/gpio/gpio-sifive.c b/drivers/gpio/gpio-sifive.c index 94ef2efbd14f57..e9a992cd4b9cdf 100644 --- a/drivers/gpio/gpio-sifive.c +++ b/drivers/gpio/gpio-sifive.c @@ -260,6 +260,7 @@ static const struct of_device_id sifive_gpio_match[] = { { .compatible = "sifive,fu540-c000-gpio" }, { }, }; +MODULE_DEVICE_TABLE(of, sifive_gpio_match); static struct platform_driver sifive_gpio_driver = { .probe = sifive_gpio_probe, diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index e97faabcd78300..16b042dc87a982 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -173,6 +173,7 @@ obj-y += clients/ obj-y += display/ obj-$(CONFIG_DRM_TTM) += ttm/ obj-$(CONFIG_DRM_SCHED) += scheduler/ +obj-y += tiny/ obj-$(CONFIG_DRM_RADEON)+= radeon/ obj-$(CONFIG_DRM_AMDGPU)+= amd/amdgpu/ obj-$(CONFIG_DRM_AMDGPU)+= amd/amdxcp/ @@ -216,7 +217,6 @@ obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/ obj-y += hisilicon/ obj-y += mxsfb/ obj-y += sysfb/ -obj-y += tiny/ obj-$(CONFIG_DRM_PL111) += pl111/ obj-$(CONFIG_DRM_TVE200) += tve200/ obj-$(CONFIG_DRM_ADP) += adp/ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index feab90e3efd1e2..5deffe2773085e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1309,6 +1309,14 @@ bool amdgpu_device_seamless_boot_supported(struct amdgpu_device *adev) */ static bool amdgpu_device_pcie_dynamic_switching_supported(struct amdgpu_device *adev) { + struct pci_dev *parent = adev->pdev; + static const struct pci_device_id broken_devids[] = { + /* SpacemiT K3 */ + { PCI_DEVICE(PCI_VENDOR_ID_SPACEMIT, + PCI_DEVICE_ID_SPACEMIT_K3) }, + {} + }; + #if IS_ENABLED(CONFIG_X86) struct cpuinfo_x86 *c = &cpu_data(0); @@ -1319,6 +1327,17 @@ static bool amdgpu_device_pcie_dynamic_switching_supported(struct amdgpu_device if (c->x86_vendor == X86_VENDOR_INTEL) return false; #endif + /* skip upstream/downstream switches internal to dGPU */ + while (parent && parent->vendor == PCI_VENDOR_ID_ATI) { + parent = pci_upstream_bridge(parent); + } + + if (!parent) + return true; + + if (pci_match_id(broken_devids, parent)) + return false; + return true; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 60debd543e44ee..53d196ef53a43c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -863,7 +863,8 @@ module_param_named(dcfeaturemask, amdgpu_dc_feature_mask, uint, 0444); * DOC: dcdebugmask (uint) * Display debug options. See enum DC_DEBUG_MASK in drivers/gpu/drm/amd/include/amd_shared.h. */ -MODULE_PARM_DESC(dcdebugmask, "all debug options disabled (default))"); +uint dcdebugmask = 0x610; +MODULE_PARM_DESC(dcdebugmask, "Disable debug options (default: disable Panel Self Refresh v1 and PSR-SU, or v2, as well as Panel Replay)"); module_param_named(dcdebugmask, amdgpu_dc_debug_mask, uint, 0444); MODULE_PARM_DESC(visualconfirm, "Visual confirm (0 = off (default), 1 = MPO, 5 = PSR)"); @@ -881,9 +882,9 @@ module_param_named(visualconfirm, amdgpu_dc_visual_confirm, uint, 0444); * Defaults to -1, or auto. Userspace can only override this level after * boot if it's set to auto. */ -int amdgpu_dm_abm_level = -1; +int amdgpu_dm_abm_level = 0; MODULE_PARM_DESC(abmlevel, - "ABM level (0 = off, 1-4 = backlight reduction level, -1 auto (default))"); + "ABM level (0 = off (default), 1-4 = backlight reduction level, -1 auto)"); module_param_named(abmlevel, amdgpu_dm_abm_level, int, 0444); int amdgpu_backlight = -1; diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c index 2b691452775bc4..083f9e8e1862e0 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c @@ -2117,6 +2117,13 @@ static void gfx_v7_0_ring_emit_fence_gfx(struct amdgpu_ring *ring, u64 addr, bool int_sel = flags & AMDGPU_FENCE_FLAG_INT; bool exec = flags & AMDGPU_FENCE_FLAG_EXEC; +/* This workaround causes instability for LoongArch/Loongson (MIPS) + * devices based on the 7A1000/2000 chipset under heavy I/O load. + * + * FIXME: Disable this workaround until we find a better fix (possibly in + * the platform-specific PCI code). + */ +#ifndef CONFIG_MACH_LOONGSON64 /* Workaround for cache flush problems. First send a dummy EOP * event down the pipe with seq one below. */ @@ -2130,6 +2137,7 @@ static void gfx_v7_0_ring_emit_fence_gfx(struct amdgpu_ring *ring, u64 addr, DATA_SEL(1) | INT_SEL(0)); amdgpu_ring_write(ring, lower_32_bits(seq - 1)); amdgpu_ring_write(ring, upper_32_bits(seq - 1)); +#endif /* Then send the real EOP event down the pipe. */ amdgpu_ring_write(ring, PACKET3(PACKET3_EVENT_WRITE_EOP, 4)); diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index a6b4c8f41dc116..892f239e092191 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -6139,6 +6139,13 @@ static void gfx_v8_0_ring_emit_fence_gfx(struct amdgpu_ring *ring, u64 addr, bool int_sel = flags & AMDGPU_FENCE_FLAG_INT; bool exec = flags & AMDGPU_FENCE_FLAG_EXEC; +/* This workaround causes instability for LoongArch/Loongson (MIPS) + * devices based on the 7A1000/2000 chipset under heavy I/O load. + * + * FIXME: Disable this workaround until we find a better fix (possibly in + * the platform-specific PCI code). + */ +#ifndef CONFIG_MACH_LOONGSON64 /* Workaround for cache flush problems. First send a dummy EOP * event down the pipe with seq one below. */ @@ -6153,6 +6160,7 @@ static void gfx_v8_0_ring_emit_fence_gfx(struct amdgpu_ring *ring, u64 addr, DATA_SEL(1) | INT_SEL(0)); amdgpu_ring_write(ring, lower_32_bits(seq - 1)); amdgpu_ring_write(ring, upper_32_bits(seq - 1)); +#endif /* Then send the real EOP event down the pipe: * EVENT_WRITE_EOP - flush caches, send int */ diff --git a/drivers/gpu/drm/amd/amdkfd/Kconfig b/drivers/gpu/drm/amd/amdkfd/Kconfig index a5d7467c2f3493..1c9e9529ca181b 100644 --- a/drivers/gpu/drm/amd/amdkfd/Kconfig +++ b/drivers/gpu/drm/amd/amdkfd/Kconfig @@ -14,7 +14,7 @@ config HSA_AMD config HSA_AMD_SVM bool "Enable HMM-based shared virtual memory manager" - depends on HSA_AMD && DEVICE_PRIVATE + depends on HSA_AMD && DEVICE_PRIVATE && !LOONGARCH && !ARM64 default y select HMM_MIRROR select MMU_NOTIFIER diff --git a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c index 389eead5f1c45f..6ec9003a8f3f7d 100644 --- a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c @@ -136,8 +136,8 @@ static int th1520_dw_hdmi_probe(struct platform_device *pdev) plat_data->priv_data = hdmi; hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); - if (IS_ERR(hdmi)) - return PTR_ERR(hdmi); + if (IS_ERR(hdmi->dw_hdmi)) + return PTR_ERR(hdmi->dw_hdmi); platform_set_drvdata(pdev, hdmi); @@ -146,9 +146,9 @@ static int th1520_dw_hdmi_probe(struct platform_device *pdev) static void th1520_dw_hdmi_remove(struct platform_device *pdev) { - struct dw_hdmi *hdmi = platform_get_drvdata(pdev); + struct th1520_hdmi *hdmi = platform_get_drvdata(pdev); - dw_hdmi_remove(hdmi); + dw_hdmi_remove(hdmi->dw_hdmi); } static const struct of_device_id th1520_dw_hdmi_of_table[] = { diff --git a/drivers/gpu/drm/drm_panel_orientation_quirks.c b/drivers/gpu/drm/drm_panel_orientation_quirks.c index 3a218fb592cef4..b1915c1f92da0c 100644 --- a/drivers/gpu/drm/drm_panel_orientation_quirks.c +++ b/drivers/gpu/drm/drm_panel_orientation_quirks.c @@ -179,6 +179,18 @@ static const struct dmi_system_id orientation_data[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Win600"), }, .driver_data = (void *)&lcd720x1280_rightside_up, + }, { /* AOKZOE A1 Pro */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AOKZOE"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AOKZOE A1 Pro"), + }, + .driver_data = (void *)&lcd1200x1920_leftside_up, + }, { /* AOKZOE A2 Pro */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AOKZOE"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AOKZOE A2 Pro"), + }, + .driver_data = (void *)&lcd1200x1920_leftside_up, }, { /* Asus T100HA */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), @@ -203,12 +215,24 @@ static const struct dmi_system_id orientation_data[] = { DMI_MATCH(DMI_PRODUCT_NAME, "AYANEO 2"), }, .driver_data = (void *)&lcd1200x1920_rightside_up, + }, { /* AYANEO 3 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AYANEO 3"), + }, + .driver_data = (void *)&lcd1080x1920_rightside_up, }, { /* AYA NEO 2021 */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYADEVICE"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AYA NEO 2021"), }, .driver_data = (void *)&lcd800x1280_rightside_up, + }, { /* AYA NEO 2021 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "NEO 2021"), + }, + .driver_data = (void *)&lcd800x1280_rightside_up, }, { /* AYA NEO AIR */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYANEO"), @@ -309,6 +333,12 @@ static const struct dmi_system_id orientation_data[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1619-01"), }, .driver_data = (void *)&lcd800x1280_rightside_up, + }, { /* GPD Win Max (2021) */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1619-03"), + }, + .driver_data = (void *)&lcd800x1280_rightside_up, }, { /* * GPD Pocket, note that the DMI data is less generic then * it seems, devices with a board-vendor of "AMI Corporation" @@ -364,6 +394,12 @@ static const struct dmi_system_id orientation_data[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1617-01") }, .driver_data = (void *)&lcd1080x1920_rightside_up, + }, { /* GPD Pocket 4 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1628-04"), + }, + .driver_data = (void *)&lcd1600x2560_rightside_up, }, { /* I.T.Works TW891 */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."), @@ -461,6 +497,12 @@ static const struct dmi_system_id orientation_data[] = { DMI_MATCH(DMI_PRODUCT_VERSION, "Blade3-10A-001"), }, .driver_data = (void *)&lcd1600x2560_rightside_up, + }, { /* Mysten SuiPlay0X1 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Mysten Labs, Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "SuiPlay0X1"), + }, + .driver_data = (void *)&lcd1200x1920_rightside_up, }, { /* Nanote UMPC-01 */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "RWC CO.,LTD"), @@ -486,6 +528,84 @@ static const struct dmi_system_id orientation_data[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONE XPLAYER"), }, .driver_data = (void *)&lcd1200x1920_leftside_up, + }, { /* OneXPlayer X1 AMD */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1 A"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 Intel */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1 i"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 mini (AMD) */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1 mini"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 Intel */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1Air"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 mini pro (AMD Strix Point) */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1Mini Pro"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 AMD second variant */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1z"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 AMD Strix Point */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1Pro"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1Pro EVA variant with Intel */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1Pro EVA-02"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer OneXFly F1 Pro (OLED) LE Red variant */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER F1 EVA-02"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, + }, { /* OneXPlayer OneXFly F1 Pro (OLED) Hawk Point */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER F1 OLED"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, + }, { /* OneXPlayer OneXFly F1 Pro (OLED) Strix Point */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER F1Pro"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, + }, { /* OneXPlayer OneXFly G1 AMD */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 A"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer OneXFly G1 Intel */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 i"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, }, { /* OrangePi Neo */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OrangePi"), @@ -498,6 +618,18 @@ static const struct dmi_system_id orientation_data[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Galaxy Book 10.6"), }, .driver_data = (void *)&lcd1280x1920_rightside_up, + }, { /* Tectoy Zeenix Lite */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Tectoy"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Zeenix Lite"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, + }, { /* Tectoy Zeenix Pro */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Tectoy"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Zeenix Pro"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, }, { /* Valve Steam Deck (Jupiter) */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Valve"), diff --git a/drivers/gpu/drm/imagination/pvr_job.c b/drivers/gpu/drm/imagination/pvr_job.c index 0c2f511a617852..dd9f5df01e0803 100644 --- a/drivers/gpu/drm/imagination/pvr_job.c +++ b/drivers/gpu/drm/imagination/pvr_job.c @@ -326,7 +326,7 @@ prepare_job_syncs(struct pvr_file *pvr_file, struct pvr_job_data *job_data, struct xarray *signal_array) { - struct dma_fence *done_fence; + struct dma_fence *finished_fence; int err = pvr_sync_signal_array_collect_ops(signal_array, from_pvr_file(pvr_file), job_data->sync_op_count, @@ -359,13 +359,13 @@ prepare_job_syncs(struct pvr_file *pvr_file, return err; } - /* We need to arm the job to get the job done fence. */ - done_fence = pvr_queue_job_arm(job_data->job); + /* We need to arm the job to get the job finished fence. */ + finished_fence = pvr_queue_job_arm(job_data->job); err = pvr_sync_signal_array_update_fences(signal_array, job_data->sync_op_count, job_data->sync_ops, - done_fence); + finished_fence); return err; } diff --git a/drivers/gpu/drm/imagination/pvr_queue.c b/drivers/gpu/drm/imagination/pvr_queue.c index dd88949f61944c..b5bec656d13cf3 100644 --- a/drivers/gpu/drm/imagination/pvr_queue.c +++ b/drivers/gpu/drm/imagination/pvr_queue.c @@ -177,11 +177,36 @@ static const struct dma_fence_ops pvr_queue_job_fence_ops = { .release = pvr_queue_fence_release, }; +/** + * pvr_queue_fence_is_ufo_backed() - Check if a dma_fence is backed by a UFO. + * @f: The dma_fence to check. + * + * Return: + * * true if the dma_fence is backed by a UFO, or + * * false otherwise. + */ +static inline bool +pvr_queue_fence_is_ufo_backed(struct dma_fence *f) +{ + /* + * Currently the only dma_fence backed by a UFO object is the job fence, + * e.g. pvr_job::done_fence, wrapped by a pvr_queue_fence object. + */ + return f && f->ops == &pvr_queue_job_fence_ops; +} + /** * to_pvr_queue_job_fence() - Return a pvr_queue_fence object if the fence is - * backed by a UFO. + * already backed by a UFO. * @f: The dma_fence to turn into a pvr_queue_fence. * + * This could be called on: + * - a job fence directly, in which case it simply returns the containing pvr_queue_fence; + * - a drm_sched_fence's scheduled or finished fence, in which case it will first try to follow + * the parent pointer to find the job fence (note that the parent pointer is initialized + * only after the run_job() callback is called on the drm_sched_fence's owning job); + * - any other dma_fence, in which case it will return NULL. + * * Return: * * A non-NULL pvr_queue_fence object if the dma_fence is backed by a UFO, or * * NULL otherwise. @@ -194,7 +219,7 @@ to_pvr_queue_job_fence(struct dma_fence *f) if (sched_fence) f = sched_fence->parent; - if (f && f->ops == &pvr_queue_job_fence_ops) + if (pvr_queue_fence_is_ufo_backed(f)) return container_of(f, struct pvr_queue_fence, base); return NULL; @@ -349,11 +374,23 @@ static u32 ufo_cmds_size(u32 elem_count) static u32 job_cmds_size(struct pvr_job *job, u32 ufo_wait_count) { - /* One UFO cmd for the fence signaling, one UFO cmd per native fence native, - * and a command for the job itself. + /* + * One UFO command per native fence this job will be waiting on (unless any are + * signaled by the time the job is submitted), plus a command for the job itself, + * plus one UFO command for the fence signaling. */ - return ufo_cmds_size(1) + ufo_cmds_size(ufo_wait_count) + - pvr_cccb_get_size_of_cmd_with_hdr(job->cmd_len); + return ufo_cmds_size(ufo_wait_count) + + pvr_cccb_get_size_of_cmd_with_hdr(job->cmd_len) + + ufo_cmds_size(1); +} + +static bool +is_paired_job_fence(struct dma_fence *fence, struct pvr_job *job) +{ + /* This assumes "fence" is one of "job"'s drm_sched_job::dependencies */ + return job->type == DRM_PVR_JOB_TYPE_FRAGMENT && + job->paired_job && + &job->paired_job->base.s_fence->scheduled == fence; } /** @@ -371,6 +408,17 @@ static unsigned long job_count_remaining_native_deps(struct pvr_job *job) xa_for_each(&job->base.dependencies, index, fence) { struct pvr_queue_fence *jfence; + if (is_paired_job_fence(fence, job)) { + /* + * A fence between paired jobs won't resolve to a pvr_queue_fence (i.e. + * be backed by a UFO) until the jobs have been submitted, together. + * The submitting code will insert a partial render fence command for this. + */ + WARN_ON(dma_fence_is_signaled(fence)); + remaining_count++; + continue; + } + jfence = to_pvr_queue_job_fence(fence); if (!jfence) continue; @@ -468,29 +516,37 @@ pvr_queue_get_job_kccb_fence(struct pvr_queue *queue, struct pvr_job *job) } static struct dma_fence * -pvr_queue_get_paired_frag_job_dep(struct pvr_queue *queue, struct pvr_job *job) +pvr_queue_get_paired_frag_job_dep(struct pvr_job *job) { struct pvr_job *frag_job = job->type == DRM_PVR_JOB_TYPE_GEOMETRY ? job->paired_job : NULL; + struct pvr_queue *frag_queue = frag_job ? frag_job->ctx->queues.fragment : NULL; struct dma_fence *f; unsigned long index; if (!frag_job) return NULL; + /* Have the geometry job wait on the paired fragment job's dependencies as well. */ xa_for_each(&frag_job->base.dependencies, index, f) { /* Skip already signaled fences. */ if (dma_fence_is_signaled(f)) continue; - /* Skip our own fence. */ + /* + * The paired job fence won't be signaled until both jobs have + * been submitted, so we can't wait on it to schedule them. + */ if (f == &job->base.s_fence->scheduled) continue; return dma_fence_get(f); } - return frag_job->base.sched->ops->prepare_job(&frag_job->base, &queue->entity); + /* Initialize the paired fragment job's done_fence, so we can signal it. */ + pvr_queue_job_fence_init(frag_job->done_fence, frag_queue); + + return pvr_queue_get_job_cccb_fence(frag_queue, frag_job); } /** @@ -509,32 +565,25 @@ pvr_queue_prepare_job(struct drm_sched_job *sched_job, struct pvr_queue *queue = container_of(s_entity, struct pvr_queue, entity); struct dma_fence *internal_dep = NULL; - /* - * Initialize the done_fence, so we can signal it. This must be done - * here because otherwise by the time of run_job() the job will end up - * in the pending list without a valid fence. - */ if (job->type == DRM_PVR_JOB_TYPE_FRAGMENT && job->paired_job) { /* - * This will be called on a paired fragment job after being - * submitted to firmware. We can tell if this is the case and - * bail early from whether run_job() has been called on the - * geometry job, which would issue a pm ref. + * This will be called on a paired fragment job after being submitted + * to the firmware as part of the paired geometry job's submission. + * We can tell if this is the case and bail early from whether run_job() + * has been called on the geometry job, which would issue a pm ref on + * this job as well. */ - if (job->paired_job->has_pm_ref) + if (job->has_pm_ref) return NULL; - - /* - * In this case we need to use the job's own ctx to initialise - * the done_fence. The other steps are done in the ctx of the - * paired geometry job. - */ - pvr_queue_job_fence_init(job->done_fence, - job->ctx->queues.fragment); - } else { - pvr_queue_job_fence_init(job->done_fence, queue); } + /* + * Initialize the done_fence, so we can signal it. This must be done + * here because otherwise by the time of run_job() the job will end up + * in the pending list without a valid fence. + */ + pvr_queue_job_fence_init(job->done_fence, queue); + /* CCCB fence is used to make sure we have enough space in the CCCB to * submit our commands. */ @@ -555,7 +604,7 @@ pvr_queue_prepare_job(struct drm_sched_job *sched_job, /* The paired job fence should come last, when everything else is ready. */ if (!internal_dep) - internal_dep = pvr_queue_get_paired_frag_job_dep(queue, job); + internal_dep = pvr_queue_get_paired_frag_job_dep(job); return internal_dep; } @@ -630,11 +679,7 @@ static void pvr_queue_submit_job_to_cccb(struct pvr_job *job) if (!jfence) continue; - /* Skip the partial render fence, we will place it at the end. */ - if (job->type == DRM_PVR_JOB_TYPE_FRAGMENT && job->paired_job && - &job->paired_job->base.s_fence->scheduled == fence) - continue; - + /* Some dependencies might have been signaled since prepare_job() */ if (dma_fence_is_signaled(&jfence->base)) continue; @@ -649,8 +694,13 @@ static void pvr_queue_submit_job_to_cccb(struct pvr_job *job) } } - /* Partial render fence goes last. */ if (job->type == DRM_PVR_JOB_TYPE_FRAGMENT && job->paired_job) { + /* + * The loop above will only process dependencies backed by a UFO i.e. with + * a valid parent fence assigned, but the paired job dependency won't have + * one until both jobs have been submitted. Access the parent fence directly + * here instead, submitting it last as partial render fence. + */ jfence = to_pvr_queue_job_fence(job->paired_job->done_fence); if (!WARN_ON(!jfence)) { pvr_fw_object_get_fw_addr(jfence->queue->timeline_ufo.fw_obj, @@ -679,7 +729,7 @@ static void pvr_queue_submit_job_to_cccb(struct pvr_job *job) pvr_cccb_write_command_with_header(cccb, job->fw_ccb_cmd_type, job->cmd_len, job->cmd, job->id, job->id); - /* Signal the job fence. */ + /* Update command to signal the job fence. */ pvr_fw_object_get_fw_addr(queue->timeline_ufo.fw_obj, &ufos[0].addr); ufos[0].value = job->done_fence->seqno; pvr_cccb_write_command_with_header(cccb, ROGUE_FWIF_CCB_CMD_TYPE_UPDATE, @@ -709,10 +759,8 @@ static struct dma_fence *pvr_queue_run_job(struct drm_sched_job *sched_job) } /* The only kind of jobs that can be paired are geometry and fragment, and - * we bail out early if we see a fragment job that's paired with a geomtry - * job. - * Paired jobs must also target the same context and point to the same - * HWRT. + * we bail out early if we see a fragment job that's paired with a geometry job. + * Paired jobs must also target the same context and point to the same HWRT. */ if (WARN_ON(job->paired_job && (job->type != DRM_PVR_JOB_TYPE_GEOMETRY || @@ -882,16 +930,16 @@ static const struct drm_sched_backend_ops pvr_queue_sched_ops = { }; /** - * pvr_queue_fence_is_ufo_backed() - Check if a dma_fence is backed by a UFO object + * pvr_queue_fence_is_native() - Check if a dma_fence is native to this driver. * @f: Fence to test. * - * A UFO-backed fence is a fence that can be signaled or waited upon FW-side. - * pvr_job::done_fence objects are backed by the timeline UFO attached to the queue - * they are pushed to, but those fences are not directly exposed to the outside - * world, so we also need to check if the fence we're being passed is a - * drm_sched_fence that was coming from our driver. + * Check if the fence we're being passed is a drm_sched_fence that is coming from this driver. + * + * It may be a UFO-backed fence i.e. a fence that can be signaled or waited upon FW-side, + * such as pvr_job::done_fence objects that are backed by the timeline UFO attached to the queue + * they are pushed to. */ -bool pvr_queue_fence_is_ufo_backed(struct dma_fence *f) +bool pvr_queue_fence_is_native(struct dma_fence *f) { struct drm_sched_fence *sched_fence = f ? to_drm_sched_fence(f) : NULL; @@ -899,10 +947,7 @@ bool pvr_queue_fence_is_ufo_backed(struct dma_fence *f) sched_fence->sched->ops == &pvr_queue_sched_ops) return true; - if (f && f->ops == &pvr_queue_job_fence_ops) - return true; - - return false; + return pvr_queue_fence_is_ufo_backed(f); } /** @@ -934,9 +979,8 @@ pvr_queue_signal_done_fences(struct pvr_queue *queue) } /** - * pvr_queue_check_job_waiting_for_cccb_space() - Check if the job waiting for CCCB space - * can be unblocked - * pushed to the CCCB + * pvr_queue_check_job_waiting_for_cccb_space() - Check if a job waiting for CCCB space + * can be unblocked and pushed to the CCCB. * @queue: Queue to check * * If we have a job waiting for CCCB, and this job now fits in the CCCB, we signal diff --git a/drivers/gpu/drm/imagination/pvr_queue.h b/drivers/gpu/drm/imagination/pvr_queue.h index fc1986d73fc88e..4aa72665ce2568 100644 --- a/drivers/gpu/drm/imagination/pvr_queue.h +++ b/drivers/gpu/drm/imagination/pvr_queue.h @@ -141,7 +141,7 @@ struct pvr_queue { u64 callstack_addr; }; -bool pvr_queue_fence_is_ufo_backed(struct dma_fence *f); +bool pvr_queue_fence_is_native(struct dma_fence *f); int pvr_queue_job_init(struct pvr_job *job, u64 drm_client_id); diff --git a/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared.h b/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared.h index 869d904e3649f9..fe54c1cad7a9c5 100644 --- a/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared.h +++ b/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared.h @@ -14,15 +14,7 @@ #define ROGUE_NUM_GEOM_CORES_SIZE 2U -/* - * Maximum number of UFOs in a CCB command. - * The number is based on having 32 sync prims (as originally), plus 32 sync - * checkpoints. - * Once the use of sync prims is no longer supported, we will retain - * the same total (64) as the number of sync checkpoints which may be - * supporting a fence is not visible to the client driver and has to - * allow for the number of different timelines involved in fence merges. - */ +/* Maximum number of UFOs in a CCB command. */ #define ROGUE_FWIF_CCB_CMD_MAX_UFOS (32U + 32U) /* diff --git a/drivers/gpu/drm/imagination/pvr_sync.c b/drivers/gpu/drm/imagination/pvr_sync.c index 3582616ff72208..936f840a522189 100644 --- a/drivers/gpu/drm/imagination/pvr_sync.c +++ b/drivers/gpu/drm/imagination/pvr_sync.c @@ -160,7 +160,7 @@ int pvr_sync_signal_array_update_fences(struct xarray *array, u32 sync_op_count, const struct drm_pvr_sync_op *sync_ops, - struct dma_fence *done_fence) + struct dma_fence *finished_fence) { for (u32 i = 0; i < sync_op_count; i++) { struct dma_fence *old_fence; @@ -175,7 +175,7 @@ pvr_sync_signal_array_update_fences(struct xarray *array, return -EINVAL; old_fence = sig_sync->fence; - sig_sync->fence = dma_fence_get(done_fence); + sig_sync->fence = dma_fence_get(finished_fence); dma_fence_put(old_fence); if (WARN_ON(!sig_sync->fence)) @@ -211,7 +211,7 @@ pvr_sync_add_dep_to_job(struct drm_sched_job *job, struct dma_fence *f) int err = 0; dma_fence_unwrap_for_each(uf, &iter, f) { - if (pvr_queue_fence_is_ufo_backed(uf)) + if (pvr_queue_fence_is_native(uf)) native_fence_count++; } @@ -227,7 +227,7 @@ pvr_sync_add_dep_to_job(struct drm_sched_job *job, struct dma_fence *f) if (err) continue; - if (pvr_queue_fence_is_ufo_backed(uf)) { + if (pvr_queue_fence_is_native(uf)) { struct drm_sched_fence *s_fence = to_drm_sched_fence(uf); /* If this is a native dependency, we wait for the scheduled fence, diff --git a/drivers/gpu/drm/imagination/pvr_sync.h b/drivers/gpu/drm/imagination/pvr_sync.h index db6ccfda104a3a..48501ad2779421 100644 --- a/drivers/gpu/drm/imagination/pvr_sync.h +++ b/drivers/gpu/drm/imagination/pvr_sync.h @@ -70,7 +70,7 @@ int pvr_sync_signal_array_update_fences(struct xarray *array, u32 sync_op_count, const struct drm_pvr_sync_op *sync_ops, - struct dma_fence *done_fence); + struct dma_fence *finished_fence); void pvr_sync_signal_array_push_fences(struct xarray *array); diff --git a/drivers/gpu/drm/radeon/cik.c b/drivers/gpu/drm/radeon/cik.c index 51a3e0fc2f56be..7e7f6248b63a63 100644 --- a/drivers/gpu/drm/radeon/cik.c +++ b/drivers/gpu/drm/radeon/cik.c @@ -3543,6 +3543,13 @@ void cik_fence_gfx_ring_emit(struct radeon_device *rdev, struct radeon_ring *ring = &rdev->ring[fence->ring]; u64 addr = rdev->fence_drv[fence->ring].gpu_addr; +/* This workaround causes instability for LoongArch/Loongson (MIPS) + * devices based on the 7A1000/2000 chipset under heavy I/O load. + * + * FIXME: Disable this workaround until we find a better fix (possibly in + * the platform-specific PCI code). + */ +#ifndef CONFIG_MACH_LOONGSON64 /* Workaround for cache flush problems. First send a dummy EOP * event down the pipe with seq one below. */ @@ -3556,6 +3563,7 @@ void cik_fence_gfx_ring_emit(struct radeon_device *rdev, DATA_SEL(1) | INT_SEL(0)); radeon_ring_write(ring, fence->seq - 1); radeon_ring_write(ring, 0); +#endif /* Then send the real EOP event down the pipe. */ radeon_ring_write(ring, PACKET3(PACKET3_EVENT_WRITE_EOP, 4)); @@ -8093,6 +8101,7 @@ int cik_irq_process(struct radeon_device *rdev) if (queue_thermal) schedule_work(&rdev->pm.dpm.thermal.work); rdev->ih.rptr = rptr; + WREG32(IH_RB_RPTR, rptr); atomic_set(&rdev->ih.lock, 0); /* make sure wptr hasn't changed while processing */ diff --git a/drivers/gpu/drm/radeon/evergreen.c b/drivers/gpu/drm/radeon/evergreen.c index 3cbc6eedbf6639..ee79eeeb838ed3 100644 --- a/drivers/gpu/drm/radeon/evergreen.c +++ b/drivers/gpu/drm/radeon/evergreen.c @@ -4927,6 +4927,7 @@ int evergreen_irq_process(struct radeon_device *rdev) if (queue_thermal && rdev->pm.dpm_enabled) schedule_work(&rdev->pm.dpm.thermal.work); rdev->ih.rptr = rptr; + WREG32(IH_RB_RPTR, rptr); atomic_set(&rdev->ih.lock, 0); /* make sure wptr hasn't changed while processing */ diff --git a/drivers/gpu/drm/radeon/r600.c b/drivers/gpu/drm/radeon/r600.c index 8b62f7faa5b99f..88e38fb3f3476b 100644 --- a/drivers/gpu/drm/radeon/r600.c +++ b/drivers/gpu/drm/radeon/r600.c @@ -4328,6 +4328,7 @@ int r600_irq_process(struct radeon_device *rdev) if (queue_thermal && rdev->pm.dpm_enabled) schedule_work(&rdev->pm.dpm.thermal.work); rdev->ih.rptr = rptr; + WREG32(IH_RB_RPTR, rptr); atomic_set(&rdev->ih.lock, 0); /* make sure wptr hasn't changed while processing */ diff --git a/drivers/gpu/drm/radeon/radeon_ring.c b/drivers/gpu/drm/radeon/radeon_ring.c index 581ae20c46e4b5..92aa8e1364c319 100644 --- a/drivers/gpu/drm/radeon/radeon_ring.c +++ b/drivers/gpu/drm/radeon/radeon_ring.c @@ -185,6 +185,9 @@ void radeon_ring_commit(struct radeon_device *rdev, struct radeon_ring *ring, if (hdp_flush && rdev->asic->mmio_hdp_flush) rdev->asic->mmio_hdp_flush(rdev); radeon_ring_set_wptr(rdev, ring); +#if defined(CONFIG_MACH_LOONGSON64) + mmiowb(); /* Make sure wptr is up-to-date for hw */ +#endif } /** diff --git a/drivers/gpu/drm/radeon/si.c b/drivers/gpu/drm/radeon/si.c index 26197aceb001cc..2d7eeb781969d1 100644 --- a/drivers/gpu/drm/radeon/si.c +++ b/drivers/gpu/drm/radeon/si.c @@ -6423,6 +6423,7 @@ int si_irq_process(struct radeon_device *rdev) if (queue_thermal && rdev->pm.dpm_enabled) schedule_work(&rdev->pm.dpm.thermal.work); rdev->ih.rptr = rptr; + WREG32(IH_RB_RPTR, rptr); atomic_set(&rdev->ih.lock, 0); /* make sure wptr hasn't changed while processing */ diff --git a/drivers/gpu/drm/ttm/ttm_bo_util.c b/drivers/gpu/drm/ttm/ttm_bo_util.c index 3e3c201a022267..6f33c77d47159a 100644 --- a/drivers/gpu/drm/ttm/ttm_bo_util.c +++ b/drivers/gpu/drm/ttm/ttm_bo_util.c @@ -307,6 +307,14 @@ pgprot_t ttm_io_prot(struct ttm_buffer_object *bo, struct ttm_resource *res, caching = res->bus.caching; } +#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL) + /* Downgrade cached mapping for non-snooping devices */ + if (!bo->bdev->dma_coherent && caching == ttm_cached) + caching = ttm_write_combined; +#endif + return ttm_prot_from_caching(caching, tmp); } EXPORT_SYMBOL(ttm_io_prot); @@ -357,6 +365,11 @@ static int ttm_bo_kmap_ttm(struct ttm_buffer_object *bo, return ret; if (num_pages == 1 && ttm->caching == ttm_cached && +#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL) + bo->bdev->dma_coherent && +#endif !(man->use_tt && (ttm->page_flags & TTM_TT_FLAG_DECRYPTED))) { /* * We're mapping a single page, and the desired diff --git a/drivers/gpu/drm/ttm/ttm_device.c b/drivers/gpu/drm/ttm/ttm_device.c index d3bfb9a696a741..7fad8dcd1ecfe3 100644 --- a/drivers/gpu/drm/ttm/ttm_device.c +++ b/drivers/gpu/drm/ttm/ttm_device.c @@ -244,6 +244,12 @@ int ttm_device_init(struct ttm_device *bdev, const struct ttm_device_funcs *func list_add_tail(&bdev->device_list, &glob->device_list); mutex_unlock(&ttm_global_mutex); +#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL) + bdev->dma_coherent = dev->dma_coherent; +#endif + return 0; } EXPORT_SYMBOL(ttm_device_init); diff --git a/drivers/gpu/drm/ttm/ttm_resource.c b/drivers/gpu/drm/ttm/ttm_resource.c index 154d6739256f86..6cd19a877b2ce4 100644 --- a/drivers/gpu/drm/ttm/ttm_resource.c +++ b/drivers/gpu/drm/ttm/ttm_resource.c @@ -848,9 +848,20 @@ ttm_kmap_iter_linear_io_init(struct ttm_kmap_iter_linear_io *iter_io, struct ttm_device *bdev, struct ttm_resource *mem) { + enum ttm_caching caching; int ret; ret = ttm_mem_io_reserve(bdev, mem); + caching = mem->bus.caching; + +#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL) + /* Downgrade cached mapping for non-snooping devices */ + if (!bdev->dma_coherent && caching == ttm_cached) + caching = ttm_write_combined; +#endif + if (ret) goto out_err; if (!mem->bus.is_iomem) { @@ -864,11 +875,11 @@ ttm_kmap_iter_linear_io_init(struct ttm_kmap_iter_linear_io *iter_io, } else { iter_io->needs_unmap = true; memset(&iter_io->dmap, 0, sizeof(iter_io->dmap)); - if (mem->bus.caching == ttm_write_combined) + if (caching == ttm_write_combined) iosys_map_set_vaddr_iomem(&iter_io->dmap, ioremap_wc(mem->bus.offset, mem->size)); - else if (mem->bus.caching == ttm_cached) + else if (caching == ttm_cached) iosys_map_set_vaddr(&iter_io->dmap, memremap(mem->bus.offset, mem->size, MEMREMAP_WB | diff --git a/drivers/gpu/drm/ttm/ttm_tt.c b/drivers/gpu/drm/ttm/ttm_tt.c index b645a181818431..47244d1c1b7aeb 100644 --- a/drivers/gpu/drm/ttm/ttm_tt.c +++ b/drivers/gpu/drm/ttm/ttm_tt.c @@ -156,6 +156,14 @@ static void ttm_tt_init_fields(struct ttm_tt *ttm, enum ttm_caching caching, unsigned long extra_pages) { +#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL) + /* Downgrade cached mapping for non-snooping devices */ + if (!bo->bdev->dma_coherent && caching == ttm_cached) + caching = ttm_write_combined; +#endif + ttm->num_pages = (PAGE_ALIGN(bo->base.size) >> PAGE_SHIFT) + extra_pages; ttm->page_flags = page_flags; ttm->dma_address = NULL; diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile index fd8d805fbcde13..426f4bcaa834d5 100644 --- a/drivers/gpu/drm/verisilicon/Makefile +++ b/drivers/gpu/drm/verisilicon/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only -verisilicon-dc-objs := vs_bridge.o vs_crtc.o vs_dc.o vs_drm.o vs_hwdb.o vs_plane.o vs_primary_plane.o +verisilicon-dc-objs := vs_bridge.o vs_crtc.o vs_dc.o vs_drm.o vs_hwdb.o \ + vs_plane.o vs_primary_plane.o vs_cursor_plane.o obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon-dc.o diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.c b/drivers/gpu/drm/verisilicon/vs_crtc.c index f4940171300067..5c9714a3e69a7e 100644 --- a/drivers/gpu/drm/verisilicon/vs_crtc.c +++ b/drivers/gpu/drm/verisilicon/vs_crtc.c @@ -159,7 +159,7 @@ struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc, unsigned int output) { struct vs_crtc *vcrtc; - struct drm_plane *primary; + struct drm_plane *primary, *cursor; int ret; vcrtc = drmm_kzalloc(drm_dev, sizeof(*vcrtc), GFP_KERNEL); @@ -175,9 +175,16 @@ struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc, return ERR_PTR(PTR_ERR(primary)); } + /* Create our cursor plane */ + cursor = vs_cursor_plane_init(drm_dev, dc); + if (IS_ERR(cursor)) { + drm_err(drm_dev, "Couldn't create the cursor plane\n"); + return ERR_CAST(cursor); + } + ret = drmm_crtc_init_with_planes(drm_dev, &vcrtc->base, primary, - NULL, + cursor, &vs_crtc_funcs, NULL); if (ret) { diff --git a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c new file mode 100644 index 00000000000000..acb9854fa67ed7 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Institute of Software, Chinese Academy of Sciences (ISCAS) + * + * Authors: + * Icenowy Zheng + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vs_crtc.h" +#include "vs_plane.h" +#include "vs_dc.h" +#include "vs_hwdb.h" +#include "vs_cursor_plane_regs.h" + +#define VSDC_MIN_CURSOR_SIZE 32 +#define VSDC_MAX_CURSOR_SIZE 256 + +#define VSDC_CURSOR_LOCATION_MAX_POSITIVE BIT_MASK(15) +#define VSDC_CURSOR_LOCATION_MAX_NEGATIVE BIT_MASK(5) + +static bool vs_cursor_plane_check_coord(int32_t coord) +{ + if (coord >= 0) + return coord <= VSDC_CURSOR_LOCATION_MAX_POSITIVE; + else + return (-coord) <= VSDC_CURSOR_LOCATION_MAX_NEGATIVE; +} + +static int vs_cursor_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_crtc *crtc = new_plane_state->crtc; + struct drm_framebuffer *fb = new_plane_state->fb; + struct drm_crtc_state *crtc_state = NULL; + struct vs_crtc *vcrtc; + struct vs_dc *dc; + int ret; + + if (crtc) + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + ret = drm_atomic_helper_check_plane_state(new_plane_state, + crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true, true); + if (ret) + return ret; + + if (!new_plane_state->visible) + return 0; /* Skip validity check */ + + vcrtc = drm_crtc_to_vs_crtc(crtc); + dc = vcrtc->dc; + + /* Only certain PoT square sizes is supported. */ + if (!is_power_of_2(new_plane_state->crtc_w) || + new_plane_state->crtc_w < VSDC_MIN_CURSOR_SIZE || + new_plane_state->crtc_w > dc->identity.max_cursor_size) + return -EINVAL; + + if (new_plane_state->crtc_w != new_plane_state->crtc_h) + return -EINVAL; + + /* Check if the cursor is inside the register fields' range */ + if (!vs_cursor_plane_check_coord(new_plane_state->crtc_x) || + !vs_cursor_plane_check_coord(new_plane_state->crtc_y)) + return -EINVAL; + + /* Extra line padding isn't supported */ + if (fb->pitches[0] != + drm_format_info_min_pitch(fb->format, 0, new_plane_state->crtc_w)) + return -EINVAL; + + return 0; +} + +static void vs_cursor_plane_commit(struct vs_dc *dc, unsigned int output) +{ + regmap_set_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_COMMIT | + VSDC_CURSOR_CONFIG_IMG_UPDATE); +} + +static void vs_cursor_plane_atomic_enable(struct drm_plane *plane, + struct drm_atomic_state *atomic_state) +{ + struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state, + plane); + struct drm_crtc *crtc = state->crtc; + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + unsigned int output = vcrtc->id; + struct vs_dc *dc = vcrtc->dc; + + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_FMT_MASK, + VSDC_CURSOR_CONFIG_FMT_ARGB8888); + + vs_cursor_plane_commit(dc, output); +} + +static void vs_cursor_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *atomic_state) +{ + struct drm_plane_state *state = drm_atomic_get_old_plane_state(atomic_state, + plane); + struct drm_crtc *crtc = state->crtc; + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + unsigned int output = vcrtc->id; + struct vs_dc *dc = vcrtc->dc; + + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_FMT_MASK, + VSDC_CURSOR_CONFIG_FMT_OFF); + + vs_cursor_plane_commit(dc, output); +} + +static void vs_cursor_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *atomic_state) +{ + struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state, + plane); + struct drm_framebuffer *fb = state->fb; + struct drm_crtc *crtc = state->crtc; + struct vs_dc *dc; + struct vs_crtc *vcrtc; + unsigned int output; + dma_addr_t dma_addr; + + if (!state->visible) { + vs_cursor_plane_atomic_disable(plane, atomic_state); + return; + } + + vcrtc = drm_crtc_to_vs_crtc(crtc); + output = vcrtc->id; + dc = vcrtc->dc; + + /* Other sizes should be rejected by atomic_check */ + switch (state->crtc_w) { + case 32: + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_SIZE_MASK, + VSDC_CURSOR_CONFIG_SIZE_32); + break; + case 64: + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_SIZE_MASK, + VSDC_CURSOR_CONFIG_SIZE_64); + break; + case 128: + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_SIZE_MASK, + VSDC_CURSOR_CONFIG_SIZE_128); + break; + case 256: + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_SIZE_MASK, + VSDC_CURSOR_CONFIG_SIZE_256); + break; + } + + dma_addr = vs_fb_get_dma_addr(fb, &state->src); + + regmap_write(dc->regs, VSDC_CURSOR_ADDRESS(output), + lower_32_bits(dma_addr)); + + /* + * The X_OFF and Y_OFF fields define which point does the LOCATION + * register represent in the cursor image, and LOCATION register + * values are unsigned. To for positive left-top coordinates the + * offset is set to 0 and the location is set to the coordinate, for + * negative coordinates the location is set to 0 and the offset + * is set to the opposite number of the coordinate to offset the + * cursor image partly off-screen. + */ + if (state->crtc_x >= 0) { + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_X_OFF_MASK, 0); + regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), + VSDC_CURSOR_LOCATION_X_MASK, + VSDC_CURSOR_LOCATION_X(state->crtc_x)); + } else { + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_X_OFF_MASK, + -state->crtc_x); + regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), + VSDC_CURSOR_LOCATION_X_MASK, 0); + } + + if (state->crtc_y >= 0) { + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_Y_OFF_MASK, 0); + regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), + VSDC_CURSOR_LOCATION_Y_MASK, + VSDC_CURSOR_LOCATION_Y(state->crtc_y)); + } else { + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_Y_OFF_MASK, + -state->crtc_y); + regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), + VSDC_CURSOR_LOCATION_Y_MASK, 0); + } + + vs_cursor_plane_commit(dc, output); +} + +static const struct drm_plane_helper_funcs vs_cursor_plane_helper_funcs = { + .atomic_check = vs_cursor_plane_atomic_check, + .atomic_update = vs_cursor_plane_atomic_update, + .atomic_enable = vs_cursor_plane_atomic_enable, + .atomic_disable = vs_cursor_plane_atomic_disable, +}; + +static const struct drm_plane_funcs vs_cursor_plane_funcs = { + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +static const u32 vs_cursor_plane_formats[] = { + DRM_FORMAT_ARGB8888, +}; + +static const u64 vs_cursor_plane_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID, /* sentinel */ +}; + +struct drm_plane *vs_cursor_plane_init(struct drm_device *drm_dev, + struct vs_dc *dc) +{ + int32_t max_cursor_size = dc->identity.max_cursor_size; + struct drm_plane *plane; + + if (drm_WARN_ON_ONCE(drm_dev, max_cursor_size < VSDC_MIN_CURSOR_SIZE || + max_cursor_size > VSDC_MAX_CURSOR_SIZE)) + return ERR_PTR(-EINVAL); + + plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0, + &vs_cursor_plane_funcs, + vs_cursor_plane_formats, + ARRAY_SIZE(vs_cursor_plane_formats), + vs_cursor_plane_modifiers, + DRM_PLANE_TYPE_CURSOR, + NULL); + + if (IS_ERR(plane)) + return plane; + + drm_plane_helper_add(plane, &vs_cursor_plane_helper_funcs); + + return plane; +} diff --git a/drivers/gpu/drm/verisilicon/vs_cursor_plane_regs.h b/drivers/gpu/drm/verisilicon/vs_cursor_plane_regs.h new file mode 100644 index 00000000000000..99693f2c95b94b --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_cursor_plane_regs.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2025 Icenowy Zheng + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_CURSOR_PLANE_REGS_H_ +#define _VS_CURSOR_PLANE_REGS_H_ + +#include + +#define VSDC_CURSOR_CONFIG(n) (0x1468 + 0x1080 * (n)) +#define VSDC_CURSOR_CONFIG_FMT_MASK GENMASK(1, 0) +#define VSDC_CURSOR_CONFIG_FMT_ARGB8888 (0x2 << 0) +#define VSDC_CURSOR_CONFIG_FMT_OFF (0x0 << 0) +#define VSDC_CURSOR_CONFIG_IMG_UPDATE BIT(2) +#define VSDC_CURSOR_CONFIG_COMMIT BIT(3) +#define VSDC_CURSOR_CONFIG_SIZE_MASK GENMASK(7, 5) +#define VSDC_CURSOR_CONFIG_SIZE_32 (0x0 << 5) +#define VSDC_CURSOR_CONFIG_SIZE_64 (0x1 << 5) +#define VSDC_CURSOR_CONFIG_SIZE_128 (0x2 << 5) +#define VSDC_CURSOR_CONFIG_SIZE_256 (0x3 << 5) +#define VSDC_CURSOR_CONFIG_Y_OFF_MASK GENMASK(12, 8) +#define VSDC_CURSOR_CONFIG_Y_OFF(v) ((v) << 8) +#define VSDC_CURSOR_CONFIG_X_OFF_MASK GENMASK(20, 16) +#define VSDC_CURSOR_CONFIG_X_OFF(v) ((v) << 16) + +#define VSDC_CURSOR_ADDRESS(n) (0x146C + 0x1080 * (n)) + +#define VSDC_CURSOR_LOCATION(n) (0x1470 + 0x1080 * (n)) +#define VSDC_CURSOR_LOCATION_X_MASK GENMASK(14, 0) +#define VSDC_CURSOR_LOCATION_X(v) ((v) << 0) +#define VSDC_CURSOR_LOCATION_Y_MASK GENMASK(30, 16) +#define VSDC_CURSOR_LOCATION_Y(v) ((v) << 16) + +#define VSDC_CURSOR_BACKGROUND(n) (0x1474 + 0x1080 * (n)) +#define VSDC_CURSOR_BACKGRUOND_DEFAULT 0x00FFFFFF + +#define VSDC_CURSOR_FOREGROUND(n) (0x1478 + 0x1080 * (n)) +#define VSDC_CURSOR_FOREGRUOND_DEFAULT 0x00AAAAAA + +#endif /* _VS_CURSOR_PLANE_REGS_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c index 09336af0900ae3..2a0f7c59afa3a5 100644 --- a/drivers/gpu/drm/verisilicon/vs_hwdb.c +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c @@ -95,6 +95,7 @@ static struct vs_chip_identity vs_chip_identities[] = { .customer_id = ~0U, .display_count = 2, + .max_cursor_size = 64, .formats = &vs_formats_no_yuv444, }, { @@ -103,6 +104,7 @@ static struct vs_chip_identity vs_chip_identities[] = { .customer_id = 0x30B, .display_count = 2, + .max_cursor_size = 64, .formats = &vs_formats_no_yuv444, }, { @@ -111,6 +113,7 @@ static struct vs_chip_identity vs_chip_identities[] = { .customer_id = 0x310, .display_count = 2, + .max_cursor_size = 64, .formats = &vs_formats_with_yuv444, }, { @@ -119,6 +122,7 @@ static struct vs_chip_identity vs_chip_identities[] = { .customer_id = 0x311, .display_count = 2, + .max_cursor_size = 64, .formats = &vs_formats_no_yuv444, }, }; diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.h b/drivers/gpu/drm/verisilicon/vs_hwdb.h index 92192e4fa08625..2065ecb7304379 100644 --- a/drivers/gpu/drm/verisilicon/vs_hwdb.h +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.h @@ -20,6 +20,11 @@ struct vs_chip_identity { u32 customer_id; u32 display_count; + /* + * The hardware only supports square cursor planes, so this field + * is both the maximum width and height in pixels. + */ + int32_t max_cursor_size; const struct vs_formats *formats; }; diff --git a/drivers/gpu/drm/verisilicon/vs_plane.h b/drivers/gpu/drm/verisilicon/vs_plane.h index 41875ea3d66a5a..60b5b3a1bc22aa 100644 --- a/drivers/gpu/drm/verisilicon/vs_plane.h +++ b/drivers/gpu/drm/verisilicon/vs_plane.h @@ -68,5 +68,6 @@ dma_addr_t vs_fb_get_dma_addr(struct drm_framebuffer *fb, const struct drm_rect *src_rect); struct drm_plane *vs_primary_plane_init(struct drm_device *dev, struct vs_dc *dc); +struct drm_plane *vs_cursor_plane_init(struct drm_device *dev, struct vs_dc *dc); #endif /* _VS_PLANE_H_ */ diff --git a/drivers/gpu/drm/xe/Kconfig b/drivers/gpu/drm/xe/Kconfig index 4d7dcaff2b9153..03edbfe22ccf6d 100644 --- a/drivers/gpu/drm/xe/Kconfig +++ b/drivers/gpu/drm/xe/Kconfig @@ -5,7 +5,6 @@ config DRM_XE depends on KUNIT || !KUNIT depends on INTEL_VSEC || !INTEL_VSEC depends on X86_PLATFORM_DEVICES || !(X86 && ACPI) - depends on PAGE_SIZE_4KB || COMPILE_TEST || BROKEN select INTERVAL_TREE # we need shmfs for the swappable backing store, and in particular # the shmem_readpage() which depends upon tmpfs diff --git a/drivers/gpu/drm/xe/regs/xe_engine_regs.h b/drivers/gpu/drm/xe/regs/xe_engine_regs.h index 1b4a7e9a703df7..aa7160d6fcac48 100644 --- a/drivers/gpu/drm/xe/regs/xe_engine_regs.h +++ b/drivers/gpu/drm/xe/regs/xe_engine_regs.h @@ -56,7 +56,7 @@ #define RING_START(base) XE_REG((base) + 0x38) #define RING_CTL(base) XE_REG((base) + 0x3c) -#define RING_CTL_SIZE(size) ((size) - PAGE_SIZE) /* in bytes -> pages */ +#define RING_CTL_SIZE(size) ((size) - SZ_4K) /* in bytes -> pages */ #define RING_START_UDW(base) XE_REG((base) + 0x48) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index 6b518858538f57..37cdf3b99e939b 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -2339,9 +2339,9 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo, flags |= XE_BO_FLAG_INTERNAL_64K; alignment = align >> PAGE_SHIFT; } else { - aligned_size = ALIGN(size, SZ_4K); + aligned_size = ALIGN(size, PAGE_SIZE); flags &= ~XE_BO_FLAG_INTERNAL_64K; - alignment = SZ_4K >> PAGE_SHIFT; + alignment = PAGE_SIZE >> PAGE_SHIFT; } if (type == ttm_bo_type_device && aligned_size != size) { @@ -2370,7 +2370,7 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo, /* Initialize purge advisory state */ bo->purgeable.state = XE_MADV_PURGEABLE_WILLNEED; - drm_gem_private_object_init(&xe->drm, &bo->ttm.base, size); + drm_gem_private_object_init(&xe->drm, &bo->ttm.base, aligned_size); if (resv) { ctx.allow_res_evict = !(flags & XE_BO_FLAG_NO_RESV_EVICT); diff --git a/drivers/gpu/drm/xe/xe_guc.c b/drivers/gpu/drm/xe/xe_guc.c index ccebb437e37fd0..14c52a24b2c40a 100644 --- a/drivers/gpu/drm/xe/xe_guc.c +++ b/drivers/gpu/drm/xe/xe_guc.c @@ -106,7 +106,7 @@ static u32 guc_ctl_feature_flags(struct xe_guc *guc) static u32 guc_ctl_log_params_flags(struct xe_guc *guc) { - u32 offset = guc_bo_ggtt_addr(guc, guc->log.bo) >> PAGE_SHIFT; + u32 offset = guc_bo_ggtt_addr(guc, guc->log.bo) >> XE_PTE_SHIFT; u32 flags; #if (((XE_GUC_LOG_CRASH_DUMP_BUFFER_SIZE) % SZ_1M) == 0) @@ -152,7 +152,7 @@ static u32 guc_ctl_log_params_flags(struct xe_guc *guc) static u32 guc_ctl_ads_flags(struct xe_guc *guc) { - u32 ads = guc_bo_ggtt_addr(guc, guc->ads.bo) >> PAGE_SHIFT; + u32 ads = guc_bo_ggtt_addr(guc, guc->ads.bo) >> XE_PTE_SHIFT; u32 flags = FIELD_PREP(GUC_ADS_ADDR, ads); return flags; diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index 5760251cb685f6..ec834a7bca014d 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -142,17 +142,17 @@ static size_t guc_ads_regset_size(struct xe_guc_ads *ads) static size_t guc_ads_golden_lrc_size(struct xe_guc_ads *ads) { - return PAGE_ALIGN(ads->golden_lrc_size); + return ALIGN(ads->golden_lrc_size, SZ_4K); } static u32 guc_ads_waklv_size(struct xe_guc_ads *ads) { - return PAGE_ALIGN(ads->ads_waklv_size); + return ALIGN(ads->ads_waklv_size, SZ_4K); } static size_t guc_ads_capture_size(struct xe_guc_ads *ads) { - return PAGE_ALIGN(ads->capture_size); + return ALIGN(ads->capture_size, SZ_4K); } static size_t guc_ads_um_queues_size(struct xe_guc_ads *ads) @@ -167,7 +167,7 @@ static size_t guc_ads_um_queues_size(struct xe_guc_ads *ads) static size_t guc_ads_private_data_size(struct xe_guc_ads *ads) { - return PAGE_ALIGN(ads_to_guc(ads)->fw.private_data_size); + return ALIGN(ads_to_guc(ads)->fw.private_data_size, SZ_4K); } static size_t guc_ads_regset_offset(struct xe_guc_ads *ads) @@ -182,7 +182,7 @@ static size_t guc_ads_golden_lrc_offset(struct xe_guc_ads *ads) offset = guc_ads_regset_offset(ads) + guc_ads_regset_size(ads); - return PAGE_ALIGN(offset); + return ALIGN(offset, SZ_4K); } static size_t guc_ads_waklv_offset(struct xe_guc_ads *ads) @@ -192,7 +192,7 @@ static size_t guc_ads_waklv_offset(struct xe_guc_ads *ads) offset = guc_ads_golden_lrc_offset(ads) + guc_ads_golden_lrc_size(ads); - return PAGE_ALIGN(offset); + return ALIGN(offset, SZ_4K); } static size_t guc_ads_capture_offset(struct xe_guc_ads *ads) @@ -202,7 +202,7 @@ static size_t guc_ads_capture_offset(struct xe_guc_ads *ads) offset = guc_ads_waklv_offset(ads) + guc_ads_waklv_size(ads); - return PAGE_ALIGN(offset); + return ALIGN(offset, SZ_4K); } static size_t guc_ads_um_queues_offset(struct xe_guc_ads *ads) @@ -212,7 +212,7 @@ static size_t guc_ads_um_queues_offset(struct xe_guc_ads *ads) offset = guc_ads_capture_offset(ads) + guc_ads_capture_size(ads); - return PAGE_ALIGN(offset); + return ALIGN(offset, SZ_4K); } static size_t guc_ads_private_data_offset(struct xe_guc_ads *ads) @@ -222,7 +222,7 @@ static size_t guc_ads_private_data_offset(struct xe_guc_ads *ads) offset = guc_ads_um_queues_offset(ads) + guc_ads_um_queues_size(ads); - return PAGE_ALIGN(offset); + return ALIGN(offset, SZ_4K); } static size_t guc_ads_size(struct xe_guc_ads *ads) @@ -275,7 +275,7 @@ static size_t calculate_golden_lrc_size(struct xe_guc_ads *ads) continue; real_size = xe_gt_lrc_size(gt, class); - alloc_size = PAGE_ALIGN(real_size); + alloc_size = ALIGN(real_size, SZ_4K); total_size += alloc_size; } @@ -623,12 +623,12 @@ static int guc_capture_prep_lists(struct xe_guc_ads *ads) offsetof(struct __guc_ads_blob, system_info)); /* first, set aside the first page for a capture_list with zero descriptors */ - total_size = PAGE_SIZE; + total_size = SZ_4K; if (!xe_guc_capture_getnullheader(guc, &ptr, &size)) xe_map_memcpy_to(ads_to_xe(ads), ads_to_map(ads), capture_offset, ptr, size); null_ggtt = ads_ggtt + capture_offset; - capture_offset += PAGE_SIZE; + capture_offset += SZ_4K; /* * Populate capture list : at this point adps is already allocated and @@ -694,8 +694,8 @@ static int guc_capture_prep_lists(struct xe_guc_ads *ads) if (ads->capture_size != PAGE_ALIGN(total_size)) xe_gt_dbg(gt, "Updated ADS capture size %d (was %d)\n", - PAGE_ALIGN(total_size), ads->capture_size); - return PAGE_ALIGN(total_size); + ALIGN(total_size, SZ_4K), ads->capture_size); + return ALIGN(total_size, SZ_4K); } static void guc_mmio_regset_write_one(struct xe_guc_ads *ads, @@ -963,7 +963,7 @@ static void guc_golden_lrc_populate(struct xe_guc_ads *ads) xe_gt_assert(gt, gt->default_lrc[class]); real_size = xe_gt_lrc_size(gt, class); - alloc_size = PAGE_ALIGN(real_size); + alloc_size = ALIGN(real_size, SZ_4K); total_size += alloc_size; xe_map_memcpy_to(xe, ads_to_map(ads), offset, diff --git a/drivers/gpu/drm/xe/xe_guc_capture.c b/drivers/gpu/drm/xe/xe_guc_capture.c index 2f5816c78fba21..0d67244895ece9 100644 --- a/drivers/gpu/drm/xe/xe_guc_capture.c +++ b/drivers/gpu/drm/xe/xe_guc_capture.c @@ -615,8 +615,8 @@ guc_capture_getlistsize(struct xe_guc *guc, u32 owner, u32 type, return -ENODATA; if (size) - *size = PAGE_ALIGN((sizeof(struct guc_debug_capture_list)) + - (num_regs * sizeof(struct guc_mmio_reg))); + *size = ALIGN((sizeof(struct guc_debug_capture_list)) + + (num_regs * sizeof(struct guc_mmio_reg)), SZ_4K); return 0; } @@ -763,7 +763,7 @@ size_t xe_guc_capture_ads_input_worst_size(struct xe_guc *guc) * sequence, that is, during the pre-hwconfig phase before we have * the exact engine fusing info. */ - total_size = PAGE_SIZE; /* Pad a page in front for empty lists */ + total_size = SZ_4K; /* Pad a page in front for empty lists */ for (i = 0; i < GUC_CAPTURE_LIST_INDEX_MAX; i++) { for (j = 0; j < GUC_CAPTURE_LIST_CLASS_MAX; j++) { if (xe_guc_capture_getlistsize(guc, i, @@ -783,7 +783,7 @@ size_t xe_guc_capture_ads_input_worst_size(struct xe_guc *guc) total_size += global_size; } - return PAGE_ALIGN(total_size); + return ALIGN(total_size, SZ_4K); } static int guc_capture_output_size_est(struct xe_guc *guc) diff --git a/drivers/gpu/drm/xe/xe_guc_ct.c b/drivers/gpu/drm/xe/xe_guc_ct.c index a11cff7a20be7e..ea0e2d34e03efe 100644 --- a/drivers/gpu/drm/xe/xe_guc_ct.c +++ b/drivers/gpu/drm/xe/xe_guc_ct.c @@ -317,8 +317,8 @@ int xe_guc_ct_init_noalloc(struct xe_guc_ct *ct) struct xe_gt *gt = ct_to_gt(ct); int err; - xe_gt_assert(gt, !(guc_h2g_size() % PAGE_SIZE)); - xe_gt_assert(gt, !(guc_g2h_size() % PAGE_SIZE)); + xe_gt_assert(gt, !(guc_h2g_size() % SZ_4K)); + xe_gt_assert(gt, !(guc_g2h_size() % SZ_4K)); err = drmm_mutex_init(&xe->drm, &ct->lock); if (err) diff --git a/drivers/gpu/drm/xe/xe_guc_pc.c b/drivers/gpu/drm/xe/xe_guc_pc.c index bb8c4e7934926d..fa8fc261071c25 100644 --- a/drivers/gpu/drm/xe/xe_guc_pc.c +++ b/drivers/gpu/drm/xe/xe_guc_pc.c @@ -1221,7 +1221,7 @@ int xe_guc_pc_start(struct xe_guc_pc *pc) { struct xe_device *xe = pc_to_xe(pc); struct xe_gt *gt = pc_to_gt(pc); - u32 size = PAGE_ALIGN(sizeof(struct slpc_shared_data)); + u32 size = ALIGN(sizeof(struct slpc_shared_data), SZ_4K); ktime_t earlier; int ret; @@ -1331,7 +1331,7 @@ int xe_guc_pc_init(struct xe_guc_pc *pc) struct xe_tile *tile = gt_to_tile(gt); struct xe_device *xe = gt_to_xe(gt); struct xe_bo *bo; - u32 size = PAGE_ALIGN(sizeof(struct slpc_shared_data)); + u32 size = ALIGN(sizeof(struct slpc_shared_data), SZ_4K); int err; if (xe->info.skip_guc_pc) diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c index a22413f892a096..e5714c85344757 100644 --- a/drivers/gpu/drm/xe/xe_migrate.c +++ b/drivers/gpu/drm/xe/xe_migrate.c @@ -659,7 +659,7 @@ static void emit_pte(struct xe_migrate *m, u64 addr, flags = 0; bool devmem = false; - addr = xe_res_dma(cur) & PAGE_MASK; + addr = xe_res_dma(cur) & ~XE_PTE_MASK; if (is_vram) { if (vm->flags & XE_VM_FLAG_64K) { u64 va = cur_ofs * XE_PAGE_SIZE / 8; @@ -680,7 +680,7 @@ static void emit_pte(struct xe_migrate *m, bb->cs[bb->len++] = lower_32_bits(addr); bb->cs[bb->len++] = upper_32_bits(addr); - xe_res_next(cur, min_t(u32, size, PAGE_SIZE)); + xe_res_next(cur, min_t(u32, size, SZ_4K)); cur_ofs += 8; } } diff --git a/drivers/gpu/drm/xe/xe_query.c b/drivers/gpu/drm/xe/xe_query.c index d84d6a422c45d6..4c47e88dce0e58 100644 --- a/drivers/gpu/drm/xe/xe_query.c +++ b/drivers/gpu/drm/xe/xe_query.c @@ -345,7 +345,7 @@ static int query_config(struct xe_device *xe, struct drm_xe_device_query *query) config->info[DRM_XE_QUERY_CONFIG_FLAGS] |= DRM_XE_QUERY_CONFIG_FLAG_HAS_PURGING_SUPPORT; config->info[DRM_XE_QUERY_CONFIG_MIN_ALIGNMENT] = - xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K ? SZ_64K : SZ_4K; + xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K ? SZ_64K : PAGE_SIZE; config->info[DRM_XE_QUERY_CONFIG_VA_BITS] = xe->info.va_bits; config->info[DRM_XE_QUERY_CONFIG_MAX_EXEC_QUEUE_PRIORITY] = xe_exec_queue_device_get_max_priority(xe); diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index ff2f580b660ba6..1640864b9c8e13 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1159,6 +1159,14 @@ config HID_SUNPLUS help Support for Sunplus wireless desktop. +config HID_SURFACE + tristate "Microsoft Surface" + depends on SURFACE_AGGREGATOR + help + Say Y here to enable HID driver for Microsoft Surface integrated + keyboard and touchpad. This driver filters out erroneous BTN_0 + (FN key) events that can cause input focus issues. + config HID_RMI tristate "Synaptics RMI4 device support" select RMI4_CORE @@ -1466,6 +1474,10 @@ source "drivers/hid/surface-hid/Kconfig" source "drivers/hid/intel-thc-hid/Kconfig" +source "drivers/hid/ipts/Kconfig" + +source "drivers/hid/ithc/Kconfig" + endif # HID # USB support may be used with HID disabled diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 0597fd6a4ffd32..e71461250b981f 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -178,5 +178,9 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ +obj-$(CONFIG_HID_SURFACE) += hid-surface.o obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ + +obj-$(CONFIG_HID_IPTS) += ipts/ +obj-$(CONFIG_HID_ITHC) += ithc/ diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index b085bed874bf9b..944ed209088882 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -36,7 +36,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -49,6 +52,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); MODULE_LICENSE("GPL"); #include "hid-ids.h" +#include "usbhid/usbhid.h" #include "hid-haptic.h" @@ -79,6 +83,9 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) #define MT_QUIRK_YOGABOOK9I BIT(24) #define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25) +#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(26) +#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(27) +#define MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE BIT(28) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -86,6 +93,10 @@ MODULE_LICENSE("GPL"); #define MT_BUTTONTYPE_CLICKPAD 0 #define MT_BUTTONTYPE_PRESSUREPAD 1 +#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 +#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 +#define MS_TYPE_COVER_APPLICATION 0xff050050 + enum latency_mode { HID_LATENCY_NORMAL = 0, HID_LATENCY_HIGH = 1, @@ -187,6 +198,8 @@ struct mt_device { struct list_head applications; struct list_head reports; + + struct notifier_block pm_notifier; }; static void mt_post_parse_default_settings(struct mt_device *td, @@ -235,6 +248,8 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_APPLE_TOUCHBAR 0x0114 #define MT_CLS_YOGABOOK9I 0x0115 #define MT_CLS_EGALAX_P80H84 0x0116 +#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0117 +#define MT_CLS_SURFACE_TOUCHPAD 0x0118 #define MT_CLS_SIS 0x0457 #define MT_DEFAULT_MAXCONTACT 10 @@ -454,6 +469,21 @@ static const struct mt_class mt_classes[] = { MT_QUIRK_IGNORE_DUPLICATES | MT_QUIRK_CONTACT_CNT_ACCURATE, }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | + MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | + MT_QUIRK_WIN8_PTP_BUTTONS, + .export_all_inputs = true + }, + { .name = MT_CLS_SURFACE_TOUCHPAD, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE + }, { } }; @@ -506,7 +536,6 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) { int ret; u32 size = hid_report_len(report); - u8 *buf; /* * Do not fetch the feature report if the device has been explicitly @@ -515,7 +544,7 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) if (hdev->quirks & HID_QUIRK_NO_INIT_REPORTS) return; - buf = hid_alloc_report_buf(report, GFP_KERNEL); + u8 *buf __free(kfree) = hid_alloc_report_buf(report, GFP_KERNEL); if (!buf) return; @@ -528,7 +557,7 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) /* The report ID in the request and the response should match */ if (report->id != buf[0]) { hid_err(hdev, "Returned feature report did not match the request\n"); - goto free; + return; } ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf, @@ -536,9 +565,6 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) if (ret) dev_warn(&hdev->dev, "failed to report feature\n"); } - -free: - kfree(buf); } static void mt_feature_mapping(struct hid_device *hdev, @@ -1497,6 +1523,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, field->application != HID_CP_CONSUMER_CONTROL && field->application != HID_GD_WIRELESS_RADIO_CTLS && field->application != HID_GD_SYSTEM_MULTIAXIS && + !(field->application == MS_TYPE_COVER_APPLICATION && + application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) return -1; @@ -1524,6 +1553,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, return 1; } + /* + * The Microsoft Surface Pro Typecover has a non-standard HID + * tablet mode switch on a vendor specific usage page with vendor + * specific usage. + */ + if (field->application == MS_TYPE_COVER_APPLICATION && + application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { + usage->type = EV_SW; + usage->code = SW_TABLET_MODE; + *max = SW_MAX; + *bit = hi->input->swbit; + return 1; + } + if (rdata->is_mt_collection) return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, application); @@ -1550,6 +1594,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, { struct mt_device *td = hid_get_drvdata(hdev); struct mt_report_data *rdata; + struct input_dev *input; rdata = mt_find_report_data(td, field->report); if (rdata && rdata->is_mt_collection) { @@ -1557,6 +1602,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, return -1; } + /* + * We own an input device which acts as a tablet mode switch for + * the Surface Pro Typecover. + */ + if (field->application == MS_TYPE_COVER_APPLICATION && + rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { + input = hi->input; + input_set_capability(input, EV_SW, SW_TABLET_MODE); + input_report_switch(input, SW_TABLET_MODE, 0); + return -1; + } + /* let hid-core decide for the others */ return 0; } @@ -1566,11 +1624,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, { struct mt_device *td = hid_get_drvdata(hid); struct mt_report_data *rdata; + struct input_dev *input; rdata = mt_find_report_data(td, field->report); if (rdata && rdata->is_mt_collection) return mt_touch_event(hid, field, usage, value); + if (field->application == MS_TYPE_COVER_APPLICATION && + rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { + input = field->hidinput->input; + input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); + input_sync(input); + return 1; + } + return 0; } @@ -1666,7 +1734,6 @@ static bool mt_need_to_apply_feature(struct hid_device *hdev, struct mt_class *cls = &td->mtclass; struct hid_report *report = field->report; unsigned int index = usage->usage_index; - char *buf; u32 report_len; int max; @@ -1681,17 +1748,18 @@ static bool mt_need_to_apply_feature(struct hid_device *hdev, return false; if (cls->quirks & MT_QUIRK_FORCE_GET_FEATURE) { - report_len = hid_report_len(report); - buf = hid_alloc_report_buf(report, GFP_KERNEL); + char *buf __free(kfree) = hid_alloc_report_buf(report, GFP_KERNEL); + if (!buf) { hid_err(hdev, "failed to allocate buffer for report\n"); return false; } + + report_len = hid_report_len(report); hid_hw_raw_request(hdev, report->id, buf, report_len, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); - kfree(buf); } field->value[index] = td->inputmode_value; @@ -1785,6 +1853,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; } +static int get_type_cover_field(struct hid_report_enum *rep_enum, + struct hid_field **field, int usage) +{ + struct hid_report *rep; + struct hid_field *cur_field; + int i, j; + + list_for_each_entry(rep, &rep_enum->report_list, list) { + for (i = 0; i < rep->maxfield; i++) { + cur_field = rep->field[i]; + if (cur_field->application != MS_TYPE_COVER_APPLICATION) + continue; + for (j = 0; j < cur_field->maxusage; j++) { + if (cur_field->usage[j].hid == usage) { + *field = cur_field; + return true; + } + } + } + } + return false; +} + +static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) +{ + struct hid_field *field; + + if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], + &field, + MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { + hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); + } else { + hid_err(hdev, "couldn't find tablet mode field\n"); + } +} + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) { struct mt_device *td = hid_get_drvdata(hdev); @@ -1843,6 +1947,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) /* force BTN_STYLUS to allow tablet matching in udev */ __set_bit(BTN_STYLUS, hi->input->keybit); break; + case MS_TYPE_COVER_APPLICATION: + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { + suffix = "Tablet Mode Switch"; + request_type_cover_tablet_mode_switch(hdev); + break; + } + fallthrough; default: suffix = "UNKNOWN"; break; @@ -1953,6 +2064,46 @@ static void mt_expired_timeout(struct timer_list *t) clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); } +static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) +{ + struct usb_device *udev = hid_to_usb_dev(hdev); + struct hid_field *field = NULL; + + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + + if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], + &field, + MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } + + field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; + hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); + +out: + pm_runtime_put_sync(&udev->dev); +} + +static int mt_pm_notifier(struct notifier_block *notifier, + unsigned long pm_event, + void *unused) +{ + struct mt_device *td = + container_of(notifier, struct mt_device, pm_notifier); + struct hid_device *hdev = td->hdev; + + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { + if (pm_event == PM_SUSPEND_PREPARE) + update_keyboard_backlight(hdev, 0); + else if (pm_event == PM_POST_SUSPEND) + update_keyboard_backlight(hdev, 1); + } + + return NOTIFY_DONE; +} + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret, i; @@ -1981,6 +2132,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; hid_set_drvdata(hdev, td); + td->pm_notifier.notifier_call = mt_pm_notifier; + register_pm_notifier(&td->pm_notifier); + INIT_LIST_HEAD(&td->applications); INIT_LIST_HEAD(&td->reports); @@ -2019,8 +2173,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) timer_setup(&td->release_timer, mt_expired_timeout, 0); ret = hid_parse(hdev); - if (ret != 0) + if (ret != 0) { + unregister_pm_notifier(&td->pm_notifier); return ret; + } if (mtclass->name == MT_CLS_APPLE_TOUCHBAR && !hid_find_field(hdev, HID_INPUT_REPORT, @@ -2034,8 +2190,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) hdev->quirks |= HID_QUIRK_NOGET; ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); - if (ret) + if (ret) { + unregister_pm_notifier(&td->pm_notifier); return ret; + } ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); if (ret) @@ -2074,13 +2232,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) static int mt_reset_resume(struct hid_device *hdev) { + struct mt_device *td = hid_get_drvdata(hdev); + mt_release_contacts(hdev); mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + + /* Request an update on the typecover folding state on resume + * after reset. + */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) + request_type_cover_tablet_mode_switch(hdev); + return 0; } static int mt_resume(struct hid_device *hdev) { + struct mt_device *td = hid_get_drvdata(hdev); + /* Some Elan legacy devices require SET_IDLE to be set on resume. * It should be safe to send it to other devices too. * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ @@ -2089,13 +2258,33 @@ static int mt_resume(struct hid_device *hdev) mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + /* Request an update on the typecover folding state on resume. */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) + request_type_cover_tablet_mode_switch(hdev); + return 0; } static void mt_remove(struct hid_device *hdev) { struct mt_device *td = hid_get_drvdata(hdev); + struct hid_field *field; + struct input_dev *input; + + /* Reset tablet mode switch on disconnect. */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { + if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], + &field, + MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { + input = field->hidinput->input; + input_report_switch(input, SW_TABLET_MODE, 0); + input_sync(input); + } else { + hid_err(hdev, "couldn't find tablet mode field\n"); + } + } + unregister_pm_notifier(&td->pm_notifier); timer_delete_sync(&td->release_timer); sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); @@ -2104,6 +2293,17 @@ static void mt_remove(struct hid_device *hdev) static void mt_on_hid_hw_open(struct hid_device *hdev) { + struct mt_device *td = hid_get_drvdata(hdev); + + /* + * Some devices (e.g. Surface Laptop Studio 2 touchpad) can get stuck + * non-functional if we change touchpad reporting modes from the HID + * open/close hooks. Avoid mode switching on hw_open/hw_close for + * those devices. + */ + if (td && td->mtclass.quirks & MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE) + return; + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); } @@ -2111,6 +2311,15 @@ static void mt_on_hid_hw_close(struct hid_device *hdev) { struct mt_device *td = hid_get_drvdata(hdev); + /* + * Some devices (e.g. Surface Laptop Studio 2 touchpad) can get stuck + * non-functional if we change touchpad reporting modes from the HID + * open/close hooks. Avoid mode switching on hw_open/hw_close for + * those devices. + */ + if (td && td->mtclass.quirks & MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE) + return; + if (td->mtclass.quirks & MT_QUIRK_KEEP_LATENCY_ON_CLOSE) mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_NONE); else @@ -2563,6 +2772,16 @@ static const struct hid_device_id mt_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) }, + /* Microsoft Surface type cover */ + { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_MICROSOFT, 0x09c0) }, + + /* Microsoft Surface touch pad */ + { .driver_data = MT_CLS_SURFACE_TOUCHPAD, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_MICROSOFT, 0x0C46) }, + /* Google MT devices */ { .driver_data = MT_CLS_GOOGLE, HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, diff --git a/drivers/hid/hid-surface.c b/drivers/hid/hid-surface.c new file mode 100644 index 00000000000000..a171ea65672fea --- /dev/null +++ b/drivers/hid/hid-surface.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Microsoft Surface devices + * + * Copyright (c) 2025 Linux Surface Project + */ + +#include +#include +#include +#include + +#include "hid-ids.h" + +/* + * The Surface Aggregator Module firmware incorrectly reports the FN key + * as BTN_0 (button 256). This button can get stuck in pressed state, + * flooding the input system and breaking focus tracking in some + * compositors. Filter out BTN_0 as FN should be handled as a hardware + * modifier, not reported to the OS. + */ +static int surface_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + /* + * Filter BTN_0 during input mapping in case it appears in the + * HID descriptor (defense in depth). + */ + if (usage->type == EV_KEY && usage->code == BTN_0) + return -1; /* Don't map this usage */ + + return 0; /* Use default mapping */ +} + +static int surface_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + /* + * The Surface Aggregator Module firmware reports the FN key as BTN_0 + * at runtime. This button can get stuck in pressed state, flooding + * the input system and breaking focus tracking. Filter out these + * events as FN should be a hardware modifier, not reported to the OS. + */ + if (usage->type == EV_KEY && usage->code == BTN_0) + return 1; /* Event handled, don't process further */ + + return 0; /* Process event normally */ +} + +static int surface_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "hw start failed\n"); + return ret; + } + + return 0; +} + +static const struct hid_device_id surface_devices[] = { + { HID_DEVICE(BUS_HOST, HID_GROUP_GENERIC, + USB_VENDOR_ID_MICROSOFT, 0x09AE) }, /* Surface Keyboard */ + { HID_DEVICE(BUS_HOST, HID_GROUP_GENERIC, + USB_VENDOR_ID_MICROSOFT, 0x09AF) }, /* Surface Mouse/Touchpad */ + { } +}; +MODULE_DEVICE_TABLE(hid, surface_devices); + +static struct hid_driver surface_driver = { + .name = "surface", + .id_table = surface_devices, + .probe = surface_probe, + .input_mapping = surface_input_mapping, + .event = surface_event, +}; +module_hid_driver(surface_driver); + +MODULE_AUTHOR("Linux Surface Project"); +MODULE_DESCRIPTION("Microsoft Surface HID driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/i2c-hid/Makefile b/drivers/hid/i2c-hid/Makefile index 55bd5e0f35af32..38d5d827f3ce4f 100644 --- a/drivers/hid/i2c-hid/Makefile +++ b/drivers/hid/i2c-hid/Makefile @@ -8,7 +8,7 @@ obj-$(CONFIG_I2C_HID_CORE) += i2c-hid.o i2c-hid-objs = i2c-hid-core.o i2c-hid-$(CONFIG_DMI) += i2c-hid-dmi-quirks.o -obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o +obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o i2c-hid-acpi-prp0001.o obj-$(CONFIG_I2C_HID_OF) += i2c-hid-of.o obj-$(CONFIG_I2C_HID_OF_ELAN) += i2c-hid-of-elan.o obj-$(CONFIG_I2C_HID_OF_GOODIX) += i2c-hid-of-goodix.o diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c b/drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c new file mode 100644 index 00000000000000..d2cf4714ae7f1c --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID over I2C driver for PRP0001 devices missing hid-descr-addr + * + * Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3, use + * _HID "PRP0001" with _DSD compatible "hid-over-i2c" but lack "hid-descr-addr" + * from the _DSD. The HID descriptor address is provided only through an ACPI + * _DSM. The TPD0 node in the DSDT shows _DSM Function 1 returning 0x20. + * + * Copyright (C) 2026 谢致邦 (XIE Zhibang) + */ + +#include +#include +#include +#include +#include + +#include "i2c-hid.h" +#include "i2c-hid-acpi.h" + +static int i2c_hid_acpi_prp0001_power_up(struct i2chid_ops *ops) +{ + /* give the device time to power up */ + msleep(750); + return 0; +} + +static struct i2chid_ops i2c_hid_acpi_prp0001_ops = { + .power_up = i2c_hid_acpi_prp0001_power_up, + /* + * No .restore_sequence needed: the _DSM on these devices returns a + * constant (0x20) with no side effects, unlike some PNP0C50 _DSM + * implementations that switch the hardware between PS/2 and I2C modes. + */ +}; + +static int i2c_hid_acpi_prp0001_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct acpi_device *adev; + u16 hid_descriptor_address; + int ret; + + /* If hid-descr-addr is present, let i2c-hid-of handle it */ + if (device_property_present(dev, "hid-descr-addr")) + return -ENODEV; + + adev = ACPI_COMPANION(dev); + if (!adev) + return -ENODEV; + + ret = i2c_hid_acpi_get_descriptor(adev); + if (ret < 0) + return ret; + dev_warn(dev, + "hid-descr-addr device property NOT found, using ACPI _DSM fallback. Contact vendor for firmware update!\n"); + hid_descriptor_address = ret; + + /* + * No acpi_device_fix_up_power() needed: TPD0 has no _PS0, _PS3, _PSC + * or _PRx methods and follows I2C bus power. + */ + return i2c_hid_core_probe(client, &i2c_hid_acpi_prp0001_ops, + hid_descriptor_address, 0); +} + +static const struct of_device_id i2c_hid_acpi_prp0001_of_match[] = { + { .compatible = "hid-over-i2c" }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_hid_acpi_prp0001_of_match); + +static const struct i2c_device_id i2c_hid_acpi_prp0001_id[] = { + { .name = "hid-over-i2c" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, i2c_hid_acpi_prp0001_id); + +static struct i2c_driver i2c_hid_acpi_prp0001_driver = { + .driver = { + .name = "i2c_hid_acpi_prp0001", + .pm = &i2c_hid_core_pm, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + /* + * of_match_ptr() makes this NULL when CONFIG_OF=n, but that's + * fine: the I2C id_table with "hid-over-i2c" handles matching + * via client->name (set by acpi_set_modalias() from the _DSD + * compatible property). + */ + .of_match_table = of_match_ptr(i2c_hid_acpi_prp0001_of_match), + }, + + .probe = i2c_hid_acpi_prp0001_probe, + .remove = i2c_hid_core_remove, + .shutdown = i2c_hid_core_shutdown, + .id_table = i2c_hid_acpi_prp0001_id, +}; + +module_i2c_driver(i2c_hid_acpi_prp0001_driver); + +MODULE_DESCRIPTION("HID over I2C driver for PRP0001 devices missing hid-descr-addr"); +MODULE_AUTHOR("谢致邦 (XIE Zhibang) "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c index abd700a101f46c..13f977d6aab615 100644 --- a/drivers/hid/i2c-hid/i2c-hid-acpi.c +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c @@ -25,9 +25,9 @@ #include #include #include -#include #include "i2c-hid.h" +#include "i2c-hid-acpi.h" struct i2c_hid_acpi { struct i2chid_ops ops; @@ -48,39 +48,11 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = { { } }; -/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */ -static guid_t i2c_hid_guid = - GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555, - 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE); - -static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi) -{ - struct acpi_device *adev = ihid_acpi->adev; - acpi_handle handle = acpi_device_handle(adev); - union acpi_object *obj; - u16 hid_descriptor_address; - - if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0) - return -ENODEV; - - obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL, - ACPI_TYPE_INTEGER); - if (!obj) { - acpi_handle_err(handle, "Error _DSM call to get HID descriptor address failed\n"); - return -ENODEV; - } - - hid_descriptor_address = obj->integer.value; - ACPI_FREE(obj); - - return hid_descriptor_address; -} - static void i2c_hid_acpi_restore_sequence(struct i2chid_ops *ops) { struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops); - i2c_hid_acpi_get_descriptor(ihid_acpi); + i2c_hid_acpi_get_descriptor(ihid_acpi->adev); } static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops) @@ -93,24 +65,28 @@ static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops) static int i2c_hid_acpi_probe(struct i2c_client *client) { struct device *dev = &client->dev; + struct acpi_device *adev = ACPI_COMPANION(dev); struct i2c_hid_acpi *ihid_acpi; u16 hid_descriptor_address; int ret; - ihid_acpi = devm_kzalloc(&client->dev, sizeof(*ihid_acpi), GFP_KERNEL); + if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0) + return -ENODEV; + + ret = i2c_hid_acpi_get_descriptor(adev); + if (ret < 0) + return ret; + hid_descriptor_address = ret; + + ihid_acpi = devm_kzalloc(dev, sizeof(*ihid_acpi), GFP_KERNEL); if (!ihid_acpi) return -ENOMEM; - ihid_acpi->adev = ACPI_COMPANION(dev); + ihid_acpi->adev = adev; ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail; ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence; - ret = i2c_hid_acpi_get_descriptor(ihid_acpi); - if (ret < 0) - return ret; - hid_descriptor_address = ret; - - acpi_device_fix_up_power(ihid_acpi->adev); + acpi_device_fix_up_power(adev); return i2c_hid_core_probe(client, &ihid_acpi->ops, hid_descriptor_address, 0); diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.h b/drivers/hid/i2c-hid/i2c-hid-acpi.h new file mode 100644 index 00000000000000..0bbed1853313dc --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _I2C_HID_ACPI_H +#define _I2C_HID_ACPI_H + +#include +#include + +static inline int i2c_hid_acpi_get_descriptor(struct acpi_device *adev) +{ + /* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */ + static const guid_t i2c_hid_guid = + GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555, + 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE); + + acpi_handle handle = acpi_device_handle(adev); + union acpi_object *obj; + u16 addr; + + obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, + 1, 1, NULL, ACPI_TYPE_INTEGER); + if (!obj) { + acpi_handle_err(handle, + "Error _DSM call to get HID descriptor address failed\n"); + return -ENODEV; + } + + addr = obj->integer.value; + ACPI_FREE(obj); + return addr; +} + +#endif diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig new file mode 100644 index 00000000000000..297401bd388dd9 --- /dev/null +++ b/drivers/hid/ipts/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config HID_IPTS + tristate "Intel Precise Touch & Stylus" + depends on INTEL_MEI + depends on HID + help + Say Y here if your system has a touchscreen using Intels + Precise Touch & Stylus (IPTS) technology. + + If unsure say N. + + To compile this driver as a module, choose M here: the + module will be called ipts. diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile new file mode 100644 index 00000000000000..883896f68e6ada --- /dev/null +++ b/drivers/hid/ipts/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Makefile for the IPTS touchscreen driver +# + +obj-$(CONFIG_HID_IPTS) += ipts.o +ipts-objs := cmd.o +ipts-objs += control.o +ipts-objs += eds1.o +ipts-objs += eds2.o +ipts-objs += hid.o +ipts-objs += main.o +ipts-objs += mei.o +ipts-objs += receiver.o +ipts-objs += resources.o +ipts-objs += thread.o diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c new file mode 100644 index 00000000000000..63a4934bbc5fae --- /dev/null +++ b/drivers/hid/ipts/cmd.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include + +#include "cmd.h" +#include "context.h" +#include "mei.h" +#include "spec-device.h" + +int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, + struct ipts_response *rsp, u64 timeout) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (!rsp) + return -EFAULT; + + /* + * In a response, the command code will have the most significant bit flipped to 1. + * If code is passed to ipts_mei_recv as is, no messages will be received. + */ + ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); + if (ret < 0) + return ret; + + dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); + + /* + * Some devices will always return this error. + * It is allowed to ignore it and to try continuing. + */ + if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) + rsp->status = IPTS_STATUS_SUCCESS; + + return 0; +} + +int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) +{ + struct ipts_command cmd = { 0 }; + + if (!ipts) + return -EFAULT; + + cmd.cmd = code; + + if (data && size > 0) + memcpy(cmd.payload, data, size); + + dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); + return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); +} diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h new file mode 100644 index 00000000000000..2b4079075b6428 --- /dev/null +++ b/drivers/hid/ipts/cmd.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_CMD_H +#define IPTS_CMD_H + +#include + +#include "context.h" +#include "spec-device.h" + +/* + * The default timeout for receiving responses + */ +#define IPTS_CMD_DEFAULT_TIMEOUT 1000 + +/** + * ipts_cmd_recv_timeout() - Receives a response to a command. + * @ipts: The IPTS driver context. + * @code: The type of the command / response. + * @rsp: The address that the received response will be copied to. + * @timeout: How many milliseconds the function will wait at most. + * + * A negative timeout means to wait forever. + * + * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. + */ +int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, + struct ipts_response *rsp, u64 timeout); + +/** + * ipts_cmd_recv() - Receives a response to a command. + * @ipts: The IPTS driver context. + * @code: The type of the command / response. + * @rsp: The address that the received response will be copied to. + * + * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. + */ +static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, + struct ipts_response *rsp) +{ + return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); +} + +/** + * ipts_cmd_send() - Executes a command on the device. + * @ipts: The IPTS driver context. + * @code: The type of the command to execute. + * @data: The payload containing parameters for the command. + * @size: The size of the payload. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); + +#endif /* IPTS_CMD_H */ diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h new file mode 100644 index 00000000000000..ba33259f1f7c5e --- /dev/null +++ b/drivers/hid/ipts/context.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_CONTEXT_H +#define IPTS_CONTEXT_H + +#include +#include +#include +#include +#include +#include +#include + +#include "mei.h" +#include "resources.h" +#include "spec-device.h" +#include "thread.h" + +struct ipts_context { + struct device *dev; + struct ipts_mei mei; + + enum ipts_mode mode; + + /* + * Prevents concurrent GET_FEATURE reports. + */ + struct mutex feature_lock; + struct completion feature_event; + + /* + * These are not inside of struct ipts_resources + * because they don't own the memory they point to. + */ + struct ipts_buffer feature_report; + struct ipts_buffer descriptor; + + bool hid_active; + struct hid_device *hid; + + struct ipts_device_info info; + struct ipts_resources resources; + + struct ipts_thread receiver_loop; +}; + +#endif /* IPTS_CONTEXT_H */ diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c new file mode 100644 index 00000000000000..5360842d260bae --- /dev/null +++ b/drivers/hid/ipts/control.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "cmd.h" +#include "context.h" +#include "control.h" +#include "desc.h" +#include "hid.h" +#include "receiver.h" +#include "resources.h" +#include "spec-data.h" +#include "spec-device.h" + +static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) +{ + int ret = 0; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + if (!info) + return -EFAULT; + + ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); + if (ret) { + dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); + return ret; + } + + ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); + if (ret) { + dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); + return ret; + } + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + memcpy(info, rsp.payload, sizeof(*info)); + return 0; +} + +static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) +{ + int ret = 0; + struct ipts_set_mode cmd = { 0 }; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + cmd.mode = mode; + + ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); + if (ret) { + dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); + return ret; + } + + ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); + if (ret) { + dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); + return ret; + } + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + return 0; +} + +static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) +{ + int i = 0; + int ret = 0; + struct ipts_mem_window cmd = { 0 }; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + if (!res) + return -EFAULT; + + for (i = 0; i < IPTS_BUFFERS; i++) { + cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); + cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); + cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); + cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); + } + + cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); + cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); + + cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); + cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); + + cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); + cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); + + cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; + cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; + + ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); + if (ret) { + dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); + return ret; + } + + ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); + if (ret) { + dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); + return ret; + } + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + return 0; +} + +static int ipts_control_get_descriptor(struct ipts_context *ipts) +{ + int ret = 0; + struct ipts_data_header *header = NULL; + struct ipts_get_descriptor cmd = { 0 }; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + if (!ipts->resources.descriptor.address) + return -EFAULT; + + memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); + + cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); + cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); + cmd.magic = 8; + + ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); + if (ret) { + dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); + return ret; + } + + ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); + if (ret) { + dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); + return ret; + } + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + header = (struct ipts_data_header *)ipts->resources.descriptor.address; + + if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { + ipts->descriptor.address = &header->data[8]; + ipts->descriptor.size = header->size - 8; + + return 0; + } + + return -ENODATA; +} + +int ipts_control_request_flush(struct ipts_context *ipts) +{ + int ret = 0; + struct ipts_quiesce_io cmd = { 0 }; + + if (!ipts) + return -EFAULT; + + ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); + if (ret) + dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); + + return ret; +} + +int ipts_control_wait_flush(struct ipts_context *ipts) +{ + int ret = 0; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); + if (ret) { + dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); + return ret; + } + + if (rsp.status == IPTS_STATUS_TIMEOUT) + return -EAGAIN; + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + return 0; +} + +int ipts_control_request_data(struct ipts_context *ipts) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); + if (ret) + dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); + + return ret; +} + +int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) +{ + int ret = 0; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + if (!shutdown) + ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); + else + ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); + + if (ret) { + if (ret != -EAGAIN) + dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); + + return ret; + } + + /* + * During shutdown, it is possible that the sensor has already been disabled. + */ + if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) + return 0; + + if (rsp.status == IPTS_STATUS_TIMEOUT) + return -EAGAIN; + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + return 0; +} + +int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) +{ + int ret = 0; + struct ipts_feedback cmd = { 0 }; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + cmd.buffer = buffer; + + ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); + if (ret) { + dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); + return ret; + } + + ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); + if (ret) { + dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); + return ret; + } + + /* + * We don't know what feedback data looks like so we are sending zeros. + * See also ipts_control_refill_buffer. + */ + if (rsp.status == IPTS_STATUS_INVALID_PARAMS) + return 0; + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + return 0; +} + +int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, + enum ipts_feedback_data_type type, void *data, size_t size) +{ + struct ipts_feedback_header *header = NULL; + + if (!ipts) + return -EFAULT; + + if (!ipts->resources.hid2me.address) + return -EFAULT; + + memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); + header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; + + header->cmd_type = cmd; + header->data_type = type; + header->size = size; + header->buffer = IPTS_HID2ME_BUFFER; + + if (size + sizeof(*header) > ipts->resources.hid2me.size) + return -EINVAL; + + if (data && size > 0) + memcpy(header->payload, data, size); + + return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); +} + +int ipts_control_start(struct ipts_context *ipts) +{ + int ret = 0; + struct ipts_device_info info = { 0 }; + + if (!ipts) + return -EFAULT; + + dev_info(ipts->dev, "Starting IPTS\n"); + + ret = ipts_control_get_device_info(ipts, &info); + if (ret) { + dev_err(ipts->dev, "Failed to get device info: %d\n", ret); + return ret; + } + + ipts->info = info; + + ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); + if (ret) { + dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); + return ret; + } + + dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); + + /* + * Handle newer devices + */ + if (info.intf_eds > 1) { + /* + * Fetching the descriptor will only work on newer devices. + * For older devices, a fallback descriptor will be used. + */ + ret = ipts_control_get_descriptor(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); + return ret; + } + + /* + * Newer devices can be directly initialized in polling mode. + */ + ipts->mode = IPTS_MODE_POLL; + } + + ret = ipts_control_set_mode(ipts, ipts->mode); + if (ret) { + dev_err(ipts->dev, "Failed to set mode: %d\n", ret); + return ret; + } + + ret = ipts_control_set_mem_window(ipts, &ipts->resources); + if (ret) { + dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); + return ret; + } + + ret = ipts_receiver_start(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); + return ret; + } + + ret = ipts_control_request_data(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to request data: %d\n", ret); + return ret; + } + + ipts_hid_enable(ipts); + + ret = ipts_hid_init(ipts, info); + if (ret) { + dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); + return ret; + } + + return 0; +} + +static int _ipts_control_stop(struct ipts_context *ipts) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + ipts_hid_disable(ipts); + dev_info(ipts->dev, "Stopping IPTS\n"); + + ret = ipts_receiver_stop(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); + return ret; + } + + ret = ipts_resources_free(&ipts->resources); + if (ret) { + dev_err(ipts->dev, "Failed to free resources: %d\n", ret); + return ret; + } + + return 0; +} + +int ipts_control_stop(struct ipts_context *ipts) +{ + int ret = 0; + + ret = _ipts_control_stop(ipts); + if (ret) + return ret; + + ret = ipts_hid_free(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); + return ret; + } + + return 0; +} + +int ipts_control_restart(struct ipts_context *ipts) +{ + int ret = 0; + + ret = _ipts_control_stop(ipts); + if (ret) + return ret; + + /* + * Wait a second to give the sensor time to fully shut down. + */ + msleep(1000); + + ret = ipts_control_start(ipts); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h new file mode 100644 index 00000000000000..26629c5144edb7 --- /dev/null +++ b/drivers/hid/ipts/control.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_CONTROL_H +#define IPTS_CONTROL_H + +#include + +#include "context.h" +#include "spec-data.h" +#include "spec-device.h" + +/** + * ipts_control_request_flush() - Stop the data flow. + * @ipts: The IPTS driver context. + * + * Runs the command to stop the data flow on the device. + * All outstanding data needs to be acknowledged using feedback before the command will return. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_request_flush(struct ipts_context *ipts); + +/** + * ipts_control_wait_flush() - Wait until data flow has been stopped. + * @ipts: The IPTS driver context. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_wait_flush(struct ipts_context *ipts); + +/** + * ipts_control_wait_flush() - Notify the device that the driver can receive new data. + * @ipts: The IPTS driver context. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_request_data(struct ipts_context *ipts); + +/** + * ipts_control_wait_data() - Wait until new data is available. + * @ipts: The IPTS driver context. + * @block: Whether to block execution until data is available. + * + * In poll mode, this function will never return while the data flow is active. Instead, + * the poll will be incremented when new data is available. + * + * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. + */ +int ipts_control_wait_data(struct ipts_context *ipts, bool block); + +/** + * ipts_control_send_feedback() - Submits a feedback buffer to the device. + * @ipts: The IPTS driver context. + * @buffer: The ID of the buffer containing feedback data. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); + +/** + * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. + * @ipts: The IPTS driver context. + * @cmd: The command that will be run on the device. + * @type: The type of the payload that is sent to the device. + * @data: The payload of the feedback command. + * @size: The size of the payload. + * + * HID2ME feedback is a special type of feedback, because it allows interfacing with + * the HID API of the device at any moment, without requiring a buffer that has to + * be acknowledged. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, + enum ipts_feedback_data_type type, void *data, size_t size); + +/** + * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. + * @ipts: The IPTS driver context. + * @buffer: The buffer that has been processed and can be refilled. + * + * Returns: 0 on success, <0 on error. + */ +static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) +{ + /* + * IPTS expects structured data in the feedback buffer matching the buffer that will be + * refilled. We don't know what that data looks like, so we just keep the buffer empty. + * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. + * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling + * the buffers on some devices. + */ + + return ipts_control_send_feedback(ipts, buffer); +} + +/** + * ipts_control_start() - Initialized the device and starts the data flow. + * @ipts: The IPTS driver context. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_start(struct ipts_context *ipts); + +/** + * ipts_control_stop() - Stops the data flow and resets the device. + * @ipts: The IPTS driver context. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_stop(struct ipts_context *ipts); + +/** + * ipts_control_restart() - Stops the device and starts it again. + * @ipts: The IPTS driver context. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_restart(struct ipts_context *ipts); + +#endif /* IPTS_CONTROL_H */ diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h new file mode 100644 index 00000000000000..307438c7c80cd7 --- /dev/null +++ b/drivers/hid/ipts/desc.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2022-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_DESC_H +#define IPTS_DESC_H + +#include + +#define IPTS_HID_REPORT_SINGLETOUCH 64 +#define IPTS_HID_REPORT_DATA 65 +#define IPTS_HID_REPORT_SET_MODE 66 + +#define IPTS_HID_REPORT_DATA_SIZE 7485 + +/* + * HID descriptor for singletouch data. + * This descriptor should be present on all IPTS devices. + */ +static const u8 ipts_singletouch_descriptor[] = { + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x04, /* Usage (Touchscreen), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x40, /* Report ID (64), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x07, /* Report Count (7), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x30, /* Usage (X), */ + 0x75, 0x10, /* Report Size (16), */ + 0x95, 0x01, /* Report Count (1), */ + 0xA4, /* Push, */ + 0x55, 0x0E, /* Unit Exponent (14), */ + 0x65, 0x11, /* Unit (Centimeter), */ + 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ + 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x31, /* Usage (Y), */ + 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ + 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ + 0x81, 0x02, /* Input (Variable), */ + 0xB4, /* Pop, */ + 0xC0, /* End Collection */ +}; + +/* + * Fallback HID descriptor for older devices that do not have + * the ability to query their HID descriptor. + */ +static const u8 ipts_fallback_descriptor[] = { + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x41, /* Report ID (65), */ + 0x09, 0x56, /* Usage (Scan Time), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x10, /* Report Size (16), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x61, /* Usage (Gesture Char Quality), */ + 0x75, 0x08, /* Report Size (8), */ + 0x96, 0x3D, 0x1D, /* Report Count (7485), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x85, 0x42, /* Report ID (66), */ + 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ + 0x09, 0xC8, /* Usage (C8h), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x01, /* Report Count (1), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0xC0, /* End Collection, */ +}; + +#endif /* IPTS_DESC_H */ diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c new file mode 100644 index 00000000000000..7b9f54388a9f68 --- /dev/null +++ b/drivers/hid/ipts/eds1.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "desc.h" +#include "eds1.h" +#include "spec-device.h" + +int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) +{ + size_t size = 0; + u8 *buffer = NULL; + + if (!ipts) + return -EFAULT; + + if (!desc_buffer) + return -EFAULT; + + if (!desc_size) + return -EFAULT; + + size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); + + buffer = kzalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); + memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, + sizeof(ipts_fallback_descriptor)); + + *desc_size = size; + *desc_buffer = buffer; + + return 0; +} + +static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (ipts->mode == mode) + return 0; + + ipts->mode = mode; + + ret = ipts_control_restart(ipts); + if (ret) + dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); + + return ret; +} + +int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum hid_report_type report_type, enum hid_class_request request_type) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (!buffer) + return -EFAULT; + + if (report_id != IPTS_HID_REPORT_SET_MODE) + return -EIO; + + if (report_type != HID_FEATURE_REPORT) + return -EIO; + + if (size != 2) + return -EINVAL; + + /* + * Implement mode switching report for older devices without native HID support. + */ + + if (request_type == HID_REQ_GET_REPORT) { + memset(buffer, 0, size); + buffer[0] = report_id; + buffer[1] = ipts->mode; + } else if (request_type == HID_REQ_SET_REPORT) { + return ipts_eds1_switch_mode(ipts, buffer[1]); + } else { + return -EIO; + } + + return ret; +} diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h new file mode 100644 index 00000000000000..eeeb6575e3e89f --- /dev/null +++ b/drivers/hid/ipts/eds1.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include + +#include "context.h" + +/** + * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. + * @ipts: The IPTS driver context. + * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. + * @desc_size: A pointer to the location where the size of the allocated buffer is stored. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); + +/** + * ipts_eds1_raw_request() - Executes an output or feature report on the device. + * @ipts: The IPTS driver context. + * @buffer: The buffer containing the report. + * @size: The size of the buffer. + * @report_id: The HID report ID. + * @report_type: Whether this report is an output or a feature report. + * @request_type: Whether this report requests or sends data. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum hid_report_type report_type, enum hid_class_request request_type); diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c new file mode 100644 index 00000000000000..639940794615df --- /dev/null +++ b/drivers/hid/ipts/eds2.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "desc.h" +#include "eds2.h" +#include "spec-data.h" + +int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) +{ + size_t size = 0; + u8 *buffer = NULL; + + if (!ipts) + return -EFAULT; + + if (!desc_buffer) + return -EFAULT; + + if (!desc_size) + return -EFAULT; + + size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; + + buffer = kzalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); + memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, + ipts->descriptor.size); + + *desc_size = size; + *desc_buffer = buffer; + + return 0; +} + +static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum ipts_feedback_data_type type) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (!buffer) + return -EFAULT; + + mutex_lock(&ipts->feature_lock); + + memset(buffer, 0, size); + buffer[0] = report_id; + + memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); + reinit_completion(&ipts->feature_event); + + ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); + if (ret) { + dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); + goto out; + } + + ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); + if (ret == 0) { + dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); + ret = -EIO; + goto out; + } + + if (!ipts->feature_report.address) { + ret = -EFAULT; + goto out; + } + + if (ipts->feature_report.size > size) { + ret = -ETOOSMALL; + goto out; + } + + ret = ipts->feature_report.size; + memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); + +out: + mutex_unlock(&ipts->feature_lock); + return ret; +} + +static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum ipts_feedback_data_type type) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (!buffer) + return -EFAULT; + + buffer[0] = report_id; + + ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); + if (ret) + dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); + + return ret; +} + +int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum hid_report_type report_type, enum hid_class_request request_type) +{ + enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; + + if (!ipts) + return -EFAULT; + + if (!buffer) + return -EFAULT; + + if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) + feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; + else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) + feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; + else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) + feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; + else + return -EIO; + + if (request_type == HID_REQ_GET_REPORT) + return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); + else + return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); +} diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h new file mode 100644 index 00000000000000..064e3716907ab5 --- /dev/null +++ b/drivers/hid/ipts/eds2.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include + +#include "context.h" + +/** + * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. + * @ipts: The IPTS driver context. + * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. + * @desc_size: A pointer to the location where the size of the allocated buffer is stored. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); + +/** + * ipts_eds2_raw_request() - Executes an output or feature report on the device. + * @ipts: The IPTS driver context. + * @buffer: The buffer containing the report. + * @size: The size of the buffer. + * @report_id: The HID report ID. + * @report_type: Whether this report is an output or a feature report. + * @request_type: Whether this report requests or sends data. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum hid_report_type report_type, enum hid_class_request request_type); diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c new file mode 100644 index 00000000000000..e34a1a4f9fa779 --- /dev/null +++ b/drivers/hid/ipts/hid.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "desc.h" +#include "eds1.h" +#include "eds2.h" +#include "hid.h" +#include "spec-data.h" +#include "spec-hid.h" + +void ipts_hid_enable(struct ipts_context *ipts) +{ + WRITE_ONCE(ipts->hid_active, true); +} + +void ipts_hid_disable(struct ipts_context *ipts) +{ + WRITE_ONCE(ipts->hid_active, false); +} + +static int ipts_hid_start(struct hid_device *hid) +{ + return 0; +} + +static void ipts_hid_stop(struct hid_device *hid) +{ +} + +static int ipts_hid_parse(struct hid_device *hid) +{ + int ret = 0; + struct ipts_context *ipts = NULL; + + u8 *buffer = NULL; + size_t size = 0; + + if (!hid) + return -ENODEV; + + ipts = hid->driver_data; + + if (!ipts) + return -EFAULT; + + if (!READ_ONCE(ipts->hid_active)) + return -ENODEV; + + if (ipts->info.intf_eds == 1) + ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); + else + ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); + + if (ret) { + dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); + return ret; + } + + ret = hid_parse_report(hid, buffer, size); + kfree(buffer); + + if (ret) { + dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); + return ret; + } + + return 0; +} + +static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, + size_t size, unsigned char report_type, int request_type) +{ + struct ipts_context *ipts = NULL; + + if (!hid) + return -ENODEV; + + ipts = hid->driver_data; + + if (!ipts) + return -EFAULT; + + if (!READ_ONCE(ipts->hid_active)) + return -ENODEV; + + if (ipts->info.intf_eds == 1) { + return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, + request_type); + } else { + return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, + request_type); + } +} + +static struct hid_ll_driver ipts_hid_driver = { + .start = ipts_hid_start, + .stop = ipts_hid_stop, + .open = ipts_hid_start, + .close = ipts_hid_stop, + .parse = ipts_hid_parse, + .raw_request = ipts_hid_raw_request, +}; + +int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) +{ + u8 *temp = NULL; + struct ipts_hid_header *frame = NULL; + struct ipts_data_header *header = NULL; + + if (!ipts) + return -EFAULT; + + if (!ipts->hid) + return -ENODEV; + + if (!READ_ONCE(ipts->hid_active)) + return -ENODEV; + + header = (struct ipts_data_header *)ipts->resources.data[buffer].address; + + temp = ipts->resources.report.address; + memset(temp, 0, ipts->resources.report.size); + + if (!header) + return -EFAULT; + + if (header->size == 0) + return 0; + + if (header->type == IPTS_DATA_TYPE_HID) + return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); + + if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { + ipts->feature_report.address = header->data; + ipts->feature_report.size = header->size; + + complete_all(&ipts->feature_event); + return 0; + } + + if (header->type != IPTS_DATA_TYPE_FRAME) + return 0; + + if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) + return -ERANGE; + + /* + * Synthesize a HID report matching the devices that natively send HID reports + */ + temp[0] = IPTS_HID_REPORT_DATA; + + frame = (struct ipts_hid_header *)&temp[3]; + frame->type = IPTS_HID_FRAME_TYPE_RAW; + frame->size = header->size + sizeof(*frame); + + memcpy(frame->data, header->data, header->size); + + return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); +} + +int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (ipts->hid) + return 0; + + ipts->hid = hid_allocate_device(); + if (IS_ERR(ipts->hid)) { + int err = PTR_ERR(ipts->hid); + + dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); + return err; + } + + ipts->hid->driver_data = ipts; + ipts->hid->dev.parent = ipts->dev; + ipts->hid->ll_driver = &ipts_hid_driver; + + ipts->hid->vendor = info.vendor; + ipts->hid->product = info.product; + ipts->hid->group = HID_GROUP_GENERIC; + + snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, + info.product); + + ret = hid_add_device(ipts->hid); + if (ret) { + dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); + ipts_hid_free(ipts); + return ret; + } + + return 0; +} + +int ipts_hid_free(struct ipts_context *ipts) +{ + if (!ipts) + return -EFAULT; + + if (!ipts->hid) + return 0; + + hid_destroy_device(ipts->hid); + ipts->hid = NULL; + + return 0; +} diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h new file mode 100644 index 00000000000000..1ebe77447903ad --- /dev/null +++ b/drivers/hid/ipts/hid.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2022-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_HID_H +#define IPTS_HID_H + +#include + +#include "context.h" +#include "spec-device.h" + +void ipts_hid_enable(struct ipts_context *ipts); +void ipts_hid_disable(struct ipts_context *ipts); + +int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); + +int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); +int ipts_hid_free(struct ipts_context *ipts); + +#endif /* IPTS_HID_H */ diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c new file mode 100644 index 00000000000000..fb5b5c13ee3eaa --- /dev/null +++ b/drivers/hid/ipts/main.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "mei.h" +#include "receiver.h" +#include "spec-device.h" + +/* + * The MEI client ID for IPTS functionality. + */ +#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) + +static int ipts_set_dma_mask(struct mei_cl_device *cldev) +{ + if (!cldev) + return -EFAULT; + + if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) + return 0; + + return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); +} + +static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) +{ + int ret = 0; + struct ipts_context *ipts = NULL; + + if (!cldev) + return -EFAULT; + + ret = ipts_set_dma_mask(cldev); + if (ret) { + dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); + return ret; + } + + ret = mei_cldev_enable(cldev); + if (ret) { + dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); + return ret; + } + + ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); + if (!ipts) { + mei_cldev_disable(cldev); + return -ENOMEM; + } + + ret = ipts_mei_init(&ipts->mei, cldev); + if (ret) { + dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); + return ret; + } + + ipts->dev = &cldev->dev; + ipts->mode = IPTS_MODE_EVENT; + + mutex_init(&ipts->feature_lock); + init_completion(&ipts->feature_event); + + mei_cldev_set_drvdata(cldev, ipts); + + ret = ipts_control_start(ipts); + if (ret) { + dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); + return ret; + } + + return 0; +} + +static void ipts_remove(struct mei_cl_device *cldev) +{ + int ret = 0; + struct ipts_context *ipts = NULL; + + if (!cldev) { + pr_err("MEI device is NULL!"); + return; + } + + ipts = mei_cldev_get_drvdata(cldev); + + ret = ipts_control_stop(ipts); + if (ret) + dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); + + mei_cldev_disable(cldev); +} + +static struct mei_cl_device_id ipts_device_id_table[] = { + { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, + {}, +}; +MODULE_DEVICE_TABLE(mei, ipts_device_id_table); + +static struct mei_cl_driver ipts_driver = { + .id_table = ipts_device_id_table, + .name = "ipts", + .probe = ipts_probe, + .remove = ipts_remove, +}; +module_mei_cl_driver(ipts_driver); + +MODULE_DESCRIPTION("IPTS touchscreen driver"); +MODULE_AUTHOR("Dorian Stoll "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c new file mode 100644 index 00000000000000..1e0395ceae4a4b --- /dev/null +++ b/drivers/hid/ipts/mei.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "mei.h" + +static void locked_list_add(struct list_head *new, struct list_head *head, + struct rw_semaphore *lock) +{ + down_write(lock); + list_add(new, head); + up_write(lock); +} + +static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) +{ + down_write(lock); + list_del(entry); + up_write(lock); +} + +static void ipts_mei_incoming(struct mei_cl_device *cldev) +{ + ssize_t ret = 0; + struct ipts_mei_message *entry = NULL; + struct ipts_context *ipts = NULL; + + if (!cldev) { + pr_err("MEI device is NULL!"); + return; + } + + ipts = mei_cldev_get_drvdata(cldev); + if (!ipts) { + pr_err("IPTS driver context is NULL!"); + return; + } + + entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return; + + INIT_LIST_HEAD(&entry->list); + + do { + ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); + } while (ret == -EINTR); + + if (ret < 0) { + dev_err(ipts->dev, "Error while reading response: %ld\n", ret); + return; + } + + if (ret == 0) { + dev_err(ipts->dev, "Received empty response\n"); + return; + } + + locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); + wake_up_all(&ipts->mei.message_queue); +} + +static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, + struct ipts_response *rsp) +{ + struct ipts_mei_message *entry = NULL; + + if (!mei) + return -EFAULT; + + if (!rsp) + return -EFAULT; + + down_read(&mei->message_lock); + + /* + * Iterate over the list of received messages, and check if there is one + * matching the requested command code. + */ + list_for_each_entry(entry, &mei->messages, list) { + if (entry->rsp.cmd == code) + break; + } + + up_read(&mei->message_lock); + + /* + * If entry is not the list head, this means that the loop above has been stopped early, + * and that we found a matching element. We drop the message from the list and return it. + */ + if (!list_entry_is_head(entry, &mei->messages, list)) { + locked_list_del(&entry->list, &mei->message_lock); + + *rsp = entry->rsp; + devm_kfree(&mei->cldev->dev, entry); + + return 0; + } + + return -EAGAIN; +} + +int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, + u64 timeout) +{ + int ret = 0; + + if (!mei) + return -EFAULT; + + /* + * A timeout of 0 means check and return immideately. + */ + if (timeout == 0) + return ipts_mei_search(mei, code, rsp); + + /* + * A timeout of less than 0 means to wait forever. + */ + if (timeout < 0) { + wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); + return 0; + } + + ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, + msecs_to_jiffies(timeout)); + + if (ret > 0) + return 0; + + return -EAGAIN; +} + +int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) +{ + int ret = 0; + + if (!mei) + return -EFAULT; + + if (!mei->cldev) + return -EFAULT; + + if (!data) + return -EFAULT; + + do { + ret = mei_cldev_send(mei->cldev, (u8 *)data, length); + } while (ret == -EINTR); + + if (ret < 0) + return ret; + + return 0; +} + +int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) +{ + if (!mei) + return -EFAULT; + + if (!cldev) + return -EFAULT; + + mei->cldev = cldev; + + INIT_LIST_HEAD(&mei->messages); + init_waitqueue_head(&mei->message_queue); + init_rwsem(&mei->message_lock); + + mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); + + return 0; +} diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h new file mode 100644 index 00000000000000..973bade6b0fdd1 --- /dev/null +++ b/drivers/hid/ipts/mei.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_MEI_H +#define IPTS_MEI_H + +#include +#include +#include +#include +#include + +#include "spec-device.h" + +struct ipts_mei_message { + struct list_head list; + struct ipts_response rsp; +}; + +struct ipts_mei { + struct mei_cl_device *cldev; + + struct list_head messages; + + wait_queue_head_t message_queue; + struct rw_semaphore message_lock; +}; + +/** + * ipts_mei_recv() - Receive data from a MEI device. + * @mei: The IPTS MEI device context. + * @code: The IPTS command code to look for. + * @rsp: The address that the received data will be copied to. + * @timeout: How many milliseconds the function will wait at most. + * + * A negative timeout means to wait forever. + * + * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. + */ +int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, + u64 timeout); + +/** + * ipts_mei_send() - Send data to a MEI device. + * @ipts: The IPTS MEI device context. + * @data: The data to send. + * @size: The size of the data. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); + +/** + * ipts_mei_init() - Initialize the MEI device context. + * @mei: The MEI device context to initialize. + * @cldev: The MEI device the context will be bound to. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); + +#endif /* IPTS_MEI_H */ diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c new file mode 100644 index 00000000000000..977724c728c3e5 --- /dev/null +++ b/drivers/hid/ipts/receiver.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "cmd.h" +#include "context.h" +#include "control.h" +#include "hid.h" +#include "receiver.h" +#include "resources.h" +#include "spec-device.h" +#include "thread.h" + +static void ipts_receiver_next_doorbell(struct ipts_context *ipts) +{ + u32 *doorbell = (u32 *)ipts->resources.doorbell.address; + *doorbell = *doorbell + 1; +} + +static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) +{ + u32 *doorbell = (u32 *)ipts->resources.doorbell.address; + return *doorbell; +} + +static void ipts_receiver_backoff(time64_t last, u32 n) +{ + /* + * If the last change was less than n seconds ago, + * sleep for a shorter period so that new data can be + * processed quickly. If there was no change for more than + * n seconds, sleep longer to avoid wasting CPU cycles. + */ + if (last + n > ktime_get_seconds()) + usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); + else + msleep(200); +} + +static int ipts_receiver_event_loop(struct ipts_thread *thread) +{ + int ret = 0; + u32 buffer = 0; + + struct ipts_context *ipts = NULL; + time64_t last = ktime_get_seconds(); + + if (!thread) + return -EFAULT; + + ipts = thread->data; + + if (!ipts) + return -EFAULT; + + dev_info(ipts->dev, "IPTS running in event mode\n"); + + while (!ipts_thread_should_stop(thread)) { + int i = 0; + + for (i = 0; i < IPTS_BUFFERS; i++) { + ret = ipts_control_wait_data(ipts, false); + if (ret == -EAGAIN) + break; + + if (ret) { + dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); + continue; + } + + buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; + ipts_receiver_next_doorbell(ipts); + + ret = ipts_hid_input_data(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); + + ret = ipts_control_refill_buffer(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); + + ret = ipts_control_request_data(ipts); + if (ret) + dev_err(ipts->dev, "Failed to request data: %d\n", ret); + + last = ktime_get_seconds(); + } + + ipts_receiver_backoff(last, 5); + } + + ret = ipts_control_request_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to request flush: %d\n", ret); + return ret; + } + + ret = ipts_control_wait_data(ipts, true); + if (ret) { + dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); + + if (ret != -EAGAIN) + return ret; + else + return 0; + } + + ret = ipts_control_wait_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); + + if (ret != -EAGAIN) + return ret; + else + return 0; + } + + return 0; +} + +static int ipts_receiver_poll_loop(struct ipts_thread *thread) +{ + int ret = 0; + u32 buffer = 0; + + u32 doorbell = 0; + u32 lastdb = 0; + + struct ipts_context *ipts = NULL; + time64_t last = ktime_get_seconds(); + + if (!thread) + return -EFAULT; + + ipts = thread->data; + + if (!ipts) + return -EFAULT; + + dev_info(ipts->dev, "IPTS running in poll mode\n"); + + while (true) { + if (ipts_thread_should_stop(thread)) { + ret = ipts_control_request_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to request flush: %d\n", ret); + return ret; + } + } + + doorbell = ipts_receiver_current_doorbell(ipts); + + /* + * After filling up one of the data buffers, IPTS will increment + * the doorbell. The value of the doorbell stands for the *next* + * buffer that IPTS is going to fill. + */ + while (lastdb != doorbell) { + buffer = lastdb % IPTS_BUFFERS; + + ret = ipts_hid_input_data(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); + + ret = ipts_control_refill_buffer(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); + + last = ktime_get_seconds(); + lastdb++; + } + + if (ipts_thread_should_stop(thread)) + break; + + ipts_receiver_backoff(last, 5); + } + + ret = ipts_control_wait_data(ipts, true); + if (ret) { + dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); + + if (ret != -EAGAIN) + return ret; + else + return 0; + } + + ret = ipts_control_wait_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); + + if (ret != -EAGAIN) + return ret; + else + return 0; + } + + return 0; +} + +int ipts_receiver_start(struct ipts_context *ipts) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (ipts->mode == IPTS_MODE_EVENT) { + ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, + "ipts_event"); + } else if (ipts->mode == IPTS_MODE_POLL) { + ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, + "ipts_poll"); + } else { + ret = -EINVAL; + } + + if (ret) { + dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); + return ret; + } + + return 0; +} + +int ipts_receiver_stop(struct ipts_context *ipts) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + ret = ipts_thread_stop(&ipts->receiver_loop); + if (ret) { + dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); + return ret; + } + + return 0; +} diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h new file mode 100644 index 00000000000000..3de7da62d40c14 --- /dev/null +++ b/drivers/hid/ipts/receiver.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_RECEIVER_H +#define IPTS_RECEIVER_H + +#include "context.h" + +int ipts_receiver_start(struct ipts_context *ipts); +int ipts_receiver_stop(struct ipts_context *ipts); + +#endif /* IPTS_RECEIVER_H */ diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c new file mode 100644 index 00000000000000..cc14653b2a9f59 --- /dev/null +++ b/drivers/hid/ipts/resources.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include + +#include "desc.h" +#include "resources.h" +#include "spec-device.h" + +static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) +{ + if (!buffer) + return -EFAULT; + + if (buffer->address) + return 0; + + buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); + + if (!buffer->address) + return -ENOMEM; + + buffer->size = size; + buffer->device = dev; + + return 0; +} + +static void ipts_resources_free_buffer(struct ipts_buffer *buffer) +{ + if (!buffer->address) + return; + + dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); + + buffer->address = NULL; + buffer->size = 0; + + buffer->dma_address = 0; + buffer->device = NULL; +} + +int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) +{ + int ret = 0; + + /* + * Some compilers (AOSP clang) complain about a redefined + * variable when this is declared inside of the for loop. + */ + int i = 0; + + if (!res) + return -EFAULT; + + for (i = 0; i < IPTS_BUFFERS; i++) { + ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); + if (ret) + goto err; + } + + for (i = 0; i < IPTS_BUFFERS; i++) { + ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); + if (ret) + goto err; + } + + ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); + if (ret) + goto err; + + ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); + if (ret) + goto err; + + ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); + if (ret) + goto err; + + ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); + if (ret) + goto err; + + if (!res->report.address) { + res->report.size = IPTS_HID_REPORT_DATA_SIZE; + res->report.address = kzalloc(res->report.size, GFP_KERNEL); + + if (!res->report.address) { + ret = -ENOMEM; + goto err; + } + } + + return 0; + +err: + + ipts_resources_free(res); + return ret; +} + +int ipts_resources_free(struct ipts_resources *res) +{ + int i = 0; + + if (!res) + return -EFAULT; + + for (i = 0; i < IPTS_BUFFERS; i++) + ipts_resources_free_buffer(&res->data[i]); + + for (i = 0; i < IPTS_BUFFERS; i++) + ipts_resources_free_buffer(&res->feedback[i]); + + ipts_resources_free_buffer(&res->doorbell); + ipts_resources_free_buffer(&res->workqueue); + ipts_resources_free_buffer(&res->hid2me); + ipts_resources_free_buffer(&res->descriptor); + + kfree(res->report.address); + res->report.address = NULL; + res->report.size = 0; + + return 0; +} diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h new file mode 100644 index 00000000000000..2068e13285f0ef --- /dev/null +++ b/drivers/hid/ipts/resources.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_RESOURCES_H +#define IPTS_RESOURCES_H + +#include +#include + +#include "spec-device.h" + +struct ipts_buffer { + u8 *address; + size_t size; + + dma_addr_t dma_address; + struct device *device; +}; + +struct ipts_resources { + struct ipts_buffer data[IPTS_BUFFERS]; + struct ipts_buffer feedback[IPTS_BUFFERS]; + + struct ipts_buffer doorbell; + struct ipts_buffer workqueue; + struct ipts_buffer hid2me; + + struct ipts_buffer descriptor; + + // Buffer for synthesizing HID reports + struct ipts_buffer report; +}; + +int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); +int ipts_resources_free(struct ipts_resources *res); + +#endif /* IPTS_RESOURCES_H */ diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h new file mode 100644 index 00000000000000..e8dd98895a7eee --- /dev/null +++ b/drivers/hid/ipts/spec-data.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_SPEC_DATA_H +#define IPTS_SPEC_DATA_H + +#include +#include + +/** + * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. + */ +enum ipts_feedback_cmd_type { + IPTS_FEEDBACK_CMD_TYPE_NONE = 0, + IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, + IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, + IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, + IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, + IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, + IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, +}; + +/** + * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. + * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. + * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. + * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. + * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. + * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. + */ +enum ipts_feedback_data_type { + IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, + IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, + IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, + IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, + IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, +}; + +/** + * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. + * @cmd_type: A command that should be executed on the sensor. + * @size: The size of the payload to be written. + * @buffer: The ID of the buffer that contains this feedback data. + * @protocol: The protocol version of the EDS. + * @data_type: The type of data that the buffer contains. + * @spi_offset: The offset at which to write the payload data to the sensor. + * @payload: Payload for the feedback command, or 0 if no payload is sent. + */ +struct ipts_feedback_header { + enum ipts_feedback_cmd_type cmd_type; + u32 size; + u32 buffer; + u32 protocol; + enum ipts_feedback_data_type data_type; + u32 spi_offset; + u8 reserved[40]; + u8 payload[]; +} __packed; + +static_assert(sizeof(struct ipts_feedback_header) == 64); + +/** + * enum ipts_data_type - Defines what type of data a buffer contains. + * @IPTS_DATA_TYPE_FRAME: Raw data frame. + * @IPTS_DATA_TYPE_ERROR: Error data. + * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. + * @IPTS_DATA_TYPE_HID: A HID report. + * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. + */ +enum ipts_data_type { + IPTS_DATA_TYPE_FRAME = 0x00, + IPTS_DATA_TYPE_ERROR = 0x01, + IPTS_DATA_TYPE_VENDOR = 0x02, + IPTS_DATA_TYPE_HID = 0x03, + IPTS_DATA_TYPE_GET_FEATURES = 0x04, + IPTS_DATA_TYPE_DESCRIPTOR = 0x05, +}; + +/** + * struct ipts_data_header - Header that is prefixed to the data in a data buffer. + * @type: What data the buffer contains. + * @size: How much data the buffer contains. + * @buffer: Which buffer the data is in. + */ +struct ipts_data_header { + enum ipts_data_type type; + u32 size; + u32 buffer; + u8 reserved[52]; + u8 data[]; +} __packed; + +static_assert(sizeof(struct ipts_data_header) == 64); + +#endif /* IPTS_SPEC_DATA_H */ diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h new file mode 100644 index 00000000000000..41845f9d902579 --- /dev/null +++ b/drivers/hid/ipts/spec-device.h @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_SPEC_DEVICE_H +#define IPTS_SPEC_DEVICE_H + +#include +#include + +/* + * The amount of buffers that IPTS can use for data transfer. + */ +#define IPTS_BUFFERS 16 + +/* + * The buffer ID that is used for HID2ME feedback + */ +#define IPTS_HID2ME_BUFFER IPTS_BUFFERS + +/** + * enum ipts_command - Commands that can be sent to the IPTS hardware. + * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. + * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. + * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. + * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. + * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. + * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. + * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. + * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. + * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. + */ +enum ipts_command_code { + IPTS_CMD_GET_DEVICE_INFO = 0x01, + IPTS_CMD_SET_MODE = 0x02, + IPTS_CMD_SET_MEM_WINDOW = 0x03, + IPTS_CMD_QUIESCE_IO = 0x04, + IPTS_CMD_READY_FOR_DATA = 0x05, + IPTS_CMD_FEEDBACK = 0x06, + IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, + IPTS_CMD_RESET_SENSOR = 0x0B, + IPTS_CMD_GET_DESCRIPTOR = 0x0F, +}; + +/** + * enum ipts_status - Possible status codes returned by the IPTS device. + * @IPTS_STATUS_SUCCESS: Operation completed successfully. + * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. + * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. + * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. + * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. + * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. + * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. + * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. + * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. + * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. + * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. + * The host can ignore this error and attempt to continue. + * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. + * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. + * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. + * @IPTS_STATUS_TIMEOUT: The operation timed out. + * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. + * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. + * Further progress is not possible. + * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. + * The driver can attempt to continue. + * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. + * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. + */ +enum ipts_status { + IPTS_STATUS_SUCCESS = 0x00, + IPTS_STATUS_INVALID_PARAMS = 0x01, + IPTS_STATUS_ACCESS_DENIED = 0x02, + IPTS_STATUS_CMD_SIZE_ERROR = 0x03, + IPTS_STATUS_NOT_READY = 0x04, + IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, + IPTS_STATUS_NO_SENSOR_FOUND = 0x06, + IPTS_STATUS_OUT_OF_MEMORY = 0x07, + IPTS_STATUS_INTERNAL_ERROR = 0x08, + IPTS_STATUS_SENSOR_DISABLED = 0x09, + IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, + IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, + IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, + IPTS_STATUS_RESET_FAILED = 0x0D, + IPTS_STATUS_TIMEOUT = 0x0E, + IPTS_STATUS_TEST_MODE_FAIL = 0x0F, + IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, + IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, + IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, + IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, +}; + +/** + * struct ipts_command - Message that is sent to the device for calling a command. + * @cmd: The command that will be called. + * @payload: Payload containing parameters for the called command. + */ +struct ipts_command { + enum ipts_command_code cmd; + u8 payload[320]; +} __packed; + +static_assert(sizeof(struct ipts_command) == 324); + +/** + * enum ipts_mode - Configures what data the device produces and how its sent. + * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. + * Older devices will return singletouch data in this mode. + * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. + * Older devices will return multitouch data in this mode. + */ +enum ipts_mode { + IPTS_MODE_EVENT = 0x00, + IPTS_MODE_POLL = 0x01, +}; + +/** + * struct ipts_set_mode - Payload for the SET_MODE command. + * @mode: Changes the mode that IPTS will operate in. + */ +struct ipts_set_mode { + enum ipts_mode mode; + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_set_mode) == 16); + +#define IPTS_WORKQUEUE_SIZE 8192 +#define IPTS_WORKQUEUE_ITEM_SIZE 16 + +/** + * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. + * @data_addr_lower: Lower 32 bits of the data buffer addresses. + * @data_addr_upper: Upper 32 bits of the data buffer addresses. + * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. + * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. + * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. + * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. + * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. + * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. + * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. + * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. + * @hid2me_size: Size of the hid2me feedback buffer. + * @workqueue_item_size: Magic value. Must be 16. + * @workqueue_size: Magic value. Must be 8192. + * + * The workqueue related items in this struct are required for using + * GuC submission with binary processing firmware. Since this driver does + * not use GuC submission and instead exports raw data to userspace, these + * items are not actually used, but they need to be allocated and passed + * to the device, otherwise initialization will fail. + */ +struct ipts_mem_window { + u32 data_addr_lower[IPTS_BUFFERS]; + u32 data_addr_upper[IPTS_BUFFERS]; + u32 workqueue_addr_lower; + u32 workqueue_addr_upper; + u32 doorbell_addr_lower; + u32 doorbell_addr_upper; + u32 feedback_addr_lower[IPTS_BUFFERS]; + u32 feedback_addr_upper[IPTS_BUFFERS]; + u32 hid2me_addr_lower; + u32 hid2me_addr_upper; + u32 hid2me_size; + u8 reserved1; + u8 workqueue_item_size; + u16 workqueue_size; + u8 reserved[32]; +} __packed; + +static_assert(sizeof(struct ipts_mem_window) == 320); + +/** + * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. + */ +struct ipts_quiesce_io { + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_quiesce_io) == 12); + +/** + * struct ipts_feedback - Payload for the FEEDBACK command. + * @buffer: The buffer that the device should refill. + */ +struct ipts_feedback { + u32 buffer; + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_feedback) == 16); + +/** + * enum ipts_reset_type - Possible ways of resetting the device. + * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. + * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. + */ +enum ipts_reset_type { + IPTS_RESET_TYPE_HARD = 0x00, + IPTS_RESET_TYPE_SOFT = 0x01, +}; + +/** + * struct ipts_reset - Payload for the RESET_SENSOR command. + * @type: How the device should get reset. + */ +struct ipts_reset_sensor { + enum ipts_reset_type type; + u8 reserved[4]; +} __packed; + +static_assert(sizeof(struct ipts_reset_sensor) == 8); + +/** + * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. + * @addr_lower: The lower 32 bits of the descriptor buffer address. + * @addr_upper: The upper 32 bits of the descriptor buffer address. + * @magic: A magic value. Must be 8. + */ +struct ipts_get_descriptor { + u32 addr_lower; + u32 addr_upper; + u32 magic; + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_get_descriptor) == 24); + +/* + * The type of a response is indicated by a + * command code, with the most significant bit flipped to 1. + */ +#define IPTS_RSP_BIT BIT(31) + +/** + * struct ipts_response - Data returned from the device in response to a command. + * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). + * @status: The return code of the command. + * @payload: The data that was produced by the command. + */ +struct ipts_response { + enum ipts_command_code cmd; + enum ipts_status status; + u8 payload[80]; +} __packed; + +static_assert(sizeof(struct ipts_response) == 88); + +/** + * struct ipts_device_info - Vendor information of the IPTS device. + * @vendor: Vendor ID of this device. + * @product: Product ID of this device. + * @hw_version: Hardware revision of this device. + * @fw_version: Firmware revision of this device. + * @data_size: Requested size for a data buffer. + * @feedback_size: Requested size for a feedback buffer. + * @mode: Mode that the device currently operates in. + * @max_contacts: Maximum amount of concurrent touches the sensor can process. + * @sensor_min_eds: The minimum EDS version supported by the sensor. + * @sensor_max_eds: The maximum EDS version supported by the sensor. + * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. + * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. + * @intf_eds: The EDS version implemented by the interface between ME and host. + */ +struct ipts_device_info { + u16 vendor; + u16 product; + u32 hw_version; + u32 fw_version; + u32 data_size; + u32 feedback_size; + enum ipts_mode mode; + u8 max_contacts; + u8 reserved1[3]; + u8 sensor_min_eds; + u8 sensor_maj_eds; + u8 me_min_eds; + u8 me_maj_eds; + u8 intf_eds; + u8 reserved2[11]; +} __packed; + +static_assert(sizeof(struct ipts_device_info) == 44); + +#endif /* IPTS_SPEC_DEVICE_H */ diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h new file mode 100644 index 00000000000000..5a58d4a0a610f4 --- /dev/null +++ b/drivers/hid/ipts/spec-hid.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_SPEC_HID_H +#define IPTS_SPEC_HID_H + +#include +#include + +/* + * Made-up type for passing raw IPTS data in a HID report. + */ +#define IPTS_HID_FRAME_TYPE_RAW 0xEE + +/** + * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. + * @size: Size of the data inside the report, including this header. + * @type: What type of data does this report contain. + */ +struct ipts_hid_header { + u32 size; + u8 reserved1; + u8 type; + u8 reserved2; + u8 data[]; +} __packed; + +static_assert(sizeof(struct ipts_hid_header) == 7); + +#endif /* IPTS_SPEC_HID_H */ diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c new file mode 100644 index 00000000000000..355e92bea26f81 --- /dev/null +++ b/drivers/hid/ipts/thread.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include + +#include "thread.h" + +bool ipts_thread_should_stop(struct ipts_thread *thread) +{ + if (!thread) + return false; + + return READ_ONCE(thread->should_stop); +} + +static int ipts_thread_runner(void *data) +{ + int ret = 0; + struct ipts_thread *thread = data; + + if (!thread) + return -EFAULT; + + if (!thread->threadfn) + return -EFAULT; + + ret = thread->threadfn(thread); + complete_all(&thread->done); + + return ret; +} + +int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), + void *data, const char *name) +{ + if (!thread) + return -EFAULT; + + if (!threadfn) + return -EFAULT; + + init_completion(&thread->done); + + thread->data = data; + thread->should_stop = false; + thread->threadfn = threadfn; + + thread->thread = kthread_run(ipts_thread_runner, thread, name); + return PTR_ERR_OR_ZERO(thread->thread); +} + +int ipts_thread_stop(struct ipts_thread *thread) +{ + int ret = 0; + + if (!thread) + return -EFAULT; + + if (!thread->thread) + return 0; + + WRITE_ONCE(thread->should_stop, true); + + /* + * Make sure that the write has gone through before waiting. + */ + wmb(); + + wait_for_completion(&thread->done); + ret = kthread_stop(thread->thread); + + thread->thread = NULL; + thread->data = NULL; + thread->threadfn = NULL; + + return ret; +} diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h new file mode 100644 index 00000000000000..1f966b8b32c45b --- /dev/null +++ b/drivers/hid/ipts/thread.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_THREAD_H +#define IPTS_THREAD_H + +#include +#include +#include + +/* + * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible + * to issue MEI commands from that thread while it shuts itself down. By using a custom + * boolean variable and a completion object, we can call kthread_stop only when the thread + * already finished all of its work and has returned. + */ +struct ipts_thread { + struct task_struct *thread; + + bool should_stop; + struct completion done; + + void *data; + int (*threadfn)(struct ipts_thread *thread); +}; + +/** + * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. + * @thread: The current thread. + * + * Returns: true if the thread should stop, false if not. + */ +bool ipts_thread_should_stop(struct ipts_thread *thread); + +/** + * ipts_thread_start() - Starts an IPTS thread. + * @thread: The thread to initialize and start. + * @threadfn: The function to execute. + * @data: An argument that will be passed to threadfn. + * @name: The name of the new thread. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), + void *data, const char name[]); + +/** + * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. + * @thread: The thread that should stop. + * + * Returns: The return value of the thread function. + */ +int ipts_thread_stop(struct ipts_thread *thread); + +#endif /* IPTS_THREAD_H */ diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild new file mode 100644 index 00000000000000..4937ba1312973a --- /dev/null +++ b/drivers/hid/ithc/Kbuild @@ -0,0 +1,6 @@ +obj-$(CONFIG_HID_ITHC) := ithc.o + +ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o + +ccflags-y := -std=gnu11 -Wno-declaration-after-statement + diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig new file mode 100644 index 00000000000000..ede71302360964 --- /dev/null +++ b/drivers/hid/ithc/Kconfig @@ -0,0 +1,12 @@ +config HID_ITHC + tristate "Intel Touch Host Controller" + depends on PCI + depends on HID + help + Say Y here if your system has a touchscreen using Intels + Touch Host Controller (ITHC / IPTS) technology. + + If unsure say N. + + To compile this driver as a module, choose M here: the + module will be called ithc. diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c new file mode 100644 index 00000000000000..2d8c6afe99663a --- /dev/null +++ b/drivers/hid/ithc/ithc-debug.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +void ithc_log_regs(struct ithc *ithc) +{ + if (!ithc->prev_regs) + return; + u32 __iomem *cur = (__iomem void *)ithc->regs; + u32 *prev = (void *)ithc->prev_regs; + for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { + u32 x = readl(cur + i); + if (x != prev[i]) { + pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); + prev[i] = x; + } + } +} + +static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, + loff_t *offset) +{ + // Debug commands consist of a single letter followed by a list of numbers (decimal or + // hexadecimal, space-separated). + struct ithc *ithc = file_inode(f)->i_private; + char cmd[256]; + if (!ithc || !ithc->pci) + return -ENODEV; + if (!len) + return -EINVAL; + if (len >= sizeof(cmd)) + return -EINVAL; + if (copy_from_user(cmd, buf, len)) + return -EFAULT; + cmd[len] = 0; + if (cmd[len-1] == '\n') + cmd[len-1] = 0; + pci_info(ithc->pci, "debug command: %s\n", cmd); + + // Parse the list of arguments into a u32 array. + u32 n = 0; + const char *s = cmd + 1; + u32 a[32]; + while (*s && *s != '\n') { + if (n >= ARRAY_SIZE(a)) + return -EINVAL; + if (*s++ != ' ') + return -EINVAL; + char *e; + a[n++] = simple_strtoul(s, &e, 0); + if (e == s) + return -EINVAL; + s = e; + } + ithc_log_regs(ithc); + + // Execute the command. + switch (cmd[0]) { + case 'x': // reset + ithc_reset(ithc); + break; + case 'w': // write register: offset mask value + if (n != 3 || (a[0] & 3)) + return -EINVAL; + pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", + a[0], a[2], a[1]); + bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); + break; + case 'r': // read register: offset + if (n != 1 || (a[0] & 3)) + return -EINVAL; + pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], + readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); + break; + case 's': // spi command: cmd offset len data... + // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // set touch cfg: s 6 12 4 XX + if (n < 3 || a[2] > (n - 3) * 4) + return -EINVAL; + pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); + if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) + for (u32 i = 0; i < (a[2] + 3) / 4; i++) + pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); + break; + case 'd': // dma command: cmd len data... + // get report descriptor: d 7 8 0 0 + // enable multitouch: d 3 2 0x0105 + if (n < 1) + return -EINVAL; + pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); + struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; + if (ithc_dma_tx(ithc, &data)) + pci_err(ithc->pci, "dma tx failed\n"); + break; + default: + return -EINVAL; + } + ithc_log_regs(ithc); + return len; +} + +static struct dentry *dbg_dir; + +void __init ithc_debug_init_module(void) +{ + struct dentry *d = debugfs_create_dir(DEVNAME, NULL); + if (IS_ERR(d)) + pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); + else + dbg_dir = d; +} + +void __exit ithc_debug_exit_module(void) +{ + debugfs_remove_recursive(dbg_dir); + dbg_dir = NULL; +} + +static const struct file_operations ithc_debugfops_cmd = { + .owner = THIS_MODULE, + .write = ithc_debugfs_cmd_write, +}; + +static void ithc_debugfs_devres_release(struct device *dev, void *res) +{ + struct dentry **dbgm = res; + debugfs_remove_recursive(*dbgm); +} + +int ithc_debug_init_device(struct ithc *ithc) +{ + if (!dbg_dir) + return -ENOENT; + struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); + if (!dbgm) + return -ENOMEM; + devres_add(&ithc->pci->dev, dbgm); + struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); + if (IS_ERR(dbg)) + return PTR_ERR(dbg); + *dbgm = dbg; + + struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); + if (IS_ERR(cmd)) + return PTR_ERR(cmd); + + return 0; +} + diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h new file mode 100644 index 00000000000000..38c53d916bdb5e --- /dev/null +++ b/drivers/hid/ithc/ithc-debug.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +void ithc_debug_init_module(void); +void ithc_debug_exit_module(void); +int ithc_debug_init_device(struct ithc *ithc); +void ithc_log_regs(struct ithc *ithc); + diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c new file mode 100644 index 00000000000000..bf4eab33062b0e --- /dev/null +++ b/drivers/hid/ithc/ithc-dma.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. +// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. +// This allows each data buffer to consist of multiple non-contiguous blocks of memory. + +static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, + unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) +{ + p->num_pages = num_pages; + p->dir = dir; + // We allocate enough space to have one PRD per data buffer page, however if the data + // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so + // some will remain unused (which is fine). + p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); + p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); + if (!p->addr) + return -ENOMEM; + if (p->dma_addr & (PAGE_SIZE - 1)) + return -EFAULT; + return 0; +} + +// Devres managed sg_table wrapper. +struct ithc_sg_table { + void *addr; + struct sg_table sgt; + enum dma_data_direction dir; +}; +static void ithc_dma_sgtable_free(struct sg_table *sgt) +{ + struct scatterlist *sg; + int i; + for_each_sgtable_sg(sgt, sg, i) { + struct page *p = sg_page(sg); + if (p) + __free_page(p); + } + sg_free_table(sgt); +} +static void ithc_dma_data_devres_release(struct device *dev, void *res) +{ + struct ithc_sg_table *sgt = res; + if (sgt->addr) + vunmap(sgt->addr); + dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); + ithc_dma_sgtable_free(&sgt->sgt); +} + +static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, + struct ithc_dma_data_buffer *b) +{ + // We don't use dma_alloc_coherent() for data buffers, because they don't have to be + // coherent (they are unidirectional) or contiguous (we can use one PRD per page). + // We could use dma_alloc_noncontiguous(), however this still always allocates a single + // DMA mapped segment, which is more restrictive than what we need. + // Instead we use an sg_table of individually allocated pages. + struct page *pages[16]; + if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) + return -EINVAL; + b->active_idx = -1; + struct ithc_sg_table *sgt = devres_alloc( + ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); + if (!sgt) + return -ENOMEM; + sgt->dir = prds->dir; + + if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { + struct scatterlist *sg; + int i; + bool ok = true; + for_each_sgtable_sg(&sgt->sgt, sg, i) { + // NOTE: don't need __GFP_DMA for PCI DMA + struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!p) { + ok = false; + break; + } + sg_set_page(sg, p, PAGE_SIZE, 0); + } + if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { + devres_add(&ithc->pci->dev, sgt); + b->sgt = &sgt->sgt; + b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); + if (!b->addr) + return -ENOMEM; + return 0; + } + ithc_dma_sgtable_free(&sgt->sgt); + } + devres_free(sgt); + return -ENOMEM; +} + +static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, + struct ithc_dma_data_buffer *b, unsigned int idx) +{ + // Give a buffer to the THC. + struct ithc_phys_region_desc *prd = prds->addr; + prd += idx * prds->num_pages; + if (b->active_idx >= 0) { + pci_err(ithc->pci, "buffer already active\n"); + return -EINVAL; + } + b->active_idx = idx; + if (prds->dir == DMA_TO_DEVICE) { + // TX buffer: Caller should have already filled the data buffer, so just fill + // the PRD and flush. + // (TODO: Support multi-page TX buffers. So far no device seems to use or need + // these though.) + if (b->data_size > PAGE_SIZE) + return -EINVAL; + prd->addr = sg_dma_address(b->sgt->sgl) >> 10; + prd->size = b->data_size | PRD_FLAG_END; + flush_kernel_vmap_range(b->addr, b->data_size); + } else if (prds->dir == DMA_FROM_DEVICE) { + // RX buffer: Reset PRDs. + struct scatterlist *sg; + int i; + for_each_sgtable_dma_sg(b->sgt, sg, i) { + prd->addr = sg_dma_address(sg) >> 10; + prd->size = sg_dma_len(sg); + prd++; + } + prd[-1].size |= PRD_FLAG_END; + } + dma_wmb(); // for the prds + dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); + return 0; +} + +static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, + struct ithc_dma_data_buffer *b, unsigned int idx) +{ + // Take a buffer from the THC. + struct ithc_phys_region_desc *prd = prds->addr; + prd += idx * prds->num_pages; + // This is purely a sanity check. We don't strictly need the idx parameter for this + // function, because it should always be the same as active_idx, unless we have a bug. + if (b->active_idx != idx) { + pci_err(ithc->pci, "wrong buffer index\n"); + return -EINVAL; + } + b->active_idx = -1; + if (prds->dir == DMA_FROM_DEVICE) { + // RX buffer: Calculate actual received data size from PRDs. + dma_rmb(); // for the prds + b->data_size = 0; + struct scatterlist *sg; + int i; + for_each_sgtable_dma_sg(b->sgt, sg, i) { + unsigned int size = prd->size; + b->data_size += size & PRD_SIZE_MASK; + if (size & PRD_FLAG_END) + break; + if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { + pci_err(ithc->pci, "truncated prd\n"); + break; + } + prd++; + } + invalidate_kernel_vmap_range(b->addr, b->data_size); + } + dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); + return 0; +} + +int ithc_dma_rx_init(struct ithc *ithc, u8 channel) +{ + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; + mutex_init(&rx->mutex); + + // Allocate buffers. + unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; + pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", + NUM_RX_BUF, ithc->max_rx_size, num_pages); + CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); + for (unsigned int i = 0; i < NUM_RX_BUF; i++) + CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); + + // Init registers. + writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); + lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); + writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); + writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); + u8 head = readb(&ithc->regs->dma_rx[channel].head); + if (head) { + pci_err(ithc->pci, "head is nonzero (%u)\n", head); + return -EIO; + } + + // Init buffers. + for (unsigned int i = 0; i < NUM_RX_BUF; i++) + CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); + + writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); + return 0; +} + +void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) +{ + bitsb_set(&ithc->regs->dma_rx[channel].control, + DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); + CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, + DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); +} + +int ithc_dma_tx_init(struct ithc *ithc) +{ + struct ithc_dma_tx *tx = &ithc->dma_tx; + mutex_init(&tx->mutex); + + // Allocate buffers. + unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; + pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", + ithc->max_tx_size, num_pages); + CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); + CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); + + // Init registers. + lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); + writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); + + // Init buffers. + CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + return 0; +} + +static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) +{ + // Process all filled RX buffers from the ringbuffer. + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; + unsigned int n = rx->num_received; + u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); + while (1) { + u8 tail = n % NUM_RX_BUF; + u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); + writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); + // ringbuffer is full if tail_wrap == head_wrap + // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG + if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) + return 0; + + // take the buffer that the device just filled + struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; + CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); + rx->num_received = ++n; + + // process data + struct ithc_data d; + if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) + (ithc, b->addr, b->data_size, &d) < 0) { + pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", + channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); + print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, + b->addr, min(b->data_size, 0x400u), 0); + } else { + ithc_hid_process_data(ithc, &d); + } + + // give the buffer back to the device + CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); + } +} +int ithc_dma_rx(struct ithc *ithc, u8 channel) +{ + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; + mutex_lock(&rx->mutex); + int ret = ithc_dma_rx_unlocked(ithc, channel); + mutex_unlock(&rx->mutex); + return ret; +} + +static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) +{ + // Send a single TX buffer to the THC. + pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); + CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + + // Fill the TX buffer with header and data. + ssize_t sz; + if (data->type == ITHC_DATA_RAW) { + sz = min(data->size, ithc->max_tx_size); + memcpy(ithc->dma_tx.buf.addr, data->data, sz); + } else { + sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) + (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); + } + ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; + CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + if (sz < 0) { + pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", + data->type, data->size, (int)sz); + return -EINVAL; + } + + // Let the THC process the buffer. + bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); + CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); + writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); + return 0; +} +int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) +{ + mutex_lock(&ithc->dma_tx.mutex); + int ret = ithc_dma_tx_unlocked(ithc, data); + mutex_unlock(&ithc->dma_tx.mutex); + return ret; +} + diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h new file mode 100644 index 00000000000000..1749a5819b3e74 --- /dev/null +++ b/drivers/hid/ithc/ithc-dma.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +#define PRD_SIZE_MASK 0xffffff +#define PRD_FLAG_END 0x1000000 +#define PRD_FLAG_SUCCESS 0x2000000 +#define PRD_FLAG_ERROR 0x4000000 + +struct ithc_phys_region_desc { + u64 addr; // physical addr/1024 + u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds + u32 unused; +}; + +struct ithc_dma_prd_buffer { + void *addr; + dma_addr_t dma_addr; + u32 size; + u32 num_pages; // per data buffer + enum dma_data_direction dir; +}; + +struct ithc_dma_data_buffer { + void *addr; + struct sg_table *sgt; + int active_idx; + u32 data_size; +}; + +struct ithc_dma_tx { + struct mutex mutex; + struct ithc_dma_prd_buffer prds; + struct ithc_dma_data_buffer buf; +}; + +struct ithc_dma_rx { + struct mutex mutex; + u32 num_received; + struct ithc_dma_prd_buffer prds; + struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; +}; + +int ithc_dma_rx_init(struct ithc *ithc, u8 channel); +void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); +int ithc_dma_tx_init(struct ithc *ithc); +int ithc_dma_rx(struct ithc *ithc, u8 channel); +int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); + diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c new file mode 100644 index 00000000000000..065646ab499ef2 --- /dev/null +++ b/drivers/hid/ithc/ithc-hid.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +static int ithc_hid_start(struct hid_device *hdev) { return 0; } +static void ithc_hid_stop(struct hid_device *hdev) { } +static int ithc_hid_open(struct hid_device *hdev) { return 0; } +static void ithc_hid_close(struct hid_device *hdev) { } + +static int ithc_hid_parse(struct hid_device *hdev) +{ + struct ithc *ithc = hdev->driver_data; + const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; + WRITE_ONCE(ithc->hid.parse_done, false); + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); + CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); + if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), + msecs_to_jiffies(200))) { + ithc_log_regs(ithc); + return 0; + } + if (retries > 5) { + ithc_log_regs(ithc); + pci_err(ithc->pci, "failed to read report descriptor\n"); + return -ETIMEDOUT; + } + pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); + } +} + +static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, + size_t len, unsigned char rtype, int reqtype) +{ + struct ithc *ithc = hdev->driver_data; + if (!buf || !len) + return -EINVAL; + + struct ithc_data d = { .size = len, .data = buf }; + buf[0] = reportnum; + + if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { + d.type = ITHC_DATA_OUTPUT_REPORT; + CHECK_RET(ithc_dma_tx, ithc, &d); + return 0; + } + + if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { + d.type = ITHC_DATA_SET_FEATURE; + CHECK_RET(ithc_dma_tx, ithc, &d); + return 0; + } + + if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { + d.type = ITHC_DATA_GET_FEATURE; + d.data = &reportnum; + d.size = 1; + + // Prepare for response. + mutex_lock(&ithc->hid.get_feature_mutex); + ithc->hid.get_feature_buf = buf; + ithc->hid.get_feature_size = len; + mutex_unlock(&ithc->hid.get_feature_mutex); + + // Transmit 'get feature' request. + int r = CHECK(ithc_dma_tx, ithc, &d); + if (!r) { + r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, + !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); + if (!r) + r = -ETIMEDOUT; + else if (r < 0) + r = -EINTR; + else + r = 0; + } + + // If everything went ok, the buffer has been filled with the response data. + // Return the response size. + mutex_lock(&ithc->hid.get_feature_mutex); + ithc->hid.get_feature_buf = NULL; + if (!r) + r = ithc->hid.get_feature_size; + mutex_unlock(&ithc->hid.get_feature_mutex); + return r; + } + + pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", + rtype, reqtype, reportnum); + return -EINVAL; +} + +// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to +// cast away the const to avoid a compiler warning... +#define NOCONST(x) ((void *)x) + +void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) +{ + WARN_ON(!ithc->hid.dev); + if (!ithc->hid.dev) + return; + + switch (d->type) { + + case ITHC_DATA_IGNORE: + return; + + case ITHC_DATA_ERROR: + CHECK(ithc_reset, ithc); + return; + + case ITHC_DATA_REPORT_DESCRIPTOR: + // Response to the report descriptor request sent by ithc_hid_parse(). + CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); + WRITE_ONCE(ithc->hid.parse_done, true); + wake_up(&ithc->hid.wait_parse); + return; + + case ITHC_DATA_INPUT_REPORT: + { + // Standard HID input report. + int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); + if (r < 0) { + pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", + r, d->size, d->size ? *(u8 *)d->data : 0); + print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, + d->data, min(d->size, 0x400u), 0); + } + return; + } + + case ITHC_DATA_GET_FEATURE: + { + // Response to a 'get feature' request sent by ithc_hid_raw_request(). + bool done = false; + mutex_lock(&ithc->hid.get_feature_mutex); + if (ithc->hid.get_feature_buf) { + if (d->size < ithc->hid.get_feature_size) + ithc->hid.get_feature_size = d->size; + memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); + ithc->hid.get_feature_buf = NULL; + done = true; + } + mutex_unlock(&ithc->hid.get_feature_mutex); + if (done) { + wake_up(&ithc->hid.wait_get_feature); + } else { + // Received data without a matching request, or the request already + // timed out. (XXX What's the correct thing to do here?) + CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, + NOCONST(d->data), d->size, 1); + } + return; + } + + default: + pci_err(ithc->pci, "unhandled data type %i\n", d->type); + return; + } +} + +static struct hid_ll_driver ithc_ll_driver = { + .start = ithc_hid_start, + .stop = ithc_hid_stop, + .open = ithc_hid_open, + .close = ithc_hid_close, + .parse = ithc_hid_parse, + .raw_request = ithc_hid_raw_request, +}; + +static void ithc_hid_devres_release(struct device *dev, void *res) +{ + struct hid_device **hidm = res; + if (*hidm) + hid_destroy_device(*hidm); +} + +int ithc_hid_init(struct ithc *ithc) +{ + struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); + if (!hidm) + return -ENOMEM; + devres_add(&ithc->pci->dev, hidm); + struct hid_device *hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + *hidm = hid; + + strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); + strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); + hid->ll_driver = &ithc_ll_driver; + hid->bus = BUS_PCI; + hid->vendor = ithc->vendor_id; + hid->product = ithc->product_id; + hid->version = 0x100; + hid->dev.parent = &ithc->pci->dev; + hid->driver_data = ithc; + + ithc->hid.dev = hid; + + init_waitqueue_head(&ithc->hid.wait_parse); + init_waitqueue_head(&ithc->hid.wait_get_feature); + mutex_init(&ithc->hid.get_feature_mutex); + + return 0; +} + diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h new file mode 100644 index 00000000000000..599eb912c8c845 --- /dev/null +++ b/drivers/hid/ithc/ithc-hid.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +enum ithc_data_type { + ITHC_DATA_IGNORE, + ITHC_DATA_RAW, + ITHC_DATA_ERROR, + ITHC_DATA_REPORT_DESCRIPTOR, + ITHC_DATA_INPUT_REPORT, + ITHC_DATA_OUTPUT_REPORT, + ITHC_DATA_GET_FEATURE, + ITHC_DATA_SET_FEATURE, +}; + +struct ithc_data { + enum ithc_data_type type; + u32 size; + const void *data; +}; + +struct ithc_hid { + struct hid_device *dev; + bool parse_done; + wait_queue_head_t wait_parse; + wait_queue_head_t wait_get_feature; + struct mutex get_feature_mutex; + void *get_feature_buf; + size_t get_feature_size; +}; + +int ithc_hid_init(struct ithc *ithc); +void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); + diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c new file mode 100644 index 00000000000000..8883987fb35219 --- /dev/null +++ b/drivers/hid/ithc/ithc-legacy.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) +#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) + +#define DEVCFG_TOUCH_MASK 0x3f +#define DEVCFG_TOUCH_ENABLE BIT(0) +#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) +#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) +#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) +#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) + +#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" + +#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) +#define DEVCFG_SPI_CLKDIV_8 BIT(4) +#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) +#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) +#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) +#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) +#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) +#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat +#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) +#define DEVCFG_SPI_UNKNOWN_25 BIT(25) +#define DEVCFG_SPI_UNKNOWN_26 BIT(26) +#define DEVCFG_SPI_UNKNOWN_27 BIT(27) +#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this +#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? + +struct ithc_device_config { // (Example values are from an SP7+.) + u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) + u32 error; // 04 = 0x00000000 + u32 dma_buf_sizes; // 08 = 0x000a00ff + u32 touch_cfg; // 0c = 0x0000001c + u32 touch_state; // 10 = 0x0000001c + u32 device_id; // 14 = 0x43495424 = "$TIC" + u32 spi_config; // 18 = 0xfda00a2e + u16 vendor_id; // 1c = 0x045e = Microsoft Corp. + u16 product_id; // 1e = 0x0c1a + u32 revision; // 20 = 0x00000001 + u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) + u32 command; // 28 = 0x00000000 + u32 fw_mode; // 2c = 0x00000000 (for fw update?) + u32 _unknown_30; // 30 = 0x00000000 + u8 eds_minor_ver; // 34 = 0x5e + u8 eds_major_ver; // 35 = 0x03 + u8 interface_rev; // 36 = 0x04 + u8 eu_kernel_ver; // 37 = 0x04 + u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) + u32 _unknown_3c; // 3c = 0x00000002 +}; +static_assert(sizeof(struct ithc_device_config) == 64); + +#define RX_CODE_INPUT_REPORT 3 +#define RX_CODE_FEATURE_REPORT 4 +#define RX_CODE_REPORT_DESCRIPTOR 5 +#define RX_CODE_RESET 7 + +#define TX_CODE_SET_FEATURE 3 +#define TX_CODE_GET_FEATURE 4 +#define TX_CODE_OUTPUT_REPORT 5 +#define TX_CODE_GET_REPORT_DESCRIPTOR 7 + +static int ithc_set_device_enabled(struct ithc *ithc, bool enable) +{ + u32 x = ithc->legacy_touch_cfg = + (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | + DEVCFG_TOUCH_HID_REPORT_ENABLE | + (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); + return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, + offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); +} + +int ithc_legacy_init(struct ithc *ithc) +{ + // Since we don't yet know which SPI config the device wants, use default speed and mode + // initially for reading config data. + CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); + + // Setting the following bit seems to make reading the config more reliable. + bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); + + // Setting this bit may be necessary on ADL devices. + switch (ithc->pci->device) { + case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: + case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: + case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: + case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: + case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: + case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: + bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); + break; + } + + // Take the touch device out of reset. + bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); + bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); + if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) + break; + if (retries > 5) { + pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", + readl(&ithc->regs->irq_cause)); + return -ETIMEDOUT; + } + pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); + bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); + if (msleep_interruptible(1000)) + return -EINTR; + } + ithc_log_regs(ithc); + + CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); + + // Read configuration data. + u32 spi_cfg; + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); + struct ithc_device_config config = { 0 }; + CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); + u32 *p = (void *)&config; + pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + if (config.device_id == DEVCFG_DEVICE_ID_TIC) { + spi_cfg = config.spi_config; + ithc->vendor_id = config.vendor_id; + ithc->product_id = config.product_id; + ithc->product_rev = config.revision; + ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); + ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); + ithc->legacy_touch_cfg = config.touch_cfg; + ithc->have_config = true; + break; + } + if (retries > 10) { + pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", + config.device_id); + return -EIO; + } + pci_warn(ithc->pci, "failed to read config, retrying\n"); + if (msleep_interruptible(100)) + return -EINTR; + } + ithc_log_regs(ithc); + + // Apply SPI config and enable touch device. + CHECK_RET(ithc_set_spi_config, ithc, + DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, + spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : + spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : + SPI_MODE_SINGLE, + SPI_MODE_SINGLE); + CHECK_RET(ithc_set_device_enabled, ithc, true); + ithc_log_regs(ithc); + return 0; +} + +void ithc_legacy_exit(struct ithc *ithc) +{ + CHECK(ithc_set_device_enabled, ithc, false); +} + +int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) +{ + const struct { + u32 code; + u32 data_size; + u32 _unknown[14]; + } *hdr = src; + + if (len < sizeof(*hdr)) + return -ENODATA; + // Note: RX data is not padded, even though TX data must be padded. + if (len != sizeof(*hdr) + hdr->data_size) + return -EMSGSIZE; + + dest->data = hdr + 1; + dest->size = hdr->data_size; + + switch (hdr->code) { + case RX_CODE_RESET: + // The THC sends a reset request when we need to reinitialize the device. + // This usually only happens if we send an invalid command or put the device + // in a bad state. + dest->type = ITHC_DATA_ERROR; + return 0; + case RX_CODE_REPORT_DESCRIPTOR: + // The descriptor is preceded by 8 nul bytes. + if (hdr->data_size < 8) + return -ENODATA; + dest->type = ITHC_DATA_REPORT_DESCRIPTOR; + dest->data = (char *)(hdr + 1) + 8; + dest->size = hdr->data_size - 8; + return 0; + case RX_CODE_INPUT_REPORT: + dest->type = ITHC_DATA_INPUT_REPORT; + return 0; + case RX_CODE_FEATURE_REPORT: + dest->type = ITHC_DATA_GET_FEATURE; + return 0; + default: + return -EINVAL; + } +} + +ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, + size_t maxlen) +{ + struct { + u32 code; + u32 data_size; + } *hdr = dest; + + size_t src_size = src->size; + const void *src_data = src->data; + const u64 get_report_desc_data = 0; + u32 code; + + switch (src->type) { + case ITHC_DATA_SET_FEATURE: + code = TX_CODE_SET_FEATURE; + break; + case ITHC_DATA_GET_FEATURE: + code = TX_CODE_GET_FEATURE; + break; + case ITHC_DATA_OUTPUT_REPORT: + code = TX_CODE_OUTPUT_REPORT; + break; + case ITHC_DATA_REPORT_DESCRIPTOR: + code = TX_CODE_GET_REPORT_DESCRIPTOR; + src_size = sizeof(get_report_desc_data); + src_data = &get_report_desc_data; + break; + default: + return -EINVAL; + } + + // Data must be padded to next 4-byte boundary. + size_t padded = round_up(src_size, 4); + if (sizeof(*hdr) + padded > maxlen) + return -EOVERFLOW; + + // Fill the TX buffer with header and data. + hdr->code = code; + hdr->data_size = src_size; + memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); + + return sizeof(*hdr) + padded; +} + diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h new file mode 100644 index 00000000000000..28d69246207227 --- /dev/null +++ b/drivers/hid/ithc/ithc-legacy.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +int ithc_legacy_init(struct ithc *ithc); +void ithc_legacy_exit(struct ithc *ithc); +int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); +ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, + size_t maxlen); + diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c new file mode 100644 index 00000000000000..094d878d671b31 --- /dev/null +++ b/drivers/hid/ithc/ithc-main.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +MODULE_DESCRIPTION("Intel Touch Host Controller driver"); +MODULE_LICENSE("Dual BSD/GPL"); + +static const struct pci_device_id ithc_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2) }, + // MTL and up are handled by drivers/hid/intel-thc-hid + {} +}; +MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); + +// Module parameters + +static bool ithc_use_polling = false; +module_param_named(poll, ithc_use_polling, bool, 0); +MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); + +// Since all known devices seem to use only channel 1, by default we disable channel 0. +static bool ithc_use_rx0 = false; +module_param_named(rx0, ithc_use_rx0, bool, 0); +MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); + +static bool ithc_use_rx1 = true; +module_param_named(rx1, ithc_use_rx1, bool, 0); +MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); + +static int ithc_active_ltr_us = -1; +module_param_named(activeltr, ithc_active_ltr_us, int, 0); +MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); + +static int ithc_idle_ltr_us = -1; +module_param_named(idleltr, ithc_idle_ltr_us, int, 0); +MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); + +static unsigned int ithc_idle_delay_ms = 1000; +module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); +MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); + +static bool ithc_log_regs_enabled = false; +module_param_named(logregs, ithc_log_regs_enabled, bool, 0); +MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); + +// Interrupts/polling + +static void ithc_disable_interrupts(struct ithc *ithc) +{ + writel(0, &ithc->regs->error_control); + bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); + bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); + bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); + bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); +} + +static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) +{ + writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, + &ithc->regs->dma_rx[channel].status); +} + +static void ithc_clear_interrupts(struct ithc *ithc) +{ + writel(0xffffffff, &ithc->regs->error_flags); + writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); + ithc_clear_dma_rx_interrupts(ithc, 0); + ithc_clear_dma_rx_interrupts(ithc, 1); + writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, + &ithc->regs->dma_tx.status); +} + +static void ithc_idle_timer_callback(struct timer_list *t) +{ + struct ithc *ithc = container_of(t, struct ithc, idle_timer); + ithc_set_ltr_idle(ithc); +} + +static void ithc_process(struct ithc *ithc) +{ + ithc_log_regs(ithc); + + // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. + // It does not appear to automatically go back to idle, so we switch it back after a delay. + mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); + + bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; + bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; + + // Read and clear error bits + u32 err = readl(&ithc->regs->error_flags); + if (err) { + writel(err, &ithc->regs->error_flags); + if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) + pci_err(ithc->pci, "error flags: 0x%08x\n", err); + if (err & ERROR_FLAG_DMA_RX_TIMEOUT) + pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); + } + + // Process DMA rx + if (ithc_use_rx0) { + ithc_clear_dma_rx_interrupts(ithc, 0); + if (rx0) + ithc_dma_rx(ithc, 0); + } + if (ithc_use_rx1) { + ithc_clear_dma_rx_interrupts(ithc, 1); + if (rx1) + ithc_dma_rx(ithc, 1); + } + + ithc_log_regs(ithc); +} + +static irqreturn_t ithc_interrupt_thread(int irq, void *arg) +{ + struct ithc *ithc = arg; + pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", + readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), + readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), + readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), + readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), + readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); + ithc_process(ithc); + return IRQ_HANDLED; +} + +static int ithc_poll_thread(void *arg) +{ + struct ithc *ithc = arg; + unsigned int sleep = 100; + while (!kthread_should_stop()) { + u32 n = ithc->dma_rx[1].num_received; + ithc_process(ithc); + // Decrease polling interval to 20ms if we received data, otherwise slowly + // increase it up to 200ms. + sleep = n != ithc->dma_rx[1].num_received ? 20 + : min(200u, sleep + (sleep >> 4) + 1); + msleep_interruptible(sleep); + } + return 0; +} + +// Device initialization and shutdown + +static void ithc_disable(struct ithc *ithc) +{ + bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); + CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); + bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); + bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); + bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); + bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); + bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); + ithc_disable_interrupts(ithc); + ithc_clear_interrupts(ithc); +} + +static int ithc_init_device(struct ithc *ithc) +{ + // Read ACPI config for QuickSPI mode + struct ithc_acpi_config cfg = { 0 }; + CHECK_RET(ithc_read_acpi_config, ithc, &cfg); + if (!cfg.has_config) + pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); + else + ithc_print_acpi_config(ithc, &cfg); + ithc->use_quickspi = cfg.has_config; + + // Shut down device + ithc_log_regs(ithc); + bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; + ithc_disable(ithc); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); + ithc_log_regs(ithc); + + // If the device was previously enabled, wait a bit to make sure it's fully shut down. + if (was_enabled) + if (msleep_interruptible(100)) + return -EINTR; + + // Set Latency Tolerance Reporting config. The device will automatically + // apply these values depending on whether it is active or idle. + // If active value is too high, DMA buffer data can become truncated. + // By default, we set the active LTR value to 50us, and idle to 100ms. + u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 + : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 + : 50 * 1000; + u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 + : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 + : 100 * 1000 * 1000; + ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); + + if (ithc->use_quickspi) + CHECK_RET(ithc_quickspi_init, ithc, &cfg); + else + CHECK_RET(ithc_legacy_init, ithc); + + return 0; +} + +int ithc_reset(struct ithc *ithc) +{ + // FIXME This should probably do devres_release_group()+ithc_start(). + // But because this is called during DMA processing, that would have to be done + // asynchronously (schedule_work()?). And with extra locking? + pci_err(ithc->pci, "reset\n"); + CHECK(ithc_init_device, ithc); + if (ithc_use_rx0) + ithc_dma_rx_enable(ithc, 0); + if (ithc_use_rx1) + ithc_dma_rx_enable(ithc, 1); + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "reset completed\n"); + return 0; +} + +static void ithc_stop(void *res) +{ + struct ithc *ithc = res; + pci_dbg(ithc->pci, "stopping\n"); + ithc_log_regs(ithc); + + if (ithc->poll_thread) + CHECK(kthread_stop, ithc->poll_thread); + if (ithc->irq >= 0) + disable_irq(ithc->irq); + if (ithc->use_quickspi) + ithc_quickspi_exit(ithc); + else + ithc_legacy_exit(ithc); + ithc_disable(ithc); + timer_delete_sync(&ithc->idle_timer); + + // Clear DMA config. + for (unsigned int i = 0; i < 2; i++) { + CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); + lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); + writeb(0, &ithc->regs->dma_rx[i].num_bufs); + writeb(0, &ithc->regs->dma_rx[i].num_prds); + } + lo_hi_writeq(0, &ithc->regs->dma_tx.addr); + writeb(0, &ithc->regs->dma_tx.num_prds); + + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "stopped\n"); +} + +static void ithc_clear_drvdata(void *res) +{ + struct pci_dev *pci = res; + pci_set_drvdata(pci, NULL); +} + +static int ithc_start(struct pci_dev *pci) +{ + pci_dbg(pci, "starting\n"); + if (pci_get_drvdata(pci)) { + pci_err(pci, "device already initialized\n"); + return -EINVAL; + } + if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) + return -ENOMEM; + + // Allocate/init main driver struct. + struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); + if (!ithc) + return -ENOMEM; + ithc->irq = -1; + ithc->pci = pci; + snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); + pci_set_drvdata(pci, ithc); + CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); + if (ithc_log_regs_enabled) + ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); + + // PCI initialization. + CHECK_RET(pcim_enable_device, pci); + pci_set_master(pci); + CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); + CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); + CHECK_RET(pci_set_power_state, pci, PCI_D0); + ithc->regs = pcim_iomap_table(pci)[0]; + + // Allocate IRQ. + if (!ithc_use_polling) { + CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); + ithc->irq = CHECK(pci_irq_vector, pci, 0); + if (ithc->irq < 0) + return ithc->irq; + } + + // Initialize THC and touch device. + CHECK_RET(ithc_init_device, ithc); + + // Initialize HID and DMA. + CHECK_RET(ithc_hid_init, ithc); + if (ithc_use_rx0) + CHECK_RET(ithc_dma_rx_init, ithc, 0); + if (ithc_use_rx1) + CHECK_RET(ithc_dma_rx_init, ithc, 1); + CHECK_RET(ithc_dma_tx_init, ithc); + + timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); + + // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are + // disabled BEFORE the buffers are freed. + CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); + + // Start polling/IRQ. + if (ithc_use_polling) { + pci_info(pci, "using polling instead of irq\n"); + // Use a thread instead of simple timer because we want to be able to sleep. + ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); + if (IS_ERR(ithc->poll_thread)) { + int err = PTR_ERR(ithc->poll_thread); + ithc->poll_thread = NULL; + return err; + } + } else { + CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, + ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); + } + + if (ithc_use_rx0) + ithc_dma_rx_enable(ithc, 0); + if (ithc_use_rx1) + ithc_dma_rx_enable(ithc, 1); + + // hid_add_device() can only be called after irq/polling is started and DMA is enabled, + // because it calls ithc_hid_parse() which reads the report descriptor via DMA. + CHECK_RET(hid_add_device, ithc->hid.dev); + + CHECK(ithc_debug_init_device, ithc); + + ithc_set_ltr_idle(ithc); + + pci_dbg(pci, "started\n"); + return 0; +} + +static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + pci_dbg(pci, "device probe\n"); + return ithc_start(pci); +} + +static void ithc_remove(struct pci_dev *pci) +{ + pci_dbg(pci, "device remove\n"); + // all cleanup is handled by devres +} + +// For suspend/resume, we just deinitialize and reinitialize everything. +// TODO It might be cleaner to keep the HID device around, however we would then have to signal +// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set +// feature' requests. Hidraw does not seem to have a facility to do that. +static int ithc_suspend(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm suspend\n"); + devres_release_group(dev, ithc_start); + return 0; +} + +static int ithc_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm resume\n"); + return ithc_start(pci); +} + +static int ithc_freeze(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm freeze\n"); + devres_release_group(dev, ithc_start); + return 0; +} + +static int ithc_thaw(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm thaw\n"); + return ithc_start(pci); +} + +static int ithc_restore(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm restore\n"); + return ithc_start(pci); +} + +static struct pci_driver ithc_driver = { + .name = DEVNAME, + .id_table = ithc_pci_tbl, + .probe = ithc_probe, + .remove = ithc_remove, + .driver.pm = &(const struct dev_pm_ops) { + .suspend = ithc_suspend, + .resume = ithc_resume, + .freeze = ithc_freeze, + .thaw = ithc_thaw, + .restore = ithc_restore, + }, + .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, +}; + +static int __init ithc_init(void) +{ + ithc_debug_init_module(); + return pci_register_driver(&ithc_driver); +} + +static void __exit ithc_exit(void) +{ + pci_unregister_driver(&ithc_driver); + ithc_debug_exit_module(); +} + +module_init(ithc_init); +module_exit(ithc_exit); + diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c new file mode 100644 index 00000000000000..e2d1690b8cf8c1 --- /dev/null +++ b/drivers/hid/ithc/ithc-quickspi.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +// Some public THC/QuickSPI documentation can be found in: +// - Intel Firmware Support Package repo: https://github.com/intel/FSP +// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 + +#include "ithc.h" + +static const guid_t guid_hidspi = + GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); +static const guid_t guid_thc_quickspi = + GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); +static const guid_t guid_thc_ltr = + GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); + +// TODO The HIDSPI spec says revision should be 3. Should we try both? +#define DSM_REV 2 + +struct hidspi_header { + u8 type; + u16 len; + u8 id; +} __packed; +static_assert(sizeof(struct hidspi_header) == 4); + +#define HIDSPI_INPUT_TYPE_DATA 1 +#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 +#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 +#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 +#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 +#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 +#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 +#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 +#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 + +#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 +#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 +#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 +#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 +#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 +#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 +#define HIDSPI_OUTPUT_TYPE_COMMAND 7 + +struct hidspi_device_descriptor { + u16 wDeviceDescLength; + u16 bcdVersion; + u16 wReportDescLength; + u16 wMaxInputLength; + u16 wMaxOutputLength; + u16 wMaxFragmentLength; + u16 wVendorID; + u16 wProductID; + u16 wVersionID; + u16 wFlags; + u32 dwReserved; +}; +static_assert(sizeof(struct hidspi_device_descriptor) == 24); + +static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) +{ + acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); + union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); + if (!o) + return 0; + if (o->type != ACPI_TYPE_INTEGER) { + pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", + guid, func, o->type); + ACPI_FREE(o); + return -1; + } + pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); + *dest = (u32)o->integer.value; + ACPI_FREE(o); + return 1; +} + +static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) +{ + acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); + union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); + if (!o) + return 0; + if (o->type != ACPI_TYPE_BUFFER) { + pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", + guid, func, o->type); + ACPI_FREE(o); + return -1; + } + if (o->buffer.length != len) { + pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", + guid, func, o->buffer.length, len); + ACPI_FREE(o); + return -1; + } + memcpy(dest, o->buffer.pointer, len); + pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); + ACPI_FREE(o); + return 1; +} + +int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) +{ + int r; + acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); + + cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); + if (!cfg->has_config) + return 0; + + // HIDSPI settings + + r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); + if (r < 0) + return r; + cfg->has_input_report_header_address = r > 0; + if (r > 0 && cfg->input_report_header_address > 0xffffff) { + pci_err(ithc->pci, "Invalid input report header address 0x%x\n", + cfg->input_report_header_address); + return -1; + } + + r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); + if (r < 0) + return r; + cfg->has_input_report_body_address = r > 0; + if (r > 0 && cfg->input_report_body_address > 0xffffff) { + pci_err(ithc->pci, "Invalid input report body address 0x%x\n", + cfg->input_report_body_address); + return -1; + } + + r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); + if (r < 0) + return r; + cfg->has_output_report_body_address = r > 0; + if (r > 0 && cfg->output_report_body_address > 0xffffff) { + pci_err(ithc->pci, "Invalid output report body address 0x%x\n", + cfg->output_report_body_address); + return -1; + } + + r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); + if (r < 0) + return r; + cfg->has_read_opcode = r > 0; + + r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); + if (r < 0) + return r; + cfg->has_write_opcode = r > 0; + + u32 flags; + r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); + if (r < 0) + return r; + cfg->has_read_mode = cfg->has_write_mode = r > 0; + if (r > 0) { + cfg->read_mode = (flags >> 14) & 3; + cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; + } + + // Quick SPI settings + + r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); + if (r < 0) + return r; + cfg->has_spi_frequency = r > 0; + + r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); + if (r < 0) + return r; + cfg->has_limit_packet_size = r > 0; + + r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); + if (r < 0) + return r; + cfg->has_tx_delay = r > 0; + if (r > 0) + cfg->tx_delay &= 0xffff; + + // LTR settings + + r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); + if (r < 0) + return r; + cfg->has_active_ltr = r > 0; + if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { + if (cfg->active_ltr != 0xffffffff) + pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", + cfg->active_ltr); + cfg->active_ltr = 500; + } + + r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); + if (r < 0) + return r; + cfg->has_idle_ltr = r > 0; + if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { + if (cfg->idle_ltr != 0xffffffff) + pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", + cfg->idle_ltr); + cfg->idle_ltr = 500; + if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) + cfg->idle_ltr = cfg->active_ltr; + } + + return 0; +} + +void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) +{ + if (!cfg->has_config) { + pci_info(ithc->pci, "No ACPI config"); + return; + } + + char input_report_header_address[16] = "-"; + if (cfg->has_input_report_header_address) + sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); + char input_report_body_address[16] = "-"; + if (cfg->has_input_report_body_address) + sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); + char output_report_body_address[16] = "-"; + if (cfg->has_output_report_body_address) + sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); + char read_opcode[16] = "-"; + if (cfg->has_read_opcode) + sprintf(read_opcode, "0x%02x", cfg->read_opcode); + char write_opcode[16] = "-"; + if (cfg->has_write_opcode) + sprintf(write_opcode, "0x%02x", cfg->write_opcode); + char read_mode[16] = "-"; + if (cfg->has_read_mode) + sprintf(read_mode, "%i", cfg->read_mode); + char write_mode[16] = "-"; + if (cfg->has_write_mode) + sprintf(write_mode, "%i", cfg->write_mode); + char spi_frequency[16] = "-"; + if (cfg->has_spi_frequency) + sprintf(spi_frequency, "%u", cfg->spi_frequency); + char limit_packet_size[16] = "-"; + if (cfg->has_limit_packet_size) + sprintf(limit_packet_size, "%u", cfg->limit_packet_size); + char tx_delay[16] = "-"; + if (cfg->has_tx_delay) + sprintf(tx_delay, "%u", cfg->tx_delay); + char active_ltr[16] = "-"; + if (cfg->has_active_ltr) + sprintf(active_ltr, "%u", cfg->active_ltr); + char idle_ltr[16] = "-"; + if (cfg->has_idle_ltr) + sprintf(idle_ltr, "%u", cfg->idle_ltr); + + pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", + input_report_header_address, input_report_body_address, output_report_body_address, + read_opcode, write_opcode, read_mode, write_mode, + spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); +} + +static void set_opcode(struct ithc *ithc, size_t i, u8 opcode) +{ + writeb(opcode, &ithc->regs->opcode[i].header); + writeb(opcode, &ithc->regs->opcode[i].single); + writeb(opcode, &ithc->regs->opcode[i].dual); + writeb(opcode, &ithc->regs->opcode[i].quad); +} + +static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) +{ + pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); + + // SPI frequency and mode + if (!cfg->has_spi_frequency || !cfg->spi_frequency) { + pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); + return -EINVAL; + } + unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); + bool clkdiv8 = clkdiv > 7; + if (clkdiv8) + clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); + if (!clkdiv) + clkdiv = 1; + CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, + cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, + cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); + + // SPI addresses and opcodes + if (cfg->has_input_report_header_address) + writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); + if (cfg->has_input_report_body_address) { + writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); + writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); + } + if (cfg->has_output_report_body_address) + writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); + + switch (ithc->pci->device) { + // LKF/TGL don't support QuickSPI. + // For ADL, opcode layout is RX/TX/unused. + case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: + case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: + case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: + case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: + case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: + case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: + if (cfg->has_read_opcode) { + set_opcode(ithc, 0, cfg->read_opcode); + } + if (cfg->has_write_opcode) { + set_opcode(ithc, 1, cfg->write_opcode); + } + break; + // For MTL, opcode layout was changed to RX/RX/TX. + // (RPL layout is unknown.) + default: + if (cfg->has_read_opcode) { + set_opcode(ithc, 0, cfg->read_opcode); + set_opcode(ithc, 1, cfg->read_opcode); + } + if (cfg->has_write_opcode) { + set_opcode(ithc, 2, cfg->write_opcode); + } + break; + } + + ithc_log_regs(ithc); + + // The rest... + bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); + + bitsl(&ithc->regs->quickspi_config1, + QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | + QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), + QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | + QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); + + bitsl(&ithc->regs->quickspi_config2, + QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | + QUICKSPI_CONFIG2_UNKNOWN_12(0xff), + QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | + QUICKSPI_CONFIG2_UNKNOWN_12(2)); + + u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; + bitsl(&ithc->regs->spi_config, + SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), + SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); + + bitsl_set(&ithc->regs->quickspi_config2, + QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); + bitsl(&ithc->regs->quickspi_config2, + QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | + QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | + QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); + + return 0; +} + +static int wait_for_report(struct ithc *ithc) +{ + CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, + DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); + writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); + + u32 h = readl(&ithc->regs->input_header); + ithc_log_regs(ithc); + if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE + || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { + pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); + return -ENODATA; + } + return INPUT_HEADER_REPORT_LENGTH(h) * 4; +} + +static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) +{ + pci_dbg(ithc->pci, "initializing HIDSPI\n"); + + // HIDSPI initialization sequence: + // "1. The host shall invoke the ACPI reset method to clear the device state." + acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); + if (ACPI_FAILURE(s)) { + pci_err(ithc->pci, "ACPI reset failed\n"); + return -EIO; + } + + bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); + + // "2. Within 1 second, the device shall signal an interrupt and make available to the host + // an input report containing a device reset response." + int size = wait_for_report(ithc); + if (size < 0) + return size; + if (size < sizeof(struct hidspi_header)) { + pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); + return -EMSGSIZE; + } + + // "3. The host shall read the reset response from the device at the Input Report addresses + // specified in ACPI." + u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; + struct { + struct hidspi_header header; + union { + struct hidspi_device_descriptor device_desc; + u32 data[16]; + }; + } resp = { 0 }; + if (size > sizeof(resp)) { + pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); + return -EMSGSIZE; + } + CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); + if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { + pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); + return -ENOMSG; + } + + // "4. The host shall then write an Output Report to the device at the Output Report Address + // specified in ACPI, requesting the Device Descriptor from the device." + u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; + struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; + CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); + + // "5. Within 1 second, the device shall signal an interrupt and make available to the host + // an input report containing the Device Descriptor." + size = wait_for_report(ithc); + if (size < 0) + return size; + if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { + pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); + return -EMSGSIZE; + } + + // "6. The host shall read the Device Descriptor from the Input Report addresses specified + // in ACPI." + if (size > sizeof(resp)) { + pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); + return -EMSGSIZE; + } + memset(&resp, 0, sizeof(resp)); + CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); + if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { + pci_err(ithc->pci, "received type %i instead of device descriptor\n", + resp.header.type); + return -ENOMSG; + } + struct hidspi_device_descriptor *d = &resp.device_desc; + if (resp.header.len < sizeof(*d)) { + pci_err(ithc->pci, "response too small for device descriptor (%u)\n", + resp.header.len); + return -EMSGSIZE; + } + if (d->wDeviceDescLength != sizeof(*d)) { + pci_err(ithc->pci, "invalid device descriptor length (%u)\n", + d->wDeviceDescLength); + return -EMSGSIZE; + } + + pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", + d->bcdVersion, d->wReportDescLength, + d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, + d->wVendorID, d->wProductID, d->wVersionID, + d->wFlags, d->dwReserved); + + ithc->vendor_id = d->wVendorID; + ithc->product_id = d->wProductID; + ithc->product_rev = d->wVersionID; + ithc->max_rx_size = max_t(u32, d->wMaxInputLength, + d->wReportDescLength + sizeof(struct hidspi_header)); + ithc->max_tx_size = d->wMaxOutputLength; + ithc->have_config = true; + + // "7. The device and host shall then enter their "Ready" states - where the device may + // begin sending Input Reports, and the device shall be prepared for Output Reports from + // the host." + + return 0; +} + +int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) +{ + bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); + + ithc_log_regs(ithc); + CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); + ithc_log_regs(ithc); + CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); + ithc_log_regs(ithc); + + // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, + // otherwise DMA will not work. Maybe selects between DMA and PIO mode? + bitsl(&ithc->regs->quickspi_config1, + QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); + + // TODO Do we need to set any of the following bits here? + //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); + //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); + //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); + //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); + //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); + + ithc_log_regs(ithc); + + return 0; +} + +void ithc_quickspi_exit(struct ithc *ithc) +{ + // TODO Should we send HIDSPI 'power off' command? + //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; + //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; + //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() +} + +int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) +{ + const struct hidspi_header *hdr = src; + + if (len < sizeof(*hdr)) + return -ENODATA; + // TODO Do we need to handle HIDSPI packet fragmentation? + if (len < sizeof(*hdr) + hdr->len) + return -EMSGSIZE; + if (len > round_up(sizeof(*hdr) + hdr->len, 4)) + return -EMSGSIZE; + + switch (hdr->type) { + case HIDSPI_INPUT_TYPE_RESET_RESPONSE: + // TODO "When the device detects an error condition, it may interrupt and make + // available to the host an Input Report containing an unsolicited Reset Response. + // After receiving an unsolicited Reset Response, the host shall initiate the + // request procedure from step (4) in the [HIDSPI initialization] process." + dest->type = ITHC_DATA_ERROR; + return 0; + case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: + dest->type = ITHC_DATA_REPORT_DESCRIPTOR; + dest->data = hdr + 1; + dest->size = hdr->len; + return 0; + case HIDSPI_INPUT_TYPE_DATA: + case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: + dest->type = ITHC_DATA_INPUT_REPORT; + dest->data = &hdr->id; + dest->size = hdr->len + 1; + return 0; + case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: + dest->type = ITHC_DATA_GET_FEATURE; + dest->data = &hdr->id; + dest->size = hdr->len + 1; + return 0; + case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: + case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: + dest->type = ITHC_DATA_IGNORE; + return 0; + default: + return -EINVAL; + } +} + +ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, + size_t maxlen) +{ + struct hidspi_header *hdr = dest; + + size_t src_size = src->size; + const u8 *src_data = src->data; + u8 type; + + switch (src->type) { + case ITHC_DATA_SET_FEATURE: + type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; + break; + case ITHC_DATA_GET_FEATURE: + type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; + break; + case ITHC_DATA_OUTPUT_REPORT: + type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; + break; + case ITHC_DATA_REPORT_DESCRIPTOR: + type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; + src_size = 0; + break; + default: + return -EINVAL; + } + + u8 id = 0; + if (src_size) { + id = *src_data++; + src_size--; + } + + // Data must be padded to next 4-byte boundary. + size_t padded = round_up(src_size, 4); + if (sizeof(*hdr) + padded > maxlen) + return -EOVERFLOW; + + // Fill the TX buffer with header and data. + hdr->type = type; + hdr->len = (u16)src_size; + hdr->id = id; + memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); + + return sizeof(*hdr) + padded; +} + diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h new file mode 100644 index 00000000000000..74d882f6b2f0a7 --- /dev/null +++ b/drivers/hid/ithc/ithc-quickspi.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +struct ithc_acpi_config { + bool has_config: 1; + bool has_input_report_header_address: 1; + bool has_input_report_body_address: 1; + bool has_output_report_body_address: 1; + bool has_read_opcode: 1; + bool has_write_opcode: 1; + bool has_read_mode: 1; + bool has_write_mode: 1; + bool has_spi_frequency: 1; + bool has_limit_packet_size: 1; + bool has_tx_delay: 1; + bool has_active_ltr: 1; + bool has_idle_ltr: 1; + u32 input_report_header_address; + u32 input_report_body_address; + u32 output_report_body_address; + u8 read_opcode; + u8 write_opcode; + u8 read_mode; + u8 write_mode; + u32 spi_frequency; + u32 limit_packet_size; + u32 tx_delay; // us/10 // TODO use? + u32 active_ltr; // ns/1024 + u32 idle_ltr; // ns/1024 +}; + +int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); +void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); + +int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); +void ithc_quickspi_exit(struct ithc *ithc); +int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); +ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, + size_t maxlen); + diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c new file mode 100644 index 00000000000000..c0f13506af205e --- /dev/null +++ b/drivers/hid/ithc/ithc-regs.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) + +void bitsl(__iomem u32 *reg, u32 mask, u32 val) +{ + if (val & ~mask) + pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", + reg_num(reg), val, mask); + writel((readl(reg) & ~mask) | (val & mask), reg); +} + +void bitsb(__iomem u8 *reg, u8 mask, u8 val) +{ + if (val & ~mask) + pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", + reg_num(reg), val, mask); + writeb((readb(reg) & ~mask) | (val & mask), reg); +} + +int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) +{ + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", + reg_num(reg), mask, val); + u32 x; + if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { + ithc_log_regs(ithc); + pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", + reg_num(reg), mask, val); + return -ETIMEDOUT; + } + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "done waiting\n"); + return 0; +} + +int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) +{ + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", + reg_num(reg), mask, val); + u8 x; + if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { + ithc_log_regs(ithc); + pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", + reg_num(reg), mask, val); + return -ETIMEDOUT; + } + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "done waiting\n"); + return 0; +} + +static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) +{ + unsigned int s = 0; + u64 v = *ns; + while (v > 0x3ff) { + s++; + v >>= 5; + } + if (s > 5) { + s = 5; + v = 0x3ff; + } + *val = v; + *scale = s; + *ns = v << (5 * s); +} + +void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) +{ + unsigned int active_val, active_scale, idle_val, idle_scale; + calc_ltr(&active_ltr_ns, &active_val, &active_scale); + calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); + pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", + active_ltr_ns, idle_ltr_ns); + writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | + LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | + LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), + &ithc->regs->ltr_config); +} + +void ithc_set_ltr_idle(struct ithc *ithc) +{ + u32 ltr = readl(&ithc->regs->ltr_config); + switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { + case LTR_CONFIG_STATUS_IDLE: + break; + case LTR_CONFIG_STATUS_ACTIVE: + writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); + break; + default: + pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); + break; + } +} + +int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) +{ + if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) + return -EINVAL; + static const char * const modes[] = { "single", "dual", "quad" }; + pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", + SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), + modes[read_mode], modes[write_mode]); + bitsl(&ithc->regs->spi_config, + SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | + SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | + SPI_CONFIG_CLKDIV_8, + SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | + SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | + (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); + return 0; +} + +int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) +{ + pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); + if (size > sizeof(ithc->regs->spi_cmd.data)) + return -EINVAL; + + // Wait if the device is still busy. + CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); + // Clear result flags. + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); + + // Init SPI command data. + writeb(command, &ithc->regs->spi_cmd.code); + writew(size, &ithc->regs->spi_cmd.size); + writel(offset, &ithc->regs->spi_cmd.offset); + u32 *p = data, n = (size + 3) / 4; + for (u32 i = 0; i < n; i++) + writel(p[i], &ithc->regs->spi_cmd.data[i]); + + // Start transmission. + bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); + CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); + + // Read response. + if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) + return -EIO; + if (readw(&ithc->regs->spi_cmd.size) != size) + return -EMSGSIZE; + for (u32 i = 0; i < n; i++) + p[i] = readl(&ithc->regs->spi_cmd.data[i]); + + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); + return 0; +} + diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h new file mode 100644 index 00000000000000..4f541fe533facf --- /dev/null +++ b/drivers/hid/ithc/ithc-regs.h @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) +#define LTR_CONFIG_TOGGLE BIT(1) +#define LTR_CONFIG_ENABLE_IDLE BIT(2) +#define LTR_CONFIG_APPLY BIT(3) +#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) +#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) +#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) +#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) +#define LTR_CONFIG_STATUS_ACTIVE BIT(30) +#define LTR_CONFIG_STATUS_IDLE BIT(31) + +#define CONTROL_QUIESCE BIT(1) +#define CONTROL_IS_QUIESCED BIT(2) +#define CONTROL_NRESET BIT(3) +#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) +#define CONTROL_READY BIT(29) + +#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) +#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) +#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) +#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) +#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) +#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write +#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) + +#define SPI_CLK_FREQ_BASE 125000000 +#define SPI_MODE_SINGLE 0 +#define SPI_MODE_DUAL 1 +#define SPI_MODE_QUAD 2 + +#define ERROR_CONTROL_UNKNOWN_0 BIT(0) +#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs +#define ERROR_CONTROL_UNKNOWN_2 BIT(2) +#define ERROR_CONTROL_UNKNOWN_3 BIT(3) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) +#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? +#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs + +#define ERROR_STATUS_DMA BIT(28) +#define ERROR_STATUS_SPI BIT(30) + +#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) +#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) +#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message +#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) +#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) +#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) +#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) +#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) +#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) +#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) + +#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete +#define SPI_CMD_CONTROL_IRQ BIT(1) + +#define SPI_CMD_CODE_READ 4 +#define SPI_CMD_CODE_WRITE 6 + +#define SPI_CMD_STATUS_DONE BIT(0) +#define SPI_CMD_STATUS_ERROR BIT(1) +#define SPI_CMD_STATUS_BUSY BIT(3) + +#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete +#define DMA_TX_CONTROL_IRQ BIT(3) + +#define DMA_TX_STATUS_DONE BIT(0) +#define DMA_TX_STATUS_ERROR BIT(1) +#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) +#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? + +#define INPUT_HEADER_VERSION(x) ((x) & 0xf) +#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) +#define INPUT_HEADER_SYNC(x) ((x) >> 24) +#define INPUT_HEADER_VERSION_VALUE 3 +#define INPUT_HEADER_SYNC_VALUE 0x5a + +#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) +#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) +#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) +#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) + +#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) +#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) +#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) +#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) +#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) +#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) +#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) +#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) +#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) + +#define DMA_RX_CONTROL_ENABLE BIT(0) +#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? +#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? +#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only +#define DMA_RX_CONTROL_IRQ_DATA BIT(5) + +#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? +#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? +#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices + +#define DMA_RX_WRAP_FLAG BIT(7) + +#define DMA_RX_STATUS_ERROR BIT(3) +#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) +#define DMA_RX_STATUS_HAVE_DATA BIT(5) +#define DMA_RX_STATUS_ENABLED BIT(8) + +#define INIT_UNKNOWN_GUC_2 BIT(2) +#define INIT_UNKNOWN_3 BIT(3) +#define INIT_UNKNOWN_GUC_4 BIT(4) +#define INIT_UNKNOWN_5 BIT(5) +#define INIT_UNKNOWN_31 BIT(31) + +// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. +#define COUNTER_RESET BIT(31) + +struct ithc_registers { + /* 0000 */ u32 _unknown_0000[5]; + /* 0014 */ u32 ltr_config; + /* 0018 */ u32 _unknown_0018[1018]; + /* 1000 */ u32 _unknown_1000; + /* 1004 */ u32 _unknown_1004; + /* 1008 */ u32 control_bits; + /* 100c */ u32 _unknown_100c; + /* 1010 */ u32 spi_config; + struct { + /* 1014/1018/101c */ u8 header; + /* 1015/1019/101d */ u8 quad; + /* 1016/101a/101e */ u8 dual; + /* 1017/101b/101f */ u8 single; + } opcode[3]; + /* 1020 */ u32 error_control; + /* 1024 */ u32 error_status; // write to clear + /* 1028 */ u32 error_flags; // write to clear + /* 102c */ u32 _unknown_102c[5]; + struct { + /* 1040 */ u8 control; + /* 1041 */ u8 code; + /* 1042 */ u16 size; + /* 1044 */ u32 status; // write to clear + /* 1048 */ u32 offset; + /* 104c */ u32 data[16]; + /* 108c */ u32 _unknown_108c; + } spi_cmd; + struct { + /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() + /* 1098 */ u8 control; + /* 1099 */ u8 _unknown_1099; + /* 109a */ u8 _unknown_109a; + /* 109b */ u8 num_prds; + /* 109c */ u32 status; // write to clear + /* 10a0 */ u32 _unknown_10a0[5]; + /* 10b4 */ u32 spi_addr; + } dma_tx; + /* 10b8 */ u32 spi_header_addr; + union { + /* 10bc */ u32 irq_cause; // in legacy THC mode + /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) + }; + /* 10c0 */ u32 _unknown_10c0[8]; + /* 10e0 */ u32 _unknown_10e0_counters[3]; + /* 10ec */ u32 quickspi_config1; + /* 10f0 */ u32 quickspi_config2; + /* 10f4 */ u32 _unknown_10f4[3]; + struct { + /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() + /* 1108/1208 */ u8 num_bufs; + /* 1109/1209 */ u8 num_prds; + /* 110a/120a */ u16 _unknown_110a; + /* 110c/120c */ u8 control; + /* 110d/120d */ u8 head; + /* 110e/120e */ u8 tail; + /* 110f/120f */ u8 control2; + /* 1110/1210 */ u32 status; // write to clear + /* 1114/1214 */ u32 _unknown_1114; + /* 1118/1218 */ u64 _unknown_1118_guc_addr; + /* 1120/1220 */ u32 _unknown_1120_guc; + /* 1124/1224 */ u32 _unknown_1124_guc; + /* 1128/1228 */ u32 init_unknown; + /* 112c/122c */ u32 _unknown_112c; + /* 1130/1230 */ u64 _unknown_1130_guc_addr; + /* 1138/1238 */ u32 _unknown_1138_guc; + /* 113c/123c */ u32 _unknown_113c; + /* 1140/1240 */ u32 _unknown_1140_guc; + /* 1144/1244 */ u32 _unknown_1144[11]; + /* 1170/1270 */ u32 spi_addr; + /* 1174/1274 */ u32 _unknown_1174[11]; + /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; + /* 11b8/12b8 */ u32 _unknown_11b8[18]; + } dma_rx[2]; +}; +static_assert(sizeof(struct ithc_registers) == 0x1300); + +void bitsl(__iomem u32 *reg, u32 mask, u32 val); +void bitsb(__iomem u8 *reg, u8 mask, u8 val); +#define bitsl_set(reg, x) bitsl(reg, x, x) +#define bitsb_set(reg, x) bitsb(reg, x, x) +int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); +int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); + +void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); +void ithc_set_ltr_idle(struct ithc *ithc); +int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); +int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); + diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h new file mode 100644 index 00000000000000..aec320d4e945c0 --- /dev/null +++ b/drivers/hid/ithc/ithc.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVNAME "ithc" +#define DEVFULLNAME "Intel Touch Host Controller" + +#undef pr_fmt +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) +#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) + +#define NUM_RX_BUF 16 + +// PCI device IDs: +// Lakefield +#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 +#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 +// Tiger Lake +#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 +#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 +#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 +#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 +// Alder Lake +#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 +#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 +#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 +#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 +#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 +#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 +// Raptor Lake +#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 +#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 +// Meteor Lake +#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT1 0x7f59 +#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b +#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 +#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b + +struct ithc; + +#include "ithc-regs.h" +#include "ithc-hid.h" +#include "ithc-dma.h" +#include "ithc-legacy.h" +#include "ithc-quickspi.h" +#include "ithc-debug.h" + +struct ithc { + char phys[32]; + struct pci_dev *pci; + int irq; + struct task_struct *poll_thread; + struct timer_list idle_timer; + + struct ithc_registers __iomem *regs; + struct ithc_registers *prev_regs; // for debugging + struct ithc_dma_rx dma_rx[2]; + struct ithc_dma_tx dma_tx; + struct ithc_hid hid; + + bool use_quickspi; + bool have_config; + u16 vendor_id; + u16 product_id; + u32 product_rev; + u32 max_rx_size; + u32 max_tx_size; + u32 legacy_touch_cfg; +}; + +int ithc_reset(struct ithc *ithc); + diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 14e4cea48acc47..d0dc67cef19ed8 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -38,6 +38,17 @@ config HWMON_DEBUG_CHIP comment "Native drivers" +config SENSORS_AAEON + tristate "AAEON hwmon driver" + depends on X86 + select MFD_AAEON + help + This hwmon driver adds support for reporting temperature or fan + speed and voltage on Single Board Computers produced by AAEON. + + This driver leverages the ASUS WMI interface to access device + resources. + config SENSORS_ABITUGURU tristate "Abit uGuru (rev 1 & 2)" depends on (X86 && DMI) || COMPILE_TEST && HAS_IOPORT diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 982ee2c6f9deb6..3a8bd0f9e64f29 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_SENSORS_HP_WMI) += hp-wmi-sensors.o # Native drivers # asb100, then w83781d go first, as they can override other drivers' addresses. +obj-$(CONFIG_SENSORS_AAEON) += hwmon-aaeon.o obj-$(CONFIG_SENSORS_ASB100) += asb100.o obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o obj-$(CONFIG_SENSORS_W83773G) += w83773g.o diff --git a/drivers/hwmon/hwmon-aaeon.c b/drivers/hwmon/hwmon-aaeon.c new file mode 100644 index 00000000000000..ea6df3c61a05f1 --- /dev/null +++ b/drivers/hwmon/hwmon-aaeon.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AAEON HWMON driver + * Copyright (c) 2021, AAEON Ltd. + * + * Author: Edward Lin + * Author: Kunyang Fan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "hwmon-aaeon" + +#define AAEON_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" + +#define AAEON_VERSION_METHOD_ID 0x00000000 +#define HWM_INFORMATION_METHOD_ID 0x00030000 +#define HWM_METHOD_ID 0x00030001 + +#define BITMAP_TEMP_ARG 0x12 +#define BITMAP_FAN_ARG 0x13 +#define BITMAP_VOLTAGE_ARG 0x14 + +#define SENSOR_TEMP_NUMBER 0 +#define SENSOR_FAN_NUMBER 1 +#define SENSOR_VOLTAGE_NUMBER 2 +#define SENSOR_MAX_NUMBER 2 + +static ssize_t aaeon_show_sensor(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t aaeon_show_sensor_name(struct device *dev, + struct device_attribute *devattr, + char *buf); +static ssize_t aaeon_show_version(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t name_show(struct device *dev, struct device_attribute *devattr, + char *buf); +static int aaeon_get_version(void); +static int aaeon_hwmon_probe(struct platform_device *pdev); +static void aaeon_hwmon_remove(struct platform_device *pdev); + +static const char * const temp_sensors_name_table[] = { + "CPU_Temp", + "SYS1_Temp", + "SYS2_Temp", +}; + +static const char * const temp_sensors_name_table_V3[] = { + "SYS_Temp", + "CPU_Temp", +}; + +static const char * const fan_sensors_name_table[] = { + "CPU_FAN", + "SYS1_FAN", + "SYS2_FAN", + "Chasis1_FAN", + "Chasis2_FAN", +}; + +static const char * const fan_sensors_name_table_V3[] = { + "Chasis_FAN", + "CPU_FAN", +}; + +static const char * const voltage_sensors_name_table[] = { + "VCORE_Voltage", + "VMEM_Voltage", + "+12_Voltage", + "+5_Voltage", + "+3.3_Voltage", + "+1.8_Voltage", + "5VSB_Voltage", + "3VSB_Voltage", + "VBAT_Voltage", +}; + +static const char * const voltage_sensors_name_table_V3[] = { + "VCORE_Voltage", + "+5_Voltage", + "AVCC_Voltage", + "+3.3_Voltage", + "+12_Voltage", + "VCOREREFIN_Voltage", + "VIN4_Voltage", + "3VSB_Voltage", + "VBAT_Voltage", +}; + +struct aaeon_hwmon_data { + struct device *hwmon_dev; + int bfpi_version; + u32 temp_bitmap; + u32 fan_bitmap; + u32 voltage_bitmap; + unsigned int sensors_number[SENSOR_MAX_NUMBER + 1]; + const char * const *temp_names; + const char * const *fan_names; + const char * const *voltage_names; +}; + +/* Temperature attributes */ +static struct sensor_device_attribute_2 temp_sys_nodes_atts[] = { + SENSOR_ATTR_2(temp1_input, 0444, aaeon_show_sensor, NULL, + SENSOR_TEMP_NUMBER, 0), + SENSOR_ATTR_2(temp1_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_TEMP_NUMBER, 0), + SENSOR_ATTR_2(temp2_input, 0444, aaeon_show_sensor, NULL, + SENSOR_TEMP_NUMBER, 1), + SENSOR_ATTR_2(temp2_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_TEMP_NUMBER, 1), + SENSOR_ATTR_2(temp3_input, 0444, aaeon_show_sensor, NULL, + SENSOR_TEMP_NUMBER, 2), + SENSOR_ATTR_2(temp3_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_TEMP_NUMBER, 2), +}; + +/* Cooler Fan attributes */ +static struct sensor_device_attribute_2 fan_sys_nodes_atts[] = { + SENSOR_ATTR_2(fan1_input, 0444, aaeon_show_sensor, NULL, + SENSOR_FAN_NUMBER, 0), + SENSOR_ATTR_2(fan1_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_FAN_NUMBER, 0), + SENSOR_ATTR_2(fan2_input, 0444, aaeon_show_sensor, NULL, + SENSOR_FAN_NUMBER, 1), + SENSOR_ATTR_2(fan2_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_FAN_NUMBER, 1), + SENSOR_ATTR_2(fan3_input, 0444, aaeon_show_sensor, NULL, + SENSOR_FAN_NUMBER, 2), + SENSOR_ATTR_2(fan3_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_FAN_NUMBER, 2), + SENSOR_ATTR_2(fan4_input, 0444, aaeon_show_sensor, NULL, + SENSOR_FAN_NUMBER, 3), + SENSOR_ATTR_2(fan4_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_FAN_NUMBER, 3), + SENSOR_ATTR_2(fan5_input, 0444, aaeon_show_sensor, NULL, + SENSOR_FAN_NUMBER, 4), + SENSOR_ATTR_2(fan5_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_FAN_NUMBER, 4), +}; + +/* Voltage attributes */ +static struct sensor_device_attribute_2 voltage_sys_nodes_atts[] = { + SENSOR_ATTR_2(in1_input, 0444, aaeon_show_sensor, NULL, + SENSOR_VOLTAGE_NUMBER, 0), + SENSOR_ATTR_2(in1_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_VOLTAGE_NUMBER, 0), + SENSOR_ATTR_2(in2_input, 0444, aaeon_show_sensor, NULL, + SENSOR_VOLTAGE_NUMBER, 1), + SENSOR_ATTR_2(in2_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_VOLTAGE_NUMBER, 1), + SENSOR_ATTR_2(in3_input, 0444, aaeon_show_sensor, NULL, + SENSOR_VOLTAGE_NUMBER, 2), + SENSOR_ATTR_2(in3_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_VOLTAGE_NUMBER, 2), + SENSOR_ATTR_2(in4_input, 0444, aaeon_show_sensor, NULL, + SENSOR_VOLTAGE_NUMBER, 3), + SENSOR_ATTR_2(in4_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_VOLTAGE_NUMBER, 3), + SENSOR_ATTR_2(in5_input, 0444, aaeon_show_sensor, NULL, + SENSOR_VOLTAGE_NUMBER, 4), + SENSOR_ATTR_2(in5_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_VOLTAGE_NUMBER, 4), + SENSOR_ATTR_2(in6_input, 0444, aaeon_show_sensor, NULL, + SENSOR_VOLTAGE_NUMBER, 5), + SENSOR_ATTR_2(in6_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_VOLTAGE_NUMBER, 5), + SENSOR_ATTR_2(in7_input, 0444, aaeon_show_sensor, NULL, + SENSOR_VOLTAGE_NUMBER, 6), + SENSOR_ATTR_2(in7_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_VOLTAGE_NUMBER, 6), + SENSOR_ATTR_2(in8_input, 0444, aaeon_show_sensor, NULL, + SENSOR_VOLTAGE_NUMBER, 7), + SENSOR_ATTR_2(in8_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_VOLTAGE_NUMBER, 7), + SENSOR_ATTR_2(in9_input, 0444, aaeon_show_sensor, NULL, + SENSOR_VOLTAGE_NUMBER, 8), + SENSOR_ATTR_2(in9_label, 0444, aaeon_show_sensor_name, NULL, + SENSOR_VOLTAGE_NUMBER, 8), + +}; + +static struct sensor_device_attribute_2 info_sys_nodes_atts[] = { + /* WMI version Information */ + SENSOR_ATTR_2(AAEON_VERSION, 0444, aaeon_show_version, NULL, 0, 0), +}; + +DEVICE_ATTR_RO(name); +static ssize_t name_show(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "%s\n", DRVNAME); +} + +static ssize_t aaeon_show_version(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct aaeon_hwmon_data *data = + (struct aaeon_hwmon_data *)dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", data->bfpi_version); +} + +static ssize_t aaeon_show_sensor_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + u8 nr = to_sensor_dev_attr_2(devattr)->nr; + u8 index = to_sensor_dev_attr_2(devattr)->index; + struct aaeon_hwmon_data *data = + (struct aaeon_hwmon_data *)dev_get_drvdata(dev); + + if (nr > SENSOR_MAX_NUMBER || index >= data->sensors_number[nr]) { + pr_debug("Can not check the device"); + return -1; + } + + switch (nr) { + case SENSOR_TEMP_NUMBER: + return sprintf(buf, "%s\n", data->temp_names[index]); + case SENSOR_FAN_NUMBER: + return sprintf(buf, "%s\n", data->fan_names[index]); + case SENSOR_VOLTAGE_NUMBER: + return sprintf(buf, "%s\n", data->voltage_names[index]); + default: + break; + } + + return 0; +} + +static ssize_t aaeon_show_sensor(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + u8 nr = to_sensor_dev_attr_2(devattr)->nr; + u8 index = to_sensor_dev_attr_2(devattr)->index; + u32 dev_id; + int retval, err; + struct aaeon_hwmon_data *data = + (struct aaeon_hwmon_data *)dev_get_drvdata(dev); + + if (nr > SENSOR_MAX_NUMBER || index >= data->sensors_number[nr]) { + pr_debug("Can not check the device"); + return -1; + } + + /* For the V3 version, index need offset */ + if (data->bfpi_version == 0x03 && nr != SENSOR_VOLTAGE_NUMBER) + index++; + + dev_id = (index << 12) | (nr << 8); + err = asus_wmi_evaluate_method(HWM_METHOD_ID, dev_id, 0, &retval); + if (err) + return err; + + /* For the V3 version, need to convert the raw value*/ + if (nr == SENSOR_VOLTAGE_NUMBER && data->bfpi_version == 0x03) { + switch (index) { + case 0: /* VCORE */ + retval = retval * 16; + break; + case 1: /* +5V */ + retval = (retval * 2008) / 50; + break; + case 2: /* AVCC */ + retval = retval * 16; + break; + case 3: /* +3.3V */ + retval = retval * 16; + break; + case 4: /* +12V */ + retval = retval * 96; + break; + case 5: /* VCOREREFIN */ + retval = (retval * 552) / 41; + break; + case 6: /* VIN4 */ + retval = retval * 8; + break; + case 7: /* 3VSB */ + retval = retval * 16; + break; + case 8: /* VBAT */ + retval = retval * 16; + break; + default: + break; + } + } else if (nr == SENSOR_TEMP_NUMBER && data->bfpi_version == 0x03) + retval = retval * 1000; + + return sprintf(buf, "%d\n", retval); +} + +static int aaeon_hwmon_create_sub_sysfs_fs(struct platform_device *pdev, + struct sensor_device_attribute_2 *attr, + int sensor_number, + u32 sensor_mask, + int bfpi_version) +{ + int i, err = 0; + + for (i = 0; i < sensor_number; i++) { + if (bfpi_version == 0x03 || sensor_mask & BIT(i)) { + err = device_create_file(&pdev->dev, &attr[2 * i].dev_attr); + if (err) + break; + err = device_create_file(&pdev->dev, &attr[2 * i + 1].dev_attr); + if (err) + break; + } + } + + return err; +} + +static int +aaeon_hwmon_create_sysfs_files(struct platform_device *pdev, struct aaeon_hwmon_data *data) +{ + int err; + + /* register sysfs interface files */ + err = device_create_file(&pdev->dev, &dev_attr_name); + if (err) + return err; + + /* registe sysfs to dump sensors BFPI version */ + err = device_create_file(&pdev->dev, &info_sys_nodes_atts[0].dev_attr); + if (err) + return err; + + /* create temperature name and value node */ + err = aaeon_hwmon_create_sub_sysfs_fs(pdev, temp_sys_nodes_atts, + data->sensors_number[SENSOR_TEMP_NUMBER], + data->temp_bitmap, data->bfpi_version); + if (err) + return err; + + /* create fan name and value node */ + err = aaeon_hwmon_create_sub_sysfs_fs(pdev, fan_sys_nodes_atts, + data->sensors_number[SENSOR_FAN_NUMBER], + data->fan_bitmap, data->bfpi_version); + if (err) + return err; + + /* create voltage name and value node */ + err = aaeon_hwmon_create_sub_sysfs_fs(pdev, voltage_sys_nodes_atts, + data->sensors_number[SENSOR_VOLTAGE_NUMBER], + data->voltage_bitmap, data->bfpi_version); + if (err) + return err; + + return 0; +} + +static void aaeon_hwmon_remove_sub_sysfs_fs(struct platform_device *pdev, + struct sensor_device_attribute_2 *attr, + int sensor_number, + u32 sensor_mask, + int bfpi_version) +{ + int i; + + for (i = 0; i < sensor_number; i++) { + if (bfpi_version == 0x03 || sensor_mask & BIT(i)) { + device_remove_file(&pdev->dev, &attr[2 * i].dev_attr); + device_remove_file(&pdev->dev, &attr[2 * i + 1].dev_attr); + } + } +} + +static void +aaeon_hwmon_remove_sysfs_files(struct platform_device *pdev, + struct aaeon_hwmon_data *data) +{ + /* degister sysfs interface files */ + device_remove_file(&pdev->dev, &dev_attr_name); + + /* degiste sysfs to dump sensors BFPI version */ + device_remove_file(&pdev->dev, &info_sys_nodes_atts[0].dev_attr); + + /* remove temperature name and value node */ + aaeon_hwmon_remove_sub_sysfs_fs(pdev, temp_sys_nodes_atts, + data->sensors_number[SENSOR_TEMP_NUMBER], + data->temp_bitmap, + data->bfpi_version); + + /* remove fan name and value node */ + aaeon_hwmon_remove_sub_sysfs_fs(pdev, fan_sys_nodes_atts, + data->sensors_number[SENSOR_FAN_NUMBER], + data->fan_bitmap, + data->bfpi_version); + + /* remove voltage name and value node */ + aaeon_hwmon_remove_sub_sysfs_fs(pdev, voltage_sys_nodes_atts, + data->sensors_number[SENSOR_VOLTAGE_NUMBER], + data->voltage_bitmap, + data->bfpi_version); +} + +static void aaeon_hwmon_remove(struct platform_device *pdev) +{ + struct aaeon_hwmon_data *data = platform_get_drvdata(pdev); + + if (data->hwmon_dev) + hwmon_device_unregister(data->hwmon_dev); + + aaeon_hwmon_remove_sysfs_files(pdev, data); + + return; +} + +static int aaeon_get_version(void) +{ + int err, retval; + u32 dev_id = 0x00; + + err = asus_wmi_evaluate_method(AAEON_VERSION_METHOD_ID, dev_id, 0, + &retval); + if (err) + return err; + + return retval; +} + +static int aaeon_hwmon_init_drv_data(struct aaeon_hwmon_data *data) +{ + int err; + + data->bfpi_version = aaeon_get_version(); + if (data->bfpi_version < 0) { + pr_debug("Error BFPI verion\n"); + return -1; + } + + if (data->bfpi_version == 0x03) { + /* set the number of bits in temp bitmap */ + data->sensors_number[SENSOR_TEMP_NUMBER] = + ARRAY_SIZE(temp_sensors_name_table_V3); + data->temp_names = temp_sensors_name_table_V3; + + /* set the number of bits in fan bitmap */ + data->sensors_number[SENSOR_FAN_NUMBER] = + ARRAY_SIZE(fan_sensors_name_table_V3); + data->fan_names = fan_sensors_name_table_V3; + + /* set the number of bits in voltage bitmap */ + data->sensors_number[SENSOR_VOLTAGE_NUMBER] = + ARRAY_SIZE(voltage_sensors_name_table_V3); + data->voltage_names = voltage_sensors_name_table_V3; + } else { + /* set the number of bits in temp bitmap */ + data->sensors_number[SENSOR_TEMP_NUMBER] = + ARRAY_SIZE(temp_sensors_name_table); + data->temp_names = temp_sensors_name_table; + + /* set the number of bits in fan bitmap */ + data->sensors_number[SENSOR_FAN_NUMBER] = + ARRAY_SIZE(fan_sensors_name_table); + data->fan_names = fan_sensors_name_table; + + /* set the number of bits in voltage bitmap */ + data->sensors_number[SENSOR_VOLTAGE_NUMBER] = + ARRAY_SIZE(voltage_sensors_name_table); + data->voltage_names = voltage_sensors_name_table; + } + + /* get temp supported bitmap */ + err = asus_wmi_evaluate_method(HWM_INFORMATION_METHOD_ID, + BITMAP_TEMP_ARG, 0, &data->temp_bitmap); + if (err) + return err; + + /* get fan supported bitmap */ + err = asus_wmi_evaluate_method(HWM_INFORMATION_METHOD_ID, + BITMAP_FAN_ARG, 0, &data->fan_bitmap); + if (err) + return err; + + /* get voltage supported bitmap */ + err = asus_wmi_evaluate_method(HWM_INFORMATION_METHOD_ID, + BITMAP_VOLTAGE_ARG, 0, &data->voltage_bitmap); + if (err) + return err; + + return 0; +} + +static int aaeon_hwmon_probe(struct platform_device *pdev) +{ + int err; + struct aaeon_hwmon_data *data; + + pr_debug("aaeon hwomon device probe (support V3)!\n"); + if (!wmi_has_guid(AAEON_WMI_MGMT_GUID)) { + pr_info("AAEON Management GUID not found\n"); + return -ENODEV; + } + + data = devm_kzalloc(&pdev->dev, sizeof(struct aaeon_hwmon_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + err = aaeon_hwmon_init_drv_data(data); + if (err) { + pr_info("Error to get sensor support bitmap\n"); + goto exit; + } + + if (data->bfpi_version != 0x03 && data->temp_bitmap == 0 && + data->fan_bitmap == 0 && data->voltage_bitmap == 0) { + pr_debug("No sensors found\n"); + err = -ENODEV; + goto exit; + } + + platform_set_drvdata(pdev, data); + err = aaeon_hwmon_create_sysfs_files(pdev, data); + if (err) + goto exit; + + data->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "AAEON_HWM", + data, + NULL, + NULL); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + data->hwmon_dev = NULL; + goto exit_unregister_sysfs; + } + + return 0; + +exit_unregister_sysfs: + aaeon_hwmon_remove(pdev); +exit: + return err; +} + +static struct platform_driver aaeon_hwmon_driver = { + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + }, + .probe = aaeon_hwmon_probe, + .remove = aaeon_hwmon_remove, +}; + +module_platform_driver_probe(aaeon_hwmon_driver, aaeon_hwmon_probe); + +MODULE_ALIAS("platform:hwmon-aaeon"); +MODULE_DESCRIPTION("AAEON Hardware Monitoring Driver"); +MODULE_AUTHOR("Edward Lin "); +MODULE_AUTHOR("Kunyang Fan "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("ASUS_WMI"); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 8c935f867a37a7..6a91cc4fe4db59 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -793,7 +793,8 @@ config I2C_JZ4780 config I2C_K1 tristate "SpacemiT K1 I2C adapter" depends on ARCH_SPACEMIT || COMPILE_TEST - depends on OF + depends on OF && COMMON_CLK + default ARCH_SPACEMIT help This option enables support for the I2C interface on the SpacemiT K1 platform. @@ -850,6 +851,17 @@ config I2C_LS2X This driver can also be built as a module. If so, the module will be called i2c-ls2x. +config I2C_LS2X_V2 + tristate "Loongson-2 Fast Speed I2C adapter" + depends on LOONGARCH || COMPILE_TEST + select REGMAP_MMIO + help + If you say yes to this option, support will be included for the + I2C interface on the Loongson-2K0300 SoCs. + + This driver can also be built as a module. If so, the module + will be called i2c-ls2x-v2. + config I2C_MLXBF tristate "Mellanox BlueField I2C controller" depends on (MELLANOX_PLATFORM && ARM64) || COMPILE_TEST diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 547123ab351f10..3755c54b3d8284 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -80,6 +80,7 @@ obj-$(CONFIG_I2C_KEBA) += i2c-keba.o obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o obj-$(CONFIG_I2C_LPC2K) += i2c-lpc2k.o obj-$(CONFIG_I2C_LS2X) += i2c-ls2x.o +obj-$(CONFIG_I2C_LS2X_V2) += i2c-ls2x-v2.o obj-$(CONFIG_I2C_MESON) += i2c-meson.o obj-$(CONFIG_I2C_MICROCHIP_CORE) += i2c-microchip-corei2c.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c index b29c99ed388393..c8e0ebe58e374a 100644 --- a/drivers/i2c/busses/i2c-i801.c +++ b/drivers/i2c/busses/i2c-i801.c @@ -988,6 +988,14 @@ static void i801_disable_host_notify(struct i801_priv *priv) iowrite8(priv->original_slvcmd, SMBSLVCMD(priv)); } +static inline __maybe_unused void __i801_register_spd(struct i801_priv *priv) +{ + if (priv->original_hstcfg & SMBHSTCFG_SPD_WD) + i2c_register_spd_write_disable(&priv->adapter); + else + i2c_register_spd_write_enable(&priv->adapter); +} + static const struct i2c_algorithm smbus_algorithm = { .smbus_xfer = i801_access, .functionality = i801_func, @@ -1170,6 +1178,19 @@ static void dmi_check_onboard_devices(const struct dmi_header *dm, void *adap) } } +#ifdef CONFIG_I2C_I801_MUX +static void i801_register_spd(struct i801_priv *priv) +{ + if (!priv->mux_pdev) + __i801_register_spd(priv); +} +#else +static void i801_register_spd(struct i801_priv *priv) +{ + __i801_register_spd(priv); +} +#endif + /* Register optional targets */ static void i801_probe_optional_targets(struct i801_priv *priv) { @@ -1190,10 +1211,7 @@ static void i801_probe_optional_targets(struct i801_priv *priv) dmi_walk(dmi_check_onboard_devices, &priv->adapter); /* Instantiate SPD EEPROMs unless the SMBus is multiplexed */ -#ifdef CONFIG_I2C_I801_MUX - if (!priv->mux_pdev) -#endif - i2c_register_spd_write_enable(&priv->adapter); + i801_register_spd(priv); } #else static void __init input_apanel_init(void) {} @@ -1296,7 +1314,7 @@ static int i801_notifier_call(struct notifier_block *nb, unsigned long action, return NOTIFY_DONE; /* Call i2c_register_spd for muxed child segments */ - i2c_register_spd_write_enable(to_i2c_adapter(dev)); + __i801_register_spd(priv); return NOTIFY_OK; } diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index 9152cf436bea08..7cf5c05a20d26b 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -4,7 +4,9 @@ */ #include +#include #include +#include #include #include #include @@ -17,6 +19,8 @@ #define SPACEMIT_ISR 0x4 /* Status register */ #define SPACEMIT_IDBR 0xc /* Data buffer register */ #define SPACEMIT_IRCR 0x18 /* Reset cycle counter */ +#define SPACEMIT_ILCR 0x10 /* Load Count Register */ +#define SPACEMIT_IWCR 0x14 /* Wait Count Register */ #define SPACEMIT_IBMR 0x1c /* Bus monitor register */ /* SPACEMIT_ICR register fields */ @@ -88,6 +92,19 @@ #define SPACEMIT_BMR_SDA BIT(0) /* SDA line level */ #define SPACEMIT_BMR_SCL BIT(1) /* SCL line level */ +#define SPACEMIT_LCR_LV_STANDARD_MASK GENMASK(8, 0) +#define SPACEMIT_LCR_LV_FAST_MASK GENMASK(17, 9) + +/* SPACEMIT_IWCR register fields */ +#define SPACEMIT_WCR_COUNT GENMASK(4, 0) +#define SPACEMIT_WCR_HS_COUNT1 GENMASK(9, 5) +#define SPACEMIT_WCR_HS_COUNT2 GENMASK(14, 10) + +/* Required by I2C IP for correct SCL timing */ +#define SPACEMIT_IWCR_INIT_VALUE (FIELD_PREP(SPACEMIT_WCR_COUNT, 10) | \ + FIELD_PREP(SPACEMIT_WCR_HS_COUNT1, 1) | \ + FIELD_PREP(SPACEMIT_WCR_HS_COUNT2, 5)) + /* i2c bus recover timeout: us */ #define SPACEMIT_I2C_BUS_BUSY_TIMEOUT 100000 @@ -109,11 +126,20 @@ enum spacemit_i2c_state { SPACEMIT_STATE_WRITE, }; +enum spacemit_i2c_mode { + SPACEMIT_MODE_STANDARD, + SPACEMIT_MODE_FAST +}; + /* i2c-spacemit driver's main struct */ struct spacemit_i2c_dev { struct device *dev; struct i2c_adapter adapt; + struct clk_hw scl_clk_hw; + struct clk *scl_clk; + enum spacemit_i2c_mode mode; + /* hardware resources */ void __iomem *base; int irq; @@ -135,6 +161,85 @@ struct spacemit_i2c_dev { u32 status; }; +static void spacemit_i2c_scl_clk_disable_unprepare(void *data) +{ + clk_disable_unprepare(data); +} + +/* + * Calculate the ILCR divider value (lv) from the target SCL rate. + * + * Hardware timing formulas: + * - standard mode: SCL = FCLK / (2 * SLV + 8) + * - fast mode: SCL = FCLK / (2 * FLV + 10) + */ +static u32 spacemit_i2c_calc_lv(struct spacemit_i2c_dev *i2c, + unsigned long parent_rate, + unsigned long target_rate) +{ + u32 offset, denom; + + offset = (i2c->mode == SPACEMIT_MODE_STANDARD) ? 8 : 10; + denom = DIV_ROUND_CLOSEST(parent_rate, target_rate); + + return (denom <= offset) ? 0 : DIV_ROUND_CLOSEST(denom - offset, 2); +} + +static int spacemit_i2c_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw); + u32 lv, lcr, mask; + + lv = spacemit_i2c_calc_lv(i2c, parent_rate, rate); + + mask = (i2c->mode == SPACEMIT_MODE_STANDARD) ? + SPACEMIT_LCR_LV_STANDARD_MASK : SPACEMIT_LCR_LV_FAST_MASK; + + lcr = readl(i2c->base + SPACEMIT_ILCR); + lcr &= ~mask; + lcr |= field_prep(mask, lv); + writel(lcr, i2c->base + SPACEMIT_ILCR); + + return 0; +} + +static int spacemit_i2c_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw); + u32 lv, offset; + + lv = spacemit_i2c_calc_lv(i2c, req->best_parent_rate, req->rate); + offset = (i2c->mode == SPACEMIT_MODE_STANDARD) ? 8 : 10; + req->rate = DIV_ROUND_CLOSEST(req->best_parent_rate, lv * 2 + offset); + + return 0; +} + +static unsigned long spacemit_i2c_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw); + u32 lcr, lv = 0; + + lcr = readl(i2c->base + SPACEMIT_ILCR); + + if (i2c->mode == SPACEMIT_MODE_STANDARD) { + lv = FIELD_GET(SPACEMIT_LCR_LV_STANDARD_MASK, lcr); + return DIV_ROUND_CLOSEST(parent_rate, lv * 2 + 8); + } + + lv = FIELD_GET(SPACEMIT_LCR_LV_FAST_MASK, lcr); + return DIV_ROUND_CLOSEST(parent_rate, lv * 2 + 10); +} + +static const struct clk_ops spacemit_i2c_clk_ops = { + .set_rate = spacemit_i2c_clk_set_rate, + .determine_rate = spacemit_i2c_clk_determine_rate, + .recalc_rate = spacemit_i2c_clk_recalc_rate, +}; + static void spacemit_i2c_enable(struct spacemit_i2c_dev *i2c) { u32 val; @@ -153,6 +258,28 @@ static void spacemit_i2c_disable(struct spacemit_i2c_dev *i2c) writel(val, i2c->base + SPACEMIT_ICR); } +static int spacemit_i2c_register_scl_clk(struct spacemit_i2c_dev *i2c) +{ + struct clk_init_data init = {}; + char name[64]; + int ret; + + ret = snprintf(name, sizeof(name), "%s_scl_clk", dev_name(i2c->dev)); + if (ret >= ARRAY_SIZE(name)) + dev_warn(i2c->dev, "scl clock name truncated"); + + init.name = name; + init.ops = &spacemit_i2c_clk_ops; + init.parent_data = (struct clk_parent_data[]) { + { .fw_name = "func" }, + }; + init.num_parents = 1; + + i2c->scl_clk_hw.init = &init; + + return devm_clk_hw_register(i2c->dev, &i2c->scl_clk_hw); +} + static void spacemit_i2c_reset(struct spacemit_i2c_dev *i2c) { writel(SPACEMIT_CR_UR, i2c->base + SPACEMIT_ICR); @@ -286,7 +413,7 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c) val |= SPACEMIT_CR_MSDIE; } - if (i2c->clock_freq == SPACEMIT_I2C_MAX_FAST_MODE_FREQ) + if (i2c->mode == SPACEMIT_MODE_FAST) val |= SPACEMIT_CR_MODE_FAST; /* disable response to general call */ @@ -309,6 +436,14 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c) writel(val, i2c->base + SPACEMIT_IRCR); spacemit_i2c_clear_int_status(i2c, SPACEMIT_I2C_INT_STATUS_MASK); + + /* + * Initialize IWCR to the value specified by the I2C IP designer. + * The SCL frequency formulas (SCL = FCLK / (2*SLV+8) for standard + * mode, SCL = FCLK / (2*FLV+10) for fast mode) are only valid when + * IWCR contains this specific value. + */ + writel(SPACEMIT_IWCR_INIT_VALUE, i2c->base + SPACEMIT_IWCR); } static void spacemit_i2c_start(struct spacemit_i2c_dev *i2c) @@ -698,19 +833,18 @@ static int spacemit_i2c_probe(struct platform_device *pdev) if (!i2c) return -ENOMEM; - ret = of_property_read_u32(of_node, "clock-frequency", &i2c->clock_freq); - if (ret && ret != -EINVAL) - dev_warn(dev, "failed to read clock-frequency property: %d\n", ret); + of_property_read_u32(of_node, "clock-frequency", &i2c->clock_freq); /* For now, this driver doesn't support high-speed. */ - if (!i2c->clock_freq || i2c->clock_freq > SPACEMIT_I2C_MAX_FAST_MODE_FREQ) { - dev_warn(dev, "unsupported clock frequency %u; using %u\n", - i2c->clock_freq, SPACEMIT_I2C_MAX_FAST_MODE_FREQ); + if (i2c->clock_freq > SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ && + i2c->clock_freq <= SPACEMIT_I2C_MAX_FAST_MODE_FREQ) { + i2c->mode = SPACEMIT_MODE_FAST; + } else if (i2c->clock_freq && i2c->clock_freq <= SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) { + i2c->mode = SPACEMIT_MODE_STANDARD; + } else { + dev_info(dev, "clock-frequency not set or out of range, using fast mode\n"); + i2c->mode = SPACEMIT_MODE_FAST; i2c->clock_freq = SPACEMIT_I2C_MAX_FAST_MODE_FREQ; - } else if (i2c->clock_freq < SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) { - dev_warn(dev, "unsupported clock frequency %u; using %u\n", - i2c->clock_freq, SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ); - i2c->clock_freq = SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ; } i2c->dev = &pdev->dev; @@ -732,6 +866,15 @@ static int spacemit_i2c_probe(struct platform_device *pdev) if (IS_ERR(clk)) return dev_err_probe(dev, PTR_ERR(clk), "failed to enable func clock"); + ret = spacemit_i2c_register_scl_clk(i2c); + if (ret) + return dev_err_probe(dev, ret, "failed to register scl clock\n"); + + i2c->scl_clk = devm_clk_hw_get_clk(dev, &i2c->scl_clk_hw, "scl"); + if (IS_ERR(i2c->scl_clk)) + return dev_err_probe(dev, PTR_ERR(i2c->scl_clk), + "failed to get scl clock\n"); + clk = devm_clk_get_enabled(dev, "bus"); if (IS_ERR(clk)) return dev_err_probe(dev, PTR_ERR(clk), "failed to enable bus clock"); @@ -741,6 +884,19 @@ static int spacemit_i2c_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(rst), "failed to acquire deasserted reset\n"); + ret = clk_set_rate(i2c->scl_clk, i2c->clock_freq); + if (ret) + return dev_err_probe(dev, ret, "failed to set rate for SCL clock"); + + ret = clk_prepare_enable(i2c->scl_clk); + if (ret) + return dev_err_probe(dev, ret, "failed to prepare and enable clock"); + + ret = devm_add_action_or_reset(dev, spacemit_i2c_scl_clk_disable_unprepare, + i2c->scl_clk); + if (ret) + return ret; + spacemit_i2c_reset(i2c); i2c_set_adapdata(&i2c->adapt, i2c); diff --git a/drivers/i2c/busses/i2c-ls2x-v2.c b/drivers/i2c/busses/i2c-ls2x-v2.c new file mode 100644 index 00000000000000..517760d7016905 --- /dev/null +++ b/drivers/i2c/busses/i2c-ls2x-v2.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Loongson-2K0300 I2C controller driver + * + * Copyright (C) 2025-2026 Loongson Technology Corporation Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Loongson-2 fast I2C offset registers */ +#define LOONGSON2_I2C_CR1 0x00 /* I2C control 1 register */ +#define LOONGSON2_I2C_CR2 0x04 /* I2C control 2 register */ +#define LOONGSON2_I2C_OAR 0x08 /* I2C slave address register */ +#define LOONGSON2_I2C_DR 0x10 /* I2C data register */ +#define LOONGSON2_I2C_SR1 0x14 /* I2C status 1 register */ +#define LOONGSON2_I2C_SR2 0x18 /* I2C status 2 register */ +#define LOONGSON2_I2C_CCR 0x1c /* I2C clock control register */ +#define LOONGSON2_I2C_TRISE 0x20 /* I2C trise register */ +#define LOONGSON2_I2C_FLTR 0x24 + +/* Bitfields of I2C control 1 register */ +#define LOONGSON2_I2C_CR1_PE BIT(0) /* Peripheral enable */ +#define LOONGSON2_I2C_CR1_START BIT(8) /* Start generation */ +#define LOONGSON2_I2C_CR1_STOP BIT(9) /* Stop generation */ +#define LOONGSON2_I2C_CR1_ACK BIT(10) /* Acknowledge enable */ +#define LOONGSON2_I2C_CR1_POS BIT(11) /* Acknowledge/PEC Position (for data reception) */ + +#define LOONGSON2_I2C_CR1_OP_MASK (LOONGSON2_I2C_CR1_START | LOONGSON2_I2C_CR1_STOP) + +/* Bitfields of I2C control 2 register */ +#define LOONGSON2_I2C_CR2_FREQ GENMASK(5, 0) /* APB Clock Frequency in MHz */ +#define LOONGSON2_I2C_CR2_ITERREN BIT(8) /* Fault-Class Interrupt Enable */ +#define LOONGSON2_I2C_CR2_ITEVTEN BIT(9) /* Event-Based Interrupt Enable */ +#define LOONGSON2_I2C_CR2_ITBUFEN BIT(10) /* Cache-Class Interrupt Enable */ + +#define LOONGSON2_I2C_CR2_INT_MASK \ + (LOONGSON2_I2C_CR2_ITBUFEN | LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN) + +/* Bitfields of I2C status 1 register */ +#define LOONGSON2_I2C_SR1_SB BIT(0) /* Start bit (Master mode) */ +#define LOONGSON2_I2C_SR1_ADDR BIT(1) /* Address sent (master mode) */ +#define LOONGSON2_I2C_SR1_BTF BIT(2) /* Byte transfer finished */ +#define LOONGSON2_I2C_SR1_RXNE BIT(6) /* Data register not empty (receivers) */ +#define LOONGSON2_I2C_SR1_TXE BIT(7) /* Data register empty (transmitters) */ +#define LOONGSON2_I2C_SR1_BERR BIT(8) /* Bus error */ +#define LOONGSON2_I2C_SR1_ARLO BIT(9) /* Arbitration lost (master mode) */ +#define LOONGSON2_I2C_SR1_AF BIT(10) /* Acknowledge failure */ + +#define LOONGSON2_I2C_SR1_ITEVTEN_MASK \ + (LOONGSON2_I2C_SR1_BTF | LOONGSON2_I2C_SR1_ADDR | LOONGSON2_I2C_SR1_SB) +#define LOONGSON2_I2C_SR1_ITBUFEN_MASK (LOONGSON2_I2C_SR1_TXE | LOONGSON2_I2C_SR1_RXNE) +#define LOONGSON2_I2C_SR1_ITERREN_MASK \ + (LOONGSON2_I2C_SR1_AF | LOONGSON2_I2C_SR1_ARLO | LOONGSON2_I2C_SR1_BERR) + +/* Bitfields of I2C status 2 register */ +#define LOONGSON2_I2C_SR2_MSL BIT(0) /* Master/slave */ +#define LOONGSON2_I2C_SR2_BUSY BIT(1) /* Bus busy */ +#define LOONGSON2_I2C_SR2_TRA BIT(2) /* Transmitter/receiver */ +#define LOONGSON2_I2C_SR2_GENCALL BIT(4) /* General call address (Slave mode) */ + +/* Bitfields of I2C clock control register */ +#define LOONGSON2_I2C_CCR_CCR GENMASK(11, 0) +#define LOONGSON2_I2C_CCR_DUTY BIT(14) +#define LOONGSON2_I2C_CCR_FS BIT(15) + +/* Bitfields of I2C trise register */ +#define LOONGSON2_I2C_TRISE_SCL GENMASK(5, 0) + +#define LOONGSON2_I2C_FREE_SLEEP_US 10 +#define LOONGSON2_I2C_FREE_TIMEOUT_US (2 * USEC_PER_MSEC) + +/** + * struct loongson2_i2c_msg - client specific data + * @buf: data buffer + * @count: number of bytes to be transferred + * @result: result of the transfer + * @addr: 8-bit slave addr, including r/w bit + * @stop: last I2C msg to be sent, i.e. STOP to be generated + */ +struct loongson2_i2c_msg { + u8 *buf; + u32 count; + int result; + u8 addr; + bool stop; +}; + +/** + * struct loongson2_i2c_priv - private data of the controller + * @adapter: I2C adapter for this controller + * @complete: completion of I2C message + * @clk: hw i2c clock + * @regmap: regmap of the I2C device + * @parent_rate_MHz: I2C clock parent rate + * @msg: I2C transfer information + */ +struct loongson2_i2c_priv { + struct i2c_adapter adapter; + struct completion complete; + struct clk *clk; + struct regmap *regmap; + unsigned long parent_rate_MHz; + struct loongson2_i2c_msg msg; +}; + +static void loongson2_i2c_disable_irq(struct loongson2_i2c_priv *priv) +{ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_INT_MASK, 0); +} + +static void loongson2_i2c_read_msg(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + u32 rbuf; + + regmap_read(priv->regmap, LOONGSON2_I2C_DR, &rbuf); + *msg->buf++ = rbuf; + msg->count--; +} + +static void loongson2_i2c_write_msg(struct loongson2_i2c_priv *priv, u8 byte) +{ + regmap_write(priv->regmap, LOONGSON2_I2C_DR, byte); +} + +static void loongson2_i2c_terminate_xfer(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + + loongson2_i2c_disable_irq(priv); + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK, + msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START); + complete(&priv->complete); +} + +static void loongson2_i2c_handle_write(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + + if (msg->count) { + loongson2_i2c_write_msg(priv, *msg->buf++); + if (!--msg->count) + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, + LOONGSON2_I2C_CR2_ITBUFEN, 0); + } else { + loongson2_i2c_terminate_xfer(priv); + } +} + +static void loongson2_i2c_handle_rx_addr(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + + switch (msg->count) { + case 0: + loongson2_i2c_terminate_xfer(priv); + break; + case 1: + /* Enable NACK and reset POS (Acknowledge position) */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, + LOONGSON2_I2C_CR1_ACK | LOONGSON2_I2C_CR1_POS, 0); + /* Set STOP or RepSTART */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK, + msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START); + break; + case 2: + /* Enable NACK */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0); + /* Set POS (NACK position) */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, + LOONGSON2_I2C_CR1_POS); + break; + + default: + /* Enable ACK */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, + LOONGSON2_I2C_CR1_ACK); + /* Reset POS (ACK position) */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, 0); + break; + } +} + +static void loongson2_i2c_isr_error(u32 status, void *data) +{ + struct loongson2_i2c_priv *priv = data; + struct loongson2_i2c_msg *msg = &priv->msg; + + /* Arbitration lost */ + if (status & LOONGSON2_I2C_SR1_ARLO) { + regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_ARLO, 0); + msg->result = -EAGAIN; + goto out; + } + + /* + * Acknowledge failure: + * In master transmitter mode a Stop must be generated by software. + */ + if (status & LOONGSON2_I2C_SR1_AF) { + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_STOP, + LOONGSON2_I2C_CR1_STOP); + regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_AF, 0); + msg->result = -EIO; + goto out; + } + + /* Bus error */ + if (status & LOONGSON2_I2C_SR1_BERR) { + regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_BERR, 0); + msg->result = -EIO; + goto out; + } + +out: + loongson2_i2c_disable_irq(priv); + complete(&priv->complete); +} + +static void loongson2_i2c_handle_read(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + + switch (msg->count) { + case 1: + loongson2_i2c_disable_irq(priv); + loongson2_i2c_read_msg(priv); + complete(&priv->complete); + break; + case 2: + case 3: + /* + * For 2-byte/3-byte reception and for N-byte reception with N > 3, we have to + * wait for byte transferred finished event before reading data. + * Just disable buffer interrupt in order to avoid another system preemption due + * to RX not empty event. + */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN, 0); + break; + default: + /* + * For N byte reception with N > 3 we directly read data register + * until N-2 data. + */ + loongson2_i2c_read_msg(priv); + break; + } +} + +static void loongson2_i2c_handle_rx_done(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + + switch (msg->count) { + case 2: + /* + * The STOP/START bit has to be set before reading the last two bytes. + * After that, we could read the last two bytes. + */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK, + msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START); + + for (unsigned int i = msg->count; i > 0; i--) + loongson2_i2c_read_msg(priv); + + loongson2_i2c_disable_irq(priv); + + complete(&priv->complete); + break; + case 3: + /* + * In order to generate the NACK after the last received data byte, enable NACK + * before reading N-2 data. + */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0); + loongson2_i2c_read_msg(priv); + break; + default: + loongson2_i2c_read_msg(priv); + break; + } +} + +static irqreturn_t loongson2_i2c_isr_event(int irq, void *data) +{ + struct loongson2_i2c_priv *priv = data; + struct device *dev = regmap_get_device(priv->regmap); + struct loongson2_i2c_msg *msg = &priv->msg; + u32 status, ien, event, cr2, possible_status; + + regmap_read(priv->regmap, LOONGSON2_I2C_SR1, &status); + if (status & LOONGSON2_I2C_SR1_ITERREN_MASK) { + loongson2_i2c_isr_error(status, data); + return IRQ_NONE; + } + + regmap_read(priv->regmap, LOONGSON2_I2C_CR2, &cr2); + ien = cr2 & LOONGSON2_I2C_CR2_INT_MASK; + + /* Update possible_status if buffer interrupt is enabled */ + possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK; + if (ien & LOONGSON2_I2C_CR2_ITBUFEN) + possible_status |= LOONGSON2_I2C_SR1_ITBUFEN_MASK; + + event = status & possible_status; + if (!event) { + dev_dbg(dev, "spurious evt IRQ (status=0x%08x, ien=0x%08x)\n", status, ien); + return IRQ_NONE; + } + + /* Start condition generated */ + if (event & LOONGSON2_I2C_SR1_SB) + loongson2_i2c_write_msg(priv, msg->addr); + + /* I2C Address sent */ + if (event & LOONGSON2_I2C_SR1_ADDR) { + if (msg->addr & I2C_M_RD) + loongson2_i2c_handle_rx_addr(priv); + /* Clear ADDR flag */ + regmap_read(priv->regmap, LOONGSON2_I2C_SR2, &status); + /* Enable buffer interrupts for RX/TX not empty events */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN, + LOONGSON2_I2C_CR2_ITBUFEN); + } + + /* TX empty */ + if ((event & LOONGSON2_I2C_SR1_TXE) && !(msg->addr & I2C_M_RD)) + loongson2_i2c_handle_write(priv); + + /* RX not empty */ + if ((event & LOONGSON2_I2C_SR1_RXNE) && (msg->addr & I2C_M_RD)) + loongson2_i2c_handle_read(priv); + + /* + * The BTF (Byte Transfer finished) event occurs when: + * - in reception: a new byte is received in the shift register + * but the previous byte has not been read yet from data register + * - in transmission: a new byte should be sent but the data register + * has not been written yet + */ + if (event & LOONGSON2_I2C_SR1_BTF) { + if (msg->addr & I2C_M_RD) + loongson2_i2c_handle_rx_done(priv); + else + loongson2_i2c_handle_write(priv); + } + + return IRQ_HANDLED; +} + +static int loongson2_i2c_xfer_msg(struct loongson2_i2c_priv *priv, struct i2c_msg *msg, + bool is_stop) +{ + struct loongson2_i2c_msg *l_msg = &priv->msg; + unsigned long timeout; + + l_msg->addr = i2c_8bit_addr_from_msg(msg); + l_msg->buf = msg->buf; + l_msg->count = msg->len; + l_msg->stop = is_stop; + l_msg->result = 0; + + reinit_completion(&priv->complete); + + /* Enable events and errors interrupts */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, + LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN, + LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN); + + timeout = wait_for_completion_timeout(&priv->complete, priv->adapter.timeout); + if (!timeout) + return -ETIMEDOUT; + + return l_msg->result; +} + +static int loongson2_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num) +{ + struct loongson2_i2c_priv *priv = i2c_get_adapdata(i2c_adap); + struct device *dev = regmap_get_device(priv->regmap); + unsigned int status; + int ret; + + /* Wait I2C bus free */ + ret = regmap_read_poll_timeout(priv->regmap, LOONGSON2_I2C_SR2, status, + !(status & LOONGSON2_I2C_SR2_BUSY), + LOONGSON2_I2C_FREE_SLEEP_US, + LOONGSON2_I2C_FREE_TIMEOUT_US); + if (ret) { + dev_dbg(dev, "The I2C bus is busy now.\n"); + return ret; + } + + /* Start generation */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_START, + LOONGSON2_I2C_CR1_START); + + for (unsigned int i = 0; i < num; i++) { + ret = loongson2_i2c_xfer_msg(priv, &msgs[i], i == num - 1); + if (ret < 0) + return ret; + } + + return num; +} + +static u32 loongson2_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm loongson2_i2c_algo = { + .xfer = loongson2_i2c_xfer, + .functionality = loongson2_i2c_func, +}; + +static int loongson2_i2c_adjust_bus_speed(struct loongson2_i2c_priv *priv) +{ + struct device *dev = regmap_get_device(priv->regmap); + struct i2c_timings i2c_t; + u32 val, freq_MHz, ccr; + + i2c_parse_fw_timings(dev, &i2c_t, true); + priv->parent_rate_MHz = clk_get_rate(priv->clk); + + if (i2c_t.bus_freq_hz == I2C_MAX_STANDARD_MODE_FREQ) { + /* Select Standard mode */ + ccr = 0; + val = DIV_ROUND_UP(priv->parent_rate_MHz, i2c_t.bus_freq_hz * 2); + } else if (i2c_t.bus_freq_hz == I2C_MAX_FAST_MODE_FREQ) { + /* Select Fast mode */ + ccr = LOONGSON2_I2C_CCR_FS; + val = DIV_ROUND_UP(priv->parent_rate_MHz, i2c_t.bus_freq_hz * 3); + } else { + return dev_err_probe(dev, -EINVAL, "Unsupported speed (%uHz)\n", i2c_t.bus_freq_hz); + } + + FIELD_MODIFY(LOONGSON2_I2C_CCR_CCR, &ccr, val); + regmap_write(priv->regmap, LOONGSON2_I2C_CCR, ccr); + + freq_MHz = DIV_ROUND_UP(priv->parent_rate_MHz, HZ_PER_MHZ); + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_FREQ, + FIELD_GET(LOONGSON2_I2C_CR2_FREQ, freq_MHz)); + + regmap_update_bits(priv->regmap, LOONGSON2_I2C_TRISE, LOONGSON2_I2C_TRISE_SCL, + LOONGSON2_I2C_TRISE_SCL); + + /* Enable I2C */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_PE, + LOONGSON2_I2C_CR1_PE); + + return 0; +} + +static const struct regmap_config loongson2_i2c_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = LOONGSON2_I2C_TRISE, +}; + +static int loongson2_i2c_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct loongson2_i2c_priv *priv; + struct i2c_adapter *adap; + void __iomem *base; + int irq, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(dev, base, &loongson2_i2c_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), "Failed to init regmap.\n"); + + priv->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to enable clock.\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + adap = &priv->adapter; + adap->retries = 5; + adap->nr = pdev->id; + adap->dev.parent = dev; + adap->owner = THIS_MODULE; + adap->algo = &loongson2_i2c_algo; + adap->timeout = 2 * HZ; + device_set_node(&adap->dev, dev_fwnode(dev)); + i2c_set_adapdata(adap, priv); + strscpy(adap->name, pdev->name); + init_completion(&priv->complete); + platform_set_drvdata(pdev, priv); + + ret = loongson2_i2c_adjust_bus_speed(priv); + if (ret) + return ret; + + ret = devm_request_irq(dev, irq, loongson2_i2c_isr_event, IRQF_SHARED, pdev->name, priv); + if (ret) + return ret; + + return devm_i2c_add_adapter(dev, adap); +} + +static const struct of_device_id loongson2_i2c_id_table[] = { + { .compatible = "loongson,ls2k0300-i2c" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, loongson2_i2c_id_table); + +static struct platform_driver loongson2_i2c_driver = { + .driver = { + .name = "loongson2-i2c-v2", + .of_match_table = loongson2_i2c_id_table, + }, + .probe = loongson2_i2c_probe, +}; +module_platform_driver(loongson2_i2c_driver); + +MODULE_DESCRIPTION("Loongson-2K0300 I2C bus driver"); +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-ls2x.c b/drivers/i2c/busses/i2c-ls2x.c index b475dd27b7af94..46dafa11b3013a 100644 --- a/drivers/i2c/busses/i2c-ls2x.c +++ b/drivers/i2c/busses/i2c-ls2x.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -63,11 +64,18 @@ /* The default bus frequency, which is an empirical value */ #define LS2X_I2C_FREQ_STD (33 * HZ_PER_KHZ) +/* The div of i2c reference clock on LS2K0500/2K1000/2K2000 */ +#define LS2X_I2C_2K_CLOCK_DIV 40 + +/* The div of i2c reference clock on LS7A1000/7A2000 */ +#define LS2X_I2C_7A_CLOCK_DIV 50 + struct ls2x_i2c_priv { struct i2c_adapter adapter; void __iomem *base; struct i2c_timings i2c_t; struct completion cmd_complete; + unsigned int div; }; /* @@ -96,6 +104,8 @@ static irqreturn_t ls2x_i2c_isr(int this_irq, void *dev_id) static void ls2x_i2c_adjust_bus_speed(struct ls2x_i2c_priv *priv) { u16 val; + u32 pclk, div; + struct clk *clk; struct i2c_timings *t = &priv->i2c_t; struct device *dev = priv->adapter.dev.parent; u32 acpi_speed = i2c_acpi_find_bus_speed(dev); @@ -107,12 +117,30 @@ static void ls2x_i2c_adjust_bus_speed(struct ls2x_i2c_priv *priv) else t->bus_freq_hz = LS2X_I2C_FREQ_STD; + if (dev_of_node(dev)) { + clk = devm_clk_get_optional_enabled(dev, NULL); + if (!IS_ERR_OR_NULL(clk)) + pclk = clk_get_rate(clk); + else + pclk = LS2X_I2C_PCLK_FREQ; + + div = priv->div; + + val = (pclk * 10) / (div * t->bus_freq_hz) - 1; + } else { + /* clocks and clock-div are only ACPI properties. */ + if (!device_property_read_u32(dev, "clocks", &pclk) && + !device_property_read_u32(dev, "clock-div", &div)) + val = (pclk * 10) / (div * t->bus_freq_hz) - 1; + else + val = LS2X_I2C_PCLK_FREQ / (5 * t->bus_freq_hz) - 1; + } + /* * According to the chip manual, we can only access the registers as bytes, * otherwise the high bits will be truncated. * So set the I2C frequency with a sequential writeb() instead of writew(). */ - val = LS2X_I2C_PCLK_FREQ / (5 * t->bus_freq_hz) - 1; writeb(FIELD_GET(GENMASK(7, 0), val), priv->base + I2C_LS2X_PRER_LO); writeb(FIELD_GET(GENMASK(15, 8), val), priv->base + I2C_LS2X_PRER_HI); } @@ -295,6 +323,8 @@ static int ls2x_i2c_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; + priv->div = (unsigned long)device_get_match_data(dev); + /* Map hardware registers */ priv->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->base)) @@ -349,8 +379,8 @@ static DEFINE_RUNTIME_DEV_PM_OPS(ls2x_i2c_pm_ops, ls2x_i2c_suspend, ls2x_i2c_resume, NULL); static const struct of_device_id ls2x_i2c_id_table[] = { - { .compatible = "loongson,ls2k-i2c" }, - { .compatible = "loongson,ls7a-i2c" }, + { .compatible = "loongson,ls2k-i2c", .data = (void *)LS2X_I2C_2K_CLOCK_DIV, }, + { .compatible = "loongson,ls7a-i2c", .data = (void *)LS2X_I2C_7A_CLOCK_DIV, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ls2x_i2c_id_table); diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c index 28c0e4884a7f2e..26ae925ab5d60c 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -663,6 +663,27 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, return (ret == 1) ? 0 : -EIO; } +static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, + u8 *data, u8 data_len) +{ + struct i2c_msg msgs[1]; + int ret; + + msgs[0].addr = client->addr; + msgs[0].flags = client->flags; + msgs[0].len = data_len + 1; + msgs[0].buf = data; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) { + dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); + return ret; + } + + /* 1 transfer must have completed successfully */ + return (ret == 1) ? 0 : -EIO; +} + static acpi_status i2c_acpi_space_handler(u32 function, acpi_physical_address command, u32 bits, u64 *value64, @@ -764,6 +785,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, } break; + case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: + if (action == ACPI_READ) { + dev_warn(&adapter->dev, + "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); + ret = AE_BAD_PARAMETER; + goto err; + } else { + status = acpi_gsb_i2c_write_raw_bytes(client, + gsb->data, info->access_length); + } + break; + default: dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", accessor_type, client->addr); diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index b8cad415c62ca2..43b5d56383e36c 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -540,8 +540,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned * devices use MSHW0040 for power and volume buttons, however the way they * have to be addressed differs. Make sure that we only load this drivers - * for the correct devices by checking the OEM Platform Revision provided by - * the _DSM method. + * for the correct devices by checking if the OEM Platform Revision DSM call + * exists. */ #define MSHW0040_DSM_REVISION 0x01 #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision @@ -552,31 +552,14 @@ static const guid_t MSHW0040_DSM_UUID = static int soc_device_check_MSHW0040(struct device *dev) { acpi_handle handle = ACPI_HANDLE(dev); - union acpi_object *result; - u64 oem_platform_rev = 0; // valid revisions are nonzero - - // get OEM platform revision - result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, - MSHW0040_DSM_REVISION, - MSHW0040_DSM_GET_OMPR, NULL, - ACPI_TYPE_INTEGER); - - if (result) { - oem_platform_rev = result->integer.value; - ACPI_FREE(result); - } - - /* - * If the revision is zero here, the _DSM evaluation has failed. This - * indicates that we have a Pro 4 or Book 1 and this driver should not - * be used. - */ - if (oem_platform_rev == 0) - return -ENODEV; + bool exists; - dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); + // check if OEM platform revision DSM call exists + exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, + MSHW0040_DSM_REVISION, + BIT(MSHW0040_DSM_GET_OMPR)); - return 0; + return exists ? 0 : -ENODEV; } /* diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig index 074130d8895381..086b2e9d443e47 100644 --- a/drivers/input/mouse/Kconfig +++ b/drivers/input/mouse/Kconfig @@ -69,6 +69,18 @@ config MOUSE_PS2_LOGIPS2PP If unsure, say Y. +config MOUSE_PS2_PIXART + bool "PixArt PS/2 touchpad protocol extension" if EXPERT + default y + depends on MOUSE_PS2 + help + This driver supports the PixArt PS/2 touchpad found in some + laptops. + Say Y here if you have a PixArt PS/2 TouchPad connected to + your system. + + If unsure, say Y. + config MOUSE_PS2_SYNAPTICS bool "Synaptics PS/2 mouse protocol extension" if EXPERT default y diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile index ed40f141691e2f..0572a02be2388a 100644 --- a/drivers/input/mouse/Makefile +++ b/drivers/input/mouse/Makefile @@ -28,6 +28,7 @@ psmouse-$(CONFIG_MOUSE_PS2_BYD) += byd.o psmouse-$(CONFIG_MOUSE_PS2_ELANTECH) += elantech.o psmouse-$(CONFIG_MOUSE_PS2_LOGIPS2PP) += logips2pp.o psmouse-$(CONFIG_MOUSE_PS2_LIFEBOOK) += lifebook.o +psmouse-$(CONFIG_MOUSE_PS2_PIXART) += pixart_ps2.o psmouse-$(CONFIG_MOUSE_PS2_SENTELIC) += sentelic.o psmouse-$(CONFIG_MOUSE_PS2_TRACKPOINT) += trackpoint.o psmouse-$(CONFIG_MOUSE_PS2_TOUCHKIT) += touchkit_ps2.o diff --git a/drivers/input/mouse/pixart_ps2.c b/drivers/input/mouse/pixart_ps2.c new file mode 100644 index 00000000000000..682aa6499e63c5 --- /dev/null +++ b/drivers/input/mouse/pixart_ps2.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Pixart Touchpad Controller 1336U PS2 driver + * + * Author: Jon Xie + * Jay Lee + * Further cleanup and restructuring by: + * Binbin Zhou + * + * Copyright (C) 2021-2024 Pixart Imaging. + * Copyright (C) 2024-2025 Loongson Technology Corporation Limited. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pixart_ps2.h" + +static int pixart_read_tp_mode(struct ps2dev *ps2dev, u8 *mode) +{ + int error; + u8 param[1] = { 0 }; + + error = ps2_command(ps2dev, param, PIXART_CMD_REPORT_FORMAT); + if (error) + return error; + + *mode = param[0] == 1 ? PIXART_MODE_ABS : PIXART_MODE_REL; + + return 0; +} + +static int pixart_read_tp_type(struct ps2dev *ps2dev, u8 *type) +{ + int error; + u8 param[3] = { 0 }; + + param[0] = 0xa; + error = ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + if (error) + return error; + + param[0] = 0x0; + error = ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + if (error) + return error; + + error = ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + if (error) + return error; + + error = ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + if (error) + return error; + + param[0] = 0x3; + error = ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + if (error) + return error; + + error = ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO); + if (error) + return error; + + switch (param[0]) { + case 0xc: + *type = PIXART_TYPE_CLICKPAD; + break; + case 0xe: + *type = PIXART_TYPE_TOUCHPAD; + break; + default: + return -EIO; + } + + return 0; +} + +static void pixart_reset(struct psmouse *psmouse) +{ + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); + + /* according to PixArt, 100ms is required for the upcoming reset */ + msleep(100); + psmouse_reset(psmouse); +} + +static void pixart_process_packet(struct psmouse *psmouse) +{ + struct pixart_data *priv = psmouse->private; + struct input_dev *dev = psmouse->dev; + const u8 *pkt = psmouse->packet; + unsigned int contact_cnt = FIELD_GET(CONTACT_CNT_MASK, pkt[0]); + unsigned int i, id, abs_x, abs_y; + bool tip; + + for (i = 0; i < contact_cnt; i++) { + const u8 *p = &pkt[i * 3]; + + id = FIELD_GET(SLOT_ID_MASK, p[3]); + abs_y = FIELD_GET(ABS_Y_MASK, p[3]) << 8 | p[1]; + abs_x = FIELD_GET(ABS_X_MASK, p[3]) << 8 | p[2]; + + if (i == PIXART_MAX_FINGERS - 1) + tip = pkt[14] & BIT(1); + else + tip = pkt[3 * contact_cnt + 1] & BIT(2 * i + 1); + + input_mt_slot(dev, id); + if (input_mt_report_slot_state(dev, MT_TOOL_FINGER, tip)) { + input_report_abs(dev, ABS_MT_POSITION_Y, abs_y); + input_report_abs(dev, ABS_MT_POSITION_X, abs_x); + } + } + + input_mt_sync_frame(dev); + + if (priv->type == PIXART_TYPE_CLICKPAD) { + input_report_key(dev, BTN_LEFT, pkt[0] & 0x03); + } else { + input_report_key(dev, BTN_LEFT, pkt[0] & BIT(0)); + input_report_key(dev, BTN_RIGHT, pkt[0] & BIT(1)); + } + + input_sync(dev); +} + +static psmouse_ret_t pixart_protocol_handler(struct psmouse *psmouse) +{ + u8 *pkt = psmouse->packet; + u8 contact_cnt; + + if ((pkt[0] & 0x8c) != 0x80) + return PSMOUSE_BAD_DATA; + + contact_cnt = FIELD_GET(CONTACT_CNT_MASK, pkt[0]); + if (contact_cnt > PIXART_MAX_FINGERS) + return PSMOUSE_BAD_DATA; + + if (contact_cnt == PIXART_MAX_FINGERS && + psmouse->pktcnt < psmouse->pktsize) { + return PSMOUSE_GOOD_DATA; + } + + if (contact_cnt == 0 && psmouse->pktcnt < 5) + return PSMOUSE_GOOD_DATA; + + if (psmouse->pktcnt < 3 * contact_cnt + 2) + return PSMOUSE_GOOD_DATA; + + pixart_process_packet(psmouse); + + return PSMOUSE_FULL_PACKET; +} + +static void pixart_disconnect(struct psmouse *psmouse) +{ + pixart_reset(psmouse); + kfree(psmouse->private); + psmouse->private = NULL; +} + +static int pixart_reconnect(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 mode; + int error; + + pixart_reset(psmouse); + + error = pixart_read_tp_mode(ps2dev, &mode); + if (error) + return error; + + if (mode != PIXART_MODE_ABS) + return -EIO; + + error = ps2_command(ps2dev, NULL, PIXART_CMD_SWITCH_PROTO); + if (error) + return error; + + return 0; +} + +static int pixart_set_input_params(struct input_dev *dev, + struct pixart_data *priv) +{ + /* No relative support */ + __clear_bit(EV_REL, dev->evbit); + __clear_bit(REL_X, dev->relbit); + __clear_bit(REL_Y, dev->relbit); + __clear_bit(BTN_MIDDLE, dev->keybit); + + /* Buttons */ + __set_bit(EV_KEY, dev->evbit); + __set_bit(BTN_LEFT, dev->keybit); + if (priv->type == PIXART_TYPE_CLICKPAD) + __set_bit(INPUT_PROP_BUTTONPAD, dev->propbit); + else + __set_bit(BTN_RIGHT, dev->keybit); + + /* Absolute position */ + input_set_abs_params(dev, ABS_X, 0, PIXART_PAD_WIDTH, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, PIXART_PAD_HEIGHT, 0, 0); + + input_set_abs_params(dev, ABS_MT_POSITION_X, + 0, PIXART_PAD_WIDTH, 0, 0); + input_set_abs_params(dev, ABS_MT_POSITION_Y, + 0, PIXART_PAD_HEIGHT, 0, 0); + + return input_mt_init_slots(dev, PIXART_MAX_FINGERS, + INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED); +} + +static int pixart_query_hardware(struct ps2dev *ps2dev, u8 *mode, u8 *type) +{ + int error; + + error = pixart_read_tp_type(ps2dev, type); + if (error) + return error; + + error = pixart_read_tp_mode(ps2dev, mode); + if (error) + return error; + + return 0; +} + +int pixart_detect(struct psmouse *psmouse, bool set_properties) +{ + u8 type; + int error; + + pixart_reset(psmouse); + + error = pixart_read_tp_type(&psmouse->ps2dev, &type); + if (error) + return error; + + if (set_properties) { + psmouse->vendor = "PixArt"; + psmouse->name = (type == PIXART_TYPE_TOUCHPAD) ? + "touchpad" : "clickpad"; + } + + return 0; +} + +int pixart_init(struct psmouse *psmouse) +{ + int error; + struct pixart_data *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + psmouse->private = priv; + pixart_reset(psmouse); + + error = pixart_query_hardware(&psmouse->ps2dev, + &priv->mode, &priv->type); + if (error) { + psmouse_err(psmouse, "init: Unable to query PixArt touchpad hardware.\n"); + goto err_exit; + } + + /* Relative mode follows standard PS/2 mouse protocol */ + if (priv->mode != PIXART_MODE_ABS) { + error = -EIO; + goto err_exit; + } + + /* Set absolute mode */ + error = ps2_command(&psmouse->ps2dev, NULL, PIXART_CMD_SWITCH_PROTO); + if (error) { + psmouse_err(psmouse, "init: Unable to initialize PixArt absolute mode.\n"); + goto err_exit; + } + + error = pixart_set_input_params(psmouse->dev, priv); + if (error) { + psmouse_err(psmouse, "init: Unable to set input params.\n"); + goto err_exit; + } + + psmouse->pktsize = 15; + psmouse->protocol_handler = pixart_protocol_handler; + psmouse->disconnect = pixart_disconnect; + psmouse->reconnect = pixart_reconnect; + psmouse->cleanup = pixart_reset; + /* resync is not supported yet */ + psmouse->resync_time = 0; + + return 0; + +err_exit: + pixart_reset(psmouse); + kfree(priv); + psmouse->private = NULL; + return error; +} diff --git a/drivers/input/mouse/pixart_ps2.h b/drivers/input/mouse/pixart_ps2.h new file mode 100644 index 00000000000000..47a1d040f2d10f --- /dev/null +++ b/drivers/input/mouse/pixart_ps2.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _PIXART_PS2_H +#define _PIXART_PS2_H + +#include "psmouse.h" + +#define PIXART_PAD_WIDTH 1023 +#define PIXART_PAD_HEIGHT 579 +#define PIXART_MAX_FINGERS 4 + +#define PIXART_CMD_REPORT_FORMAT 0x01d8 +#define PIXART_CMD_SWITCH_PROTO 0x00de + +#define PIXART_MODE_REL 0 +#define PIXART_MODE_ABS 1 + +#define PIXART_TYPE_CLICKPAD 0 +#define PIXART_TYPE_TOUCHPAD 1 + +#define CONTACT_CNT_MASK GENMASK(6, 4) + +#define SLOT_ID_MASK GENMASK(2, 0) +#define ABS_Y_MASK GENMASK(5, 4) +#define ABS_X_MASK GENMASK(7, 6) + +struct pixart_data { + u8 mode; + u8 type; + int x_max; + int y_max; +}; + +int pixart_detect(struct psmouse *psmouse, bool set_properties); +int pixart_init(struct psmouse *psmouse); + +#endif /* _PIXART_PS2_H */ diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index 6ab5f1d96eaeaa..6a3faaedd4f764 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -35,6 +35,7 @@ #include "focaltech.h" #include "vmmouse.h" #include "byd.h" +#include "pixart_ps2.h" #define DRIVER_DESC "PS/2 mouse driver" @@ -886,6 +887,15 @@ static const struct psmouse_protocol psmouse_protocols[] = { .detect = byd_detect, .init = byd_init, }, +#endif +#ifdef CONFIG_MOUSE_PS2_PIXART + { + .type = PSMOUSE_PIXART, + .name = "PixArtPS/2", + .alias = "pixart", + .detect = pixart_detect, + .init = pixart_init, + }, #endif { .type = PSMOUSE_AUTO, @@ -1146,6 +1156,13 @@ static int psmouse_extensions(struct psmouse *psmouse, return ret; } + /* Try PixArt touchpad */ + if (max_proto > PSMOUSE_IMEX && + psmouse_try_protocol(psmouse, PSMOUSE_PIXART, &max_proto, + set_properties, true)) { + return PSMOUSE_PIXART; + } + if (max_proto > PSMOUSE_IMEX) { if (psmouse_try_protocol(psmouse, PSMOUSE_GENPS, &max_proto, set_properties, true)) diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h index 90ed8cd15d857b..03aaa419d074cf 100644 --- a/drivers/input/mouse/psmouse.h +++ b/drivers/input/mouse/psmouse.h @@ -69,6 +69,7 @@ enum psmouse_type { PSMOUSE_BYD, PSMOUSE_SYNAPTICS_SMBUS, PSMOUSE_ELANTECH_SMBUS, + PSMOUSE_PIXART, PSMOUSE_AUTO /* This one should always be last */ }; @@ -94,7 +95,7 @@ struct psmouse { const char *vendor; const char *name; const struct psmouse_protocol *protocol; - unsigned char packet[8]; + unsigned char packet[16]; unsigned char badbyte; unsigned char pktcnt; unsigned char pktsize; diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c index 8bcce11cb7ce3d..316e9506f26171 100644 --- a/drivers/input/serio/i8042.c +++ b/drivers/input/serio/i8042.c @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -183,6 +184,24 @@ static bool i8042_handle_data(int irq); static i8042_filter_t i8042_platform_filter; static void *i8042_platform_filter_context; +static int __init i8042_set_noaux(const struct dmi_system_id *dmi) +{ + i8042_noaux = true; + return 1; +} + +static const struct dmi_system_id i8042_quirks[] = { + { + .callback = i8042_set_noaux, + .ident = "Dell laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Precision 5550"), + }, + }, + {}, +}; + void i8042_lock_chip(void) { mutex_lock(&i8042_mutex); @@ -622,7 +641,7 @@ static int i8042_enable_kbd_port(void) if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { i8042_ctr &= ~I8042_CTR_KBDINT; i8042_ctr |= I8042_CTR_KBDDIS; - pr_err("Failed to enable KBD port\n"); + pr_info("Failed to enable KBD port\n"); return -EIO; } @@ -641,7 +660,7 @@ static int i8042_enable_aux_port(void) if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { i8042_ctr &= ~I8042_CTR_AUXINT; i8042_ctr |= I8042_CTR_AUXDIS; - pr_err("Failed to enable AUX port\n"); + pr_info("Failed to enable AUX port\n"); return -EIO; } @@ -733,7 +752,7 @@ static int i8042_check_mux(void) i8042_ctr &= ~I8042_CTR_AUXINT; if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { - pr_err("Failed to disable AUX port, can't use MUX\n"); + pr_info("Failed to disable AUX port, can't use MUX\n"); return -EIO; } @@ -942,25 +961,28 @@ static int i8042_controller_selftest(void) { unsigned char param; int i = 0; + int ret; /* * We try this 5 times; on some really fragile systems this does not * take the first time... */ - do { + while (i++ < 5) { - if (i8042_command(¶m, I8042_CMD_CTL_TEST)) { - pr_err("i8042 controller selftest timeout\n"); - return -ENODEV; - } - - if (param == I8042_RET_CTL_TEST) + ret = i8042_command(¶m, I8042_CMD_CTL_TEST); + if (ret) + pr_info("i8042 controller selftest timeout (%d/5)\n", i); + else if (param == I8042_RET_CTL_TEST) return 0; + else + dbg("i8042 controller selftest: %#x != %#x\n", + param, I8042_RET_CTL_TEST); - dbg("i8042 controller selftest: %#x != %#x\n", - param, I8042_RET_CTL_TEST); msleep(50); - } while (i++ < 5); + } + + if (ret) + return -ENODEV; #ifdef CONFIG_X86 /* @@ -972,7 +994,7 @@ static int i8042_controller_selftest(void) pr_info("giving up on controller selftest, continuing anyway...\n"); return 0; #else - pr_err("i8042 controller selftest failed\n"); + pr_info("i8042 controller selftest failed\n"); return -EIO; #endif } @@ -1553,6 +1575,8 @@ static int i8042_probe(struct platform_device *dev) i8042_dritek_enable(); #endif + dmi_check_system(i8042_quirks); + if (!i8042_noaux) { error = i8042_setup_aux(); if (error && error != -ENODEV && error != -EBUSY) diff --git a/drivers/input/touchscreen/surface3_spi.c b/drivers/input/touchscreen/surface3_spi.c index 6074b7730e8621..6aa3e1d6f160a8 100644 --- a/drivers/input/touchscreen/surface3_spi.c +++ b/drivers/input/touchscreen/surface3_spi.c @@ -25,6 +25,12 @@ #define SURFACE3_REPORT_TOUCH 0xd2 #define SURFACE3_REPORT_PEN 0x16 +bool use_dma = false; +module_param(use_dma, bool, 0644); +MODULE_PARM_DESC(use_dma, + "Disable DMA mode if you encounter touch input crash. " + "(default: false, disabled to avoid crash)"); + struct surface3_ts_data { struct spi_device *spi; struct gpio_desc *gpiod_rst[2]; @@ -317,6 +323,13 @@ static int surface3_spi_create_pen_input(struct surface3_ts_data *data) return 0; } +static bool surface3_spi_can_dma(struct spi_controller *ctlr, + struct spi_device *spi, + struct spi_transfer *tfr) +{ + return use_dma; +} + static int surface3_spi_probe(struct spi_device *spi) { struct surface3_ts_data *data; @@ -359,6 +372,19 @@ static int surface3_spi_probe(struct spi_device *spi) if (error) return error; + /* + * Set up DMA + * + * TODO: Currently, touch input with DMA seems to be broken. + * On 4.19 LTS, touch input will crash after suspend. + * On recent stable kernel (at least after 5.1), touch input will crash after + * the first touch. No problem with PIO on those kernels. + * Maybe we need to configure DMA here. + * + * Link to issue: https://github.com/jakeday/linux-surface/issues/596 + */ + spi->controller->can_dma = surface3_spi_can_dma; + return 0; } diff --git a/drivers/iommu/generic_pt/fmt/riscv.h b/drivers/iommu/generic_pt/fmt/riscv.h index a7fef6266a36a6..ae9a76514416c2 100644 --- a/drivers/iommu/generic_pt/fmt/riscv.h +++ b/drivers/iommu/generic_pt/fmt/riscv.h @@ -64,6 +64,8 @@ enum { RISCVPT_PPN64 = GENMASK_ULL(53, 10), RISCVPT_PPN64_64K = GENMASK_ULL(53, 14), RISCVPT_PBMT = GENMASK_ULL(62, 61), + RISCVPT_NC = BIT_ULL(61), + RISCVPT_IO = BIT_ULL(62), RISCVPT_N = BIT_ULL(63), /* Svnapot encodings for ppn[0] */ @@ -201,7 +203,8 @@ static inline void riscvpt_attr_from_entry(const struct pt_state *pts, { attrs->descriptor_bits = pts->entry & (RISCVPT_R | RISCVPT_W | RISCVPT_X | RISCVPT_U | - RISCVPT_G | RISCVPT_A | RISCVPT_D); + RISCVPT_G | RISCVPT_A | RISCVPT_D | RISCVPT_NC | + RISCVPT_IO); } #define pt_attr_from_entry riscvpt_attr_from_entry @@ -237,6 +240,12 @@ static inline int riscvpt_iommu_set_prot(struct pt_common *common, pte |= RISCVPT_R; if (!(iommu_prot & IOMMU_NOEXEC)) pte |= RISCVPT_X; + if (common->features & BIT(PT_FEAT_RISCV_SVPBMT)) { + if (iommu_prot & IOMMU_MMIO) + pte |= RISCVPT_IO; + else if (!(iommu_prot & IOMMU_CACHE)) + pte |= RISCVPT_NC; + } /* Caller must specify a supported combination of flags */ if (unlikely((pte & (RISCVPT_X | RISCVPT_W | RISCVPT_R)) == 0)) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 4d0e65bc131d77..b0428bf4269b94 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -37,8 +37,21 @@ #define IS_GFX_DEVICE(pdev) pci_is_display(pdev) #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) +#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ + ((pdev)->device == 0xa75d || \ + (pdev)->device == 0x9a19 || \ + (pdev)->device == 0x9a39 || \ + (pdev)->device == 0x7d19 || \ + (pdev)->device == 0x4e19 || \ + (pdev)->device == 0x465d || \ + (pdev)->device == 0x1919)) #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) +#define IS_IPTS(pdev) ( \ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x9D3E) || \ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ + ) + #define IOAPIC_RANGE_START (0xfee00000) #define IOAPIC_RANGE_END (0xfeefffff) #define IOVA_START_ADDR (0x1000) @@ -202,12 +215,16 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); int intel_iommu_enabled = 0; EXPORT_SYMBOL_GPL(intel_iommu_enabled); +static int dmar_map_ipu = 1; +static int dmar_map_ipts = 1; static int intel_iommu_superpage = 1; static int iommu_identity_mapping; static int iommu_skip_te_disable; static int disable_igfx_iommu; #define IDENTMAP_AZALIA 4 +#define IDENTMAP_IPU 8 +#define IDENTMAP_IPTS 16 const struct iommu_ops intel_iommu_ops; @@ -1398,6 +1415,12 @@ static int device_def_domain_type(struct device *dev) if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) return IOMMU_DOMAIN_IDENTITY; + + if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) + return IOMMU_DOMAIN_IDENTITY; + + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; } return 0; @@ -1688,6 +1711,12 @@ static int __init init_dmars(void) iommu_set_root_entry(iommu); } + if (!dmar_map_ipu) + iommu_identity_mapping |= IDENTMAP_IPU; + + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + check_tylersburg_isoch(); /* @@ -3979,6 +4008,105 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); +/* SKL */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1906, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1913, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x190E, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1915, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1902, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x190A, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x190B, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1917, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1916, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1921, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x191E, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1912, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x191A, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x191B, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x191D, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1923, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1926, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1927, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x192A, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x192B, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x192D, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1932, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x193A, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x193B, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x193D, quirk_iommu_igfx); + +/* KBL */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5902, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5906, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5908, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x590A, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x590B, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x590E, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5912, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5913, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5915, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5916, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5917, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x591A, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x591B, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x591D, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x591E, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5921, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5923, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5926, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x5927, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x593B, quirk_iommu_igfx); + +/* CML */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9B21, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BA2, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BA4, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BA5, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BA8, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BAA, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BAC, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BC2, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BC4, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BC5, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BC6, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BC8, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BE6, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BF6, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9B41, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BCA, quirk_iommu_igfx); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9BCC, quirk_iommu_igfx); + +static void quirk_iommu_ipts(struct pci_dev *dev) +{ + if (!IS_IPTS(dev)) + return; + + if (risky_device(dev)) + return; + + pci_info(dev, "Disabling IOMMU for IPTS\n"); + dmar_map_ipts = 0; +} + +/* disable IPTS dmar support */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); + +static void quirk_iommu_ipu(struct pci_dev *dev) +{ + if (!IS_INTEL_IPU(dev)) + return; + + if (risky_device(dev)) + return; + + pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); + dmar_map_ipu = 0; +} + +/* disable IPU dmar support */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); + static void quirk_iommu_rwbf(struct pci_dev *dev) { if (risky_device(dev)) diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c index 25c26f706984a4..772bb0d8e5888b 100644 --- a/drivers/iommu/intel/irq_remapping.c +++ b/drivers/iommu/intel/irq_remapping.c @@ -381,6 +381,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) data.busmatch_count = 0; pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + /* + * The Intel Touch Host Controller is at 00:10.6, but for some reason + * the MSI interrupts have request id 01:05.0. + * Disable id verification to work around this. + * FIXME Find proper fix or turn this into a quirk. + */ + if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { + switch(dev->device) { + case 0x98d0: case 0x98d1: // LKF + case 0xa0d0: case 0xa0d1: // TGL LP + case 0x43d0: case 0x43d1: // TGL H + set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); + return 0; + } + } + /* * DMA alias provides us with a PCI device and alias. The only case * where the it will return an alias on a different bus than the diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c index a31f50bbad3535..6c324f9fdc5347 100644 --- a/drivers/iommu/riscv/iommu.c +++ b/drivers/iommu/riscv/iommu.c @@ -1268,6 +1268,8 @@ static struct iommu_domain *riscv_iommu_alloc_paging_domain(struct device *dev) cfg.common.features = BIT(PT_FEAT_SIGN_EXTEND) | BIT(PT_FEAT_FLUSH_RANGE) | BIT(PT_FEAT_RISCV_SVNAPOT_64K); + if (iommu->caps & RISCV_IOMMU_CAPABILITIES_SVPBMT) + cfg.common.features |= BIT(PT_FEAT_RISCV_SVPBMT); domain->riscvpt.iommu.nid = dev_to_node(iommu->dev); domain->domain.ops = &riscv_iommu_paging_domain_ops; diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index f4a0a3c8c8705e..646f1836005d96 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -72,6 +72,17 @@ config LEDS_88PM860X This option enables support for on-chip LED drivers found on Marvell Semiconductor 88PM8606 PMIC. +config LEDS_AAEON + tristate "AAEON LED driver" + depends on X86 + select MFD_AAEON + help + This led driver adds support for LED brightness control on Single + Board Computers produced by AAEON. + + This driver leverages the ASUS WMI interface to access device + resources. + config LEDS_AN30259A tristate "LED support for Panasonic AN30259A" depends on LEDS_CLASS && I2C && OF @@ -1010,6 +1021,18 @@ config LEDS_TPS6105X It is a single boost converter primarily for white LEDs and audio amplifiers. +config LEDS_TPS68470 + tristate "LED support for TI TPS68470" + depends on LEDS_CLASS + depends on INTEL_SKL_INT3472 + help + This driver supports TPS68470 PMIC with LED chip. + It provides two LED controllers, with the ability to drive 2 + indicator LEDs and 2 flash LEDs. + + To compile this driver as a module, choose M and it will be + called leds-tps68470 + config LEDS_IP30 tristate "LED support for SGI Octane machines" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 8fdb45d5b43930..9d3cc8a0b6b460 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_KUNIT_TEST) += led-test.o # LED Platform Drivers (keep this sorted, M-| sort) obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o +obj-$(CONFIG_LEDS_AAEON) += leds-aaeon.o obj-$(CONFIG_LEDS_ACER_A500) += leds-acer-a500.o obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o @@ -94,6 +95,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o +obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o obj-$(CONFIG_LEDS_UPBOARD) += leds-upboard.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o diff --git a/drivers/leds/leds-aaeon.c b/drivers/leds/leds-aaeon.c new file mode 100644 index 00000000000000..4034dfaba9eab0 --- /dev/null +++ b/drivers/leds/leds-aaeon.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AAEON LED driver + * + * Copyright (c) 2021, AAEON Ltd. + * + * Author: Kunyang Fan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include + +#define DRVNAME "led_aaeon" +#define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C" +#define AAEON_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" + +#define GET_LED_NUMBER_ID 0x00060000 +#define GET_LED_METHOD_ID 0x00060001 +#define SET_LED_METHOD_ID 0x00060002 +#define GET_LED_NUMBER_METHOD_ID 0x10 + + +struct aaeon_led_data { + int id; + struct led_classdev cdev; +}; + +static int aaeon_led_get_number(void) +{ + int err, retval; + + err = asus_wmi_evaluate_method(GET_LED_NUMBER_ID, + GET_LED_NUMBER_METHOD_ID, + 0, &retval); + if (err) + return err; + + return retval; +} + +static enum led_brightness aaeon_led_brightness_get(struct led_classdev + *cdev) +{ + int err, brightness; + struct aaeon_led_data *led = + container_of(cdev, struct aaeon_led_data, cdev); + u32 arg0; + + arg0 = (u32)(led->id & 0xF); + err = asus_wmi_evaluate_method(GET_LED_METHOD_ID, arg0, 0, &brightness); + if (err) + return err; + + return brightness; +}; + +static void aaeon_led_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + int err, retval; + struct aaeon_led_data *led = + container_of(cdev, struct aaeon_led_data, cdev); + u32 arg0; + + arg0 = (u32)(led->id & 0xF); + if (brightness != LED_OFF) + arg0 |= BIT(16); + + err = asus_wmi_evaluate_method(SET_LED_METHOD_ID, arg0, 0, &retval); +}; + +static int __init aaeon_add_led_device(struct platform_device *pdev, + int id) +{ + struct aaeon_led_data *led; + + led = devm_kzalloc(&pdev->dev, sizeof(struct aaeon_led_data), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->id = id; + led->cdev.brightness_get = aaeon_led_brightness_get; + led->cdev.brightness_set = aaeon_led_brightness_set; + led->cdev.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "led:%d:", id); + + if (!led->cdev.name) + return -ENOMEM; + + return devm_led_classdev_register(&pdev->dev, &led->cdev); +} + +static int aaeon_led_probe(struct platform_device *pdev) +{ + int err = -ENODEV, i; + int led_number = 0; + + pr_debug("aaeon led device probe!\n"); + /* Prevent other drivers adding this platfom device */ + if (!wmi_has_guid(AAEON_WMI_MGMT_GUID)) { + pr_debug("AAEON Management GUID not found\n"); + return -ENODEV; + } + + /* Query the number of led devices board support */ + led_number = aaeon_led_get_number(); + + /* + * If the number is 0 or can't get the number of leds, + * no need to register any led device node. + */ + if (led_number <= 0) + return -ENODEV; + + for (i = 0; i < led_number; i++) { + err = aaeon_add_led_device(pdev, i); + if (err) + break; + } + + return err; +} + +static struct platform_driver aaeon_led_driver = { + .driver = { + .name = "leds-aaeon", + }, +}; + +module_platform_driver_probe(aaeon_led_driver, aaeon_led_probe); + +MODULE_ALIAS("platform:leds-aaeon"); +MODULE_DESCRIPTION("AAEON LED Driver"); +MODULE_AUTHOR("Kunyang Fan "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("ASUS_WMI"); diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c new file mode 100644 index 00000000000000..35aeb5db89c8fe --- /dev/null +++ b/drivers/leds/leds-tps68470.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LED driver for TPS68470 PMIC + * + * Copyright (C) 2023 Red Hat + * + * Authors: + * Kate Hsuan + */ + +#include +#include +#include +#include +#include +#include + + +#define lcdev_to_led(led_cdev) \ + container_of(led_cdev, struct tps68470_led, lcdev) + +#define led_to_tps68470(led, index) \ + container_of(led, struct tps68470_device, leds[index]) + +enum tps68470_led_ids { + TPS68470_ILED_A, + TPS68470_ILED_B, + TPS68470_NUM_LEDS +}; + +static const char *tps68470_led_names[] = { + [TPS68470_ILED_A] = "tps68470-iled_a", + [TPS68470_ILED_B] = "tps68470-iled_b", +}; + +struct tps68470_led { + unsigned int led_id; + struct led_classdev lcdev; +}; + +struct tps68470_device { + struct device *dev; + struct regmap *regmap; + struct tps68470_led leds[TPS68470_NUM_LEDS]; +}; + +enum ctrlb_current { + CTRLB_2MA = 0, + CTRLB_4MA = 1, + CTRLB_8MA = 2, + CTRLB_16MA = 3, +}; + +static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) +{ + struct tps68470_led *led = lcdev_to_led(led_cdev); + struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); + struct regmap *regmap = tps68470->regmap; + + switch (led->led_id) { + case TPS68470_ILED_A: + return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, + brightness ? TPS68470_ILEDCTL_ENA : 0); + case TPS68470_ILED_B: + return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, + brightness ? TPS68470_ILEDCTL_ENB : 0); + } + return -EINVAL; +} + +static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) +{ + struct tps68470_led *led = lcdev_to_led(led_cdev); + struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); + struct regmap *regmap = tps68470->regmap; + int ret = 0; + int value = 0; + + ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); + if (ret) + return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); + + switch (led->led_id) { + case TPS68470_ILED_A: + value = value & TPS68470_ILEDCTL_ENA; + break; + case TPS68470_ILED_B: + value = value & TPS68470_ILEDCTL_ENB; + break; + } + + return value ? LED_ON : LED_OFF; +} + + +static int tps68470_ledb_current_init(struct platform_device *pdev, + struct tps68470_device *tps68470) +{ + int ret = 0; + unsigned int curr; + + /* configure LEDB current if the properties can be got */ + if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { + if (curr > CTRLB_16MA) { + dev_err(&pdev->dev, + "Invalid LEDB current value: %d\n", + curr); + return -EINVAL; + } + ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, + TPS68470_ILEDCTL_CTRLB, curr); + } + return ret; +} + +static int tps68470_leds_probe(struct platform_device *pdev) +{ + int i = 0; + int ret = 0; + struct tps68470_device *tps68470; + struct tps68470_led *led; + struct led_classdev *lcdev; + + tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), + GFP_KERNEL); + if (!tps68470) + return -ENOMEM; + + tps68470->dev = &pdev->dev; + tps68470->regmap = dev_get_drvdata(pdev->dev.parent); + + for (i = 0; i < TPS68470_NUM_LEDS; i++) { + led = &tps68470->leds[i]; + lcdev = &led->lcdev; + + led->led_id = i; + + lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", + tps68470_led_names[i], LED_FUNCTION_INDICATOR); + if (!lcdev->name) + return -ENOMEM; + + lcdev->max_brightness = 1; + lcdev->brightness = 0; + lcdev->brightness_set_blocking = tps68470_brightness_set; + lcdev->brightness_get = tps68470_brightness_get; + lcdev->dev = &pdev->dev; + + ret = devm_led_classdev_register(tps68470->dev, lcdev); + if (ret) { + dev_err_probe(tps68470->dev, ret, + "error registering led\n"); + goto err_exit; + } + + if (i == TPS68470_ILED_B) { + ret = tps68470_ledb_current_init(pdev, tps68470); + if (ret) + goto err_exit; + } + } + +err_exit: + if (ret) { + for (i = 0; i < TPS68470_NUM_LEDS; i++) { + if (tps68470->leds[i].lcdev.name) + devm_led_classdev_unregister(&pdev->dev, + &tps68470->leds[i].lcdev); + } + } + + return ret; +} +static struct platform_driver tps68470_led_driver = { + .driver = { + .name = "tps68470-led", + }, + .probe = tps68470_leds_probe, +}; + +module_platform_driver(tps68470_led_driver); + +MODULE_ALIAS("platform:tps68470-led"); +MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c index 59558335989ed2..b89765521af5fa 100644 --- a/drivers/media/i2c/dw9719.c +++ b/drivers/media/i2c/dw9719.c @@ -121,6 +121,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719, bool detect) if (ret) return ret; + /* Wait for device to be acknowledged */ + fsleep(10000); + /* * Need 100us to transition from SHUTDOWN to STANDBY. * Jiggle the SCL pin to wake up the device (even when the regulator is diff --git a/drivers/media/i2c/ov13858.c b/drivers/media/i2c/ov13858.c index 162b49046990a1..85cd0c98af031e 100644 --- a/drivers/media/i2c/ov13858.c +++ b/drivers/media/i2c/ov13858.c @@ -2,6 +2,7 @@ // Copyright (c) 2017 Intel Corporation. #include +#include #include #include #include @@ -119,6 +120,14 @@ struct ov13858_mode { struct ov13858_reg_list reg_list; }; +// Or use standard names that might be more correct: +static const char * const ov13858_supply_names[] = { + "avdd", // Analog voltage + "pwr1", // Digital core voltage +}; + +#define OV13858_NUM_SUPPLIES ARRAY_SIZE(ov13858_supply_names) + /* 4224x3136 needs 1080Mbps/lane, 4 lanes */ static const struct ov13858_reg mipi_data_rate_1080mbps[] = { /* PLL1 registers */ @@ -1046,12 +1055,126 @@ struct ov13858 { /* Current mode */ const struct ov13858_mode *cur_mode; + struct regulator_bulk_data supplies[OV13858_NUM_SUPPLIES]; + struct gpio_desc *reset; + struct clk *xvclk; + /* Mutex for serialized access */ struct mutex mutex; }; #define to_ov13858(_sd) container_of(_sd, struct ov13858, sd) + +static int ov13858_get_regulators(struct ov13858 *ov13858) +{ + unsigned int i; + + for (i = 0; i < OV13858_NUM_SUPPLIES; i++) + ov13858->supplies[i].supply = ov13858_supply_names[i]; + + return devm_regulator_bulk_get(ov13858->dev, OV13858_NUM_SUPPLIES, + ov13858->supplies); +} + +static int ov13858_sensor_powerup(struct ov13858 *ov13858) +{ + int ret; + + dev_info(ov13858->dev, "Powering up sensor\n"); + + /* Assert reset */ + gpiod_set_value_cansleep(ov13858->reset, 1); + + /* Enable clock FIRST - sensor needs clock to communicate */ + ret = clk_prepare_enable(ov13858->xvclk); + if (ret) { + dev_err(ov13858->dev, "Failed to enable clock: %d\n", ret); + return ret; + } + dev_info(ov13858->dev, "Clock enabled\n"); + + /* Enable regulators */ + ret = regulator_bulk_enable(OV13858_NUM_SUPPLIES, ov13858->supplies); + if (ret) { + dev_err(ov13858->dev, "Failed to enable regulators: %d\n", ret); + clk_disable_unprepare(ov13858->xvclk); + return ret; + } + + dev_info(ov13858->dev, "Regulators enabled\n"); + + /* Wait for power to stabilize */ + usleep_range(5000, 10000); + + /* De-assert reset */ + gpiod_set_value_cansleep(ov13858->reset, 0); + + /* Wait for sensor to boot */ + msleep(20); + + dev_info(ov13858->dev, "Reset de-asserted, sensor should be ready\n"); + + return 0; +} + +static void ov13858_sensor_powerdown(struct ov13858 *ov13858) +{ + dev_info(ov13858->dev, "Powering down sensor\n"); + + /* Assert reset to put sensor in reset state */ + gpiod_set_value_cansleep(ov13858->reset, 1); + + /* Disable regulators */ + regulator_bulk_disable(OV13858_NUM_SUPPLIES, ov13858->supplies); + dev_info(ov13858->dev, "Regulators disabled\n"); + + /* Disable clock */ + clk_disable_unprepare(ov13858->xvclk); + dev_info(ov13858->dev, "Clock disabled\n"); +} + +static int __maybe_unused ov13858_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov13858 *ov13858 = to_ov13858(sd); + + ov13858_sensor_powerdown(ov13858); + + return 0; +} + +static int __maybe_unused ov13858_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov13858 *ov13858 = to_ov13858(sd); + int ret; + + ret = ov13858_sensor_powerup(ov13858); + if (ret) + return ret; + + return 0; +} + +static const struct dev_pm_ops ov13858_pm_ops = { + SET_RUNTIME_PM_OPS(ov13858_suspend, ov13858_resume, NULL) +}; + +static int ov13858_get_gpios(struct ov13858 *ov13858) +{ + ov13858->reset = devm_gpiod_get_optional(ov13858->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ov13858->reset)) { + dev_err(ov13858->dev, "Error fetching reset GPIO\n"); + return PTR_ERR(ov13858->reset); + } + + return 0; +} + /* Read registers up to 4 at a time */ static int ov13858_read_reg(struct ov13858 *ov13858, u16 reg, u32 len, u32 *val) @@ -1498,8 +1621,11 @@ static int ov13858_identify_module(struct ov13858 *ov13858) int ret; u32 val; + dev_info(ov13858->dev, "Attempting to read chip ID from register 0x300a\n"); ret = ov13858_read_reg(ov13858, OV13858_REG_CHIP_ID, OV13858_REG_VALUE_24BIT, &val); + dev_info(ov13858->dev, "Chip ID read result: ret=%d, val=0x%06x (expected 0x%06x)\n", + ret, val, OV13858_CHIP_ID); if (ret) return ret; @@ -1509,6 +1635,7 @@ static int ov13858_identify_module(struct ov13858 *ov13858) return -EIO; } + dev_info(ov13858->dev, "Chip ID verified successfully!\n"); return 0; } @@ -1678,14 +1805,33 @@ static int ov13858_probe(struct i2c_client *client) "external clock %lu is not supported\n", freq); + /* Get the external clock */ + ov13858->xvclk = devm_clk_get_optional(ov13858->dev, "xvclk"); + if (IS_ERR(ov13858->xvclk)) + return dev_err_probe(ov13858->dev, PTR_ERR(ov13858->xvclk), + "failed to get xvclk\n"); + /* Initialize subdev */ v4l2_i2c_subdev_init(&ov13858->sd, client, &ov13858_subdev_ops); + ret = ov13858_get_regulators(ov13858); + if (ret) + return dev_err_probe(ov13858->dev, ret, + "Error fetching regulators\n"); + + ret = ov13858_get_gpios(ov13858); + if (ret) + return ret; + + ret = ov13858_sensor_powerup(ov13858); + if (ret) + return ret; // No cleanup needed yet, devm handles GPIOs/regulators + /* Check module identity */ ret = ov13858_identify_module(ov13858); if (ret) { dev_err(ov13858->dev, "failed to find sensor: %d\n", ret); - return ret; + goto error_power_off; // Now we need to power down } /* Set default mode to max resolution */ @@ -1693,7 +1839,7 @@ static int ov13858_probe(struct i2c_client *client) ret = ov13858_init_controls(ov13858); if (ret) - return ret; + goto error_power_off; /* Initialize subdev */ ov13858->sd.internal_ops = &ov13858_internal_ops; @@ -1729,6 +1875,10 @@ static int ov13858_probe(struct i2c_client *client) error_handler_free: ov13858_free_controls(ov13858); + +error_power_off: + ov13858_sensor_powerdown(ov13858); + dev_err(ov13858->dev, "%s failed:%d\n", __func__, ret); return ret; @@ -1744,6 +1894,9 @@ static void ov13858_remove(struct i2c_client *client) ov13858_free_controls(ov13858); pm_runtime_disable(ov13858->dev); + if (!pm_runtime_status_suspended(ov13858->dev)) + ov13858_sensor_powerdown(ov13858); + pm_runtime_set_suspended(ov13858->dev); } static const struct i2c_device_id ov13858_id_table[] = { @@ -1765,6 +1918,7 @@ MODULE_DEVICE_TABLE(acpi, ov13858_acpi_ids); static struct i2c_driver ov13858_i2c_driver = { .driver = { .name = "ov13858", + .pm = &ov13858_pm_ops, .acpi_match_table = ACPI_PTR(ov13858_acpi_ids), }, .probe = ov13858_probe, diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c index 4cc796bbee9263..02236f3db19dc7 100644 --- a/drivers/media/i2c/ov5693.c +++ b/drivers/media/i2c/ov5693.c @@ -1396,6 +1396,7 @@ static const struct dev_pm_ops ov5693_pm_ops = { static const struct acpi_device_id ov5693_acpi_match[] = { {"INT33BE"}, + {"OVTI5693"}, {}, }; MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c index 27afc3fc01752e..28b192c9a5ec5b 100644 --- a/drivers/media/i2c/ov7251.c +++ b/drivers/media/i2c/ov7251.c @@ -1053,7 +1053,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_EXPOSURE: ret = ov7251_set_exposure(ov7251, ctrl->val); break; - case V4L2_CID_GAIN: + case V4L2_CID_ANALOGUE_GAIN: ret = ov7251_set_gain(ov7251, ctrl->val); break; case V4L2_CID_TEST_PATTERN: @@ -1579,7 +1579,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_EXPOSURE, 1, 32, 1, 32); ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, - V4L2_CID_GAIN, 16, 1023, 1, 16); + V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_TEST_PATTERN, ARRAY_SIZE(ov7251_test_pattern_menu) - 1, diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index fc6608e33de40a..209767f44bd21f 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -61,6 +61,10 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("INT33BE", 1, 419200000), /* Onsemi MT9M114 */ IPU_SENSOR_CONFIG("INT33F0", 1, 384000000), + /* Omnivision OV5693 - Surface Pro 9 */ + IPU_SENSOR_CONFIG("OVTI5693", 1, 419200000), + /* Omnivision OV13858 - Surface Pro 9 */ + IPU_SENSOR_CONFIG("OVTID858", 4, 540000000), /* Omnivision OV2740 */ IPU_SENSOR_CONFIG("INT3474", 1, 180000000), /* Omnivision OV5670 */ @@ -134,6 +138,20 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { }, .driver_data = "OVTI02C1", }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 8"), + }, + .driver_data = "INT33BE", + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), + }, + .driver_data = "OVTI5693", + }, {} /* Terminating entry */ }; diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 3f0b7bb68cc940..0b33e927bd59cb 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -63,6 +63,7 @@ config VIDEO_MUX # Platform drivers - Please keep it alphabetically sorted source "drivers/media/platform/allegro-dvt/Kconfig" +source "drivers/media/platform/amd/Kconfig" source "drivers/media/platform/amlogic/Kconfig" source "drivers/media/platform/amphion/Kconfig" source "drivers/media/platform/arm/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 6d5f79ddfcc37a..16c185752474e5 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -6,6 +6,7 @@ # Place here, alphabetically sorted by directory # (e. g. LC_ALL=C sort Makefile) obj-y += allegro-dvt/ +obj-y += amd/ obj-y += amlogic/ obj-y += amphion/ obj-y += arm/ diff --git a/drivers/media/platform/amd/Kconfig b/drivers/media/platform/amd/Kconfig new file mode 100644 index 00000000000000..25af49f246b260 --- /dev/null +++ b/drivers/media/platform/amd/Kconfig @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0+ + +source "drivers/media/platform/amd/isp4/Kconfig" diff --git a/drivers/media/platform/amd/Makefile b/drivers/media/platform/amd/Makefile new file mode 100644 index 00000000000000..8bfc1955f22e69 --- /dev/null +++ b/drivers/media/platform/amd/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-y += isp4/ diff --git a/drivers/media/platform/amd/isp4/Kconfig b/drivers/media/platform/amd/isp4/Kconfig new file mode 100644 index 00000000000000..55dd2dc453a208 --- /dev/null +++ b/drivers/media/platform/amd/isp4/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0+ + +config VIDEO_AMD_ISP4_CAPTURE + tristate "AMD ISP4 and camera driver" + depends on DRM_AMD_ISP && VIDEO_DEV && HAS_DMA + select VIDEOBUF2_CORE + select VIDEOBUF2_MEMOPS + select VIDEOBUF2_V4L2 + select VIDEOBUF2_VMALLOC + select VIDEO_V4L2_SUBDEV_API + help + This is support for AMD ISP4 and camera subsystem driver. + Say Y here to enable the ISP4 and camera device for video capture. + To compile this driver as a module, choose M here. The module will + be called amd_isp4_capture. diff --git a/drivers/media/platform/amd/isp4/Makefile b/drivers/media/platform/amd/isp4/Makefile new file mode 100644 index 00000000000000..3849062e17f3d0 --- /dev/null +++ b/drivers/media/platform/amd/isp4/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2025 Advanced Micro Devices, Inc. + +obj-$(CONFIG_VIDEO_AMD_ISP4_CAPTURE) += amd_isp4_capture.o +amd_isp4_capture-objs := isp4.o \ + isp4_debug.o \ + isp4_interface.o \ + isp4_subdev.o \ + isp4_video.o diff --git a/drivers/media/platform/amd/isp4/isp4.c b/drivers/media/platform/amd/isp4/isp4.c new file mode 100644 index 00000000000000..bf6b8e26c2c0fb --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include + +#include "isp4.h" +#include "isp4_debug.h" +#include "isp4_hw_reg.h" + +#define ISP4_DRV_NAME "amd_isp_capture" +#define ISP4_FW_RESP_RB_IRQ_STATUS_MASK \ + (ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK | \ + ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK) + +static const struct { + const char *name; + u32 status_mask; + u32 en_mask; + u32 ack_mask; + u32 rb_int_num; +} isp4_irq[ISP4SD_MAX_FW_RESP_STREAM_NUM] = { + /* The IRQ order is aligned with the isp4_subdev.fw_resp_thread order */ + { + .name = "isp_irq_global", + .status_mask = + ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK, + .en_mask = ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK, + .ack_mask = ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT12_ACK_MASK, + .rb_int_num = 4, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT12 */ + }, + { + .name = "isp_irq_stream1", + .status_mask = + ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK, + .en_mask = ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK, + .ack_mask = ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT9_ACK_MASK, + .rb_int_num = 0, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT9 */ + }, +}; + +void isp4_intr_enable(struct isp4_subdev *isp_subdev, u32 index, bool enable) +{ + u32 intr_en; + + /* Synchronize ISP_SYS_INT0_EN writes with the IRQ handler's writes */ + spin_lock_irq(&isp_subdev->irq_lock); + intr_en = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN); + if (enable) + intr_en |= isp4_irq[index].en_mask; + else + intr_en &= ~isp4_irq[index].en_mask; + + isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en); + spin_unlock_irq(&isp_subdev->irq_lock); +} + +static void isp4_wake_up_resp_thread(struct isp4_subdev *isp_subdev, u32 index) +{ + struct isp4sd_thread_handler *thread_ctx = + &isp_subdev->fw_resp_thread[index]; + + thread_ctx->resp_ready = true; + wake_up_interruptible(&thread_ctx->waitq); +} + +static irqreturn_t isp4_irq_handler(int irq, void *arg) +{ + struct isp4_subdev *isp_subdev = arg; + u32 intr_ack = 0, intr_en = 0, intr_status; + int seen = 0; + + /* Get the ISP_SYS interrupt status */ + intr_status = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_STATUS); + intr_status &= ISP4_FW_RESP_RB_IRQ_STATUS_MASK; + + /* Find which ISP_SYS interrupts fired */ + for (size_t i = 0; i < ARRAY_SIZE(isp4_irq); i++) { + if (intr_status & isp4_irq[i].status_mask) { + intr_ack |= isp4_irq[i].ack_mask; + intr_en |= isp4_irq[i].en_mask; + seen |= BIT(i); + } + } + + /* + * Disable the ISP_SYS interrupts that fired. Must be done before waking + * the response threads, since they re-enable interrupts when finished. + * The lock synchronizes RMW of INT0_EN with isp4_enable_interrupt(). + */ + spin_lock(&isp_subdev->irq_lock); + intr_en = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN) & ~intr_en; + isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en); + spin_unlock(&isp_subdev->irq_lock); + + /* + * Clear the ISP_SYS interrupts. This must be done after the interrupts + * are disabled, so that ISP FW won't flag any new interrupts on these + * streams, and thus we don't need to clear interrupts again before + * re-enabling them in the response thread. + */ + isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_ACK, intr_ack); + + /* + * The operation `(seen >> i) << i` is logically equivalent to + * `seen &= ~BIT(i)`, with fewer instructions after compilation. + */ + for (int i; (i = ffs(seen)); seen = (seen >> i) << i) + isp4_wake_up_resp_thread(isp_subdev, i - 1); + + return IRQ_HANDLED; +} + +static int isp4_capture_probe(struct platform_device *pdev) +{ + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]; + struct device *dev = &pdev->dev; + struct isp4_subdev *isp_subdev; + struct isp4_device *isp_dev; + int ret; + + isp_dev = devm_kzalloc(dev, sizeof(*isp_dev), GFP_KERNEL); + if (!isp_dev) + return -ENOMEM; + + dev->init_name = ISP4_DRV_NAME; + + isp_subdev = &isp_dev->isp_subdev; + isp_subdev->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(isp_subdev->mmio)) + return dev_err_probe(dev, PTR_ERR(isp_subdev->mmio), + "isp ioremap fail\n"); + + for (size_t i = 0; i < ARRAY_SIZE(isp4_irq); i++) { + irq[i] = platform_get_irq(pdev, isp4_irq[i].rb_int_num); + if (irq[i] < 0) + return dev_err_probe(dev, irq[i], + "fail to get irq %d\n", + isp4_irq[i].rb_int_num); + + ret = devm_request_irq(dev, irq[i], isp4_irq_handler, + IRQF_NO_AUTOEN, isp4_irq[i].name, + isp_subdev); + if (ret) + return dev_err_probe(dev, ret, "fail to req irq %d\n", + irq[i]); + } + + isp_dev->v4l2_dev.mdev = &isp_dev->mdev; + + strscpy(isp_dev->mdev.model, "amd_isp41_mdev", + sizeof(isp_dev->mdev.model)); + isp_dev->mdev.dev = dev; + media_device_init(&isp_dev->mdev); + + snprintf(isp_dev->v4l2_dev.name, sizeof(isp_dev->v4l2_dev.name), + "AMD-V4L2-ROOT"); + ret = v4l2_device_register(dev, &isp_dev->v4l2_dev); + if (ret) { + dev_err_probe(dev, ret, "fail register v4l2 device\n"); + goto err_clean_media; + } + + pm_runtime_set_suspended(dev); + pm_runtime_enable(dev); + spin_lock_init(&isp_subdev->irq_lock); + ret = isp4sd_init(&isp_dev->isp_subdev, &isp_dev->v4l2_dev, irq); + if (ret) { + dev_err_probe(dev, ret, "fail init isp4 sub dev\n"); + goto err_pm_disable; + } + + ret = media_create_pad_link(&isp_dev->isp_subdev.sdev.entity, + 0, + &isp_dev->isp_subdev.isp_vdev.vdev.entity, + 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err_probe(dev, ret, "fail to create pad link\n"); + goto err_isp4_deinit; + } + + ret = media_device_register(&isp_dev->mdev); + if (ret) { + dev_err_probe(dev, ret, "fail to register media device\n"); + goto err_isp4_deinit; + } + + platform_set_drvdata(pdev, isp_dev); + isp_debugfs_create(isp_dev); + + return 0; + +err_isp4_deinit: + isp4sd_deinit(&isp_dev->isp_subdev); +err_pm_disable: + pm_runtime_disable(dev); + v4l2_device_unregister(&isp_dev->v4l2_dev); +err_clean_media: + media_device_cleanup(&isp_dev->mdev); + + return ret; +} + +static void isp4_capture_remove(struct platform_device *pdev) +{ + struct isp4_device *isp_dev = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + isp_debugfs_remove(isp_dev); + + media_device_unregister(&isp_dev->mdev); + isp4sd_deinit(&isp_dev->isp_subdev); + pm_runtime_disable(dev); + v4l2_device_unregister(&isp_dev->v4l2_dev); + media_device_cleanup(&isp_dev->mdev); +} + +static struct platform_driver isp4_capture_drv = { + .probe = isp4_capture_probe, + .remove = isp4_capture_remove, + .driver = { + .name = ISP4_DRV_NAME, + } +}; + +module_platform_driver(isp4_capture_drv); + +MODULE_ALIAS("platform:" ISP4_DRV_NAME); +MODULE_IMPORT_NS("DMA_BUF"); + +MODULE_DESCRIPTION("AMD ISP4 Driver"); +MODULE_AUTHOR("Bin Du "); +MODULE_AUTHOR("Pratap Nirujogi "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/amd/isp4/isp4.h b/drivers/media/platform/amd/isp4/isp4.h new file mode 100644 index 00000000000000..2db6683d6d8b5d --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_H_ +#define _ISP4_H_ + +#include +#include "isp4_subdev.h" + +struct isp4_device { + struct v4l2_device v4l2_dev; + struct isp4_subdev isp_subdev; + struct media_device mdev; +}; + +void isp4_intr_enable(struct isp4_subdev *isp_subdev, u32 index, bool enable); + +#endif /* _ISP4_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_debug.c b/drivers/media/platform/amd/isp4/isp4_debug.c new file mode 100644 index 00000000000000..2fc00fc9a19403 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_debug.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include "isp4.h" +#include "isp4_debug.h" +#include "isp4_hw_reg.h" +#include "isp4_interface.h" + +#define ISP4DBG_FW_LOG_RINGBUF_SIZE (2 * 1024 * 1024) +#define ISP4DBG_MACRO_2_STR(X) #X +#define ISP4DBG_ONE_TIME_LOG_LEN 510 + +#ifdef CONFIG_DEBUG_FS + +void isp_debugfs_create(struct isp4_device *isp_dev) +{ + isp_dev->isp_subdev.debugfs_dir = debugfs_create_dir("amd_isp4", NULL); + debugfs_create_bool("fw_log_enable", 0644, + isp_dev->isp_subdev.debugfs_dir, + &isp_dev->isp_subdev.enable_fw_log); + isp_dev->isp_subdev.fw_log_output = + devm_kzalloc(isp_dev->isp_subdev.dev, + ISP4DBG_FW_LOG_RINGBUF_SIZE + 32, + GFP_KERNEL); +} + +void isp_debugfs_remove(struct isp4_device *isp_dev) +{ + debugfs_remove_recursive(isp_dev->isp_subdev.debugfs_dir); + isp_dev->isp_subdev.debugfs_dir = NULL; +} + +static u32 isp_fw_fill_rb_log(struct isp4_subdev *isp, void *sys, u32 rb_size) +{ + struct isp4_interface *ispif = &isp->ispif; + char *buf = isp->fw_log_output; + struct device *dev = isp->dev; + u32 rd_ptr, wr_ptr; + u32 total_cnt = 0; + u32 offset = 0; + u32 cnt; + + if (!sys || !rb_size) + return 0; + + guard(mutex)(&ispif->isp4if_mutex); + + rd_ptr = isp4hw_rreg(isp->mmio, ISP_LOG_RB_RPTR0); + wr_ptr = isp4hw_rreg(isp->mmio, ISP_LOG_RB_WPTR0); + + do { + if (wr_ptr > rd_ptr) + cnt = wr_ptr - rd_ptr; + else if (wr_ptr < rd_ptr) + cnt = rb_size - rd_ptr; + else + goto quit; + + if (cnt > rb_size) { + dev_err(dev, "fail bad fw log size %u\n", cnt); + goto quit; + } + + memcpy(buf + offset, sys + rd_ptr, cnt); + + offset += cnt; + total_cnt += cnt; + rd_ptr = (rd_ptr + cnt) % rb_size; + } while (rd_ptr < wr_ptr); + + isp4hw_wreg(isp->mmio, ISP_LOG_RB_RPTR0, rd_ptr); + +quit: + return total_cnt; +} + +void isp_fw_log_print(struct isp4_subdev *isp) +{ + struct isp4_interface *ispif = &isp->ispif; + char *fw_log_buf = isp->fw_log_output; + u32 cnt; + + if (!isp->enable_fw_log || !fw_log_buf) + return; + + cnt = isp_fw_fill_rb_log(isp, ispif->fw_log_buf->sys_addr, + ispif->fw_log_buf->mem_size); + + if (cnt) { + char temp_ch; + char *str; + char *end; + /* line end */ + char *le; + + str = (char *)fw_log_buf; + end = ((char *)fw_log_buf + cnt); + fw_log_buf[cnt] = 0; + + while (str < end) { + le = strchr(str, 0x0A); + if ((le && str + ISP4DBG_ONE_TIME_LOG_LEN >= le) || + (!le && str + ISP4DBG_ONE_TIME_LOG_LEN >= end)) { + if (le) + *le = 0; + + if (*str != '\0') + dev_dbg(isp->dev, "%s", str); + + if (le) { + *le = 0x0A; + str = le + 1; + } else { + break; + } + } else { + u32 tmp_len = ISP4DBG_ONE_TIME_LOG_LEN; + + temp_ch = str[tmp_len]; + str[tmp_len] = 0; + dev_dbg(isp->dev, "%s", str); + str[tmp_len] = temp_ch; + str = &str[tmp_len]; + } + } + } +} +#endif + +char *isp4dbg_get_buf_src_str(u32 src) +{ + switch (src) { + case ISP4FW_BUFFER_SOURCE_STREAM: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_SOURCE_STREAM); + default: + return "Unknown buf source"; + } +} + +char *isp4dbg_get_buf_done_str(u32 status) +{ + switch (status) { + case ISP4FW_BUFFER_STATUS_INVALID: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_INVALID); + case ISP4FW_BUFFER_STATUS_SKIPPED: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_SKIPPED); + case ISP4FW_BUFFER_STATUS_EXIST: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_EXIST); + case ISP4FW_BUFFER_STATUS_DONE: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_DONE); + case ISP4FW_BUFFER_STATUS_LACK: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_LACK); + case ISP4FW_BUFFER_STATUS_DIRTY: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_DIRTY); + case ISP4FW_BUFFER_STATUS_MAX: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_MAX); + default: + return "Unknown Buf Done Status"; + } +} + +char *isp4dbg_get_img_fmt_str(int fmt /* enum isp4fw_image_format * */) +{ + switch (fmt) { + case ISP4FW_IMAGE_FORMAT_NV12: + return "NV12"; + case ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED: + return "YUV422INTERLEAVED"; + default: + return "unknown fmt"; + } +} + +void isp4dbg_show_bufmeta_info(struct device *dev, char *pre, + void *in, void *orig_buf) +{ + struct isp4fw_buffer_meta_info *p; + struct isp4if_img_buf_info *orig; + + if (!in) + return; + + if (!pre) + pre = ""; + + p = in; + orig = orig_buf; + + dev_dbg(dev, "%s(%s) en:%d,stat:%s(%u),src:%s\n", pre, + isp4dbg_get_img_fmt_str(p->image_prop.image_format), + p->enabled, isp4dbg_get_buf_done_str(p->status), p->status, + isp4dbg_get_buf_src_str(p->source)); + + dev_dbg(dev, "%p,0x%llx(%u) %p,0x%llx(%u) %p,0x%llx(%u)\n", + orig->planes[0].sys_addr, orig->planes[0].mc_addr, + orig->planes[0].len, orig->planes[1].sys_addr, + orig->planes[1].mc_addr, orig->planes[1].len, + orig->planes[2].sys_addr, orig->planes[2].mc_addr, + orig->planes[2].len); +} + +char *isp4dbg_get_buf_type(u32 type) +{ + /* enum isp4fw_buffer_type */ + switch (type) { + case ISP4FW_BUFFER_TYPE_PREVIEW: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_PREVIEW); + case ISP4FW_BUFFER_TYPE_META_INFO: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_META_INFO); + case ISP4FW_BUFFER_TYPE_MEM_POOL: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_MEM_POOL); + default: + return "unknown type"; + } +} + +char *isp4dbg_get_cmd_str(u32 cmd) +{ + switch (cmd) { + case ISP4FW_CMD_ID_START_STREAM: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_START_STREAM); + case ISP4FW_CMD_ID_STOP_STREAM: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_STOP_STREAM); + case ISP4FW_CMD_ID_SEND_BUFFER: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SEND_BUFFER); + case ISP4FW_CMD_ID_SET_STREAM_CONFIG: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SET_STREAM_CONFIG); + case ISP4FW_CMD_ID_SET_OUT_CHAN_PROP: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SET_OUT_CHAN_PROP); + case ISP4FW_CMD_ID_ENABLE_OUT_CHAN: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_ENABLE_OUT_CHAN); + default: + return "unknown cmd"; + } +} + +char *isp4dbg_get_resp_str(u32 cmd) +{ + switch (cmd) { + case ISP4FW_RESP_ID_CMD_DONE: + return ISP4DBG_MACRO_2_STR(ISP4FW_RESP_ID_CMD_DONE); + case ISP4FW_RESP_ID_NOTI_FRAME_DONE: + return ISP4DBG_MACRO_2_STR(ISP4FW_RESP_ID_NOTI_FRAME_DONE); + default: + return "unknown respid"; + } +} + +char *isp4dbg_get_if_stream_str(u32 stream /* enum fw_cmd_resp_stream_id */) +{ + switch (stream) { + case ISP4IF_STREAM_ID_GLOBAL: + return "STREAM_GLOBAL"; + case ISP4IF_STREAM_ID_1: + return "STREAM1"; + default: + return "unknown streamID"; + } +} + +char *isp4dbg_get_out_ch_str(int ch /* enum isp4fw_pipe_out_ch */) +{ + switch ((enum isp4fw_pipe_out_ch)ch) { + case ISP4FW_ISP_PIPE_OUT_CH_PREVIEW: + return "prev"; + default: + return "unknown channel"; + } +} diff --git a/drivers/media/platform/amd/isp4/isp4_debug.h b/drivers/media/platform/amd/isp4/isp4_debug.h new file mode 100644 index 00000000000000..d1262e03ae6405 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_debug.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_DEBUG_H_ +#define _ISP4_DEBUG_H_ + +#include +#include + +#include "isp4_subdev.h" + +#ifdef CONFIG_DEBUG_FS +struct isp4_device; + +void isp_debugfs_create(struct isp4_device *isp_dev); +void isp_debugfs_remove(struct isp4_device *isp_dev); +void isp_fw_log_print(struct isp4_subdev *isp); + +#else + +/* to avoid checkpatch warning */ +#define isp_debugfs_create(cam) ((void)(cam)) +#define isp_debugfs_remove(cam) ((void)(cam)) +#define isp_fw_log_print(isp) ((void)(isp)) + +#endif /* CONFIG_DEBUG_FS */ + +void isp4dbg_show_bufmeta_info(struct device *dev, char *pre, void *p, + void *orig_buf /* struct sys_img_buf_handle */); +char *isp4dbg_get_img_fmt_str(int fmt /* enum _image_format_t */); +char *isp4dbg_get_out_ch_str(int ch /* enum _isp_pipe_out_ch_t */); +char *isp4dbg_get_cmd_str(u32 cmd); +char *isp4dbg_get_buf_type(u32 type);/* enum _buffer_type_t */ +char *isp4dbg_get_resp_str(u32 resp); +char *isp4dbg_get_buf_src_str(u32 src); +char *isp4dbg_get_buf_done_str(u32 status); +char *isp4dbg_get_if_stream_str(u32 stream); + +#endif /* _ISP4_DEBUG_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h b/drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h new file mode 100644 index 00000000000000..88bacb00355c49 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h @@ -0,0 +1,318 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_FW_CMD_RESP_H_ +#define _ISP4_FW_CMD_RESP_H_ + +/* + * Two types of command/response channel. + * Type Global Command has one command/response channel. + * Type Stream Command has one command/response channel. + *----------- ------------ + *| | --------------------------- | | + *| | ---->| Global Command |----> | | + *| | --------------------------- | | + *| | | | + *| | | | + *| | --------------------------- | | + *| | ---->| Stream Command |----> | | + *| | --------------------------- | | + *| | | | + *| | | | + *| | | | + *| HOST | | Firmware | + *| | | | + *| | | | + *| | -------------------------- | | + *| | <----| Global Response |<---- | | + *| | -------------------------- | | + *| | | | + *| | | | + *| | -------------------------- | | + *| | <----| Stream Response |<---- | | + *| | -------------------------- | | + *| | | | + *| | | | + *----------- ------------ + */ + +/* + * cmd_id is in the format of following type: + * type: indicate command type, global/stream commands. + * group: indicate the command group. + * id: A unique command identification in one type and group. + * |<-Bit31 ~ Bit24->|<-Bit23 ~ Bit16->|<-Bit15 ~ Bit0->| + * | type | group | id | + */ + +#define ISP4FW_CMD_TYPE_SHIFT 24 +#define ISP4FW_CMD_GROUP_SHIFT 16 +#define ISP4FW_CMD_TYPE_STREAM_CTRL (0x2U << ISP4FW_CMD_TYPE_SHIFT) + +#define ISP4FW_CMD_GROUP_STREAM_CTRL (0x1U << ISP4FW_CMD_GROUP_SHIFT) +#define ISP4FW_CMD_GROUP_STREAM_BUFFER (0x4U << ISP4FW_CMD_GROUP_SHIFT) + +/* Stream Command */ +#define ISP4FW_CMD_ID_SET_STREAM_CONFIG (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x1) +#define ISP4FW_CMD_ID_SET_OUT_CHAN_PROP (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x3) +#define ISP4FW_CMD_ID_ENABLE_OUT_CHAN (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x5) +#define ISP4FW_CMD_ID_START_STREAM (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x7) +#define ISP4FW_CMD_ID_STOP_STREAM (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x8) + +/* Stream Buffer Command */ +#define ISP4FW_CMD_ID_SEND_BUFFER (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_BUFFER | 0x1) + +/* + * resp_id is in the format of following type: + * type: indicate command type, global/stream commands. + * group: indicate the command group. + * id: A unique command identification in one type and group. + * |<-Bit31 ~ Bit24->|<-Bit23 ~ Bit16->|<-Bit15 ~ Bit0->| + * | type | group | id | + */ + +#define ISP4FW_RESP_GROUP_SHIFT 16 + +#define ISP4FW_RESP_GROUP_GENERAL (0x1 << ISP4FW_RESP_GROUP_SHIFT) +#define ISP4FW_RESP_GROUP_NOTIFICATION (0x3 << ISP4FW_RESP_GROUP_SHIFT) + +/* General Response */ +#define ISP4FW_RESP_ID_CMD_DONE (ISP4FW_RESP_GROUP_GENERAL | 0x1) + +/* Notification */ +#define ISP4FW_RESP_ID_NOTI_FRAME_DONE (ISP4FW_RESP_GROUP_NOTIFICATION | 0x1) + +#define ISP4FW_CMD_STATUS_SUCCESS 0 +#define ISP4FW_CMD_STATUS_FAIL 1 +#define ISP4FW_CMD_STATUS_SKIPPED 2 + +#define ISP4FW_ADDR_SPACE_TYPE_GPU_VA 4 + +#define ISP4FW_MEMORY_POOL_SIZE (100 * 1024 * 1024) + +/* + * standard ISP pipeline: mipicsi=>isp + */ +#define ISP4FW_MIPI0_ISP_PIPELINE_ID 0x5f91 + +enum isp4fw_sensor_id { + /* Sensor id for ISP input from MIPI port 0 */ + ISP4FW_SENSOR_ID_ON_MIPI0 = 0, +}; + +enum isp4fw_stream_id { + ISP4FW_STREAM_ID_INVALID = -1, + ISP4FW_STREAM_ID_1 = 0, + ISP4FW_STREAM_ID_2 = 1, + ISP4FW_STREAM_ID_3 = 2, + ISP4FW_STREAM_ID_MAXIMUM +}; + +enum isp4fw_image_format { + /* 4:2:0,semi-planar, 8-bit */ + ISP4FW_IMAGE_FORMAT_NV12 = 1, + /* interleave, 4:2:2, 8-bit */ + ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED = 7, +}; + +enum isp4fw_pipe_out_ch { + ISP4FW_ISP_PIPE_OUT_CH_PREVIEW = 0, +}; + +enum isp4fw_yuv_range { + ISP4FW_ISP_YUV_RANGE_FULL = 0, /* YUV value range in 0~255 */ + ISP4FW_ISP_YUV_RANGE_NARROW = 1, /* YUV value range in 16~235 */ + ISP4FW_ISP_YUV_RANGE_MAX +}; + +enum isp4fw_buffer_type { + ISP4FW_BUFFER_TYPE_PREVIEW = 8, + ISP4FW_BUFFER_TYPE_META_INFO = 10, + ISP4FW_BUFFER_TYPE_MEM_POOL = 15, +}; + +enum isp4fw_buffer_status { + /* The buffer is INVALID */ + ISP4FW_BUFFER_STATUS_INVALID, + /* The buffer is not filled with image data */ + ISP4FW_BUFFER_STATUS_SKIPPED, + /* The buffer is available and awaiting to be filled */ + ISP4FW_BUFFER_STATUS_EXIST, + /* The buffer is filled with image data */ + ISP4FW_BUFFER_STATUS_DONE, + /* The buffer is unavailable */ + ISP4FW_BUFFER_STATUS_LACK, + /* The buffer is dirty, probably caused by LMI leakage */ + ISP4FW_BUFFER_STATUS_DIRTY, + ISP4FW_BUFFER_STATUS_MAX +}; + +enum isp4fw_buffer_source { + /* The buffer is from the stream buffer queue */ + ISP4FW_BUFFER_SOURCE_STREAM, +}; + +struct isp4fw_error_code { + u32 code1; + u32 code2; + u32 code3; + u32 code4; + u32 code5; +}; + +/* Command Structure for FW */ + +struct isp4fw_cmd { + u32 cmd_seq_num; + u32 cmd_id; + u32 cmd_param[12]; + u16 cmd_stream_id; + u8 cmd_silent_resp; + u8 reserved; + u32 cmd_check_sum; +}; + +struct isp4fw_resp_cmd_done { + /* + * The host2fw command seqNum. + * To indicate which command this response refers to. + */ + u32 cmd_seq_num; + /* The host2fw command id for host double check. */ + u32 cmd_id; + /* + * Indicate the command process status. + * 0 means success. 1 means fail. 2 means skipped + */ + u16 cmd_status; + /* + * If cmd_status is 1, the command failed. The host can check + * isp4fw_error_code for details. + */ + u16 isp4fw_error_code; + /* The response payload type varies by cmd. */ + u8 payload[36]; +}; + +struct isp4fw_resp_param_package { + u32 package_addr_lo; /* The low 32 bit of the pkg address. */ + u32 package_addr_hi; /* The high 32 bit of the pkg address. */ + u32 package_size; /* The total pkg size in bytes. */ + u32 package_check_sum; /* The byte sum of the pkg. */ +}; + +struct isp4fw_resp { + u32 resp_seq_num; + u32 resp_id; + union { + struct isp4fw_resp_cmd_done cmd_done; + struct isp4fw_resp_param_package frame_done; + u32 resp_param[12]; + } param; + u8 reserved[4]; + u32 resp_check_sum; +}; + +struct isp4fw_mipi_pipe_path_cfg { + u32 b_enable; + enum isp4fw_sensor_id isp4fw_sensor_id; +}; + +struct isp4fw_isp_pipe_path_cfg { + u32 isp_pipe_id; /* pipe ids for pipeline construction */ +}; + +struct isp4fw_isp_stream_cfg { + /* Isp mipi path */ + struct isp4fw_mipi_pipe_path_cfg mipi_pipe_path_cfg; + /* Isp pipe path */ + struct isp4fw_isp_pipe_path_cfg isp_pipe_path_cfg; + /* enable TNR */ + u32 b_enable_tnr; + /* + * Number of frames for RTA processing. + * Set to 0 to use the firmware's default value. + */ + u32 rta_frames_per_proc; +}; + +struct isp4fw_image_prop { + enum isp4fw_image_format image_format; + u32 width; + u32 height; + u32 luma_pitch; + u32 chroma_pitch; + enum isp4fw_yuv_range yuv_range; +}; + +struct isp4fw_buffer { + /* + * A check num for debug usage, host can set the buf_tags + * to different numbers + */ + u32 buf_tags; + union { + u32 value; + struct { + u32 space : 16; + u32 vmid : 16; + } bit; + } vmid_space; + u32 buf_base_a_lo; /* Low address of buffer A */ + u32 buf_base_a_hi; /* High address of buffer A */ + u32 buf_size_a; /* Buffer size of buffer A */ + + u32 buf_base_b_lo; /* Low address of buffer B */ + u32 buf_base_b_hi; /* High address of buffer B */ + u32 buf_size_b; /* Buffer size of buffer B */ + + u32 buf_base_c_lo; /* Low address of buffer C */ + u32 buf_base_c_hi; /* High address of buffer C */ + u32 buf_size_c; /* Buffer size of buffer C */ +}; + +struct isp4fw_buffer_meta_info { + u32 enabled; /* enabled flag */ + enum isp4fw_buffer_status status; /* BufferStatus */ + struct isp4fw_error_code err; /* err code */ + enum isp4fw_buffer_source source; /* BufferSource */ + struct isp4fw_image_prop image_prop; /* image_prop */ + struct isp4fw_buffer buffer; /* buffer info */ +}; + +struct isp4fw_meta_info { + u32 poc; /* frame id */ + u32 fc_id; /* frame ctl id */ + u32 time_stamp_lo; /* timestamp low 32 bits */ + u32 time_stamp_hi; /* timestamp_high 32 bits */ + struct isp4fw_buffer_meta_info preview; /* preview BufferMetaInfo */ +}; + +struct isp4fw_cmd_send_buffer { + enum isp4fw_buffer_type buffer_type; + struct isp4fw_buffer buffer; /* buffer info */ +}; + +struct isp4fw_cmd_set_out_ch_prop { + enum isp4fw_pipe_out_ch ch; /* ISP output channel */ + struct isp4fw_image_prop image_prop; /* image property */ +}; + +struct isp4fw_cmd_enable_out_ch { + enum isp4fw_pipe_out_ch ch; /* ISP output channel */ + u32 is_enable; /* If channel is enabled or not */ +}; + +struct isp4fw_cmd_set_stream_cfg { + struct isp4fw_isp_stream_cfg stream_cfg; /* stream path config */ +}; + +#endif /* _ISP4_FW_CMD_RESP_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_hw_reg.h b/drivers/media/platform/amd/isp4/isp4_hw_reg.h new file mode 100644 index 00000000000000..09c76f75c5ee92 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_hw_reg.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_HW_REG_H_ +#define _ISP4_HW_REG_H_ + +#include + +#define ISP_SOFT_RESET 0x62000 +#define ISP_SYS_INT0_EN 0x62010 +#define ISP_SYS_INT0_STATUS 0x62014 +#define ISP_SYS_INT0_ACK 0x62018 +#define ISP_CCPU_CNTL 0x62054 +#define ISP_STATUS 0x62058 +#define ISP_LOG_RB_BASE_LO0 0x62148 +#define ISP_LOG_RB_BASE_HI0 0x6214c +#define ISP_LOG_RB_SIZE0 0x62150 +#define ISP_LOG_RB_RPTR0 0x62154 +#define ISP_LOG_RB_WPTR0 0x62158 +#define ISP_RB_BASE_LO1 0x62170 +#define ISP_RB_BASE_HI1 0x62174 +#define ISP_RB_SIZE1 0x62178 +#define ISP_RB_RPTR1 0x6217c +#define ISP_RB_WPTR1 0x62180 +#define ISP_RB_BASE_LO2 0x62184 +#define ISP_RB_BASE_HI2 0x62188 +#define ISP_RB_SIZE2 0x6218c +#define ISP_RB_RPTR2 0x62190 +#define ISP_RB_WPTR2 0x62194 +#define ISP_RB_BASE_LO3 0x62198 +#define ISP_RB_BASE_HI3 0x6219c +#define ISP_RB_SIZE3 0x621a0 +#define ISP_RB_RPTR3 0x621a4 +#define ISP_RB_WPTR3 0x621a8 +#define ISP_RB_BASE_LO4 0x621ac +#define ISP_RB_BASE_HI4 0x621b0 +#define ISP_RB_SIZE4 0x621b4 +#define ISP_RB_RPTR4 0x621b8 +#define ISP_RB_WPTR4 0x621bc +#define ISP_RB_BASE_LO5 0x621c0 +#define ISP_RB_BASE_HI5 0x621c4 +#define ISP_RB_SIZE5 0x621c8 +#define ISP_RB_RPTR5 0x621cc +#define ISP_RB_WPTR5 0x621d0 +#define ISP_RB_BASE_LO6 0x621d4 +#define ISP_RB_BASE_HI6 0x621d8 +#define ISP_RB_SIZE6 0x621dc +#define ISP_RB_RPTR6 0x621e0 +#define ISP_RB_WPTR6 0x621e4 +#define ISP_RB_BASE_LO7 0x621e8 +#define ISP_RB_BASE_HI7 0x621ec +#define ISP_RB_SIZE7 0x621f0 +#define ISP_RB_RPTR7 0x621f4 +#define ISP_RB_WPTR7 0x621f8 +#define ISP_RB_BASE_LO8 0x621fc +#define ISP_RB_BASE_HI8 0x62200 +#define ISP_RB_SIZE8 0x62204 +#define ISP_RB_RPTR8 0x62208 +#define ISP_RB_WPTR8 0x6220c +#define ISP_RB_BASE_LO9 0x62210 +#define ISP_RB_BASE_HI9 0x62214 +#define ISP_RB_SIZE9 0x62218 +#define ISP_RB_RPTR9 0x6221c +#define ISP_RB_WPTR9 0x62220 +#define ISP_RB_BASE_LO10 0x62224 +#define ISP_RB_BASE_HI10 0x62228 +#define ISP_RB_SIZE10 0x6222c +#define ISP_RB_RPTR10 0x62230 +#define ISP_RB_WPTR10 0x62234 +#define ISP_RB_BASE_LO11 0x62238 +#define ISP_RB_BASE_HI11 0x6223c +#define ISP_RB_SIZE11 0x62240 +#define ISP_RB_RPTR11 0x62244 +#define ISP_RB_WPTR11 0x62248 +#define ISP_RB_BASE_LO12 0x6224c +#define ISP_RB_BASE_HI12 0x62250 +#define ISP_RB_SIZE12 0x62254 +#define ISP_RB_RPTR12 0x62258 +#define ISP_RB_WPTR12 0x6225c + +#define ISP_POWER_STATUS 0x60000 + +/* ISP_SOFT_RESET */ +#define ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK 0x00000001UL + +/* ISP_CCPU_CNTL */ +#define ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK 0x00040000UL + +/* ISP_STATUS */ +#define ISP_STATUS__CCPU_REPORT_MASK 0x000000feUL + +/* ISP_SYS_INT0_STATUS */ +#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK 0x00010000UL +#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT10_INT_MASK 0x00040000UL +#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT11_INT_MASK 0x00100000UL +#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK 0x00400000UL + +/* ISP_SYS_INT0_EN */ +#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK 0x00010000UL +#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT10_EN_MASK 0x00040000UL +#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT11_EN_MASK 0x00100000UL +#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK 0x00400000UL + +/* ISP_SYS_INT0_ACK */ +#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT9_ACK_MASK 0x00010000UL +#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT10_ACK_MASK 0x00040000UL +#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT11_ACK_MASK 0x00100000UL +#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT12_ACK_MASK 0x00400000UL + +/* Helper functions for reading isp registers */ +static inline u32 isp4hw_rreg(void __iomem *base, u32 reg) +{ + return readl(base + reg); +} + +/* Helper functions for writing isp registers */ +static inline void isp4hw_wreg(void __iomem *base, u32 reg, u32 val) +{ + return writel(val, base + reg); +} + +#endif /* _ISP4_HW_REG_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_interface.c b/drivers/media/platform/amd/isp4/isp4_interface.c new file mode 100644 index 00000000000000..4324d4adbcffa6 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_interface.c @@ -0,0 +1,832 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include + +#include "isp4_debug.h" +#include "isp4_fw_cmd_resp.h" +#include "isp4_hw_reg.h" +#include "isp4_interface.h" + +#define ISP4IF_FW_RESP_RB_IRQ_EN_MASK \ + (ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK\ + | ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK) + +#define ISP4IF_FW_CMD_TIMEOUT (HZ / 2) + +struct isp4if_rb_config { + const char *name; + u32 index; + u32 reg_rptr; + u32 reg_wptr; + u32 reg_base_lo; + u32 reg_base_hi; + u32 reg_size; + u32 val_size; + u64 base_mc_addr; + void *base_sys_addr; +}; + +/* FW cmd ring buffer configuration */ +static struct isp4if_rb_config isp4if_cmd_rb_config[ISP4IF_STREAM_ID_MAX] = { + { + .name = "CMD_RB_GBL0", + .index = 3, + .reg_rptr = ISP_RB_RPTR4, + .reg_wptr = ISP_RB_WPTR4, + .reg_base_lo = ISP_RB_BASE_LO4, + .reg_base_hi = ISP_RB_BASE_HI4, + .reg_size = ISP_RB_SIZE4, + }, + { + .name = "CMD_RB_STR1", + .index = 0, + .reg_rptr = ISP_RB_RPTR1, + .reg_wptr = ISP_RB_WPTR1, + .reg_base_lo = ISP_RB_BASE_LO1, + .reg_base_hi = ISP_RB_BASE_HI1, + .reg_size = ISP_RB_SIZE1, + }, + { + .name = "CMD_RB_STR2", + .index = 1, + .reg_rptr = ISP_RB_RPTR2, + .reg_wptr = ISP_RB_WPTR2, + .reg_base_lo = ISP_RB_BASE_LO2, + .reg_base_hi = ISP_RB_BASE_HI2, + .reg_size = ISP_RB_SIZE2, + }, + { + .name = "CMD_RB_STR3", + .index = 2, + .reg_rptr = ISP_RB_RPTR3, + .reg_wptr = ISP_RB_WPTR3, + .reg_base_lo = ISP_RB_BASE_LO3, + .reg_base_hi = ISP_RB_BASE_HI3, + .reg_size = ISP_RB_SIZE3, + }, +}; + +/* FW resp ring buffer configuration */ +static struct isp4if_rb_config isp4if_resp_rb_config[ISP4IF_STREAM_ID_MAX] = { + { + .name = "RES_RB_GBL0", + .index = 3, + .reg_rptr = ISP_RB_RPTR12, + .reg_wptr = ISP_RB_WPTR12, + .reg_base_lo = ISP_RB_BASE_LO12, + .reg_base_hi = ISP_RB_BASE_HI12, + .reg_size = ISP_RB_SIZE12, + }, + { + .name = "RES_RB_STR1", + .index = 0, + .reg_rptr = ISP_RB_RPTR9, + .reg_wptr = ISP_RB_WPTR9, + .reg_base_lo = ISP_RB_BASE_LO9, + .reg_base_hi = ISP_RB_BASE_HI9, + .reg_size = ISP_RB_SIZE9, + }, + { + .name = "RES_RB_STR2", + .index = 1, + .reg_rptr = ISP_RB_RPTR10, + .reg_wptr = ISP_RB_WPTR10, + .reg_base_lo = ISP_RB_BASE_LO10, + .reg_base_hi = ISP_RB_BASE_HI10, + .reg_size = ISP_RB_SIZE10, + }, + { + .name = "RES_RB_STR3", + .index = 2, + .reg_rptr = ISP_RB_RPTR11, + .reg_wptr = ISP_RB_WPTR11, + .reg_base_lo = ISP_RB_BASE_LO11, + .reg_base_hi = ISP_RB_BASE_HI11, + .reg_size = ISP_RB_SIZE11, + }, +}; + +/* FW log ring buffer configuration */ +static struct isp4if_rb_config isp4if_log_rb_config = { + .name = "LOG_RB", + .index = 0, + .reg_rptr = ISP_LOG_RB_RPTR0, + .reg_wptr = ISP_LOG_RB_WPTR0, + .reg_base_lo = ISP_LOG_RB_BASE_LO0, + .reg_base_hi = ISP_LOG_RB_BASE_HI0, + .reg_size = ISP_LOG_RB_SIZE0, +}; + +static struct isp4if_gpu_mem_info * +isp4if_gpu_mem_alloc(struct isp4_interface *ispif, u32 mem_size) +{ + struct isp4if_gpu_mem_info *mem_info; + struct device *dev = ispif->dev; + int ret; + + mem_info = kmalloc(sizeof(*mem_info), GFP_KERNEL); + if (!mem_info) + return NULL; + + mem_info->mem_size = mem_size; + ret = isp_kernel_buffer_alloc(dev, mem_info->mem_size, + &mem_info->mem_handle, + &mem_info->gpu_mc_addr, + &mem_info->sys_addr); + if (ret) { + kfree(mem_info); + return NULL; + } + + return mem_info; +} + +static void isp4if_gpu_mem_free(struct isp4_interface *ispif, + struct isp4if_gpu_mem_info **mem_info_ptr) +{ + struct isp4if_gpu_mem_info *mem_info = *mem_info_ptr; + struct device *dev = ispif->dev; + + if (!mem_info) { + dev_err(dev, "invalid mem_info\n"); + return; + } + + *mem_info_ptr = NULL; + isp_kernel_buffer_free(&mem_info->mem_handle, &mem_info->gpu_mc_addr, + &mem_info->sys_addr); + kfree(mem_info); +} + +static void isp4if_dealloc_fw_gpumem(struct isp4_interface *ispif) +{ + isp4if_gpu_mem_free(ispif, &ispif->fw_mem_pool); + isp4if_gpu_mem_free(ispif, &ispif->fw_cmd_resp_buf); + isp4if_gpu_mem_free(ispif, &ispif->fw_log_buf); + + for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) + isp4if_gpu_mem_free(ispif, &ispif->meta_info_buf[i]); +} + +static int isp4if_alloc_fw_gpumem(struct isp4_interface *ispif) +{ + struct device *dev = ispif->dev; + + ispif->fw_mem_pool = isp4if_gpu_mem_alloc(ispif, + ISP4FW_MEMORY_POOL_SIZE); + if (!ispif->fw_mem_pool) + goto error_no_memory; + + ispif->fw_cmd_resp_buf = + isp4if_gpu_mem_alloc(ispif, ISP4IF_RB_PMBMAP_MEM_SIZE); + if (!ispif->fw_cmd_resp_buf) + goto error_no_memory; + + ispif->fw_log_buf = + isp4if_gpu_mem_alloc(ispif, ISP4IF_FW_LOG_RINGBUF_SIZE); + if (!ispif->fw_log_buf) + goto error_no_memory; + + for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) { + ispif->meta_info_buf[i] = + isp4if_gpu_mem_alloc(ispif, ISP4IF_META_INFO_BUF_SIZE); + if (!ispif->meta_info_buf[i]) + goto error_no_memory; + } + + return 0; + +error_no_memory: + dev_err(dev, "failed to allocate gpu memory\n"); + return -ENOMEM; +} + +static u32 isp4if_compute_check_sum(const void *buf, size_t buf_size) +{ + const u8 *surplus_ptr; + const u32 *buffer; + u32 checksum = 0; + size_t i; + + buffer = (const u32 *)buf; + for (i = 0; i < buf_size / sizeof(u32); i++) + checksum += buffer[i]; + + surplus_ptr = (const u8 *)&buffer[i]; + /* add surplus data crc checksum */ + for (i = 0; i < buf_size % sizeof(u32); i++) + checksum += surplus_ptr[i]; + + return checksum; +} + +void isp4if_clear_cmdq(struct isp4_interface *ispif) +{ + struct isp4if_cmd_element *buf_node, *tmp_node; + LIST_HEAD(free_list); + + scoped_guard(spinlock, &ispif->cmdq_lock) + list_splice_init(&ispif->cmdq, &free_list); + + list_for_each_entry_safe(buf_node, tmp_node, &free_list, list) + kfree(buf_node); +} + +static bool isp4if_is_cmdq_rb_full(struct isp4_interface *ispif, + enum isp4if_stream_id stream) +{ + struct isp4if_rb_config *rb_config = &isp4if_cmd_rb_config[stream]; + u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr; + u32 len = rb_config->val_size; + u32 rd_ptr, wr_ptr; + u32 bytes_free; + + rd_ptr = isp4hw_rreg(ispif->mmio, rreg); + wr_ptr = isp4hw_rreg(ispif->mmio, wreg); + + /* + * Read and write pointers are equal, indicating the ring buffer + * is empty + */ + if (wr_ptr == rd_ptr) + return false; + + if (wr_ptr > rd_ptr) + bytes_free = len - (wr_ptr - rd_ptr); + else + bytes_free = rd_ptr - wr_ptr; + + /* + * Ignore one byte from the bytes free to prevent rd_ptr from equaling + * wr_ptr when the ring buffer is full, because rd_ptr == wr_ptr is + * supposed to indicate that the ring buffer is empty. + */ + return bytes_free <= sizeof(struct isp4fw_cmd); +} + +struct isp4if_cmd_element *isp4if_rm_cmd_from_cmdq(struct isp4_interface *ispif, + u32 seq_num, u32 cmd_id) +{ + struct isp4if_cmd_element *ele; + + guard(spinlock)(&ispif->cmdq_lock); + + list_for_each_entry(ele, &ispif->cmdq, list) { + if (ele->seq_num == seq_num && ele->cmd_id == cmd_id) { + list_del(&ele->list); + return ele; + } + } + + return NULL; +} + +/* Must check that isp4if_is_cmdq_rb_full() == false before calling */ +static int isp4if_insert_isp_fw_cmd(struct isp4_interface *ispif, + enum isp4if_stream_id stream, + const struct isp4fw_cmd *cmd) +{ + struct isp4if_rb_config *rb_config = &isp4if_cmd_rb_config[stream]; + u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr; + void *mem_sys = rb_config->base_sys_addr; + const u32 cmd_sz = sizeof(*cmd); + struct device *dev = ispif->dev; + u32 len = rb_config->val_size; + const void *src = cmd; + u32 rd_ptr, wr_ptr; + u32 bytes_to_end; + + rd_ptr = isp4hw_rreg(ispif->mmio, rreg); + wr_ptr = isp4hw_rreg(ispif->mmio, wreg); + if (rd_ptr >= len || wr_ptr >= len) { + dev_err(dev, + "rb invalid: stream=%u(%s), rd=%u, wr=%u, len=%u, cmd_sz=%u\n", + stream, isp4dbg_get_if_stream_str(stream), rd_ptr, + wr_ptr, len, cmd_sz); + return -EINVAL; + } + + bytes_to_end = len - wr_ptr; + if (bytes_to_end >= cmd_sz) { + /* FW cmd is just a straight copy to the write pointer */ + memcpy(mem_sys + wr_ptr, src, cmd_sz); + isp4hw_wreg(ispif->mmio, wreg, (wr_ptr + cmd_sz) % len); + } else { + /* + * FW cmd is split because the ring buffer needs to wrap + * around + */ + memcpy(mem_sys + wr_ptr, src, bytes_to_end); + memcpy(mem_sys, src + bytes_to_end, cmd_sz - bytes_to_end); + isp4hw_wreg(ispif->mmio, wreg, cmd_sz - bytes_to_end); + } + + return 0; +} + +static inline enum isp4if_stream_id isp4if_get_fw_stream(u32 cmd_id) +{ + return ISP4IF_STREAM_ID_1; +} + +static int isp4if_send_fw_cmd(struct isp4_interface *ispif, u32 cmd_id, + const void *package, + u32 package_size, bool sync) +{ + enum isp4if_stream_id stream = isp4if_get_fw_stream(cmd_id); + struct isp4if_cmd_element *ele = NULL; + struct device *dev = ispif->dev; + struct isp4fw_cmd cmd; + u32 seq_num; + int ret; + + if (package_size > sizeof(cmd.cmd_param)) { + dev_err(dev, "fail pkgsize(%u) > %zu cmd:0x%x, stream %d\n", + package_size, sizeof(cmd.cmd_param), cmd_id, stream); + return -EINVAL; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = cmd_id; + switch (stream) { + case ISP4IF_STREAM_ID_GLOBAL: + cmd.cmd_stream_id = ISP4FW_STREAM_ID_INVALID; + break; + case ISP4IF_STREAM_ID_1: + cmd.cmd_stream_id = ISP4FW_STREAM_ID_1; + break; + default: + dev_err(dev, "fail bad stream id %d\n", stream); + return -EINVAL; + } + + /* Allocate the sync command object early and outside of the lock */ + if (sync) { + ele = kmalloc(sizeof(*ele), GFP_KERNEL); + if (!ele) + return -ENOMEM; + + /* Get two references: one for the resp thread, one for us */ + atomic_set(&ele->refcnt, 2); + init_completion(&ele->cmd_done); + } + + if (package && package_size) + memcpy(cmd.cmd_param, package, package_size); + + scoped_guard(mutex, &ispif->isp4if_mutex) { + ret = read_poll_timeout(isp4if_is_cmdq_rb_full, ret, !ret, + ISP4IF_RB_FULL_SLEEP_US, + ISP4IF_RB_FULL_TIMEOUT_US, false, ispif, + stream); + if (ret) { + struct isp4if_rb_config *rb_config = + &isp4if_resp_rb_config[stream]; + u32 rd_ptr = isp4hw_rreg(ispif->mmio, + rb_config->reg_rptr); + u32 wr_ptr = isp4hw_rreg(ispif->mmio, + rb_config->reg_wptr); + + dev_err(dev, + "failed to get free cmdq slot, stream %s(%d),rd %u, wr %u\n", + isp4dbg_get_if_stream_str(stream), stream, + rd_ptr, wr_ptr); + ret = -ETIMEDOUT; + goto free_ele; + } + + seq_num = ispif->host2fw_seq_num++; + cmd.cmd_seq_num = seq_num; + cmd.cmd_check_sum = isp4if_compute_check_sum(&cmd, sizeof(cmd) + - sizeof(u32)); + + /* + * only append the fw cmd to queue when its response needs to + * be waited for, currently there are only two such commands, + * disable channel and stop stream which are only sent after + * close camera + */ + if (ele) { + ele->seq_num = seq_num; + ele->cmd_id = cmd_id; + scoped_guard(spinlock, &ispif->cmdq_lock) + list_add_tail(&ele->list, &ispif->cmdq); + } + + ret = isp4if_insert_isp_fw_cmd(ispif, stream, &cmd); + if (ret) { + dev_err(dev, + "fail for insert_isp_fw_cmd cmd_id %s(0x%08x)\n", + isp4dbg_get_cmd_str(cmd_id), cmd_id); + goto err_dequeue_ele; + } + } + + if (ele) { + ret = wait_for_completion_timeout(&ele->cmd_done, + ISP4IF_FW_CMD_TIMEOUT); + if (!ret) { + ret = -ETIMEDOUT; + goto err_dequeue_ele; + } + + ret = 0; + goto put_ele_ref; + } + + return 0; + +err_dequeue_ele: + /* + * Try to remove the command from the queue. If that fails, then it + * means the response thread is currently using the object, and we need + * to use the refcount to avoid a use-after-free by either side. + */ + if (ele && isp4if_rm_cmd_from_cmdq(ispif, seq_num, cmd_id)) + goto free_ele; + +put_ele_ref: + /* Don't free the command if we didn't put the last reference */ + if (ele && atomic_dec_return(&ele->refcnt)) + ele = NULL; + +free_ele: + kfree(ele); + return ret; +} + +static int isp4if_send_buffer(struct isp4_interface *ispif, + struct isp4if_img_buf_info *buf_info) +{ + struct isp4fw_cmd_send_buffer cmd; + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.buffer_type = ISP4FW_BUFFER_TYPE_PREVIEW; + cmd.buffer.vmid_space.bit.space = ISP4FW_ADDR_SPACE_TYPE_GPU_VA; + isp4if_split_addr64(buf_info->planes[0].mc_addr, + &cmd.buffer.buf_base_a_lo, + &cmd.buffer.buf_base_a_hi); + cmd.buffer.buf_size_a = buf_info->planes[0].len; + + isp4if_split_addr64(buf_info->planes[1].mc_addr, + &cmd.buffer.buf_base_b_lo, + &cmd.buffer.buf_base_b_hi); + cmd.buffer.buf_size_b = buf_info->planes[1].len; + + isp4if_split_addr64(buf_info->planes[2].mc_addr, + &cmd.buffer.buf_base_c_lo, + &cmd.buffer.buf_base_c_hi); + cmd.buffer.buf_size_c = buf_info->planes[2].len; + + return isp4if_send_fw_cmd(ispif, ISP4FW_CMD_ID_SEND_BUFFER, &cmd, + sizeof(cmd), false); +} + +static void isp4if_init_rb_config(struct isp4_interface *ispif, + struct isp4if_rb_config *rb_config) +{ + isp4hw_wreg(ispif->mmio, rb_config->reg_rptr, 0x0); + isp4hw_wreg(ispif->mmio, rb_config->reg_wptr, 0x0); + isp4hw_wreg(ispif->mmio, rb_config->reg_base_lo, + rb_config->base_mc_addr); + isp4hw_wreg(ispif->mmio, rb_config->reg_base_hi, + rb_config->base_mc_addr >> 32); + isp4hw_wreg(ispif->mmio, rb_config->reg_size, rb_config->val_size); +} + +static int isp4if_fw_init(struct isp4_interface *ispif) +{ + u32 aligned_rb_chunk_size = ISP4IF_RB_PMBMAP_MEM_CHUNK & 0xffffffc0; + struct isp4if_rb_config *rb_config; + u32 offset; + unsigned int i; + + /* initialize CMD_RB streams */ + for (i = 0; i < ISP4IF_STREAM_ID_MAX; i++) { + rb_config = (isp4if_cmd_rb_config + i); + offset = aligned_rb_chunk_size * rb_config->index; + + rb_config->val_size = ISP4IF_FW_CMD_BUF_SIZE; + rb_config->base_sys_addr = + ispif->fw_cmd_resp_buf->sys_addr + offset; + rb_config->base_mc_addr = + ispif->fw_cmd_resp_buf->gpu_mc_addr + offset; + + isp4if_init_rb_config(ispif, rb_config); + } + + /* initialize RESP_RB streams */ + for (i = 0; i < ISP4IF_STREAM_ID_MAX; i++) { + rb_config = (isp4if_resp_rb_config + i); + offset = aligned_rb_chunk_size * + (rb_config->index + ISP4IF_RESP_CHAN_TO_RB_OFFSET - 1); + + rb_config->val_size = ISP4IF_FW_CMD_BUF_SIZE; + rb_config->base_sys_addr = + ispif->fw_cmd_resp_buf->sys_addr + offset; + rb_config->base_mc_addr = + ispif->fw_cmd_resp_buf->gpu_mc_addr + offset; + + isp4if_init_rb_config(ispif, rb_config); + } + + /* initialize LOG_RB stream */ + rb_config = &isp4if_log_rb_config; + rb_config->val_size = ISP4IF_FW_LOG_RINGBUF_SIZE; + rb_config->base_mc_addr = ispif->fw_log_buf->gpu_mc_addr; + rb_config->base_sys_addr = ispif->fw_log_buf->sys_addr; + + isp4if_init_rb_config(ispif, rb_config); + + return 0; +} + +static int isp4if_wait_fw_ready(struct isp4_interface *ispif, + u32 isp_status_addr) +{ + struct device *dev = ispif->dev; + u32 timeout_ms = 100; + u32 interval_ms = 1; + u32 reg_val; + + /* wait for FW initialize done! */ + if (!read_poll_timeout(isp4hw_rreg, reg_val, reg_val + & ISP_STATUS__CCPU_REPORT_MASK, + interval_ms * 1000, timeout_ms * 1000, false, + ispif->mmio, isp_status_addr)) + return 0; + + dev_err(dev, "ISP CCPU FW boot failed\n"); + + return -ETIME; +} + +static void isp4if_enable_ccpu(struct isp4_interface *ispif) +{ + u32 reg_val; + + reg_val = isp4hw_rreg(ispif->mmio, ISP_SOFT_RESET); + reg_val &= (~ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK); + isp4hw_wreg(ispif->mmio, ISP_SOFT_RESET, reg_val); + + usleep_range(100, 150); + + reg_val = isp4hw_rreg(ispif->mmio, ISP_CCPU_CNTL); + reg_val &= (~ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK); + isp4hw_wreg(ispif->mmio, ISP_CCPU_CNTL, reg_val); +} + +static void isp4if_disable_ccpu(struct isp4_interface *ispif) +{ + u32 reg_val; + + reg_val = isp4hw_rreg(ispif->mmio, ISP_CCPU_CNTL); + reg_val |= ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK; + isp4hw_wreg(ispif->mmio, ISP_CCPU_CNTL, reg_val); + + usleep_range(100, 150); + + reg_val = isp4hw_rreg(ispif->mmio, ISP_SOFT_RESET); + reg_val |= ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK; + isp4hw_wreg(ispif->mmio, ISP_SOFT_RESET, reg_val); +} + +static int isp4if_fw_boot(struct isp4_interface *ispif) +{ + struct device *dev = ispif->dev; + + if (ispif->status != ISP4IF_STATUS_PWR_ON) { + dev_err(dev, "invalid isp power status %d\n", ispif->status); + return -EINVAL; + } + + isp4if_disable_ccpu(ispif); + + isp4if_fw_init(ispif); + + /* clear ccpu status */ + isp4hw_wreg(ispif->mmio, ISP_STATUS, 0x0); + + isp4if_enable_ccpu(ispif); + + if (isp4if_wait_fw_ready(ispif, ISP_STATUS)) { + isp4if_disable_ccpu(ispif); + return -EINVAL; + } + + /* enable interrupts */ + isp4hw_wreg(ispif->mmio, ISP_SYS_INT0_EN, + ISP4IF_FW_RESP_RB_IRQ_EN_MASK); + + ispif->status = ISP4IF_STATUS_FW_RUNNING; + + dev_dbg(dev, "ISP CCPU FW boot success\n"); + + return 0; +} + +int isp4if_f2h_resp(struct isp4_interface *ispif, enum isp4if_stream_id stream, + struct isp4fw_resp *resp) +{ + struct isp4if_rb_config *rb_config = &isp4if_resp_rb_config[stream]; + u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr; + void *mem_sys = rb_config->base_sys_addr; + const u32 resp_sz = sizeof(*resp); + struct device *dev = ispif->dev; + u32 len = rb_config->val_size; + u32 rd_ptr, wr_ptr; + u32 bytes_to_end; + void *dst = resp; + u32 checksum; + + rd_ptr = isp4hw_rreg(ispif->mmio, rreg); + wr_ptr = isp4hw_rreg(ispif->mmio, wreg); + if (rd_ptr >= len || wr_ptr >= len) + goto err_rb_invalid; + + /* + * Read and write pointers are equal, indicating the ring buffer is + * empty + */ + if (rd_ptr == wr_ptr) + return -ENODATA; + + bytes_to_end = len - rd_ptr; + if (bytes_to_end >= resp_sz) { + /* FW response is just a straight copy from the read pointer */ + if (wr_ptr > rd_ptr && wr_ptr - rd_ptr < resp_sz) + goto err_rb_invalid; + + memcpy(dst, mem_sys + rd_ptr, resp_sz); + isp4hw_wreg(ispif->mmio, rreg, (rd_ptr + resp_sz) % len); + } else { + /* + * FW response is split because the ring buffer wrapped + * around + */ + if (wr_ptr > rd_ptr || wr_ptr < resp_sz - bytes_to_end) + goto err_rb_invalid; + + memcpy(dst, mem_sys + rd_ptr, bytes_to_end); + memcpy(dst + bytes_to_end, mem_sys, resp_sz - bytes_to_end); + isp4hw_wreg(ispif->mmio, rreg, resp_sz - bytes_to_end); + } + + checksum = isp4if_compute_check_sum(resp, resp_sz - sizeof(u32)); + if (checksum != resp->resp_check_sum) { + dev_err(dev, "resp checksum 0x%x,should 0x%x,rptr %u,wptr %u\n", + checksum, resp->resp_check_sum, rd_ptr, wr_ptr); + dev_err(dev, "%s(%u), seqNo %u, resp_id %s(0x%x)\n", + isp4dbg_get_if_stream_str(stream), stream, + resp->resp_seq_num, isp4dbg_get_resp_str(resp->resp_id), + resp->resp_id); + return -EINVAL; + } + + return 0; + +err_rb_invalid: + dev_err(dev, + "rb invalid: stream=%u(%s), rd=%u, wr=%u, len=%u, resp_sz=%u\n", + stream, isp4dbg_get_if_stream_str(stream), rd_ptr, wr_ptr, len, + resp_sz); + return -EINVAL; +} + +int isp4if_send_command(struct isp4_interface *ispif, u32 cmd_id, + const void *package, u32 package_size) +{ + return isp4if_send_fw_cmd(ispif, cmd_id, package, package_size, false); +} + +int isp4if_send_command_sync(struct isp4_interface *ispif, u32 cmd_id, + const void *package, u32 package_size) +{ + return isp4if_send_fw_cmd(ispif, cmd_id, package, package_size, true); +} + +void isp4if_clear_bufq(struct isp4_interface *ispif) +{ + struct isp4if_img_buf_node *buf_node, *tmp_node; + LIST_HEAD(free_list); + + scoped_guard(spinlock, &ispif->bufq_lock) + list_splice_init(&ispif->bufq, &free_list); + + list_for_each_entry_safe(buf_node, tmp_node, &free_list, node) + kfree(buf_node); +} + +void isp4if_dealloc_buffer_node(struct isp4if_img_buf_node *buf_node) +{ + kfree(buf_node); +} + +struct isp4if_img_buf_node * +isp4if_alloc_buffer_node(struct isp4if_img_buf_info *buf_info) +{ + struct isp4if_img_buf_node *node; + + node = kmalloc(sizeof(*node), GFP_KERNEL); + if (node) + node->buf_info = *buf_info; + + return node; +} + +struct isp4if_img_buf_node *isp4if_dequeue_buffer(struct isp4_interface *ispif) +{ + struct isp4if_img_buf_node *buf_node; + + guard(spinlock)(&ispif->bufq_lock); + + buf_node = list_first_entry_or_null(&ispif->bufq, typeof(*buf_node), + node); + if (buf_node) + list_del(&buf_node->node); + + return buf_node; +} + +int isp4if_queue_buffer(struct isp4_interface *ispif, + struct isp4if_img_buf_node *buf_node) +{ + int ret; + + ret = isp4if_send_buffer(ispif, &buf_node->buf_info); + if (ret) + return ret; + + scoped_guard(spinlock, &ispif->bufq_lock) + list_add_tail(&buf_node->node, &ispif->bufq); + + return 0; +} + +int isp4if_stop(struct isp4_interface *ispif) +{ + isp4if_disable_ccpu(ispif); + + isp4if_dealloc_fw_gpumem(ispif); + + return 0; +} + +int isp4if_start(struct isp4_interface *ispif) +{ + int ret; + + ret = isp4if_alloc_fw_gpumem(ispif); + if (ret) + return ret; + + ret = isp4if_fw_boot(ispif); + if (ret) + goto failed_fw_boot; + + return 0; + +failed_fw_boot: + isp4if_dealloc_fw_gpumem(ispif); + return ret; +} + +int isp4if_deinit(struct isp4_interface *ispif) +{ + isp4if_clear_cmdq(ispif); + + isp4if_clear_bufq(ispif); + + mutex_destroy(&ispif->isp4if_mutex); + + return 0; +} + +int isp4if_init(struct isp4_interface *ispif, struct device *dev, + void __iomem *isp_mmio) +{ + ispif->dev = dev; + ispif->mmio = isp_mmio; + + spin_lock_init(&ispif->cmdq_lock); /* used for cmdq access */ + spin_lock_init(&ispif->bufq_lock); /* used for bufq access */ + mutex_init(&ispif->isp4if_mutex); /* used for commands sent to ispfw */ + + INIT_LIST_HEAD(&ispif->cmdq); + INIT_LIST_HEAD(&ispif->bufq); + + return 0; +} diff --git a/drivers/media/platform/amd/isp4/isp4_interface.h b/drivers/media/platform/amd/isp4/isp4_interface.h new file mode 100644 index 00000000000000..ce3ac9b9e5cda1 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_interface.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_INTERFACE_H_ +#define _ISP4_INTERFACE_H_ + +#include +#include +#include +#include + +struct isp4fw_resp; + +#define ISP4IF_RB_MAX 25 +#define ISP4IF_RESP_CHAN_TO_RB_OFFSET 9 +#define ISP4IF_RB_PMBMAP_MEM_SIZE (SZ_16M - 1) +#define ISP4IF_RB_PMBMAP_MEM_CHUNK \ + (ISP4IF_RB_PMBMAP_MEM_SIZE / (ISP4IF_RB_MAX - 1)) +#define ISP4IF_HOST2FW_COMMAND_SIZE sizeof(struct isp4fw_cmd) +#define ISP4IF_MAX_NUM_HOST2FW_COMMAND 40 +#define ISP4IF_FW_CMD_BUF_SIZE \ + (ISP4IF_MAX_NUM_HOST2FW_COMMAND * ISP4IF_HOST2FW_COMMAND_SIZE) +#define ISP4IF_RB_FULL_SLEEP_US (33 * USEC_PER_MSEC) +#define ISP4IF_RB_FULL_TIMEOUT_US (10 * ISP4IF_RB_FULL_SLEEP_US) + +#define ISP4IF_META_INFO_BUF_SIZE ALIGN(sizeof(struct isp4fw_meta_info), 0x8000) +#define ISP4IF_MAX_STREAM_BUF_COUNT 8 + +#define ISP4IF_FW_LOG_RINGBUF_SIZE SZ_2M + +enum isp4if_stream_id { + ISP4IF_STREAM_ID_GLOBAL = 0, + ISP4IF_STREAM_ID_1 = 1, + ISP4IF_STREAM_ID_MAX = 4 +}; + +enum isp4if_status { + ISP4IF_STATUS_PWR_OFF, + ISP4IF_STATUS_PWR_ON, + ISP4IF_STATUS_FW_RUNNING, + ISP4IF_FSM_STATUS_MAX +}; + +struct isp4if_gpu_mem_info { + u64 mem_size; + u64 gpu_mc_addr; + void *sys_addr; + void *mem_handle; +}; + +struct isp4if_img_buf_info { + struct { + void *sys_addr; + u64 mc_addr; + u32 len; + } planes[3]; +}; + +struct isp4if_img_buf_node { + struct list_head node; + struct isp4if_img_buf_info buf_info; +}; + +struct isp4if_cmd_element { + struct list_head list; + u32 seq_num; + u32 cmd_id; + struct completion cmd_done; + atomic_t refcnt; +}; + +struct isp4_interface { + struct device *dev; + void __iomem *mmio; + + spinlock_t cmdq_lock; /* used for cmdq access */ + spinlock_t bufq_lock; /* used for bufq access */ + struct mutex isp4if_mutex; /* used to send fw cmd and read fw log */ + + struct list_head cmdq; /* commands sent to fw */ + struct list_head bufq; /* buffers sent to fw */ + + enum isp4if_status status; + u32 host2fw_seq_num; + + /* ISP fw buffers */ + struct isp4if_gpu_mem_info *fw_log_buf; + struct isp4if_gpu_mem_info *fw_cmd_resp_buf; + struct isp4if_gpu_mem_info *fw_mem_pool; + struct isp4if_gpu_mem_info *meta_info_buf[ISP4IF_MAX_STREAM_BUF_COUNT]; +}; + +static inline void isp4if_split_addr64(u64 addr, u32 *lo, u32 *hi) +{ + if (lo) + *lo = addr & 0xffffffff; + + if (hi) + *hi = addr >> 32; +} + +static inline u64 isp4if_join_addr64(u32 lo, u32 hi) +{ + return (((u64)hi) << 32) | (u64)lo; +} + +int isp4if_f2h_resp(struct isp4_interface *ispif, enum isp4if_stream_id stream, + struct isp4fw_resp *resp); + +int isp4if_send_command(struct isp4_interface *ispif, u32 cmd_id, + const void *package, u32 package_size); + +int isp4if_send_command_sync(struct isp4_interface *ispif, u32 cmd_id, + const void *package, u32 package_size); + +struct isp4if_cmd_element *isp4if_rm_cmd_from_cmdq(struct isp4_interface *ispif, + u32 seq_num, u32 cmd_id); + +void isp4if_clear_cmdq(struct isp4_interface *ispif); + +void isp4if_clear_bufq(struct isp4_interface *ispif); + +void isp4if_dealloc_buffer_node(struct isp4if_img_buf_node *buf_node); + +struct isp4if_img_buf_node * +isp4if_alloc_buffer_node(struct isp4if_img_buf_info *buf_info); + +struct isp4if_img_buf_node *isp4if_dequeue_buffer(struct isp4_interface *ispif); + +int isp4if_queue_buffer(struct isp4_interface *ispif, + struct isp4if_img_buf_node *buf_node); + +int isp4if_stop(struct isp4_interface *ispif); + +int isp4if_start(struct isp4_interface *ispif); + +int isp4if_deinit(struct isp4_interface *ispif); + +int isp4if_init(struct isp4_interface *ispif, struct device *dev, + void __iomem *isp_mmio); + +#endif /* _ISP4_INTERFACE_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.c b/drivers/media/platform/amd/isp4/isp4_subdev.c new file mode 100644 index 00000000000000..48deea79ce6c25 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_subdev.c @@ -0,0 +1,1047 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include +#include + +#include "isp4.h" +#include "isp4_debug.h" +#include "isp4_fw_cmd_resp.h" +#include "isp4_interface.h" + +#define ISP4SD_MIN_BUF_CNT_BEF_START_STREAM 4 + +#define ISP4SD_PERFORMANCE_STATE_LOW 0 +#define ISP4SD_PERFORMANCE_STATE_HIGH 1 + +/* align 32KB */ +#define ISP4SD_META_BUF_SIZE ALIGN(sizeof(struct isp4fw_meta_info), 0x8000) + +#define to_isp4_subdev(sd) container_of(sd, struct isp4_subdev, sdev) + +static const char *isp4sd_entity_name = "amd isp4"; + +static const char *isp4sd_thread_name[ISP4SD_MAX_FW_RESP_STREAM_NUM] = { + "amd_isp4_thread_global", + "amd_isp4_thread_stream1", +}; + +static void isp4sd_module_enable(struct isp4_subdev *isp_subdev, bool enable) +{ + if (isp_subdev->enable_gpio) { + gpiod_set_value(isp_subdev->enable_gpio, enable ? 1 : 0); + dev_dbg(isp_subdev->dev, "%s isp_subdev module\n", + enable ? "enable" : "disable"); + } +} + +static int isp4sd_setup_fw_mem_pool(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4fw_cmd_send_buffer buf_type; + struct device *dev = isp_subdev->dev; + int ret; + + if (!ispif->fw_mem_pool) { + dev_err(dev, "fail to alloc mem pool\n"); + return -ENOMEM; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&buf_type, 0, sizeof(buf_type)); + buf_type.buffer_type = ISP4FW_BUFFER_TYPE_MEM_POOL; + buf_type.buffer.vmid_space.bit.space = ISP4FW_ADDR_SPACE_TYPE_GPU_VA; + isp4if_split_addr64(ispif->fw_mem_pool->gpu_mc_addr, + &buf_type.buffer.buf_base_a_lo, + &buf_type.buffer.buf_base_a_hi); + buf_type.buffer.buf_size_a = ispif->fw_mem_pool->mem_size; + + ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_SEND_BUFFER, + &buf_type, sizeof(buf_type)); + if (ret) { + dev_err(dev, "send fw mem pool 0x%llx(%u) fail %d\n", + ispif->fw_mem_pool->gpu_mc_addr, + buf_type.buffer.buf_size_a, ret); + return ret; + } + + dev_dbg(dev, "send fw mem pool 0x%llx(%u) suc\n", + ispif->fw_mem_pool->gpu_mc_addr, buf_type.buffer.buf_size_a); + + return 0; +} + +static int isp4sd_set_stream_path(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4fw_cmd_set_stream_cfg cmd; + struct device *dev = isp_subdev->dev; + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.stream_cfg.mipi_pipe_path_cfg.isp4fw_sensor_id = + ISP4FW_SENSOR_ID_ON_MIPI0; + cmd.stream_cfg.mipi_pipe_path_cfg.b_enable = true; + cmd.stream_cfg.isp_pipe_path_cfg.isp_pipe_id = + ISP4FW_MIPI0_ISP_PIPELINE_ID; + + cmd.stream_cfg.b_enable_tnr = true; + dev_dbg(dev, "isp4fw_sensor_id %d, pipeId 0x%x EnableTnr %u\n", + cmd.stream_cfg.mipi_pipe_path_cfg.isp4fw_sensor_id, + cmd.stream_cfg.isp_pipe_path_cfg.isp_pipe_id, + cmd.stream_cfg.b_enable_tnr); + + return isp4if_send_command(ispif, ISP4FW_CMD_ID_SET_STREAM_CONFIG, + &cmd, sizeof(cmd)); +} + +static int isp4sd_send_meta_buf(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4fw_cmd_send_buffer buf_type; + struct device *dev = isp_subdev->dev; + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&buf_type, 0, sizeof(buf_type)); + for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) { + struct isp4if_gpu_mem_info *meta_info_buf = + isp_subdev->ispif.meta_info_buf[i]; + int ret; + + if (!meta_info_buf) { + dev_err(dev, "fail for no meta info buf(%u)\n", i); + return -ENOMEM; + } + + buf_type.buffer_type = ISP4FW_BUFFER_TYPE_META_INFO; + buf_type.buffer.vmid_space.bit.space = + ISP4FW_ADDR_SPACE_TYPE_GPU_VA; + isp4if_split_addr64(meta_info_buf->gpu_mc_addr, + &buf_type.buffer.buf_base_a_lo, + &buf_type.buffer.buf_base_a_hi); + buf_type.buffer.buf_size_a = meta_info_buf->mem_size; + ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_SEND_BUFFER, + &buf_type, sizeof(buf_type)); + if (ret) { + dev_err(dev, "send meta info(%u) fail\n", i); + return ret; + } + } + + dev_dbg(dev, "send meta info suc\n"); + return 0; +} + +static bool isp4sd_get_str_out_prop(struct isp4_subdev *isp_subdev, + struct isp4fw_image_prop *out_prop, + struct v4l2_subdev_state *state, u32 pad) +{ + struct device *dev = isp_subdev->dev; + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_state_get_format(state, pad, 0); + if (!format) { + dev_err(dev, "fail get subdev state format\n"); + return false; + } + + switch (format->code) { + case MEDIA_BUS_FMT_YUYV8_1_5X8: + out_prop->image_format = ISP4FW_IMAGE_FORMAT_NV12; + out_prop->width = format->width; + out_prop->height = format->height; + out_prop->luma_pitch = format->width; + out_prop->chroma_pitch = out_prop->width; + break; + case MEDIA_BUS_FMT_YUYV8_1X16: + out_prop->image_format = ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED; + out_prop->width = format->width; + out_prop->height = format->height; + out_prop->luma_pitch = format->width * 2; + out_prop->chroma_pitch = 0; + break; + default: + dev_err(dev, "fail for bad image format:0x%x\n", + format->code); + return false; + } + + if (!out_prop->width || !out_prop->height) + return false; + + return true; +} + +static int isp4sd_kickoff_stream(struct isp4_subdev *isp_subdev, u32 w, u32 h) +{ + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + + if (sensor_info->status == ISP4SD_START_STATUS_STARTED) + return 0; + + if (sensor_info->status == ISP4SD_START_STATUS_START_FAIL) { + dev_err(dev, "fail for previous start fail\n"); + return -EINVAL; + } + + dev_dbg(dev, "w:%u,h:%u\n", w, h); + + if (isp4sd_send_meta_buf(isp_subdev)) { + dev_err(dev, "fail to send meta buf\n"); + sensor_info->status = ISP4SD_START_STATUS_START_FAIL; + return -EINVAL; + } + + sensor_info->status = ISP4SD_START_STATUS_OFF; + + if (!sensor_info->start_stream_cmd_sent && + sensor_info->buf_sent_cnt >= ISP4SD_MIN_BUF_CNT_BEF_START_STREAM) { + int ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_START_STREAM, + NULL, 0); + if (ret) { + dev_err(dev, "fail to start stream\n"); + return ret; + } + + sensor_info->start_stream_cmd_sent = true; + } else { + dev_dbg(dev, + "no send START_STREAM, start_sent %u, buf_sent %u\n", + sensor_info->start_stream_cmd_sent, + sensor_info->buf_sent_cnt); + } + + return 0; +} + +static int isp4sd_setup_output(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_output_info *output_info = + &isp_subdev->sensor_info.output_info; + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4fw_cmd_set_out_ch_prop cmd_ch_prop; + struct isp4fw_cmd_enable_out_ch cmd_ch_en; + struct device *dev = isp_subdev->dev; + int ret; + + if (output_info->start_status == ISP4SD_START_STATUS_STARTED) + return 0; + + if (output_info->start_status == ISP4SD_START_STATUS_START_FAIL) { + dev_err(dev, "fail for previous start fail\n"); + return -EINVAL; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&cmd_ch_prop, 0, sizeof(cmd_ch_prop)); + cmd_ch_prop.ch = ISP4FW_ISP_PIPE_OUT_CH_PREVIEW; + + if (!isp4sd_get_str_out_prop(isp_subdev, + &cmd_ch_prop.image_prop, state, pad)) { + dev_err(dev, "fail to get out prop\n"); + return -EINVAL; + } + + dev_dbg(dev, "channel:%s,fmt %s,w:h=%u:%u,lp:%u,cp%u\n", + isp4dbg_get_out_ch_str(cmd_ch_prop.ch), + isp4dbg_get_img_fmt_str(cmd_ch_prop.image_prop.image_format), + cmd_ch_prop.image_prop.width, cmd_ch_prop.image_prop.height, + cmd_ch_prop.image_prop.luma_pitch, + cmd_ch_prop.image_prop.chroma_pitch); + + ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_SET_OUT_CHAN_PROP, + &cmd_ch_prop, sizeof(cmd_ch_prop)); + if (ret) { + output_info->start_status = ISP4SD_START_STATUS_START_FAIL; + dev_err(dev, "fail to set out prop\n"); + return ret; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&cmd_ch_en, 0, sizeof(cmd_ch_en)); + cmd_ch_en.ch = ISP4FW_ISP_PIPE_OUT_CH_PREVIEW; + cmd_ch_en.is_enable = true; + ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_ENABLE_OUT_CHAN, + &cmd_ch_en, sizeof(cmd_ch_en)); + if (ret) { + output_info->start_status = ISP4SD_START_STATUS_START_FAIL; + dev_err(dev, "fail to enable channel\n"); + return ret; + } + + dev_dbg(dev, "enable channel %s\n", + isp4dbg_get_out_ch_str(cmd_ch_en.ch)); + + if (!sensor_info->start_stream_cmd_sent) { + ret = isp4sd_kickoff_stream(isp_subdev, + cmd_ch_prop.image_prop.width, + cmd_ch_prop.image_prop.height); + if (ret) { + dev_err(dev, "kickoff stream fail %d\n", ret); + return ret; + } + /* + * sensor_info->start_stream_cmd_sent will be set to true + * 1. in isp4sd_kickoff_stream, if app first send buffer then + * start stream + * 2. in isp_set_stream_buf, if app first start stream, then + * send buffer because ISP FW has the requirement, host needs + * to send buffer before send start stream cmd + */ + if (sensor_info->start_stream_cmd_sent) { + sensor_info->status = ISP4SD_START_STATUS_STARTED; + output_info->start_status = ISP4SD_START_STATUS_STARTED; + dev_dbg(dev, "kickoff stream suc,start cmd sent\n"); + } + } else { + dev_dbg(dev, "stream running, no need kickoff\n"); + output_info->start_status = ISP4SD_START_STATUS_STARTED; + } + + dev_dbg(dev, "setup output suc\n"); + return 0; +} + +static int isp4sd_init_stream(struct isp4_subdev *isp_subdev) +{ + struct device *dev = isp_subdev->dev; + int ret; + + ret = isp4sd_setup_fw_mem_pool(isp_subdev); + if (ret) { + dev_err(dev, "fail to setup fw mem pool\n"); + return ret; + } + + ret = isp4sd_set_stream_path(isp_subdev); + if (ret) { + dev_err(dev, "fail to setup stream path\n"); + return ret; + } + + return 0; +} + +static void isp4sd_uninit_stream(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + struct isp4sd_output_info *output_info = &sensor_info->output_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_state_get_format(state, pad, 0); + if (!format) { + dev_err(isp_subdev->dev, "fail to get v4l2 format\n"); + } else { + memset(format, 0, sizeof(*format)); + format->code = MEDIA_BUS_FMT_YUYV8_1_5X8; + } + + isp4if_clear_bufq(ispif); + isp4if_clear_cmdq(ispif); + + sensor_info->start_stream_cmd_sent = false; + sensor_info->buf_sent_cnt = 0; + + sensor_info->status = ISP4SD_START_STATUS_OFF; + output_info->start_status = ISP4SD_START_STATUS_OFF; +} + +static void isp4sd_fw_resp_cmd_done(struct isp4_subdev *isp_subdev, + enum isp4if_stream_id stream_id, + struct isp4fw_resp_cmd_done *para) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4if_cmd_element *ele = + isp4if_rm_cmd_from_cmdq(ispif, para->cmd_seq_num, para->cmd_id); + struct device *dev = isp_subdev->dev; + + dev_dbg(dev, "stream %d,cmd %s(0x%08x)(%d),seq %u, ele %p\n", + stream_id, + isp4dbg_get_cmd_str(para->cmd_id), + para->cmd_id, para->cmd_status, para->cmd_seq_num, + ele); + + if (ele) { + complete(&ele->cmd_done); + if (atomic_dec_and_test(&ele->refcnt)) + kfree(ele); + } +} + +static struct isp4fw_meta_info * +isp4sd_get_meta_by_mc(struct isp4_subdev *isp_subdev, u64 mc) +{ + for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) { + struct isp4if_gpu_mem_info *meta_info_buf = + isp_subdev->ispif.meta_info_buf[i]; + + if (meta_info_buf->gpu_mc_addr == mc) + return meta_info_buf->sys_addr; + } + + return NULL; +} + +static void isp4sd_send_meta_info(struct isp4_subdev *isp_subdev, + u64 meta_info_mc) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4fw_cmd_send_buffer buf_type; + struct device *dev = isp_subdev->dev; + + if (isp_subdev->sensor_info.status != ISP4SD_START_STATUS_STARTED) { + dev_warn(dev, "not working status %i, meta_info 0x%llx\n", + isp_subdev->sensor_info.status, meta_info_mc); + return; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&buf_type, 0, sizeof(buf_type)); + buf_type.buffer_type = ISP4FW_BUFFER_TYPE_META_INFO; + buf_type.buffer.vmid_space.bit.space = ISP4FW_ADDR_SPACE_TYPE_GPU_VA; + isp4if_split_addr64(meta_info_mc, + &buf_type.buffer.buf_base_a_lo, + &buf_type.buffer.buf_base_a_hi); + buf_type.buffer.buf_size_a = ISP4SD_META_BUF_SIZE; + + if (isp4if_send_command(ispif, ISP4FW_CMD_ID_SEND_BUFFER, + &buf_type, sizeof(buf_type))) + dev_err(dev, "fail send meta_info 0x%llx\n", + meta_info_mc); + else + dev_dbg(dev, "resend meta_info 0x%llx\n", meta_info_mc); +} + +static void isp4sd_fw_resp_frame_done(struct isp4_subdev *isp_subdev, + enum isp4if_stream_id stream_id, + struct isp4fw_resp_param_package *para) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + struct isp4if_img_buf_node *prev; + struct isp4fw_meta_info *meta; + u64 mc; + + mc = isp4if_join_addr64(para->package_addr_lo, para->package_addr_hi); + meta = isp4sd_get_meta_by_mc(isp_subdev, mc); + if (!meta) { + dev_err(dev, "fail to get meta from mc %llx\n", mc); + return; + } + + dev_dbg(dev, "ts:%llu,streamId:%d,poc:%u,preview_en:%u,status:%s(%i)\n", + ktime_get_ns(), stream_id, meta->poc, meta->preview.enabled, + isp4dbg_get_buf_done_str(meta->preview.status), + meta->preview.status); + + if (meta->preview.enabled && + (meta->preview.status == ISP4FW_BUFFER_STATUS_SKIPPED || + meta->preview.status == ISP4FW_BUFFER_STATUS_DONE || + meta->preview.status == ISP4FW_BUFFER_STATUS_DIRTY)) { + prev = isp4if_dequeue_buffer(ispif); + if (prev) { + isp4dbg_show_bufmeta_info(dev, "prev", &meta->preview, + &prev->buf_info); + isp4vid_handle_frame_done(&isp_subdev->isp_vdev, + &prev->buf_info); + isp4if_dealloc_buffer_node(prev); + } else { + dev_err(dev, "fail null prev buf\n"); + } + } else if (meta->preview.enabled) { + dev_err(dev, "fail bad preview status %u(%s)\n", + meta->preview.status, + isp4dbg_get_buf_done_str(meta->preview.status)); + } + + if (isp_subdev->sensor_info.status == ISP4SD_START_STATUS_STARTED) + isp4sd_send_meta_info(isp_subdev, mc); + + dev_dbg(dev, "stream_id:%d, status:%d\n", stream_id, + isp_subdev->sensor_info.status); +} + +static void isp4sd_fw_resp_func(struct isp4_subdev *isp_subdev, + enum isp4if_stream_id stream_id) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + struct isp4fw_resp resp; + + if (stream_id == ISP4IF_STREAM_ID_1) + isp_fw_log_print(isp_subdev); + + while (true) { + if (isp4if_f2h_resp(ispif, stream_id, &resp)) { + /* Re-enable the interrupt */ + isp4_intr_enable(isp_subdev, stream_id, true); + /* + * Recheck to see if there is a new response. + * To ensure that an in-flight interrupt is not lost, + * enabling the interrupt must occur _before_ checking + * for a new response, hence a memory barrier is needed. + * Disable the interrupt again if there was a new + * response. + */ + mb(); + if (likely(isp4if_f2h_resp(ispif, stream_id, &resp))) + break; + + isp4_intr_enable(isp_subdev, stream_id, false); + } + + switch (resp.resp_id) { + case ISP4FW_RESP_ID_CMD_DONE: + isp4sd_fw_resp_cmd_done(isp_subdev, stream_id, + &resp.param.cmd_done); + break; + case ISP4FW_RESP_ID_NOTI_FRAME_DONE: + isp4sd_fw_resp_frame_done(isp_subdev, stream_id, + &resp.param.frame_done); + break; + default: + dev_err(dev, "-><- fail respid %s(0x%x)\n", + isp4dbg_get_resp_str(resp.resp_id), + resp.resp_id); + break; + } + } +} + +static s32 isp4sd_fw_resp_thread(void *context) +{ + struct isp4_subdev_thread_param *para = context; + struct isp4_subdev *isp_subdev = para->isp_subdev; + struct isp4sd_thread_handler *thread_ctx = + &isp_subdev->fw_resp_thread[para->idx]; + struct device *dev = isp_subdev->dev; + + dev_dbg(dev, "[%u] fw resp thread started\n", para->idx); + while (true) { + wait_event_interruptible(thread_ctx->waitq, + thread_ctx->resp_ready); + thread_ctx->resp_ready = false; + + if (kthread_should_stop()) { + dev_dbg(dev, "[%u] fw resp thread quit\n", para->idx); + break; + } + + isp4sd_fw_resp_func(isp_subdev, para->idx); + } + + return 0; +} + +static int isp4sd_stop_resp_proc_threads(struct isp4_subdev *isp_subdev) +{ + for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) { + struct isp4sd_thread_handler *thread_ctx = + &isp_subdev->fw_resp_thread[i]; + + if (thread_ctx->thread) { + kthread_stop(thread_ctx->thread); + thread_ctx->thread = NULL; + } + } + + return 0; +} + +static int isp4sd_start_resp_proc_threads(struct isp4_subdev *isp_subdev) +{ + struct device *dev = isp_subdev->dev; + + for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) { + struct isp4sd_thread_handler *thread_ctx = + &isp_subdev->fw_resp_thread[i]; + + isp_subdev->isp_resp_para[i].idx = i; + isp_subdev->isp_resp_para[i].isp_subdev = isp_subdev; + init_waitqueue_head(&thread_ctx->waitq); + thread_ctx->resp_ready = false; + + thread_ctx->thread = kthread_run(isp4sd_fw_resp_thread, + &isp_subdev->isp_resp_para[i], + isp4sd_thread_name[i]); + if (IS_ERR(thread_ctx->thread)) { + dev_err(dev, "create thread [%d] fail\n", i); + thread_ctx->thread = NULL; + isp4sd_stop_resp_proc_threads(isp_subdev); + return -EINVAL; + } + } + + return 0; +} + +int isp4sd_pwroff_and_deinit(struct v4l2_subdev *sd) +{ + struct isp4_subdev *isp_subdev = to_isp4_subdev(sd); + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + unsigned int perf_state = ISP4SD_PERFORMANCE_STATE_LOW; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + int ret; + + guard(mutex)(&isp_subdev->ops_mutex); + if (sensor_info->status == ISP4SD_START_STATUS_STARTED) { + dev_err(dev, "fail for stream still running\n"); + return -EINVAL; + } + + sensor_info->status = ISP4SD_START_STATUS_OFF; + + if (isp_subdev->irq_enabled) { + for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) + disable_irq(isp_subdev->irq[i]); + isp_subdev->irq_enabled = false; + } + + isp4sd_stop_resp_proc_threads(isp_subdev); + dev_dbg(dev, "isp_subdev stop resp proc threads suc\n"); + + isp4if_stop(ispif); + + ret = dev_pm_genpd_set_performance_state(dev, perf_state); + if (ret) + dev_err(dev, + "fail to set isp_subdev performance state %u,ret %d\n", + perf_state, ret); + + /* hold ccpu reset */ + isp4hw_wreg(isp_subdev->mmio, ISP_SOFT_RESET, 0); + isp4hw_wreg(isp_subdev->mmio, ISP_POWER_STATUS, 0); + ret = pm_runtime_put_sync(dev); + if (ret) + dev_err(dev, "power off isp_subdev fail %d\n", ret); + else + dev_dbg(dev, "power off isp_subdev suc\n"); + + ispif->status = ISP4IF_STATUS_PWR_OFF; + isp4if_clear_cmdq(ispif); + isp4sd_module_enable(isp_subdev, false); + + /* + * When opening the camera, isp4sd_module_enable(isp_subdev, true) is + * called. Hardware requires at least a 20ms delay between disabling + * and enabling the module, so a sleep is added to ensure ISP stability + * during quick reopen scenarios. + */ + msleep(20); + + return 0; +} + +int isp4sd_pwron_and_init(struct v4l2_subdev *sd) +{ + struct isp4_subdev *isp_subdev = to_isp4_subdev(sd); + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + int ret; + + guard(mutex)(&isp_subdev->ops_mutex); + if (ispif->status == ISP4IF_STATUS_FW_RUNNING) { + dev_dbg(dev, "camera already opened, do nothing\n"); + return 0; + } + + isp4sd_module_enable(isp_subdev, true); + + if (ispif->status < ISP4IF_STATUS_PWR_ON) { + unsigned int perf_state = ISP4SD_PERFORMANCE_STATE_HIGH; + + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "fail to power on isp_subdev ret %d\n", + ret); + goto err_deinit; + } + + /* ISPPG ISP Power Status */ + isp4hw_wreg(isp_subdev->mmio, ISP_POWER_STATUS, 0x7FF); + ret = dev_pm_genpd_set_performance_state(dev, perf_state); + if (ret) { + dev_err(dev, + "fail to set performance state %u, ret %d\n", + perf_state, ret); + goto err_deinit; + } + + ispif->status = ISP4IF_STATUS_PWR_ON; + } + + isp_subdev->sensor_info.start_stream_cmd_sent = false; + isp_subdev->sensor_info.buf_sent_cnt = 0; + + ret = isp4if_start(ispif); + if (ret) { + dev_err(dev, "fail to start isp_subdev interface\n"); + goto err_deinit; + } + + if (isp4sd_start_resp_proc_threads(isp_subdev)) { + dev_err(dev, "isp_start_resp_proc_threads fail\n"); + goto err_deinit; + } + + dev_dbg(dev, "create resp threads ok\n"); + + for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) + enable_irq(isp_subdev->irq[i]); + isp_subdev->irq_enabled = true; + + return 0; +err_deinit: + isp4sd_pwroff_and_deinit(sd); + return -EINVAL; +} + +static int isp4sd_stop_stream(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + struct isp4sd_output_info *output_info = &sensor_info->output_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + + guard(mutex)(&isp_subdev->ops_mutex); + dev_dbg(dev, "status %i\n", output_info->start_status); + + if (output_info->start_status == ISP4SD_START_STATUS_STARTED) { + struct isp4fw_cmd_enable_out_ch cmd_ch_disable; + int ret; + + /* + * The struct will be shared with ISP FW, use memset() to + * guarantee padding bits are zeroed, since this is not + * guaranteed on all compilers. + */ + memset(&cmd_ch_disable, 0, sizeof(cmd_ch_disable)); + cmd_ch_disable.ch = ISP4FW_ISP_PIPE_OUT_CH_PREVIEW; + /* `cmd_ch_disable.is_enable` is already false */ + ret = isp4if_send_command_sync(ispif, + ISP4FW_CMD_ID_ENABLE_OUT_CHAN, + &cmd_ch_disable, + sizeof(cmd_ch_disable)); + if (ret) + dev_err(dev, "fail to disable stream\n"); + else + dev_dbg(dev, "wait disable stream suc\n"); + + ret = isp4if_send_command_sync(ispif, ISP4FW_CMD_ID_STOP_STREAM, + NULL, 0); + if (ret) + dev_err(dev, "fail to stop stream\n"); + else + dev_dbg(dev, "wait stop stream suc\n"); + } + + isp4sd_uninit_stream(isp_subdev, state, pad); + + /* + * Return success to ensure the stop process proceeds, + * and disregard any errors since they are not fatal. + */ + return 0; +} + +static int isp4sd_start_stream(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_output_info *output_info = + &isp_subdev->sensor_info.output_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + int ret; + + guard(mutex)(&isp_subdev->ops_mutex); + + if (ispif->status != ISP4IF_STATUS_FW_RUNNING) { + dev_err(dev, "fail, bad fsm %d\n", ispif->status); + return -EINVAL; + } + + switch (output_info->start_status) { + case ISP4SD_START_STATUS_OFF: + break; + case ISP4SD_START_STATUS_STARTED: + dev_dbg(dev, "stream already started, do nothing\n"); + return 0; + case ISP4SD_START_STATUS_START_FAIL: + dev_err(dev, "stream previously failed to start\n"); + return -EINVAL; + } + + ret = isp4sd_init_stream(isp_subdev); + if (ret) { + dev_err(dev, "fail to init isp_subdev stream\n"); + goto err_stop_stream; + } + + ret = isp4sd_setup_output(isp_subdev, state, pad); + if (ret) { + dev_err(dev, "fail to setup output\n"); + goto err_stop_stream; + } + + return 0; + +err_stop_stream: + isp4sd_stop_stream(isp_subdev, state, pad); + return ret; +} + +int isp4sd_ioc_send_img_buf(struct v4l2_subdev *sd, + struct isp4if_img_buf_info *buf_info) +{ + struct isp4_subdev *isp_subdev = to_isp4_subdev(sd); + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4if_img_buf_node *buf_node; + struct device *dev = isp_subdev->dev; + int ret; + + guard(mutex)(&isp_subdev->ops_mutex); + + if (ispif->status != ISP4IF_STATUS_FW_RUNNING) { + dev_err(dev, "fail send img buf for bad fsm %d\n", + ispif->status); + return -EINVAL; + } + + buf_node = isp4if_alloc_buffer_node(buf_info); + if (!buf_node) { + dev_err(dev, "fail alloc sys img buf info node\n"); + return -ENOMEM; + } + + ret = isp4if_queue_buffer(ispif, buf_node); + if (ret) { + dev_err(dev, "fail to queue image buf, %d\n", ret); + goto error_release_buf_node; + } + + if (!isp_subdev->sensor_info.start_stream_cmd_sent) { + isp_subdev->sensor_info.buf_sent_cnt++; + + if (isp_subdev->sensor_info.buf_sent_cnt >= + ISP4SD_MIN_BUF_CNT_BEF_START_STREAM) { + ret = isp4if_send_command(ispif, + ISP4FW_CMD_ID_START_STREAM, + NULL, 0); + if (ret) { + dev_err(dev, "fail to START_STREAM"); + goto error_release_buf_node; + } + isp_subdev->sensor_info.start_stream_cmd_sent = true; + isp_subdev->sensor_info.output_info.start_status = + ISP4SD_START_STATUS_STARTED; + isp_subdev->sensor_info.status = + ISP4SD_START_STATUS_STARTED; + } else { + dev_dbg(dev, + "no send start, required %u, buf sent %u\n", + ISP4SD_MIN_BUF_CNT_BEF_START_STREAM, + isp_subdev->sensor_info.buf_sent_cnt); + } + } + + return 0; + +error_release_buf_node: + isp4if_dealloc_buffer_node(buf_node); + return ret; +} + +static const struct v4l2_subdev_video_ops isp4sd_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static int isp4sd_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct isp4sd_output_info *stream_info = + &(to_isp4_subdev(sd)->sensor_info.output_info); + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + + if (!fmt) { + dev_err(sd->dev, "fail to get state format\n"); + return -EINVAL; + } + + *fmt = format->format; + switch (fmt->code) { + case MEDIA_BUS_FMT_YUYV8_1X16: + stream_info->image_size = fmt->width * fmt->height * 2; + break; + case MEDIA_BUS_FMT_YUYV8_1_5X8: + default: + stream_info->image_size = fmt->width * fmt->height * 3 / 2; + break; + } + + if (!stream_info->image_size) { + dev_err(sd->dev, + "fail set pad format,code 0x%x,width %u, height %u\n", + fmt->code, fmt->width, fmt->height); + return -EINVAL; + } + + dev_dbg(sd->dev, "set pad format suc, code:%x w:%u h:%u size:%u\n", + fmt->code, fmt->width, fmt->height, + stream_info->image_size); + + return 0; +} + +static int isp4sd_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct isp4_subdev *isp_subdev = to_isp4_subdev(sd); + + return isp4sd_start_stream(isp_subdev, state, pad); +} + +static int isp4sd_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct isp4_subdev *isp_subdev = to_isp4_subdev(sd); + + return isp4sd_stop_stream(isp_subdev, state, pad); +} + +static const struct v4l2_subdev_pad_ops isp4sd_pad_ops = { + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = isp4sd_set_pad_format, + .enable_streams = isp4sd_enable_streams, + .disable_streams = isp4sd_disable_streams, +}; + +static const struct v4l2_subdev_ops isp4sd_subdev_ops = { + .video = &isp4sd_video_ops, + .pad = &isp4sd_pad_ops, +}; + +int isp4sd_init(struct isp4_subdev *isp_subdev, struct v4l2_device *v4l2_dev, + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]) +{ + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = v4l2_dev->dev; + int ret; + + isp_subdev->dev = dev; + v4l2_subdev_init(&isp_subdev->sdev, &isp4sd_subdev_ops); + isp_subdev->sdev.owner = THIS_MODULE; + isp_subdev->sdev.dev = dev; + snprintf(isp_subdev->sdev.name, sizeof(isp_subdev->sdev.name), "%s", + dev_name(dev)); + + isp_subdev->sdev.entity.name = isp4sd_entity_name; + isp_subdev->sdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP; + isp_subdev->sdev_pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&isp_subdev->sdev.entity, 1, + &isp_subdev->sdev_pad); + if (ret) { + dev_err(dev, "fail to init isp4 subdev entity pad %d\n", ret); + return ret; + } + + ret = v4l2_subdev_init_finalize(&isp_subdev->sdev); + if (ret < 0) { + dev_err(dev, "fail to init finalize isp4 subdev %d\n", + ret); + return ret; + } + + ret = v4l2_device_register_subdev(v4l2_dev, &isp_subdev->sdev); + if (ret) { + dev_err(dev, "fail to register isp4 subdev to V4L2 device %d\n", + ret); + goto err_media_clean_up; + } + + isp4if_init(ispif, dev, isp_subdev->mmio); + + mutex_init(&isp_subdev->ops_mutex); + sensor_info->status = ISP4SD_START_STATUS_OFF; + + /* create ISP enable gpio control */ + isp_subdev->enable_gpio = devm_gpiod_get(isp_subdev->dev, + "enable_isp", + GPIOD_OUT_LOW); + if (IS_ERR(isp_subdev->enable_gpio)) { + ret = PTR_ERR(isp_subdev->enable_gpio); + dev_err(dev, "fail to get gpiod %d\n", ret); + goto err_subdev_unreg; + } + + for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) + isp_subdev->irq[i] = irq[i]; + + isp_subdev->host2fw_seq_num = 1; + ispif->status = ISP4IF_STATUS_PWR_OFF; + + ret = isp4vid_dev_init(&isp_subdev->isp_vdev, &isp_subdev->sdev); + if (ret) + goto err_subdev_unreg; + + return 0; + +err_subdev_unreg: + v4l2_device_unregister_subdev(&isp_subdev->sdev); +err_media_clean_up: + v4l2_subdev_cleanup(&isp_subdev->sdev); + media_entity_cleanup(&isp_subdev->sdev.entity); + return ret; +} + +void isp4sd_deinit(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + + isp4vid_dev_deinit(&isp_subdev->isp_vdev); + v4l2_device_unregister_subdev(&isp_subdev->sdev); + media_entity_cleanup(&isp_subdev->sdev.entity); + isp4if_deinit(ispif); + isp4sd_module_enable(isp_subdev, false); + + ispif->status = ISP4IF_STATUS_PWR_OFF; +} diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.h b/drivers/media/platform/amd/isp4/isp4_subdev.h new file mode 100644 index 00000000000000..20ea08a830afd0 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_subdev.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_SUBDEV_H_ +#define _ISP4_SUBDEV_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "isp4_fw_cmd_resp.h" +#include "isp4_hw_reg.h" +#include "isp4_interface.h" +#include "isp4_video.h" + +/* + * One is for none sensor specific response which is not used now. + * Another is for sensor specific response + */ +#define ISP4SD_MAX_FW_RESP_STREAM_NUM 2 + +/* Indicates the ISP status */ +enum isp4sd_status { + ISP4SD_STATUS_PWR_OFF, + ISP4SD_STATUS_PWR_ON, + ISP4SD_STATUS_FW_RUNNING, + ISP4SD_STATUS_MAX +}; + +/* Indicates sensor and output stream status */ +enum isp4sd_start_status { + ISP4SD_START_STATUS_OFF, + ISP4SD_START_STATUS_STARTED, + ISP4SD_START_STATUS_START_FAIL, +}; + +struct isp4sd_img_buf_node { + struct list_head node; + struct isp4if_img_buf_info buf_info; +}; + +/* This is ISP output after processing Bayer raw sensor input */ +struct isp4sd_output_info { + enum isp4sd_start_status start_status; + u32 image_size; +}; + +/* + * Struct for sensor info used as ISP input or source. + * status: sensor status. + * output_info: ISP output after processing the sensor input. + * start_stream_cmd_sent: indicates if ISP4FW_CMD_ID_START_STREAM was sent + * to firmware. + * buf_sent_cnt: number of buffers sent to receive images. + */ +struct isp4sd_sensor_info { + struct isp4sd_output_info output_info; + enum isp4sd_start_status status; + bool start_stream_cmd_sent; + u32 buf_sent_cnt; +}; + +/* + * The thread is created by the driver to handle firmware responses which will + * be waken up when a firmware-to-driver response interrupt occurs. + */ +struct isp4sd_thread_handler { + struct task_struct *thread; + wait_queue_head_t waitq; + bool resp_ready; +}; + +struct isp4_subdev_thread_param { + u32 idx; + struct isp4_subdev *isp_subdev; +}; + +struct isp4_subdev { + struct v4l2_subdev sdev; + struct isp4_interface ispif; + struct isp4vid_dev isp_vdev; + + struct media_pad sdev_pad; + + enum isp4sd_status isp_status; + /* mutex used to synchronize the operation with firmware */ + struct mutex ops_mutex; + + struct isp4sd_thread_handler + fw_resp_thread[ISP4SD_MAX_FW_RESP_STREAM_NUM]; + + u32 host2fw_seq_num; + + struct isp4sd_sensor_info sensor_info; + + /* gpio descriptor */ + struct gpio_desc *enable_gpio; + struct device *dev; + void __iomem *mmio; + struct isp4_subdev_thread_param + isp_resp_para[ISP4SD_MAX_FW_RESP_STREAM_NUM]; + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]; + bool irq_enabled; + /* spin lock to access ISP_SYS_INT0_EN exclusively */ + spinlock_t irq_lock; +#ifdef CONFIG_DEBUG_FS + bool enable_fw_log; + struct dentry *debugfs_dir; + char *fw_log_output; +#endif +}; + +int isp4sd_init(struct isp4_subdev *isp_subdev, struct v4l2_device *v4l2_dev, + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]); +void isp4sd_deinit(struct isp4_subdev *isp_subdev); +int isp4sd_ioc_send_img_buf(struct v4l2_subdev *sd, + struct isp4if_img_buf_info *buf_info); +int isp4sd_pwron_and_init(struct v4l2_subdev *sd); +int isp4sd_pwroff_and_deinit(struct v4l2_subdev *sd); + +#endif /* _ISP4_SUBDEV_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_video.c b/drivers/media/platform/amd/isp4/isp4_video.c new file mode 100644 index 00000000000000..0cebb39f98e1da --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_video.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include +#include +#include + +#include "isp4_interface.h" +#include "isp4_subdev.h" +#include "isp4_video.h" + +#define ISP4VID_ISP_DRV_NAME "amd_isp_capture" +#define ISP4VID_MAX_PREVIEW_FPS 30 +#define ISP4VID_DEFAULT_FMT V4L2_PIX_FMT_NV12 + +#define ISP4VID_PAD_VIDEO_OUTPUT 0 + +/* time perframe default */ +#define ISP4VID_ISP_TPF_DEFAULT isp4vid_tpfs[0] + +static const char *const isp4vid_video_dev_name = "Preview"; + +/* Sizes must be in increasing order */ +static const struct v4l2_frmsize_discrete isp4vid_frmsize[] = { + {640, 360}, + {640, 480}, + {1280, 720}, + {1280, 960}, + {1920, 1080}, + {1920, 1440}, + {2560, 1440}, + {2880, 1620}, + {2880, 1624}, + {2888, 1808}, +}; + +static const u32 isp4vid_formats[] = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUYV +}; + +/* time perframe list */ +static const struct v4l2_fract isp4vid_tpfs[] = { + { 1, ISP4VID_MAX_PREVIEW_FPS } +}; + +void isp4vid_handle_frame_done(struct isp4vid_dev *isp_vdev, + const struct isp4if_img_buf_info *img_buf) +{ + struct isp4vid_capture_buffer *isp4vid_buf; + void *vbuf; + + scoped_guard(mutex, &isp_vdev->buf_list_lock) { + isp4vid_buf = list_first_entry_or_null(&isp_vdev->buf_list, + typeof(*isp4vid_buf), + list); + if (!isp4vid_buf) + return; + + vbuf = vb2_plane_vaddr(&isp4vid_buf->vb2.vb2_buf, 0); + + if (vbuf != img_buf->planes[0].sys_addr) { + dev_err(isp_vdev->dev, "Invalid vbuf\n"); + return; + } + + list_del(&isp4vid_buf->list); + } + + /* Fill the buffer */ + isp4vid_buf->vb2.vb2_buf.timestamp = ktime_get_ns(); + isp4vid_buf->vb2.sequence = isp_vdev->sequence++; + isp4vid_buf->vb2.field = V4L2_FIELD_ANY; + + vb2_set_plane_payload(&isp4vid_buf->vb2.vb2_buf, + 0, isp_vdev->format.sizeimage); + + vb2_buffer_done(&isp4vid_buf->vb2.vb2_buf, VB2_BUF_STATE_DONE); + + dev_dbg(isp_vdev->dev, "call vb2_buffer_done(size=%u)\n", + isp_vdev->format.sizeimage); +} + +static const struct v4l2_pix_format isp4vid_fmt_default = { + .width = 1920, + .height = 1080, + .pixelformat = ISP4VID_DEFAULT_FMT, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, +}; + +static void isp4vid_capture_return_all_buffers(struct isp4vid_dev *isp_vdev, + enum vb2_buffer_state state) +{ + struct isp4vid_capture_buffer *vbuf, *node; + + scoped_guard(mutex, &isp_vdev->buf_list_lock) { + list_for_each_entry_safe(vbuf, node, &isp_vdev->buf_list, list) + vb2_buffer_done(&vbuf->vb2.vb2_buf, state); + INIT_LIST_HEAD(&isp_vdev->buf_list); + } + + dev_dbg(isp_vdev->dev, "call vb2_buffer_done(%d)\n", state); +} + +static int isp4vid_vdev_link_validate(struct media_link *link) +{ + return 0; +} + +static const struct media_entity_operations isp4vid_vdev_ent_ops = { + .link_validate = isp4vid_vdev_link_validate, +}; + +static const struct v4l2_file_operations isp4vid_vdev_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static int isp4vid_ioctl_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + + strscpy(cap->driver, ISP4VID_ISP_DRV_NAME, sizeof(cap->driver)); + snprintf(cap->card, sizeof(cap->card), "%s", ISP4VID_ISP_DRV_NAME); + cap->capabilities |= V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE; + + dev_dbg(isp_vdev->dev, "%s|capabilities=0x%X\n", isp_vdev->vdev.name, + cap->capabilities); + + return 0; +} + +static int isp4vid_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + + f->fmt.pix = isp_vdev->format; + + return 0; +} + +static int isp4vid_fill_buffer_size(struct v4l2_pix_format *fmt) +{ + int ret = 0; + + switch (fmt->pixelformat) { + case V4L2_PIX_FMT_NV12: + fmt->bytesperline = fmt->width; + fmt->sizeimage = fmt->bytesperline * fmt->height * 3 / 2; + break; + case V4L2_PIX_FMT_YUYV: + fmt->bytesperline = fmt->width * 2; + fmt->sizeimage = fmt->bytesperline * fmt->height; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int isp4vid_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + struct v4l2_pix_format *format = &f->fmt.pix; + const struct v4l2_frmsize_discrete *fsz; + size_t i; + + /* + * Check if the hardware supports the requested format, use the default + * format otherwise. + */ + for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++) + if (isp4vid_formats[i] == format->pixelformat) + break; + + if (i == ARRAY_SIZE(isp4vid_formats)) + format->pixelformat = ISP4VID_DEFAULT_FMT; + + switch (format->pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_YUYV: + fsz = v4l2_find_nearest_size(isp4vid_frmsize, + ARRAY_SIZE(isp4vid_frmsize), + width, height, format->width, + format->height); + format->width = fsz->width; + format->height = fsz->height; + break; + default: + dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n", + isp_vdev->vdev.name, + format->pixelformat); + return -EINVAL; + } + + /* + * There is no need to check the return value, as failure will never + * happen here + */ + isp4vid_fill_buffer_size(format); + + if (format->field == V4L2_FIELD_ANY) + format->field = isp4vid_fmt_default.field; + + if (format->colorspace == V4L2_COLORSPACE_DEFAULT) + format->colorspace = isp4vid_fmt_default.colorspace; + + return 0; +} + +static int isp4vid_set_fmt_2_isp(struct v4l2_subdev *sdev, + struct v4l2_pix_format *pix_fmt) +{ + struct v4l2_subdev_format fmt = {}; + + switch (pix_fmt->pixelformat) { + case V4L2_PIX_FMT_NV12: + fmt.format.code = MEDIA_BUS_FMT_YUYV8_1_5X8; + break; + case V4L2_PIX_FMT_YUYV: + fmt.format.code = MEDIA_BUS_FMT_YUYV8_1X16; + break; + default: + return -EINVAL; + } + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.pad = ISP4VID_PAD_VIDEO_OUTPUT; + fmt.format.width = pix_fmt->width; + fmt.format.height = pix_fmt->height; + return v4l2_subdev_call(sdev, pad, set_fmt, NULL, &fmt); +} + +static int isp4vid_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + int ret; + + /* Do not change the format while stream is on */ + if (vb2_is_busy(&isp_vdev->vbq)) + return -EBUSY; + + ret = isp4vid_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + dev_dbg(isp_vdev->dev, "%s|width height:%ux%u->%ux%u\n", + isp_vdev->vdev.name, + isp_vdev->format.width, isp_vdev->format.height, + f->fmt.pix.width, f->fmt.pix.height); + dev_dbg(isp_vdev->dev, "%s|pixelformat:0x%x-0x%x\n", + isp_vdev->vdev.name, isp_vdev->format.pixelformat, + f->fmt.pix.pixelformat); + dev_dbg(isp_vdev->dev, "%s|bytesperline:%u->%u\n", + isp_vdev->vdev.name, isp_vdev->format.bytesperline, + f->fmt.pix.bytesperline); + dev_dbg(isp_vdev->dev, "%s|sizeimage:%u->%u\n", + isp_vdev->vdev.name, isp_vdev->format.sizeimage, + f->fmt.pix.sizeimage); + + isp_vdev->format = f->fmt.pix; + ret = isp4vid_set_fmt_2_isp(isp_vdev->isp_sdev, &isp_vdev->format); + + return ret; +} + +static int isp4vid_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + + switch (f->index) { + case 0: + f->pixelformat = V4L2_PIX_FMT_NV12; + break; + case 1: + f->pixelformat = V4L2_PIX_FMT_YUYV; + break; + default: + return -EINVAL; + } + + dev_dbg(isp_vdev->dev, "%s|index=%d, pixelformat=0x%X\n", + isp_vdev->vdev.name, f->index, f->pixelformat); + + return 0; +} + +static int isp4vid_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++) { + if (isp4vid_formats[i] == fsize->pixel_format) + break; + } + + if (i == ARRAY_SIZE(isp4vid_formats)) + return -EINVAL; + + if (fsize->index < ARRAY_SIZE(isp4vid_frmsize)) { + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete = isp4vid_frmsize[fsize->index]; + dev_dbg(isp_vdev->dev, "%s|size[%d]=%dx%d\n", + isp_vdev->vdev.name, fsize->index, + fsize->discrete.width, fsize->discrete.height); + } else { + return -EINVAL; + } + + return 0; +} + +static int isp4vid_ioctl_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + size_t i; + + if (fival->index >= ARRAY_SIZE(isp4vid_tpfs)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++) + if (isp4vid_formats[i] == fival->pixel_format) + break; + + if (i == ARRAY_SIZE(isp4vid_formats)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(isp4vid_frmsize); i++) + if (isp4vid_frmsize[i].width == fival->width && + isp4vid_frmsize[i].height == fival->height) + break; + + if (i == ARRAY_SIZE(isp4vid_frmsize)) + return -EINVAL; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete = isp4vid_tpfs[fival->index]; + v4l2_simplify_fraction(&fival->discrete.numerator, + &fival->discrete.denominator, 8, 333); + + dev_dbg(isp_vdev->dev, "%s|interval[%d]=%d/%d\n", + isp_vdev->vdev.name, fival->index, + fival->discrete.numerator, + fival->discrete.denominator); + + return 0; +} + +static int isp4vid_ioctl_g_param(struct file *file, void *priv, + struct v4l2_streamparm *param) +{ + struct v4l2_captureparm *capture = ¶m->parm.capture; + struct isp4vid_dev *isp_vdev = video_drvdata(file); + + if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + capture->capability = V4L2_CAP_TIMEPERFRAME; + capture->timeperframe = isp_vdev->timeperframe; + capture->readbuffers = 0; + + dev_dbg(isp_vdev->dev, "%s|timeperframe=%d/%d\n", isp_vdev->vdev.name, + capture->timeperframe.numerator, + capture->timeperframe.denominator); + + return 0; +} + +static const struct v4l2_ioctl_ops isp4vid_vdev_ioctl_ops = { + .vidioc_querycap = isp4vid_ioctl_querycap, + .vidioc_enum_fmt_vid_cap = isp4vid_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = isp4vid_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = isp4vid_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = isp4vid_try_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_parm = isp4vid_ioctl_g_param, + .vidioc_s_parm = isp4vid_ioctl_g_param, + .vidioc_enum_framesizes = isp4vid_enum_framesizes, + .vidioc_enum_frameintervals = isp4vid_ioctl_enum_frameintervals, +}; + +static unsigned int isp4vid_get_image_size(struct v4l2_pix_format *fmt) +{ + switch (fmt->pixelformat) { + case V4L2_PIX_FMT_NV12: + return fmt->width * fmt->height * 3 / 2; + case V4L2_PIX_FMT_YUYV: + return fmt->width * fmt->height * 2; + default: + return 0; + } +} + +static int isp4vid_qops_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq); + unsigned int q_num_bufs = vb2_get_num_buffers(vq); + + if (*nplanes > 1) { + dev_err(isp_vdev->dev, + "fail to setup queue, no mplane supported %u\n", + *nplanes); + return -EINVAL; + } + + if (*nplanes == 1) { + unsigned int size; + + size = isp4vid_get_image_size(&isp_vdev->format); + if (sizes[0] < size) { + dev_err(isp_vdev->dev, + "fail for small plane size %u, %u expected\n", + sizes[0], size); + return -EINVAL; + } + } + + if (q_num_bufs + *nbuffers < ISP4IF_MAX_STREAM_BUF_COUNT) + *nbuffers = ISP4IF_MAX_STREAM_BUF_COUNT - q_num_bufs; + + switch (isp_vdev->format.pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_YUYV: { + *nplanes = 1; + sizes[0] = max(sizes[0], isp_vdev->format.sizeimage); + isp_vdev->format.sizeimage = sizes[0]; + } + break; + default: + dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n", + isp_vdev->vdev.name, isp_vdev->format.pixelformat); + return -EINVAL; + } + + dev_dbg(isp_vdev->dev, "%s|*nbuffers=%u *nplanes=%u sizes[0]=%u\n", + isp_vdev->vdev.name, + *nbuffers, *nplanes, sizes[0]); + + return 0; +} + +static void isp4vid_qops_buffer_queue(struct vb2_buffer *vb) +{ + struct isp4vid_capture_buffer *buf = + container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf); + struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vb->vb2_queue); + struct isp4if_img_buf_info *img_buf = &buf->img_buf; + void *vaddr = vb2_plane_vaddr(vb, 0); + + dev_dbg(isp_vdev->dev, "queue buf, vaddr %p, gpuva 0x%llx, size %u\n", + vaddr, buf->gpu_addr, vb->planes[0].length); + + switch (isp_vdev->format.pixelformat) { + case V4L2_PIX_FMT_NV12: { + u32 y_size = isp_vdev->format.sizeimage / 3 * 2; + u32 uv_size = isp_vdev->format.sizeimage / 3; + + img_buf->planes[0].len = y_size; + img_buf->planes[0].sys_addr = vaddr; + img_buf->planes[0].mc_addr = buf->gpu_addr; + + dev_dbg(isp_vdev->dev, "img_buf[0]: mc=0x%llx size=%u\n", + img_buf->planes[0].mc_addr, + img_buf->planes[0].len); + + img_buf->planes[1].len = uv_size; + img_buf->planes[1].sys_addr = vaddr + y_size; + img_buf->planes[1].mc_addr = buf->gpu_addr + y_size; + + dev_dbg(isp_vdev->dev, "img_buf[1]: mc=0x%llx size=%u\n", + img_buf->planes[1].mc_addr, + img_buf->planes[1].len); + + img_buf->planes[2].len = 0; + } + break; + case V4L2_PIX_FMT_YUYV: { + img_buf->planes[0].len = isp_vdev->format.sizeimage; + img_buf->planes[0].sys_addr = vaddr; + img_buf->planes[0].mc_addr = buf->gpu_addr; + + dev_dbg(isp_vdev->dev, "img_buf[0]: mc=0x%llx size=%u\n", + img_buf->planes[0].mc_addr, + img_buf->planes[0].len); + + img_buf->planes[1].len = 0; + img_buf->planes[2].len = 0; + } + break; + default: + dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n", + isp_vdev->vdev.name, isp_vdev->format.pixelformat); + return; + } + + if (isp_vdev->stream_started) + isp4sd_ioc_send_img_buf(isp_vdev->isp_sdev, img_buf); + + scoped_guard(mutex, &isp_vdev->buf_list_lock) + list_add_tail(&buf->list, &isp_vdev->buf_list); +} + +static int isp4vid_qops_start_streaming(struct vb2_queue *vq, + unsigned int count) +{ + struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq); + struct isp4vid_capture_buffer *isp4vid_buf; + struct media_entity *entity; + struct v4l2_subdev *subdev; + struct media_pad *pad; + int ret = 0; + + isp_vdev->sequence = 0; + + ret = isp4sd_pwron_and_init(isp_vdev->isp_sdev); + if (ret) { + dev_err(isp_vdev->dev, "power up isp fail %d\n", ret); + goto release_buffers; + } + + entity = &isp_vdev->vdev.entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_dbg(isp_vdev->dev, "fail start streaming: %s %d\n", + subdev->name, ret); + goto release_buffers; + } + } + + list_for_each_entry(isp4vid_buf, &isp_vdev->buf_list, list) + isp4sd_ioc_send_img_buf(isp_vdev->isp_sdev, + &isp4vid_buf->img_buf); + + isp_vdev->stream_started = true; + + return 0; + +release_buffers: + isp4vid_capture_return_all_buffers(isp_vdev, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void isp4vid_qops_stop_streaming(struct vb2_queue *vq) +{ + struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq); + struct media_entity *entity; + struct v4l2_subdev *subdev; + struct media_pad *pad; + int ret; + + entity = &isp_vdev->vdev.entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, 0); + + if (ret < 0 && ret != -ENOIOCTLCMD) + dev_dbg(isp_vdev->dev, "fail stop streaming: %s %d\n", + subdev->name, ret); + } + + isp_vdev->stream_started = false; + isp4sd_pwroff_and_deinit(isp_vdev->isp_sdev); + + /* Release all active buffers */ + isp4vid_capture_return_all_buffers(isp_vdev, VB2_BUF_STATE_ERROR); +} + +static int isp4vid_qops_buf_init(struct vb2_buffer *vb) +{ + struct isp4vid_capture_buffer *buf = + container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf); + struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vb->vb2_queue); + void *mem_priv = vb->planes[0].mem_priv; + struct device *dev = isp_vdev->dev; + u64 gpu_addr; + void *bo; + int ret; + + if (vb->planes[0].dbuf) { + buf->dbuf = vb->planes[0].dbuf; + } else { + /* + * HAS_DMA is a Kconfig dependency so CONFIG_HAS_DMA is always + * defined when this driver is compiled. The #else branch is + * kept as a safeguard in case the dependency is ever removed. + */ +#ifdef CONFIG_HAS_DMA + buf->dbuf = vb2_vmalloc_memops.get_dmabuf(vb, mem_priv, 0); + if (IS_ERR_OR_NULL(buf->dbuf)) { + dev_err(dev, "fail to get dma buf\n"); + return -EINVAL; + } +#else + dev_err(dev, "get dmabuf fail -- CONFIG_HAS_DMA not defined\n"); + buf->dbuf = NULL; + return -EINVAL; +#endif + } + + /* create isp user BO and obtain gpu_addr */ + ret = isp_user_buffer_alloc(dev, buf->dbuf, &bo, &gpu_addr); + if (ret) { + dev_err(dev, "fail to create isp user BO\n"); + if (!vb->planes[0].dbuf) { + dma_buf_put(buf->dbuf); + buf->dbuf = NULL; + } + + return ret; + } + + buf->bo = bo; + buf->gpu_addr = gpu_addr; + return 0; +} + +static void isp4vid_qops_buf_cleanup(struct vb2_buffer *vb) +{ + struct isp4vid_capture_buffer *buf = + container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf); + + if (buf->bo) { + isp_user_buffer_free(buf->bo); + buf->bo = NULL; + } + + /* + * Only put dmabufs we obtained ourselves via get_dmabuf, not ones + * provided by the framework for DMABUF import + */ + if (buf->dbuf && buf->dbuf != vb->planes[0].dbuf) + dma_buf_put(buf->dbuf); + + buf->dbuf = NULL; +} + +static const struct vb2_ops isp4vid_qops = { + .queue_setup = isp4vid_qops_queue_setup, + .buf_init = isp4vid_qops_buf_init, + .buf_cleanup = isp4vid_qops_buf_cleanup, + .start_streaming = isp4vid_qops_start_streaming, + .stop_streaming = isp4vid_qops_stop_streaming, + .buf_queue = isp4vid_qops_buffer_queue, +}; + +int isp4vid_dev_init(struct isp4vid_dev *isp_vdev, struct v4l2_subdev *isp_sd) +{ + const char *vdev_name = isp4vid_video_dev_name; + struct v4l2_device *v4l2_dev; + struct video_device *vdev; + struct vb2_queue *q; + int ret; + + if (!isp_vdev || !isp_sd || !isp_sd->v4l2_dev) + return -EINVAL; + + v4l2_dev = isp_sd->v4l2_dev; + vdev = &isp_vdev->vdev; + + isp_vdev->isp_sdev = isp_sd; + isp_vdev->dev = v4l2_dev->dev; + + /* Initialize the vb2_queue struct */ + mutex_init(&isp_vdev->vbq_lock); + q = &isp_vdev->vbq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->buf_struct_size = sizeof(struct isp4vid_capture_buffer); + q->min_queued_buffers = 2; + q->ops = &isp4vid_qops; + q->drv_priv = isp_vdev; + q->mem_ops = &vb2_vmalloc_memops; + q->lock = &isp_vdev->vbq_lock; + q->dev = v4l2_dev->dev; + ret = vb2_queue_init(q); + if (ret) { + dev_err(v4l2_dev->dev, "vb2_queue_init error:%d\n", ret); + return ret; + } + + /* Initialize buffer list and its lock */ + mutex_init(&isp_vdev->buf_list_lock); + INIT_LIST_HEAD(&isp_vdev->buf_list); + + /* Set default frame format */ + isp_vdev->format = isp4vid_fmt_default; + isp_vdev->timeperframe = ISP4VID_ISP_TPF_DEFAULT; + v4l2_simplify_fraction(&isp_vdev->timeperframe.numerator, + &isp_vdev->timeperframe.denominator, 8, 333); + + ret = isp4vid_fill_buffer_size(&isp_vdev->format); + if (ret) { + dev_err(v4l2_dev->dev, "fail to fill buffer size: %d\n", ret); + goto err_release_vb2_queue; + } + + ret = isp4vid_set_fmt_2_isp(isp_sd, &isp_vdev->format); + if (ret) { + dev_err(v4l2_dev->dev, "fail init format :%d\n", ret); + goto err_release_vb2_queue; + } + + /* Initialize the video_device struct */ + isp_vdev->vdev.entity.name = vdev_name; + isp_vdev->vdev.entity.function = MEDIA_ENT_F_IO_V4L; + isp_vdev->vdev_pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&isp_vdev->vdev.entity, 1, + &isp_vdev->vdev_pad); + + if (ret) { + dev_err(v4l2_dev->dev, "init media entity pad fail:%d\n", ret); + goto err_release_vb2_queue; + } + + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | V4L2_CAP_IO_MC; + vdev->entity.ops = &isp4vid_vdev_ent_ops; + vdev->release = video_device_release_empty; + vdev->fops = &isp4vid_vdev_fops; + vdev->ioctl_ops = &isp4vid_vdev_ioctl_ops; + vdev->lock = NULL; + vdev->queue = q; + vdev->v4l2_dev = v4l2_dev; + vdev->vfl_dir = VFL_DIR_RX; + strscpy(vdev->name, vdev_name, sizeof(vdev->name)); + video_set_drvdata(vdev, isp_vdev); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(v4l2_dev->dev, "register video device fail:%d\n", ret); + goto err_entity_cleanup; + } + + return 0; + +err_entity_cleanup: + media_entity_cleanup(&isp_vdev->vdev.entity); +err_release_vb2_queue: + vb2_queue_release(q); + return ret; +} + +void isp4vid_dev_deinit(struct isp4vid_dev *isp_vdev) +{ + vb2_video_unregister_device(&isp_vdev->vdev); +} diff --git a/drivers/media/platform/amd/isp4/isp4_video.h b/drivers/media/platform/amd/isp4/isp4_video.h new file mode 100644 index 00000000000000..c66451e2616644 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_video.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_VIDEO_H_ +#define _ISP4_VIDEO_H_ + +#include +#include + +#include "isp4_interface.h" + +struct isp4vid_capture_buffer { + /* + * struct vb2_v4l2_buffer must be the first element + * the videobuf2 framework will allocate this struct based on + * buf_struct_size and use the first sizeof(struct vb2_buffer) bytes of + * memory as a vb2_buffer + */ + struct vb2_v4l2_buffer vb2; + struct isp4if_img_buf_info img_buf; + struct list_head list; + struct dma_buf *dbuf; + void *bo; + u64 gpu_addr; +}; + +struct isp4vid_dev { + struct video_device vdev; + struct media_pad vdev_pad; + struct v4l2_pix_format format; + + /* mutex that protects vbq */ + struct mutex vbq_lock; + struct vb2_queue vbq; + + /* mutex that protects buf_list */ + struct mutex buf_list_lock; + struct list_head buf_list; + + u32 sequence; + bool stream_started; + + struct device *dev; + struct v4l2_subdev *isp_sdev; + struct v4l2_fract timeperframe; +}; + +int isp4vid_dev_init(struct isp4vid_dev *isp_vdev, struct v4l2_subdev *isp_sd); + +void isp4vid_dev_deinit(struct isp4vid_dev *isp_vdev); + +void isp4vid_handle_frame_done(struct isp4vid_dev *isp_vdev, + const struct isp4if_img_buf_info *img_buf); + +#endif /* _ISP4_VIDEO_H_ */ diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index 888a2e213b0885..f1043fce13e273 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -811,6 +811,10 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) INIT_LIST_HEAD(&sd->asc_list); + ret = v4l2_subdev_get_privacy_led(sd); + if (ret < 0) + return ret; + /* * No reference taken. The reference is held by the device (struct * v4l2_subdev.dev), and async sub-device does not exist independently diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c index 77f3298821b5c6..10a3eb923fa2ee 100644 --- a/drivers/media/v4l2-core/v4l2-fwnode.c +++ b/drivers/media/v4l2-core/v4l2-fwnode.c @@ -1270,10 +1270,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) v4l2_async_subdev_nf_init(notifier, sd); - ret = v4l2_subdev_get_privacy_led(sd); - if (ret < 0) - goto out_cleanup; - ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); if (ret < 0) goto out_cleanup; diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 7192c9d1d268e9..b596ce54a6645c 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2402,6 +2402,17 @@ config MFD_QCOM_PM8008 under it in the device tree. Additional drivers must be enabled in order to use the functionality of the device. +config MFD_AAEON + tristate "AAEON WMI MFD devices" + depends on ASUS_WMI + help + Say yes here to support mltiple IO devices on Single Board Computers + produced by AAEON. + + This driver leverages the ASUS WMI interface to access device + resources. + + menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100 diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index e75e8045c28afa..438827883d8061 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -295,6 +295,7 @@ obj-$(CONFIG_MFD_LS2K_BMC_CORE) += ls2k-bmc-core.o obj-$(CONFIG_MFD_ATC260X) += atc260x-core.o obj-$(CONFIG_MFD_ATC260X_I2C) += atc260x-i2c.o +obj-$(CONFIG_MFD_AAEON) += mfd-aaeon.o obj-$(CONFIG_MFD_QNAP_MCU) += qnap-mcu.o diff --git a/drivers/mfd/mfd-aaeon.c b/drivers/mfd/mfd-aaeon.c new file mode 100644 index 00000000000000..de702c45a3b6dc --- /dev/null +++ b/drivers/mfd/mfd-aaeon.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * UP Board main platform driver and FPGA configuration support + * + * Copyright (c) 2021, AAEON Ltd. + * + * Author: Kunyang_Fan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AAEON_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" + +#define WMI_REPORT_CAPABILITY_METHOD 0x00000000 +#define MAX_BFPI_VERSION 255 +#define GET_REVISION_ID 0x00 + +struct aaeon_wmi_priv { + const struct mfd_cell *cells; + size_t ncells; +}; + +static const struct mfd_cell aaeon_mfd_cells[] = { + { .name = "gpio-aaeon" }, + { .name = "hwmon-aaeon"}, + { .name = "leds-aaeon"}, + { .name = "wdt-aaeon"}, +}; + +static const struct aaeon_wmi_priv aaeon_wmi_priv_data = { + .cells = aaeon_mfd_cells, + .ncells = ARRAY_SIZE(aaeon_mfd_cells), +}; + +static int aaeon_wmi_check_device(void) +{ + int err; + int retval; + + err = asus_wmi_evaluate_method(WMI_REPORT_CAPABILITY_METHOD, GET_REVISION_ID, 0, + &retval); + if (err) + return -ENODEV; + if (retval < 3 || retval > MAX_BFPI_VERSION) + return -ENODEV; + + return 0; +} + +static int aaeon_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct aaeon_wmi_priv *priv; + + if (!wmi_has_guid(AAEON_WMI_MGMT_GUID)) { + dev_info(&wdev->dev, "AAEON Management GUID not found\n"); + return -ENODEV; + } + + if (aaeon_wmi_check_device()) + return -ENODEV; + + priv = (struct aaeon_wmi_priv *)context; + dev_set_drvdata(&wdev->dev, priv); + + return devm_mfd_add_devices(&wdev->dev, 0, priv->cells, + priv->ncells, NULL, 0, NULL); +} + +static const struct wmi_device_id aaeon_wmi_id_table[] = { + { AAEON_WMI_MGMT_GUID, (void *)&aaeon_wmi_priv_data }, + {} +}; + +static struct wmi_driver aaeon_wmi_driver = { + .driver = { + .name = "mfd-aaeon", + }, + .id_table = aaeon_wmi_id_table, + .probe = aaeon_wmi_probe, +}; + +module_wmi_driver(aaeon_wmi_driver); + +MODULE_DEVICE_TABLE(wmi, aaeon_wmi_id_table); +MODULE_AUTHOR("Kunyang Fan "); +MODULE_DESCRIPTION("AAEON Board WMI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("ASUS_WMI"); diff --git a/drivers/mfd/simple-mfd-i2c.c b/drivers/mfd/simple-mfd-i2c.c index 7315fad618e445..52c81b18750e12 100644 --- a/drivers/mfd/simple-mfd-i2c.c +++ b/drivers/mfd/simple-mfd-i2c.c @@ -105,6 +105,7 @@ static const struct regmap_config spacemit_p1_regmap_config = { }; static const struct mfd_cell spacemit_p1_cells[] = { + { .name = "spacemit-p1-reboot", }, { .name = "spacemit-p1-regulator", }, { .name = "spacemit-p1-rtc", }, }; diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index f145e8e36cb3d0..513ddb26c11a7b 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -92,6 +92,7 @@ #define PCI_DEVICE_ID_INTEL_MEI_CDF 0x18D3 /* Cedar Fork */ #define PCI_DEVICE_ID_INTEL_MEI_ICP_LP 0x34E0 /* Ice Lake Point LP */ +#define PCI_DEVICE_ID_INTEL_MEI_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ #define PCI_DEVICE_ID_INTEL_MEI_ICP_N 0x38E0 /* Ice Lake Point N */ #define PCI_DEVICE_ID_INTEL_MEI_JSP_N 0x4DE0 /* Jasper Lake Point N */ diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 9efeafa8f1cae9..e5063149c47d3f 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -98,6 +98,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {PCI_DEVICE_DATA(INTEL, MEI_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, {PCI_DEVICE_DATA(INTEL, MEI_ICP_LP, MEI_ME_PCH12_CFG)}, + {PCI_DEVICE_DATA(INTEL, MEI_ICP_LP_3, MEI_ME_PCH12_CFG)}, {PCI_DEVICE_DATA(INTEL, MEI_ICP_N, MEI_ME_PCH12_CFG)}, {PCI_DEVICE_DATA(INTEL, MEI_TGP_LP, MEI_ME_PCH15_CFG)}, diff --git a/drivers/mmc/host/sdhci-of-k1.c b/drivers/mmc/host/sdhci-of-k1.c index 455656f9842df9..37b0911e7cf202 100644 --- a/drivers/mmc/host/sdhci-of-k1.c +++ b/drivers/mmc/host/sdhci-of-k1.c @@ -16,11 +16,19 @@ #include #include #include +#include #include #include "sdhci.h" #include "sdhci-pltfm.h" +#define SPACEMIT_SDHC_OP_EXT_REG 0x108 +#define SDHC_OVRRD_CLK_OEN BIT(11) +#define SDHC_FORCE_CLK_ON BIT(12) + +#define SPACEMIT_SDHC_LEGACY_CTRL_REG 0x10C +#define SDHC_GEN_PAD_CLK_ON BIT(6) + #define SPACEMIT_SDHC_MMC_CTRL_REG 0x114 #define SDHC_MISC_INT_EN BIT(1) #define SDHC_MISC_INT BIT(2) @@ -61,9 +69,34 @@ #define SDHC_PHY_DRIVE_SEL GENMASK(2, 0) #define SDHC_RX_BIAS_CTRL BIT(5) +#define SPACEMIT_SDHC_RX_CFG_REG 0x118 +#define SDHC_RX_SDCLK_SEL0_MASK GENMASK(1, 0) +#define SDHC_RX_SDCLK_SEL1_MASK GENMASK(3, 2) +#define SDHC_RX_SDCLK_SEL1 FIELD_PREP(SDHC_RX_SDCLK_SEL1_MASK, 1) + +#define SPACEMIT_SDHC_DLINE_CTRL_REG 0x130 +#define SDHC_DLINE_PU BIT(0) +#define SDHC_RX_DLINE_CODE_MASK GENMASK(23, 16) +#define SDHC_TX_DLINE_CODE_MASK GENMASK(31, 24) + +#define SPACEMIT_SDHC_DLINE_CFG_REG 0x134 +#define SDHC_RX_DLINE_REG_MASK GENMASK(7, 0) +#define SDHC_RX_DLINE_GAIN BIT(8) +#define SDHC_TX_DLINE_REG_MASK GENMASK(23, 16) + +#define SPACEMIT_RX_DLINE_REG 9 +#define SPACEMIT_RX_TUNE_DELAY_MIN 0x0 +#define SPACEMIT_RX_TUNE_DELAY_MAX 0xFF + +#define SPACEMIT_TX_TUNING_DLINE_REG 0x00 +#define SPACEMIT_TX_TUNING_DELAYCODE 127 + struct spacemit_sdhci_host { struct clk *clk_core; struct clk *clk_io; + struct pinctrl *pinctrl; + struct pinctrl_state *pinctrl_default; + struct pinctrl_state *pinctrl_uhs; }; /* All helper functions will update clr/set while preserve rest bits */ @@ -85,6 +118,50 @@ static inline void spacemit_sdhci_clrsetbits(struct sdhci_host *host, u32 clr, u sdhci_writel(host, val, reg); } +static void spacemit_sdhci_set_rx_delay(struct sdhci_host *host, u8 delay) +{ + spacemit_sdhci_clrsetbits(host, SDHC_RX_DLINE_CODE_MASK, + FIELD_PREP(SDHC_RX_DLINE_CODE_MASK, delay), + SPACEMIT_SDHC_DLINE_CTRL_REG); +} + +static void spacemit_sdhci_set_tx_delay(struct sdhci_host *host, u8 delay) +{ + spacemit_sdhci_clrsetbits(host, SDHC_TX_DLINE_CODE_MASK, + FIELD_PREP(SDHC_TX_DLINE_CODE_MASK, delay), + SPACEMIT_SDHC_DLINE_CTRL_REG); +} + +static void spacemit_sdhci_set_tx_dline_reg(struct sdhci_host *host, u8 dline_reg) +{ + spacemit_sdhci_clrsetbits(host, SDHC_TX_DLINE_REG_MASK, + FIELD_PREP(SDHC_TX_DLINE_REG_MASK, dline_reg), + SPACEMIT_SDHC_DLINE_CFG_REG); +} + +static void spacemit_sdhci_tx_tuning_prepare(struct sdhci_host *host) +{ + spacemit_sdhci_setbits(host, SDHC_TX_MUX_SEL, SPACEMIT_SDHC_TX_CFG_REG); + spacemit_sdhci_setbits(host, SDHC_DLINE_PU, SPACEMIT_SDHC_DLINE_CTRL_REG); + udelay(5); +} + +static void spacemit_sdhci_prepare_tuning(struct sdhci_host *host) +{ + spacemit_sdhci_clrsetbits(host, SDHC_RX_DLINE_REG_MASK, + FIELD_PREP(SDHC_RX_DLINE_REG_MASK, SPACEMIT_RX_DLINE_REG), + SPACEMIT_SDHC_DLINE_CFG_REG); + + spacemit_sdhci_setbits(host, SDHC_DLINE_PU, SPACEMIT_SDHC_DLINE_CTRL_REG); + udelay(5); + + spacemit_sdhci_clrsetbits(host, SDHC_RX_SDCLK_SEL1_MASK, SDHC_RX_SDCLK_SEL1, + SPACEMIT_SDHC_RX_CFG_REG); + + if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200) + spacemit_sdhci_setbits(host, SDHC_HS200_USE_RFIFO, SPACEMIT_SDHC_PHY_FUNC_REG); +} + static void spacemit_sdhci_reset(struct sdhci_host *host, u8 mask) { sdhci_reset(host, mask); @@ -101,6 +178,12 @@ static void spacemit_sdhci_reset(struct sdhci_host *host, u8 mask) if (!(host->mmc->caps2 & MMC_CAP2_NO_MMC)) spacemit_sdhci_setbits(host, SDHC_MMC_CARD_MODE, SPACEMIT_SDHC_MMC_CTRL_REG); + + spacemit_sdhci_setbits(host, SDHC_GEN_PAD_CLK_ON, SPACEMIT_SDHC_LEGACY_CTRL_REG); + + if (host->mmc->caps2 & MMC_CAP2_NO_MMC) + spacemit_sdhci_setbits(host, SDHC_OVRRD_CLK_OEN | SDHC_FORCE_CLK_ON, + SPACEMIT_SDHC_OP_EXT_REG); } static void spacemit_sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned int timing) @@ -174,6 +257,111 @@ static unsigned int spacemit_sdhci_clk_get_max_clock(struct sdhci_host *host) return clk_get_rate(pltfm_host->clk); } +static int spacemit_sdhci_execute_tuning(struct sdhci_host *host, u32 opcode) +{ + int current_len = 0, current_start = 0; + int max_pass_len = 0, max_pass_start = 0; + struct mmc_host *mmc = host->mmc; + struct mmc_ios ios = mmc->ios; + u8 final_delay; + int ret = 0; + int i; + + /* + * Tuning is required for SDR50/SDR104, HS200/HS400 cards and + * if clock frequency is greater than 100MHz in these modes. + */ + if (host->clock < 100 * 1000 * 1000 || + !(ios.timing == MMC_TIMING_MMC_HS200 || + ios.timing == MMC_TIMING_UHS_SDR50 || + ios.timing == MMC_TIMING_UHS_SDR104)) + return 0; + + if (mmc->caps2 & MMC_CAP2_NO_MMC) { + spacemit_sdhci_set_tx_dline_reg(host, SPACEMIT_TX_TUNING_DLINE_REG); + spacemit_sdhci_set_tx_delay(host, SPACEMIT_TX_TUNING_DELAYCODE); + spacemit_sdhci_tx_tuning_prepare(host); + + dev_dbg(mmc_dev(host->mmc), "TX tuning: dline_reg=%d, delaycode=%d\n", + SPACEMIT_TX_TUNING_DLINE_REG, SPACEMIT_TX_TUNING_DELAYCODE); + } + + spacemit_sdhci_prepare_tuning(host); + + for (i = SPACEMIT_RX_TUNE_DELAY_MIN; i <= SPACEMIT_RX_TUNE_DELAY_MAX; i++) { + spacemit_sdhci_set_rx_delay(host, i); + ret = mmc_send_tuning(host->mmc, opcode, NULL); + + dev_dbg(mmc_dev(host->mmc), "RX delay %d: %s\n", + i, ret == 0 ? "pass" : "fail"); + + if (ret == 0) { + /* Test passed - extend current window */ + if (current_len == 0) + current_start = i; + current_len++; + } else { + /* Test failed - check if current window is best so far */ + if (current_len > max_pass_len) { + max_pass_len = current_len; + max_pass_start = current_start; + } + current_len = 0; + } + } + + if (current_len > max_pass_len) { + max_pass_len = current_len; + max_pass_start = current_start; + } + + if (max_pass_len < 3) { + dev_err(mmc_dev(host->mmc), "Tuning failed: no stable window found\n"); + return -EIO; + } + + final_delay = max_pass_start + max_pass_len / 2; + spacemit_sdhci_set_rx_delay(host, final_delay); + ret = mmc_send_tuning(host->mmc, opcode, NULL); + if (ret) { + u8 retry_delays[] = { + max_pass_start + max_pass_len / 4, + max_pass_start + (3 * max_pass_len) / 4, + max_pass_start, + max_pass_start + max_pass_len - 1 + }; + int retry_count = ARRAY_SIZE(retry_delays); + + dev_warn(mmc_dev(mmc), "Primary delay %d failed, trying alternatives\n", + final_delay); + + for (i = 0; i < retry_count; i++) { + if (retry_delays[i] >= SPACEMIT_RX_TUNE_DELAY_MIN && + retry_delays[i] <= SPACEMIT_RX_TUNE_DELAY_MAX) { + spacemit_sdhci_set_rx_delay(host, retry_delays[i]); + ret = mmc_send_tuning(host->mmc, opcode, NULL); + if (!ret) { + final_delay = retry_delays[i]; + dev_info(mmc_dev(mmc), "Retry successful with delay %d\n", + final_delay); + break; + } + } + } + + if (ret) { + dev_err(mmc_dev(mmc), "All retry attempts failed\n"); + return -EIO; + } + } + + dev_dbg(mmc_dev(host->mmc), + "Tuning successful: window %d-%d, using delay %d\n", + max_pass_start, max_pass_start + max_pass_len - 1, final_delay); + + return 0; +} + static int spacemit_sdhci_pre_select_hs400(struct mmc_host *mmc) { struct sdhci_host *host = mmc_priv(mmc); @@ -206,6 +394,46 @@ static void spacemit_sdhci_pre_hs400_to_hs200(struct mmc_host *mmc) SPACEMIT_SDHC_PHY_CTRL_REG); } +static int spacemit_sdhci_start_signal_voltage_switch(struct mmc_host *mmc, + struct mmc_ios *ios) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct spacemit_sdhci_host *sdhst = sdhci_pltfm_priv(pltfm_host); + struct pinctrl_state *state; + int ret; + + ret = sdhci_start_signal_voltage_switch(mmc, ios); + if (ret) + return ret; + + if (!sdhst->pinctrl) + return 0; + + /* Select appropriate pinctrl state based on signal voltage */ + switch (ios->signal_voltage) { + case MMC_SIGNAL_VOLTAGE_330: + state = sdhst->pinctrl_default; + break; + case MMC_SIGNAL_VOLTAGE_180: + state = sdhst->pinctrl_uhs; + break; + default: + dev_warn(mmc_dev(mmc), "unsupported voltage %d\n", ios->signal_voltage); + return 0; + } + + ret = pinctrl_select_state(sdhst->pinctrl, state); + if (ret) { + dev_warn(mmc_dev(mmc), "failed to select pinctrl state: %d\n", ret); + return 0; + } + dev_dbg(mmc_dev(mmc), "switched to %s pinctrl state\n", + ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180 ? "UHS" : "default"); + + return 0; +} + static inline int spacemit_sdhci_get_clocks(struct device *dev, struct sdhci_pltfm_host *pltfm_host) { @@ -239,12 +467,37 @@ static inline int spacemit_sdhci_get_resets(struct device *dev) return 0; } +static inline void spacemit_sdhci_get_pins(struct device *dev, + struct sdhci_pltfm_host *pltfm_host) +{ + struct spacemit_sdhci_host *sdhst = sdhci_pltfm_priv(pltfm_host); + + sdhst->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(sdhst->pinctrl)) { + sdhst->pinctrl = NULL; + dev_dbg(dev, "pinctrl not available, voltage switching will work without it\n"); + return; + } + + sdhst->pinctrl_default = pinctrl_lookup_state(sdhst->pinctrl, "default"); + if (IS_ERR(sdhst->pinctrl_default)) + sdhst->pinctrl_default = NULL; + + sdhst->pinctrl_uhs = pinctrl_lookup_state(sdhst->pinctrl, "uhs"); + if (IS_ERR(sdhst->pinctrl_uhs)) + sdhst->pinctrl_uhs = NULL; + + dev_dbg(dev, "pinctrl setup: default=%p, uhs=%p\n", + sdhst->pinctrl_default, sdhst->pinctrl_uhs); +} + static const struct sdhci_ops spacemit_sdhci_ops = { .get_max_clock = spacemit_sdhci_clk_get_max_clock, .reset = spacemit_sdhci_reset, .set_bus_width = sdhci_set_bus_width, .set_clock = spacemit_sdhci_set_clock, .set_uhs_signaling = spacemit_sdhci_set_uhs_signaling, + .platform_execute_tuning = spacemit_sdhci_execute_tuning, }; static const struct sdhci_pltfm_data spacemit_sdhci_k1_pdata = { @@ -311,6 +564,10 @@ static int spacemit_sdhci_probe(struct platform_device *pdev) host->mmc->caps |= MMC_CAP_NEED_RSP_BUSY; + spacemit_sdhci_get_pins(dev, pltfm_host); + + host->mmc_host_ops.start_signal_voltage_switch = spacemit_sdhci_start_signal_voltage_switch; + ret = spacemit_sdhci_get_clocks(dev, pltfm_host); if (ret) goto err_pltfm; diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index e15e320db47636..f367517ede0d13 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -189,6 +189,17 @@ config CAN_KVASER_PCIEFD Kvaser M.2 PCIe 4xCAN Kvaser PCIe 8xCAN +config CAN_LOONGSON_CANFD + tristate "Loongson CAN-FD controller" + depends on HAS_IOMEM && (LOONGSON2_APB_CMC_DMA || COMPILE_TEST) + select REGMAP_MMIO + help + This is a canfd driver switch for the Loongson platform, + integrated with the Loongson-2K series SoCs. + + To compile as a module, choose M here: the module will be + called loongson_canfd. + config CAN_SLCAN tristate "Serial / USB serial CAN Adaptors (slcan)" depends on TTY diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index d7bc10a6b8eae2..1ce78443d7102b 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_CAN_GRCAN) += grcan.o obj-$(CONFIG_CAN_IFI_CANFD) += ifi_canfd/ obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o obj-$(CONFIG_CAN_KVASER_PCIEFD) += kvaser_pciefd/ +obj-$(CONFIG_CAN_LOONGSON_CANFD) += loongson_canfd.o obj-$(CONFIG_CAN_MSCAN) += mscan/ obj-$(CONFIG_CAN_M_CAN) += m_can/ obj-$(CONFIG_CAN_PEAK_PCIEFD) += peak_canfd/ diff --git a/drivers/net/can/loongson_canfd.c b/drivers/net/can/loongson_canfd.c new file mode 100644 index 00000000000000..a4b5d9e5067655 --- /dev/null +++ b/drivers/net/can/loongson_canfd.c @@ -0,0 +1,1859 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * LOONGSON CANFD controller + * + * Copyright (C) 2024-2026 Loongson Technology Corporation Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOONGSON_CANFD_DEVICE_ID 0x0 /* CANFD controller ID Register */ +#define LOONGSON_CANFD_MODE 0x4 /* Mode Configuration Register */ +#define LOONGSON_CANFD_CONF 0x8 /* Configure Register */ +#define LOONGSON_CANFD_STAT 0xc /* Status Register */ +#define LOONGSON_CANFD_CMD 0x10 /* Command Register */ +#define LOONGSON_CANFD_INT_STAT 0x14 /* Interrupt Status Register */ +#define LOONGSON_CANFD_INT_ENA 0x18 /* Interrupt Enable Register */ +#define LOONGSON_CANFD_INT_MASK 0x1c /* Interrupt Mask Register */ +#define LOONGSON_CANFD_BTR_NORM 0x20 /* Normal Rate Configuration Register */ +#define LOONGSON_CANFD_BTR_FD 0x24 /* FD Data Rate Configuration Register */ +#define LOONGSON_CANFD_ERL 0x28 /* Error Threshold Configuration Register */ +#define LOONGSON_CANFD_FSTAT 0x2c /* Error Status Register */ +#define LOONGSON_CANFD_ERC 0x30 /* Error Count Register */ +#define LOONGSON_CANFD_BRE 0x34 /* Rate Error Count Register */ +#define LOONGSON_CANFD_CTR_PRES 0x38 /* Error Count Debug Register */ +#define LOONGSON_CANFD_ERR_CAPT 0x3c /* Error Capture Status Register */ +#define LOONGSON_CANFD_RETX_CNT 0x40 /* Retransmission Count Register */ +#define LOONGSON_CANFD_ALC 0x44 /* Lost Arbitration Capture Register */ +#define LOONGSON_CANFD_TRV_DLY 0x48 /* Transmission Delay Measurement Register */ +#define LOONGSON_CANFD_SSP_CFG 0x4c /* Second Sampling Point Configuration Register */ +#define LOONGSON_CANFD_RX_FR_CNT 0x50 /* Receive Message Count Register */ +#define LOONGSON_CANFD_TX_FR_CNT 0x54 /* Transmit Message Count Register */ +#define LOONGSON_CANFD_DEBUG 0x58 /* Debug Register */ +#define LOONGSON_CANFD_TS 0x5c /* Timestamp Register */ +#define LOONGSON_CANFD_TX_FRM_TST 0x60 /* Transmit Message Debug Register */ +#define LOONGSON_CANFD_FRC_DIV 0x64 /* Fractional Divider Ratio Register */ +#define LOONGSON_CANFD_FLT_A_MASK 0x68 /* Filter A Mask Register */ +#define LOONGSON_CANFD_FLT_A_VAL 0x6c /* Filter A value Register */ +#define LOONGSON_CANFD_FLT_B_MASK 0x70 /* Filter B Mask Register */ +#define LOONGSON_CANFD_FLT_B_VAL 0x74 /* Filter B value Register */ +#define LOONGSON_CANFD_FLT_C_MASK 0x78 /* Filter C Mask Register */ +#define LOONGSON_CANFD_FLT_C_VAL 0x7c /* Filter C value Register */ +#define LOONGSON_CANFD_FLT_R_LOW 0x80 /* Range Filter Low Threshold Register */ +#define LOONGSON_CANFD_FLT_R_HI 0x84 /* Range Filter High Threshold Register */ +#define LOONGSON_CANFD_FLT_CTRL 0x88 /* Filter Control Register */ +#define LOONGSON_CANFD_RX_MEM_INFO 0x8c /* Receive Buffer Information Register */ +#define LOONGSON_CANFD_RX_PRT 0x90 /* Receive Buffer Pointer Register */ +#define LOONGSON_CANFD_RX_STAT 0x94 /* Receive Buffer Status Register */ +#define LOONGSON_CANFD_RX_DATA 0x98 /* Receive Data Register */ +#define LOONGSON_CANFD_TX_STAT 0x9c /* Transmit Buffer Status Register */ +#define LOONGSON_CANFD_TX_CMD 0xa0 /* Transmit Command Register */ +#define LOONGSON_CANFD_TX_SEL 0xa4 /* Transmit Buffer Selection Register */ +#define LOONGSON_CANFD_TX_DATA_1 0xb0 +#define LOONGSON_CANFD_TX_DATA_18 0xf4 + +/* Bitfields of CANFD controller ID register */ +#define REG_ID_MASK GENMASK(15, 0) +#define REG_ID_VER_MIN GENMASK(23, 16) +#define REG_ID_VER_MAJ GENMASK(31, 24) + +/* Bitfields of Mode Configuration register */ +#define REG_MODE_RST BIT(0) /* Soft Reset */ +#define REG_MODE_BMM BIT(1) /* Bus Monitoring Mode */ +#define REG_MODE_STM BIT(2) /* Self-Test Mode */ +#define REG_MODE_AFM BIT(3) /* Receive Filter Mode */ +#define REG_MODE_FDE BIT(4) /* CANFD Enabled */ +#define REG_MODE_TTTM BIT(5) /* Timed Transmission Mode */ +#define REG_MODE_ROM BIT(6) /* Restricted Operation Mode */ +#define REG_MODE_ACF BIT(7) /* No ACK Mode*/ +#define REG_MODE_TSTM BIT(8) /* Test Mode */ +#define REG_MODE_RXBAM BIT(9) /* Receive Buffer Auto Mode */ +#define REG_MODE_ITSM BIT(10) /* Internal Timestamp Mode */ +#define REG_MODE_RTSOP BIT(12) /* Receive Buffer Timestamp Addition Mode */ +#define REG_MODE_BUFM BIT(13) /* Transmit Buffer Mode */ + +/* Bitfields of Configure register */ +#define REG_CONF_RTRLE BIT(0) /* Retransmission Threshold Enable */ +#define REG_CONF_RTRTH GENMASK(4, 1) /* Retransmission Threshold */ +#define REG_CONF_ILBP BIT(5) /* Internal Loopback Mode */ +#define REG_CONF_ENA BIT(6) /* Controller Enable */ +#define REG_CONF_NISOFD BIT(7) /* NON-ISO CANFD Protocol Enable */ +#define REG_CONF_PEX BIT(8) /* Protocol Exception Handling */ +#define REG_CONF_FDRF BIT(10) /* Ignore Remote Frame Enable */ + +/* Bitfields of Status register */ +#define REG_STAT_RXNE BIT(0) /* Receive Buffer Not Empty Status */ +#define REG_STAT_DOR BIT(1) /* Receive Data Overflow Status */ +#define REG_STAT_EFT BIT(3) /* Error Frame Flag */ +#define REG_STAT_RXS BIT(4) /* Receive Status Flag */ +#define REG_STAT_TXS BIT(5) /* Transmission Status Flag */ +#define REG_STAT_EWL BIT(6) /* Transmission Error Threshold Flag */ +#define REG_STAT_IDLE BIT(7) /* Bus Idle Flag */ +#define REG_STAT_PEXS BIT(8) /* Protocol Exception Status */ +#define REG_STAT_STCNT BIT(16) /* Transmission Count Support */ + +/* Bitfields of Command register */ +#define REG_CMD_RXRPMV BIT(1) /* Receive Buffer Read-to-Move */ +#define REG_CMD_RRB BIT(2) /* Receive Buffer Release */ +#define REG_CMD_CDO BIT(3) /* Received Data Overflow Status Clear */ +#define REG_CMD_ERCRST BIT(4) /* Error Count Clear */ +#define REG_CMD_RXFCRST BIT(5) /* Receive Frame Count Clear */ +#define REG_CMD_TXFCRST BIT(6) /* Transmit Frame Count Clear */ +#define REG_CMD_CPEXS BIT(7) /* Exception Status Clear */ + +/* Bitfields of Interrupt Status register */ +#define REG_INT_STAT_RXI BIT(0) +#define REG_INT_STAT_TXI BIT(1) +#define REG_INT_STAT_EWLI BIT(2) +#define REG_INT_STAT_DOI BIT(3) +#define REG_INT_STAT_FCSI BIT(4) +#define REG_INT_STAT_ALI BIT(5) +#define REG_INT_STAT_BEI BIT(6) +#define REG_INT_STAT_RXFI BIT(7) +#define REG_INT_STAT_BSI BIT(8) +#define REG_INT_STAT_RBNEI BIT(9) +#define REG_INT_STAT_TXBHCI BIT(10) +#define REG_INT_STAT_OFI BIT(11) +#define REG_INT_STAT_DMADI BIT(12) + +#define REG_INT_STAT_ERRORI (REG_INT_STAT_EWLI | REG_INT_STAT_FCSI |\ + REG_INT_STAT_ALI | REG_INT_STAT_BEI) + +/* Bitfields of Interrupt Enable register */ +#define REG_INT_ENA_CLR GENMASK(28, 16) +#define REG_INT_ENA_SET GENMASK(12, 0) + +/* Bitfields of Interrupt Mask register */ +#define REG_INT_MASK_SET GENMASK(12, 0) +#define REG_INT_MASK_CLR GENMASK(28, 16) + +/* Bitfields of Normal Rate Configuration register */ +#define REG_BTR_PROP GENMASK(6, 0) +#define REG_BTR_PH1 GENMASK(12, 7) +#define REG_BTR_PH2 GENMASK(18, 13) +#define REG_BTR_BRP GENMASK(22, 19) +#define REG_BTR_SJW GENMASK(31, 27) + +/* Bitfields of FD Data Rate Configuration register */ +#define REG_BTR_FD_PROP GENMASK(6, 0) +#define REG_BTR_FD_PH1 GENMASK(11, 7) +#define REG_BTR_FD_PH2 GENMASK(17, 13) +#define REG_BTR_FD_BRP GENMASK(26, 19) +#define REG_BTR_FD_SJW GENMASK(31, 27) + +/* Bitfields of Error Threshold Configuration register */ +#define REG_ERL_ERP GENMASK(7, 0) +#define REG_ERL_EW GENMASK(23, 16) + +/* Bitfields of Error Status register */ +#define REG_FSTAT_ERA BIT(0) +#define REG_FSTAT_ERP BIT(1) +#define REG_FSTAT_BOF BIT(2) + +#define REG_FSTAT_MASK GENMASK(2, 0) + +/* Bitfields of Error Count register */ +#define REG_ERC_TEC GENMASK(8, 0) +#define REG_ERC_REC GENMASK(24, 16) + +/* Bitfields of Rate Error Count register */ +#define REG_BRE_NORM GENMASK(15, 0) +#define REG_BRE_FD_DATA GENMASK(31, 16) + +/* Bitfields of Error Count Debug register */ +#define REG_CTR_PRES_CTPV GENMASK(8, 0) +#define REG_CTR_PRES_PTX BIT(9) +#define REG_CTR_PRES_PRX BIT(10) + +/* Bitfields of Error Capture Status register */ +#define REG_ERR_CAPT_POS GENMASK(4, 0) +#define REG_ERR_CAPT_TYPE GENMASK(7, 5) + +/* Bitfields of Retransmission Count register */ +#define REG_RETX_CNT_VAL GENMASK(3, 0) + +/* Bitfields of Lost Arbitration Capture register */ +#define REG_ALC_BIT_POS GENMASK(4, 0) +#define REG_ALC_ID_FIELD GENMASK(7, 5) + +/* Bitfields of Transmission Delay Measurement register */ +#define REG_TRV_DLY_VAL GENMASK(6, 0) + +/* Bitfields of Second Sampling Point Configuration register */ +#define REG_SSP_CFG_OFF GENMASK(7, 0) +#define REG_SSP_CFG_SRC GENMASK(9, 8) +#define REG_SSP_CFG_SAT BIT(10) + +/* Bitfields of Receive Message Count register */ +#define REG_RX_FR_CNT_VAL GENMASK(31, 0) + +/* Bitfields of Transmit Message Count register */ +#define REG_TX_FR_CNT_VAL GENMASK(31, 0) + +/* Bitfields of Debug register */ +#define REG_DEBUG_STF_CNT GENMASK(2, 0) +#define REG_DEBUG_DSTF_CNT GENMASK(5, 3) +#define REG_DEBUG_PC_ARB BIT(6) +#define REG_DEBUG_PC_CON BIT(7) +#define REG_DEBUG_PC_DAT BIT(8) +#define REG_DEBUG_PC_STC BIT(9) +#define REG_DEBUG_PC_CRC BIT(10) +#define REG_DEBUG_PC_CRCD BIT(11) +#define REG_DEBUG_PC_ACK BIT(12) +#define REG_DEBUG_PC_ACKD BIT(13) +#define REG_DEBUG_PC_EOF BIT(14) +#define REG_DEBUG_PC_INT BIT(15) +#define REG_DEBUG_PC_SUSP BIT(16) +#define REG_DEBUG_PC_OVR BIT(17) +#define REG_DEBUG_PC_SOF BIT(18) + +/* Bitfields of Timestamp register */ +#define REG_TS_TIMESTAMP GENMASK(15, 0) +#define REG_TS_PSC GENMASK(24, 16) + +/* Bitfields of Fractional Divider Ratio register */ +#define REG_FRC_FRC_DBT GENMASK(15, 8) +#define REG_FRC_FRC_NBT GENMASK(7, 0) + +/* Bitfields of Filter A Mask register */ +#define REG_FIL_A_MASK GENMASK(28, 0) + +/* Bitfields of Filter A value register */ +#define REG_FIL_A_VAL GENMASK(28, 0) + +/* Bitfields of Filter B Mask register */ +#define REG_FIL_B_MASK GENMASK(28, 0) + +/* Bitfields of Filter B value register */ +#define REG_FIL_B_VAL GENMASK(28, 0) + +/* Bitfields of Filter C Mask register */ +#define REG_FIL_C_MASK GENMASK(28, 0) + +/* Bitfields of Filter C value register */ +#define REG_FIL_C_VAL GENMASK(28, 0) + +/* Bitfields of Range Filter Low Threshold register */ +#define REG_FIL_R_LOW_VAL GENMASK(28, 0) + +/* Bitfields of Range Filter High Threshold register */ +#define REG_FIL_R_HI_VAL GENMASK(28, 0) + +/* Bitfields of Filter Control register */ +#define REG_FIL_CTRL_FANB BIT(0) +#define REG_FIL_CTRL_FANE BIT(1) +#define REG_FIL_CTRL_FAFB BIT(2) +#define REG_FIL_CTRL_FAFE BIT(3) +#define REG_FIL_CTRL_FBNB BIT(4) +#define REG_FIL_CTRL_FBNE BIT(5) +#define REG_FIL_CTRL_FBFB BIT(6) +#define REG_FIL_CTRL_FBFE BIT(7) +#define REG_FIL_CTRL_FCNB BIT(8) +#define REG_FIL_CTRL_FCNE BIT(9) +#define REG_FIL_CTRL_FCFB BIT(10) +#define REG_FIL_CTRL_FCFE BIT(11) +#define REG_FIL_CTRL_FRNB BIT(12) +#define REG_FIL_CTRL_FRNE BIT(13) +#define REG_FIL_CTRL_FRFB BIT(14) +#define REG_FIL_CTRL_FRFE BIT(15) +#define REG_FIL_CTRL_SFA BIT(16) +#define REG_FIL_CTRL_SFB BIT(17) +#define REG_FIL_CTRL_SFC BIT(18) +#define REG_FIL_CTRL_SFR BIT(19) + +/* Bitfields of Receive Buffer Information register */ +#define REG_RX_MEM_INFO_BUFF_SIZE GENMASK(12, 0) +#define REG_RX_MEM_INFO_MEM_FREE GENMASK(28, 16) + +/* Bitfields of Receive Buffer Pointer register */ +#define REG_RX_PTR_WPP GENMASK(11, 0) +#define REG_RX_PTR_RPP GENMASK(27, 16) + +/* Bitfields of Receive Buffer Status register */ +#define REG_RX_STAT_RXE BIT(0) +#define REG_RX_STAT_RXF BIT(1) +#define REG_RX_STAT_RXMOF BIT(2) +#define REG_RX_STAT_RXFRC GENMASK(14, 4) +#define REG_RX_STAT_RTSOP BIT(16) + +/* Bitfields of Receive Data register */ +#define REG_RX_DATA_VAL GENMASK(31, 0) + +/* Bitfields of Transmit Buffer Status register */ +#define REG_TX_STAT_BRP GENMASK(7, 0) +#define REG_TX_STAT_TXS GENMASK(10, 8) +#define REG_TX_STAT_BS GENMASK(31, 16) + +#define REG_TX_STAT_BS_TX_MASK_BASE GENMASK(17, 16) + +/* Bitfields of Transmit Command register */ +#define REG_TX_CMD_BAR GENMASK(7, 0) +#define REG_TX_CMD_BCR GENMASK(15, 8) +#define REG_TX_CMD_BSC GENMASK(23, 16) + +/* Bitfields of Transmit Buffer Selection register */ +#define REG_TX_SEL_BUF_SEL GENMASK(3, 0) +#define REG_TX_SEL_BUF_CNT GENMASK(7, 4) + +/* Loongson CANFD Frame format */ +#define LOONGSON_CANFD_FRAME_META0 0x0 +#define LOONGSON_CANFD_FRAME_META1 0x4 +#define LOONGSON_CANFD_FRAME_DB_1 0x8 + +/* Bitfields of FRAME META0 */ +#define REG_FRAME_META0_ID_EXT GENMASK(17, 0) +#define REG_FRAME_META0_ID_BASE GENMASK(28, 18) +#define REG_FRAME_META0_RTR BIT(29) /* Remote Transmission Request, only in CAN2.0 */ +/* Extended Identifier Type. 1: 29 bits ID, 0: 11 bits ID. */ +#define REG_FRAME_META0_XDT BIT(30) +#define REG_FRAME_META0_ESI BIT(31) /* Error State Indicator, only in CAN-FD */ + +/* Bitfields of FRAME META1 */ +#define REG_FRAME_META1_TIMESTAMP GENMASK(15, 0) +#define REG_FRAME_META1_DLC GENMASK(19, 16) /* Data Length Code */ +#define REG_FRAME_META1_BRS BIT(20) /* Bit Rate Shift, Only in CANFD */ +/* Flexible Data-rate Format. 1: CANFD, 0: CAN2.0 */ +#define REG_FRAME_META1_FDF BIT(21) +#define REG_FRAME_META1_RWCNT GENMASK(28, 24) /* Read Word Counter */ + +/* Bitfields of FRAME TEST */ +#define REG_FRAME_TEST_FSTC BIT(0) +#define REG_FRAME_TEST_FCRC BIT(1) +#define REG_FRAME_TEST_SDLC BIT(2) +#define REG_FRAME_TEST_TPRM GENMASK(12, 8) + +#define DEV_NAME "loongson_canfd" +#define LOONGSON_CANFD_ID 0xBABE +#define LOONGSON_CANFD_DW_BYTE 4 +#define LOONGSON_CANFD_RXBUF_SZ SZ_1K +#define LOONGSON_CANFD_TXBUF_NUM 8 +#define LOONGSON_CANFD_MAX_RTXTH 0xf +#define LOONGSON_CANFD_RXDMA_NUM (LOONGSON_CANFD_RXBUF_SZ / DMA_SLAVE_BUSWIDTH_4_BYTES) + +/** + * struct loongson_canfd_priv - This definition define CAN driver instance + * @can: CAN private data structure. + * @napi: NAPI structure + * @regmap: regmap of the CAN device + * @res: Pointer to the CAN device respurce + * @rx_ch: CAN DMA rx channel + * @rx_dma_buf: CAN DMA rx buffer bus address + * @rx_buf: CAN DMA rx buffer cpu address + * @last_res: Last rx data in DMA route + * @tx_lock: Lock for synchronizing TX interrupt handling + * @get_rx_data: Callback of reading CAN rx data + * @get_rxbuf_empty: Callback of gets the RX buffer is empty in dma mode + */ +struct loongson_canfd_priv { + struct can_priv can; /* must be first member! */ + struct napi_struct napi; + struct regmap *regmap; + struct resource *res; + struct dma_chan *rx_ch; + dma_addr_t rx_dma_buf; /* dma rx buffer bus address */ + unsigned int *rx_buf; /* dma rx buffer cpu address */ + u16 last_res; + spinlock_t tx_lock; /* protect the sending queue */ + u32 (*get_rx_data)(struct loongson_canfd_priv *priv); + bool (*get_rxbuf_empty)(struct loongson_canfd_priv *priv); +}; + +/** + * enum loongson_canfd_txbuf_sts - status of TX buffer + * @TX_BS_IDLE: Status processed or not sent. + * @TX_BS_VALID: Sending successful. + * @TX_BS_FAIL: Sending failed. + * @TX_BS_CANCEL: Sending cancelled. + */ +enum loongson_canfd_txbuf_sts { + TX_BS_IDLE = 0x0, + TX_BS_VALID = 0x1, + TX_BS_FAIL = 0x2, + TX_BS_CANCEL = 0x3 +}; + +/** + * enum loongson_canfd_txbuf_cmd - command of TX buffer + * @TXT_CMD_ADD: Buffer add request. + * @TXT_CMD_CANCEL: Buffer cancel request. + * @TXT_CMD_SR_CLEAN: Buffer send record clear. + * 1: to clear the send completion record (LOONGSON_CANFD_TX_STAT[BS]). + */ +enum loongson_canfd_txbuf_cmd { + TXT_CMD_ADD = 0x01, + TXT_CMD_CANCEL = 0x02, + TXT_CMD_SR_CLEAN = 0x03 +}; + +static const struct can_bittiming_const loongson_canfd_bit_timing = { + .name = DEV_NAME, + .tseg1_min = 2, + .tseg1_max = 190, + .tseg2_min = 2, + .tseg2_max = 63, + .sjw_max = 31, + .brp_min = 1, + .brp_max = 15, + .brp_inc = 1, +}; + +static const struct can_bittiming_const loongson_canfd_bit_timing_data = { + .name = DEV_NAME, + .tseg1_min = 2, + .tseg1_max = 190, + .tseg2_min = 2, + .tseg2_max = 63, + .sjw_max = 31, + .brp_min = 1, + .brp_max = 255, + .brp_inc = 1, +}; + +/* CAN FD Transmission Delay Compensation constants */ +static const struct can_tdc_const loongson_canfd_tdc_data = { + .tdcv_min = 0, + .tdcv_max = 0, + .tdco_min = 1, + .tdco_max = 127, + .tdcf_min = 0, /* Filter window not supported */ + .tdcf_max = 0, +}; + +/** + * loongson_canfd_enabled() - Gets the controller is enabled + * @priv: Pointer to private data + * + * Return: True - the controller is enabled. + * False - the controller is disabled. + */ +static bool loongson_canfd_enabled(struct loongson_canfd_priv *priv) +{ + return !!regmap_test_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA); +} + +/** + * loongson_canfd_txbuf_freed() - Gets the flag for TX buffer to be sent + * @priv: Pointer to private data + * + * Return: True - TX buffer is empty. + * False - TX buffer is processing + */ +static bool loongson_canfd_txbuf_freed(struct loongson_canfd_priv *priv) +{ + return !regmap_test_bits(priv->regmap, LOONGSON_CANFD_TX_STAT, REG_TX_STAT_BRP); +} + +/** + * loongson_canfd_get_txbuf_sts() - Gets status of TX buffer + * @priv: Pointer to private data + * @buf_id: Buffer index (0-based) + * + * Return: Status of TX buffer + */ +static enum loongson_canfd_txbuf_sts +loongson_canfd_get_txbuf_sts(struct loongson_canfd_priv *priv, u8 buf_id) +{ + u32 sts, mask; + + mask = REG_TX_STAT_BS_TX_MASK_BASE << 2 * buf_id; + regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts); + + return field_get(mask, sts); +} + +/** + * loongson_canfd_get_txbuf_id() - Gets the index of a non-idle TX buffer + * @priv: Pointer to private data + * @sts: Status of TXT buffer to outside + * + * Return: Buffer index (0-based) + */ +static unsigned int loongson_canfd_get_txbuf_id(struct loongson_canfd_priv *priv, + enum loongson_canfd_txbuf_sts *sts) +{ + unsigned int i; + + for (i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) { + if (loongson_canfd_get_txbuf_sts(priv, i)) { + *sts = loongson_canfd_get_txbuf_sts(priv, i); + break; + } + } + + if (i == LOONGSON_CANFD_TXBUF_NUM) + *sts = TX_BS_IDLE; + + return i; +} + +/** + * loongson_canfd_txbuf_is_writable() - Checks if frame can be inserted to TX Buffer + * @priv: Pointer to private data + * @buf_id: Buffer index (0-based) + * + * Return: True - Frame can be inserted to TXT Buffer, + * False - If attempted, frame will not be inserted to TX Buffer + */ +static bool loongson_canfd_txbuf_is_writable(struct loongson_canfd_priv *priv, u8 buf_id) +{ + enum loongson_canfd_txbuf_sts bs; + + bs = loongson_canfd_get_txbuf_sts(priv, buf_id); + if (bs) + return false; + + return !regmap_test_bits(priv->regmap, LOONGSON_CANFD_TX_STAT, BIT(buf_id)); +} + +/** + * loongson_canfd_set_txbuf_cmd() - Applies command on TX buffer + * @ndev: Pointer to net_device structure + * @cmd: Command to set + * @buf_id: Buffer index (0-based) + */ +static void loongson_canfd_set_txbuf_cmd(struct net_device *ndev, + enum loongson_canfd_txbuf_cmd cmd, u8 buf_id) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 mask; + + switch (cmd) { + case TXT_CMD_ADD: + mask = REG_TX_CMD_BAR; + break; + case TXT_CMD_CANCEL: + mask = REG_TX_CMD_BCR; + break; + case TXT_CMD_SR_CLEAN: + mask = REG_TX_CMD_BSC; + break; + default: + netdev_err(ndev, "Unknown command id: %x\n", cmd); + return; + } + + regmap_write(priv->regmap, LOONGSON_CANFD_TX_CMD, field_prep(mask, BIT(buf_id))); +} + +/** + * loongson_canfd_get_rxdata_in_dma() - Reading RX data in DMA mode + * @priv: Pointer to private data + * + * Return: The CANFD RX data. + */ +static u32 loongson_canfd_get_rxdata_in_dma(struct loongson_canfd_priv *priv) +{ + u32 data = 0; + + data = priv->rx_buf[LOONGSON_CANFD_RXDMA_NUM - priv->last_res--]; + if (!priv->last_res) + priv->last_res = LOONGSON_CANFD_RXDMA_NUM; + + return data; +} + +/** + * loongson_canfd_get_rxbuf_empty_in_dma() - Gets the RX buffer is empty in dma mode + * @priv: Pointer to private data + * + * Return: True - RX buffer is empty. + * False - RX buffer is processing + */ +static bool loongson_canfd_get_rxbuf_empty_in_dma(struct loongson_canfd_priv *priv) +{ + struct dma_tx_state state; + enum dma_status status; + + status = dmaengine_tx_status(priv->rx_ch, priv->rx_ch->cookie, &state); + if (status != DMA_IN_PROGRESS) + return true; + + return priv->last_res == (state.residue / DMA_SLAVE_BUSWIDTH_4_BYTES); +} + +static void loongson_canfd_rxdma_free(struct loongson_canfd_priv *priv, struct device *dev) +{ + dma_free_coherent(dev, LOONGSON_CANFD_RXBUF_SZ, priv->rx_buf, priv->rx_dma_buf); +} + +static void loongson_canfd_rxdma_remove(struct loongson_canfd_priv *priv, struct device *dev) +{ + if (!priv->rx_ch) + return; + + dmaengine_terminate_sync(priv->rx_ch); + loongson_canfd_rxdma_free(priv, dev); + dma_release_channel(priv->rx_ch); +} + +/** + * loongson_canfd_rxdma_init() - Loongson canfd RXDMA initialization + * @ndev: Pointer to net_device structure + * + * Return: The number of messages in the receive buffer + */ +static int loongson_canfd_rxdma_init(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + struct dma_async_tx_descriptor *desc = NULL; + struct device *dev = ndev->dev.parent; + struct dma_slave_config config; + int ret; + + if (!priv->rx_ch) + return -EINVAL; + + priv->rx_buf = dma_alloc_coherent(dev, LOONGSON_CANFD_RXBUF_SZ, + &priv->rx_dma_buf, GFP_KERNEL); + if (!priv->rx_buf) + return -ENOMEM; + + /* Configure DMA channel */ + memset(&config, 0, sizeof(config)); + config.src_addr = priv->res->start + LOONGSON_CANFD_RX_DATA; + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + ret = dmaengine_slave_config(priv->rx_ch, &config); + if (ret < 0) { + netdev_err(ndev, "Loongson canfd rxdma channel config failed\n"); + goto err_config; + } + + /* Prepare a DMA cyclic transaction */ + desc = dmaengine_prep_dma_cyclic(priv->rx_ch, priv->rx_dma_buf, + LOONGSON_CANFD_RXBUF_SZ, LOONGSON_CANFD_RXBUF_SZ, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + if (!desc) { + netdev_err(ndev, "Loongson canfd rxdma cyclic transaction failed\n"); + ret = -EBUSY; + goto err_config; + } + + /* Push current dma transaction in the pending queue */ + ret = dma_submit_error(dmaengine_submit(desc)); + if (ret) { + dmaengine_terminate_sync(priv->rx_ch); + goto err_config; + } + + /* Issue pending DMA requests */ + dma_async_issue_pending(priv->rx_ch); + + return 0; + +err_config: + loongson_canfd_rxdma_free(priv, dev); + return ret; +} + +/** + * loongson_canfd_get_rxdata_in_poll() - Reading RX data in poll mode + * @priv: Pointer to private data + * + * Return: The CANFD RX data. + */ +static u32 loongson_canfd_get_rxdata_in_poll(struct loongson_canfd_priv *priv) +{ + u32 data; + + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data); + + return data; +} + +/** + * loongson_canfd_get_rxbuf_empty_in_poll() - Gets the RX buffer is empty in poll mode + * @priv: Pointer to private data + * + * Return: True - RX buffer is empty. + * False - RX buffer is processing + */ +static bool loongson_canfd_get_rxbuf_empty_in_poll(struct loongson_canfd_priv *priv) +{ + return !!regmap_test_bits(priv->regmap, LOONGSON_CANFD_RX_STAT, REG_RX_STAT_RXE); +} + +/** + * loongson_canfd_reset() - Issues software reset request to Loongson CANFD + * @ndev: Pointer to net_device structure + */ +static void loongson_canfd_reset(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + + regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RST); + regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RXBAM | REG_MODE_BUFM); +} + +/** + * loongson_canfd_set_btr() - Sets CAN bus bit timing in Loongson CANFD + * @ndev: Pointer to net_device structure + * @bt: Pointer to Bit timing structure + * @nominal: True - Nominal bit timing, False - Data bit timing + */ +static void loongson_canfd_set_btr(struct net_device *ndev, struct can_bittiming *bt, bool nominal) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 ph1_max = FIELD_MAX(REG_BTR_PH1); + u32 btr = 0; + + /* + * The timing calculation functions have only constraints on tseg1, + * which is prop_seg + phase1_seg combined. + * tseg1 is then split in half and stored into prog_seg and phase_seg1. + * In Loongson CAN-FD, PROP is 7 bits wide but PH1 only 6, so we must + * re-distribute the values here. + */ + if (bt->phase_seg1 > ph1_max) { + bt->prop_seg += bt->phase_seg1 - ph1_max; + bt->phase_seg1 = ph1_max; + } + + if (nominal) { + btr = FIELD_PREP(REG_BTR_PROP, bt->prop_seg) | + FIELD_PREP(REG_BTR_PH1, bt->phase_seg1) | + FIELD_PREP(REG_BTR_PH2, bt->phase_seg2) | + FIELD_PREP(REG_BTR_BRP, bt->brp) | + FIELD_PREP(REG_BTR_SJW, bt->sjw); + + regmap_write(priv->regmap, LOONGSON_CANFD_BTR_NORM, btr); + } else { + btr = FIELD_PREP(REG_BTR_FD_PROP, bt->prop_seg) | + FIELD_PREP(REG_BTR_FD_PH1, bt->phase_seg1) | + FIELD_PREP(REG_BTR_FD_PH2, bt->phase_seg2) | + FIELD_PREP(REG_BTR_FD_BRP, bt->brp) | + FIELD_PREP(REG_BTR_FD_SJW, bt->sjw); + + regmap_write(priv->regmap, LOONGSON_CANFD_BTR_FD, btr); + } +} + +/** + * loongson_canfd_set_bittiming() - CAN set nominal bit timing routine + * @ndev: Pointer to net_device structure + * + * Return: 0 on success, -%EPERM on error + */ +static int loongson_canfd_set_bittiming(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + struct can_bittiming *bt = &priv->can.bittiming; + + /* Note that bt may be modified here */ + loongson_canfd_set_btr(ndev, bt, true); + + return 0; +} + +/** + * loongson_canfd_set_data_bittiming() - CAN set data bit timing routine + * @ndev: Pointer to net_device structure + * + * Return: 0 on success, -%EPERM on error + */ +static int loongson_canfd_set_data_bittiming(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + struct can_bittiming *dbt = &priv->can.fd.data_bittiming; + + /* Note that dbt may be modified here */ + loongson_canfd_set_btr(ndev, dbt, false); + + return 0; +} + +/** + * loongson_canfd_get_auto_tdcv - Get Transmitter Delay Compensation Value + * @ndev: Pointer to net_device structure + * @tdcv: Pointer to TDCV value + * + * Return: 0 on success + */ +static int loongson_canfd_get_auto_tdcv(const struct net_device *ndev, u32 *tdcv) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 val; + + regmap_read(priv->regmap, LOONGSON_CANFD_TRV_DLY, &val); + *tdcv = FIELD_GET(REG_TRV_DLY_VAL, val); + + return 0; +} + +/** + * loongson_canfd_set_secondary_sample_point() - Set secondary sample point in Loongson CANFD + * @ndev: Pointer to net_device structure + * + * Return: 0 on success, -%EPERM if controller is enabled + */ +static int loongson_canfd_set_secondary_sample_point(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 ssp_cfg; + + if (loongson_canfd_enabled(priv)) { + netdev_err(ndev, "BUG! Cannot set SSP - CAN is enabled\n"); + return -EPERM; + } + + if (can_fd_tdc_is_enabled(&priv->can)) + ssp_cfg = FIELD_PREP(REG_SSP_CFG_OFF, priv->can.fd.tdc.tdco) | + FIELD_PREP(REG_SSP_CFG_SRC, 0x0); + else + ssp_cfg = FIELD_PREP(REG_SSP_CFG_SRC, 0x1); + + regmap_write(priv->regmap, LOONGSON_CANFD_SSP_CFG, ssp_cfg); + + return 0; +} + +/** + * loongson_canfd_set_conf_mode() - Sets Loongson CANFD mode and configure registers + * @priv: Pointer to private data + */ +static void loongson_canfd_set_conf_mode(struct loongson_canfd_priv *priv) +{ + u32 ctrlmode = priv->can.ctrlmode; + u32 mode, conf; + + regmap_read(priv->regmap, LOONGSON_CANFD_MODE, &mode); + + if (ctrlmode & CAN_CTRLMODE_LISTENONLY) + mode |= REG_MODE_BMM; + else + mode &= ~REG_MODE_BMM; + + if (ctrlmode & CAN_CTRLMODE_FD) + mode |= REG_MODE_FDE; + else + mode &= ~REG_MODE_FDE; + + if (ctrlmode & CAN_CTRLMODE_PRESUME_ACK) + mode |= REG_MODE_ACF; + else + mode &= ~REG_MODE_ACF; + + /* + * Some bits fixed: + * TSTM - Off, User shall not be able to change REC/TEC by hand + * during operation + */ + mode &= ~REG_MODE_TSTM; + regmap_write(priv->regmap, LOONGSON_CANFD_MODE, mode); + + regmap_read(priv->regmap, LOONGSON_CANFD_CONF, &conf); + + if (ctrlmode & CAN_CTRLMODE_LOOPBACK) + conf |= REG_CONF_ILBP; + else + conf &= ~REG_CONF_ILBP; + + if (ctrlmode & CAN_CTRLMODE_FD_NON_ISO) + conf |= REG_CONF_NISOFD; + else + conf &= ~REG_CONF_NISOFD; + + /* One shot mode supported indirectly via Retransmit limit */ + conf &= ~FIELD_PREP(REG_CONF_RTRTH, LOONGSON_CANFD_MAX_RTXTH); + + if (ctrlmode & CAN_CTRLMODE_ONE_SHOT) + conf |= REG_CONF_RTRLE; + else + conf |= REG_CONF_RTRLE | FIELD_PREP(REG_CONF_RTRTH, LOONGSON_CANFD_MAX_RTXTH); + + regmap_write(priv->regmap, LOONGSON_CANFD_CONF, conf); +} + +/** + * loongson_canfd_chip_start() - This routine starts the driver + * @ndev: Pointer to net_device structure + * + * Return: 0 on success and failure value on error + */ +static int loongson_canfd_chip_start(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u16 int_ena; + int ret; + + /* Configure bit-rates and ssp */ + ret = loongson_canfd_set_bittiming(ndev); + if (ret < 0) + return ret; + + ret = loongson_canfd_set_data_bittiming(ndev); + if (ret < 0) + return ret; + + ret = loongson_canfd_set_secondary_sample_point(ndev); + if (ret < 0) + return ret; + + /* Setting mode and configure registers */ + loongson_canfd_set_conf_mode(priv); + + /* Configure interrupts */ + int_ena = REG_INT_STAT_TXBHCI | REG_INT_STAT_EWLI | REG_INT_STAT_FCSI; + int_ena |= priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI; + + /* Bus error reporting */ + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) + int_ena |= REG_INT_STAT_ALI | REG_INT_STAT_BEI; + + /* It's after reset, so there is no need to clear anything */ + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, ~int_ena); + regmap_write(priv->regmap, LOONGSON_CANFD_INT_ENA, int_ena); + + /* Controller enters ERROR_ACTIVE on initial FCSI */ + priv->can.state = CAN_STATE_STOPPED; + + /* Enable the controller */ + regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, REG_CONF_ENA); + + return 0; +} + +/** + * loongson_canfd_do_set_mode() - Sets mode of the driver + * @ndev: Pointer to net_device structure + * @mode: Tells the mode of the driver + * + * This check the drivers state and calls the corresponding modes to set. + * + * Return: 0 on success and failure value on error + */ +static int loongson_canfd_do_set_mode(struct net_device *ndev, enum can_mode mode) +{ + int ret; + + switch (mode) { + case CAN_MODE_START: + loongson_canfd_reset(ndev); + + ret = loongson_canfd_chip_start(ndev); + if (ret < 0) { + netdev_err(ndev, "loongson_canfd_chip_start failed!\n"); + return ret; + } + + netif_wake_queue(ndev); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +/** + * loongson_canfd_insert_frame() - Inserts frame to TXT buffer + * @priv: Pointer to private data + * @cf: Pointer to CAN frame to be inserted + * @buf: TXT Buffer index to which frame is inserted (0-based) + * @isfdf: True - CAN FD Frame, False - CAN 2.0 Frame + * + * Return: + * * True - Frame inserted successfully + * * False - Frame was not inserted due to one of: + * 1. TXT Buffer is not writable (it is in wrong state) + * 2. Invalid TXT buffer index + * 3. Invalid frame length + */ +static bool loongson_canfd_insert_frame(struct loongson_canfd_priv *priv, + const struct canfd_frame *cf, u8 buf, bool isfdf) +{ + u32 meta0, meta1 = 0; + + if (buf >= LOONGSON_CANFD_TXBUF_NUM) + return false; + + if (!loongson_canfd_txbuf_is_writable(priv, buf)) + return false; + + if (cf->len > CANFD_MAX_DLEN) + return false; + + /* Prepare identifier */ + if (cf->can_id & CAN_EFF_FLAG) { + meta0 = cf->can_id & CAN_EFF_MASK; + meta0 |= REG_FRAME_META0_XDT; + } else { + meta0 = FIELD_PREP(REG_FRAME_META0_ID_BASE, cf->can_id & CAN_SFF_MASK); + } + + /* Prepare Frame format */ + if (cf->can_id & CAN_RTR_FLAG) + meta0 |= REG_FRAME_META0_RTR; + + if (isfdf) { + meta1 = REG_FRAME_META1_FDF; + + if (cf->flags & CANFD_BRS) + meta1 |= REG_FRAME_META1_BRS; + } + + meta1 |= FIELD_PREP(REG_FRAME_META1_DLC, can_fd_len2dlc(cf->len)); + + /* TXT buffer select */ + regmap_write(priv->regmap, LOONGSON_CANFD_TX_SEL, buf); + + /* Write ID, Frame format */ + regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_FRAME_META0, meta0); + regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_FRAME_META1, meta1); + + if (cf->can_id & CAN_RTR_FLAG) + return true; + + /* Write Data payload */ + for (unsigned int i = 0; i < cf->len; i += LOONGSON_CANFD_DW_BYTE) { + regmap_write(priv->regmap, + LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_FRAME_DB_1 + i, + *(u32 *)(cf->data + i)); + } + + return true; +} + +/** + * loongson_canfd_start_xmit() - Starts the transmission + * @skb: sk_buff pointer that contains data to be Txed + * @ndev: Pointer to net_device structure + * + * Invoked from upper layers to initiate transmission. Uses the next available free TX Buffer and + * populates its fields to start the transmission. + * + * Return: %NETDEV_TX_OK on success, + * %NETDEV_TX_BUSY when no free TX buffer is available, negative return values reserved + * for error cases. + */ +static netdev_tx_t loongson_canfd_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct canfd_frame *cf = (struct canfd_frame *)skb->data; + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 buf_id = 0, tx_stat, i = 0; + unsigned long tx_brp; + u16 tx_bs; + + if (can_dropped_invalid_skb(ndev, skb)) + return NETDEV_TX_OK; + + if (unlikely(!loongson_canfd_txbuf_freed(priv))) { + netif_stop_queue(ndev); + netdev_err(ndev, "BUG!, no TXB free when queue awake!\n"); + return NETDEV_TX_BUSY; + } + + guard(spinlock_irqsave)(&priv->tx_lock); + + regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &tx_stat); + tx_brp = FIELD_GET(REG_TX_STAT_BRP, tx_stat); + tx_bs = FIELD_GET(REG_TX_STAT_BS, tx_stat); + + for_each_clear_bit(i, &tx_brp, LOONGSON_CANFD_TXBUF_NUM) { + u32 transmission_complete_mask = 0x3 << (i * 2); + + if (!(tx_bs & transmission_complete_mask)) { + buf_id = i; + break; + } + } + + if (!loongson_canfd_insert_frame(priv, cf, buf_id, can_is_canfd_skb(skb))) { + netdev_err(ndev, "TXNF set but cannot insert frame into TXTB! HW Bug?"); + kfree_skb(skb); + ndev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + + can_put_echo_skb(skb, ndev, buf_id, 0); + + loongson_canfd_set_txbuf_cmd(ndev, TXT_CMD_ADD, buf_id); + + /* Check if all TX buffers are full */ + if (!loongson_canfd_txbuf_freed(priv)) + netif_stop_queue(ndev); + + return NETDEV_TX_OK; +} + +/** + * loongson_canfd_read_rx_frame() - Reads frame from RX FIFO + * @priv: Pointer to private data + * @cf: Pointer to CAN frame struct + * @meta0: The first `frame format` read previously + * @meta1: The second `frame format` read previously + */ +static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struct canfd_frame *cf, + u32 meta0, u32 meta1) +{ + u32 i, wc, len; + + /* Extended Identifier Type */ + if (meta0 & REG_FRAME_META0_XDT) + cf->can_id = (meta0 & CAN_EFF_MASK) | CAN_EFF_FLAG; + else + cf->can_id = FIELD_GET(REG_FRAME_META0_ID_BASE, meta0) & CAN_SFF_MASK; + + /* BRS, ESI, RTR Flags */ + cf->flags = 0; + + if (meta1 & REG_FRAME_META1_FDF) { + if (meta1 & REG_FRAME_META1_BRS) + cf->flags |= CANFD_BRS; + + if (meta0 & REG_FRAME_META0_ESI) + cf->flags |= CANFD_ESI; + } else if (meta0 & REG_FRAME_META0_RTR) { + cf->can_id |= CAN_RTR_FLAG; + } + + wc = FIELD_GET(REG_FRAME_META1_RWCNT, meta1) - 2; + + /* Data Length Code */ + len = FIELD_GET(REG_FRAME_META1_DLC, meta1); + if (len > 8) { + if (meta1 & REG_FRAME_META1_FDF) + len = wc << 2; + else + len = 8; + } + + cf->len = len; + if (unlikely(len > wc * LOONGSON_CANFD_DW_BYTE)) + len = wc * LOONGSON_CANFD_DW_BYTE; + + /* Data */ + for (i = 0; i < len; i += LOONGSON_CANFD_DW_BYTE) + *(u32 *)(cf->data + i) = priv->get_rx_data(priv); + + while (unlikely(i < wc * LOONGSON_CANFD_DW_BYTE)) { + priv->get_rx_data(priv); + i += LOONGSON_CANFD_DW_BYTE; + } +} + +/** + * loongson_canfd_rx() - Called from CAN ISR to complete the received frame processing + * @ndev: Pointer to net_device structure + * + * This function is invoked from the CAN isr to process the Rx frames. It does minimal + * processing and invokes "netif_receive_skb" to complete further processing. + * Return: 1 when frame is passed to the network layer, 0 when the first frame word is read but + * system is out of free SKBs temporally and left code to resolve SKB allocation later, + * -%EAGAIN in a case of empty Rx FIFO. + */ +static int loongson_canfd_rx(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + struct canfd_frame *cf; + struct sk_buff *skb; + u32 meta0, meta1; + + meta0 = priv->get_rx_data(priv); + meta1 = priv->get_rx_data(priv); + + /* Number of characters received */ + if (!FIELD_GET(REG_FRAME_META1_RWCNT, meta1)) + return -EAGAIN; + + /* Flexible Data-rate Format */ + if (meta1 & REG_FRAME_META1_FDF) + skb = alloc_canfd_skb(ndev, &cf); + else + skb = alloc_can_skb(ndev, (struct can_frame **)&cf); + if (unlikely(!skb)) + return 0; + + loongson_canfd_read_rx_frame(priv, cf, meta0, meta1); + + if (cf->can_id != CAN_RTR_FLAG) + stats->rx_bytes += cf->len; + stats->rx_packets++; + netif_receive_skb(skb); + + return 1; +} + +/** + * loongson_canfd_read_fault_state() - Reads Loongson CANFD fault state. + * @ndev: Pointer to net_device structure + * + * Returns: Fault confinement state of controller + */ +static enum can_state loongson_canfd_read_fault_state(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + enum can_state sts = CAN_STATE_ERROR_PASSIVE; + u32 fstat; + + regmap_read(priv->regmap, LOONGSON_CANFD_FSTAT, &fstat); + + switch (FIELD_GET(REG_FSTAT_MASK, fstat)) { + case REG_FSTAT_ERA: + u32 ewl, erl, rec_tec, max_tec; + + regmap_read(priv->regmap, LOONGSON_CANFD_ERL, &erl); + regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &rec_tec); + + ewl = FIELD_GET(REG_ERL_EW, erl); + max_tec = max(FIELD_GET(REG_ERC_REC, rec_tec), FIELD_GET(REG_ERC_TEC, rec_tec)); + + if (ewl > max_tec) + sts = CAN_STATE_ERROR_ACTIVE; + else + sts = CAN_STATE_ERROR_WARNING; + break; + case REG_FSTAT_ERP: + sts = CAN_STATE_ERROR_PASSIVE; + break; + case REG_FSTAT_BOF: + sts = CAN_STATE_BUS_OFF; + break; + default: + netdev_err(ndev, "Invalid error state.\n"); + break; + } + + return sts; +} + +/** + * loongson_canfd_get_bec() - Reads REC/TEC counter values from controller + * @priv: Pointer to private data + * @bec: Pointer to Error counter structure + */ +static void loongson_canfd_get_bec(struct loongson_canfd_priv *priv, struct can_berr_counter *bec) +{ + u32 erc; + + regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &erc); + bec->rxerr = FIELD_GET(REG_ERC_REC, erc); + bec->txerr = FIELD_GET(REG_ERC_TEC, erc); +} + +/** + * loongson_canfd_get_berr_counter() - error counter routine + * @ndev: Pointer to net_device structure + * @bec: Pointer to can_berr_counter structure + * + * Return: 0 always + */ +static int loongson_canfd_get_berr_counter(const struct net_device *ndev, + struct can_berr_counter *bec) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + + loongson_canfd_get_bec(priv, bec); + return 0; +} + +/** + * loongson_canfd_err_interrupt() - Error frame ISR + * @ndev: net_device pointer + * @isr: interrupt status register value + * + * This is the CAN error interrupt and it will check the type of error and forward the error + * frame to upper layers. + */ +static void loongson_canfd_err_interrupt(struct net_device *ndev, u32 isr) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + struct can_berr_counter bec; + enum can_state state; + struct can_frame *cf; + struct sk_buff *skb; + u32 err_capt, alc; + + loongson_canfd_get_bec(priv, &bec); + + state = loongson_canfd_read_fault_state(ndev); + regmap_read(priv->regmap, LOONGSON_CANFD_ERR_CAPT, &err_capt); + regmap_read(priv->regmap, LOONGSON_CANFD_ALC, &alc); + + netdev_dbg(ndev, "%s: ISR 0x%08x, rxerr %d, txerr %d, error type %lu, pos %lu, ALC id_field %lu, bit %lu\n", + __func__, isr, bec.rxerr, bec.txerr, + FIELD_GET(REG_ERR_CAPT_TYPE, err_capt), + FIELD_GET(REG_ERR_CAPT_POS, err_capt), + FIELD_GET(REG_ALC_ID_FIELD, alc), + FIELD_GET(REG_ALC_BIT_POS, alc)); + + skb = alloc_can_err_skb(ndev, &cf); + + /* + * EWLI: error warning limit condition met + * FCSI: fault confinement state changed + * ALI: arbitration lost (just informative) + * BEI: bus error interrupt + */ + if ((isr & REG_INT_STAT_FCSI) || (isr & REG_INT_STAT_EWLI)) { + netdev_info(ndev, "state changes from %s to %s\n", + can_get_state_str(priv->can.state), can_get_state_str(state)); + + if (priv->can.state == state) + netdev_warn(ndev, "cur and pre state is the same!(miss intr?)\n"); + + isr = REG_INT_STAT_FCSI | REG_INT_STAT_EWLI; + priv->can.state = state; + switch (state) { + case CAN_STATE_BUS_OFF: + priv->can.can_stats.bus_off++; + if (priv->can.restart_ms) + regmap_write(priv->regmap, LOONGSON_CANFD_CMD, + REG_CMD_ERCRST); + + can_bus_off(ndev); + if (skb) + cf->can_id |= CAN_ERR_BUSOFF; + break; + case CAN_STATE_ERROR_PASSIVE: + priv->can.can_stats.error_passive++; + if (skb) { + cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT; + if (bec.rxerr >= CAN_ERROR_PASSIVE_THRESHOLD) + cf->data[1] = CAN_ERR_CRTL_RX_PASSIVE; + else + cf->data[1] = CAN_ERR_CRTL_TX_PASSIVE; + cf->data[6] = bec.txerr; + cf->data[7] = bec.rxerr; + } + break; + case CAN_STATE_ERROR_WARNING: + priv->can.can_stats.error_warning++; + if (skb) { + cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT; + if (bec.txerr > bec.rxerr) + cf->data[1] |= CAN_ERR_CRTL_TX_WARNING; + else + cf->data[1] |= CAN_ERR_CRTL_RX_WARNING; + cf->data[6] = bec.txerr; + cf->data[7] = bec.rxerr; + } + break; + case CAN_STATE_ERROR_ACTIVE: + cf->can_id |= CAN_ERR_CNT; + cf->data[1] = CAN_ERR_CRTL_ACTIVE; + cf->data[6] = bec.txerr; + cf->data[7] = bec.rxerr; + break; + default: + netdev_err(ndev, "Unexpected state: %d, %s!\n", state, + can_get_state_str(state)); + break; + } + } + + /* Check for Arbitration Lost interrupt */ + if (isr & REG_INT_STAT_ALI) { + isr = REG_INT_STAT_ALI; + netdev_err(ndev, "Arbitration Lost interrupt\n"); + priv->can.can_stats.arbitration_lost++; + if (skb) { + cf->can_id |= CAN_ERR_LOSTARB; + cf->data[0] = CAN_ERR_LOSTARB_UNSPEC; + } + } + + /* Check for Bus Error interrupt */ + if (isr & REG_INT_STAT_BEI) { + isr = REG_INT_STAT_BEI; + netdev_err(ndev, "Bus Error interrupt\n"); + priv->can.can_stats.bus_error++; + stats->rx_errors++; + if (skb) { + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR; + cf->data[2] = CAN_ERR_PROT_UNSPEC; + cf->data[3] = CAN_ERR_PROT_LOC_UNSPEC; + } + } + + if (skb) + netif_rx(skb); + + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, isr); + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, isr << 16); +} + +static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota) +{ + struct net_device *ndev = napi->dev; + struct loongson_canfd_priv *priv = netdev_priv(ndev); + int int_ena = priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI; + int work_done = 0, ret = 1; + bool rxbuf_is_empty; + u32 sts; + + rxbuf_is_empty = priv->get_rxbuf_empty(priv); + + while (!rxbuf_is_empty && work_done < quota && ret > 0) { + ret = loongson_canfd_rx(ndev); + work_done++; + rxbuf_is_empty = priv->get_rxbuf_empty(priv); + } + + /* Check for RX FIFO Overflow */ + regmap_read(priv->regmap, LOONGSON_CANFD_STAT, &sts); + if (sts & REG_STAT_DOR) { + struct net_device_stats *stats = &ndev->stats; + struct can_frame *cf; + struct sk_buff *skb; + + netdev_info(ndev, "Loongson canfd RX overflow\n"); + stats->rx_over_errors++; + stats->rx_errors++; + + skb = alloc_can_err_skb(ndev, &cf); + if (skb) { + cf->can_id |= CAN_ERR_CRTL; + cf->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW; + netif_rx(skb); + } + + /* Clear Data Overrun */ + regmap_write(priv->regmap, LOONGSON_CANFD_CMD, REG_CMD_CDO); + } + + if (rxbuf_is_empty && ret != 0) { + if (napi_complete_done(napi, work_done)) { + /* + * Clear and enable RBNEI/DMADI. It is level-triggered, so + * so there is no race condition. + */ + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, int_ena); + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, (int_ena << 16)); + } + } + + return work_done; +} + +static void loongson_canfd_tx_interrupt(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + enum loongson_canfd_txbuf_sts sts; + u32 buf_id; + + guard(spinlock_irqsave)(&priv->tx_lock); + + while ((buf_id = loongson_canfd_get_txbuf_id(priv, &sts)) < LOONGSON_CANFD_TXBUF_NUM) { + switch (sts) { + case TX_BS_VALID: + stats->tx_bytes += can_get_echo_skb(ndev, buf_id, NULL); + stats->tx_packets++; + break; + case TX_BS_FAIL: + u32 cnt; + /* + * This indicated that retransmit limit has been reached. + * Obviously we should not echo the frame, but also not indicate any + * kind of error. If desired, it was already reported (possible + * multiple times) on each arbitration lost. + */ + regmap_read(priv->regmap, LOONGSON_CANFD_TX_FR_CNT, &cnt); + netdev_warn(ndev, "TXB in FAIL state, TX frame count: %d\n", cnt); + can_free_echo_skb(ndev, buf_id, NULL); + stats->tx_dropped++; + break; + case TX_BS_CANCEL: + /* + * We *could* re-queue the frame, but multiqueue/abort is + * not supported yet anyway. + */ + netdev_warn(ndev, "TXB in CANCEL state\n"); + can_free_echo_skb(ndev, buf_id, NULL); + stats->tx_dropped++; + break; + default: + break; + } + + loongson_canfd_set_txbuf_cmd(ndev, TXT_CMD_SR_CLEAN, buf_id); + } + + /* + * Clear the interrupt again. We do not want to receive again interrupt + * for the buffer already handled. If it is the last finished one then + * it would cause log of spurious interrupt. + */ + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_TXBHCI); + + /* Check if at least one TX buffer is free */ + if (loongson_canfd_txbuf_freed(priv)) + netif_wake_queue(ndev); +} + +/** + * loongson_canfd_interrupt() - CAN Isr + * @irq: irq number + * @dev_id: device id pointer + * + * This is the Loongson CANFD ISR. It checks for the type of interrupt + * and invokes the corresponding ISR. + * + * Return: + * IRQ_NONE - If CAN device is in sleep mode, IRQ_HANDLED otherwise + */ +static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id) +{ + struct net_device *ndev = (struct net_device *)dev_id; + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 isr, imask; + + for (unsigned int irq_loops = 0; irq_loops < 10000; irq_loops++) { + /* Get the interrupt status */ + regmap_read(priv->regmap, LOONGSON_CANFD_INT_STAT, &isr); + if (!isr) { + if (irq_loops) + return IRQ_HANDLED; + else + return IRQ_NONE; + } + + /* Receive Buffer Not Empty Interrupt */ + imask = priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI; + if (isr & imask) { + /* + * Mask RXBNEI the first, then clear interrupt and schedule NAPI. + * Even if another IRQ fires, RBNEI will always be 0 (masked). + */ + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, imask); + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, imask); + napi_schedule(&priv->napi); + } + + /* TX Buffer HW Command Interrupt */ + if (isr & REG_INT_STAT_TXBHCI) + loongson_canfd_tx_interrupt(ndev); + + /* Error interrupts */ + imask = isr & REG_INT_STAT_ERRORI; + if (imask) { + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, imask); + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, imask); + loongson_canfd_err_interrupt(ndev, isr); + } + + /* Ignore RI, TI, LFI, RFI, BSI */ + } + + netdev_err(ndev, "Intterupt state: 0x%x.\n", isr); + + if (isr & REG_INT_STAT_TXBHCI) { + for (unsigned int i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) { + u32 sts = loongson_canfd_get_txbuf_sts(priv, i); + + netdev_err(ndev, "TX buf %d status: 0x%x.\n", i, sts); + } + } + + regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR); + regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK, + REG_INT_MASK_SET, REG_INT_MASK_SET); + + return IRQ_HANDLED; +} + +/** + * loongson_canfd_chip_stop() - Driver stop routine + * @ndev: Pointer to net_device structure + * + * This is the drivers stop routine. It will disable the interrupts and + * disable the controller. + */ +static void loongson_canfd_chip_stop(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + + /* Disable interrupts and disable CAN */ + regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR); + regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK, + REG_INT_MASK_SET, REG_INT_MASK_SET); + regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, 0); + + priv->can.state = CAN_STATE_STOPPED; +} + +/** + * loongson_canfd_open() - Driver open routine + * @ndev: Pointer to net_device structure + * + * This is the driver open routine. + * Return: 0 on success and failure value on error + */ +static int loongson_canfd_open(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + int ret; + + loongson_canfd_reset(ndev); + + /* Common open */ + ret = open_candev(ndev); + if (ret) { + netdev_warn(ndev, "open_candev failed!\n"); + return ret; + } + + napi_enable(&priv->napi); + ret = request_irq(ndev->irq, loongson_canfd_interrupt, IRQF_SHARED, ndev->name, ndev); + if (ret < 0) { + netdev_err(ndev, "irq allocation for CAN failed\n"); + goto err_irq; + } + + ret = loongson_canfd_chip_start(ndev); + if (ret < 0) { + netdev_err(ndev, "loongson_canfd_chip_start failed!\n"); + goto err_chip_start; + } + + netdev_info(ndev, "loongson_canfd_device registered\n"); + netif_start_queue(ndev); + + return 0; + +err_chip_start: + free_irq(ndev->irq, ndev); +err_irq: + close_candev(ndev); + return ret; +} + +/** + * loongson_canfd_close() - Driver close routine + * @ndev: Pointer to net_device structure + * + * Return: 0 always + */ +static int loongson_canfd_close(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + + netif_stop_queue(ndev); + napi_disable(&priv->napi); + loongson_canfd_chip_stop(ndev); + free_irq(ndev->irq, ndev); + close_candev(ndev); + + return 0; +} + +static const struct net_device_ops loongson_canfd_netdev_ops = { + .ndo_open = loongson_canfd_open, + .ndo_stop = loongson_canfd_close, + .ndo_start_xmit = loongson_canfd_start_xmit, +}; + +static const struct ethtool_ops loongson_canfd_ethtool_ops = { + .get_ts_info = ethtool_op_get_ts_info, +}; + +static const struct regmap_range loongson_canfd_reg_table_wr_range[] = { + regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_CONF), + regmap_reg_range(LOONGSON_CANFD_CMD, LOONGSON_CANFD_CMD), + regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_ERL), + regmap_reg_range(LOONGSON_CANFD_CTR_PRES, LOONGSON_CANFD_CTR_PRES), + regmap_reg_range(LOONGSON_CANFD_SSP_CFG, LOONGSON_CANFD_SSP_CFG), + regmap_reg_range(LOONGSON_CANFD_TS, LOONGSON_CANFD_FLT_CTRL), + regmap_reg_range(LOONGSON_CANFD_TX_CMD, LOONGSON_CANFD_TX_DATA_18), +}; + +static const struct regmap_range loongson_canfd_reg_table_rd_range[] = { + regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_STAT), + regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_BRE), + regmap_reg_range(LOONGSON_CANFD_ERR_CAPT, LOONGSON_CANFD_TX_STAT), + regmap_reg_range(LOONGSON_CANFD_TX_SEL, LOONGSON_CANFD_TX_DATA_18), +}; + +static const struct regmap_access_table loongson_canfd_reg_table_wr = { + .yes_ranges = loongson_canfd_reg_table_wr_range, + .n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_wr_range), +}; + +static const struct regmap_access_table loongson_canfd_reg_table_rd = { + .yes_ranges = loongson_canfd_reg_table_rd_range, + .n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_rd_range), +}; + +static bool loongson_canfd_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LOONGSON_CANFD_MODE: + case LOONGSON_CANFD_CONF: + case LOONGSON_CANFD_STAT: + case LOONGSON_CANFD_INT_STAT: + case LOONGSON_CANFD_INT_ENA: + case LOONGSON_CANFD_INT_MASK: + case LOONGSON_CANFD_ERL: + case LOONGSON_CANFD_FSTAT: + case LOONGSON_CANFD_ERC: + case LOONGSON_CANFD_ERR_CAPT: + case LOONGSON_CANFD_ALC: + case LOONGSON_CANFD_TX_FR_CNT: + case LOONGSON_CANFD_RX_STAT: + case LOONGSON_CANFD_RX_DATA: + case LOONGSON_CANFD_TX_STAT: + case LOONGSON_CANFD_TX_SEL: + return true; + default: + return false; + }; +} + +static const struct regmap_config loongson_cangfd_regmap = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .wr_table = &loongson_canfd_reg_table_wr, + .rd_table = &loongson_canfd_reg_table_rd, + .volatile_reg = loongson_canfd_volatile_reg, + .max_register = LOONGSON_CANFD_TX_DATA_18, + .cache_type = REGCACHE_MAPLE, +}; + +static int loongson_canfd_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct loongson_canfd_priv *priv; + struct net_device *ndev; + struct regmap *regmap; + struct resource *res; + void __iomem *base; + u32 clk_rate; + int ret = 0, irq; + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(dev, base, &loongson_cangfd_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + device_property_read_u32(dev, "clock-frequency", &clk_rate); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + /* Create a CAN device instance */ + ndev = alloc_candev(sizeof(*priv), LOONGSON_CANFD_TXBUF_NUM); + if (!ndev) + return -ENOMEM; + + priv = netdev_priv(ndev); + spin_lock_init(&priv->tx_lock); + priv->regmap = regmap; + priv->res = res; + + priv->can.clock.freq = clk_rate; + priv->can.bittiming_const = &loongson_canfd_bit_timing; + priv->can.fd.data_bittiming_const = &loongson_canfd_bit_timing_data; + priv->can.fd.tdc_const = &loongson_canfd_tdc_data; + + priv->can.fd.do_set_data_bittiming = loongson_canfd_set_data_bittiming; + priv->can.fd.do_get_auto_tdcv = loongson_canfd_get_auto_tdcv; + priv->can.do_set_mode = loongson_canfd_do_set_mode; + priv->can.do_set_bittiming = loongson_canfd_set_bittiming; + priv->can.do_get_berr_counter = loongson_canfd_get_berr_counter; + + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY | + CAN_CTRLMODE_3_SAMPLES | CAN_CTRLMODE_ONE_SHOT | + CAN_CTRLMODE_BERR_REPORTING | CAN_CTRLMODE_FD | + CAN_CTRLMODE_PRESUME_ACK | CAN_CTRLMODE_FD_NON_ISO | + CAN_CTRLMODE_TDC_AUTO | CAN_CTRLMODE_CC_LEN8_DLC; + + ndev->irq = irq; + ndev->flags |= IFF_ECHO; /* We support local echo */ + platform_set_drvdata(pdev, ndev); + ndev->netdev_ops = &loongson_canfd_netdev_ops; + ndev->ethtool_ops = &loongson_canfd_ethtool_ops; + SET_NETDEV_DEV(ndev, dev); + + priv->get_rx_data = loongson_canfd_get_rxdata_in_poll; + priv->get_rxbuf_empty = loongson_canfd_get_rxbuf_empty_in_poll; + + priv->rx_ch = dma_request_chan(dev, "rx"); + if (PTR_ERR(priv->rx_ch) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (IS_ERR(priv->rx_ch)) { + dev_warn(dev, "Fall back in poll mode for any non-deferral error.\n"); + priv->rx_ch = NULL; + } + + ret = loongson_canfd_rxdma_init(ndev); + if (!ret) { + priv->get_rx_data = loongson_canfd_get_rxdata_in_dma; + priv->get_rxbuf_empty = loongson_canfd_get_rxbuf_empty_in_dma; + priv->last_res = LOONGSON_CANFD_RXDMA_NUM; + } + netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi); + + ret = register_candev(ndev); + if (ret) { + dev_err(dev, "register_candev failed with %d\n", ret); + goto err_candev_free; + } + + return 0; + +err_candev_free: + netif_napi_del(&priv->napi); + loongson_canfd_rxdma_remove(priv, &pdev->dev); + free_candev(ndev); + return ret; +} + +static void loongson_canfd_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct loongson_canfd_priv *priv = netdev_priv(ndev); + + unregister_candev(ndev); + netif_napi_del(&priv->napi); + loongson_canfd_rxdma_remove(priv, &pdev->dev); + free_candev(ndev); +} + +static const struct acpi_device_id loongson_canfd_acpi_match[] = { + { "LOON0015" }, + {} +}; +MODULE_DEVICE_TABLE(acpi, loongson_canfd_acpi_match); + +static struct platform_driver loongson_canfd_driver = { + .probe = loongson_canfd_probe, + .remove = loongson_canfd_remove, + .driver = { + .name = DEV_NAME, + .acpi_match_table = loongson_canfd_acpi_match, + }, +}; +module_platform_driver(loongson_canfd_driver); + +MODULE_SOFTDEP("pre: loongson2-apb-cmc-dma"); +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_DESCRIPTION("Loongson CAN-FD Controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index b8f70e2a1763ad..637a07cd3fe1f7 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -158,6 +158,7 @@ config OA_TC6 source "drivers/net/ethernet/pasemi/Kconfig" source "drivers/net/ethernet/pensando/Kconfig" +source "drivers/net/ethernet/phytium/Kconfig" source "drivers/net/ethernet/qlogic/Kconfig" source "drivers/net/ethernet/brocade/Kconfig" source "drivers/net/ethernet/qualcomm/Kconfig" diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index 57344fec6ce047..d2590894e2228b 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -73,6 +73,7 @@ obj-$(CONFIG_LPC_ENET) += nxp/ obj-$(CONFIG_NET_VENDOR_OKI) += oki-semi/ obj-$(CONFIG_ETHOC) += ethoc.o obj-$(CONFIG_NET_VENDOR_PASEMI) += pasemi/ +obj-$(CONFIG_NET_VENDOR_PHYTIUM) += phytium/ obj-$(CONFIG_NET_VENDOR_QLOGIC) += qlogic/ obj-$(CONFIG_NET_VENDOR_QUALCOMM) += qualcomm/ obj-$(CONFIG_NET_VENDOR_REALTEK) += realtek/ diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index 8ac16808023cc6..ab567c3eaa92ed 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -7202,6 +7202,9 @@ static int igc_probe(struct pci_dev *pdev, memcpy(&hw->mac.ops, ei->mac_ops, sizeof(hw->mac.ops)); memcpy(&hw->phy.ops, ei->phy_ops, sizeof(hw->phy.ops)); + if (pci_is_thunderbolt_attached(pdev)) + msleep(1000); + /* Initialize skew-specific constants */ err = ei->get_invariants(hw); if (err) diff --git a/drivers/net/ethernet/phytium/Kconfig b/drivers/net/ethernet/phytium/Kconfig new file mode 100644 index 00000000000000..4ba96e11a59490 --- /dev/null +++ b/drivers/net/ethernet/phytium/Kconfig @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Phytium device configuration +# + +config NET_VENDOR_PHYTIUM + bool "Phytium devices" + depends on ARCH_PHYTIUM || COMPILE_TEST + depends on HAS_IOMEM + default y + help + If you have a network (Ethernet) card belonging to this class, say Y. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all the + remaining Cadence network card questions. If you say Y, you will be + asked for your specific card in the following questions. + +if NET_VENDOR_PHYTIUM + +config PHYTMAC + tristate "Phytium GMAC support" + depends on ACPI + depends on ARCH_PHYTIUM || COMPILE_TEST + depends on HAS_DMA + select PHYLINK + select CRC32 + help + If you have a network (Ethernet) controller of this type, say Y + or M here. + + To compile this driver as a module, choose M here: the module + will be phytmac. + +config PHYTMAC_ENABLE_PTP + bool "Enable IEEE 1588 hwstamp" + depends on PHYTMAC + depends on PTP_1588_CLOCK + default y + help + Enable IEEE 1588 PTP support for PHYTMAC. + +config PHYTMAC_PLATFORM + tristate "Phytmac Platform support" + depends on PHYTMAC + help + This is Platform driver. + + To compile this driver as a module, choose M here: the module + will be called phytmac_platform. + +config PHYTMAC_PCI + tristate "Phytmac PCI support" + depends on PHYTMAC && PCI + help + This is PCI driver. + + To compile this driver as a module, choose M here: the module + will be called phytmac_pci. + +endif # NET_VENDOR_PHYTIUM diff --git a/drivers/net/ethernet/phytium/Makefile b/drivers/net/ethernet/phytium/Makefile new file mode 100644 index 00000000000000..debaed63e45388 --- /dev/null +++ b/drivers/net/ethernet/phytium/Makefile @@ -0,0 +1,17 @@ + +# SPDX-License-Identifier: GPL-2.0 +# Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. +# +# Makefile for the Phytium network device drivers. +# + +obj-$(CONFIG_PHYTMAC) += phytmac.o + +phytmac-objs := phytmac_main.o phytmac_ethtool.o phytmac_v1.o phytmac_v2.o +phytmac-$(CONFIG_PHYTMAC_ENABLE_PTP) += phytmac_ptp.o + +obj-$(CONFIG_PHYTMAC_PLATFORM) += phytmac-platform.o +phytmac-platform-objs := phytmac_platform.o + +obj-$(CONFIG_PHYTMAC_PCI) += phytmac-pci.o +phytmac-pci-objs := phytmac_pci.o diff --git a/drivers/net/ethernet/phytium/phytmac.h b/drivers/net/ethernet/phytium/phytmac.h new file mode 100644 index 00000000000000..c3feca3d9611a0 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac.h @@ -0,0 +1,700 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. */ + +#ifndef _PHYTMAC_H +#define _PHYTMAC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PHYTMAC_PCI_DRV_NAME "phytmac_pci" +#define PHYTMAC_PLAT_DRV_NAME "phytmac_platform" +#define PHYTMAC_DRV_DESC "PHYTIUM Ethernet Driver" +#define PHYTMAC_DRIVER_VERSION "1.0.50" +#define PHYTMAC_DEFAULT_MSG_ENABLE \ + (NETIF_MSG_DRV | \ + NETIF_MSG_PROBE | \ + NETIF_MSG_LINK | \ + NETIF_MSG_INTR | \ + NETIF_MSG_HW |\ + NETIF_MSG_PKTDATA) + +#define IRQ_TYPE_INT 0 +#define IRQ_TYPE_MSI 1 +#define IRQ_TYPE_INTX 2 + +#define PHYTMAC_MAX_QUEUES 8 +#define DEFAULT_DMA_BURST_LENGTH 16 +#define DEFAULT_JUMBO_MAX_LENGTH 10240 +#define PHYTMAC_MAX_TX_LEN 16320 +#define PHYTMAC_MIN_TX_LEN 64 +#define DEFAULT_TX_RING_SIZE 512 +#define DEFAULT_RX_RING_SIZE 512 +#define MAX_TX_RING_SIZE 1024 +#define MAX_RX_RING_SIZE 4096 +#define MIN_TX_RING_SIZE 64 +#define MIN_RX_RING_SIZE 64 +#define DEFAULT_TX_DESC_MIN_FREE 64 + +#define MEMORY_SIZE 4096 +#define MHU_SIZE 0x20 + +#define PHYTMAC_POWEROFF 1 +#define PHYTMAC_POWERON 2 +#define PHYTMAC_PWCTL_GMAC_ID 6 +#define PHYTMAC_PWCTL_DEFAULT_VAL 0 + +#define PHYTMAC_WOL_MAGIC_PACKET 1 + +#define DEFAULT_MSG_RING_SIZE 16 + +#define PHYTMAC_MDIO_TIMEOUT 1000000 /* in usecs */ + +#define PHYTMAC_CAPS_JUMBO 0x00000001 +#define PHYTMAC_CAPS_PTP 0x00000002 +#define PHYTMAC_CAPS_BD_RD_PREFETCH 0x00000004 +#define PHYTMAC_CAPS_PCS 0x00000008 +#define PHYTMAC_CAPS_LSO 0x00000010 +#define PHYTMAC_CAPS_SG_DISABLED 0x00000020 +#define PHYTMAC_CAPS_TAILPTR 0x00000040 +#define PHYTMAC_CAPS_START 0x00000080 +#define PHYTMAC_CAPS_NO_WOL 0x0000100 +#define PHYTMAC_CAPS_PWCTRL 0x0000200 +#define PHYTMAC_CAPS_MSG 0x0000800 +#define PHYTMAC_CAPS_RXPTR 0x0001000 + +#define VERSION_V0 0 +#define VERSION_V3 0x3 + +#define PHYTMAC_TX 0x1 +#define PHYTMAC_RX 0x2 + +#define PHYTMAC_ETHTOOLD_REGS_LEN 64 +#define PHYTMAC_STATIS_REG_NUM 45 + +#define PHYTMAC_MTU_MIN_SIZE ETH_MIN_MTU + +#define EQUAL(a, b) ((a) == (b)) + +#define TXD_USE_COUNT(_pdata, s) DIV_ROUND_UP((s), (_pdata)->max_tx_length) + +/* Bit manipulation macros */ +#define PHYTMAC_BIT(_field) \ + (1 << PHYTMAC_##_field##_INDEX) + +#define PHYTMAC_BITS(_field, value) \ + (((value) & ((1 << PHYTMAC_##_field##_WIDTH) - 1)) \ + << PHYTMAC_##_field##_INDEX) + +#define PHYTMAC_GET_BITS(_var, _field) \ + (((_var) >> (PHYTMAC_##_field##_INDEX)) \ + & ((0x1 << (PHYTMAC_##_field##_WIDTH)) - 1)) + +#define PHYTMAC_SET_BITS(_var, _field, _val) \ + (((_var) & ~(((1 << PHYTMAC_##_field##_WIDTH) - 1) \ + << PHYTMAC_##_field##_INDEX)) \ + | (((_val) & ((1 << PHYTMAC_##_field##_WIDTH) - 1)) \ + << PHYTMAC_##_field##_INDEX)) + +#define PHYTMAC_READ(_pdata, _reg) \ + __raw_readl((_pdata)->mac_regs + (_reg)) + +#define PHYTMAC_READ_BITS(_pdata, _reg, _field) \ + PHYTMAC_GET_BITS(PHYTMAC_READ((_pdata), _reg), _field) + +#define PHYTMAC_WRITE(_pdata, _reg, _val) \ + __raw_writel((_val), (_pdata)->mac_regs + (_reg)) + +#define PHYTMAC_MSG_READ(_pdata, _reg) \ + __raw_readl((_pdata)->mac_regs + (_reg)) + +#define LSO_UFO 1 +#define LSO_TSO 2 + +#define PHYTMAC_INT_TX_COMPLETE 0x1 +#define PHYTMAC_INT_TX_ERR 0x2 +#define PHYTMAC_INT_RX_COMPLETE 0x4 +#define PHYTMAC_INT_RX_OVERRUN 0x8 +#define PHYTMAC_INT_RX_DESC_FULL 0x10 +#define PHYTMAC_RX_INT_FLAGS (PHYTMAC_INT_RX_COMPLETE) +#define PHYTMAC_TX_INT_FLAGS (PHYTMAC_INT_TX_COMPLETE \ + | PHYTMAC_INT_TX_ERR) + +#define PHYTMAC_WAKE_MAGIC 0x00000001 +#define PHYTMAC_WAKE_ARP 0x00000002 +#define PHYTMAC_WAKE_UCAST 0x00000004 +#define PHYTMAC_WAKE_MCAST 0x00000008 + +/* XDP */ +#define PHYTMAC_XDP_PASS 0 +#define PHYTMAC_XDP_CONSUMED BIT(0) +#define PHYTMAC_XDP_TX BIT(1) +#define PHYTMAC_XDP_REDIR BIT(2) + +#define PHYTMAC_DESC_NEEDED (MAX_SKB_FRAGS + 4) + +enum phytmac_interface { + PHYTMAC_PHY_INTERFACE_MODE_NA, + PHYTMAC_PHY_INTERFACE_MODE_INTERNAL, + PHYTMAC_PHY_INTERFACE_MODE_MII, + PHYTMAC_PHY_INTERFACE_MODE_GMII, + PHYTMAC_PHY_INTERFACE_MODE_SGMII, + PHYTMAC_PHY_INTERFACE_MODE_TBI, + PHYTMAC_PHY_INTERFACE_MODE_REVMII, + PHYTMAC_PHY_INTERFACE_MODE_RMII, + PHYTMAC_PHY_INTERFACE_MODE_RGMII, + PHYTMAC_PHY_INTERFACE_MODE_RGMII_ID, + PHYTMAC_PHY_INTERFACE_MODE_RGMII_RXID, + PHYTMAC_PHY_INTERFACE_MODE_RGMII_TXID, + PHYTMAC_PHY_INTERFACE_MODE_RTBI, + PHYTMAC_PHY_INTERFACE_MODE_SMII, + PHYTMAC_PHY_INTERFACE_MODE_XGMII, + PHYTMAC_PHY_INTERFACE_MODE_MOCA, + PHYTMAC_PHY_INTERFACE_MODE_QSGMII, + PHYTMAC_PHY_INTERFACE_MODE_TRGMII, + PHYTMAC_PHY_INTERFACE_MODE_100BASEX, + PHYTMAC_PHY_INTERFACE_MODE_1000BASEX, + PHYTMAC_PHY_INTERFACE_MODE_2500BASEX, + PHYTMAC_PHY_INTERFACE_MODE_5GBASER, + PHYTMAC_PHY_INTERFACE_MODE_RXAUI, + PHYTMAC_PHY_INTERFACE_MODE_XAUI, + /* 10GBASE-R, XFI, SFI - single lane 10G Serdes */ + PHYTMAC_PHY_INTERFACE_MODE_10GBASER, + PHYTMAC_PHY_INTERFACE_MODE_USXGMII, + /* 10GBASE-KR - with Clause 73 AN */ + PHYTMAC_PHY_INTERFACE_MODE_10GKR, + PHYTMAC_PHY_INTERFACE_MODE_MAX, +}; + +struct packet_info { + int lso; + int desc_cnt; + int hdrlen; + int nocrc; + u32 mss; + u32 seq; +}; + +#define DEV_TYPE_PLATFORM 0 +#define DEV_TYPE_PCI 1 + +struct phytmac_statistics { + char stat_string[ETH_GSTRING_LEN]; +}; + +#define STAT_TITLE(title) { \ + .stat_string = title, \ +} + +static const struct phytmac_statistics phytmac_statistics[] = { + STAT_TITLE("tx_octets"), + STAT_TITLE("tx_packets"), + STAT_TITLE("tx_bcast_packets"), + STAT_TITLE("tx_mcase_packets"), + STAT_TITLE("tx_pause_packets"), + STAT_TITLE("tx_64_byte_packets"), + STAT_TITLE("tx_65_127_byte_packets"), + STAT_TITLE("tx_128_255_byte_packets"), + STAT_TITLE("tx_256_511_byte_packets"), + STAT_TITLE("tx_512_1023_byte_packets"), + STAT_TITLE("tx_1024_1518_byte_packets"), + STAT_TITLE("tx_more_than_1518_byte_packets"), + STAT_TITLE("tx_underrun"), + STAT_TITLE("tx_single_collisions"), + STAT_TITLE("tx_multiple_collisions"), + STAT_TITLE("tx_excessive_collisions"), + STAT_TITLE("tx_late_collisions"), + STAT_TITLE("tx_deferred"), + STAT_TITLE("tx_carrier_sense_errors"), + STAT_TITLE("rx_octets"), + STAT_TITLE("rx_packets"), + STAT_TITLE("rx_bcast_packets"), + STAT_TITLE("rx_mcast_packets"), + STAT_TITLE("rx_pause_packets"), + STAT_TITLE("rx_64_byte_packets"), + STAT_TITLE("rx_65_127_byte_packets"), + STAT_TITLE("rx_128_255_byte_packets"), + STAT_TITLE("rx_256_511_byte_packets"), + STAT_TITLE("rx_512_1023_byte_packets"), + STAT_TITLE("rx_1024_1518_byte_packets"), + STAT_TITLE("rx_more_than_1518_byte_packets"), + STAT_TITLE("rx_undersized_packets"), + STAT_TITLE("rx_oversize_packets"), + STAT_TITLE("rx_jabbers"), + STAT_TITLE("rx_fcs_errors"), + STAT_TITLE("rx_length_errors"), + STAT_TITLE("rx_symbol_errors"), + STAT_TITLE("rx_alignment_errors"), + STAT_TITLE("rx_resource_over"), + STAT_TITLE("rx_overruns"), + STAT_TITLE("rx_iphdr_csum_errors"), + STAT_TITLE("rx_tcp_csum_errors"), + STAT_TITLE("rx_udp_csum_errors"), +}; + +#define PHYTMAC_STATS_LEN ARRAY_SIZE(phytmac_statistics) + +/* per queue statistics, each should be unsigned long type */ +struct phytmac_queue_stats { + unsigned long rx_packets; + unsigned long rx_bytes; + unsigned long rx_dropped; + unsigned long tx_packets; + unsigned long tx_bytes; + unsigned long tx_dropped; +}; + +static const struct phytmac_statistics queue_statistics[] = { + STAT_TITLE("rx_packets"), + STAT_TITLE("rx_bytes"), + STAT_TITLE("rx_dropped"), + STAT_TITLE("tx_packets"), + STAT_TITLE("tx_bytes"), + STAT_TITLE("tx_dropped"), +}; + +#define QUEUE_STATS_LEN ARRAY_SIZE(queue_statistics) + +struct phytmac_config { + struct phytmac_hw_if *hw_if; + u32 caps; + u16 queue_num; +}; + +struct phytmac_stats { + u64 tx_octets; + u64 tx_packets; + u64 tx_bcast_packets; + u64 tx_mcase_packets; + u64 tx_pause_packets; + u64 tx_64_byte_packets; + u64 tx_65_127_byte_packets; + u64 tx_128_255_byte_packets; + u64 tx_256_511_byte_packets; + u64 tx_512_1023_byte_packets; + u64 tx_1024_1518_byte_packets; + u64 tx_more_than_1518_byte_packets; + u64 tx_underrun; + u64 tx_single_collisions; + u64 tx_multiple_collisions; + u64 tx_excessive_collisions; + u64 tx_late_collisions; + u64 tx_deferred; + u64 tx_carrier_sense_errors; + u64 rx_octets; + u64 rx_packets; + u64 rx_bcast_packets; + u64 rx_mcast_packets; + u64 rx_pause_packets; + u64 rx_64_byte_packets; + u64 rx_65_127_byte_packets; + u64 rx_128_255_byte_packets; + u64 rx_256_511_byte_packets; + u64 rx_512_1023_byte_packets; + u64 rx_1024_1518_byte_packets; + u64 rx_more_than_1518_byte_packets; + u64 rx_undersized_packets; + u64 rx_oversize_packets; + u64 rx_jabbers; + u64 rx_fcs_errors; + u64 rx_length_errors; + u64 rx_symbol_errors; + u64 rx_alignment_errors; + u64 rx_resource_over; + u64 rx_overruns; + u64 rx_iphdr_csum_errors; + u64 rx_tcp_csum_errors; + u64 rx_udp_csum_errors; +}; + +struct ts_incr { + u32 sub_ns; + u32 ns; +}; + +enum phytmac_bd_control { + TS_DISABLED, + TS_FRAME_PTP_EVENT_ONLY, + TS_ALL_PTP_FRAMES, + TS_ALL_FRAMES, +}; + +#ifdef CONFIG_PHYTMAC_ENABLE_PTP +struct phytmac_dma_desc { + u32 desc0; + u32 desc1; + u32 desc2; + u32 desc3; + u32 desc4; + u32 desc5; +}; +#else +struct phytmac_dma_desc { + u32 desc0; + u32 desc1; + u32 desc2; + u32 desc3; +}; +#endif + +/* TX resources are shared between XDP and netstack + * and we need to tag the buffer type to distinguish them + */ +enum phytmac_tx_buf_type { + PHYTMAC_TYPE_SKB = 0, + PHYTMAC_TYPE_XDP, +}; + +struct phytmac_tx_skb { + union { + struct sk_buff *skb; + struct xdp_frame *xdpf; + }; + enum phytmac_tx_buf_type type; + dma_addr_t addr; + size_t length; + bool mapped_as_page; +}; + +struct phytmac_tx_ts { + struct sk_buff *skb; + u32 ts_1; + u32 ts_2; +}; + +struct phytmac_rx_buffer { + dma_addr_t addr; + struct page *page; + __u16 page_offset; + __u16 pagecnt_bias; +}; + +struct phytmac_queue { + struct phytmac *pdata; + int irq; + int index; + struct bpf_prog *xdp_prog; + + /* tx queue info */ + unsigned int tx_head; + unsigned int tx_tail; + unsigned int tx_xmit_more; + dma_addr_t tx_ring_addr; + struct work_struct tx_error_task; + struct napi_struct tx_napi; + struct phytmac_dma_desc *tx_ring; + struct phytmac_tx_skb *tx_skb; + /* Lock to protect tx */ + spinlock_t tx_lock; + + /* rx queue info */ + dma_addr_t rx_ring_addr; + unsigned int rx_head; + unsigned int rx_tail; + unsigned int rx_next_to_alloc; + struct phytmac_dma_desc *rx_ring; + struct phytmac_rx_buffer *rx_buffer_info; + struct napi_struct rx_napi; + struct phytmac_queue_stats stats; + struct xdp_rxq_info xdp_rxq; + +#ifdef CONFIG_PHYTMAC_ENABLE_PTP + struct work_struct tx_ts_task; + unsigned int tx_ts_head; + unsigned int tx_ts_tail; + struct phytmac_tx_ts tx_timestamps[128]; +#endif +}; + +struct ethtool_rx_fs_item { + struct ethtool_rx_flow_spec fs; + struct list_head list; +}; + +struct ethtool_rx_fs_list { + struct list_head list; + unsigned int count; +}; + +struct phytmac_msg { + struct completion tx_msg_comp; + u32 tx_msg_ring_size; + u32 rx_msg_ring_size; + u32 tx_msg_head; + u32 tx_msg_wr_tail; + u32 tx_msg_rd_tail; + u32 rx_msg_head; + u32 rx_msg_tail; + /*use msg_lock to protect msg */ + spinlock_t msg_lock; +}; + +struct ts_ctrl { + int tx_control; + int rx_control; + int one_step; +}; + +struct phytmac { + void __iomem *mac_regs; + void __iomem *msg_regs; + void __iomem *mhu_regs; + struct pci_dev *pcidev; + struct platform_device *platdev; + struct net_device *ndev; + struct device *dev; + struct bpf_prog *xdp_prog; + struct ncsi_dev *ncsidev; + struct fwnode_handle *fwnode; + struct phytmac_hw_if *hw_if; + struct phytmac_msg msg_ring; + int dev_type; + int sfp_irq; + int irq_type; + int queue_irq[PHYTMAC_MAX_QUEUES]; + u32 rx_irq_mask; + u32 tx_irq_mask; + u32 msg_enable; + u32 capacities; + u32 max_tx_length; + u32 min_tx_length; + u32 jumbo_len; + u32 wol; + u32 power_state; + struct work_struct restart_task; + /* Lock to protect mac config */ + spinlock_t lock; + u32 rx_ring_size; + u32 tx_ring_size; + u32 dma_data_width; + u32 dma_addr_width; + u32 dma_burst_length; + int rx_bd_prefetch; + int tx_bd_prefetch; + int rx_buffer_len; + u16 queues_max_num; + u16 queues_num; + struct phytmac_queue queues[PHYTMAC_MAX_QUEUES]; + struct phytmac_stats stats; + u64 ethtool_stats[PHYTMAC_STATS_LEN + + QUEUE_STATS_LEN * PHYTMAC_MAX_QUEUES]; + int use_ncsi; + int use_mii; + struct mii_bus *mii_bus; + struct phylink *phylink; + struct phylink_config phylink_config; + struct phylink_pcs phylink_pcs; + int pause; + phy_interface_t phy_interface; + enum phytmac_interface phytmac_v2_interface; + int speed; + int duplex; + int autoneg; + /* 1588 */ + spinlock_t ts_clk_lock; /* clock locking */ + unsigned int ts_rate; + struct ptp_clock *ptp_clock; + struct ptp_clock_info ptp_clock_info; + struct ts_incr ts_incr; + struct hwtstamp_config ts_config; + struct ts_ctrl ts_ctrl; + /* RX queue filer rule set */ + struct ethtool_rx_fs_list rx_fs_list; + /* Lock to protect fs */ + spinlock_t rx_fs_lock; + unsigned int max_rx_fs; + u32 version; + char fw_version[32]; +}; + +/* phytmac_desc_unused - calculate if we have unused descriptors */ +static inline int phytmac_txdesc_unused(struct phytmac_queue *queue) +{ + struct phytmac *pdata = queue->pdata; + + if (queue->tx_head > queue->tx_tail) + return queue->tx_head - queue->tx_tail - 1; + + return pdata->tx_ring_size + queue->tx_head - queue->tx_tail - 1; +} + +static inline struct netdev_queue *phytmac_get_txq(const struct phytmac *pdata, + struct phytmac_queue *queue) +{ + return netdev_get_tx_queue(pdata->ndev, queue->index); +} + +struct phytmac_hw_if { + int (*init_msg_ring)(struct phytmac *pdata); + int (*init_hw)(struct phytmac *pdata); + void (*reset_hw)(struct phytmac *pdata); + int (*init_ring_hw)(struct phytmac *pdata); + int (*poweron)(struct phytmac *pdata, int on); + int (*set_wol)(struct phytmac *pdata, int wake); + int (*get_feature)(struct phytmac *pdata); + int (*set_mac_address)(struct phytmac *pdata, const u8 *addr); + int (*get_mac_address)(struct phytmac *pdata, u8 *addr); + int (*enable_promise)(struct phytmac *pdata, int enable); + int (*enable_multicast)(struct phytmac *pdata, int enable); + int (*set_hash_table)(struct phytmac *pdata, unsigned long *mc_filter); + int (*enable_rx_csum)(struct phytmac *pdata, int enable); + int (*enable_tx_csum)(struct phytmac *pdata, int enable); + int (*enable_pause)(struct phytmac *pdata, int enable); + int (*enable_autoneg)(struct phytmac *pdata, int enable); + int (*enable_network)(struct phytmac *pdata, int enable, int rx_tx); + void (*get_stats)(struct phytmac *pdata); + void (*get_regs)(struct phytmac *pdata, u32 *reg_buff); + int (*set_speed)(struct phytmac *pdata); + + void (*mac_config)(struct phytmac *pdata, u32 mode, + const struct phylink_link_state *state); + int (*mac_linkup)(struct phytmac *pdata, phy_interface_t interface, + int speed, int duplex); + int (*mac_linkdown)(struct phytmac *pdata); + int (*pcs_linkup)(struct phytmac *pdata, phy_interface_t interface, + int speed, int duplex); + int (*pcs_linkdown)(struct phytmac *pdata); + unsigned int (*get_link)(struct phytmac *pdata, phy_interface_t interface); + + /* For RX coalescing */ + int (*config_rx_coalesce)(struct phytmac *pdata); + int (*config_tx_coalesce)(struct phytmac *pdata); + + /* ethtool nfc */ + int (*add_fdir_entry)(struct phytmac *pdata, struct ethtool_rx_flow_spec *rx_flow); + int (*del_fdir_entry)(struct phytmac *pdata, struct ethtool_rx_flow_spec *rx_flow); + + /* mido ops */ + int (*enable_mdio_control)(struct phytmac *pdata, int enable); + int (*mdio_read)(struct phytmac *pdata, int mii_id, int regnum); + int (*mdio_write)(struct phytmac *pdata, int mii_id, + int regnum, u16 data); + int (*mdio_idle)(struct phytmac *pdata); + int (*mdio_read_c45)(struct phytmac *pdata, int mii_id, int devad, int regnum); + int (*mdio_write_c45)(struct phytmac *pdata, int mii_id, int devad, + int regnum, u16 data); + void (*enable_irq)(struct phytmac *pdata, int queue_index, u32 mask); + void (*disable_irq)(struct phytmac *pdata, int queue_index, u32 mask); + void (*clear_irq)(struct phytmac *pdata, int queue_index, u32 mask); + void (*mask_irq)(struct phytmac *pdata, int queue_index, u32 mask); + unsigned int (*get_irq)(struct phytmac *pdata, int queue_index); + unsigned int (*get_intx_mask)(struct phytmac *pdata); + unsigned int (*tx_map)(struct phytmac_queue *pdata, u32 tx_tail, + struct packet_info *packet); + void (*init_rx_map)(struct phytmac_queue *queue, u32 index); + unsigned int (*rx_map)(struct phytmac_queue *queue, u32 index, dma_addr_t addr); + void (*transmit)(struct phytmac_queue *queue); + void (*update_rx_tail)(struct phytmac_queue *queue); + int (*tx_complete)(const struct phytmac_dma_desc *desc); + bool (*rx_complete)(const struct phytmac_dma_desc *desc); + int (*get_rx_pkt_len)(struct phytmac *pdata, const struct phytmac_dma_desc *desc); + bool (*rx_checksum)(const struct phytmac_dma_desc *desc); + void (*set_desc_rxused)(struct phytmac_dma_desc *desc); + bool (*rx_single_buffer)(const struct phytmac_dma_desc *desc); + bool (*rx_pkt_start)(const struct phytmac_dma_desc *desc); + bool (*rx_pkt_end)(const struct phytmac_dma_desc *desc); + unsigned int (*zero_rx_desc_addr)(struct phytmac_dma_desc *desc); + unsigned int (*zero_tx_desc)(struct phytmac_dma_desc *desc); + void (*clear_rx_desc)(struct phytmac_queue *queue, int begin, int end); + void (*clear_tx_desc)(struct phytmac_queue *queue); + /* ptp */ + void (*init_ts_hw)(struct phytmac *pdata); + void (*get_time)(struct phytmac *pdata, struct timespec64 *ts); + void (*set_time)(struct phytmac *pdata, time64_t sec, long nsec); + int (*set_ts_config)(struct phytmac *pdata, struct ts_ctrl *ctrl); + void (*clear_time)(struct phytmac *pdata); + int (*set_incr)(struct phytmac *pdata, struct ts_incr *incr); + int (*adjust_fine)(struct phytmac *pdata, long ppm, bool negative); + int (*adjust_time)(struct phytmac *pdata, s64 delta, int neg); + int (*ts_valid)(struct phytmac *pdata, struct phytmac_dma_desc *desc, + int direction); + void (*get_timestamp)(struct phytmac *pdata, u32 ts_1, u32 ts_2, + struct timespec64 *ts); + unsigned int (*get_ts_rate)(struct phytmac *pdata); +}; + +/* mhu */ +#define PHYTMAC_MHU_AP_CPP_STAT 0x00 +#define PHYTMAC_MHU_AP_CPP_SET 0x04 +#define PHYTMAC_MHU_CPP_DATA0 0x18 +#define PHYTMAC_MHU_CPP_DATA1 0x1c + +#define PHYTMAC_MHU_STAT_BUSY_INDEX 0 +#define PHYTMAC_MHU_STAT_BUSY_WIDTH 1 + +#define PHYTMAC_MHU_SET_INDEX 0 +#define PHYTMAC_MHU_SET_WIDTH 1 + +#define PHYTMAC_DATA0_FREE_INDEX 0 +#define PHYTMAC_DATA0_FREE_WIDTH 1 +#define PHYTMAC_DATA0_DOMAIN_INDEX 1 +#define PHYTMAC_DATA0_DOMAIN_WIDTH 7 +#define PHYTMAC_DATA0_MSG_INDEX 8 +#define PHYTMAC_DATA0_MSG_WIDTH 8 +#define PHYTMAC_MSG_PM 0x04 +#define PHYTMAC_DATA0_PRO_INDEX 16 +#define PHYTMAC_DATA0_PRO_WIDTH 8 +#define PHYTMAC_PRO_ID 0x11 +#define PHYTMAC_DATA0_PAYLOAD_INDEX 24 +#define PHYTMAC_DATA0_PAYLOAD_WIDTH 8 + +#define PHYTMAC_DATA1_STAT_INDEX 0 +#define PHYTMAC_DATA1_STAT_WIDTH 28 +#define PHYTMAC_STATON 8 +#define PHYTMAC_STATOFF 0 +#define PHYTMAC_DATA1_MUST0_INDEX 28 +#define PHYTMAC_DATA1_MUST0_WIDTH 2 +#define PHYTMAC_DATA1_STATTYPE_INDEX 30 +#define PHYTMAC_DATA1_STATTYPE_WIDTH 1 +#define PHYTMAC_STATTYPE 0x1 +#define PHYTMAC_DATA1_MUST1_INDEX 31 +#define PHYTMAC_DATA1_MUST1_WIDTH 1 + +#define PHYTMAC_MHU_READ(_pdata, _reg) \ + __raw_readl((_pdata)->mhu_regs + (_reg)) +#define PHYTMAC_MHU_WRITE(_pdata, _reg, _val) \ + __raw_writel((_val), (_pdata)->mhu_regs + (_reg)) +#define PHYTMAC_READ_STAT(pdata) PHYTMAC_MHU_READ(pdata, PHYTMAC_MHU_AP_CPP_STAT) +#define PHYTMAC_READ_DATA0(pdata) PHYTMAC_MHU_READ(pdata, PHYTMAC_MHU_CPP_DATA0) +#define PHYTMAC_TIMEOUT 1000000000 /* in usecs */ + +#define PHYTMAC_GFP_FLAGS \ + (GFP_ATOMIC | __GFP_NOWARN | GFP_DMA | __GFP_DMA32) +#define PHYTMAC_RX_DMA_ATTR \ + (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING) +#define PHYTMAC_SKB_PAD (NET_SKB_PAD) +#define PHYTMAC_ETH_PKT_HDR_PAD (ETH_HLEN + ETH_FCS_LEN + (VLAN_HLEN * 2)) + +#define PHYTMAC_RXBUFFER_2048 2048 +#define PHYTMAC_MAX_FRAME_BUILD_SKB \ + (SKB_WITH_OVERHEAD(PHYTMAC_RXBUFFER_2048) - PHYTMAC_SKB_PAD) + +#define PHYTMAC_RX_PAGE_ORDER 0 +#define PHYTMAC_RX_PAGE_SIZE (PAGE_SIZE << PHYTMAC_RX_PAGE_ORDER) + +void __iomem * +phytmac_devm_ioremap_resource_np(struct device *dev, const struct resource *res); +struct phytmac_tx_skb *phytmac_get_tx_skb(struct phytmac_queue *queue, + unsigned int index); +inline struct phytmac_dma_desc *phytmac_get_tx_desc(struct phytmac_queue *queue, + unsigned int index); +inline struct phytmac_dma_desc *phytmac_get_rx_desc(struct phytmac_queue *queue, + unsigned int index); +void phytmac_set_ethtool_ops(struct net_device *netdev); +int phytmac_drv_probe(struct phytmac *pdata); +int phytmac_drv_remove(struct phytmac *pdata); +int phytmac_drv_suspend(struct phytmac *pdata); +int phytmac_drv_resume(struct phytmac *pdata); +void phytmac_drv_shutdown(struct phytmac *pdata); +struct phytmac *phytmac_alloc_pdata(struct device *dev); +void phytmac_free_pdata(struct phytmac *pdata); +int phytmac_reset_ringsize(struct phytmac *pdata, u32 rx_size, u32 tx_size); +void phytmac_set_bios_wol_enable(struct phytmac *pdata, u32 wol); +#endif diff --git a/drivers/net/ethernet/phytium/phytmac_ethtool.c b/drivers/net/ethernet/phytium/phytmac_ethtool.c new file mode 100644 index 00000000000000..cf6085d2dc04ff --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_ethtool.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. */ + +#include +#include +#include +#include +#include "phytmac.h" +#include "phytmac_v1.h" +#include "phytmac_v2.h" +#include "phytmac_ptp.h" + +static void phytmac_get_ethtool_stats(struct net_device *ndev, + struct ethtool_stats *stats, + u64 *data) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + int i = PHYTMAC_STATS_LEN, j; + int q; + struct phytmac_queue *queue; + unsigned long *stat; + + hw_if->get_stats(pdata); + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) + for (j = 0, stat = &queue->stats.rx_packets; j < QUEUE_STATS_LEN; ++j, ++stat) + pdata->ethtool_stats[i++] = *stat; + + memcpy(data, &pdata->ethtool_stats, sizeof(u64) + * (PHYTMAC_STATS_LEN + QUEUE_STATS_LEN * pdata->queues_num)); +} + +static inline int phytmac_get_sset_count(struct net_device *ndev, int sset) +{ + struct phytmac *pdata = netdev_priv(ndev); + + switch (sset) { + case ETH_SS_STATS: + return PHYTMAC_STATS_LEN + QUEUE_STATS_LEN * pdata->queues_num; + default: + return -EOPNOTSUPP; + } +} + +static void phytmac_get_ethtool_strings(struct net_device *ndev, u32 sset, u8 *p) +{ + char stat_string[ETH_GSTRING_LEN]; + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_queue *queue; + unsigned int i; + unsigned int q; + + switch (sset) { + case ETH_SS_STATS: + for (i = 0; i < PHYTMAC_STATS_LEN; i++, p += ETH_GSTRING_LEN) + memcpy(p, phytmac_statistics[i].stat_string, + ETH_GSTRING_LEN); + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + for (i = 0; i < QUEUE_STATS_LEN; i++, p += ETH_GSTRING_LEN) { + snprintf(stat_string, ETH_GSTRING_LEN, "q%d_%s", + q, queue_statistics[i].stat_string); + memcpy(p, stat_string, ETH_GSTRING_LEN); + } + } + break; + } +} + +static inline int phytmac_get_regs_len(struct net_device *ndev) +{ + return PHYTMAC_ETHTOOLD_REGS_LEN; +} + +static void phytmac_get_regs(struct net_device *ndev, + struct ethtool_regs *regs, + void *p) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + u32 *regs_buff = p; + + memset(p, 0, PHYTMAC_ETHTOOLD_REGS_LEN); + + hw_if->get_regs(pdata, regs_buff); +} + +static void phytmac_get_wol(struct net_device *ndev, struct ethtool_wolinfo *wol) +{ + struct phytmac *pdata = netdev_priv(ndev); + + wol->wolopts = 0; + phylink_ethtool_get_wol(pdata->phylink, wol); + + wol->supported = WAKE_MAGIC | WAKE_ARP | + WAKE_UCAST | WAKE_MCAST; + + if (pdata->wol & PHYTMAC_WAKE_MAGIC) + wol->wolopts |= WAKE_MAGIC; + if (pdata->wol & PHYTMAC_WAKE_ARP) + wol->wolopts |= WAKE_ARP; + if (pdata->wol & PHYTMAC_WAKE_UCAST) + wol->wolopts |= WAKE_UCAST; + if (pdata->wol & PHYTMAC_WAKE_MCAST) + wol->wolopts |= WAKE_MCAST; +} + +static int phytmac_set_wol(struct net_device *ndev, struct ethtool_wolinfo *wol) +{ + struct phytmac *pdata = netdev_priv(ndev); + int ret; + + ret = phylink_ethtool_set_wol(pdata->phylink, wol); + + if (!ret || ret != -EOPNOTSUPP) + return ret; + + pdata->wol = 0; + + if (wol->wolopts & WAKE_MAGIC) + pdata->wol |= PHYTMAC_WAKE_MAGIC; + if (wol->wolopts & WAKE_ARP) + pdata->wol |= PHYTMAC_WAKE_ARP; + if (wol->wolopts & WAKE_UCAST) + pdata->wol |= PHYTMAC_WAKE_UCAST; + if (wol->wolopts & WAKE_MCAST) + pdata->wol |= PHYTMAC_WAKE_MCAST; + + device_set_wakeup_enable(pdata->dev, pdata->wol ? 1 : 0); + phytmac_set_bios_wol_enable(pdata, pdata->wol ? 1 : 0); + + return 0; +} + +static void phytmac_get_ringparam(struct net_device *ndev, + struct ethtool_ringparam *ring, + struct kernel_ethtool_ringparam *kernel_ring, + struct netlink_ext_ack *extack) +{ + struct phytmac *pdata = netdev_priv(ndev); + + ring->rx_max_pending = MAX_RX_RING_SIZE; + ring->tx_max_pending = MAX_TX_RING_SIZE; + + ring->rx_pending = pdata->rx_ring_size; + ring->tx_pending = pdata->tx_ring_size; +} + +static int phytmac_set_ringparam(struct net_device *ndev, + struct ethtool_ringparam *ring, + struct kernel_ethtool_ringparam *kernel_ring, + struct netlink_ext_ack *extack) +{ + struct phytmac *pdata = netdev_priv(ndev); + u32 new_rx_size, new_tx_size; + + if (ring->rx_mini_pending || ring->rx_jumbo_pending) + return -EINVAL; + + new_rx_size = clamp_t(u32, ring->rx_pending, + MIN_RX_RING_SIZE, MAX_RX_RING_SIZE); + new_rx_size = roundup_pow_of_two(new_rx_size); + + new_tx_size = clamp_t(u32, ring->tx_pending, + MIN_TX_RING_SIZE, MAX_TX_RING_SIZE); + new_tx_size = roundup_pow_of_two(new_tx_size); + + if (EQUAL(new_tx_size, pdata->tx_ring_size) && + EQUAL(new_rx_size, pdata->rx_ring_size)) { + /* nothing to do */ + return 0; + } + + return phytmac_reset_ringsize(pdata, new_rx_size, new_tx_size); +} + +static int phytmac_get_ts_info(struct net_device *ndev, + struct kernel_ethtool_ts_info *info) +{ + struct phytmac *pdata = netdev_priv(ndev); + + if (IS_REACHABLE(CONFIG_PHYTMAC_ENABLE_PTP)) { + info->so_timestamping = + SOF_TIMESTAMPING_TX_SOFTWARE | + SOF_TIMESTAMPING_RX_SOFTWARE | + SOF_TIMESTAMPING_SOFTWARE | + SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + info->tx_types = + (1 << HWTSTAMP_TX_ONESTEP_SYNC) | + (1 << HWTSTAMP_TX_OFF) | + (1 << HWTSTAMP_TX_ON); + info->rx_filters = + (1 << HWTSTAMP_FILTER_NONE) | + (1 << HWTSTAMP_FILTER_ALL); + + info->phc_index = pdata->ptp_clock ? ptp_clock_index(pdata->ptp_clock) : -1; + + return 0; + } + + return ethtool_op_get_ts_info(ndev, info); +} + +static int phytmac_add_fdir_ethtool(struct net_device *ndev, + struct ethtool_rxnfc *cmd) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct ethtool_rx_flow_spec *fs = &cmd->fs; + struct ethtool_rx_fs_item *item, *newfs; + unsigned long flags; + int ret = -EINVAL; + bool added = false; + struct phytmac_hw_if *hw_if = pdata->hw_if; + + if (cmd->fs.location >= pdata->max_rx_fs || + cmd->fs.ring_cookie >= pdata->queues_num) { + return -EINVAL; + } + + newfs = kmalloc(sizeof(*newfs), GFP_KERNEL); + if (!newfs) + return -ENOMEM; + memcpy(&newfs->fs, fs, sizeof(newfs->fs)); + + netdev_dbg(ndev, "Adding flow filter entry,type=%u,queue=%u,loc=%u,src=%08X,dst=%08X,ps=%u,pd=%u\n", + fs->flow_type, (int)fs->ring_cookie, fs->location, + htonl(fs->h_u.tcp_ip4_spec.ip4src), + htonl(fs->h_u.tcp_ip4_spec.ip4dst), + htons(fs->h_u.tcp_ip4_spec.psrc), htons(fs->h_u.tcp_ip4_spec.pdst)); + + spin_lock_irqsave(&pdata->rx_fs_lock, flags); + + /* find correct place to add in list */ + list_for_each_entry(item, &pdata->rx_fs_list.list, list) { + if (item->fs.location > newfs->fs.location) { + list_add_tail(&newfs->list, &item->list); + added = true; + break; + } else if (item->fs.location == fs->location) { + netdev_err(ndev, "Rule not added: location %d not free!\n", + fs->location); + ret = -EBUSY; + goto err; + } + } + if (!added) + list_add_tail(&newfs->list, &pdata->rx_fs_list.list); + + hw_if->add_fdir_entry(pdata, fs); + pdata->rx_fs_list.count++; + + spin_unlock_irqrestore(&pdata->rx_fs_lock, flags); + return 0; + +err: + spin_unlock_irqrestore(&pdata->rx_fs_lock, flags); + kfree(newfs); + return ret; +} + +static int phytmac_del_fdir_ethtool(struct net_device *ndev, + struct ethtool_rxnfc *cmd) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct ethtool_rx_fs_item *item; + struct ethtool_rx_flow_spec *fs; + unsigned long flags; + struct phytmac_hw_if *hw_if = pdata->hw_if; + + spin_lock_irqsave(&pdata->rx_fs_lock, flags); + + list_for_each_entry(item, &pdata->rx_fs_list.list, list) { + if (item->fs.location == cmd->fs.location) { + /* disable screener regs for the flow entry */ + fs = &item->fs; + netdev_dbg(ndev, "Deleting flow filter entry,type=%u,queue=%u,loc=%u,src=%08X,dst=%08X,ps=%u,pd=%u\n", + fs->flow_type, (int)fs->ring_cookie, fs->location, + htonl(fs->h_u.tcp_ip4_spec.ip4src), + htonl(fs->h_u.tcp_ip4_spec.ip4dst), + htons(fs->h_u.tcp_ip4_spec.psrc), + htons(fs->h_u.tcp_ip4_spec.pdst)); + + hw_if->del_fdir_entry(pdata, fs); + + list_del(&item->list); + pdata->rx_fs_list.count--; + spin_unlock_irqrestore(&pdata->rx_fs_lock, flags); + kfree(item); + return 0; + } + } + + spin_unlock_irqrestore(&pdata->rx_fs_lock, flags); + return -EINVAL; +} + +static int phytmac_get_fdir_entry(struct net_device *ndev, + struct ethtool_rxnfc *cmd) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct ethtool_rx_fs_item *item; + + list_for_each_entry(item, &pdata->rx_fs_list.list, list) { + if (item->fs.location == cmd->fs.location) { + memcpy(&cmd->fs, &item->fs, sizeof(cmd->fs)); + return 0; + } + } + return -EINVAL; +} + +static int phytmac_get_all_fdir_entries(struct net_device *ndev, + struct ethtool_rxnfc *cmd, + u32 *rule_locs) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct ethtool_rx_fs_item *item; + u32 cnt = 0; + + list_for_each_entry(item, &pdata->rx_fs_list.list, list) { + if (cnt == cmd->rule_cnt) + return -EMSGSIZE; + rule_locs[cnt] = item->fs.location; + cnt++; + } + cmd->data = pdata->max_rx_fs; + cmd->rule_cnt = cnt; + + return 0; +} + +static int phytmac_get_rxnfc(struct net_device *ndev, + struct ethtool_rxnfc *cmd, + u32 *rule_locs) +{ + struct phytmac *pdata = netdev_priv(ndev); + int ret = 0; + + switch (cmd->cmd) { + case ETHTOOL_GRXRINGS: + cmd->data = pdata->queues_num; + break; + case ETHTOOL_GRXCLSRLCNT: + cmd->rule_cnt = pdata->rx_fs_list.count; + break; + case ETHTOOL_GRXCLSRULE: + ret = phytmac_get_fdir_entry(ndev, cmd); + break; + case ETHTOOL_GRXCLSRLALL: + ret = phytmac_get_all_fdir_entries(ndev, cmd, rule_locs); + break; + default: + netdev_err(ndev, "Command parameter %d is not supported\n", cmd->cmd); + ret = -EOPNOTSUPP; + } + + return ret; +} + +static int phytmac_set_rxnfc(struct net_device *ndev, struct ethtool_rxnfc *cmd) +{ + switch (cmd->cmd) { + case ETHTOOL_SRXCLSRLINS: + return phytmac_add_fdir_ethtool(ndev, cmd); + case ETHTOOL_SRXCLSRLDEL: + return phytmac_del_fdir_ethtool(ndev, cmd); + default: + netdev_err(ndev, "Command parameter %d is not supported\n", cmd->cmd); + return -EOPNOTSUPP; + } +} + +static int phytmac_get_link_ksettings(struct net_device *ndev, + struct ethtool_link_ksettings *kset) +{ + int ret = 0; + struct phytmac *pdata = netdev_priv(ndev); + u32 supported = 0; + u32 advertising = 0; + + if (!ndev->phydev) { + kset->base.port = PORT_FIBRE; + kset->base.transceiver = XCVR_INTERNAL; + if (netif_carrier_ok(ndev)) { + kset->base.duplex = pdata->duplex; + kset->base.speed = pdata->speed; + } else { + kset->base.duplex = DUPLEX_UNKNOWN; + kset->base.speed = SPEED_UNKNOWN; + } + + if (pdata->phy_interface == PHY_INTERFACE_MODE_USXGMII || + pdata->phy_interface == PHY_INTERFACE_MODE_10GBASER) { + supported = SUPPORTED_10000baseT_Full + | SUPPORTED_FIBRE | SUPPORTED_Pause; + advertising = ADVERTISED_10000baseT_Full + | ADVERTISED_FIBRE | ADVERTISED_Pause; + } else if (pdata->phy_interface == PHY_INTERFACE_MODE_2500BASEX) { + supported = SUPPORTED_2500baseX_Full + | SUPPORTED_FIBRE | SUPPORTED_Pause; + advertising = ADVERTISED_2500baseX_Full + | ADVERTISED_FIBRE | ADVERTISED_Pause; + } else if (pdata->phy_interface == PHY_INTERFACE_MODE_1000BASEX) { + supported = SUPPORTED_1000baseT_Full + | SUPPORTED_FIBRE | SUPPORTED_Pause; + advertising = ADVERTISED_1000baseT_Full + | ADVERTISED_FIBRE | ADVERTISED_Pause; + } else if (pdata->phy_interface == PHY_INTERFACE_MODE_SGMII) { + supported = SUPPORTED_1000baseT_Full + | SUPPORTED_FIBRE | SUPPORTED_Pause; + advertising = ADVERTISED_1000baseT_Full + | ADVERTISED_FIBRE | ADVERTISED_Pause; + } + + ethtool_convert_legacy_u32_to_link_mode(kset->link_modes.supported, + supported); + ethtool_convert_legacy_u32_to_link_mode(kset->link_modes.advertising, + advertising); + } else { + phy_ethtool_get_link_ksettings(ndev, kset); + } + + return ret; +} + +static int phytmac_set_link_ksettings(struct net_device *ndev, + const struct ethtool_link_ksettings *kset) +{ + int ret = 0; + + if (!ndev->phydev) { + netdev_err(ndev, "Without a PHY, setting link is not supported\n"); + ret = -EOPNOTSUPP; + } else { + phy_ethtool_set_link_ksettings(ndev, kset); + } + + return ret; +} + +static inline void phytmac_get_pauseparam(struct net_device *ndev, + struct ethtool_pauseparam *pause) +{ + struct phytmac *pdata = netdev_priv(ndev); + + pause->rx_pause = pdata->pause; + pause->tx_pause = pdata->pause; +} + +static int phytmac_set_pauseparam(struct net_device *ndev, + struct ethtool_pauseparam *pause) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + + if (pause->rx_pause != pdata->pause) + hw_if->enable_pause(pdata, pause->rx_pause); + + pdata->pause = pause->rx_pause; + + return 0; +} + +static inline void phytmac_get_channels(struct net_device *ndev, + struct ethtool_channels *ch) +{ + struct phytmac *pdata = netdev_priv(ndev); + + ch->max_combined = pdata->queues_max_num; + ch->combined_count = pdata->queues_num; +} + +static int phytmac_set_channels(struct net_device *ndev, + struct ethtool_channels *ch) +{ + struct phytmac *pdata = netdev_priv(ndev); + + if (netif_running(ndev)) + return -EBUSY; + + if (ch->combined_count > pdata->queues_max_num) { + netdev_err(ndev, "combined channel count cannot exceed %u\n", + ch->combined_count); + + return -EINVAL; + } + + pdata->queues_num = ch->combined_count; + + return 0; +} + +static inline u32 phytmac_get_msglevel(struct net_device *ndev) +{ + struct phytmac *pdata = netdev_priv(ndev); + + return pdata->msg_enable; +} + +static inline void phytmac_set_msglevel(struct net_device *ndev, u32 level) +{ + struct phytmac *pdata = netdev_priv(ndev); + + pdata->msg_enable = level; +} + +static void phytmac_get_drvinfo(struct net_device *ndev, struct ethtool_drvinfo *drvinfo) +{ + struct phytmac *pdata = netdev_priv(ndev); + + strscpy(drvinfo->version, PHYTMAC_DRIVER_VERSION, sizeof(drvinfo->version)); + strscpy(drvinfo->fw_version, pdata->fw_version, sizeof(drvinfo->fw_version)); + + if (pdata->platdev) { + strscpy(drvinfo->driver, PHYTMAC_PLAT_DRV_NAME, sizeof(drvinfo->driver)); + strscpy(drvinfo->bus_info, pdata->platdev->name, sizeof(drvinfo->bus_info)); + } else if (pdata->pcidev) { + strscpy(drvinfo->driver, PHYTMAC_PCI_DRV_NAME, sizeof(drvinfo->driver)); + strscpy(drvinfo->bus_info, pci_name(pdata->pcidev), sizeof(drvinfo->bus_info)); + } +} + +static const struct ethtool_ops phytmac_ethtool_ops = { + .get_regs_len = phytmac_get_regs_len, + .get_regs = phytmac_get_regs, + .get_msglevel = phytmac_get_msglevel, + .set_msglevel = phytmac_set_msglevel, + .get_link = ethtool_op_get_link, + .get_ts_info = phytmac_get_ts_info, + .get_ethtool_stats = phytmac_get_ethtool_stats, + .get_strings = phytmac_get_ethtool_strings, + .get_sset_count = phytmac_get_sset_count, + .get_link_ksettings = phytmac_get_link_ksettings, + .set_link_ksettings = phytmac_set_link_ksettings, + .get_ringparam = phytmac_get_ringparam, + .set_ringparam = phytmac_set_ringparam, + .get_rxnfc = phytmac_get_rxnfc, + .set_rxnfc = phytmac_set_rxnfc, + .get_pauseparam = phytmac_get_pauseparam, + .set_pauseparam = phytmac_set_pauseparam, + .get_channels = phytmac_get_channels, + .set_channels = phytmac_set_channels, + .get_wol = phytmac_get_wol, + .set_wol = phytmac_set_wol, + .get_drvinfo = phytmac_get_drvinfo, +}; + +void phytmac_set_ethtool_ops(struct net_device *ndev) +{ + ndev->ethtool_ops = &phytmac_ethtool_ops; +} + diff --git a/drivers/net/ethernet/phytium/phytmac_main.c b/drivers/net/ethernet/phytium/phytmac_main.c new file mode 100644 index 00000000000000..0d6bdb44cf516b --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_main.c @@ -0,0 +1,2967 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Phytium Ethernet Controller driver + * + * Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phytmac.h" +#include "phytmac_ptp.h" + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0=none,...,16=all)"); + +#define RX_BUFFER_MULTIPLE 64 /* bytes */ +#define MAX_MTU 3072 +#define RING_ADDR_INTERVAL 128 +#define MAX_RING_ADDR_ALLOC_TIMES 3 + +#define RX_RING_BYTES(pdata) (sizeof(struct phytmac_dma_desc) \ + * (pdata)->rx_ring_size) + +#define TX_RING_BYTES(pdata) (sizeof(struct phytmac_dma_desc)\ + * (pdata)->tx_ring_size) + +/* Max length of transmit frame must be a multiple of 8 bytes */ +#define PHYTMAC_TX_LEN_ALIGN 8 +/* Limit maximum TX length as per TSO errata. This is to avoid a + * false amba_error in TX path from the DMA assuming there is not enough + * space in the SRAM (16KB) even when there is. + */ + +static int phytmac_xdp_xmit_back(struct phytmac *pdata, struct xdp_buff *xdp); + +static inline int phytmac_calc_rx_buf_len(void) +{ +#if (PAGE_SIZE < 8192) + return rounddown(PHYTMAC_MAX_FRAME_BUILD_SKB, RX_BUFFER_MULTIPLE); +#endif + return rounddown(PHYTMAC_RXBUFFER_2048, RX_BUFFER_MULTIPLE); +} + +static int phytmac_queue_phyaddr_check(struct phytmac *pdata, dma_addr_t ring_base_addr, + int offset) +{ + u32 bus_addr_high; + int i; + + /* Check the high address of the DMA ring. */ + bus_addr_high = upper_32_bits(ring_base_addr); + for (i = 1; i < pdata->queues_num; i++) { + ring_base_addr += offset; + if (bus_addr_high != upper_32_bits(ring_base_addr)) + return -EFAULT; + } + + return 0; +} + +static int phytmac_change_mtu(struct net_device *ndev, int new_mtu) +{ + struct phytmac *pdata = netdev_priv(ndev); + int max_frame = new_mtu + PHYTMAC_ETH_PKT_HDR_PAD; + + if (netif_running(ndev)) + return -EBUSY; + + if (pdata->xdp_prog) { + if (max_frame > phytmac_calc_rx_buf_len()) { + netdev_warn(pdata->ndev, + "Requested MTU size is not supported with XDP.\n"); + return -EINVAL; + } + } + + if (new_mtu > MAX_MTU) { + netdev_info(ndev, "Can not set MTU over %d.\n", MAX_MTU); + return -EINVAL; + } + + ndev->mtu = new_mtu; + + return 0; +} + +static int phytmac_set_mac_address(struct net_device *netdev, void *addr) +{ + struct phytmac *pdata = netdev_priv(netdev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct sockaddr *saddr = addr; + + if (netif_msg_drv(pdata)) + netdev_info(netdev, "phytmac set mac address"); + + if (!is_valid_ether_addr(saddr->sa_data)) + return -EADDRNOTAVAIL; + + eth_hw_addr_set(netdev, saddr->sa_data); + + hw_if->set_mac_address(pdata, saddr->sa_data); + + phytmac_set_bios_wol_enable(pdata, pdata->wol ? 1 : 0); + + return 0; +} + +static int phytmac_get_mac_address(struct phytmac *pdata) +{ + struct phytmac_hw_if *hw_if = pdata->hw_if; + u8 addr[6]; + + hw_if->get_mac_address(pdata, addr); + + if (is_valid_ether_addr(addr)) { + eth_hw_addr_set(pdata->ndev, addr); + ether_addr_copy(pdata->ndev->perm_addr, addr); + return 0; + } + dev_info(pdata->dev, "invalid hw address, using random\n"); + eth_hw_addr_random(pdata->ndev); + ether_addr_copy(pdata->ndev->perm_addr, pdata->ndev->dev_addr); + + return 0; +} + +static int phytmac_mdio_read_c22(struct mii_bus *bus, int mii_id, int regnum) +{ + struct phytmac *pdata = bus->priv; + struct phytmac_hw_if *hw_if = pdata->hw_if; + int data; + + data = hw_if->mdio_read(pdata, mii_id, regnum); + + if (netif_msg_link(pdata)) + netdev_info(pdata->ndev, "mdio read c22 mii_id=%d, regnum=%x, data=%x\n", + mii_id, regnum, data); + + return data; +} + +static int phytmac_mdio_write_c22(struct mii_bus *bus, int mii_id, int regnum, u16 data) +{ + struct phytmac *pdata = bus->priv; + struct phytmac_hw_if *hw_if = pdata->hw_if; + int ret; + + ret = hw_if->mdio_write(pdata, mii_id, regnum, data); + if (ret) + netdev_err(pdata->ndev, "mdio %d, reg %x, data %x write failed!\n", + mii_id, regnum, data); + + if (netif_msg_link(pdata)) + netdev_info(pdata->ndev, "mdio write c22 mii_id=%d, regnum=%x, data=%x\n", + mii_id, regnum, data); + + return 0; +} + +static int phytmac_mdio_read_c45(struct mii_bus *bus, int mii_id, int devad, int regnum) +{ + struct phytmac *pdata = bus->priv; + struct phytmac_hw_if *hw_if = pdata->hw_if; + int data; + + data = hw_if->mdio_read_c45(pdata, mii_id, devad, regnum); + + if (netif_msg_link(pdata)) + netdev_info(pdata->ndev, "mdio read c45 mii_id=%d, regnum=%x, data=%x\n", + mii_id, regnum, data); + + return data; +} + +static int phytmac_mdio_write_c45(struct mii_bus *bus, int mii_id, int devad, int regnum, u16 data) +{ + struct phytmac *pdata = bus->priv; + struct phytmac_hw_if *hw_if = pdata->hw_if; + int ret; + + ret = hw_if->mdio_write_c45(pdata, mii_id, devad, regnum, data); + if (ret) + netdev_err(pdata->ndev, "mdio %d, reg %x, data %x write failed!\n", + mii_id, regnum, data); + + if (netif_msg_link(pdata)) + netdev_info(pdata->ndev, "mdio write c45 mii_id=%d, regnum=%x, data=%x\n", + mii_id, regnum, data); + + return 0; +} +static inline int hash_bit_value(int bitnr, __u8 *addr) +{ + if (addr[bitnr / 8] & (1 << (bitnr % 8))) + return 1; + return 0; +} + +/* Return the hash index value for the specified address. */ +static int phytmac_get_hash_index(__u8 *addr) +{ + int i, j, bitval; + int hash_index = 0; + + for (j = 0; j < 6; j++) { + for (i = 0, bitval = 0; i < 8; i++) + bitval ^= hash_bit_value(i * 6 + j, addr); + + hash_index |= (bitval << j); + } + + return hash_index; +} + +static void phytmac_set_mac_hash_table(struct net_device *ndev) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct netdev_hw_addr *ha; + unsigned long mc_filter[2]; + unsigned int bitnr; + + mc_filter[0] = 0; + mc_filter[1] = 0; + + netdev_for_each_mc_addr(ha, ndev) { + bitnr = phytmac_get_hash_index(ha->addr); + mc_filter[bitnr >> 5] |= 1 << (bitnr & 31); + } + + hw_if->set_hash_table(pdata, mc_filter); +} + +/* Enable/Disable promiscuous and multicast modes. */ +static void phytmac_set_rx_mode(struct net_device *ndev) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + + hw_if->enable_promise(pdata, ndev->flags & IFF_PROMISC); + + hw_if->enable_multicast(pdata, ndev->flags & IFF_ALLMULTI); + if (!netdev_mc_empty(ndev)) + phytmac_set_mac_hash_table(ndev); +} + +static struct net_device_stats *phytmac_get_stats(struct net_device *dev) +{ + struct phytmac *pdata = netdev_priv(dev); + struct net_device_stats *nstat = &pdata->ndev->stats; + struct phytmac_stats *stat = &pdata->stats; + struct phytmac_hw_if *hw_if = pdata->hw_if; + + if (pdata->power_state == PHYTMAC_POWEROFF) + return nstat; + + hw_if->get_stats(pdata); + + nstat->rx_errors = (stat->rx_fcs_errors + + stat->rx_alignment_errors + + stat->rx_overruns + + stat->rx_oversize_packets + + stat->rx_jabbers + + stat->rx_undersized_packets + + stat->rx_length_errors); + nstat->rx_dropped = stat->rx_resource_over; + nstat->tx_errors = (stat->tx_late_collisions + + stat->tx_excessive_collisions + + stat->tx_underrun + + stat->tx_carrier_sense_errors); + nstat->multicast = stat->rx_mcast_packets; + nstat->collisions = (stat->tx_single_collisions + + stat->tx_multiple_collisions + + stat->tx_excessive_collisions + + stat->tx_late_collisions); + nstat->rx_length_errors = (stat->rx_oversize_packets + + stat->rx_jabbers + + stat->rx_undersized_packets + + stat->rx_length_errors); + nstat->rx_over_errors = stat->rx_resource_over; + nstat->rx_crc_errors = stat->rx_fcs_errors; + nstat->rx_frame_errors = stat->rx_alignment_errors; + nstat->rx_fifo_errors = stat->rx_overruns; + nstat->tx_aborted_errors = stat->tx_excessive_collisions; + nstat->tx_carrier_errors = stat->tx_carrier_sense_errors; + nstat->tx_fifo_errors = stat->tx_underrun; + + return nstat; +} + +inline struct phytmac_dma_desc *phytmac_get_rx_desc(struct phytmac_queue *queue, + unsigned int index) +{ + return &queue->rx_ring[index & (queue->pdata->rx_ring_size - 1)]; +} + +struct phytmac_tx_skb *phytmac_get_tx_skb(struct phytmac_queue *queue, + unsigned int index) +{ + return &queue->tx_skb[index & (queue->pdata->tx_ring_size - 1)]; +} + +inline struct phytmac_dma_desc *phytmac_get_tx_desc(struct phytmac_queue *queue, + unsigned int index) +{ + return &queue->tx_ring[index & (queue->pdata->tx_ring_size - 1)]; +} + +static void phytmac_rx_unmap(struct phytmac_queue *queue) +{ + struct phytmac_rx_buffer *rx_buffer_info; + struct phytmac *pdata = queue->pdata; + int i; + + if (queue->rx_buffer_info) { + /* Free all the Rx ring sk_buffs */ + i = queue->rx_tail; + + while (i != queue->rx_next_to_alloc) { + rx_buffer_info = &queue->rx_buffer_info[i]; + + /* Invalidate cache lines that may have been written to by + * device so that we avoid corrupting memory. + */ + dma_sync_single_range_for_cpu(pdata->dev, + rx_buffer_info->addr, + rx_buffer_info->page_offset, + pdata->rx_buffer_len, + DMA_FROM_DEVICE); + + /* free resources associated with mapping */ + dma_unmap_page_attrs(pdata->dev, + rx_buffer_info->addr, + PHYTMAC_RX_PAGE_SIZE, + DMA_FROM_DEVICE, + PHYTMAC_RX_DMA_ATTR); + + __page_frag_cache_drain(rx_buffer_info->page, + rx_buffer_info->pagecnt_bias); + + i++; + if (i == pdata->rx_ring_size) + i = 0; + } + + queue->rx_tail = 0; + queue->rx_head = 0; + queue->rx_next_to_alloc = 0; + } +} + +static int phytmac_free_tx_resource(struct phytmac *pdata) +{ + struct phytmac_queue *queue; + struct phytmac_dma_desc *tx_ring_base = NULL; + dma_addr_t tx_ring_base_addr; + unsigned int q; + int tx_offset; + int size; + + queue = pdata->queues; + if (queue->tx_ring) { + tx_ring_base = queue->tx_ring; + tx_ring_base_addr = queue->tx_ring_addr; + } + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + kfree(queue->tx_skb); + queue->tx_skb = NULL; + + if (queue->tx_ring) + queue->tx_ring = NULL; + } + + if (tx_ring_base) { + tx_offset = TX_RING_BYTES(pdata) + pdata->tx_bd_prefetch + RING_ADDR_INTERVAL; + tx_offset = ALIGN(tx_offset, 4096); + size = pdata->queues_num * tx_offset; + dma_free_coherent(pdata->dev, size, tx_ring_base, tx_ring_base_addr); + } + + return 0; +} + +static int phytmac_free_rx_resource(struct phytmac *pdata) +{ + struct phytmac_queue *queue; + struct phytmac_dma_desc *rx_ring_base = NULL; + dma_addr_t rx_ring_base_addr; + unsigned int q; + int rx_offset; + int size; + + queue = pdata->queues; + if (queue->rx_ring) { + rx_ring_base = queue->rx_ring; + rx_ring_base_addr = queue->rx_ring_addr; + } + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + phytmac_rx_unmap(queue); + + if (queue->rx_ring) + queue->rx_ring = NULL; + + if (queue->xdp_prog) { + queue->xdp_prog = NULL; + xdp_rxq_info_unreg(&queue->xdp_rxq); + } + + if (queue->rx_buffer_info) { + vfree(queue->rx_buffer_info); + queue->rx_buffer_info = NULL; + } + } + + if (rx_ring_base) { + rx_offset = RX_RING_BYTES(pdata) + pdata->rx_bd_prefetch + RING_ADDR_INTERVAL; + rx_offset = ALIGN(rx_offset, 4096); + size = pdata->queues_num * rx_offset; + dma_free_coherent(pdata->dev, size, rx_ring_base, rx_ring_base_addr); + } + + return 0; +} + +static int phytmac_alloc_tx_resource(struct phytmac *pdata) +{ + struct phytmac_queue *queue; + struct phytmac_dma_desc *tx_ring_base; + dma_addr_t tx_ring_base_addr; + unsigned int q; + int tx_offset; + int tx_size; + int size = 0; + int ret, i; + + tx_offset = TX_RING_BYTES(pdata) + pdata->tx_bd_prefetch + RING_ADDR_INTERVAL; + tx_offset = ALIGN(tx_offset, 4096); + tx_size = pdata->queues_num * tx_offset; + for (i = 0; i < MAX_RING_ADDR_ALLOC_TIMES + 1; i++) { + if (i == MAX_RING_ADDR_ALLOC_TIMES) + goto err; + + tx_ring_base = dma_alloc_coherent(pdata->dev, tx_size, + &tx_ring_base_addr, GFP_KERNEL); + if (!tx_ring_base) + continue; + + ret = phytmac_queue_phyaddr_check(pdata, tx_ring_base_addr, + tx_offset); + if (ret) { + dma_free_coherent(pdata->dev, tx_size, tx_ring_base, + tx_ring_base_addr); + continue; + } else { + break; + } + } + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + queue->tx_ring = (void *)tx_ring_base + q * tx_offset; + queue->tx_ring_addr = tx_ring_base_addr + q * tx_offset; + if (!queue->tx_ring) + goto err; + + if (netif_msg_drv(pdata)) + netdev_info(pdata->ndev, + "Allocated TX ring for queue %u of %d bytes at %08lx\n", + q, tx_offset, (unsigned long)queue->tx_ring_addr); + + size = pdata->tx_ring_size * sizeof(struct phytmac_tx_skb); + queue->tx_skb = kzalloc(size, GFP_KERNEL); + if (!queue->tx_skb) + goto err; + + if (netif_msg_drv(pdata)) + netdev_info(pdata->ndev, + "Allocated %d TX struct tx_skb entries at %p\n", + pdata->tx_ring_size, queue->tx_skb); + } + + return 0; +err: + phytmac_free_tx_resource(pdata); + + return -ENOMEM; +} + +static int phytmac_alloc_rx_resource(struct phytmac *pdata) +{ + struct phytmac_queue *queue; + struct phytmac_dma_desc *rx_ring_base; + dma_addr_t rx_ring_base_addr; + int rx_offset; + int rx_size; + unsigned int q; + int size = 0; + int ret, i; + + rx_offset = RX_RING_BYTES(pdata) + pdata->rx_bd_prefetch + RING_ADDR_INTERVAL; + rx_offset = ALIGN(rx_offset, 4096); + rx_size = pdata->queues_num * rx_offset; + for (i = 0; i < MAX_RING_ADDR_ALLOC_TIMES + 1; i++) { + if (i == MAX_RING_ADDR_ALLOC_TIMES) + goto err; + + rx_ring_base = dma_alloc_coherent(pdata->dev, rx_size, + &rx_ring_base_addr, GFP_KERNEL); + if (!rx_ring_base) + continue; + + ret = phytmac_queue_phyaddr_check(pdata, rx_ring_base_addr, + rx_offset); + if (ret) { + dma_free_coherent(pdata->dev, rx_size, rx_ring_base, + rx_ring_base_addr); + continue; + } else { + break; + } + } + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + queue->rx_ring = (void *)rx_ring_base + q * rx_offset; + queue->rx_ring_addr = rx_ring_base_addr + q * rx_offset; + if (!queue->rx_ring) + goto err; + + if (netif_msg_drv(pdata)) + netdev_info(pdata->ndev, + "Allocated RX ring for queue %u of %d bytes at %08lx\n", + q, rx_offset, (unsigned long)queue->rx_ring_addr); + + size = pdata->rx_ring_size * sizeof(struct phytmac_rx_buffer); + queue->rx_buffer_info = vzalloc(size); + if (!queue->rx_buffer_info) + goto err; + + memset(&queue->xdp_rxq, 0, sizeof(queue->xdp_rxq)); + WRITE_ONCE(queue->xdp_prog, pdata->xdp_prog); + + /* XDP RX-queue info */ + ret = xdp_rxq_info_reg(&queue->xdp_rxq, queue->pdata->ndev, q, 0); + if (ret < 0) { + netdev_err(pdata->ndev, "Failed to register xdp_rxq index %u\n", q); + goto err; + } + + xdp_rxq_info_unreg_mem_model(&queue->xdp_rxq); + WARN_ON(xdp_rxq_info_reg_mem_model(&queue->xdp_rxq, + MEM_TYPE_PAGE_SHARED, NULL)); + } + + return 0; +err: + phytmac_free_rx_resource(pdata); + + return -ENOMEM; +} + +static int phytmac_alloc_resource(struct phytmac *pdata) +{ + int ret; + + pdata->rx_buffer_len = phytmac_calc_rx_buf_len(); + + if (netif_msg_drv(pdata)) + netdev_info(pdata->ndev, "alloc resource, rx_buffer_len = %d\n", + pdata->rx_buffer_len); + + ret = phytmac_alloc_tx_resource(pdata); + if (ret) + return ret; + + ret = phytmac_alloc_rx_resource(pdata); + if (ret) { + phytmac_free_tx_resource(pdata); + return ret; + } + + return 0; +} + +static void phytmac_free_resource(struct phytmac *pdata) +{ + phytmac_free_tx_resource(pdata); + phytmac_free_rx_resource(pdata); +} + +static irqreturn_t phytmac_irq(int irq, void *data) +{ + struct phytmac_queue *queue = data; + struct phytmac *pdata = queue->pdata; + struct phytmac_hw_if *hw_if = pdata->hw_if; + u32 status; + + status = hw_if->get_irq(pdata, queue->index); + + if (netif_msg_intr(pdata)) + netdev_info(pdata->ndev, "phymac irq status = %x\n", status); + + if (unlikely(!status)) + return IRQ_NONE; + + while (status) { + if (status & pdata->rx_irq_mask) { + /* Disable RX interrupts */ + hw_if->disable_irq(pdata, queue->index, pdata->rx_irq_mask); + hw_if->clear_irq(pdata, queue->index, PHYTMAC_INT_RX_COMPLETE); + + if (napi_schedule_prep(&queue->rx_napi)) + __napi_schedule(&queue->rx_napi); + } + + if (status & (PHYTMAC_INT_TX_COMPLETE)) { + /* Disable TX interrupts */ + hw_if->disable_irq(pdata, queue->index, PHYTMAC_INT_TX_COMPLETE); + hw_if->clear_irq(pdata, queue->index, PHYTMAC_INT_TX_COMPLETE); + + if (napi_schedule_prep(&queue->tx_napi)) + __napi_schedule(&queue->tx_napi); + } + + if (status & PHYTMAC_INT_TX_ERR) + hw_if->clear_irq(pdata, queue->index, PHYTMAC_INT_TX_ERR); + + if (status & PHYTMAC_INT_RX_OVERRUN) { + hw_if->clear_irq(pdata, queue->index, PHYTMAC_INT_RX_OVERRUN); + pdata->stats.rx_overruns++; + } + status = hw_if->get_irq(pdata, queue->index); + } + + return IRQ_HANDLED; +} + +static irqreturn_t phytmac_intx_irq(int irq, void *data) +{ + struct phytmac *pdata = data; + struct phytmac_hw_if *hw_if = pdata->hw_if; + u32 irq_mask; + int i; + + irq_mask = hw_if->get_intx_mask(pdata); + + if (unlikely(!irq_mask)) + return IRQ_NONE; + + for (i = 0; i < pdata->queues_num; i++) { + if (irq_mask & BIT(i)) + phytmac_irq(irq, &pdata->queues[i]); + } + + return IRQ_HANDLED; +} + +static void phytmac_dump_pkt(struct phytmac *pdata, struct sk_buff *skb, bool tx) +{ + struct net_device *ndev = pdata->ndev; + + if (tx) { + netdev_dbg(ndev, "start_xmit: queue %u len %u head %p data %p tail %p end %p\n", + skb->queue_mapping, skb->len, skb->head, skb->data, + skb_tail_pointer(skb), skb_end_pointer(skb)); + } else { + netdev_dbg(ndev, "queue %u received skb of length %u, csum: %08x\n", + skb->queue_mapping, skb->len, skb->csum); + print_hex_dump(KERN_DEBUG, " mac: ", DUMP_PREFIX_ADDRESS, 16, 1, + skb_mac_header(skb), 16, true); + } + + print_hex_dump(KERN_DEBUG, "data: ", DUMP_PREFIX_OFFSET, 16, 1, + skb->data, skb->len, true); +} + +static bool phytmac_alloc_mapped_page(struct phytmac *pdata, + struct phytmac_rx_buffer *bi) +{ + struct page *page = bi->page; + dma_addr_t dma; + + /* since we are recycling buffers we should seldom need to alloc */ + if (likely(page)) + return true; + + /* alloc new page for storage */ + page = __dev_alloc_pages(PHYTMAC_GFP_FLAGS, PHYTMAC_RX_PAGE_ORDER); + if (unlikely(!page)) { + netdev_err(pdata->ndev, "rx alloc page failed\n"); + return false; + } + + /* map page for use */ + dma = dma_map_page_attrs(pdata->dev, page, 0, + PHYTMAC_RX_PAGE_SIZE, + DMA_FROM_DEVICE, PHYTMAC_RX_DMA_ATTR); + if (dma_mapping_error(pdata->dev, dma)) { + __free_pages(page, PHYTMAC_RX_PAGE_ORDER); + return false; + } + + bi->addr = dma; + bi->page = page; + bi->page_offset = PHYTMAC_SKB_PAD; + page_ref_add(page, USHRT_MAX - 1); + bi->pagecnt_bias = USHRT_MAX; + + return true; +} + +static bool phytmac_can_reuse_rx_page(struct phytmac_rx_buffer *rx_buffer) +{ + unsigned int pagecnt_bias = rx_buffer->pagecnt_bias; + struct page *page = rx_buffer->page; + + /* avoid re-using remote and pfmemalloc pages */ + if (!dev_page_is_reusable(page)) + return false; + +#if (PAGE_SIZE < 8192) + /* if we are only owner of page we can reuse it */ + if (unlikely((page_ref_count(page) - pagecnt_bias) > 1)) + return false; +#else +#define PHYTMAC_LAST_OFFSET \ + (SKB_WITH_OVERHEAD(PAGE_SIZE) - PHYTMAC_RXBUFFER_2048) + + if (rx_buffer->page_offset > PHYTMAC_LAST_OFFSET) + return false; +#endif + + /* If we have drained the page fragment pool we need to update + * the pagecnt_bias and page count so that we fully restock the + * number of references the driver holds. + */ + if (unlikely(pagecnt_bias == 1)) { + page_ref_add(page, USHRT_MAX - 1); + rx_buffer->pagecnt_bias = USHRT_MAX; + } + + return true; +} + +static void phytmac_reuse_rx_page(struct phytmac_queue *queue, + struct phytmac_rx_buffer *old_buff) +{ + struct phytmac_rx_buffer *new_buff; + struct phytmac *pdata = queue->pdata; + u16 nta = queue->rx_next_to_alloc; + + new_buff = &queue->rx_buffer_info[nta & (pdata->rx_ring_size - 1)]; + + /* update, and store next to alloc */ + nta++; + queue->rx_next_to_alloc = (nta < pdata->rx_ring_size) ? nta : 0; + + /* Transfer page from old buffer to new buffer. + * Move each member individually to avoid possible store + * forwarding stalls. + */ + new_buff->addr = old_buff->addr; + new_buff->page = old_buff->page; + new_buff->page_offset = old_buff->page_offset; + new_buff->pagecnt_bias = old_buff->pagecnt_bias; +} + +static struct phytmac_rx_buffer *phytmac_get_rx_buffer(struct phytmac_queue *queue, + unsigned int index, + const unsigned int size) +{ + struct phytmac_rx_buffer *rx_buffer; + struct phytmac *pdata = queue->pdata; + + rx_buffer = &queue->rx_buffer_info[index & (pdata->rx_ring_size - 1)]; + prefetchw(rx_buffer->page); + + /* we are reusing so sync this buffer for CPU use */ + dma_sync_single_range_for_cpu(pdata->dev, + rx_buffer->addr, + rx_buffer->page_offset, + size, + DMA_FROM_DEVICE); + + rx_buffer->pagecnt_bias--; + + return rx_buffer; +} + +static void phytmac_put_rx_buffer(struct phytmac_queue *queue, + struct phytmac_rx_buffer *rx_buffer) +{ + struct phytmac *pdata = queue->pdata; + + if (phytmac_can_reuse_rx_page(rx_buffer)) { + /* hand second half of page back to the ring */ + phytmac_reuse_rx_page(queue, rx_buffer); + } else { + dma_unmap_page_attrs(pdata->dev, rx_buffer->addr, + PHYTMAC_RX_PAGE_SIZE, + DMA_FROM_DEVICE, PHYTMAC_RX_DMA_ATTR); + __page_frag_cache_drain(rx_buffer->page, + rx_buffer->pagecnt_bias); + } + + /* clear contents of rx_buffer */ + rx_buffer->page = NULL; +} + +static void phytmac_add_rx_frag(struct phytmac_queue *queue, + struct phytmac_rx_buffer *rx_buffer, + struct sk_buff *skb, + unsigned int size) +{ + unsigned int truesize; + +#if (PAGE_SIZE < 8192) + truesize = PHYTMAC_RX_PAGE_SIZE / 2; +#else + truesize = SKB_DATA_ALIGN(PHYTMAC_SKB_PAD + size); +#endif + + skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, rx_buffer->page, + rx_buffer->page_offset, size, truesize); +#if (PAGE_SIZE < 8192) + rx_buffer->page_offset ^= truesize; +#else + rx_buffer->page_offset += truesize; +#endif +} + +static struct sk_buff *phytmac_build_skb(struct phytmac_rx_buffer *rx_buffer, + unsigned int size) +{ + struct sk_buff *skb; + unsigned int truesize; + void *va; + +#if (PAGE_SIZE < 8192) + truesize = PHYTMAC_RX_PAGE_SIZE / 2; +#else + truesize = SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) + + SKB_DATA_ALIGN(PHYTMAC_SKB_PAD + size); +#endif + + va = page_address(rx_buffer->page) + rx_buffer->page_offset; + /* prefetch first cache line of first page */ + prefetch(va); + + /* build an skb around the page buffer */ + skb = build_skb(va - PHYTMAC_SKB_PAD, truesize); + if (unlikely(!skb)) + return NULL; + + /* update pointers within the skb to store the data */ + skb_reserve(skb, PHYTMAC_SKB_PAD); + __skb_put(skb, size); + + /* update buffer offset */ +#if (PAGE_SIZE < 8192) + rx_buffer->page_offset ^= truesize; +#else + rx_buffer->page_offset += truesize; +#endif + + return skb; +} + +static void phytmac_rx_buffer_flip(struct phytmac_rx_buffer *rx_buffer, unsigned int size) +{ + unsigned int truesize; + +#if (PAGE_SIZE < 8192) + truesize = PHYTMAC_RX_PAGE_SIZE / 2; + rx_buffer->page_offset ^= truesize; +#else + truesize = SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) + + SKB_DATA_ALIGN(PHYTMAC_SKB_PAD + size); + rx_buffer->page_offset += truesize; +#endif +} + +static struct sk_buff *phytmac_run_xdp(struct phytmac *pdata, + struct xdp_buff *xdp) +{ + int err, result = PHYTMAC_XDP_PASS; + struct bpf_prog *xdp_prog; + u32 act; + + rcu_read_lock(); + xdp_prog = READ_ONCE(pdata->xdp_prog); + + if (!xdp_prog) + goto xdp_out; + + prefetchw(xdp->data_hard_start); /* xdp_frame write */ + + act = bpf_prog_run_xdp(xdp_prog, xdp); + switch (act) { + case XDP_PASS: + break; + case XDP_TX: + result = phytmac_xdp_xmit_back(pdata, xdp); + if (result == PHYTMAC_XDP_CONSUMED) + goto out_failure; + break; + case XDP_REDIRECT: + err = xdp_do_redirect(pdata->ndev, xdp, xdp_prog); + if (err) + goto out_failure; + result = PHYTMAC_XDP_REDIR; + break; + default: + bpf_warn_invalid_xdp_action(pdata->ndev, xdp_prog, act); + fallthrough; + case XDP_ABORTED: +out_failure: + trace_xdp_exception(pdata->ndev, xdp_prog, act); + fallthrough; + case XDP_DROP: + result = PHYTMAC_XDP_CONSUMED; + break; + } +xdp_out: + rcu_read_unlock(); + return ERR_PTR(-result); +} + +static struct sk_buff *phytmac_rx_xdp_single(struct phytmac_queue *queue, + struct phytmac_dma_desc *desc, + unsigned int *xdp_xmit) +{ + struct phytmac *pdata = queue->pdata; + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct phytmac_rx_buffer *rx_buffer; + struct sk_buff *skb = NULL; + unsigned int len; + struct xdp_buff xdp; + unsigned char *hard_start; + u32 frame_sz = 0; + + len = hw_if->get_rx_pkt_len(pdata, desc); + rx_buffer = phytmac_get_rx_buffer(queue, queue->rx_tail, len); + +#if (PAGE_SIZE < 8192) + frame_sz = PHYTMAC_RX_PAGE_SIZE / 2; +#else + frame_sz = SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) + + SKB_DATA_ALIGN(PHYTMAC_SKB_PAD + len); +#endif + xdp_init_buff(&xdp, frame_sz, &queue->xdp_rxq); + + hard_start = page_address(rx_buffer->page) + rx_buffer->page_offset - PHYTMAC_SKB_PAD; + xdp_prepare_buff(&xdp, hard_start, PHYTMAC_SKB_PAD, len, true); + xdp_buff_clear_frags_flag(&xdp); + skb = phytmac_run_xdp(pdata, &xdp); + + if (IS_ERR(skb)) { + unsigned int xdp_res = -PTR_ERR(skb); + + if (xdp_res & (PHYTMAC_XDP_TX | PHYTMAC_XDP_REDIR)) { + *xdp_xmit |= xdp_res; + phytmac_rx_buffer_flip(rx_buffer, len); + } else { + rx_buffer->pagecnt_bias++; + } + hw_if->zero_rx_desc_addr(desc); + phytmac_put_rx_buffer(queue, rx_buffer); + pdata->ndev->stats.rx_bytes += len; + queue->stats.rx_bytes += len; + } else { + rx_buffer->pagecnt_bias++; + } + + return skb; +} + +static struct sk_buff *phytmac_rx_single(struct phytmac_queue *queue, struct phytmac_dma_desc *desc) +{ + struct phytmac *pdata = queue->pdata; + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct phytmac_rx_buffer *rx_buffer; + struct sk_buff *skb = NULL; + unsigned int len; + + len = hw_if->get_rx_pkt_len(pdata, desc); + rx_buffer = phytmac_get_rx_buffer(queue, queue->rx_tail, len); + + skb = phytmac_build_skb(rx_buffer, len); + if (unlikely(!skb)) { + netdev_err(pdata->ndev, + "rx single build skb failed\n"); + pdata->ndev->stats.rx_dropped++; + queue->stats.rx_dropped++; + rx_buffer->pagecnt_bias++; + return NULL; + } + + hw_if->zero_rx_desc_addr(desc); + phytmac_put_rx_buffer(queue, rx_buffer); + + skb->protocol = eth_type_trans(skb, pdata->ndev); + skb_checksum_none_assert(skb); + + if (pdata->ndev->features & NETIF_F_RXCSUM && + !(pdata->ndev->flags & IFF_PROMISC) && + hw_if->rx_checksum(desc)) + skb->ip_summed = CHECKSUM_UNNECESSARY; + + if (netif_msg_pktdata(pdata)) + phytmac_dump_pkt(pdata, skb, false); + + return skb; +} + +static struct sk_buff *phytmac_rx_frame(struct phytmac_queue *queue, + unsigned int first_frag, unsigned int last_frag, + unsigned int total_len) +{ + unsigned int frag = 0; + struct sk_buff *skb; + struct phytmac_dma_desc *desc; + struct phytmac *pdata = queue->pdata; + struct phytmac_hw_if *hw_if = pdata->hw_if; + unsigned int frag_len = pdata->rx_buffer_len; + unsigned int offset = frag_len; + struct phytmac_rx_buffer *rx_buffer; + + if (netif_msg_drv(pdata)) + netdev_info(pdata->ndev, "rx frame %u - %u (len %u)\n", + first_frag, last_frag, total_len); + + desc = phytmac_get_rx_desc(queue, first_frag); + rx_buffer = phytmac_get_rx_buffer(queue, first_frag, frag_len); + + skb = phytmac_build_skb(rx_buffer, frag_len); + if (unlikely(!skb)) { + netdev_err(pdata->ndev, "rx frame build skb failed\n"); + pdata->ndev->stats.rx_dropped++; + queue->stats.rx_dropped++; + rx_buffer->pagecnt_bias++; + return NULL; + } + + hw_if->zero_rx_desc_addr(desc); + phytmac_put_rx_buffer(queue, rx_buffer); + + for (frag = first_frag + 1; ; frag++) { + desc = phytmac_get_rx_desc(queue, frag); + rx_buffer = phytmac_get_rx_buffer(queue, frag, frag_len); + hw_if->zero_rx_desc_addr(desc); + + if (offset + frag_len > total_len) { + if (unlikely(frag != last_frag)) { + dev_kfree_skb_any(skb); + phytmac_put_rx_buffer(queue, rx_buffer); + return NULL; + } + frag_len = total_len - offset; + } + + phytmac_add_rx_frag(queue, rx_buffer, skb, frag_len); + phytmac_put_rx_buffer(queue, rx_buffer); + + offset += frag_len; + + if (frag == last_frag) + break; + } + + skb_checksum_none_assert(skb); + + if (pdata->ndev->features & NETIF_F_RXCSUM && + !(pdata->ndev->flags & IFF_PROMISC) && + hw_if->rx_checksum(phytmac_get_rx_desc(queue, last_frag))) + skb->ip_summed = CHECKSUM_UNNECESSARY; + + skb->protocol = eth_type_trans(skb, pdata->ndev); + if (netif_msg_pktdata(pdata)) + phytmac_dump_pkt(pdata, skb, false); + + return skb; +} + +static struct sk_buff *phytmac_rx_mbuffer(struct phytmac_queue *queue) +{ + struct phytmac *pdata = queue->pdata; + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct phytmac_dma_desc *desc; + struct sk_buff *skb = NULL; + unsigned int rx_tail = 0; + int first_frag = -1; + unsigned int len; + + for (rx_tail = queue->rx_tail; ; rx_tail++) { + desc = phytmac_get_rx_desc(queue, rx_tail); + if (!hw_if->rx_complete(desc)) + return ERR_PTR(-EAGAIN); + + if (hw_if->rx_pkt_start(desc)) { + if (first_frag != -1) + hw_if->clear_rx_desc(queue, first_frag, rx_tail); + first_frag = rx_tail; + continue; + } + + if (hw_if->rx_pkt_end(desc)) { + queue->rx_tail = rx_tail; + len = hw_if->get_rx_pkt_len(pdata, desc); + skb = phytmac_rx_frame(queue, first_frag, rx_tail, len); + first_frag = -1; + break; + } + } + return skb; +} + +static void phytmac_rx_clean(struct phytmac_queue *queue) +{ + struct phytmac *pdata = queue->pdata; + struct phytmac_hw_if *hw_if = pdata->hw_if; + unsigned int index, space; + struct phytmac_rx_buffer *rx_buf_info; + + space = CIRC_SPACE(queue->rx_head, queue->rx_tail, + pdata->rx_ring_size); + + while (space > 0) { + index = queue->rx_head & (pdata->rx_ring_size - 1); + rx_buf_info = &queue->rx_buffer_info[index]; + + if (!phytmac_alloc_mapped_page(pdata, rx_buf_info)) + break; + /* sync the buffer for use by the device */ + dma_sync_single_range_for_device(pdata->dev, rx_buf_info->addr, + rx_buf_info->page_offset, + pdata->rx_buffer_len, + DMA_FROM_DEVICE); + + hw_if->rx_map(queue, index, rx_buf_info->addr + rx_buf_info->page_offset); + + queue->rx_head++; + if (queue->rx_head >= pdata->rx_ring_size) + queue->rx_head &= (pdata->rx_ring_size - 1); + + space--; + } + + queue->rx_next_to_alloc = queue->rx_head; + /* make newly descriptor to hardware */ + wmb(); + + hw_if->update_rx_tail(queue); +} + +static int phytmac_rx(struct phytmac_queue *queue, struct napi_struct *napi, + int budget) +{ + struct phytmac *pdata = queue->pdata; + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct sk_buff *skb; + struct phytmac_dma_desc *desc; + unsigned int xdp_xmit = 0; + int count = 0; + + while (count < budget) { + desc = phytmac_get_rx_desc(queue, queue->rx_tail); + /* make newly desc to cpu */ + rmb(); + + if (!hw_if->rx_complete(desc)) + break; + + /* Ensure ctrl is at least as up-to-date as rxused */ + dma_rmb(); + + if (hw_if->rx_single_buffer(desc)) { + skb = phytmac_rx_xdp_single(queue, desc, &xdp_xmit); + if (!IS_ERR(skb)) + skb = phytmac_rx_single(queue, desc); + } else { + if (pdata->xdp_prog) + netdev_warn(pdata->ndev, "xdp does not support multiple buffers!!\n"); + skb = phytmac_rx_mbuffer(queue); + if (PTR_ERR(skb) == -EAGAIN) { + break; + } + } + + if (!skb) { + netdev_warn(pdata->ndev, "phytmac rx skb is NULL\n"); + break; + } + + pdata->ndev->stats.rx_packets++; + queue->stats.rx_packets++; + if (!IS_ERR(skb)) { + pdata->ndev->stats.rx_bytes += skb->len; + queue->stats.rx_bytes += skb->len; + } + queue->rx_tail = (queue->rx_tail + 1) & (pdata->rx_ring_size - 1); + + count++; + + if (IS_ERR(skb)) { + skb = NULL; + continue; + } + + if (IS_REACHABLE(CONFIG_PHYTMAC_ENABLE_PTP)) + phytmac_ptp_rxstamp(pdata, skb, desc); + + napi_gro_receive(napi, skb); + } + + if (xdp_xmit & PHYTMAC_XDP_REDIR) + xdp_do_flush(); + + phytmac_rx_clean(queue); + + return count; +} + +static void phytmac_tx_unmap(struct phytmac *pdata, struct phytmac_tx_skb *tx_skb, int budget) +{ + if (tx_skb->addr) { + if (tx_skb->mapped_as_page) + dma_unmap_page(pdata->dev, tx_skb->addr, + tx_skb->length, DMA_TO_DEVICE); + else + dma_unmap_single(pdata->dev, tx_skb->addr, + tx_skb->length, DMA_TO_DEVICE); + tx_skb->addr = 0; + } + + if (tx_skb->type == PHYTMAC_TYPE_XDP) { + if (tx_skb->xdpf) + xdp_return_frame(tx_skb->xdpf); + tx_skb->xdpf = NULL; + } else { + if (tx_skb->skb) + napi_consume_skb(tx_skb->skb, budget); + tx_skb->skb = NULL; + } +} + +static int phytmac_maybe_stop_tx_queue(struct phytmac_queue *queue, + unsigned int count) +{ + struct phytmac *pdata = queue->pdata; + int space = CIRC_SPACE(queue->tx_tail, queue->tx_head, + pdata->tx_ring_size); + + if (space < count) { + if (netif_msg_drv(pdata)) + netdev_info(pdata->ndev, "Tx queue %d stopped, not enough descriptors available\n", + queue->index); + + netif_stop_subqueue(pdata->ndev, queue->index); + + return NETDEV_TX_BUSY; + } + + return 0; +} + +static int phytmac_maybe_wake_tx_queue(struct phytmac_queue *queue) +{ + struct phytmac *pdata = queue->pdata; + int space = CIRC_CNT(queue->tx_tail, queue->tx_head, + pdata->tx_ring_size); + + return (space <= (3 * pdata->tx_ring_size / 4)) ? 1 : 0; +} + +static inline void phytmac_do_ptp_txstamp(struct phytmac_queue *queue, + struct sk_buff *skb, + struct phytmac_dma_desc *desc) +{ + if (IS_REACHABLE(CONFIG_PHYTMAC_ENABLE_PTP)) { + if (unlikely(skb_shinfo(skb)->tx_flags & + SKBTX_HW_TSTAMP) && + !phytmac_ptp_one_step(skb)) { + phytmac_ptp_txstamp(queue, skb, desc); + } + } +} + +static int phytmac_tx_clean(struct phytmac_queue *queue, int budget) +{ + struct phytmac *pdata = queue->pdata; + u16 queue_index = queue->index; + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct phytmac_tx_skb *tx_skb; + struct phytmac_dma_desc *desc; + int complete = 0; + int packet_count = 0; + unsigned int tail = queue->tx_tail; + unsigned int head; + + spin_lock(&queue->tx_lock); + + for (head = queue->tx_head; head != tail && packet_count < budget; ) { + struct sk_buff *skb; + + desc = phytmac_get_tx_desc(queue, head); + /* make newly desc to cpu */ + rmb(); + if (!hw_if->tx_complete(desc)) + break; + + /* Process all buffers of the current transmitted frame */ + for (;; head++) { + tx_skb = phytmac_get_tx_skb(queue, head); + + if (tx_skb->type == PHYTMAC_TYPE_SKB) { + skb = tx_skb->skb; + if (skb) { + complete = 1; + phytmac_do_ptp_txstamp(queue, skb, desc); + + if (netif_msg_drv(pdata)) + netdev_info(pdata->ndev, "desc %u (data %p) tx complete\n", + head, tx_skb->skb->data); + + pdata->ndev->stats.tx_packets++; + queue->stats.tx_packets++; + pdata->ndev->stats.tx_bytes += tx_skb->skb->len; + queue->stats.tx_bytes += tx_skb->skb->len; + packet_count++; + } + } else if (tx_skb->type == PHYTMAC_TYPE_XDP) { + if (tx_skb->xdpf) { + complete = 1; + pdata->ndev->stats.tx_packets++; + queue->stats.tx_packets++; + pdata->ndev->stats.tx_bytes += tx_skb->xdpf->len; + queue->stats.tx_bytes += tx_skb->xdpf->len; + packet_count++; + } + } + + /* Now we can safely release resources */ + phytmac_tx_unmap(pdata, tx_skb, budget); + + if (complete) { + complete = 0; + break; + } + } + + head++; + if (head >= pdata->tx_ring_size) + head &= (pdata->tx_ring_size - 1); + } + + queue->tx_head = head; + if (__netif_subqueue_stopped(pdata->ndev, queue_index) && + (phytmac_maybe_wake_tx_queue(queue))) + netif_wake_subqueue(pdata->ndev, queue_index); + spin_unlock(&queue->tx_lock); + + return packet_count; +} + +static int phytmac_rx_poll(struct napi_struct *napi, int budget) +{ + struct phytmac_queue *queue = container_of(napi, struct phytmac_queue, rx_napi); + struct phytmac *pdata = queue->pdata; + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct phytmac_dma_desc *desc; + int work_done; + + work_done = phytmac_rx(queue, napi, budget); + + if (netif_msg_drv(pdata)) + netdev_info(pdata->ndev, "RX poll: queue = %u, work_done = %d, budget = %d\n", + (unsigned int)(queue->index), work_done, budget); + if (work_done < budget && napi_complete_done(napi, work_done)) { + hw_if->enable_irq(pdata, queue->index, pdata->rx_irq_mask); + + desc = phytmac_get_rx_desc(queue, queue->rx_tail); + /* make newly desc to cpu */ + rmb(); + + if (hw_if->rx_complete(desc)) { + hw_if->disable_irq(pdata, queue->index, pdata->rx_irq_mask); + hw_if->clear_irq(pdata, queue->index, PHYTMAC_INT_RX_COMPLETE); + + napi_schedule(napi); + } + } + + return work_done; +} + +static int phytmac_tx_poll(struct napi_struct *napi, int budget) +{ + struct phytmac_queue *queue = container_of(napi, struct phytmac_queue, tx_napi); + struct phytmac *pdata = queue->pdata; + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct phytmac_dma_desc *desc; + int work_done; + + work_done = phytmac_tx_clean(queue, budget); + + if (netif_msg_drv(pdata)) + netdev_info(pdata->ndev, "TX poll: queue = %u, work_done = %d, budget = %d\n", + (unsigned int)(queue->index), work_done, budget); + if (work_done < budget && napi_complete_done(napi, work_done)) { + hw_if->enable_irq(pdata, queue->index, PHYTMAC_INT_TX_COMPLETE); + if (queue->tx_head != queue->tx_tail) { + desc = phytmac_get_tx_desc(queue, queue->tx_head); + /* make newly desc to cpu */ + rmb(); + + if (hw_if->tx_complete(desc)) { + hw_if->disable_irq(pdata, queue->index, PHYTMAC_INT_TX_COMPLETE); + hw_if->clear_irq(pdata, queue->index, PHYTMAC_INT_TX_COMPLETE); + + napi_schedule(napi); + } + } + } + + return work_done; +} + +static inline int phytmac_clear_csum(struct sk_buff *skb) +{ + if (skb->ip_summed != CHECKSUM_PARTIAL) + return 0; + + /* make sure we can modify the header */ + if (unlikely(skb_cow_head(skb, 0))) + return -1; + + *(__sum16 *)(skb_checksum_start(skb) + skb->csum_offset) = 0; + return 0; +} + +static int phytmac_add_fcs(struct sk_buff **skb, struct net_device *ndev) +{ +#ifdef PHYTMAC_SW_FCS + bool cloned = skb_cloned(*skb) || skb_header_cloned(*skb) || + skb_is_nonlinear(*skb); + int padlen = ETH_ZLEN - (*skb)->len; + int headroom = skb_headroom(*skb); + int tailroom = skb_tailroom(*skb); + struct sk_buff *nskb; + u32 fcs; + int i; + + if (!(ndev->features & NETIF_F_HW_CSUM) || + !((*skb)->ip_summed != CHECKSUM_PARTIAL) || + skb_shinfo(*skb)->gso_size || phytmac_ptp_one_step(*skb)) + return 0; + + if (padlen <= 0) { + if (tailroom >= ETH_FCS_LEN) + goto add_fcs; + else if (!cloned && headroom + tailroom >= ETH_FCS_LEN) + padlen = 0; + else + padlen = ETH_FCS_LEN; + } else { + padlen += ETH_FCS_LEN; + } + + if (!cloned && headroom + tailroom >= padlen) { + (*skb)->data = memmove((*skb)->head, (*skb)->data, (*skb)->len); + skb_set_tail_pointer(*skb, (*skb)->len); + } else { + nskb = skb_copy_expand(*skb, 0, padlen, GFP_ATOMIC); + if (!nskb) + return -ENOMEM; + + dev_consume_skb_any(*skb); + *skb = nskb; + } + + if (padlen > ETH_FCS_LEN) + skb_put_zero(*skb, padlen - ETH_FCS_LEN); + +add_fcs: + fcs = crc32_le(~0, (*skb)->data, (*skb)->len); + fcs = ~fcs; + + for (i = 0; i < 4; ++i) + skb_put_u8(*skb, (fcs >> (i * 8)) & 0xff); +#endif + + return 0; +} + +static int phytmac_packet_info(struct phytmac *pdata, + struct phytmac_queue *queue, struct sk_buff *skb, + struct packet_info *packet) +{ + int is_lso; + unsigned int hdrlen, f; + int desc_cnt; + + memset(packet, 0, sizeof(struct packet_info)); + + is_lso = (skb_shinfo(skb)->gso_size != 0); + + if (is_lso) { + /* length of headers */ + if (ip_hdr(skb)->protocol == IPPROTO_UDP) { + /* only queue eth + ip headers separately for UDP */ + hdrlen = skb_transport_offset(skb); + packet->lso = LSO_UFO; + packet->mss = skb_shinfo(skb)->gso_size + hdrlen + ETH_FCS_LEN; + } else { + hdrlen = skb_transport_offset(skb) + tcp_hdrlen(skb); + packet->lso = LSO_TSO; + packet->mss = skb_shinfo(skb)->gso_size; + } + + if (skb_headlen(skb) < hdrlen) { + dev_err(pdata->dev, "Error - LSO headers fragmented!!!\n"); + return NETDEV_TX_BUSY; + } + } else { + hdrlen = min(skb_headlen(skb), pdata->max_tx_length); + packet->lso = 0; + packet->mss = 0; + } + + packet->hdrlen = hdrlen; + + if (is_lso && (skb_headlen(skb) > hdrlen)) + desc_cnt = TXD_USE_COUNT(pdata, (skb_headlen(skb) - hdrlen)) + 1; + else + desc_cnt = TXD_USE_COUNT(pdata, hdrlen); + + for (f = 0; f < skb_shinfo(skb)->nr_frags; f++) + desc_cnt += TXD_USE_COUNT(pdata, skb_frag_size(&skb_shinfo(skb)->frags[f])); + packet->desc_cnt = desc_cnt; + +#ifdef PHYTMAC_SW_FCS + if ((pdata->ndev->features & NETIF_F_HW_CSUM) && + skb->ip_summed != CHECKSUM_PARTIAL && + !is_lso && + !phytmac_ptp_one_step(skb)) + packet->nocrc = 1; +#endif + + if (netif_msg_pktdata(pdata)) { + netdev_info(pdata->ndev, "packet info: desc_cnt=%d, nocrc=%d,ip_summed=%d\n", + desc_cnt, packet->nocrc, skb->ip_summed); + netdev_info(pdata->ndev, "packet info: mss=%d, lso=%d,skb_len=%d, nr_frags=%d\n", + packet->mss, packet->lso, skb->len, skb_shinfo(skb)->nr_frags); + } + + return 0; +} + +static unsigned int phytmac_tx_map(struct phytmac *pdata, + struct phytmac_queue *queue, + struct sk_buff *skb, + struct packet_info *packet) +{ + dma_addr_t mapping; + struct phytmac_hw_if *hw_if = pdata->hw_if; + unsigned int len, i, tx_tail; + struct phytmac_tx_skb *tx_skb = NULL; + unsigned int offset, size, count = 0; + unsigned int f, nr_frags = skb_shinfo(skb)->nr_frags; + + len = skb_headlen(skb); + size = packet->hdrlen; + + offset = 0; + tx_tail = queue->tx_tail; + while (len) { + tx_skb = phytmac_get_tx_skb(queue, tx_tail); + + mapping = dma_map_single(pdata->dev, + skb->data + offset, + size, DMA_TO_DEVICE); + if (dma_mapping_error(pdata->dev, mapping)) + goto dma_error; + + /* Save info to properly release resources */ + tx_skb->skb = NULL; + tx_skb->type = PHYTMAC_TYPE_SKB; + tx_skb->addr = mapping; + tx_skb->length = size; + tx_skb->mapped_as_page = false; + + len -= size; + offset += size; + count++; + tx_tail++; + + size = min(len, pdata->max_tx_length); + } + + /* Then, map paged data from fragments */ + for (f = 0; f < nr_frags; f++) { + const skb_frag_t *frag = &skb_shinfo(skb)->frags[f]; + + len = skb_frag_size(frag); + offset = 0; + while (len) { + size = min(len, pdata->max_tx_length); + tx_skb = phytmac_get_tx_skb(queue, tx_tail); + mapping = skb_frag_dma_map(pdata->dev, frag, + offset, size, DMA_TO_DEVICE); + if (dma_mapping_error(pdata->dev, mapping)) + goto dma_error; + + /* Save info to properly release resources */ + tx_skb->skb = NULL; + tx_skb->type = PHYTMAC_TYPE_SKB; + tx_skb->addr = mapping; + tx_skb->length = size; + tx_skb->mapped_as_page = true; + + len -= size; + offset += size; + count++; + tx_tail++; + } + } + + /* Should never happen */ + if (unlikely(!tx_skb)) { + netdev_err(pdata->ndev, "BUG! empty skb!\n"); + return 0; + } + + /* This is the last buffer of the frame: save socket buffer */ + tx_skb->skb = skb; + + if (hw_if->tx_map(queue, tx_tail, packet)) { + netdev_err(pdata->ndev, "BUG!hw tx map failed!\n"); + return 0; + } + + queue->tx_tail = tx_tail & (pdata->tx_ring_size - 1); + + return count; + +dma_error: + netdev_err(pdata->ndev, "TX DMA map failed\n"); + + for (i = queue->tx_tail; i != tx_tail; i++) { + tx_skb = phytmac_get_tx_skb(queue, i); + phytmac_tx_unmap(pdata, tx_skb, 0); + } + + return 0; +} + +static inline void phytmac_init_ring(struct phytmac *pdata) +{ + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct phytmac_queue *queue; + unsigned int q = 0; + int i; + + for (queue = pdata->queues; q < pdata->queues_num; ++q) { + queue->tx_head = 0; + queue->tx_tail = 0; + hw_if->clear_tx_desc(queue); + + for (i = 0; i < pdata->rx_ring_size; i++) + hw_if->init_rx_map(queue, i); + queue->rx_head = 0; + queue->rx_tail = 0; + queue->rx_next_to_alloc = 0; + phytmac_rx_clean(queue); + ++queue; + } + + hw_if->init_ring_hw(pdata); +} + +static int phytmac_start_xmit_xdp(struct phytmac *pdata, + struct phytmac_queue *queue, + struct xdp_frame *xdpf) +{ + u32 len; + struct phytmac_tx_skb *tx_buffer; + struct packet_info packet; + dma_addr_t dma; + struct phytmac_hw_if *hw_if = pdata->hw_if; + u16 tx_tail; + + len = xdpf->len; + + memset(&packet, 0, sizeof(struct packet_info)); + + if (unlikely(!phytmac_txdesc_unused(queue))) + return PHYTMAC_XDP_CONSUMED; + + dma = dma_map_single(pdata->dev, xdpf->data, len, DMA_TO_DEVICE); + if (dma_mapping_error(pdata->dev, dma)) + return PHYTMAC_XDP_CONSUMED; + + /* record the location of the first descriptor for this packet */ + tx_buffer = phytmac_get_tx_skb(queue, queue->tx_tail); + tx_buffer->mapped_as_page = false; + + /* Temporarily set the tail pointer for the next package */ + tx_tail = queue->tx_tail + 1; + + dma_unmap_len_set(tx_buffer, length, len); + dma_unmap_addr_set(tx_buffer, addr, dma); + tx_buffer->type = PHYTMAC_TYPE_XDP; + tx_buffer->xdpf = xdpf; + + packet.lso = 0; + packet.mss = 0; + packet.nocrc = 0; + + /* Avoid any potential race with xdp_xmit and cleanup */ + smp_wmb(); + + hw_if->tx_map(queue, tx_tail, &packet); + + queue->tx_tail = tx_tail & (pdata->tx_ring_size - 1); + + hw_if->transmit(queue); + + /* Make sure there is space in the ring for the next send. */ + phytmac_maybe_stop_tx_queue(queue, PHYTMAC_DESC_NEEDED); + + return PHYTMAC_XDP_TX; +} + +static netdev_tx_t phytmac_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + u16 queue_index = skb->queue_mapping; + struct phytmac_queue *queue = &pdata->queues[queue_index]; + netdev_tx_t ret = NETDEV_TX_OK; + struct packet_info packet; + + if (phytmac_clear_csum(skb)) { + dev_kfree_skb_any(skb); + return ret; + } + + if (phytmac_add_fcs(&skb, ndev)) { + dev_kfree_skb_any(skb); + return ret; + } + + ret = phytmac_packet_info(pdata, queue, skb, &packet); + if (ret) { + dev_kfree_skb_any(skb); + return ret; + } + + if (netif_msg_pktdata(pdata)) + phytmac_dump_pkt(pdata, skb, true); + + spin_lock_bh(&queue->tx_lock); + /* Check that there are enough descriptors available */ + ret = phytmac_maybe_stop_tx_queue(queue, packet.desc_cnt); + if (ret) + goto tx_return; + + /* Map socket buffer for DMA transfer */ + if (!phytmac_tx_map(pdata, queue, skb, &packet)) { + dev_kfree_skb_any(skb); + goto tx_return; + } + + skb_tx_timestamp(skb); + /* Make newly descriptor to hardware */ + wmb(); + + hw_if->transmit(queue); + +tx_return: + spin_unlock_bh(&queue->tx_lock); + return ret; +} + +static int phytmac_phylink_connect(struct phytmac *pdata) +{ + struct net_device *ndev = pdata->ndev; + struct phy_device *phydev; + struct fwnode_handle *fwnode = dev_fwnode(pdata->dev); + int ret = 0; + + if (fwnode) + ret = phylink_fwnode_phy_connect(pdata->phylink, fwnode, 0); + + if (!fwnode || ret) { + if (pdata->mii_bus) { + phydev = phy_find_first(pdata->mii_bus); + if (!phydev) { + dev_err(pdata->dev, "no PHY found\n"); + return -ENXIO; + } + /* attach the mac to the phy */ + ret = phylink_connect_phy(pdata->phylink, phydev); + } else { + netdev_err(ndev, "Not mii register\n"); + return -ENXIO; + } + } + + if (ret) { + netdev_err(ndev, "Could not attach PHY (%d)\n", ret); + return ret; + } + + return 0; +} + +static int phytmac_pcs_config(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + return 0; +} + +static void phytmac_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, int speed, int duplex) +{ + struct phytmac *pdata = container_of(pcs, struct phytmac, phylink_pcs); + struct phytmac_hw_if *hw_if = pdata->hw_if; + + if (netif_msg_link(pdata)) + netdev_info(pdata->ndev, "pcs link up, interface = %s, speed = %d, duplex = %d\n", + phy_modes(interface), speed, duplex); + hw_if->pcs_linkup(pdata, interface, speed, duplex); +} + +static const struct phylink_pcs_ops phytmac_pcs_phylink_ops = { + .pcs_config = phytmac_pcs_config, + .pcs_link_up = phytmac_pcs_link_up, +}; + +static struct phylink_pcs *phytmac_mac_select_pcs(struct phylink_config *config, + phy_interface_t interface) +{ + struct phytmac *pdata = netdev_priv(to_net_dev(config->dev)); + + if (interface == PHY_INTERFACE_MODE_USXGMII || + interface == PHY_INTERFACE_MODE_10GBASER || + interface == PHY_INTERFACE_MODE_SGMII || + interface == PHY_INTERFACE_MODE_1000BASEX || + interface == PHY_INTERFACE_MODE_2500BASEX) { + pdata->phylink_pcs.ops = &phytmac_pcs_phylink_ops; + } else { + pdata->phylink_pcs.ops = NULL; + } + + return &pdata->phylink_pcs; +} +static void phytmac_mac_config(struct phylink_config *config, unsigned int mode, + const struct phylink_link_state *state) +{ + struct phytmac *pdata = netdev_priv(to_net_dev(config->dev)); + struct phytmac_hw_if *hw_if = pdata->hw_if; + unsigned long flags; + + if (netif_msg_link(pdata)) { + netdev_info(pdata->ndev, "mac config interface=%s, mode=%d\n", + phy_modes(state->interface), mode); + } + + spin_lock_irqsave(&pdata->lock, flags); + hw_if->mac_config(pdata, mode, state); + spin_unlock_irqrestore(&pdata->lock, flags); +} + +static void phytmac_mac_link_down(struct phylink_config *config, unsigned int mode, + phy_interface_t interface) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct phytmac_queue *queue; + unsigned int q; + unsigned long flags; + struct phytmac_tx_skb *tx_skb; + struct phytmac_dma_desc *tx_desc = NULL; + int i; + + if (netif_msg_link(pdata)) { + netdev_info(ndev, "link down interface:%s, mode=%d\n", + phy_modes(interface), mode); + } + + if (pdata->use_ncsi) + ncsi_stop_dev(pdata->ncsidev); + + spin_lock_irqsave(&pdata->lock, flags); + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + hw_if->disable_irq(pdata, queue->index, pdata->rx_irq_mask | pdata->tx_irq_mask); + hw_if->clear_irq(pdata, queue->index, pdata->rx_irq_mask | pdata->tx_irq_mask); + } + + /* Disable Rx and Tx */ + hw_if->enable_network(pdata, false, PHYTMAC_RX | PHYTMAC_TX); + spin_unlock_irqrestore(&pdata->lock, flags); + + /* Tx clean */ + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + spin_lock_bh(&queue->tx_lock); + for (i = 0; i < pdata->tx_ring_size; i++) { + tx_skb = phytmac_get_tx_skb(queue, i); + if (tx_skb) + phytmac_tx_unmap(pdata, tx_skb, 0); + + tx_desc = phytmac_get_tx_desc(queue, i); + hw_if->zero_tx_desc(tx_desc); + } + spin_unlock_bh(&queue->tx_lock); + } + + netif_tx_stop_all_queues(ndev); +} + +static void phytmac_mac_link_up(struct phylink_config *config, + struct phy_device *phy, + unsigned int mode, phy_interface_t interface, + int speed, int duplex, + bool tx_pause, bool rx_pause) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct phytmac_queue *queue; + unsigned long flags; + unsigned int q; + int ret; + + if (netif_msg_link(pdata)) + netdev_info(pdata->ndev, "link up interface:%s, speed:%d, duplex:%s\n", + phy_modes(interface), speed, duplex ? "full-duplex" : "half-duplex"); + + spin_lock_irqsave(&pdata->lock, flags); + + hw_if->mac_linkup(pdata, interface, speed, duplex); + + if (rx_pause != pdata->pause) { + hw_if->enable_pause(pdata, rx_pause); + pdata->pause = rx_pause; + } + + pdata->speed = speed; + pdata->duplex = duplex; + + phytmac_init_ring(pdata); + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) + hw_if->enable_irq(pdata, queue->index, pdata->rx_irq_mask | pdata->tx_irq_mask); + + /* Enable Rx and Tx */ + hw_if->enable_network(pdata, true, PHYTMAC_RX | PHYTMAC_TX); + spin_unlock_irqrestore(&pdata->lock, flags); + + if (pdata->use_ncsi) { + /* Start the NCSI device */ + ret = ncsi_start_dev(pdata->ncsidev); + if (ret) { + netdev_err(pdata->ndev, "Ncsi start dev failed (error %d)\n", ret); + return; + } + } + + netif_tx_wake_all_queues(ndev); +} + +static int phytmac_mdio_register(struct phytmac *pdata) +{ + struct phytmac_hw_if *hw_if = pdata->hw_if; + int ret; + + pdata->mii_bus = mdiobus_alloc(); + if (!pdata->mii_bus) { + ret = -ENOMEM; + goto err_out; + } + + if (hw_if->mdio_idle) { + ret = hw_if->mdio_idle(pdata); + if (ret) + goto free_mdio; + } + + pdata->mii_bus->name = "phytmac_mii_bus"; + pdata->mii_bus->read = &phytmac_mdio_read_c22; + pdata->mii_bus->write = &phytmac_mdio_write_c22; + pdata->mii_bus->read_c45 = &phytmac_mdio_read_c45; + pdata->mii_bus->write_c45 = &phytmac_mdio_write_c45; + + if (pdata->platdev) { + snprintf(pdata->mii_bus->id, MII_BUS_ID_SIZE, "%s-%s", + pdata->mii_bus->name, pdata->platdev->name); + } else if (pdata->pcidev) { + snprintf(pdata->mii_bus->id, MII_BUS_ID_SIZE, "%s-%s", + pdata->mii_bus->name, pci_name(pdata->pcidev)); + } else { + ret = -ENOMEM; + goto free_mdio; + } + + pdata->mii_bus->priv = pdata; + pdata->mii_bus->parent = pdata->dev; + + hw_if->enable_mdio_control(pdata, 1); + + return mdiobus_register(pdata->mii_bus); +free_mdio: + mdiobus_free(pdata->mii_bus); + pdata->mii_bus = NULL; + +err_out: + return ret; +} + +static void phytmac_pcs_get_state(struct phylink_config *config, + struct phylink_link_state *state) +{ + struct phytmac *pdata = container_of(config, struct phytmac, phylink_config); + struct phytmac_hw_if *hw_if = pdata->hw_if; + + state->link = hw_if->get_link(pdata, state->interface); +} + +static const struct phylink_mac_ops phytmac_phylink_ops = { + .mac_select_pcs = phytmac_mac_select_pcs, + .mac_config = phytmac_mac_config, + .mac_link_down = phytmac_mac_link_down, + .mac_link_up = phytmac_mac_link_up, +}; + +static inline void set_phy_interface(unsigned long *intf) +{ + __set_bit(PHY_INTERFACE_MODE_SGMII, intf); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, intf); + __set_bit(PHY_INTERFACE_MODE_2500BASEX, intf); + __set_bit(PHY_INTERFACE_MODE_USXGMII, intf); + __set_bit(PHY_INTERFACE_MODE_5GBASER, intf); + __set_bit(PHY_INTERFACE_MODE_10GBASER, intf); + phy_interface_set_rgmii(intf); +} + +static int phytmac_phylink_create(struct phytmac *pdata) +{ + struct fwnode_handle *fw_node = dev_fwnode(pdata->dev); + + pdata->phylink_config.dev = &pdata->ndev->dev; + pdata->phylink_config.type = PHYLINK_NETDEV; + pdata->phylink_config.mac_managed_pm = true; + + if (pdata->phy_interface == PHY_INTERFACE_MODE_SGMII || + pdata->phy_interface == PHY_INTERFACE_MODE_1000BASEX || + pdata->phy_interface == PHY_INTERFACE_MODE_2500BASEX || + pdata->phy_interface == PHY_INTERFACE_MODE_USXGMII || + pdata->phy_interface == PHY_INTERFACE_MODE_10GBASER) { + pdata->phylink_config.poll_fixed_state = true; + pdata->phylink_config.get_fixed_state = phytmac_pcs_get_state; + pdata->phylink_pcs.ops = &phytmac_pcs_phylink_ops; + } + + set_phy_interface(pdata->phylink_config.supported_interfaces); + pdata->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | + MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD | + MAC_5000FD | MAC_10000FD; + + pdata->phylink = phylink_create(&pdata->phylink_config, fw_node, + pdata->phy_interface, &phytmac_phylink_ops); + if (IS_ERR(pdata->phylink)) { + dev_err(pdata->dev, "Could not create a phylink instance (%ld)\n", + PTR_ERR(pdata->phylink)); + return PTR_ERR(pdata->phylink); + } + + return 0; +} + +static int phytmac_open(struct net_device *ndev) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_queue *queue; + struct phytmac_hw_if *hw_if = pdata->hw_if; + unsigned int q = 0; + int ret; + + if (netif_msg_probe(pdata)) + dev_dbg(pdata->dev, "open\n"); + + if (hw_if->init_msg_ring) + hw_if->init_msg_ring(pdata); + + ret = hw_if->get_feature(pdata); + if (ret) { + netdev_err(ndev, "phytmac get features failed\n"); + return ret; + } + + hw_if->reset_hw(pdata); + + ret = netif_set_real_num_tx_queues(ndev, pdata->queues_num); + if (ret) { + netdev_err(ndev, "error setting real tx queue number\n"); + return ret; + } + ret = netif_set_real_num_rx_queues(ndev, pdata->queues_num); + if (ret) { + netdev_err(ndev, "error setting real tx queue number\n"); + return ret; + } + + /* RX buffers initialization */ + ret = phytmac_alloc_resource(pdata); + if (ret) { + netdev_err(ndev, "Unable to allocate DMA memory (error %d)\n", + ret); + goto reset_hw; + } + + for (queue = pdata->queues; q < pdata->queues_num; ++q) { + napi_enable(&queue->tx_napi); + napi_enable(&queue->rx_napi); + ++queue; + } + + hw_if->init_hw(pdata); + + ret = phytmac_phylink_connect(pdata); + if (ret) { + netdev_err(ndev, "phylink connect failed,(error %d)\n", + ret); + goto reset_hw; + } + + phylink_start(pdata->phylink); + phytmac_set_bios_wol_enable(pdata, pdata->wol ? 1 : 0); + + netif_tx_start_all_queues(pdata->ndev); + + if (IS_REACHABLE(CONFIG_PHYTMAC_ENABLE_PTP)) { + ret = phytmac_ptp_register(pdata); + if (ret) { + netdev_err(ndev, "ptp register failed, (error %d)\n", + ret); + goto reset_hw; + } + + phytmac_ptp_init(pdata->ndev); + } + + return 0; + +reset_hw: + hw_if->reset_hw(pdata); + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q) { + napi_disable(&queue->tx_napi); + napi_disable(&queue->rx_napi); + ++queue; + } + phytmac_free_resource(pdata); + return ret; +} + +static int phytmac_close(struct net_device *ndev) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_queue *queue; + struct phytmac_hw_if *hw_if = pdata->hw_if; + unsigned long flags; + unsigned int q; + + if (netif_msg_probe(pdata)) + dev_dbg(pdata->dev, "close"); + + netif_tx_stop_all_queues(ndev); + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + napi_disable(&queue->tx_napi); + napi_disable(&queue->rx_napi); + } + + phylink_stop(pdata->phylink); + phylink_disconnect_phy(pdata->phylink); + + netif_carrier_off(ndev); + + spin_lock_irqsave(&pdata->lock, flags); + hw_if->reset_hw(pdata); + spin_unlock_irqrestore(&pdata->lock, flags); + + phytmac_free_resource(pdata); + + if (IS_REACHABLE(CONFIG_PHYTMAC_ENABLE_PTP)) + phytmac_ptp_unregister(pdata); + + return 0; +} + +static int phytmac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct phytmac *pdata = netdev_priv(dev); + int ret = -EOPNOTSUPP; + + if (!netif_running(dev)) + return -EINVAL; + + switch (cmd) { + case SIOCGMIIPHY: + case SIOCGMIIREG: + case SIOCSMIIREG: + ret = phylink_mii_ioctl(pdata->phylink, rq, cmd); + break; +#ifdef CONFIG_PHYTMAC_ENABLE_PTP + case SIOCSHWTSTAMP: + ret = phytmac_ptp_set_ts_config(dev, rq, cmd); + break; + case SIOCGHWTSTAMP: + ret = phytmac_ptp_get_ts_config(dev, rq); + break; +#endif + default: + break; + } + + return ret; +} + +static inline int phytmac_set_features(struct net_device *netdev, + netdev_features_t features) +{ + struct phytmac *pdata = netdev_priv(netdev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + netdev_features_t changed = features ^ netdev->features; + + /* TX checksum offload */ + if (changed & NETIF_F_HW_CSUM) { + if (features & NETIF_F_HW_CSUM) + hw_if->enable_tx_csum(pdata, 1); + else + hw_if->enable_tx_csum(pdata, 0); + } + + /* RX checksum offload */ + if (changed & NETIF_F_RXCSUM) { + if (features & NETIF_F_RXCSUM && + !(netdev->flags & IFF_PROMISC)) + hw_if->enable_rx_csum(pdata, 1); + else + hw_if->enable_rx_csum(pdata, 0); + } + return 0; +} + +static netdev_features_t phytmac_features_check(struct sk_buff *skb, + struct net_device *dev, + netdev_features_t features) +{ + unsigned int nr_frags, f; + unsigned int hdrlen; + + /* there is only one buffer or protocol is not UDP */ + if (!skb_is_nonlinear(skb) || (ip_hdr(skb)->protocol != IPPROTO_UDP)) + return features; + + /* length of header */ + hdrlen = skb_transport_offset(skb); + + if (!IS_ALIGNED(skb_headlen(skb) - hdrlen, PHYTMAC_TX_LEN_ALIGN)) + return features & ~(NETIF_F_TSO | NETIF_F_TSO6); + + nr_frags = skb_shinfo(skb)->nr_frags; + /* No need to check last fragment */ + nr_frags--; + for (f = 0; f < nr_frags; f++) { + const skb_frag_t *frag = &skb_shinfo(skb)->frags[f]; + + if (!IS_ALIGNED(skb_frag_size(frag), PHYTMAC_TX_LEN_ALIGN)) + return features & ~(NETIF_F_TSO | NETIF_F_TSO6); + } + return features; +} + +void phytmac_set_bios_wol_enable(struct phytmac *pdata, u32 wol) +{ + struct net_device *ndev = pdata->ndev; + + if (ndev->phydev) { +#ifdef CONFIG_ACPI + if (has_acpi_companion(pdata->dev)) { + acpi_handle handle = ACPI_HANDLE(pdata->dev); + + if (acpi_has_method(handle, "PWOL")) { + union acpi_object args[] = { + { .type = ACPI_TYPE_INTEGER, }, + }; + struct acpi_object_list arg_input = { + .pointer = args, + .count = ARRAY_SIZE(args), + }; + acpi_status status; + + /* Set the input parameters */ + args[0].integer.value = wol; + + status = acpi_evaluate_object(handle, "PWOL", &arg_input, NULL); + if (ACPI_FAILURE(status)) + netdev_err(ndev, "The PWOL method failed to be executed.\n"); + } + } +#endif + } +} + +int phytmac_reset_ringsize(struct phytmac *pdata, u32 rx_size, u32 tx_size) +{ + int ret = 0; + int reset = 0; + + if (netif_running(pdata->ndev)) { + reset = 1; + phytmac_close(pdata->ndev); + } + + pdata->rx_ring_size = rx_size; + pdata->tx_ring_size = tx_size; + + if (reset) + phytmac_open(pdata->ndev); + + return ret; +} + +static int phytmac_xdp_setup(struct net_device *dev, struct bpf_prog *prog) +{ + int i, frame_size = dev->mtu + PHYTMAC_ETH_PKT_HDR_PAD; + struct phytmac *pdata = netdev_priv(dev); + struct bpf_prog *old_prog; + bool running = netif_running(dev); + bool need_reset; + + /* verify phytmac rx ring attributes are sufficient for XDP */ + if (frame_size > phytmac_calc_rx_buf_len()) { + netdev_warn(dev, "XDP RX buffer size %d is too small for the frame size %d\n", + phytmac_calc_rx_buf_len(), frame_size); + return -EINVAL; + } + + old_prog = xchg(&pdata->xdp_prog, prog); + need_reset = (!!prog != !!old_prog); + + /* device is up and bpf is added/removed, must setup the RX queues */ + if (need_reset && running) { + phytmac_close(dev); + } else { + for (i = 0; i < pdata->queues_num; i++) + (void)xchg(&pdata->queues[i].xdp_prog, + pdata->xdp_prog); + } + + if (old_prog) + bpf_prog_put(old_prog); + + /* bpf is just replaced, RXQ and MTU are already setup */ + if (!need_reset) + return 0; + + if (prog) + xdp_features_set_redirect_target(dev, false); + else + xdp_features_clear_redirect_target(dev); + + if (running) + phytmac_open(dev); + + return 0; +} + +static int phytmac_xdp(struct net_device *dev, struct netdev_bpf *xdp) +{ + switch (xdp->command) { + case XDP_SETUP_PROG: + return phytmac_xdp_setup(dev, xdp->prog); + default: + return -EINVAL; + } +} + +static struct phytmac_queue *phytmac_xdp_txq_mapping(struct phytmac *pdata) +{ + unsigned int r_idx = smp_processor_id(); + + if (r_idx >= pdata->queues_num) + r_idx = r_idx % pdata->queues_num; + + return &pdata->queues[r_idx]; +} + +static int phytmac_xdp_xmit_back(struct phytmac *pdata, struct xdp_buff *xdp) +{ + struct xdp_frame *xdpf = xdp_convert_buff_to_frame(xdp); + int cpu = smp_processor_id(); + struct phytmac_queue *queue; + struct netdev_queue *nq; + u32 ret; + + if (unlikely(!xdpf)) + return PHYTMAC_XDP_CONSUMED; + + /* During program transitions its possible adapter->xdp_prog is assigned + * but ring has not been configured yet. In this case simply abort xmit. + */ + queue = pdata->xdp_prog ? phytmac_xdp_txq_mapping(pdata) : NULL; + if (unlikely(!queue)) + return PHYTMAC_XDP_CONSUMED; + + nq = phytmac_get_txq(pdata, queue); + __netif_tx_lock(nq, cpu); + /* Avoid transmit queue timeout since we share it with the slow path */ + txq_trans_cond_update(nq); + ret = phytmac_start_xmit_xdp(pdata, queue, xdpf); + __netif_tx_unlock(nq); + + return ret; +} + +static int phytmac_xdp_xmit(struct net_device *dev, int n, + struct xdp_frame **frames, u32 flags) +{ + struct phytmac *pdata = netdev_priv(dev); + int cpu = smp_processor_id(); + struct phytmac_queue *queue; + struct netdev_queue *nq; + int nxmit = 0; + int i; + + if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK)) + return -EINVAL; + + /* During program transitions its possible pdata->xdp_prog is assigned + * but ring has not been configured yet. In this case simply abort xmit. + */ + queue = pdata->xdp_prog ? phytmac_xdp_txq_mapping(pdata) : NULL; + if (unlikely(!queue)) + return -ENXIO; + + nq = phytmac_get_txq(pdata, queue); + __netif_tx_lock(nq, cpu); + + /* Avoid transmit queue timeout since we share it with the slow path */ + txq_trans_cond_update(nq); + + for (i = 0; i < n; i++) { + struct xdp_frame *xdpf = frames[i]; + int err; + + err = phytmac_start_xmit_xdp(pdata, queue, xdpf); + if (err != PHYTMAC_XDP_TX) + break; + nxmit++; + } + + __netif_tx_unlock(nq); + + return nxmit; +} + +static const struct net_device_ops phytmac_netdev_ops = { + .ndo_open = phytmac_open, + .ndo_stop = phytmac_close, + .ndo_start_xmit = phytmac_start_xmit, + .ndo_set_rx_mode = phytmac_set_rx_mode, + .ndo_get_stats = phytmac_get_stats, + .ndo_eth_ioctl = phytmac_ioctl, + .ndo_validate_addr = eth_validate_addr, + .ndo_change_mtu = phytmac_change_mtu, + .ndo_set_mac_address = phytmac_set_mac_address, + .ndo_set_features = phytmac_set_features, + .ndo_features_check = phytmac_features_check, + .ndo_vlan_rx_add_vid = ncsi_vlan_rx_add_vid, + .ndo_vlan_rx_kill_vid = ncsi_vlan_rx_kill_vid, + .ndo_bpf = phytmac_xdp, + .ndo_xdp_xmit = phytmac_xdp_xmit, +}; + +static int phytmac_init(struct phytmac *pdata) +{ + struct net_device *ndev = pdata->ndev; + unsigned int q; + struct phytmac_queue *queue; + int ret; + + if (netif_msg_probe(pdata)) + dev_dbg(pdata->dev, "phytmac init !\n"); + + spin_lock_init(&pdata->lock); + + /* set the queue register mapping once for all: queue0 has a special + * register mapping but we don't want to test the queue index then + * compute the corresponding register offset at run time. + */ + for (q = 0; q < pdata->queues_num; ++q) { + queue = &pdata->queues[q]; + queue->pdata = pdata; + queue->index = q; + spin_lock_init(&queue->tx_lock); + + netif_napi_add(ndev, &queue->tx_napi, phytmac_tx_poll); + netif_napi_add(ndev, &queue->rx_napi, phytmac_rx_poll); + + if (pdata->irq_type == IRQ_TYPE_INT || pdata->irq_type == IRQ_TYPE_MSI) { + queue->irq = pdata->queue_irq[q]; + if (pdata->irq_type == IRQ_TYPE_INT) + ret = devm_request_irq(pdata->dev, queue->irq, phytmac_irq, + IRQF_SHARED, ndev->name, queue); + else + ret = devm_request_irq(pdata->dev, queue->irq, phytmac_irq, + 0, ndev->name, queue); + + if (ret) { + dev_err(pdata->dev, + "Unable to request IRQ %d (error %d)\n", + queue->irq, ret); + return ret; + } + } + } + + if (pdata->irq_type == IRQ_TYPE_INTX) { + ret = devm_request_irq(pdata->dev, pdata->queue_irq[0], phytmac_intx_irq, + IRQF_SHARED, ndev->name, pdata); + if (ret) { + dev_err(pdata->dev, + "Unable to request INTX IRQ %d (error %d)\n", + pdata->queue_irq[0], ret); + return ret; + } + } + + ndev->netdev_ops = &phytmac_netdev_ops; + phytmac_set_ethtool_ops(ndev); + + if (ndev->hw_features & NETIF_F_NTUPLE) { + INIT_LIST_HEAD(&pdata->rx_fs_list.list); + pdata->rx_fs_list.count = 0; + spin_lock_init(&pdata->rx_fs_lock); + } + + device_set_wakeup_enable(pdata->dev, pdata->wol ? 1 : 0); + + return 0; +} + +static void phytmac_default_config(struct phytmac *pdata) +{ + struct net_device *ndev = pdata->ndev; + + pdata->rx_irq_mask = PHYTMAC_RX_INT_FLAGS; + pdata->tx_irq_mask = PHYTMAC_TX_INT_FLAGS; + pdata->tx_ring_size = DEFAULT_TX_RING_SIZE; + pdata->rx_ring_size = DEFAULT_RX_RING_SIZE; + pdata->max_tx_length = PHYTMAC_MAX_TX_LEN; + pdata->min_tx_length = PHYTMAC_MIN_TX_LEN; + pdata->pause = true; + + ndev->hw_features = NETIF_F_SG; + + if (pdata->capacities & PHYTMAC_CAPS_LSO) + ndev->hw_features |= NETIF_F_TSO | NETIF_F_TSO6; + + if (pdata->use_ncsi) { + ndev->hw_features &= ~(NETIF_F_HW_CSUM | NETIF_F_RXCSUM); + ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER; + } else { + ndev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; + } + + if (pdata->capacities & PHYTMAC_CAPS_SG_DISABLED) + ndev->hw_features &= ~NETIF_F_SG; + + ndev->hw_features |= NETIF_F_NTUPLE; + + ndev->min_mtu = ETH_MIN_MTU; + if (pdata->capacities & PHYTMAC_CAPS_JUMBO) + ndev->max_mtu = pdata->jumbo_len - ETH_HLEN - ETH_FCS_LEN; + else + ndev->max_mtu = ETH_DATA_LEN; + + ndev->features = ndev->hw_features; + ndev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT; + + switch (pdata->version) { + case VERSION_V3: + strscpy(pdata->fw_version, "MAC_FTM300", sizeof(pdata->fw_version)); + break; + + default: + strscpy(pdata->fw_version, "", sizeof(pdata->fw_version)); + break; + } +} + +static void phytmac_ncsi_handler(struct ncsi_dev *nd) +{ + if (unlikely(nd->state != ncsi_dev_state_functional)) + return; + + netdev_dbg(nd->dev, "NCSI interface %s\n", + nd->link_up ? "up" : "down"); +} + +int phytmac_drv_probe(struct phytmac *pdata) +{ + struct net_device *ndev = pdata->ndev; + struct device *dev = pdata->dev; + struct phytmac_hw_if *hw_if = pdata->hw_if; + int ret = 0; + + if (netif_msg_probe(pdata)) + dev_dbg(pdata->dev, "phytmac drv probe start\n"); + + phytmac_default_config(pdata); + + if (dma_set_mask(dev, DMA_BIT_MASK(40)) || + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40))) { + dev_err(dev, "dma_set_mask or coherent failed\n"); + return 1; + } + + ret = phytmac_init(pdata); + if (ret) + goto err_out; + + if (pdata->use_ncsi) { + pdata->ncsidev = ncsi_register_dev(ndev, phytmac_ncsi_handler); + if (!pdata->ncsidev) + goto err_out; + } + + if (pdata->power_state == PHYTMAC_POWEROFF) + hw_if->poweron(pdata, PHYTMAC_POWERON); + + if (hw_if->init_msg_ring) + hw_if->init_msg_ring(pdata); + + if (pdata->use_mii && !pdata->mii_bus) { + ret = phytmac_mdio_register(pdata); + if (ret) { + netdev_err(ndev, "MDIO bus registration failed\n"); + goto err_out_free_mdiobus; + } + } + + ret = phytmac_phylink_create(pdata); + if (ret) { + netdev_err(ndev, "phytmac phylink create failed, error %d\n", ret); + goto err_phylink_init; + } + + ret = phytmac_get_mac_address(pdata); + if (ret) { + netdev_err(ndev, "phytmac get mac address failed\n"); + goto err_phylink_init; + } + + ret = register_netdev(ndev); + if (ret) { + dev_err(pdata->dev, "Cannot register net device, aborting.\n"); + goto err_phylink_init; + } + netif_carrier_off(ndev); + + if (netif_msg_probe(pdata)) + dev_dbg(pdata->dev, "probe successfully! Phytium %s at 0x%08lx irq %d (%pM)\n", + "MAC", ndev->base_addr, ndev->irq, ndev->dev_addr); + + return 0; + +err_phylink_init: + if (pdata->mii_bus) + mdiobus_unregister(pdata->mii_bus); + +err_out_free_mdiobus: + if (pdata->mii_bus) + mdiobus_free(pdata->mii_bus); + +err_out: + return ret; +} +EXPORT_SYMBOL_GPL(phytmac_drv_probe); + +int phytmac_drv_remove(struct phytmac *pdata) +{ + struct net_device *ndev = pdata->ndev; + struct phytmac_hw_if *hw_if = pdata->hw_if; + + if (ndev) { + if (pdata->use_ncsi && pdata->ncsidev) + ncsi_unregister_dev(pdata->ncsidev); + + unregister_netdev(ndev); + if (pdata->power_state == PHYTMAC_POWERON) + hw_if->poweron(pdata, PHYTMAC_POWEROFF); + + if (pdata->use_mii && pdata->mii_bus) { + mdiobus_unregister(pdata->mii_bus); + mdiobus_free(pdata->mii_bus); + } + + if (pdata->phylink) + phylink_destroy(pdata->phylink); + } + + return 0; +} +EXPORT_SYMBOL_GPL(phytmac_drv_remove); + +int phytmac_drv_suspend(struct phytmac *pdata) +{ + int q; + unsigned long flags; + struct phytmac_queue *queue; + struct phytmac_hw_if *hw_if = pdata->hw_if; + + if (!netif_running(pdata->ndev)) + return 0; + + if (pdata->power_state == PHYTMAC_POWEROFF) + return 0; + + netif_carrier_off(pdata->ndev); + netif_device_detach(pdata->ndev); + + /* napi_disable */ + for (q = 0, queue = pdata->queues; q < pdata->queues_num; + ++q, ++queue) { + hw_if->disable_irq(pdata, queue->index, pdata->rx_irq_mask | pdata->tx_irq_mask); + hw_if->clear_irq(pdata, queue->index, pdata->rx_irq_mask | pdata->tx_irq_mask); + napi_disable(&queue->tx_napi); + napi_disable(&queue->rx_napi); + } + + if (pdata->wol) { + hw_if->set_wol(pdata, pdata->wol); + pdata->power_state = PHYTMAC_POWEROFF; + } else { + rtnl_lock(); + phylink_stop(pdata->phylink); + rtnl_unlock(); + spin_lock_irqsave(&pdata->lock, flags); + hw_if->reset_hw(pdata); + spin_unlock_irqrestore(&pdata->lock, flags); + hw_if->poweron(pdata, PHYTMAC_POWEROFF); + } + + return 0; +} +EXPORT_SYMBOL_GPL(phytmac_drv_suspend); + +int phytmac_drv_resume(struct phytmac *pdata) +{ + int q; + struct phytmac_queue *queue; + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct ethtool_rx_fs_item *item; + + hw_if->poweron(pdata, PHYTMAC_POWERON); + + if (hw_if->init_msg_ring) + hw_if->init_msg_ring(pdata); + + if (!netif_running(pdata->ndev)) + return 0; + + if (pdata->wol) { + hw_if->set_wol(pdata, 0); + rtnl_lock(); + phylink_stop(pdata->phylink); + rtnl_unlock(); + } + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; + ++q, ++queue) { + napi_enable(&queue->tx_napi); + napi_enable(&queue->rx_napi); + } + + hw_if->init_hw(pdata); + phytmac_set_rx_mode(pdata->ndev); + phytmac_set_features(pdata->ndev, pdata->ndev->features); + list_for_each_entry(item, &pdata->rx_fs_list.list, list) + hw_if->add_fdir_entry(pdata, &item->fs); + + rtnl_lock(); + phylink_start(pdata->phylink); + rtnl_unlock(); + + netif_device_attach(pdata->ndev); + + return 0; +} +EXPORT_SYMBOL_GPL(phytmac_drv_resume); + +struct phytmac *phytmac_alloc_pdata(struct device *dev) +{ + struct phytmac *pdata; + struct net_device *netdev; + + netdev = alloc_etherdev_mq(sizeof(struct phytmac), + PHYTMAC_MAX_QUEUES); + if (!netdev) { + dev_err(dev, "alloc_etherdev_mq failed\n"); + return ERR_PTR(-ENOMEM); + } + SET_NETDEV_DEV(netdev, dev); + pdata = netdev_priv(netdev); + pdata->ndev = netdev; + pdata->dev = dev; + + spin_lock_init(&pdata->lock); + spin_lock_init(&pdata->msg_ring.msg_lock); + spin_lock_init(&pdata->ts_clk_lock); + pdata->msg_enable = netif_msg_init(debug, PHYTMAC_DEFAULT_MSG_ENABLE); + + return pdata; +} +EXPORT_SYMBOL_GPL(phytmac_alloc_pdata); + +void phytmac_free_pdata(struct phytmac *pdata) +{ + struct net_device *netdev = pdata->ndev; + + free_netdev(netdev); +} +EXPORT_SYMBOL_GPL(phytmac_free_pdata); + +void phytmac_drv_shutdown(struct phytmac *pdata) +{ + struct net_device *netdev = pdata->ndev; + struct phytmac_hw_if *hw_if = pdata->hw_if; + + rtnl_lock(); + netif_device_detach(netdev); + + if (netif_running(netdev)) + phytmac_close(netdev); + rtnl_unlock(); + + if (pdata->power_state == PHYTMAC_POWERON) + hw_if->poweron(pdata, PHYTMAC_POWEROFF); +} +EXPORT_SYMBOL_GPL(phytmac_drv_shutdown); + +static void phytmac_devm_iounmap_np(struct device *dev, void *res) +{ + iounmap(*(void __iomem **)res); +} + +static void __iomem *phytmac_devm_ioremap_np(struct device *dev, resource_size_t offset, + resource_size_t size) +{ + void __iomem **ptr, *addr = NULL; + + ptr = devres_alloc_node(phytmac_devm_iounmap_np, sizeof(*ptr), GFP_KERNEL, + dev_to_node(dev)); + if (!ptr) + return NULL; + + addr = ioremap_np(offset, size); + if (addr) { + *ptr = addr; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return addr; +} + +void __iomem * +phytmac_devm_ioremap_resource_np(struct device *dev, const struct resource *res) +{ + resource_size_t size; + void __iomem *dest_ptr; + char *pretty_name; + + if (!res || resource_type(res) != IORESOURCE_MEM) { + dev_err(dev, "invalid resource %pR\n", res); + return IOMEM_ERR_PTR(-EINVAL); + } + + size = resource_size(res); + + if (res->name) + pretty_name = devm_kasprintf(dev, GFP_KERNEL, "%s %s", + dev_name(dev), res->name); + else + pretty_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL); + if (!pretty_name) { + dev_err(dev, "can't generate pretty name for resource %pR\n", res); + return IOMEM_ERR_PTR(-ENOMEM); + } + + if (!devm_request_mem_region(dev, res->start, size, pretty_name)) { + dev_err(dev, "can't request region for resource %pR\n", res); + return IOMEM_ERR_PTR(-EBUSY); + } + + dest_ptr = phytmac_devm_ioremap_np(dev, res->start, size); + if (!dest_ptr) { + dev_err(dev, "ioremap failed for resource %pR\n", res); + devm_release_mem_region(dev, res->start, size); + dest_ptr = IOMEM_ERR_PTR(-ENOMEM); + } + + return dest_ptr; +} +EXPORT_SYMBOL_GPL(phytmac_devm_ioremap_resource_np); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium Ethernet driver"); +MODULE_AUTHOR("Wenting Song"); +MODULE_ALIAS("platform:phytmac"); +MODULE_VERSION(PHYTMAC_DRIVER_VERSION); + diff --git a/drivers/net/ethernet/phytium/phytmac_pci.c b/drivers/net/ethernet/phytium/phytmac_pci.c new file mode 100644 index 00000000000000..69f34c2faaf5b0 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_pci.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Phytium GMAC PCI wrapper. + * + * Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. + * + * Author: Wenting Song + */ + +#include +#include +#include "phytmac.h" +#include "phytmac_v1.h" +#include "phytmac_v2.h" + +#define PCI_DEVICE_ID_GMAC 0xDC3B +#define PCI_SUBDEVICE_ID_SGMII 0x1000 +#define PCI_SUBDEVICE_ID_1000BASEX 0x1001 +#define PCI_SUBDEVICE_ID_2500BASEX 0x1002 +#define PCI_SUBDEVICE_ID_5GBASER 0x1003 +#define PCI_SUBDEVICE_ID_USXGMII 0x1004 +#define PCI_SUBDEVICE_ID_10GBASER 0x1005 + +struct phytmac_data { + struct phytmac_hw_if *hw_if; + u32 caps; + u16 queue_num; + int speed; + bool duplex; + bool use_mii; + bool use_ncsi; + phy_interface_t interface; + const struct property_entry *properties; +}; + +static const u32 fixedlink[][5] = { + {0, 1, 1000, 1, 0}, + {0, 1, 2500, 1, 0}, + {0, 1, 5000, 1, 0}, + {0, 1, 10000, 1, 0}, +}; + +static const struct property_entry fl_properties[][2] = { + {PROPERTY_ENTRY_U32_ARRAY("fixed-link", fixedlink[0]), {} }, + {PROPERTY_ENTRY_U32_ARRAY("fixed-link", fixedlink[1]), {} }, + {PROPERTY_ENTRY_U32_ARRAY("fixed-link", fixedlink[2]), {} }, + {PROPERTY_ENTRY_U32_ARRAY("fixed-link", fixedlink[3]), {} }, +}; + +static int phytmac_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct phytmac_data *data = (struct phytmac_data *)id->driver_data; + struct phytmac *pdata; + struct device *dev = &pdev->dev; + void __iomem * const *iomap_table; + struct fwnode_handle *fw_node = NULL; + int bar_mask; + int ret, i; + + pdata = phytmac_alloc_pdata(dev); + if (IS_ERR(pdata)) { + ret = PTR_ERR(pdata); + goto err_alloc; + } + + pdata->pcidev = pdev; + pci_set_drvdata(pdev, pdata); + + ret = pcim_enable_device(pdev); + if (ret) { + dev_err(dev, "pcim_enable_device failed\n"); + goto err_pci_enable; + } + + /* Obtain the mmio areas for the device */ + bar_mask = pci_select_bars(pdev, IORESOURCE_MEM); + ret = pcim_iomap_regions(pdev, bar_mask, PHYTMAC_PCI_DRV_NAME); + if (ret) { + dev_err(dev, "pcim_iomap_regions failed\n"); + goto err_pci_enable; + } + + iomap_table = pcim_iomap_table(pdev); + if (!iomap_table) { + dev_err(dev, "pcim_iomap_table failed\n"); + ret = -ENOMEM; + goto err_pci_enable; + } + + pdata->mac_regs = iomap_table[0]; + if (!pdata->mac_regs) { + dev_err(dev, "xgmac ioremap failed\n"); + ret = -ENOMEM; + goto err_pci_enable; + } + + pdata->msg_regs = iomap_table[1]; + if (!pdata->msg_regs) { + dev_err(dev, "xpcs ioremap failed\n"); + ret = -ENOMEM; + goto err_pci_enable; + } + + pci_set_master(pdev); + + /* para */ + pdata->dma_burst_length = DEFAULT_DMA_BURST_LENGTH; + pdata->jumbo_len = DEFAULT_DMA_BURST_LENGTH; + pdata->wol |= PHYTMAC_WAKE_MAGIC; + pdata->use_ncsi = data->use_ncsi; + pdata->use_mii = data->use_mii; + pdata->phy_interface = data->interface; + pdata->queues_num = data->queue_num; + pdata->capacities = data->caps; + pdata->hw_if = data->hw_if; + + if (!pdata->use_mii) { + fw_node = fwnode_create_software_node(data->properties, NULL); + if (IS_ERR(fw_node)) { + dev_err(dev, "Failed to create software node\n"); + goto err_pci_enable; + } + pdata->dev->fwnode = fw_node; + } + + /* irq */ + ret = pci_alloc_irq_vectors(pdev, pdata->queues_num, pdata->queues_num, PCI_IRQ_MSI); + if (ret < 0) { + pdata->irq_type = IRQ_TYPE_INTX; + pdata->queue_irq[0] = pdev->irq; + } else { + pdata->irq_type = IRQ_TYPE_MSI; + for (i = 0; i < pdata->queues_num; i++) + pdata->queue_irq[i] = pci_irq_vector(pdev, i); + } + + /* Configure the netdev resource */ + ret = phytmac_drv_probe(pdata); + if (ret) + goto err_irq_vectors; + + netdev_notice(pdata->ndev, "net device enabled\n"); + + return 0; + +err_irq_vectors: + if (fw_node) + fwnode_remove_software_node(fw_node); + pci_free_irq_vectors(pdata->pcidev); + +err_pci_enable: + phytmac_free_pdata(pdata); + +err_alloc: + dev_notice(dev, "net device not enabled\n"); + + return ret; +} + +static void phytmac_pci_remove(struct pci_dev *pdev) +{ + struct phytmac *pdata = pci_get_drvdata(pdev); + struct fwnode_handle *fw_node = dev_fwnode(pdata->dev); + int i = 0; + int bar_mask; + + if (fw_node) { + fwnode_remove_software_node(fw_node); + pdata->dev->fwnode = NULL; + } + + phytmac_drv_remove(pdata); + + for (i = 0; i < pdata->queues_num; i++) + free_irq(pci_irq_vector(pdev, i), &pdata->queues[i]); + pci_free_irq_vectors(pdev); + + phytmac_free_pdata(pdata); + bar_mask = pci_select_bars(pdev, IORESOURCE_MEM); + + pci_disable_device(pdev); +} + +static int __maybe_unused phytmac_pci_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct phytmac *pdata = pci_get_drvdata(pdev); + int ret; + + ret = phytmac_drv_suspend(pdata); + + return ret; +} + +static int __maybe_unused phytmac_pci_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct phytmac *pdata = pci_get_drvdata(pdev); + int ret; + + ret = phytmac_drv_resume(pdata); + + return ret; +} + +struct phytmac_data phytmac_sgmii = { + .hw_if = &phytmac_1p0_hw, + .caps = PHYTMAC_CAPS_TAILPTR + | PHYTMAC_CAPS_START + | PHYTMAC_CAPS_JUMBO + | PHYTMAC_CAPS_LSO, + .queue_num = 4, + .use_ncsi = false, + .use_mii = true, + .interface = PHY_INTERFACE_MODE_SGMII, +}; + +struct phytmac_data phytmac_1000basex = { + .hw_if = &phytmac_1p0_hw, + .caps = PHYTMAC_CAPS_TAILPTR + | PHYTMAC_CAPS_START + | PHYTMAC_CAPS_JUMBO + | PHYTMAC_CAPS_LSO, + .queue_num = 4, + .use_ncsi = false, + .use_mii = false, + .speed = 1000, + .duplex = true, + .interface = PHY_INTERFACE_MODE_1000BASEX, + .properties = fl_properties[0], +}; + +struct phytmac_data phytmac_2500basex = { + .hw_if = &phytmac_1p0_hw, + .caps = PHYTMAC_CAPS_TAILPTR + | PHYTMAC_CAPS_START + | PHYTMAC_CAPS_JUMBO + | PHYTMAC_CAPS_LSO, + .queue_num = 4, + .use_ncsi = false, + .use_mii = false, + .speed = 2500, + .duplex = true, + .interface = PHY_INTERFACE_MODE_2500BASEX, + .properties = fl_properties[1], +}; + +struct phytmac_data phytmac_5000baser = { + .hw_if = &phytmac_1p0_hw, + .caps = PHYTMAC_CAPS_TAILPTR + | PHYTMAC_CAPS_START + | PHYTMAC_CAPS_JUMBO + | PHYTMAC_CAPS_LSO, + .queue_num = 4, + .use_ncsi = false, + .use_mii = false, + .speed = 5000, + .duplex = true, + .interface = PHY_INTERFACE_MODE_5GBASER, + .properties = fl_properties[2], +}; + +struct phytmac_data phytmac_usxgmii = { + .hw_if = &phytmac_1p0_hw, + .caps = PHYTMAC_CAPS_TAILPTR + | PHYTMAC_CAPS_START + | PHYTMAC_CAPS_JUMBO + | PHYTMAC_CAPS_LSO, + .queue_num = 4, + .use_ncsi = false, + .use_mii = false, + .speed = 10000, + .duplex = true, + .interface = PHY_INTERFACE_MODE_USXGMII, + .properties = fl_properties[3], +}; + +static const struct pci_device_id phytmac_pci_table[] = { + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PHYTIUM, PCI_DEVICE_ID_GMAC, + PCI_VENDOR_ID_PHYTIUM, PCI_SUBDEVICE_ID_SGMII), + .driver_data = (kernel_ulong_t)&phytmac_sgmii}, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PHYTIUM, PCI_DEVICE_ID_GMAC, + PCI_VENDOR_ID_PHYTIUM, PCI_SUBDEVICE_ID_1000BASEX), + .driver_data = (kernel_ulong_t)&phytmac_1000basex}, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PHYTIUM, PCI_DEVICE_ID_GMAC, + PCI_VENDOR_ID_PHYTIUM, PCI_SUBDEVICE_ID_2500BASEX), + .driver_data = (kernel_ulong_t)&phytmac_2500basex}, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PHYTIUM, PCI_DEVICE_ID_GMAC, + PCI_VENDOR_ID_PHYTIUM, PCI_SUBDEVICE_ID_5GBASER), + .driver_data = (kernel_ulong_t)&phytmac_5000baser}, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PHYTIUM, PCI_DEVICE_ID_GMAC, + PCI_VENDOR_ID_PHYTIUM, PCI_SUBDEVICE_ID_USXGMII), + .driver_data = (kernel_ulong_t)&phytmac_usxgmii}, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PHYTIUM, PCI_DEVICE_ID_GMAC, + PCI_VENDOR_ID_PHYTIUM, PCI_SUBDEVICE_ID_10GBASER), + .driver_data = (kernel_ulong_t)&phytmac_usxgmii}, + /* Last entry must be zero */ + { 0, } +}; +MODULE_DEVICE_TABLE(pci, phytmac_pci_table); + +static const struct dev_pm_ops phytmac_pci_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(phytmac_pci_suspend, phytmac_pci_resume) +}; + +static struct pci_driver phytmac_driver = { + .name = PHYTMAC_PCI_DRV_NAME, + .id_table = phytmac_pci_table, + .probe = phytmac_pci_probe, + .remove = phytmac_pci_remove, + .driver = { + .pm = &phytmac_pci_pm_ops, + } +}; + +module_pci_driver(phytmac_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium NIC PCI wrapper"); +MODULE_VERSION(PHYTMAC_DRIVER_VERSION); diff --git a/drivers/net/ethernet/phytium/phytmac_platform.c b/drivers/net/ethernet/phytium/phytmac_platform.c new file mode 100644 index 00000000000000..225ba0835df927 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_platform.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Phytium GMAC Platform wrapper. + * + * Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. + * + * Author: Wenting Song + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include "phytmac.h" +#include "phytmac_v1.h" +#include "phytmac_v2.h" + +#include + +static const struct phytmac_config phytium_1p0_config = { + .hw_if = &phytmac_1p0_hw, + .caps = PHYTMAC_CAPS_TAILPTR + | PHYTMAC_CAPS_START + | PHYTMAC_CAPS_JUMBO + | PHYTMAC_CAPS_LSO, + .queue_num = 4, +}; + +static const struct phytmac_config phytium_2p0_config = { + .hw_if = &phytmac_2p0_hw, + .caps = PHYTMAC_CAPS_TAILPTR + | PHYTMAC_CAPS_RXPTR + | PHYTMAC_CAPS_LSO + | PHYTMAC_CAPS_MSG + | PHYTMAC_CAPS_JUMBO, + .queue_num = 2, +}; + +#if defined(CONFIG_OF) +static const struct of_device_id phytmac_dt_ids[] = { + { .compatible = "phytium,gmac-1.0", .data = &phytium_1p0_config }, + { .compatible = "phytium,gmac-2.0", .data = &phytium_2p0_config }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, phytmac_dt_ids); +#endif /* CONFIG_OF */ + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytmac_acpi_ids[] = { + { .id = "PHYT0046", .driver_data = (kernel_ulong_t)&phytium_1p0_config }, + { .id = "PHYT0056", .driver_data = (kernel_ulong_t)&phytium_2p0_config }, + {} +}; + +MODULE_DEVICE_TABLE(acpi, phytmac_acpi_ids); +#else +#define phytmac_acpi_ids NULL +#endif + +static const char *phytmac_phy_modes(enum phytmac_interface interface) +{ + switch (interface) { + case PHYTMAC_PHY_INTERFACE_MODE_NA: + return ""; + case PHYTMAC_PHY_INTERFACE_MODE_INTERNAL: + return "internal"; + case PHYTMAC_PHY_INTERFACE_MODE_MII: + return "mii"; + case PHYTMAC_PHY_INTERFACE_MODE_GMII: + return "gmii"; + case PHYTMAC_PHY_INTERFACE_MODE_SGMII: + return "sgmii"; + case PHYTMAC_PHY_INTERFACE_MODE_TBI: + return "tbi"; + case PHYTMAC_PHY_INTERFACE_MODE_REVMII: + return "rev-mii"; + case PHYTMAC_PHY_INTERFACE_MODE_RMII: + return "rmii"; + case PHYTMAC_PHY_INTERFACE_MODE_RGMII: + return "rgmii"; + case PHYTMAC_PHY_INTERFACE_MODE_RGMII_ID: + return "rgmii-id"; + case PHYTMAC_PHY_INTERFACE_MODE_RGMII_RXID: + return "rgmii-rxid"; + case PHYTMAC_PHY_INTERFACE_MODE_RGMII_TXID: + return "rgmii-txid"; + case PHYTMAC_PHY_INTERFACE_MODE_RTBI: + return "rtbi"; + case PHYTMAC_PHY_INTERFACE_MODE_SMII: + return "smii"; + case PHYTMAC_PHY_INTERFACE_MODE_XGMII: + return "xgmii"; + case PHYTMAC_PHY_INTERFACE_MODE_MOCA: + return "moca"; + case PHYTMAC_PHY_INTERFACE_MODE_QSGMII: + return "qsgmii"; + case PHYTMAC_PHY_INTERFACE_MODE_TRGMII: + return "trgmii"; + case PHYTMAC_PHY_INTERFACE_MODE_100BASEX: + return "100base-x"; + case PHYTMAC_PHY_INTERFACE_MODE_1000BASEX: + return "1000base-x"; + case PHYTMAC_PHY_INTERFACE_MODE_2500BASEX: + return "2500base-x"; + case PHYTMAC_PHY_INTERFACE_MODE_5GBASER: + return "5gbase-r"; + case PHYTMAC_PHY_INTERFACE_MODE_RXAUI: + return "rxaui"; + case PHYTMAC_PHY_INTERFACE_MODE_XAUI: + return "xaui"; + case PHYTMAC_PHY_INTERFACE_MODE_10GBASER: + return "10gbase-r"; + case PHYTMAC_PHY_INTERFACE_MODE_USXGMII: + return "usxgmii"; + case PHYTMAC_PHY_INTERFACE_MODE_10GKR: + return "10gbase-kr"; + default: + return "unknown"; + } +} + +static int phytmac_v2_get_phy_mode(struct platform_device *pdev) +{ + const char *pm; + int err, i; + int phy_interface; + + err = device_property_read_string(&pdev->dev, "phy-mode", &pm); + if (err < 0) + return err; + + phy_interface = PHYTMAC_PHY_INTERFACE_MODE_MAX + 1; + for (i = 0; i < PHYTMAC_PHY_INTERFACE_MODE_MAX; i++) { + if (!strcasecmp(pm, phytmac_phy_modes(i))) { + phy_interface = i; + dev_notice(&pdev->dev, "Phy mode is %s.\n", pm); + break; + } + } + + if (phy_interface > PHYTMAC_PHY_INTERFACE_MODE_MAX) { + dev_err(&pdev->dev, "Invalid phy mode value: %s!\n", pm); + return -EINVAL; + } + + return phy_interface; +} + +static int phytmac_get_phy_mode(struct platform_device *pdev) +{ + const char *pm; + int err, i; + + err = device_property_read_string(&pdev->dev, "phy-mode", &pm); + if (err < 0) + return err; + + for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++) { + if (!strcasecmp(pm, phy_modes(i))) + return i; + } + + return -ENODEV; +} + +static int phytmac_plat_probe(struct platform_device *pdev) +{ + const struct phytmac_config *phytmac_config = &phytium_1p0_config; +#ifdef CONFIG_OF + struct device_node *np; +#endif + struct resource *regs; + struct phytmac *pdata; + int ret, i; + u32 queue_num; + const struct of_device_id *match = NULL; + const struct acpi_device_id *match_acpi = NULL; + + pdata = phytmac_alloc_pdata(&pdev->dev); + if (IS_ERR(pdata)) { + ret = PTR_ERR(pdata); + goto err_alloc; + } + + platform_set_drvdata(pdev, pdata); + + pdata->platdev = pdev; + + if (pdev->dev.of_node) { + match = of_match_node(phytmac_dt_ids, np); + if (match && match->data) { + phytmac_config = match->data; + pdata->hw_if = phytmac_config->hw_if; + pdata->capacities = phytmac_config->caps; + pdata->queues_max_num = phytmac_config->queue_num; + } + } else if (has_acpi_companion(&pdev->dev)) { + match_acpi = acpi_match_device(phytmac_acpi_ids, &pdev->dev); + if (match_acpi && match_acpi->driver_data) { + phytmac_config = (void *)match_acpi->driver_data; + pdata->hw_if = phytmac_config->hw_if; + pdata->capacities = phytmac_config->caps; + pdata->queues_max_num = phytmac_config->queue_num; + } + } + + i = 0; + regs = platform_get_resource(pdev, IORESOURCE_MEM, i); + pdata->mac_regs = phytmac_devm_ioremap_resource_np(&pdev->dev, regs); + if (IS_ERR(pdata->mac_regs)) { + dev_err(&pdev->dev, "mac_regs ioremap failed\n"); + ret = PTR_ERR(pdata->mac_regs); + goto err_mem; + } + pdata->ndev->base_addr = regs->start; + + if (pdev->dev.of_node && match) { + if (!strcmp(match->compatible, "phytium,gmac-1.0")) + pdata->version = PHYTMAC_READ(pdata, PHYTMAC_VERSION) & 0xff; + else + pdata->version = VERSION_V3; + } else if (has_acpi_companion(&pdev->dev) && match_acpi) { + if (!strcmp(match_acpi->id, "PHYT0046")) + pdata->version = PHYTMAC_READ(pdata, PHYTMAC_VERSION) & 0xff; + else + pdata->version = VERSION_V3; + } + + if (pdata->capacities & PHYTMAC_CAPS_MSG) { + ++i; + regs = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (regs) { + pdata->msg_regs = ioremap_wt(regs->start, MEMORY_SIZE); + if (!pdata->msg_regs) { + dev_err(&pdev->dev, "msg_regs ioremap failed, i=%d\n", i); + goto err_mem; + } + } + } + + if (device_property_read_bool(&pdev->dev, "powerctrl")) + pdata->capacities |= PHYTMAC_CAPS_PWCTRL; + + if (pdata->version == VERSION_V3 && pdev->dev.of_node) { + ++i; + regs = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (regs) { + pdata->mhu_regs = ioremap(regs->start, MHU_SIZE); + if (!pdata->mhu_regs) + dev_err(&pdev->dev, "mhu_regs ioremap failed, i=%d\n", i); + } + } + + if (device_property_read_u32(&pdev->dev, "dma-burst-length", &pdata->dma_burst_length)) + pdata->dma_burst_length = DEFAULT_DMA_BURST_LENGTH; + + if (device_property_read_u32(&pdev->dev, "jumbo-max-length", &pdata->jumbo_len)) + pdata->jumbo_len = DEFAULT_JUMBO_MAX_LENGTH; + + if (device_property_read_u32(&pdev->dev, "queue-number", &queue_num)) + pdata->queues_num = pdata->queues_max_num; + else + pdata->queues_num = queue_num; + + pdata->wol = 0; + if (device_property_read_bool(&pdev->dev, "magic-packet")) + pdata->wol |= PHYTMAC_WAKE_MAGIC; + + pdata->use_ncsi = device_property_read_bool(&pdev->dev, "use-ncsi"); + pdata->use_mii = device_property_read_bool(&pdev->dev, "use-mii"); + + pdata->power_state = PHYTMAC_POWEROFF; + + device_set_wakeup_capable(&pdev->dev, pdata->wol & PHYTMAC_WOL_MAGIC_PACKET); + + for (i = 0; i < pdata->queues_num; i++) { + pdata->irq_type = IRQ_TYPE_INT; + pdata->queue_irq[i] = platform_get_irq(pdev, i); + } + + ret = phytmac_get_phy_mode(pdev); + if (ret < 0) + pdata->phy_interface = PHY_INTERFACE_MODE_MII; + else + pdata->phy_interface = ret; + + if (pdata->version == VERSION_V3) { + ret = phytmac_v2_get_phy_mode(pdev); + if (ret < 0) + pdata->phytmac_v2_interface = PHYTMAC_PHY_INTERFACE_MODE_USXGMII; + else + pdata->phytmac_v2_interface = ret; + } + + ret = phytmac_drv_probe(pdata); + if (ret) + goto err_mem; + + if (netif_msg_probe(pdata)) { + dev_notice(&pdev->dev, "phytium net device enabled\n"); + dev_dbg(pdata->dev, "use_ncsi:%d, use_mii:%d, wol:%d, queues_num:%d\n", + pdata->use_ncsi, pdata->use_mii, pdata->wol, pdata->queues_num); + } + + return 0; + +err_mem: + phytmac_free_pdata(pdata); + +err_alloc: + dev_err(&pdev->dev, "phytium net device not enabled\n"); + + return ret; +} + +static void phytmac_plat_remove(struct platform_device *pdev) +{ + struct phytmac *pdata = platform_get_drvdata(pdev); + + phytmac_drv_remove(pdata); + phytmac_free_pdata(pdata); +} + +static void phytmac_plat_shutdown(struct platform_device *pdev) +{ + struct phytmac *pdata = platform_get_drvdata(pdev); + + phytmac_drv_shutdown(pdata); +} + +static int __maybe_unused phytmac_plat_suspend(struct device *dev) +{ + struct phytmac *pdata = dev_get_drvdata(dev); + int ret; + + ret = phytmac_drv_suspend(pdata); + + return ret; +} + +static int __maybe_unused phytmac_plat_resume(struct device *dev) +{ + struct phytmac *pdata = dev_get_drvdata(dev); + int ret; + + ret = phytmac_drv_resume(pdata); + + return ret; +} + +static const struct dev_pm_ops phytmac_plat_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(phytmac_plat_suspend, phytmac_plat_resume) +}; + +static struct platform_driver phytmac_driver = { + .probe = phytmac_plat_probe, + .remove = phytmac_plat_remove, + .driver = { + .name = PHYTMAC_PLAT_DRV_NAME, + .of_match_table = of_match_ptr(phytmac_dt_ids), + .acpi_match_table = phytmac_acpi_ids, + .pm = &phytmac_plat_pm_ops, + }, + .shutdown = phytmac_plat_shutdown, +}; + +module_platform_driver(phytmac_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium Ethernet driver"); +MODULE_AUTHOR("Wenting Song"); +MODULE_ALIAS("platform:phytmac"); +MODULE_VERSION(PHYTMAC_DRIVER_VERSION); diff --git a/drivers/net/ethernet/phytium/phytmac_ptp.c b/drivers/net/ethernet/phytium/phytmac_ptp.c new file mode 100644 index 00000000000000..4803842bd7c17c --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_ptp.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** + * 1588 PTP support for Phytium GMAC device. + * + * Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. + * + * Author: Wenting Song + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phytmac.h" +#include "phytmac_ptp.h" + +bool phytmac_ptp_one_step(struct sk_buff *skb) +{ + struct ptp_header *hdr; + unsigned int ptp_class; + u8 msgtype; + + /* No need to parse packet if PTP TS is not involved */ + if (likely(!(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP))) + goto not_oss; + + /* Identify and return whether PTP one step sync is being processed */ + ptp_class = ptp_classify_raw(skb); + if (ptp_class == PTP_CLASS_NONE) + goto not_oss; + + hdr = ptp_parse_header(skb, ptp_class); + if (!hdr) + goto not_oss; + + if (hdr->flag_field[0] & 0x2) + goto not_oss; + + msgtype = ptp_get_msgtype(hdr, ptp_class); + if (msgtype == PTP_MSGTYPE_SYNC) + return true; + +not_oss: + return false; +} + +int phytmac_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) +{ + struct phytmac *pdata = container_of(ptp, struct phytmac, ptp_clock_info); + struct phytmac_hw_if *hw_if = pdata->hw_if; + unsigned long flags; + + spin_lock_irqsave(&pdata->ts_clk_lock, flags); + hw_if->get_time(pdata, ts); + spin_unlock_irqrestore(&pdata->ts_clk_lock, flags); + + return 0; +} + +int phytmac_ptp_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct phytmac *pdata = container_of(ptp, struct phytmac, ptp_clock_info); + struct phytmac_hw_if *hw_if = pdata->hw_if; + unsigned long flags; + + spin_lock_irqsave(&pdata->ts_clk_lock, flags); + hw_if->set_time(pdata, ts->tv_sec, ts->tv_nsec); + spin_unlock_irqrestore(&pdata->ts_clk_lock, flags); + + return 0; +} + +int phytmac_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + struct phytmac *pdata = container_of(ptp, struct phytmac, ptp_clock_info); + struct phytmac_hw_if *hw_if = pdata->hw_if; + bool negative = false; + + if (scaled_ppm < 0) { + negative = true; + scaled_ppm = -scaled_ppm; + } + + hw_if->adjust_fine(pdata, scaled_ppm, negative); + return 0; +} + +int phytmac_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + struct phytmac *pdata = container_of(ptp, struct phytmac, ptp_clock_info); + struct phytmac_hw_if *hw_if = pdata->hw_if; + int negative = 0; + + if (delta < 0) { + negative = 1; + delta = -delta; + } + + spin_lock_irq(&pdata->ts_clk_lock); + hw_if->adjust_time(pdata, delta, negative); + spin_unlock_irq(&pdata->ts_clk_lock); + + return 0; +} + +int phytmac_ptp_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + return -EOPNOTSUPP; +} + +void phytmac_ptp_init_timer(struct phytmac *pdata) +{ + struct phytmac_hw_if *hw_if = pdata->hw_if; + u32 rem = 0; + u64 adj; + + pdata->ts_rate = hw_if->get_ts_rate(pdata); + pdata->ts_incr.ns = div_u64_rem(NSEC_PER_SEC, pdata->ts_rate, &rem); + if (rem) { + adj = rem; + adj <<= 24; + pdata->ts_incr.sub_ns = div_u64(adj, pdata->ts_rate); + } else { + pdata->ts_incr.sub_ns = 0; + } +} + +void phytmac_ptp_rxstamp(struct phytmac *pdata, struct sk_buff *skb, + struct phytmac_dma_desc *desc) +{ + struct skb_shared_hwtstamps *shhwtstamps = skb_hwtstamps(skb); + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct timespec64 ts; + + if (hw_if->ts_valid(pdata, desc, PHYTMAC_RX)) { + hw_if->get_timestamp(pdata, desc->desc4, desc->desc5, &ts); + memset(shhwtstamps, 0, sizeof(struct skb_shared_hwtstamps)); + shhwtstamps->hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec); + } +} + +int phytmac_ptp_txstamp(struct phytmac_queue *queue, struct sk_buff *skb, + struct phytmac_dma_desc *desc) +{ + struct phytmac *pdata = queue->pdata; + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct timespec64 ts; + struct skb_shared_hwtstamps shhwtstamps; + + if (queue->pdata->ts_config.tx_type == TS_DISABLED) + return -EOPNOTSUPP; + + if (!hw_if->ts_valid(pdata, desc, PHYTMAC_TX)) + return -EINVAL; + + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + hw_if->get_timestamp(pdata, desc->desc4, desc->desc5, &ts); + memset(&shhwtstamps, 0, sizeof(shhwtstamps)); + shhwtstamps.hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec); + skb_tstamp_tx(skb, &shhwtstamps); + + return 0; +} + +int phytmac_ptp_register(struct phytmac *pdata) +{ + pdata->ptp_clock_info.owner = THIS_MODULE; + snprintf(pdata->ptp_clock_info.name, 16, "%s", pdata->ndev->name); + pdata->ptp_clock_info.max_adj = 64000000; /* In PPB */ + pdata->ptp_clock_info.n_alarm = 0; + pdata->ptp_clock_info.n_ext_ts = 0; + pdata->ptp_clock_info.n_per_out = 0; + pdata->ptp_clock_info.pps = 1; + pdata->ptp_clock_info.adjfine = phytmac_ptp_adjfine; + pdata->ptp_clock_info.adjtime = phytmac_ptp_adjtime; + pdata->ptp_clock_info.gettime64 = phytmac_ptp_gettime; + pdata->ptp_clock_info.settime64 = phytmac_ptp_settime; + pdata->ptp_clock_info.enable = phytmac_ptp_enable; + pdata->ptp_clock = ptp_clock_register(&pdata->ptp_clock_info, pdata->dev); + if (IS_ERR_OR_NULL(pdata->ptp_clock)) { + dev_err(pdata->dev, "ptp_clock_register failed %lu\n", + PTR_ERR(pdata->ptp_clock)); + return -EINVAL; + } + + return 0; +} + +void phytmac_ptp_unregister(struct phytmac *pdata) +{ + struct phytmac_hw_if *hw_if = pdata->hw_if; + + if (pdata->ptp_clock) + ptp_clock_unregister(pdata->ptp_clock); + pdata->ptp_clock = NULL; + + hw_if->clear_time(pdata); + + dev_info(pdata->dev, "phytmac ptp clock unregistered.\n"); +} + +void phytmac_ptp_init(struct net_device *ndev) +{ + struct phytmac *pdata = netdev_priv(ndev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + + phytmac_ptp_init_timer(pdata); + + hw_if->init_ts_hw(pdata); + + dev_info(pdata->dev, "phytmac ptp clock init success.\n"); +} + +int phytmac_ptp_get_ts_config(struct net_device *dev, struct ifreq *rq) +{ + struct hwtstamp_config *tstamp_config; + struct phytmac *pdata = netdev_priv(dev); + + if (!IS_REACHABLE(CONFIG_PHYTMAC_ENABLE_PTP)) + return -EOPNOTSUPP; + + tstamp_config = &pdata->ts_config; + + if (copy_to_user(rq->ifr_data, tstamp_config, sizeof(*tstamp_config))) + return -EFAULT; + else + return 0; +} + +int phytmac_ptp_set_ts_config(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct hwtstamp_config config; + struct phytmac *pdata = netdev_priv(dev); + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct ts_ctrl tstamp_ctrl; + int ret; + + memset(&tstamp_ctrl, 0, sizeof(struct ts_ctrl)); + + if (copy_from_user(&config, ifr->ifr_data, sizeof(config))) + return -EFAULT; + + switch (config.tx_type) { + case HWTSTAMP_TX_OFF: + break; + case HWTSTAMP_TX_ONESTEP_SYNC: + tstamp_ctrl.one_step = 1; + tstamp_ctrl.tx_control = TS_ALL_FRAMES; + break; + case HWTSTAMP_TX_ON: + tstamp_ctrl.one_step = 0; + tstamp_ctrl.tx_control = TS_ALL_FRAMES; + break; + default: + return -ERANGE; + } + + switch (config.rx_filter) { + case HWTSTAMP_FILTER_NONE: + case HWTSTAMP_FILTER_PTP_V1_L4_SYNC: + case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ: + break; + case HWTSTAMP_FILTER_PTP_V2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: + case HWTSTAMP_FILTER_PTP_V2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: + case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: + case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: + case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ: + tstamp_ctrl.rx_control = TS_ALL_PTP_FRAMES; + config.rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT; + break; + case HWTSTAMP_FILTER_PTP_V1_L4_EVENT: + case HWTSTAMP_FILTER_ALL: + tstamp_ctrl.rx_control = TS_ALL_FRAMES; + config.rx_filter = HWTSTAMP_FILTER_ALL; + break; + default: + config.rx_filter = HWTSTAMP_FILTER_NONE; + return -ERANGE; + } + + ret = hw_if->set_ts_config(pdata, &tstamp_ctrl); + if (ret) + return ret; + + /* save these settings for future reference */ + pdata->ts_config = config; + memcpy(&pdata->ts_config, &config, sizeof(config)); + + if (copy_to_user(ifr->ifr_data, &config, sizeof(config))) + return -EFAULT; + else + return 0; +} + diff --git a/drivers/net/ethernet/phytium/phytmac_ptp.h b/drivers/net/ethernet/phytium/phytmac_ptp.h new file mode 100644 index 00000000000000..e9497a172d25e2 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_ptp.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Phytium Ethernet Controller driver + * + * Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _PHYTMAC_PTP_H +#define _PHYTMAC_PTP_H + +#ifdef CONFIG_PHYTMAC_ENABLE_PTP +bool phytmac_ptp_one_step(struct sk_buff *skb); +int phytmac_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts); +int phytmac_ptp_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts); +int phytmac_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm); +int phytmac_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta); +int phytmac_ptp_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on); +void phytmac_ptp_init_timer(struct phytmac *pdata); +void phytmac_ptp_rxstamp(struct phytmac *pdata, struct sk_buff *skb, + struct phytmac_dma_desc *desc); +int phytmac_ptp_txstamp(struct phytmac_queue *queue, struct sk_buff *skb, + struct phytmac_dma_desc *desc); +int phytmac_ptp_register(struct phytmac *pdata); +void phytmac_ptp_unregister(struct phytmac *pdata); +void phytmac_ptp_init(struct net_device *ndev); +int phytmac_ptp_get_ts_config(struct net_device *dev, struct ifreq *rq); +int phytmac_ptp_set_ts_config(struct net_device *dev, struct ifreq *ifr, int cmd); +#else +static inline bool phytmac_ptp_one_step(struct sk_buff *skb) +{ + return 1; +} + +static inline void phytmac_ptp_rxstamp(struct phytmac *pdata, struct sk_buff *skb, + struct phytmac_dma_desc *desc) {} +static inline int phytmac_ptp_txstamp(struct phytmac_queue *queue, struct sk_buff *skb, + struct phytmac_dma_desc *desc) +{ + return -1; +} + +static inline int phytmac_ptp_register(struct phytmac *pdata) +{ + return 0; +} + +static inline void phytmac_ptp_unregister(struct phytmac *pdata) {} +static inline void phytmac_ptp_init(struct net_device *ndev) {} + +#endif +#endif diff --git a/drivers/net/ethernet/phytium/phytmac_v1.c b/drivers/net/ethernet/phytium/phytmac_v1.c new file mode 100644 index 00000000000000..870f07edc58fc5 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_v1.c @@ -0,0 +1,1482 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phytmac.h" +#include "phytmac_v1.h" + +static int phytmac_enable_promise(struct phytmac *pdata, int enable) +{ + u32 value = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG); + + if (enable) + value |= PHYTMAC_BIT(PROMISC); + else + value &= ~PHYTMAC_BIT(PROMISC); + + PHYTMAC_WRITE(pdata, PHYTMAC_NCONFIG, value); + + return 0; +} + +static int phytmac_enable_multicast(struct phytmac *pdata, int enable) +{ + u32 config; + + if (enable) { + PHYTMAC_WRITE(pdata, PHYTMAC_HASHB, -1); + PHYTMAC_WRITE(pdata, PHYTMAC_HASHT, -1); + config = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG); + config |= PHYTMAC_BIT(MH_EN); + PHYTMAC_WRITE(pdata, PHYTMAC_NCONFIG, config); + } else { + PHYTMAC_WRITE(pdata, PHYTMAC_HASHB, 0); + PHYTMAC_WRITE(pdata, PHYTMAC_HASHT, 0); + config = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG); + config &= ~PHYTMAC_BIT(MH_EN); + PHYTMAC_WRITE(pdata, PHYTMAC_NCONFIG, config); + } + + return 0; +} + +static int phytmac_set_mc_hash(struct phytmac *pdata, unsigned long *mc_filter) +{ + u32 config; + + PHYTMAC_WRITE(pdata, PHYTMAC_HASHB, mc_filter[0]); + PHYTMAC_WRITE(pdata, PHYTMAC_HASHT, mc_filter[1]); + config = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG); + config |= PHYTMAC_BIT(MH_EN); + PHYTMAC_WRITE(pdata, PHYTMAC_NCONFIG, config); + + return 0; +} + +static int phytmac_enable_rxcsum(struct phytmac *pdata, int enable) +{ + u32 value = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG); + + if (enable) + value |= PHYTMAC_BIT(RCO_EN); + else + value &= ~PHYTMAC_BIT(RCO_EN); + + PHYTMAC_WRITE(pdata, PHYTMAC_NCONFIG, value); + + return 0; +} + +static int phytmac_enable_txcsum(struct phytmac *pdata, int enable) +{ + u32 value = PHYTMAC_READ(pdata, PHYTMAC_DCONFIG); + + if (enable) + value |= PHYTMAC_BIT(TCO_EN); + else + value &= ~PHYTMAC_BIT(TCO_EN); + + PHYTMAC_WRITE(pdata, PHYTMAC_DCONFIG, value); + + return 0; +} + +static int phytmac_enable_mdio(struct phytmac *pdata, int enable) +{ + u32 value = PHYTMAC_READ(pdata, PHYTMAC_NCTRL); + + if (enable) + value |= PHYTMAC_BIT(MPE); + else + value &= ~PHYTMAC_BIT(MPE); + + PHYTMAC_WRITE(pdata, PHYTMAC_NCTRL, value); + + return 0; +} + +static int phytmac_enable_pause(struct phytmac *pdata, int enable) +{ + u32 value = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG); + + if (enable) + value |= PHYTMAC_BIT(PAUSE_EN); + else + value &= ~PHYTMAC_BIT(PAUSE_EN); + + PHYTMAC_WRITE(pdata, PHYTMAC_NCONFIG, value); + return 0; +} + +static int phytmac_enable_network(struct phytmac *pdata, int enable, int rx_tx) +{ + u32 old_ctrl = PHYTMAC_READ(pdata, PHYTMAC_NCTRL); + u32 ctrl; + + ctrl = old_ctrl; + + if (rx_tx & PHYTMAC_TX) { + if (enable) + ctrl |= PHYTMAC_BIT(TE); + else + ctrl &= ~PHYTMAC_BIT(TE); + } + + if (rx_tx & PHYTMAC_RX) { + if (enable) + ctrl |= PHYTMAC_BIT(RE); + else + ctrl &= ~PHYTMAC_BIT(RE); + } + + if (ctrl ^ old_ctrl) + PHYTMAC_WRITE(pdata, PHYTMAC_NCTRL, ctrl); + + return 0; +} + +static int phytmac_enable_autoneg(struct phytmac *pdata, int enable) +{ + u32 value = PHYTMAC_READ(pdata, PHYTMAC_PCSCTRL); + + if (enable) + value |= PHYTMAC_BIT(AUTONEG); + else + value &= ~PHYTMAC_BIT(AUTONEG); + + PHYTMAC_WRITE(pdata, PHYTMAC_PCSCTRL, value); + + return 0; +} + +static int phytmac_pcs_software_reset(struct phytmac *pdata, int reset) +{ + u32 value = PHYTMAC_READ(pdata, PHYTMAC_PCSCTRL); + + if (reset) + value |= PHYTMAC_BIT(PCS_RESET); + else + value &= ~PHYTMAC_BIT(PCS_RESET); + + PHYTMAC_WRITE(pdata, PHYTMAC_PCSCTRL, value); + + return 0; +} + +static int phytmac_mac_linkup(struct phytmac *pdata, phy_interface_t interface, + int speed, int duplex) +{ + u32 ctrl, config; + + config = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG); + + config &= ~(PHYTMAC_BIT(SPEED) | PHYTMAC_BIT(FD) | PHYTMAC_BIT(GM_EN)); + + if (speed == SPEED_100) + config |= PHYTMAC_BIT(SPEED); + else if (speed == SPEED_1000 || speed == SPEED_2500) + config |= PHYTMAC_BIT(GM_EN); + + if (duplex) + config |= PHYTMAC_BIT(FD); + + PHYTMAC_WRITE(pdata, PHYTMAC_NCONFIG, config); + + if (speed == SPEED_2500) { + ctrl = PHYTMAC_READ(pdata, PHYTMAC_NCTRL); + ctrl |= PHYTMAC_BIT(2PT5G); + PHYTMAC_WRITE(pdata, PHYTMAC_NCTRL, ctrl); + } + + if (speed == SPEED_10000) + PHYTMAC_WRITE(pdata, PHYTMAC_HCONFIG, PHYTMAC_SPEED_10000M); + else if (speed == SPEED_5000) + PHYTMAC_WRITE(pdata, PHYTMAC_HCONFIG, PHYTMAC_SPEED_5000M); + else if (speed == SPEED_2500) + PHYTMAC_WRITE(pdata, PHYTMAC_HCONFIG, PHYTMAC_SPEED_2500M); + else if (speed == SPEED_1000) + PHYTMAC_WRITE(pdata, PHYTMAC_HCONFIG, PHYTMAC_SPEED_1000M); + else + PHYTMAC_WRITE(pdata, PHYTMAC_HCONFIG, PHYTMAC_SPEED_100M); + + if (interface == PHY_INTERFACE_MODE_SGMII) { + if (speed == SPEED_2500) + phytmac_enable_autoneg(pdata, 0); + } + + return 0; +} + +static int phytmac_mac_linkdown(struct phytmac *pdata) +{ + return 0; +} + +static int phytmac_pcs_linkup(struct phytmac *pdata, phy_interface_t interface, + int speed, int duplex) +{ + u32 config; + + if (interface == PHY_INTERFACE_MODE_USXGMII || + interface == PHY_INTERFACE_MODE_10GBASER) { + config = PHYTMAC_READ(pdata, PHYTMAC_USXCTRL); + if (speed == SPEED_10000) { + config = PHYTMAC_SET_BITS(config, SERDES_RATE, PHYTMAC_SERDES_RATE_10G); + config = PHYTMAC_SET_BITS(config, USX_SPEED, PHYTMAC_SPEED_10000M); + } else if (speed == SPEED_5000) { + config = PHYTMAC_SET_BITS(config, SERDES_RATE, PHYTMAC_SERDES_RATE_5G); + config = PHYTMAC_SET_BITS(config, USX_SPEED, PHYTMAC_SPEED_5000M); + } + + /* reset */ + config &= ~(PHYTMAC_BIT(RX_EN) | PHYTMAC_BIT(TX_EN)); + config |= PHYTMAC_BIT(RX_SYNC_RESET); + + PHYTMAC_WRITE(pdata, PHYTMAC_USXCTRL, config); + + /* enable rx and tx */ + config &= ~(PHYTMAC_BIT(RX_SYNC_RESET)); + config |= PHYTMAC_BIT(RX_EN) | PHYTMAC_BIT(TX_EN); + + PHYTMAC_WRITE(pdata, PHYTMAC_USXCTRL, config); + } + + return 0; +} + +static int phytmac_pcs_linkdown(struct phytmac *pdata) +{ + return 0; +} + +static int phytmac_get_mac_addr(struct phytmac *pdata, u8 *addr) +{ + u32 bottom; + u16 top; + + bottom = PHYTMAC_READ(pdata, PHYTMAC_MAC1B); + top = PHYTMAC_READ(pdata, PHYTMAC_MAC1T); + + addr[0] = bottom & 0xff; + addr[1] = (bottom >> 8) & 0xff; + addr[2] = (bottom >> 16) & 0xff; + addr[3] = (bottom >> 24) & 0xff; + addr[4] = top & 0xff; + addr[5] = (top >> 8) & 0xff; + + return 0; +} + +static int phytmac_set_mac_addr(struct phytmac *pdata, const u8 *addr) +{ + u32 bottom; + u16 top; + + bottom = cpu_to_le32(*((u32 *)addr)); + PHYTMAC_WRITE(pdata, PHYTMAC_MAC1B, bottom); + top = cpu_to_le16(*((u16 *)(addr + 4))); + PHYTMAC_WRITE(pdata, PHYTMAC_MAC1T, top); + + return 0; +} + +static void phytmac_reset_hw(struct phytmac *pdata) +{ + unsigned int q; + u32 ctrl; + + ctrl = PHYTMAC_READ(pdata, PHYTMAC_NCTRL); + + ctrl &= ~(PHYTMAC_BIT(RE) | PHYTMAC_BIT(TE)); + ctrl |= PHYTMAC_BIT(CLEARSTAT); + PHYTMAC_WRITE(pdata, PHYTMAC_NCTRL, ctrl); + + /* Disable and clear all interrupts and disable queues */ + for (q = 0; q < pdata->queues_max_num; ++q) { + if (q == 0) { + PHYTMAC_WRITE(pdata, PHYTMAC_ID, -1); + PHYTMAC_WRITE(pdata, PHYTMAC_IS, -1); + PHYTMAC_WRITE(pdata, PHYTMAC_TXPTR_Q0, 1); + PHYTMAC_WRITE(pdata, PHYTMAC_RXPTR_Q0, 1); + } else { + PHYTMAC_WRITE(pdata, PHYTMAC_IDR(q - 1), -1); + PHYTMAC_WRITE(pdata, PHYTMAC_ISR(q - 1), -1); + PHYTMAC_WRITE(pdata, PHYTMAC_TXPTR(q - 1), 1); + PHYTMAC_WRITE(pdata, PHYTMAC_RXPTR(q - 1), 1); + } + + PHYTMAC_WRITE(pdata, PHYTMAC_TXPTRH(q), 0); + PHYTMAC_WRITE(pdata, PHYTMAC_RXPTRH(q), 0); + + if (pdata->capacities & PHYTMAC_CAPS_TAILPTR) + PHYTMAC_WRITE(pdata, PHYTMAC_TX_TAILPTR(q), 0); + + if (pdata->capacities & PHYTMAC_CAPS_RXPTR) + PHYTMAC_WRITE(pdata, PHYTMAC_RX_TAILPTR(q), 0); + } +} + +static void phytmac_get_regs(struct phytmac *pdata, u32 *reg_buff) +{ + reg_buff[0] = PHYTMAC_READ(pdata, PHYTMAC_NCTRL); + reg_buff[1] = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG); + reg_buff[2] = PHYTMAC_READ(pdata, PHYTMAC_NSTATUS); + reg_buff[3] = PHYTMAC_READ(pdata, PHYTMAC_DCONFIG); + reg_buff[4] = PHYTMAC_READ(pdata, PHYTMAC_TXPTR_Q0); + reg_buff[5] = PHYTMAC_READ(pdata, PHYTMAC_RXPTR_Q0); + reg_buff[6] = PHYTMAC_READ(pdata, PHYTMAC_TXPTR(1)); + reg_buff[7] = PHYTMAC_READ(pdata, PHYTMAC_RXPTR(1)); + reg_buff[8] = PHYTMAC_READ(pdata, PHYTMAC_TXPTR(2)); + reg_buff[9] = PHYTMAC_READ(pdata, PHYTMAC_RXPTR(2)); + reg_buff[10] = PHYTMAC_READ(pdata, PHYTMAC_TXPTR(3)); + reg_buff[11] = PHYTMAC_READ(pdata, PHYTMAC_RXPTR(3)); + reg_buff[12] = PHYTMAC_READ(pdata, PHYTMAC_HCONFIG); + reg_buff[13] = PHYTMAC_READ(pdata, PHYTMAC_IM); + if (pdata->phy_interface == PHY_INTERFACE_MODE_USXGMII || + pdata->phy_interface == PHY_INTERFACE_MODE_10GBASER) { + reg_buff[14] = PHYTMAC_READ(pdata, PHYTMAC_USXCTRL); + reg_buff[15] = PHYTMAC_READ(pdata, PHYTMAC_USXSTATUS); + } else { + reg_buff[14] = PHYTMAC_READ(pdata, PHYTMAC_PCSCTRL); + reg_buff[15] = PHYTMAC_READ(pdata, PHYTMAC_PCSSTATUS); + } +} + +static int phytmac_init_hw(struct phytmac *pdata) +{ + u32 config = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG); + u32 dmaconfig; + u32 nctrlconfig; + u32 ptrconfig = 0; + + nctrlconfig = PHYTMAC_READ(pdata, PHYTMAC_NCTRL); + nctrlconfig |= PHYTMAC_BIT(MPE); + PHYTMAC_WRITE(pdata, PHYTMAC_NCTRL, nctrlconfig); + + phytmac_set_mac_addr(pdata, pdata->ndev->dev_addr); + + PHYTMAC_WRITE(pdata, PHYTMAC_AXICTRL, 0x1010); + + /* jumbo */ + if (pdata->capacities & PHYTMAC_CAPS_JUMBO) + config |= PHYTMAC_BIT(JUMBO_EN); + else + config |= PHYTMAC_BIT(RCV_BIG); + /* promisc */ + if (pdata->ndev->flags & IFF_PROMISC) + config |= PHYTMAC_BIT(PROMISC); + if (pdata->ndev->features & NETIF_F_RXCSUM) + config |= PHYTMAC_BIT(RCO_EN); + if (pdata->ndev->flags & IFF_BROADCAST) + config &= ~PHYTMAC_BIT(NO_BCAST); + else + config |= PHYTMAC_BIT(NO_BCAST); + + /* pause enable */ + config |= PHYTMAC_BIT(PAUSE_EN); + /* Rx Fcs remove */ + config |= PHYTMAC_BIT(FCS_REMOVE); + if (pdata->dma_data_width == PHYTMAC_DBW_64) + config |= PHYTMAC_BIT(DBW64); + if (pdata->dma_data_width == PHYTMAC_DBW_128) + config |= PHYTMAC_BIT(DBW128); + /* mdc div */ + config = PHYTMAC_SET_BITS(config, MCD, 6); + netdev_dbg(pdata->ndev, "phytmac configure NetConfig with 0x%08x\n", + config); + PHYTMAC_WRITE(pdata, PHYTMAC_NCONFIG, config); + + /* init dma */ + dmaconfig = PHYTMAC_READ(pdata, PHYTMAC_DCONFIG); + if (pdata->dma_burst_length) + dmaconfig = PHYTMAC_SET_BITS(dmaconfig, BURST, pdata->dma_burst_length); + /* default in small endian */ + dmaconfig &= ~(PHYTMAC_BIT(ENDIA_PKT) | PHYTMAC_BIT(ENDIA_DESC)); + + if (pdata->ndev->features & NETIF_F_HW_CSUM) + dmaconfig |= PHYTMAC_BIT(TCO_EN); + else + dmaconfig &= ~PHYTMAC_BIT(TCO_EN); + + if (pdata->dma_addr_width) + dmaconfig |= PHYTMAC_BIT(ABW); + + /* fdir ethtype -- ipv4 */ + PHYTMAC_WRITE(pdata, PHYTMAC_ETHT(0), (uint16_t)ETH_P_IP); + + if (IS_REACHABLE(CONFIG_PHYTMAC_ENABLE_PTP)) + dmaconfig |= PHYTMAC_BIT(RX_EXBD_EN) | PHYTMAC_BIT(TX_EXBD_EN); + + PHYTMAC_WRITE(pdata, PHYTMAC_DCONFIG, dmaconfig); + + if (pdata->capacities & PHYTMAC_CAPS_TAILPTR) + ptrconfig |= PHYTMAC_BIT(TXPTR_EN); + if (pdata->capacities & PHYTMAC_CAPS_RXPTR) + ptrconfig |= PHYTMAC_BIT(RXPTR_EN); + + PHYTMAC_WRITE(pdata, PHYTMAC_TAIL_ENABLE, ptrconfig); + + if (pdata->capacities & PHYTMAC_CAPS_START) + PHYTMAC_WRITE(pdata, PHYTMAC_TXSTARTSEL, PHYTMAC_BIT(TSTARTSEL_ENABLE)); + + if (phy_interface_mode_is_8023z(pdata->phy_interface)) + phytmac_pcs_software_reset(pdata, 1); + + return 0; +} + +static int phytmac_powerup_hw(struct phytmac *pdata, int on) +{ + u32 status, data0, data1, rdata1; + int ret; + acpi_handle handle; + union acpi_object args[3]; + struct acpi_object_list arg_list = { + .pointer = args, + .count = ARRAY_SIZE(args), + }; + acpi_status acpi_sts; + unsigned long long rv; + + if (!(pdata->capacities & PHYTMAC_CAPS_PWCTRL)) { + pdata->power_state = on; + return 0; + } + + if (has_acpi_companion(pdata->dev)) { + handle = ACPI_HANDLE(pdata->dev); + + netdev_info(pdata->ndev, "set gmac power %s\n", + on == PHYTMAC_POWERON ? "on" : "off"); + args[0].type = ACPI_TYPE_INTEGER; + args[0].integer.value = PHYTMAC_PWCTL_GMAC_ID; + args[1].type = ACPI_TYPE_INTEGER; + args[1].integer.value = PHYTMAC_PWCTL_DEFAULT_VAL; + args[2].type = ACPI_TYPE_INTEGER; + args[2].integer.value = PHYTMAC_PWCTL_DEFAULT_VAL; + + if (on == PHYTMAC_POWERON) { + acpi_sts = acpi_evaluate_integer(handle, "PPWO", &arg_list, &rv); + if (ACPI_FAILURE(acpi_sts)) + netdev_err(pdata->ndev, "NO PPWO Method\n"); + if (rv) + netdev_err(pdata->ndev, "Failed to power on\n"); + } else { + acpi_sts = acpi_evaluate_integer(handle, "PPWD", &arg_list, &rv); + if (ACPI_FAILURE(acpi_sts)) + netdev_err(pdata->ndev, "NO PPWD Method\n"); + if (rv) + netdev_err(pdata->ndev, "Failed to power off\n"); + } + } else { + ret = readx_poll_timeout(PHYTMAC_READ_STAT, pdata, status, !status, + 1, PHYTMAC_TIMEOUT); + if (ret) + netdev_err(pdata->ndev, "mnh status is busy, status=%x\n", status); + + ret = readx_poll_timeout(PHYTMAC_READ_DATA0, pdata, data0, + data0 & PHYTMAC_BIT(DATA0_FREE), + 1, PHYTMAC_TIMEOUT); + if (ret) + netdev_err(pdata->ndev, "mnh data0 is busy, data0=%x\n", data0); + + data0 = 0; + data0 = PHYTMAC_SET_BITS(data0, DATA0_MSG, PHYTMAC_MSG_PM); + data0 = PHYTMAC_SET_BITS(data0, DATA0_PRO, PHYTMAC_PRO_ID); + PHYTMAC_MHU_WRITE(pdata, PHYTMAC_MHU_CPP_DATA0, data0); + data1 = 0; + + if (on == PHYTMAC_POWERON) { + data1 = PHYTMAC_SET_BITS(data1, DATA1_STAT, PHYTMAC_STATON); + data1 = PHYTMAC_SET_BITS(data1, DATA1_STATTYPE, PHYTMAC_STATTYPE); + PHYTMAC_MHU_WRITE(pdata, PHYTMAC_MHU_CPP_DATA1, data1); + } else { + data1 = PHYTMAC_SET_BITS(data1, DATA1_STAT, PHYTMAC_STATOFF); + data1 = PHYTMAC_SET_BITS(data1, DATA1_STATTYPE, PHYTMAC_STATTYPE); + PHYTMAC_MHU_WRITE(pdata, PHYTMAC_MHU_CPP_DATA1, data1); + } + + PHYTMAC_MHU_WRITE(pdata, PHYTMAC_MHU_AP_CPP_SET, 1); + ret = readx_poll_timeout(PHYTMAC_READ_DATA0, pdata, data0, + data0 & PHYTMAC_BIT(DATA0_FREE), + 1, PHYTMAC_TIMEOUT); + if (ret) + netdev_err(pdata->ndev, "mnh data0 is busy\n"); + + rdata1 = PHYTMAC_MHU_READ(pdata, PHYTMAC_MHU_CPP_DATA1); + if (rdata1 == data1) + netdev_err(pdata->ndev, "gmac power %s success, data1 = %x, rdata1=%x\n", + on == PHYTMAC_POWERON ? "up" : "down", data1, rdata1); + else + netdev_err(pdata->ndev, "gmac power %s failed, data1 = %x, rdata1=%x\n", + on == PHYTMAC_POWERON ? "up" : "down", data1, rdata1); + } + + pdata->power_state = on; + + return 0; +} + +static int phytmac_set_wake(struct phytmac *pdata, int wake) +{ + u32 value = 0; + + if (wake & PHYTMAC_WAKE_MAGIC) + value |= PHYTMAC_BIT(MAGIC); + if (wake & PHYTMAC_WAKE_ARP) + value |= PHYTMAC_BIT(ARP); + if (wake & PHYTMAC_WAKE_UCAST) + value |= PHYTMAC_BIT(UCAST); + if (wake & PHYTMAC_WAKE_MCAST) + value |= PHYTMAC_BIT(MCAST); + + PHYTMAC_WRITE(pdata, PHYTMAC_WOL, value); + if (wake) { + PHYTMAC_WRITE(pdata, PHYTMAC_IE, PHYTMAC_BIT(WOL_RECEIVE_ENABLE)); + value = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG) | PHYTMAC_BIT(IGNORE_RX_FCS); + PHYTMAC_WRITE(pdata, PHYTMAC_NCONFIG, value); + } else { + PHYTMAC_WRITE(pdata, PHYTMAC_ID, PHYTMAC_BIT(WOL_RECEIVE_DISABLE)); + value = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG) & ~PHYTMAC_BIT(IGNORE_RX_FCS); + PHYTMAC_WRITE(pdata, PHYTMAC_NCONFIG, value); + } + + return 0; +} + +static int phytmac_mdio_idle(struct phytmac *pdata) +{ + u32 val; + int ret; + + /* wait for end of transfer */ + ret = readx_poll_timeout(PHYTMAC_READ_NSTATUS, pdata, val, val & PHYTMAC_BIT(MDIO_IDLE), + 1, PHYTMAC_MDIO_TIMEOUT); + if (ret) + netdev_err(pdata->ndev, "mdio wait for idle time out!"); + + return ret; +} + +static int phytmac_mdio_data_read_c22(struct phytmac *pdata, int mii_id, int regnum) +{ + u16 data; + + PHYTMAC_WRITE(pdata, PHYTMAC_MDATA, (PHYTMAC_BITS(CLAUSE_SEL, PHYTMAC_CLAUSE_C22) + | PHYTMAC_BITS(OPS, PHYTMAC_OPS_C22_READ) + | PHYTMAC_BITS(PHY_ADDR, mii_id) + | PHYTMAC_BITS(REG_ADDR, regnum) + | PHYTMAC_BITS(MUST, 2))); + + phytmac_mdio_idle(pdata); + data = PHYTMAC_READ(pdata, PHYTMAC_MDATA) & 0xffff; + phytmac_mdio_idle(pdata); + + return data; +} + +static int phytmac_mdio_data_write_c22(struct phytmac *pdata, int mii_id, + int regnum, u16 data) +{ + PHYTMAC_WRITE(pdata, PHYTMAC_MDATA, (PHYTMAC_BITS(CLAUSE_SEL, PHYTMAC_CLAUSE_C22) + | PHYTMAC_BITS(OPS, PHYTMAC_OPS_C22_WRITE) + | PHYTMAC_BITS(PHY_ADDR, mii_id) + | PHYTMAC_BITS(REG_ADDR, regnum) + | PHYTMAC_BITS(DATA, data) + | PHYTMAC_BITS(MUST, 2))); + phytmac_mdio_idle(pdata); + + return 0; +} + +static int phytmac_mdio_data_read_c45(struct phytmac *pdata, int mii_id, int devad, int regnum) +{ + u16 data; + + PHYTMAC_WRITE(pdata, PHYTMAC_MDATA, (PHYTMAC_BITS(CLAUSE_SEL, PHYTMAC_CLAUSE_C45) + | PHYTMAC_BITS(OPS, PHYTMAC_OPS_C45_ADDR) + | PHYTMAC_BITS(PHY_ADDR, mii_id) + | PHYTMAC_BITS(REG_ADDR, devad & 0x1F) + | PHYTMAC_BITS(DATA, regnum & 0xFFFF) + | PHYTMAC_BITS(MUST, 2))); + phytmac_mdio_idle(pdata); + PHYTMAC_WRITE(pdata, PHYTMAC_MDATA, (PHYTMAC_BITS(CLAUSE_SEL, PHYTMAC_CLAUSE_C45) + | PHYTMAC_BITS(OPS, PHYTMAC_OPS_C45_READ) + | PHYTMAC_BITS(PHY_ADDR, mii_id) + | PHYTMAC_BITS(REG_ADDR, devad & 0x1F) + | PHYTMAC_BITS(DATA, regnum & 0xFFFF) + | PHYTMAC_BITS(MUST, 2))); + + phytmac_mdio_idle(pdata); + data = PHYTMAC_READ(pdata, PHYTMAC_MDATA) & 0xffff; + phytmac_mdio_idle(pdata); + + return data; +} + +static int phytmac_mdio_data_write_c45(struct phytmac *pdata, int mii_id, int devad, + int regnum, u16 data) +{ + PHYTMAC_WRITE(pdata, PHYTMAC_MDATA, (PHYTMAC_BITS(CLAUSE_SEL, PHYTMAC_CLAUSE_C45) + | PHYTMAC_BITS(OPS, PHYTMAC_OPS_C45_ADDR) + | PHYTMAC_BITS(PHY_ADDR, mii_id) + | PHYTMAC_BITS(REG_ADDR, devad & 0x1F) + | PHYTMAC_BITS(DATA, regnum & 0xFFFF) + | PHYTMAC_BITS(MUST, 2))); + phytmac_mdio_idle(pdata); + PHYTMAC_WRITE(pdata, PHYTMAC_MDATA, (PHYTMAC_BITS(CLAUSE_SEL, PHYTMAC_CLAUSE_C45) + | PHYTMAC_BITS(OPS, PHYTMAC_OPS_C45_WRITE) + | PHYTMAC_BITS(PHY_ADDR, mii_id) + | PHYTMAC_BITS(REG_ADDR, devad & 0x1F) + | PHYTMAC_BITS(DATA, data) + | PHYTMAC_BITS(MUST, 2))); + phytmac_mdio_idle(pdata); + + return 0; +} + +static int phytmac_get_feature_all(struct phytmac *pdata) +{ + unsigned int queue_mask; + unsigned int num_queues; + int val; + + /* get max queues */ + queue_mask = 0x1; + queue_mask |= PHYTMAC_READ(pdata, PHYTMAC_DEFAULT2) & 0xff; + num_queues = hweight32(queue_mask); + pdata->queues_max_num = num_queues; + + /* get dma desc prefetch number */ + val = PHYTMAC_READ_BITS(pdata, PHYTMAC_DEFAULT4, TXDESCRD); + if (val) + pdata->tx_bd_prefetch = (2 << (val - 1)) * + sizeof(struct phytmac_dma_desc); + + val = PHYTMAC_READ_BITS(pdata, PHYTMAC_DEFAULT4, RXDESCRD); + if (val) + pdata->rx_bd_prefetch = (2 << (val - 1)) * + sizeof(struct phytmac_dma_desc); + + /* dma bus width */ + pdata->dma_data_width = PHYTMAC_READ_BITS(pdata, PHYTMAC_DEFAULT1, DBW); + + /* dma addr width */ + if (PHYTMAC_READ_BITS(pdata, PHYTMAC_DEFAULT2, DAW64)) + pdata->dma_addr_width = 64; + else + pdata->dma_addr_width = 32; + + /* max rx fs */ + pdata->max_rx_fs = PHYTMAC_READ_BITS(pdata, PHYTMAC_DEFAULT3, SCR2CMP); + + if (pdata->version == VERSION_V3) + pdata->capacities |= PHYTMAC_CAPS_RXPTR; + + if (netif_msg_hw(pdata)) + netdev_info(pdata->ndev, "get feature queue_num=%d, daw=%d, dbw=%d, rx_fs=%d, rx_bd=%d, tx_bd=%d\n", + pdata->queues_num, pdata->dma_addr_width, pdata->dma_data_width, + pdata->max_rx_fs, pdata->rx_bd_prefetch, pdata->tx_bd_prefetch); + return 0; +} + +static int phytmac_add_fdir_entry(struct phytmac *pdata, struct ethtool_rx_flow_spec *rx_flow) +{ + struct ethtool_tcpip4_spec *tp4sp_v, *tp4sp_m; + u16 index = rx_flow->location; + u32 tmp, fdir_ctrl; + bool ipsrc = false; + bool ipdst = false; + bool port = false; + + tp4sp_v = &rx_flow->h_u.tcp_ip4_spec; + tp4sp_m = &rx_flow->m_u.tcp_ip4_spec; + + if (tp4sp_m->ip4src == 0xFFFFFFFF) { + tmp = 0; + tmp = PHYTMAC_SET_BITS(tmp, OFFSET, ETHTYPE_SIP_OFFSET); + tmp = PHYTMAC_SET_BITS(tmp, OFFSET_TYPE, PHYTMAC_OFFSET_AFTER_L2HEAD); + tmp = PHYTMAC_SET_BITS(tmp, DIS_MASK, 1); + PHYTMAC_WRITE(pdata, PHYTMAC_COMP1(3 * index), tp4sp_v->ip4src); + PHYTMAC_WRITE(pdata, PHYTMAC_COMP2(3 * index), tmp); + ipsrc = true; + } + + if (tp4sp_m->ip4dst == 0xFFFFFFFF) { + /* 2nd compare reg - IP destination address */ + tmp = 0; + tmp = PHYTMAC_SET_BITS(tmp, OFFSET, ETHTYPE_DIP_OFFSET); + tmp = PHYTMAC_SET_BITS(tmp, OFFSET_TYPE, PHYTMAC_OFFSET_AFTER_L2HEAD); + tmp = PHYTMAC_SET_BITS(tmp, DIS_MASK, 1); + PHYTMAC_WRITE(pdata, PHYTMAC_COMP1(3 * index + 1), tp4sp_v->ip4dst); + PHYTMAC_WRITE(pdata, PHYTMAC_COMP2(3 * index + 1), tmp); + ipdst = true; + } + + if (tp4sp_m->psrc == 0xFFFF || tp4sp_m->pdst == 0xFFFF) { + tmp = 0; + tmp = PHYTMAC_SET_BITS(tmp, OFFSET_TYPE, PHYTMAC_OFFSET_AFTER_L3HEAD); + if (tp4sp_m->psrc == tp4sp_m->pdst) { + tmp = PHYTMAC_SET_BITS(tmp, DIS_MASK, 1); + tmp = PHYTMAC_SET_BITS(tmp, OFFSET, IPHEAD_SPORT_OFFSET); + PHYTMAC_WRITE(pdata, PHYTMAC_COMP1(3 * index + 2), + tp4sp_v->psrc | (u32)(tp4sp_v->pdst << 16)); + } else { + tmp = PHYTMAC_SET_BITS(tmp, DIS_MASK, 0); + if (tp4sp_m->psrc == 0xFFFF) { /* src port */ + tmp = PHYTMAC_SET_BITS(tmp, OFFSET, IPHEAD_SPORT_OFFSET); + PHYTMAC_WRITE(pdata, PHYTMAC_COMP1(3 * index + 2), + tp4sp_m->psrc | (u32)(tp4sp_v->psrc << 16)); + } else { /* dst port */ + tmp = PHYTMAC_SET_BITS(tmp, OFFSET, IPHEAD_DPORT_OFFSET); + PHYTMAC_WRITE(pdata, PHYTMAC_COMP1(3 * index + 2), + tp4sp_m->pdst | (u32)(tp4sp_v->pdst << 16)); + } + } + PHYTMAC_WRITE(pdata, PHYTMAC_COMP2(3 * index + 2), tmp); + port = true; + } + + fdir_ctrl = PHYTMAC_READ(pdata, PHYTMAC_FDIR(rx_flow->location)); + + if (ipsrc) { + fdir_ctrl = PHYTMAC_SET_BITS(fdir_ctrl, CA, 3 * index); + fdir_ctrl = PHYTMAC_SET_BITS(fdir_ctrl, CA_EN, 1); + } + + if (ipdst) { + fdir_ctrl = PHYTMAC_SET_BITS(fdir_ctrl, CB, 3 * index + 1); + fdir_ctrl = PHYTMAC_SET_BITS(fdir_ctrl, CB_EN, 1); + } + + if (port) { + fdir_ctrl = PHYTMAC_SET_BITS(fdir_ctrl, CC, 3 * index + 2); + fdir_ctrl = PHYTMAC_SET_BITS(fdir_ctrl, CC_EN, 1); + } + + fdir_ctrl = PHYTMAC_SET_BITS(fdir_ctrl, QUEUE_NUM, (rx_flow->ring_cookie) & 0xFF); + fdir_ctrl = PHYTMAC_SET_BITS(fdir_ctrl, ETH_TYPE, 0); + fdir_ctrl = PHYTMAC_SET_BITS(fdir_ctrl, ETH_EN, 1); + + PHYTMAC_WRITE(pdata, PHYTMAC_FDIR(rx_flow->location), fdir_ctrl); + + return 0; +} + +static int phytmac_del_fdir_entry(struct phytmac *pdata, struct ethtool_rx_flow_spec *rx_flow) +{ + int i; + int index = rx_flow->location; + + PHYTMAC_WRITE(pdata, PHYTMAC_FDIR(index), 0); + for (i = 0; i < 3; i++) { + PHYTMAC_WRITE(pdata, PHYTMAC_COMP1(3 * index + i), 0); + PHYTMAC_WRITE(pdata, PHYTMAC_COMP2(3 * index + i), 0); + } + return 0; +} + +static int phytmac_init_ring_hw(struct phytmac *pdata) +{ + struct phytmac_queue *queue; + unsigned int q = 0; + u32 buffer_size = pdata->rx_buffer_len / 64; + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + if (q == 0) { + PHYTMAC_WRITE(pdata, PHYTMAC_RXPTR_Q0, + lower_32_bits(queue->rx_ring_addr)); + PHYTMAC_WRITE(pdata, PHYTMAC_TXPTR_Q0, + lower_32_bits(queue->tx_ring_addr)); + PHYTMAC_WRITE(pdata, PHYTMAC_DCONFIG, + PHYTMAC_SET_BITS(PHYTMAC_READ(pdata, PHYTMAC_DCONFIG), + RX_BUF_LEN, buffer_size)); + } else { + PHYTMAC_WRITE(pdata, PHYTMAC_RXPTR(q - 1), + lower_32_bits(queue->rx_ring_addr)); + PHYTMAC_WRITE(pdata, PHYTMAC_TXPTR(q - 1), + lower_32_bits(queue->tx_ring_addr)); + PHYTMAC_WRITE(pdata, PHYTMAC_RBQS(q - 1), buffer_size); + } + + PHYTMAC_WRITE(pdata, PHYTMAC_TXPTRH(q), upper_32_bits(queue->tx_ring_addr)); + PHYTMAC_WRITE(pdata, PHYTMAC_RXPTRH(q), upper_32_bits(queue->rx_ring_addr)); + + if (pdata->capacities & PHYTMAC_CAPS_TAILPTR) + PHYTMAC_WRITE(pdata, PHYTMAC_TX_TAILPTR(q), queue->tx_tail); + } + + return 0; +} + +static u32 phytmac_get_irq_mask(u32 mask) +{ + u32 value = 0; + + value |= (mask & PHYTMAC_INT_TX_COMPLETE) ? PHYTMAC_BIT(TXCOMP) : 0; + value |= (mask & PHYTMAC_INT_TX_ERR) ? PHYTMAC_BIT(BUS_ERR) : 0; + value |= (mask & PHYTMAC_INT_RX_COMPLETE) ? PHYTMAC_BIT(RXCOMP) : 0; + value |= (mask & PHYTMAC_INT_RX_OVERRUN) ? PHYTMAC_BIT(RXOVERRUN) : 0; + value |= (mask & PHYTMAC_INT_RX_DESC_FULL) ? PHYTMAC_BIT(RUB) : 0; + + return value; +} + +static u32 phytmac_get_irq_status(u32 value) +{ + u32 status = 0; + + status |= (value & PHYTMAC_BIT(TXCOMP)) ? PHYTMAC_INT_TX_COMPLETE : 0; + status |= (value & PHYTMAC_BIT(BUS_ERR)) ? PHYTMAC_INT_TX_ERR : 0; + status |= (value & PHYTMAC_BIT(RXCOMP)) ? PHYTMAC_INT_RX_COMPLETE : 0; + status |= (value & PHYTMAC_BIT(RXOVERRUN)) ? PHYTMAC_INT_RX_OVERRUN : 0; + status |= (value & PHYTMAC_BIT(RUB)) ? PHYTMAC_INT_RX_DESC_FULL : 0; + + return status; +} + +static void phytmac_enable_irq(struct phytmac *pdata, + int queue_index, u32 mask) +{ + u32 value; + + value = phytmac_get_irq_mask(mask); + + if (queue_index == 0) + PHYTMAC_WRITE(pdata, PHYTMAC_IE, value); + else + PHYTMAC_WRITE(pdata, PHYTMAC_IER(queue_index - 1), value); +} + +static void phytmac_disable_irq(struct phytmac *pdata, + int queue_index, u32 mask) +{ + u32 value; + + value = phytmac_get_irq_mask(mask); + + if (queue_index == 0) + PHYTMAC_WRITE(pdata, PHYTMAC_ID, value); + else + PHYTMAC_WRITE(pdata, PHYTMAC_IDR(queue_index - 1), value); +} + +static void phytmac_clear_irq(struct phytmac *pdata, + int queue_index, u32 mask) +{ + u32 value; + + value = phytmac_get_irq_mask(mask); + + if (queue_index == 0) + PHYTMAC_WRITE(pdata, PHYTMAC_IS, value); + else + PHYTMAC_WRITE(pdata, PHYTMAC_ISR(queue_index - 1), value); +} + +static unsigned int phytmac_get_intx_mask(struct phytmac *pdata) +{ + u32 value; + + value = PHYTMAC_READ(pdata, PHYTMAC_INTX_IRQ_MASK); + PHYTMAC_WRITE(pdata, PHYTMAC_INTX_IRQ_MASK, value); + + return value; +} + +static unsigned int phytmac_get_irq(struct phytmac *pdata, int queue_index) +{ + u32 status, value; + + if (queue_index == 0) + value = PHYTMAC_READ(pdata, PHYTMAC_IS); + else + value = PHYTMAC_READ(pdata, PHYTMAC_ISR(queue_index - 1)); + + status = phytmac_get_irq_status(value); + + return status; +} + +static unsigned int phytmac_tx_map_desc(struct phytmac_queue *queue, + u32 tx_tail, struct packet_info *packet) +{ + unsigned int i, ctrl; + struct phytmac *pdata = queue->pdata; + struct phytmac_dma_desc *desc; + struct phytmac_tx_skb *tx_skb; + unsigned int eof = 1; + + i = tx_tail; + + if (!(pdata->capacities & PHYTMAC_CAPS_TAILPTR)) { + ctrl = PHYTMAC_BIT(TX_USED); + desc = phytmac_get_tx_desc(queue, i); + desc->desc1 = ctrl; + } + + do { + i--; + tx_skb = phytmac_get_tx_skb(queue, i); + desc = phytmac_get_tx_desc(queue, i); + + ctrl = (u32)tx_skb->length; + if (eof) { + ctrl |= PHYTMAC_BIT(TX_LAST); + eof = 0; + } + + if (unlikely(i == (pdata->tx_ring_size - 1))) + ctrl |= PHYTMAC_BIT(TX_WRAP); + + if (i == queue->tx_tail) { + ctrl |= PHYTMAC_BITS(TX_LSO, packet->lso); + ctrl |= PHYTMAC_BITS(TX_TCP_SEQ_SRC, packet->seq); + if (packet->nocrc) + ctrl |= PHYTMAC_BIT(TX_NOCRC); + } else { + ctrl |= PHYTMAC_BITS(MSS_MFS, packet->mss); + } + + desc->desc2 = upper_32_bits(tx_skb->addr); + desc->desc0 = lower_32_bits(tx_skb->addr); + /* Make newly descriptor visible to hardware */ + wmb(); + desc->desc1 = ctrl; + } while (i != queue->tx_tail); + + return 0; +} + +static void phytmac_init_rx_map_desc(struct phytmac_queue *queue, + u32 index) +{ + struct phytmac_dma_desc *desc; + + desc = phytmac_get_rx_desc(queue, index); + + desc->desc1 = 0; + /* Make newly descriptor to hardware */ + dma_wmb(); + desc->desc0 |= PHYTMAC_BIT(RX_USED); +} + +static unsigned int phytmac_rx_map_desc(struct phytmac_queue *queue, + u32 index, dma_addr_t addr) +{ + struct phytmac *pdata = queue->pdata; + struct phytmac_dma_desc *desc; + + desc = phytmac_get_rx_desc(queue, index); + + if (addr) { + if (unlikely(index == (pdata->rx_ring_size - 1))) + addr |= PHYTMAC_BIT(RX_WRAP); + desc->desc1 = 0; + desc->desc2 = upper_32_bits(addr); + /* Make newly descriptor to hardware */ + if (!(pdata->capacities & PHYTMAC_CAPS_RXPTR)) + dma_wmb(); + desc->desc0 = lower_32_bits(addr); + } else { + desc->desc1 = 0; + /* Make newly descriptor to hardware */ + dma_wmb(); + desc->desc0 &= ~PHYTMAC_BIT(RX_USED); + } + + return 0; +} + +static unsigned int phytmac_zero_rx_desc_addr(struct phytmac_dma_desc *desc) +{ + desc->desc2 = 0; + desc->desc0 = (desc->desc0 & PHYTMAC_BIT(RX_TS_VALID)) | PHYTMAC_BIT(RX_USED); + + return 0; +} + +static unsigned int phytmac_zero_tx_desc(struct phytmac_dma_desc *desc) +{ + desc->desc2 = 0; + desc->desc0 = 0; + desc->desc1 &= ~PHYTMAC_BIT(TX_USED); + + return 0; +} + +static void phytmac_tx_start(struct phytmac_queue *queue) +{ + struct phytmac *pdata = queue->pdata; + + if (pdata->capacities & PHYTMAC_CAPS_TAILPTR) + PHYTMAC_WRITE(pdata, PHYTMAC_TX_TAILPTR(queue->index), + PHYTMAC_BIT(TXTAIL_UPDATE) | queue->tx_tail); + + if (likely(pdata->capacities & PHYTMAC_CAPS_START)) + PHYTMAC_WRITE(pdata, PHYTMAC_TXSTART, PHYTMAC_BIT(TSTART)); +} + +static void phytmac_update_rx_tail(struct phytmac_queue *queue) +{ + struct phytmac *pdata = queue->pdata; + + if (pdata->capacities & PHYTMAC_CAPS_RXPTR) + PHYTMAC_WRITE(pdata, PHYTMAC_RX_TAILPTR(queue->index), queue->rx_head); +} + +static int phytmac_tx_complete(const struct phytmac_dma_desc *desc) +{ + return PHYTMAC_GET_BITS(desc->desc1, TX_USED); +} + +static bool phytmac_rx_complete(const struct phytmac_dma_desc *desc) +{ + dma_addr_t addr; + bool used; + + used = desc->desc0 & PHYTMAC_BIT(RX_USED); + addr = ((u64)(desc->desc2) << 32); + addr |= desc->desc0 & 0xfffffff8; + + if (used != 0 && addr != 0) + return true; + else + return false; +} + +static int phytmac_rx_pkt_len(struct phytmac *pdata, const struct phytmac_dma_desc *desc) +{ + if (pdata->capacities & PHYTMAC_CAPS_JUMBO) + return desc->desc1 & PHYTMAC_JUMBO_FRAME_MASK; + else + return desc->desc1 & PHYTMAC_FRAME_MASK; +} + +static bool phytmac_rx_checksum(const struct phytmac_dma_desc *desc) +{ + u32 value = desc->desc1; + u32 check = value >> PHYTMAC_RX_CSUM_INDEX & 0x3; + + return (check == PHYTMAC_RX_CSUM_IP_TCP || check == PHYTMAC_RX_CSUM_IP_UDP); +} + +static bool phytmac_rx_single_buffer(const struct phytmac_dma_desc *desc) +{ + u32 value = desc->desc1; + + return ((value & PHYTMAC_BIT(RX_SOF)) && (value & PHYTMAC_BIT(RX_EOF))); +} + +static bool phytmac_rx_sof(const struct phytmac_dma_desc *desc) +{ + u32 value = desc->desc1; + + return (value & PHYTMAC_BIT(RX_SOF)); +} + +static bool phytmac_rx_eof(const struct phytmac_dma_desc *desc) +{ + u32 value = desc->desc1; + + return (value & PHYTMAC_BIT(RX_EOF)); +} + +static void phytmac_clear_rx_desc(struct phytmac_queue *queue, int begin, int end) +{ + unsigned int frag; + unsigned int tmp = end; + struct phytmac_dma_desc *desc; + + if (begin > end) + tmp = end + queue->pdata->rx_ring_size; + + for (frag = begin; frag != tmp; frag++) { + desc = phytmac_get_rx_desc(queue, frag); + desc->desc0 &= ~PHYTMAC_BIT(RX_USED); + } +} + +static void phytmac_mac_interface_config(struct phytmac *pdata, unsigned int mode, + const struct phylink_link_state *state) +{ + u32 old_ctrl, old_config; + u32 ctrl, config, usxctl; + + old_ctrl = PHYTMAC_READ(pdata, PHYTMAC_NCTRL); + old_config = PHYTMAC_READ(pdata, PHYTMAC_NCONFIG); + ctrl = old_ctrl; + config = old_config; + + if (state->speed == SPEED_10000) + PHYTMAC_WRITE(pdata, PHYTMAC_HCONFIG, PHYTMAC_SPEED_10000M); + else if (state->speed == SPEED_5000) + PHYTMAC_WRITE(pdata, PHYTMAC_HCONFIG, PHYTMAC_SPEED_5000M); + else if (state->speed == SPEED_2500) + PHYTMAC_WRITE(pdata, PHYTMAC_HCONFIG, PHYTMAC_SPEED_2500M); + else if (state->speed == SPEED_1000) + PHYTMAC_WRITE(pdata, PHYTMAC_HCONFIG, PHYTMAC_SPEED_1000M); + else if (state->speed == SPEED_100 || state->speed == SPEED_10) + PHYTMAC_WRITE(pdata, PHYTMAC_HCONFIG, PHYTMAC_SPEED_100M); + + config &= ~(PHYTMAC_BIT(SGMII_EN) | PHYTMAC_BIT(PCS_EN) + | PHYTMAC_BIT(SPEED) | PHYTMAC_BIT(FD) | PHYTMAC_BIT(GM_EN)); + ctrl &= ~(PHYTMAC_BIT(HIGHSPEED) | PHYTMAC_BIT(2PT5G)); + + if (state->interface == PHY_INTERFACE_MODE_SGMII) { + config |= PHYTMAC_BIT(SGMII_EN) | PHYTMAC_BIT(PCS_EN); + if (state->speed == SPEED_1000) + config |= PHYTMAC_BIT(GM_EN); + else if (state->speed == SPEED_2500) { + ctrl |= PHYTMAC_BIT(2PT5G); + config |= PHYTMAC_BIT(GM_EN); + } + } else if (state->interface == PHY_INTERFACE_MODE_1000BASEX) { + config |= PHYTMAC_BIT(PCS_EN) | PHYTMAC_BIT(GM_EN); + } else if (state->interface == PHY_INTERFACE_MODE_2500BASEX) { + ctrl |= PHYTMAC_BIT(2PT5G); + config |= PHYTMAC_BIT(PCS_EN) | PHYTMAC_BIT(GM_EN); + } else if (state->interface == PHY_INTERFACE_MODE_10GBASER || + state->interface == PHY_INTERFACE_MODE_USXGMII || + state->interface == PHY_INTERFACE_MODE_5GBASER) { + usxctl = PHYTMAC_READ(pdata, PHYTMAC_USXCTRL); + if (state->speed == SPEED_10000) { + usxctl = PHYTMAC_SET_BITS(usxctl, SERDES_RATE, PHYTMAC_SERDES_RATE_10G); + usxctl = PHYTMAC_SET_BITS(usxctl, USX_SPEED, PHYTMAC_SPEED_10000M); + } else if (state->speed == SPEED_5000) { + usxctl = PHYTMAC_SET_BITS(usxctl, SERDES_RATE, PHYTMAC_SERDES_RATE_5G); + usxctl = PHYTMAC_SET_BITS(usxctl, USX_SPEED, PHYTMAC_SPEED_5000M); + } + usxctl |= PHYTMAC_BIT(RX_EN) | PHYTMAC_BIT(TX_EN); + PHYTMAC_WRITE(pdata, PHYTMAC_USXCTRL, usxctl); + + config |= PHYTMAC_BIT(PCS_EN); + ctrl |= PHYTMAC_BIT(HIGHSPEED); + } + + if (state->duplex) + config |= PHYTMAC_BIT(FD); + + if (old_ctrl ^ ctrl) + PHYTMAC_WRITE(pdata, PHYTMAC_NCTRL, ctrl); + + if (old_config ^ config) + PHYTMAC_WRITE(pdata, PHYTMAC_NCONFIG, config); + + /* Disable AN for SGMII fixed link or speed equal to 2.5G, enable otherwise.*/ + if (state->interface == PHY_INTERFACE_MODE_SGMII) { + if (mode == MLO_AN_FIXED) + phytmac_enable_autoneg(pdata, 0); + else + phytmac_enable_autoneg(pdata, 1); + } + if (state->interface == PHY_INTERFACE_MODE_1000BASEX) + phytmac_enable_autoneg(pdata, 1); + if (state->interface == PHY_INTERFACE_MODE_2500BASEX) + phytmac_enable_autoneg(pdata, 0); +} + +static unsigned int phytmac_pcs_get_link(struct phytmac *pdata, + phy_interface_t interface) +{ + if (interface == PHY_INTERFACE_MODE_SGMII || + interface == PHY_INTERFACE_MODE_1000BASEX || + interface == PHY_INTERFACE_MODE_2500BASEX) + return PHYTMAC_READ_BITS(pdata, PHYTMAC_NSTATUS, PCS_LINK); + else if (interface == PHY_INTERFACE_MODE_USXGMII || + interface == PHY_INTERFACE_MODE_10GBASER) + return PHYTMAC_READ_BITS(pdata, PHYTMAC_USXSTATUS, USX_PCS_LINK); + + return 0; +} + +static void phytmac_clear_tx_desc(struct phytmac_queue *queue) +{ + struct phytmac *pdata = queue->pdata; + struct phytmac_dma_desc *desc = NULL; + int i; + + for (i = 0; i < queue->pdata->tx_ring_size; i++) { + desc = phytmac_get_tx_desc(queue, i); + desc->desc2 = 0; + desc->desc0 = 0; + /* make newly desc1 to hardware */ + wmb(); + desc->desc1 = PHYTMAC_BIT(TX_USED); + } + desc->desc1 |= PHYTMAC_BIT(TX_WRAP); + + if (pdata->capacities & PHYTMAC_CAPS_TAILPTR) + PHYTMAC_WRITE(pdata, PHYTMAC_TX_TAILPTR(queue->index), queue->tx_tail); +} + +static void phytmac_get_hw_stats(struct phytmac *pdata) +{ + u32 stats[PHYTMAC_STATIS_REG_NUM]; + int i, j; + u64 val; + u64 *p = &pdata->stats.tx_octets; + + for (i = 0 ; i < PHYTMAC_STATIS_REG_NUM; i++) + stats[i] = PHYTMAC_READ(pdata, PHYTMAC_OCTTX + i * 4); + + for (i = 0, j = 0; i < PHYTMAC_STATIS_REG_NUM; i++) { + if (i == 0 || i == 20) { + val = (u64)stats[i + 1] << 32 | stats[i]; + *p += val; + pdata->ethtool_stats[j] = *p; + ++j; + ++p; + } else { + if (i != 1 && i != 21) { + val = stats[i]; + *p += val; + pdata->ethtool_stats[j] = *p; + ++j; + ++p; + } + } + } +} + +static void phytmac_get_time(struct phytmac *pdata, struct timespec64 *ts) +{ + u32 ns, secl, sech; + + ns = PHYTMAC_READ(pdata, PHYTMAC_TN); + secl = PHYTMAC_READ(pdata, PHYTMAC_TSL); + sech = PHYTMAC_READ(pdata, PHYTMAC_TSH); + + ts->tv_nsec = ns; + ts->tv_sec = (((u64)sech << 32) | secl) & SEC_MAX_VAL; +} + +static void phytmac_set_time(struct phytmac *pdata, time64_t sec, long nsec) +{ + u32 secl, sech; + + secl = (u32)sec; + sech = (sec >> 32) & (0xffff); + + PHYTMAC_WRITE(pdata, PHYTMAC_TN, 0); + PHYTMAC_WRITE(pdata, PHYTMAC_TSH, sech); + PHYTMAC_WRITE(pdata, PHYTMAC_TSL, secl); + PHYTMAC_WRITE(pdata, PHYTMAC_TN, nsec); +} + +static void phytmac_clear_time(struct phytmac *pdata) +{ + u32 value; + + pdata->ts_incr.sub_ns = 0; + pdata->ts_incr.ns = 0; + + value = PHYTMAC_READ(pdata, PHYTMAC_TISN); + value = PHYTMAC_SET_BITS(value, SUB_NSECL, 0); + value = PHYTMAC_SET_BITS(value, SUB_NSECH, 0); + PHYTMAC_WRITE(pdata, PHYTMAC_TISN, value); + + value = PHYTMAC_READ(pdata, PHYTMAC_TI); + value = PHYTMAC_SET_BITS(value, INCR_NS, 0); + PHYTMAC_WRITE(pdata, PHYTMAC_TI, value); + + PHYTMAC_WRITE(pdata, PHYTMAC_TA, 0); +} + +static int phytmac_set_tsmode(struct phytmac *pdata, struct ts_ctrl *ctrl) +{ + if (ctrl->rx_control == TS_ALL_PTP_FRAMES) + PHYTMAC_WRITE(pdata, PHYTMAC_NCTRL, + PHYTMAC_READ(pdata, PHYTMAC_NCTRL) | PHYTMAC_BIT(STORE_RX_TS)); + + PHYTMAC_WRITE(pdata, PHYTMAC_TXBDCTRL, PHYTMAC_BITS(TX_TSMODE, ctrl->tx_control)); + PHYTMAC_WRITE(pdata, PHYTMAC_RXBDCTRL, PHYTMAC_BITS(RX_TSMODE, ctrl->rx_control)); + + return 0; +} + +static int phytmac_set_tsincr(struct phytmac *pdata, struct ts_incr *incr) +{ + u32 value; + + value = PHYTMAC_BITS(SUB_NSECL, incr->sub_ns) | + PHYTMAC_BITS(SUB_NSECH, incr->sub_ns >> 8); + PHYTMAC_WRITE(pdata, PHYTMAC_TISN, value); + PHYTMAC_WRITE(pdata, PHYTMAC_TI, incr->ns); + + return 0; +} + +static void phytmac_ptp_init_hw(struct phytmac *pdata) +{ + struct timespec64 ts; + + ts = ns_to_timespec64(ktime_to_ns(ktime_get_real())); + phytmac_set_time(pdata, ts.tv_sec, ts.tv_nsec); + + phytmac_set_tsincr(pdata, &pdata->ts_incr); +} + +static int phytmac_adjust_fine(struct phytmac *pdata, long ppm, bool negative) +{ + struct ts_incr ts_incr; + u32 tmp; + u64 adj; + + ts_incr.ns = pdata->ts_incr.ns; + ts_incr.sub_ns = pdata->ts_incr.sub_ns; + + tmp = ((u64)ts_incr.ns << PHYTMAC_SUB_NSECL_INDEX) + ts_incr.sub_ns; + adj = ((u64)ppm * tmp + (USEC_PER_SEC >> 1)) >> PHYTMAC_SUB_NSECL_INDEX; + + adj = div_u64(adj, USEC_PER_SEC); + adj = negative ? (tmp - adj) : (tmp + adj); + + ts_incr.ns = (adj >> PHYTMAC_SUB_NSEC_WIDTH) + & ((1 << PHYTMAC_SUB_NSECL_WIDTH) - 1); + ts_incr.sub_ns = adj & ((1 << PHYTMAC_SUB_NSEC_WIDTH) - 1); + + phytmac_set_tsincr(pdata, &ts_incr); + + return 0; +} + +static int phytmac_adjust_time(struct phytmac *pdata, s64 delta, int neg) +{ + u32 adj; + + if (delta > PHYTMAC_ADJUST_SEC_MAX) { + struct timespec64 now, then; + + if (neg) + then = ns_to_timespec64(-delta); + else + then = ns_to_timespec64(delta); + phytmac_get_time(pdata, &now); + now = timespec64_add(now, then); + phytmac_set_time(pdata, now.tv_sec, now.tv_nsec); + } else { + adj = (neg << PHYTMAC_INCR_ADD_INDEX) | delta; + PHYTMAC_WRITE(pdata, PHYTMAC_TA, adj); + } + + return 0; +} + +static int phytmac_ts_valid(struct phytmac *pdata, struct phytmac_dma_desc *desc, int direction) +{ + int ts_valid = 0; + + if (direction == PHYTMAC_TX) + ts_valid = desc->desc1 & PHYTMAC_BIT(TX_TS_VALID); + else if (direction == PHYTMAC_RX) + ts_valid = desc->desc0 & PHYTMAC_BIT(RX_TS_VALID); + return ts_valid; +} + +static void phytmac_get_dma_ts(struct phytmac *pdata, u32 ts_1, u32 ts_2, struct timespec64 *ts) +{ + struct timespec64 ts2; + + ts->tv_sec = (PHYTMAC_GET_BITS(ts_2, DMA_SECH) << 2) | + PHYTMAC_GET_BITS(ts_1, DMA_SECL); + ts->tv_nsec = PHYTMAC_GET_BITS(ts_1, DMA_NSEC); + + phytmac_get_time(pdata, &ts2); + + if (((ts->tv_sec ^ ts2.tv_sec) & (PHYTMAC_DMA_SEC_TOP >> 1)) != 0) + ts->tv_sec -= PHYTMAC_DMA_SEC_TOP; + + ts->tv_sec += (ts2.tv_sec & (~PHYTMAC_DMA_SEC_MASK)); +} + +static unsigned int phytmac_get_ts_rate(struct phytmac *pdata) +{ + return 300000000; +} + +struct phytmac_hw_if phytmac_1p0_hw = { + .reset_hw = phytmac_reset_hw, + .init_hw = phytmac_init_hw, + .init_ring_hw = phytmac_init_ring_hw, + .get_feature = phytmac_get_feature_all, + .get_regs = phytmac_get_regs, + .get_stats = phytmac_get_hw_stats, + .set_mac_address = phytmac_set_mac_addr, + .get_mac_address = phytmac_get_mac_addr, + .mdio_idle = phytmac_mdio_idle, + .mdio_read = phytmac_mdio_data_read_c22, + .mdio_write = phytmac_mdio_data_write_c22, + .mdio_read_c45 = phytmac_mdio_data_read_c45, + .mdio_write_c45 = phytmac_mdio_data_write_c45, + .poweron = phytmac_powerup_hw, + .set_wol = phytmac_set_wake, + .enable_promise = phytmac_enable_promise, + .enable_multicast = phytmac_enable_multicast, + .set_hash_table = phytmac_set_mc_hash, + .enable_rx_csum = phytmac_enable_rxcsum, + .enable_tx_csum = phytmac_enable_txcsum, + .enable_mdio_control = phytmac_enable_mdio, + .enable_autoneg = phytmac_enable_autoneg, + .enable_pause = phytmac_enable_pause, + .enable_network = phytmac_enable_network, + .add_fdir_entry = phytmac_add_fdir_entry, + .del_fdir_entry = phytmac_del_fdir_entry, + /* mac config */ + .mac_config = phytmac_mac_interface_config, + .mac_linkup = phytmac_mac_linkup, + .mac_linkdown = phytmac_mac_linkdown, + .pcs_linkup = phytmac_pcs_linkup, + .pcs_linkdown = phytmac_pcs_linkdown, + .get_link = phytmac_pcs_get_link, + /* irq */ + .enable_irq = phytmac_enable_irq, + .disable_irq = phytmac_disable_irq, + .clear_irq = phytmac_clear_irq, + .get_irq = phytmac_get_irq, + .get_intx_mask = phytmac_get_intx_mask, + /* tx and rx */ + .tx_map = phytmac_tx_map_desc, + .transmit = phytmac_tx_start, + .update_rx_tail = phytmac_update_rx_tail, + .tx_complete = phytmac_tx_complete, + .rx_complete = phytmac_rx_complete, + .get_rx_pkt_len = phytmac_rx_pkt_len, + .init_rx_map = phytmac_init_rx_map_desc, + .rx_map = phytmac_rx_map_desc, + .rx_checksum = phytmac_rx_checksum, + .rx_single_buffer = phytmac_rx_single_buffer, + .rx_pkt_start = phytmac_rx_sof, + .rx_pkt_end = phytmac_rx_eof, + .clear_rx_desc = phytmac_clear_rx_desc, + .clear_tx_desc = phytmac_clear_tx_desc, + .zero_rx_desc_addr = phytmac_zero_rx_desc_addr, + .zero_tx_desc = phytmac_zero_tx_desc, + /* ptp */ + .init_ts_hw = phytmac_ptp_init_hw, + .set_time = phytmac_set_time, + .clear_time = phytmac_clear_time, + .get_time = phytmac_get_time, + .set_ts_config = phytmac_set_tsmode, + .set_incr = phytmac_set_tsincr, + .adjust_fine = phytmac_adjust_fine, + .adjust_time = phytmac_adjust_time, + .ts_valid = phytmac_ts_valid, + .get_timestamp = phytmac_get_dma_ts, + .get_ts_rate = phytmac_get_ts_rate, +}; +EXPORT_SYMBOL_GPL(phytmac_1p0_hw); diff --git a/drivers/net/ethernet/phytium/phytmac_v1.h b/drivers/net/ethernet/phytium/phytmac_v1.h new file mode 100644 index 00000000000000..e0ca0ab8354749 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_v1.h @@ -0,0 +1,486 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. */ + +#ifndef _PHYTMAC_V1_H +#define _PHYTMAC_V1_H + +extern struct phytmac_hw_if phytmac_1p0_hw; + +#define PHYTMAC_FRAME_MASK 0x1fff +#define PHYTMAC_JUMBO_FRAME_MASK 0x3fff + +#define PHYTMAC_SPEED_100M 0 +#define PHYTMAC_SPEED_1000M 1 +#define PHYTMAC_SPEED_2500M 2 +#define PHYTMAC_SPEED_5000M 3 +#define PHYTMAC_SPEED_10000M 4 +#define PHYTMAC_SERDES_RATE_5G 0 +#define PHYTMAC_SERDES_RATE_10G 1 + +#define PHYTMAC_NCTRL 0x0000 +#define PHYTMAC_NCONFIG 0x0004 +#define PHYTMAC_NSTATUS 0x0008 +#define PHYTMAC_DCONFIG 0x0010 +#define PHYTMAC_RXPTR_Q0 0x0018 +#define PHYTMAC_TXPTR_Q0 0x001C +#define PHYTMAC_IS 0x0024 +#define PHYTMAC_IE 0x0028 +#define PHYTMAC_ID 0x002C +#define PHYTMAC_IM 0x0030 +#define PHYTMAC_MDATA 0x0034 +#define PHYTMAC_HCONFIG 0x0050 +#define PHYTMAC_AXICTRL 0x0054 +#define PHYTMAC_INT_MODERATION 0x005C +#define PHYTMAC_HASHB 0x0080 +#define PHYTMAC_HASHT 0x0084 +#define PHYTMAC_MAC1B 0x0088 +#define PHYTMAC_MAC1T 0x008C +#define PHYTMAC_WOL 0x00B8 +#define PHYTMAC_OCTTX 0x0100 +#define PHYTMAC_TISN 0x01BC +#define PHYTMAC_TSH 0x01C0 +#define PHYTMAC_TSL 0x01D0 +#define PHYTMAC_TN 0x01D4 +#define PHYTMAC_TA 0x01D8 +#define PHYTMAC_TI 0x01DC +#define PHYTMAC_PCSCTRL 0x0200 +#define PHYTMAC_PCSSTATUS 0x0204 +#define PHYTMAC_PCSANLPBASE 0x0214 +#define PHYTMAC_DEFAULT1 0x0280 /* Default HW Config 1 */ +#define PHYTMAC_DEFAULT2 0x0294 /* Default HW Config 2 */ +#define PHYTMAC_DEFAULT3 0x029C /* Default HW Config 3 */ +#define PHYTMAC_DEFAULT4 0x02A4 /* Default HW Config 4 */ +#define PHYTMAC_DEFAULT5 0x02AC /* Default HW Config 5 */ +#define PHYTMAC_USXCTRL 0x0A80 +#define PHYTMAC_USXSTATUS 0x0A88 +#define PHYTMAC_TXBDCTRL 0x04CC +#define PHYTMAC_RXBDCTRL 0x04D0 +#define PHYTMAC_TXSTART 0x0E70 +#define PHYTMAC_TXSTARTSEL 0x0E74 +#define PHYTMAC_VERSION 0x0FF0 + +/* Fdir match registers */ +#define PHYTMAC_FDIR(i) (0x0540 + ((i) << 2)) + +/* EtherType registers */ +#define PHYTMAC_ETHT(i) (0x06E0 + ((i) << 2)) + +/* Fdir compare registers */ +#define PHYTMAC_COMP1(i) (0x0700 + ((i) << 3)) +#define PHYTMAC_COMP2(i) (0x0704 + ((i) << 3)) + +#define PHYTMAC_ISR(i) (0x0400 + ((i) << 2)) +#define PHYTMAC_TXPTR(i) (0x0440 + ((i) << 2)) +#define PHYTMAC_RXPTR(i) (0x0480 + ((i) << 2)) +#define PHYTMAC_RBQS(i) (0x04A0 + ((i) << 2)) +#define PHYTMAC_TXPTRH(i) (0x04c8) +#define PHYTMAC_RXPTRH(i) (0x04d4) + +#define PHYTMAC_IER(i) (0x0600 + ((i) << 2)) +#define PHYTMAC_IDR(i) (0x0620 + ((i) << 2)) +#define PHYTMAC_IMR(i) (0x0640 + ((i) << 2)) +#define PHYTMAC_TAIL_ENABLE (0x0e7c) +#define PHYTMAC_TX_TAILPTR(i) (0x0e80 + ((i) << 2)) +#define PHYTMAC_RX_TAILPTR(i) (0x0ec0 + ((i) << 2)) + +#define PHYTMAC_PHY_INT_ENABLE 0x1C88 +#define PHYTMAC_PHY_INT_CLEAR 0x1C8C +#define PHYTMAC_PHY_INT_STATE 0x1C90 +#define PHYTMAC_INTX_IRQ_MASK 0x1C7C + +#define PHYTMAC_READ_NSTATUS(pdata) PHYTMAC_READ(pdata, PHYTMAC_NSTATUS) + +/* Ethernet Network Control Register */ +#define PHYTMAC_RE_INDEX 2 /* Receive enable */ +#define PHYTMAC_RE_WIDTH 1 +#define PHYTMAC_TE_INDEX 3 /* Transmit enable */ +#define PHYTMAC_TE_WIDTH 1 +#define PHYTMAC_MPE_INDEX 4 /* Management port enable */ +#define PHYTMAC_MPE_WIDTH 1 +#define PHYTMAC_CLEARSTAT_INDEX 5 /* Clear stats regs */ +#define PHYTMAC_CLEARSTAT_WIDTH 1 +#define PHYTMAC_TSTART_INDEX 9 /* Start transmission */ +#define PHYTMAC_TSTART_WIDTH 1 +#define PHYTMAC_THALT_INDEX 10 /* Transmit halt */ +#define PHYTMAC_THALT_WIDTH 1 +#define PHYTMAC_STORE_RX_TS_INDEX 15 +#define PHYTMAC_STORE_RX_TS_WIDTH 1 +#define PHYTMAC_OSSMODE_INDEX 24 /* Enable One Step Synchro Mode */ +#define PHYTMAC_OSSMODE_WIDTH 1 +#define PHYTMAC_2PT5G_INDEX 29 /* 2.5G operation selected */ +#define PHYTMAC_2PT5G_WIDTH 1 +#define PHYTMAC_HIGHSPEED_INDEX 31 /* High speed enable */ +#define PHYTMAC_HIGHSPEED_WIDTH 1 + +/* Ethernet Network Config Register */ +#define PHYTMAC_SPEED_INDEX 0 /* Speed */ +#define PHYTMAC_SPEED_WIDTH 1 +#define PHYTMAC_FD_INDEX 1 /* Full duplex */ +#define PHYTMAC_FD_WIDTH 1 +#define PHYTMAC_JUMBO_EN_INDEX 3 /* Transmit enable */ +#define PHYTMAC_JUMBO_EN_WIDTH 1 +#define PHYTMAC_PROMISC_INDEX 4 /* Copy all frames */ +#define PHYTMAC_PROMISC_WIDTH 1 +#define PHYTMAC_NO_BCAST_INDEX 5 /* No broadcast */ +#define PHYTMAC_NO_BCAST_WIDTH 1 +#define PHYTMAC_MH_EN_INDEX 6 /* Multicast hash enable */ +#define PHYTMAC_MH_EN_WIDTH 1 +#define PHYTMAC_RCV_BIG_INDEX 8 +#define PHYTMAC_RCV_BIG_WIDTH 1 +#define PHYTMAC_GM_EN_INDEX 10 /* Gigabit mode enable */ +#define PHYTMAC_GM_EN_WIDTH 1 +#define PHYTMAC_PCS_EN_INDEX 11 /* PCS select */ +#define PHYTMAC_PCS_EN_WIDTH 1 +#define PHYTMAC_PAUSE_EN_INDEX 13 /* Pause enable */ +#define PHYTMAC_PAUSE_EN_WIDTH 1 +#define PHYTMAC_FCS_REMOVE_INDEX 17 /* FCS remove */ +#define PHYTMAC_FCS_REMOVE_WIDTH 1 +#define PHYTMAC_MCD_INDEX 18 /* MDC clock division */ +#define PHYTMAC_MCD_WIDTH 3 +#define PHYTMAC_DBW64_INDEX 21 /* Data bus width */ +#define PHYTMAC_DBW64_WIDTH 1 +#define PHYTMAC_DBW128_INDEX 22 /* Data bus width */ +#define PHYTMAC_DBW128_WIDTH 1 +#define PHYTMAC_DBW_32 1 +#define PHYTMAC_DBW_64 2 +#define PHYTMAC_DBW_128 4 +#define PHYTMAC_RCO_EN_INDEX 24 /* Receive checksum offload enable */ +#define PHYTMAC_RCO_EN_WIDTH 1 +#define PHYTMAC_IGNORE_RX_FCS_INDEX 26 +#define PHYTMAC_IGNORE_RX_FCS_WIDTH 1 +#define PHYTMAC_SGMII_EN_INDEX 27 /* Sgmii mode enable */ +#define PHYTMAC_SGMII_EN_WIDTH 1 + +/* Ethernet Network Status Register */ +#define PHYTMAC_PCS_LINK_INDEX 0 /* PCS link status */ +#define PHYTMAC_PCS_LINK_WIDTH 1 +#define PHYTMAC_MDIO_IDLE_INDEX 2 /* Mdio idle */ +#define PHYTMAC_MDIO_IDLE_WIDTH 1 +#define PHYTMAC_PCS_FD_INDEX 3 /* PCS auto negotiation duplex resolution */ +#define PHYTMAC_PCS_FD__WIDTH 1 + +/* Ethernet Network Dma config Register */ +#define PHYTMAC_BURST_INDEX 0 /* Amba burst length */ +#define PHYTMAC_BURST_WIDTH 5 +#define PHYTMAC_ENDIA_PKT_INDEX 6 +#define PHYTMAC_ENDIA_PKT_WIDTH 1 +#define PHYTMAC_ENDIA_DESC_INDEX 7 +#define PHYTMAC_ENDIA_DESC_WIDTH 1 +#define PHYTMAC_TCO_EN_INDEX 11 /* Tx Checksum Offload en */ +#define PHYTMAC_TCO_EN_WIDTH 1 +#define PHYTMAC_RX_BUF_LEN_INDEX 16 /* DMA receive buffer size */ +#define PHYTMAC_RX_BUF_LEN_WIDTH 8 +#define PHYTMAC_RX_EXBD_EN_INDEX 28 /* Enable RX extended BD mode */ +#define PHYTMAC_RX_EXBD_EN_WIDTH 1 +#define PHYTMAC_TX_EXBD_EN_INDEX 29 /* Enable TX extended BD mode */ +#define PHYTMAC_TX_EXBD_EN_WIDTH 1 +#define PHYTMAC_ABW_INDEX 30 /* DMA address bus width */ +#define PHYTMAC_ABW_WIDTH 1 + +/* Int stauts/Enable/Disable/Mask Register */ +#define PHYTMAC_RXCOMP_INDEX 1 /* Rx complete */ +#define PHYTMAC_RXCOMP_WIDTH 1 +#define PHYTMAC_RUB_INDEX 2 /* Rx used bit read */ +#define PHYTMAC_RUB_WIDTH 1 +#define PHYTMAC_BUS_ERR_INDEX 6 /* AMBA error */ +#define PHYTMAC_BUS_ERR_WIDTH 1 +#define PHYTMAC_TXCOMP_INDEX 7 /* Tx complete */ +#define PHYTMAC_TXCOMP_WIDTH 1 +#define PHYTMAC_RXOVERRUN_INDEX 10 /* Tx overrun */ +#define PHYTMAC_RXOVERRUN_WIDTH 1 +#define PHYTMAC_RESP_ERR_INDEX 11 /* Resp not ok */ +#define PHYTMAC_RESP_ERR_WIDTH 1 + +/* Mdio read/write Register */ +#define PHYTMAC_DATA_INDEX 0 /* Data */ +#define PHYTMAC_DATA_WIDTH 16 +#define PHYTMAC_MUST_INDEX 16 /* Must Be 10 */ +#define PHYTMAC_MUST_WIDTH 2 +#define PHYTMAC_REG_ADDR_INDEX 18 /* Register address */ +#define PHYTMAC_REG_ADDR_WIDTH 5 +#define PHYTMAC_PHY_ADDR_INDEX 23 /* Phy address */ +#define PHYTMAC_PHY_ADDR_WIDTH 5 +#define PHYTMAC_OPS_INDEX 28 +#define PHYTMAC_OPS_WIDTH 2 +#define PHYTMAC_CLAUSE_SEL_INDEX 30 +#define PHYTMAC_CLAUSE_SEL_WIDTH 1 +#define PHYTMAC_CLAUSE_C22 1 +#define PHYTMAC_CLAUSE_C45 0 +#define PHYTMAC_OPS_C45_ADDR 0 +#define PHYTMAC_OPS_C45_WRITE 1 +#define PHYTMAC_OPS_C45_READ 3 +#define PHYTMAC_OPS_C22_WRITE 1 +#define PHYTMAC_OPS_C22_READ 2 + +/* hs mac config register */ +#define PHYTMAC_HS_SPEED_INDEX 0 +#define PHYTMAC_HS_SPEED_WIDTH 3 +#define PHYTMAC_HS_SPEED_100M 0 +#define PHYTMAC_HS_SPEED_1000M 1 +#define PHYTMAC_HS_SPEED_2500M 2 +#define PHYTMAC_HS_SPEED_5000M 3 +#define PHYTMAC_HS_SPEED_10G 4 + +/* WOL register */ +#define PHYTMAC_ARP_IP_INDEX 0 +#define PHYTMAC_ARP_IP_WIDTH 16 +#define PHYTMAC_MAGIC_INDEX 16 +#define PHYTMAC_MAGIC_WIDTH 1 +#define PHYTMAC_ARP_INDEX 17 +#define PHYTMAC_ARP_WIDTH 1 +#define PHYTMAC_UCAST_INDEX 18 +#define PHYTMAC_UCAST_WIDTH 1 +#define PHYTMAC_MCAST_INDEX 19 +#define PHYTMAC_MCAST_WIDTH 1 + +/* PCSCTRL register */ +#define PHYTMAC_AUTONEG_INDEX 12 +#define PHYTMAC_AUTONEG_WIDTH 1 +#define PHYTMAC_PCS_RESET_INDEX 15 +#define PHYTMAC_PCS_RESET_WIDTH 1 + +/* DEFAULT1 register */ +#define PHYTMAC_DBW_INDEX 25 +#define PHYTMAC_DBW_WIDTH 3 + +/* DEFAULT2 register */ +#define PHYTMAC_DAW64_INDEX 23 +#define PHYTMAC_DAW64_WIDTH 1 + +/* DEFAULT3 register */ +#define PHYTMAC_SCR2CMP_INDEX 0 +#define PHYTMAC_SCR2CMP_WIDTH 8 +#define PHYTMAC_SCR2ETH_INDEX 8 +#define PHYTMAC_SCR2ETH_WIDTH 8 + +/* DEFAULT4 register */ +#define PHYTMAC_TXDESCRD_INDEX 12 +#define PHYTMAC_TXDESCRD_WIDTH 4 +#define PHYTMAC_RXDESCRD_INDEX 8 +#define PHYTMAC_RXDESCRD_WIDTH 4 + +/* USXCTRL register */ +#define PHYTMAC_RX_EN_INDEX 0 +#define PHYTMAC_RX_EN_WIDTH 1 +#define PHYTMAC_TX_EN_INDEX 1 +#define PHYTMAC_TX_EN_WIDTH 1 +#define PHYTMAC_RX_SYNC_RESET_INDEX 2 +#define PHYTMAC_RX_SYNC_RESET_WIDTH 1 +#define PHYTMAC_SERDES_RATE_INDEX 12 +#define PHYTMAC_SERDES_RATE_WIDTH 2 +#define PHYTMAC_USX_SPEED_INDEX 14 +#define PHYTMAC_USX_SPEED_WIDTH 3 + +/* Bitfields in USX_STATUS. */ +#define PHYTMAC_USX_PCS_LINK_INDEX 0 +#define PHYTMAC_USX_PCS_LINK_WIDTH 1 + +/* Bitfields in PHYTMAC_TISN */ +#define PHYTMAC_SUB_NSECH_INDEX 0 +#define PHYTMAC_SUB_NSECH_WIDTH 16 +#define PHYTMAC_SUB_NSECL_INDEX 24 +#define PHYTMAC_SUB_NSECL_WIDTH 8 +#define PHYTMAC_SUB_NSEC_WIDTH 24 + +/* Bitfields in PHYTMAC_TSH */ +#define PHYTMAC_SECH_INDEX 0 +#define PHYTMAC_SECH_WIDTH 16 + +/* Bitfields in PHYTMAC_TSL */ +#define PHYTMAC_SECL_INDEX 0 +#define PHYTMAC_SECL_WIDTH 32 + +/* Bitfields in PHYTMAC_TN */ +#define PHYTMAC_NSEC_INDEX 0 +#define PHYTMAC_NSEC_WIDTH 30 + +/* Bitfields in PHYTMAC_TA */ +#define PHYTMAC_INCR_SEC_INDEX 0 +#define PHYTMAC_INCR_SEC_WIDTH 30 +#define PHYTMAC_INCR_ADD_INDEX 31 +#define PHYTMAC_INCR_ADD_WIDTH 1 +#define PHYTMAC_ADJUST_SEC_MAX ((1 << PHYTMAC_INCR_SEC_WIDTH) - 1) + +/* Bitfields in PHYTMAC_TI */ +#define PHYTMAC_INCR_NS_INDEX 0 +#define PHYTMAC_INCR_NS_WIDTH 8 + +/* PHYTMAC_TXBDCTRL register */ +#define PHYTMAC_TX_TSMODE_INDEX 4 +#define PHYTMAC_TX_TSMODE_WIDTH 2 + +#define PHYTMAC_RX_TSMODE_INDEX 4 +#define PHYTMAC_RX_TSMODE_WIDTH 2 + +/* Bitfields in PHYTMAC_FDIR */ +#define PHYTMAC_QUEUE_NUM_INDEX 0 +#define PHYTMAC_QUEUE_NUM_WIDTH 4 +#define PHYTMAC_VLAN_PRI_INDEX 4 +#define PHYTMAC_VLAN_PRI_WIDTH 3 +#define PHYTMAC_VLAN_EN_INDEX 8 +#define PHYTMAC_VLAN_EN_WIDTH 1 +#define PHYTMAC_ETH_TYPE_INDEX 9 +#define PHYTMAC_ETH_TYPE_WIDTH 3 +#define PHYTMAC_ETH_EN_INDEX 12 +#define PHYTMAC_ETH_EN_WIDTH 1 +#define PHYTMAC_CA_INDEX 13 +#define PHYTMAC_CA_WIDTH 5 +#define PHYTMAC_CA_EN_INDEX 18 +#define PHYTMAC_CA_EN_WIDTH 1 +#define PHYTMAC_CB_INDEX 19 +#define PHYTMAC_CB_WIDTH 5 +#define PHYTMAC_CB_EN_INDEX 24 +#define PHYTMAC_CB_EN_WIDTH 1 +#define PHYTMAC_CC_INDEX 25 +#define PHYTMAC_CC_WIDTH 5 +#define PHYTMAC_CC_EN_INDEX 30 +#define PHYTMAC_CC_EN_WIDTH 1 + +/* Bitfields in PHYTMAC_ETHERTYPE */ +#define PHYTMAC_ETHTYPE_VALUE_INDEX 0 +#define PHYTMAC_ETHTYPE_VALUE_WIDTH 16 + +/* Bitfields in PHYTMAC_COMP1 */ +#define PHYTMAC_SPORT_INDEX 0 +#define PHYTMAC_SPORT_WIDTH 16 +#define PHYTMAC_DPORT_INDEX 16 +#define PHYTMAC_DPORTE_WIDTH 16 + +/* Bitfields in PHYTMAC_COMP2 */ +#define PHYTMAC_OFFSET_INDEX 0 +#define PHYTMAC_OFFSET_WIDTH 7 +#define ETHTYPE_SIP_OFFSET 12 +#define ETHTYPE_DIP_OFFSET 16 +#define IPHEAD_SPORT_OFFSET 0 +#define IPHEAD_DPORT_OFFSET 2 +#define PHYTMAC_OFFSET_TYPE_INDEX 7 +#define PHYTMAC_OFFSET_TYPE_WIDTH 2 +#define PHYTMAC_OFFSET_BEGIN 0 +#define PHYTMAC_OFFSET_AFTER_L2HEAD 1 +#define PHYTMAC_OFFSET_AFTER_L3HEAD 2 +#define PHYTMAC_OFFSET_AFTER_L4HEAD 3 +#define PHYTMAC_DIS_MASK_INDEX 9 +#define PHYTMAC_DIS_MASK_WIDTH 1 +#define PHYTMAC_VLAN_ID_INDEX 10 +#define PHYTMAC_VLAN_ID_WIDTH 1 + +/* Bitfields in TAILPTR */ +#define PHYTMAC_TXTAIL_UPDATE_INDEX 31 /* Update tx tail */ +#define PHYTMAC_TXTAIL_UPDATE_WIDTH 1 + +/* Bitfields in TAIL_ENABLE */ +#define PHYTMAC_TXPTR_EN_INDEX 0 /* Enable tx tail */ +#define PHYTMAC_TXPTR_EN_WIDTH 1 + +#define PHYTMAC_RXPTR_EN_INDEX 16 /* Enable rx tail */ +#define PHYTMAC_RXPTR_EN_WIDTH 1 + +/* Bitfields in INT ENABLE */ +#define PHYTMAC_WOL_RECEIVE_ENABLE_INDEX 28 /* Enable wol_event_recieve */ +#define PHYTMAC_WOL_RECEIVE_ENABLE_WIDTH 1 + +/* Bitfields in INT DISABLE */ +#define PHYTMAC_WOL_RECEIVE_DISABLE_INDEX 28 /* Disable wol_event_recieve */ +#define PHYTMAC_WOL_RECEIVE_DISABLE_WIDTH 1 + +/* Bitfields in PHYTMAC_TXSTARTSEL */ +#define PHYTMAC_TSTARTSEL_ENABLE_INDEX 0 /* Enable Tstart_sel */ +#define PHYTMAC_TSTARTSEL_ENABLE_WIDTH 1 + +#define PHYTMAC_TSEC_WIDTH (PHYTMAC_SECH_WIDTH + PHYTMAC_SECL_WIDTH) +#define SEC_MAX_VAL (((u64)1 << PHYTMAC_TSEC_WIDTH) - 1) +#define NSEC_MAX_VAL ((1 << PHYTMAC_NSEC_WIDTH) - 1) + +/* rx dma desc bit */ +/* DMA descriptor bitfields */ +#define PHYTMAC_RX_USED_INDEX 0 +#define PHYTMAC_RX_USED_WIDTH 1 +#define PHYTMAC_RX_WRAP_INDEX 1 +#define PHYTMAC_RX_WRAP_WIDTH 1 +#define PHYTMAC_RX_TS_VALID_INDEX 2 +#define PHYTMAC_RX_TS_VALID_WIDTH 1 +#define PHYTMAC_RX_WADDR_INDEX 2 +#define PHYTMAC_RX_WADDR_WIDTH 30 + +#define PHYTMAC_RX_FRMLEN_INDEX 0 +#define PHYTMAC_RX_FRMLEN_WIDTH 12 +#define PHYTMAC_RX_INDEX_INDEX 12 +#define PHYTMAC_RX_INDEX_WIDTH 2 +#define PHYTMAC_RX_SOF_INDEX 14 +#define PHYTMAC_RX_SOF_WIDTH 1 +#define PHYTMAC_RX_EOF_INDEX 15 +#define PHYTMAC_RX_EOF_WIDTH 1 +#define PHYTMAC_RX_CFI_INDEX 16 +#define PHYTMAC_RX_CFI_WIDTH 1 +#define PHYTMAC_RX_VLAN_PRI_INDEX 17 +#define PHYTMAC_RX_VLAN_PRI_WIDTH 3 +#define PHYTMAC_RX_PRI_TAG_INDEX 20 +#define PHYTMAC_RX_PRI_TAG_WIDTH 1 +#define PHYTMAC_RX_VLAN_TAG_INDEX 21 +#define PHYTMAC_RX_VLAN_TAG_WIDTH 1 +#define PHYTMAC_RX_UHASH_MATCH_INDEX 29 +#define PHYTMAC_RX_UHASH_MATCH_WIDTH 1 +#define PHYTMAC_RX_MHASH_MATCH_INDEX 30 +#define PHYTMAC_RX_MHASH_MATCH_WIDTH 1 +#define PHYTMAC_RX_BROADCAST_INDEX 31 +#define PHYTMAC_RX_BROADCAST_WIDTH 1 + +#define PHYTMAC_RX_FRMLEN_MASK 0x1FFF +#define PHYTMAC_RX_JFRMLEN_MASK 0x3FFF + +/* RX checksum offload disabled: bit 24 clear in NCFGR */ +#define PHYTMAC_RX_TYPEID_MATCH_INDEX 22 +#define PHYTMAC_RX_TYPEID_MATCH_WIDTH 2 + +/* RX checksum offload enabled: bit 24 set in NCFGR */ +#define PHYTMAC_RX_CSUM_INDEX 22 +#define PHYTMAC_RX_CSUM_WIDTH 2 + +/* tx dma desc bit */ +#define PHYTMAC_TX_FRMLEN_INDEX 0 +#define PHYTMAC_TX_FRMLEN_WIDTH 14 +#define PHYTMAC_TX_LAST_INDEX 15 +#define PHYTMAC_TX_LAST_WIDTH 1 +#define PHYTMAC_TX_NOCRC_INDEX 16 +#define PHYTMAC_TX_NOCRC_WIDTH 1 +#define PHYTMAC_MSS_MFS_INDEX 16 +#define PHYTMAC_MSS_MFS_WIDTH 14 +#define PHYTMAC_TX_LSO_INDEX 17 +#define PHYTMAC_TX_LSO_WIDTH 2 +#define PHYTMAC_TX_TCP_SEQ_SRC_INDEX 19 +#define PHYTMAC_TX_TCP_SEQ_SRC_WIDTH 1 +#define PHYTMAC_TX_TS_VALID_INDEX 23 +#define PHYTMAC_TX_TS_VALID_WIDTH 1 +#define PHYTMAC_TX_BUF_EXHAUSTED_INDEX 27 +#define PHYTMAC_TX_BUF_EXHAUSTED_WIDTH 1 +#define PHYTMAC_TX_UNDERRUN_INDEX 28 +#define PHYTMAC_TX_UNDERRUN_WIDTH 1 +#define PHYTMAC_TX_ERROR_INDEX 29 +#define PHYTMAC_TX_ERROR_WIDTH 1 +#define PHYTMAC_TX_WRAP_INDEX 30 +#define PHYTMAC_TX_WRAP_WIDTH 1 +#define PHYTMAC_TX_USED_INDEX 31 +#define PHYTMAC_TX_USED_WIDTH 1 + +/* Transmit DMA buffer descriptor Word 4 */ +#define PHYTMAC_DMA_NSEC_INDEX 0 +#define PHYTMAC_DMA_NSEC_WIDTH 30 +#define PHYTMAC_DMA_SECL_INDEX 30 +#define PHYTMAC_DMA_SECL_WIDTH 2 + +/* Transmit DMA buffer descriptor Word 4 */ +#define PHYTMAC_DMA_SECH_INDEX 0 +#define PHYTMAC_DMA_SECH_WIDTH 4 +#define PHYTMAC_DMA_SEC_MASK 0x3f +#define PHYTMAC_DMA_SEC_TOP 0x40 + +/* Buffer descriptor constants */ +#define PHYTMAC_RX_CSUM_NONE 0 +#define PHYTMAC_RX_CSUM_IP_ONLY 1 +#define PHYTMAC_RX_CSUM_IP_TCP 2 +#define PHYTMAC_RX_CSUM_IP_UDP 3 + +/* limit RX checksum offload to TCP and UDP packets */ +#define PHYTMAC_RX_CSUM_CHECKED_MASK 2 + +#endif diff --git a/drivers/net/ethernet/phytium/phytmac_v2.c b/drivers/net/ethernet/phytium/phytmac_v2.c new file mode 100644 index 00000000000000..0df1771ca3972f --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_v2.c @@ -0,0 +1,1417 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phytmac.h" +#include "phytmac_v2.h" + +static int phytmac_v2_msg_send(struct phytmac *pdata, u16 cmd_id, + u16 cmd_subid, void *data, int len, int wait) +{ + u32 tx_head, tx_tail, ring_size; + struct phytmac_msg_info msg; + struct phytmac_msg_info msg_rx; + unsigned long flags; + u32 retry = 0; + int ret = 0; + + spin_lock_irqsave(&pdata->msg_ring.msg_lock, flags); + tx_head = PHYTMAC_READ(pdata, PHYTMAC_TX_MSG_HEAD) & 0xff; + tx_tail = phytmac_v2_tx_ring_wrap(pdata, pdata->msg_ring.tx_msg_wr_tail); + pdata->msg_ring.tx_msg_rd_tail = tx_tail; + ring_size = pdata->msg_ring.tx_msg_ring_size; + + while ((tx_tail + 1) % ring_size == tx_head) { + udelay(1); + tx_head = PHYTMAC_READ(pdata, PHYTMAC_TX_MSG_HEAD) & 0xff; + retry++; + if (retry >= PHYTMAC_RETRY_TIMES) { + netdev_err(pdata->ndev, + "Time out waiting for Tx msg ring free, tx_tail:0x%x, tx_head:0x%x", + tx_tail, tx_head); + ret = -EINVAL; + goto err_out; + } + } + + retry = 0; + wait = 1; + memset(&msg, 0, sizeof(msg)); + memset(&msg_rx, 0, sizeof(msg_rx)); + msg.cmd_type = cmd_id; + msg.cmd_subid = cmd_subid; + if (len > 0 && len <= PHYTMAC_MSG_PARA_LEN) { + memcpy(&msg.para[0], data, len); + } else if (len > PHYTMAC_MSG_PARA_LEN) { + netdev_err(pdata->ndev, "Tx msg para len %d is greater than the max len %d", + len, PHYTMAC_MSG_PARA_LEN); + ret = -EINVAL; + goto err_out; + } + + if (netif_msg_hw(pdata)) { + netdev_info(pdata->ndev, "Tx msg: cmdid:%d, subid:%d, status0:%d, len:%d, tail:%d", + msg.cmd_type, msg.cmd_subid, msg.status0, len, tx_tail); + } + + memcpy(pdata->msg_regs + PHYTMAC_MSG(tx_tail), &msg, sizeof(msg)); + tx_tail = phytmac_v2_tx_ring_wrap(pdata, ++tx_tail); + PHYTMAC_WRITE(pdata, PHYTMAC_TX_MSG_TAIL, tx_tail | PHYTMAC_BIT(TX_MSG_INT)); + pdata->msg_ring.tx_msg_wr_tail = tx_tail; + + if (wait) { + tx_head = PHYTMAC_READ(pdata, PHYTMAC_TX_MSG_HEAD) & 0xff; + while (tx_head != tx_tail) { + udelay(1); + tx_head = PHYTMAC_READ(pdata, PHYTMAC_TX_MSG_HEAD) & 0xff; + retry++; + if (retry >= PHYTMAC_RETRY_TIMES) { + netdev_err(pdata->ndev, "Msg process time out!"); + ret = -EINVAL; + goto err_out; + } + } + + memcpy(&msg_rx, pdata->msg_regs + PHYTMAC_MSG(pdata->msg_ring.tx_msg_rd_tail), + PHYTMAC_MSG_HDR_LEN); + if (!(msg_rx.status0 & PHYTMAC_CMD_PRC_SUCCESS)) { + netdev_err(pdata->ndev, "Msg process error, cmdid:%d, subid:%d, status0:%d, tail:%d", + msg.cmd_type, msg.cmd_subid, msg.status0, tx_tail); + ret = -EINVAL; + goto err_out; + } + } + +err_out: + spin_unlock_irqrestore(&pdata->msg_ring.msg_lock, flags); + return ret; +} + +static void phytmac_v2_reset_hw(struct phytmac *pdata) +{ + int q; + u16 cmd_id, cmd_subid; + struct phytmac_ring_info ring; + + /* Disable and clear all interrupts and disable queues */ + for (q = 0; q < pdata->queues_max_num; ++q) { + PHYTMAC_WRITE(pdata, PHYTMAC_INT_DR(q), -1); + PHYTMAC_WRITE(pdata, PHYTMAC_INT_SR(q), -1); + PHYTMAC_WRITE(pdata, PHYTMAC_TX_PTR(q), 0); + PHYTMAC_WRITE(pdata, PHYTMAC_RX_PTR(q), 0); + } + + /* reset hw rx/tx enable */ + cmd_id = PHYTMAC_MSG_CMD_DEFAULT; + cmd_subid = PHYTMAC_MSG_CMD_DEFAULT_RESET_HW; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 0); + + /* reset tx ring */ + memset(&ring, 0, sizeof(ring)); + ring.queue_num = pdata->queues_max_num; + cmd_subid = PHYTMAC_MSG_CMD_DEFAULT_RESET_TX_QUEUE; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(&ring), sizeof(ring), 0); + + /* reset rx ring */ + cmd_subid = PHYTMAC_MSG_CMD_DEFAULT_RESET_RX_QUEUE; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(&ring), sizeof(ring), 1); +} + +static int phytmac_v2_get_mac_addr(struct phytmac *pdata, u8 *addr) +{ + int index; + u16 cmd_id, cmd_subid; + struct phytmac_mac para; + + cmd_id = PHYTMAC_MSG_CMD_GET; + cmd_subid = PHYTMAC_MSG_CMD_GET_ADDR; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + index = phytmac_v2_tx_ring_wrap(pdata, pdata->msg_ring.tx_msg_rd_tail); + memcpy(¶, pdata->msg_regs + PHYTMAC_MSG(index) + PHYTMAC_MSG_HDR_LEN, + sizeof(struct phytmac_mac)); + + addr[0] = para.addrl & 0xff; + addr[1] = (para.addrl >> 8) & 0xff; + addr[2] = (para.addrl >> 16) & 0xff; + addr[3] = (para.addrl >> 24) & 0xff; + addr[4] = para.addrh & 0xff; + addr[5] = (para.addrh >> 8) & 0xff; + + return 0; +} + +static int phytmac_v2_set_mac_addr(struct phytmac *pdata, const u8 *addr) +{ + u16 cmd_id; + u16 cmd_subid; + struct phytmac_mac para; + + memset(¶, 0, sizeof(para)); + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_ADDR; + para.addrl = cpu_to_le32(*((u32 *)addr)); + para.addrh = cpu_to_le16(*((u16 *)(addr + 4))); + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); + + return 0; +} + +static int phytmac_v2_pcs_software_reset(struct phytmac *pdata, int reset) +{ + u16 cmd_id; + u16 cmd_subid; + + cmd_id = PHYTMAC_MSG_CMD_SET; + if (reset) + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_PCS_RESET; + else + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_PCS_RESET; + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + return 0; +} + +static int phytmac_v2_init_hw(struct phytmac *pdata) +{ + u16 cmd_id, cmd_subid; + struct phytmac_dma_info dma; + struct phytmac_eth_info eth; + u32 ptrconfig = 0; + + if (pdata->capacities & PHYTMAC_CAPS_TAILPTR) + ptrconfig |= PHYTMAC_BIT(TXTAIL_EN); + if (pdata->capacities & PHYTMAC_CAPS_RXPTR) + ptrconfig |= PHYTMAC_BIT(RXTAIL_EN); + + PHYTMAC_WRITE(pdata, PHYTMAC_TAILPTR_ENABLE, ptrconfig); + + if (pdata->mii_bus) { + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_MDIO; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 0); + } + + phytmac_v2_set_mac_addr(pdata, pdata->ndev->dev_addr); + + cmd_id = PHYTMAC_MSG_CMD_SET; + if (pdata->capacities & PHYTMAC_CAPS_JUMBO) + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_JUMBO; + else + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_1536_FRAMES; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 0); + + if (pdata->ndev->flags & IFF_PROMISC) { + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_PROMISE; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 0); + } + + if (pdata->ndev->features & NETIF_F_RXCSUM) { + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_RXCSUM; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 0); + } + + if (pdata->ndev->flags & IFF_BROADCAST) + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_BC; + else + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_BC; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 0); + + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_PAUSE; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 0); + + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_STRIPCRC; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 0); + + memset(&dma, 0, sizeof(dma)); + cmd_subid = PHYTMAC_MSG_CMD_SET_DMA; + dma.dma_burst_length = pdata->dma_burst_length; + if (pdata->dma_addr_width) + dma.hw_dma_cap |= HW_DMA_CAP_64B; + if (pdata->ndev->features & NETIF_F_HW_CSUM) + dma.hw_dma_cap |= HW_DMA_CAP_CSUM; + if (IS_REACHABLE(CONFIG_PHYTMAC_ENABLE_PTP)) + dma.hw_dma_cap |= HW_DMA_CAP_PTP; + if (pdata->dma_data_width == PHYTMAC_DBW32) + dma.hw_dma_cap |= HW_DMA_CAP_DDW32; + if (pdata->dma_data_width == PHYTMAC_DBW64) + dma.hw_dma_cap |= HW_DMA_CAP_DDW64; + if (pdata->dma_data_width == PHYTMAC_DBW128) + dma.hw_dma_cap |= HW_DMA_CAP_DDW128; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)&dma, sizeof(dma), 0); + + memset(ð, 0, sizeof(eth)); + cmd_subid = PHYTMAC_MSG_CMD_SET_ETH_MATCH; + eth.index = 0; + eth.etype = (uint16_t)ETH_P_IP; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)ð, sizeof(eth), 1); + + if (phy_interface_mode_is_8023z(pdata->phy_interface)) + phytmac_v2_pcs_software_reset(pdata, 1); + + return 0; +} + +static int phytmac_v2_enable_multicast(struct phytmac *pdata, int enable) +{ + u16 cmd_id, cmd_subid; + + cmd_id = PHYTMAC_MSG_CMD_SET; + if (enable) + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_MC; + else + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_MC; + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + return 0; +} + +static int phytmac_v2_set_mc_hash(struct phytmac *pdata, unsigned long *mc_filter) +{ + u16 cmd_id, cmd_subid; + struct phytmac_mc_info para; + + memset(¶, 0, sizeof(para)); + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_HASH_MC; + para.mc_bottom = (u32)mc_filter[0]; + para.mc_top = (u32)mc_filter[1]; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); + + return 0; +} + +static int phytmac_v2_init_ring_hw(struct phytmac *pdata) +{ + u16 cmd_id, cmd_subid; + struct phytmac_ring_info rxring; + struct phytmac_ring_info txring; + struct phytmac_rxbuf_info rxbuf; + struct phytmac_queue *queue; + u32 q; + + memset(&rxring, 0, sizeof(rxring)); + memset(&txring, 0, sizeof(txring)); + memset(&rxbuf, 0, sizeof(rxbuf)); + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_INIT_TX_RING; + txring.queue_num = pdata->queues_num; + rxring.queue_num = pdata->queues_num; + txring.hw_dma_cap |= HW_DMA_CAP_64B; + rxring.hw_dma_cap |= HW_DMA_CAP_64B; + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + PHYTMAC_WRITE(pdata, PHYTMAC_TX_PTR(q), queue->tx_head); + txring.addr[q] = queue->tx_ring_addr; + rxring.addr[q] = queue->rx_ring_addr; + } + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(&txring), sizeof(txring), 0); + + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_DMA_RX_BUFSIZE; + rxbuf.queue_num = pdata->queues_num; + rxbuf.buffer_size = pdata->rx_buffer_len / 64; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(&rxbuf), sizeof(rxbuf), 0); + + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_INIT_RX_RING; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(&rxring), sizeof(rxring), 1); + + return 0; +} + +static int phytmac_v2_init_msg_ring(struct phytmac *pdata) +{ + u32 tx_msg_tail; + + pdata->msg_ring.tx_msg_ring_size = PHYTMAC_READ_BITS(pdata, PHYTMAC_SIZE, TXRING_SIZE); + tx_msg_tail = PHYTMAC_READ(pdata, PHYTMAC_TX_MSG_TAIL) & 0xff; + pdata->msg_ring.tx_msg_wr_tail = phytmac_v2_tx_ring_wrap(pdata, tx_msg_tail); + pdata->msg_ring.tx_msg_rd_tail = pdata->msg_ring.tx_msg_wr_tail; + PHYTMAC_WRITE(pdata, PHYTMAC_MSG_IMR, 0xfffffffe); + if (netif_msg_hw(pdata)) + netdev_info(pdata->ndev, "Msg ring size:%d, tx msg tail=%d\n", + pdata->msg_ring.tx_msg_ring_size, tx_msg_tail); + + return 0; +} + +static int phytmac_v2_get_feature_all(struct phytmac *pdata) +{ + u16 cmd_id, cmd_subid; + int index; + struct phytmac_feature para; + + memset(¶, 0, sizeof(para)); + cmd_id = PHYTMAC_MSG_CMD_GET; + cmd_subid = PHYTMAC_MSG_CMD_GET_CAPS; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + index = phytmac_v2_tx_ring_wrap(pdata, pdata->msg_ring.tx_msg_rd_tail); + memcpy(¶, pdata->msg_regs + PHYTMAC_MSG(index) + PHYTMAC_MSG_HDR_LEN, + sizeof(struct phytmac_feature)); + + pdata->queues_max_num = para.queue_num; + if (para.dma_addr_width) + pdata->dma_addr_width = 64; + else + pdata->dma_addr_width = 32; + pdata->dma_data_width = para.dma_data_width; + pdata->max_rx_fs = para.max_rx_fs; + + if (para.tx_bd_prefetch) + pdata->tx_bd_prefetch = (2 << (para.tx_bd_prefetch - 1)) * + sizeof(struct phytmac_dma_desc); + if (para.rx_bd_prefetch) + pdata->rx_bd_prefetch = (2 << (para.rx_bd_prefetch - 1)) * + sizeof(struct phytmac_dma_desc); + + if (netif_msg_hw(pdata)) { + netdev_info(pdata->ndev, "feature qnum=%d, daw=%d, dbw=%d, rxfs=%d, rxbd=%d, txbd=%d\n", + pdata->queues_num, pdata->dma_addr_width, pdata->dma_data_width, + pdata->max_rx_fs, pdata->rx_bd_prefetch, pdata->tx_bd_prefetch); + } + + return 0; +} + +static void phytmac_v2_get_regs(struct phytmac *pdata, u32 *reg_buff) +{ + u16 cmd_id, cmd_subid; + int index; + struct phytmac_ethtool_reg msg; + + memset(&msg, 0, sizeof(msg)); + cmd_id = PHYTMAC_MSG_CMD_GET; + cmd_subid = PHYTMAC_MSG_CMD_GET_REGS_FOR_ETHTOOL; + msg.interface = pdata->phytmac_v2_interface; + /* There are 16 regs in total, read 14 regs at first time, read 2 regs at last time */ + msg.cnt = 0; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(&msg), sizeof(msg), 1); + index = phytmac_v2_tx_ring_wrap(pdata, pdata->msg_ring.tx_msg_rd_tail); + memcpy(reg_buff, pdata->msg_regs + PHYTMAC_MSG(index) + PHYTMAC_MSG_HDR_LEN, + PHYTMAC_MSG_PARA_LEN); + + msg.cnt = 1; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(&msg), sizeof(msg), 1); + index = phytmac_v2_tx_ring_wrap(pdata, pdata->msg_ring.tx_msg_rd_tail); + memcpy(reg_buff + PHYTMAC_MSG_PARA_LEN / sizeof(u32), pdata->msg_regs + + PHYTMAC_MSG(index) + PHYTMAC_MSG_HDR_LEN, + PHYTMAC_ETHTOOLD_REGS_LEN - PHYTMAC_MSG_PARA_LEN); +} + +static void phytmac_v2_get_hw_stats(struct phytmac *pdata) +{ + u32 stats[PHYTMAC_STATIS_REG_NUM]; + int i, j; + u64 val; + u64 *p = &pdata->stats.tx_octets; + + for (i = 0 ; i < PHYTMAC_STATIS_REG_NUM; i++) + stats[i] = PHYTMAC_READ(pdata, PHYTMAC_OCT_TX + i * 4); + + for (i = 0, j = 0; i < PHYTMAC_STATIS_REG_NUM; i++) { + if (i == 0 || i == 20) { + val = (u64)stats[i + 1] << 32 | stats[i]; + *p += val; + pdata->ethtool_stats[j] = *p; + ++j; + ++p; + } else { + if (i != 1 && i != 21) { + val = stats[i]; + *p += val; + pdata->ethtool_stats[j] = *p; + ++j; + ++p; + } + } + } +} + +static int phytmac_v2_mdio_idle(struct phytmac *pdata) +{ + u32 val; + int ret; + + /* wait for end of transfer */ + ret = readx_poll_timeout(PHYTMAC_READ_NSR, pdata, val, val & PHYTMAC_BIT(MIDLE), + 1, PHYTMAC_MDIO_TIMEOUT); + if (ret) + netdev_err(pdata->ndev, "mdio wait for idle time out!"); + + return ret; +} + +static int phytmac_v2_mdio_data_read_c22(struct phytmac *pdata, int mii_id, int regnum) +{ + u16 data; + + PHYTMAC_WRITE(pdata, PHYTMAC_MDIO, (PHYTMAC_BITS(CLAUSESEL, PHYTMAC_C22) + | PHYTMAC_BITS(MDCOPS, PHYTMAC_C22_READ) + | PHYTMAC_BITS(PHYADDR, mii_id) + | PHYTMAC_BITS(REGADDR, regnum) + | PHYTMAC_BITS(CONST, 2))); + phytmac_v2_mdio_idle(pdata); + data = PHYTMAC_READ(pdata, PHYTMAC_MDIO) & 0xffff; + phytmac_v2_mdio_idle(pdata); + return data; +} + +static int phytmac_v2_mdio_data_write_c22(struct phytmac *pdata, int mii_id, + int regnum, u16 data) +{ + PHYTMAC_WRITE(pdata, PHYTMAC_MDIO, (PHYTMAC_BITS(CLAUSESEL, PHYTMAC_C22) + | PHYTMAC_BITS(MDCOPS, PHYTMAC_C22_WRITE) + | PHYTMAC_BITS(PHYADDR, mii_id) + | PHYTMAC_BITS(REGADDR, regnum) + | PHYTMAC_BITS(VALUE, data) + | PHYTMAC_BITS(CONST, 2))); + phytmac_v2_mdio_idle(pdata); + + return 0; +} + +static int phytmac_v2_mdio_data_read_c45(struct phytmac *pdata, int mii_id, int devad, int regnum) +{ + u16 data; + + PHYTMAC_WRITE(pdata, PHYTMAC_MDIO, (PHYTMAC_BITS(CLAUSESEL, PHYTMAC_C45) + | PHYTMAC_BITS(MDCOPS, PHYTMAC_C45_ADDR) + | PHYTMAC_BITS(PHYADDR, mii_id) + | PHYTMAC_BITS(REGADDR, devad & 0x1F) + | PHYTMAC_BITS(VALUE, regnum & 0xFFFF) + | PHYTMAC_BITS(CONST, 2))); + phytmac_v2_mdio_idle(pdata); + PHYTMAC_WRITE(pdata, PHYTMAC_MDIO, (PHYTMAC_BITS(CLAUSESEL, PHYTMAC_C45) + | PHYTMAC_BITS(MDCOPS, PHYTMAC_C45_READ) + | PHYTMAC_BITS(PHYADDR, mii_id) + | PHYTMAC_BITS(REGADDR, devad & 0x1F) + | PHYTMAC_BITS(VALUE, regnum & 0xFFFF) + | PHYTMAC_BITS(CONST, 2))); + phytmac_v2_mdio_idle(pdata); + data = PHYTMAC_READ(pdata, PHYTMAC_MDIO) & 0xffff; + phytmac_v2_mdio_idle(pdata); + return data; +} + +static int phytmac_v2_mdio_data_write_c45(struct phytmac *pdata, int mii_id, int devad, + int regnum, u16 data) +{ + PHYTMAC_WRITE(pdata, PHYTMAC_MDIO, (PHYTMAC_BITS(CLAUSESEL, PHYTMAC_C45) + | PHYTMAC_BITS(MDCOPS, PHYTMAC_C45_ADDR) + | PHYTMAC_BITS(PHYADDR, mii_id) + | PHYTMAC_BITS(REGADDR, (regnum >> 16) & 0x1F) + | PHYTMAC_BITS(VALUE, regnum & 0xFFFF) + | PHYTMAC_BITS(CONST, 2))); + phytmac_v2_mdio_idle(pdata); + PHYTMAC_WRITE(pdata, PHYTMAC_MDIO, (PHYTMAC_BITS(CLAUSESEL, PHYTMAC_C45) + | PHYTMAC_BITS(MDCOPS, PHYTMAC_C45_WRITE) + | PHYTMAC_BITS(PHYADDR, mii_id) + | PHYTMAC_BITS(REGADDR, (regnum >> 16) & 0x1F) + | PHYTMAC_BITS(VALUE, data) + | PHYTMAC_BITS(CONST, 2))); + phytmac_v2_mdio_idle(pdata); + + return 0; +} + +static int phytmac_v2_powerup_hw(struct phytmac *pdata, int on) +{ + u32 status, data0, data1, rdata1; + int ret; + acpi_handle handle; + union acpi_object args[3]; + struct acpi_object_list arg_list = { + .pointer = args, + .count = ARRAY_SIZE(args), + }; + acpi_status acpi_sts; + unsigned long long rv; + + if (!(pdata->capacities & PHYTMAC_CAPS_PWCTRL)) { + pdata->power_state = on; + return 0; + } + + if (has_acpi_companion(pdata->dev)) { + handle = ACPI_HANDLE(pdata->dev); + + netdev_info(pdata->ndev, "set gmac power %s\n", + on == PHYTMAC_POWERON ? "on" : "off"); + args[0].type = ACPI_TYPE_INTEGER; + args[0].integer.value = PHYTMAC_PWCTL_GMAC_ID; + args[1].type = ACPI_TYPE_INTEGER; + args[1].integer.value = PHYTMAC_PWCTL_DEFAULT_VAL; + args[2].type = ACPI_TYPE_INTEGER; + args[2].integer.value = PHYTMAC_PWCTL_DEFAULT_VAL; + + if (on == PHYTMAC_POWERON) { + acpi_sts = acpi_evaluate_integer(handle, "PPWO", &arg_list, &rv); + if (ACPI_FAILURE(acpi_sts)) + netdev_err(pdata->ndev, "NO PPWO Method\n"); + if (rv) + netdev_err(pdata->ndev, "Failed to power on\n"); + } else { + acpi_sts = acpi_evaluate_integer(handle, "PPWD", &arg_list, &rv); + if (ACPI_FAILURE(acpi_sts)) + netdev_err(pdata->ndev, "NO PPWD Method\n"); + if (rv) + netdev_err(pdata->ndev, "Failed to power off\n"); + } + } else { + ret = readx_poll_timeout(PHYTMAC_READ_STAT, pdata, status, !status, + 1, PHYTMAC_TIMEOUT); + if (ret) + netdev_err(pdata->ndev, "mnh status is busy"); + + ret = readx_poll_timeout(PHYTMAC_READ_DATA0, pdata, data0, + data0 & PHYTMAC_BIT(DATA0_FREE), + 1, PHYTMAC_TIMEOUT); + if (ret) + netdev_err(pdata->ndev, "mnh data0 is busy"); + + data0 = 0; + data0 = PHYTMAC_SET_BITS(data0, DATA0_MSG, PHYTMAC_MSG_PM); + data0 = PHYTMAC_SET_BITS(data0, DATA0_PRO, PHYTMAC_PRO_ID); + PHYTMAC_MHU_WRITE(pdata, PHYTMAC_MHU_CPP_DATA0, data0); + data1 = 0; + + if (on == PHYTMAC_POWERON) { + data1 = PHYTMAC_SET_BITS(data1, DATA1_STAT, PHYTMAC_STATON); + data1 = PHYTMAC_SET_BITS(data1, DATA1_STATTYPE, PHYTMAC_STATTYPE); + PHYTMAC_MHU_WRITE(pdata, PHYTMAC_MHU_CPP_DATA1, data1); + } else { + data1 = PHYTMAC_SET_BITS(data1, DATA1_STAT, PHYTMAC_STATOFF); + data1 = PHYTMAC_SET_BITS(data1, DATA1_STATTYPE, PHYTMAC_STATTYPE); + PHYTMAC_MHU_WRITE(pdata, PHYTMAC_MHU_CPP_DATA1, data1); + } + + PHYTMAC_MHU_WRITE(pdata, PHYTMAC_MHU_AP_CPP_SET, 1); + ret = readx_poll_timeout(PHYTMAC_READ_DATA0, pdata, data0, + data0 & PHYTMAC_BIT(DATA0_FREE), + 1, PHYTMAC_TIMEOUT); + if (ret) + netdev_err(pdata->ndev, "mnh data0 is busy\n"); + + rdata1 = PHYTMAC_MHU_READ(pdata, PHYTMAC_MHU_CPP_DATA1); + if (rdata1 == data1) + netdev_err(pdata->ndev, "gmac power %s success, data1 = %x, rdata1=%x\n", + on == PHYTMAC_POWERON ? "up" : "down", data1, rdata1); + else + netdev_err(pdata->ndev, "gmac power %s failed, data1 = %x, rdata1=%x\n", + on == PHYTMAC_POWERON ? "up" : "down", data1, rdata1); + } + + pdata->power_state = on; + + return 0; +} + +static int phytmac_v2_set_wake(struct phytmac *pdata, int wake) +{ + u16 cmd_id, cmd_subid; + struct phytmac_wol para; + u32 wol_type = 0; + + if (wake & PHYTMAC_WAKE_MAGIC) + wol_type |= PHYTMAC_BIT(MAGIC); + if (wake & PHYTMAC_WAKE_ARP) + wol_type |= PHYTMAC_BIT(ARP); + if (wake & PHYTMAC_WAKE_UCAST) + wol_type |= PHYTMAC_BIT(UCAST); + if (wake & PHYTMAC_WAKE_MCAST) + wol_type |= PHYTMAC_BIT(MCAST); + + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_WOL; + memset(¶, 0, sizeof(para)); + para.wol_type = cpu_to_le32(wol_type); + para.wake = (u8)wake; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); + + return 0; +} + +static int phytmac_v2_enable_promise(struct phytmac *pdata, int enable) +{ + u16 cmd_id, cmd_subid; + u8 rxcsum = 0; + + cmd_id = PHYTMAC_MSG_CMD_SET; + if (enable) { + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_PROMISE; + } else { + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_PROMISE; + if (pdata->ndev->features & NETIF_F_RXCSUM) + rxcsum = 1; + } + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(&rxcsum), 1, 1); + + return 0; +} + +static int phytmac_v2_enable_rxcsum(struct phytmac *pdata, int enable) +{ + u16 cmd_id, cmd_subid; + + cmd_id = PHYTMAC_MSG_CMD_SET; + if (enable) + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_RXCSUM; + else + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_RXCSUM; + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + return 0; +} + +static int phytmac_v2_enable_txcsum(struct phytmac *pdata, int enable) +{ + u16 cmd_id, cmd_subid; + + cmd_id = PHYTMAC_MSG_CMD_SET; + if (enable) + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_TXCSUM; + else + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_TXCSUM; + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + return 0; +} + +static int phytmac_v2_enable_mdio(struct phytmac *pdata, int enable) +{ + u16 cmd_id, cmd_subid; + u8 mdc; + + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_MDC; + mdc = PHYTMAC_CLK_DIV96; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(&mdc), sizeof(mdc), 0); + + if (enable) + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_MDIO; + else + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_MDIO; + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + return 0; +} + +static int phytmac_v2_enable_autoneg(struct phytmac *pdata, int enable) +{ + u16 cmd_id, cmd_subid; + + cmd_id = PHYTMAC_MSG_CMD_SET; + if (enable) + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_AUTONEG; + else + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_AUTONEG; + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + pdata->autoneg = enable; + return 0; +} + +static int phytmac_v2_enable_pause(struct phytmac *pdata, int enable) +{ + u16 cmd_id, cmd_subid; + + cmd_id = PHYTMAC_MSG_CMD_SET; + if (enable) + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_PAUSE; + else + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_PAUSE; + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + return 0; +} + +static int phytmac_v2_enable_network(struct phytmac *pdata, int enable, int rx_tx) +{ + u16 cmd_id, cmd_subid; + + cmd_id = PHYTMAC_MSG_CMD_SET; + if (enable) + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_NETWORK; + else + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_NETWORK; + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + return 0; +} + +static int phytmac_v2_add_fdir_entry(struct phytmac *pdata, struct ethtool_rx_flow_spec *rx_flow) +{ + struct ethtool_tcpip4_spec *tp4sp_v, *tp4sp_m; + struct phytmac_fdir_info fdir; + u16 cmd_id, cmd_subid; + + memset(&fdir, 0, sizeof(fdir)); + + tp4sp_v = &rx_flow->h_u.tcp_ip4_spec; + tp4sp_m = &rx_flow->m_u.tcp_ip4_spec; + if (tp4sp_m->ip4src == 0xFFFFFFFF) { + fdir.ipsrc_en = true; + fdir.ip4src = tp4sp_v->ip4src; + } + + if (tp4sp_m->ip4dst == 0xFFFFFFFF) { + fdir.ipdst_en = true; + fdir.ip4dst = tp4sp_v->ip4dst; + } + + if (tp4sp_m->psrc == 0xFFFF || tp4sp_m->pdst == 0xFFFF) { + fdir.port_en = true; + fdir.dstport = tp4sp_v->pdst; + fdir.srcport = tp4sp_v->psrc; + fdir.dstport_mask = tp4sp_m->pdst; + fdir.srcport_mask = tp4sp_m->psrc; + } + + fdir.location = (u8)(rx_flow->location); + fdir.queue = (u8)(rx_flow->ring_cookie); + + if (fdir.ipsrc_en || fdir.ipdst_en || fdir.port_en) { + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_ADD_FDIR; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(&fdir), sizeof(fdir), 1); + } + + return 0; +} + +static int phytmac_v2_del_fdir_entry(struct phytmac *pdata, struct ethtool_rx_flow_spec *rx_flow) +{ + struct phytmac_fdir_info fdir; + u16 cmd_id, cmd_subid; + + memset(&fdir, 0, sizeof(fdir)); + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_DEL_FDIR; + fdir.location = (u8)(rx_flow->location); + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(&fdir), sizeof(fdir), 1); + + return 0; +} + +static void phytmac_v2_tx_start(struct phytmac_queue *queue) +{ + struct phytmac *pdata = queue->pdata; + + PHYTMAC_WRITE(pdata, PHYTMAC_TX_PTR(queue->index), queue->tx_tail); + queue->tx_xmit_more = 0; +} + +static u32 phytmac_v2_get_irq_mask(u32 mask) +{ + u32 value = 0; + + value |= (mask & PHYTMAC_INT_TX_COMPLETE) ? PHYTMAC_BIT(TXCOMP) : 0; + value |= (mask & PHYTMAC_INT_TX_ERR) ? PHYTMAC_BIT(DMA_ERR) : 0; + value |= (mask & PHYTMAC_INT_RX_COMPLETE) ? PHYTMAC_BIT(RXCOMP) : 0; + value |= (mask & PHYTMAC_INT_RX_OVERRUN) ? PHYTMAC_BIT(RXOVERRUN) : 0; + value |= (mask & PHYTMAC_INT_RX_DESC_FULL) ? PHYTMAC_BIT(RUSED) : 0; + + return value; +} + +static u32 phytmac_v2_get_irq_status(u32 value) +{ + u32 status = 0; + + status |= (value & PHYTMAC_BIT(TXCOMP)) ? PHYTMAC_INT_TX_COMPLETE : 0; + status |= (value & PHYTMAC_BIT(DMA_ERR)) ? PHYTMAC_INT_TX_ERR : 0; + status |= (value & PHYTMAC_BIT(RXCOMP)) ? PHYTMAC_INT_RX_COMPLETE : 0; + status |= (value & PHYTMAC_BIT(RXOVERRUN)) ? PHYTMAC_INT_RX_OVERRUN : 0; + status |= (value & PHYTMAC_BIT(RUSED)) ? PHYTMAC_INT_RX_DESC_FULL : 0; + + return status; +} + +static void phytmac_v2_enable_irq(struct phytmac *pdata, + int queue_index, u32 mask) +{ + u32 value; + + value = phytmac_v2_get_irq_mask(mask); + PHYTMAC_WRITE(pdata, PHYTMAC_INT_ER(queue_index), value); +} + +static void phytmac_v2_disable_irq(struct phytmac *pdata, + int queue_index, u32 mask) +{ + u32 value; + + value = phytmac_v2_get_irq_mask(mask); + PHYTMAC_WRITE(pdata, PHYTMAC_INT_DR(queue_index), value); +} + +static void phytmac_v2_clear_irq(struct phytmac *pdata, + int queue_index, u32 mask) +{ + u32 value; + + value = phytmac_v2_get_irq_mask(mask); + PHYTMAC_WRITE(pdata, PHYTMAC_INT_SR(queue_index), value); +} + +static unsigned int phytmac_v2_get_irq(struct phytmac *pdata, int queue_index) +{ + u32 status; + u32 value; + + value = PHYTMAC_READ(pdata, PHYTMAC_INT_SR(queue_index)); + status = phytmac_v2_get_irq_status(value); + + return status; +} + +static void phytmac_v2_interface_config(struct phytmac *pdata, unsigned int mode, + const struct phylink_link_state *state) +{ + struct phytmac_interface_info para; + u16 cmd_id, cmd_subid; + u8 autoneg = 0; + + if (state->interface == PHY_INTERFACE_MODE_SGMII) { + if (mode == MLO_AN_FIXED) + autoneg = 0; + else + autoneg = 1; + } + + if (state->interface == PHY_INTERFACE_MODE_1000BASEX) + autoneg = 1; + if (state->interface == PHY_INTERFACE_MODE_2500BASEX) + autoneg = 0; + + memset(¶, 0, sizeof(para)); + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_INIT_MAC_CONFIG; + para.interface = pdata->phytmac_v2_interface; + para.autoneg = autoneg; + para.speed = state->speed; + para.duplex = state->duplex; + pdata->autoneg = para.autoneg; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); +} + +static int phytmac_v2_interface_linkup(struct phytmac *pdata, phy_interface_t interface, + int speed, int duplex) +{ + struct phytmac_interface_info para; + u16 cmd_id, cmd_subid; + + if (interface == PHY_INTERFACE_MODE_SGMII) { + if (speed == SPEED_2500) + pdata->autoneg = 0; + } + + memset(¶, 0, sizeof(para)); + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_MAC_LINK_CONFIG; + para.interface = pdata->phytmac_v2_interface; + para.duplex = duplex; + para.speed = speed; + para.autoneg = pdata->autoneg; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); + + return 0; +} + +static int phytmac_v2_interface_linkdown(struct phytmac *pdata) +{ + return 0; +} + +static int phytmac_v2_pcs_linkup(struct phytmac *pdata, phy_interface_t interface, + int speed, int duplex) +{ + u16 cmd_id, cmd_subid; + + if (interface == PHY_INTERFACE_MODE_USXGMII || + interface == PHY_INTERFACE_MODE_10GBASER) { + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_PCS_LINK_UP; + + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + } + + return 0; +} + +static int phytmac_v2_pcs_linkdown(struct phytmac *pdata) +{ + return 0; +} + +static unsigned int phytmac_v2_pcs_get_link(struct phytmac *pdata, phy_interface_t interface) +{ + if (interface == PHY_INTERFACE_MODE_SGMII || + interface == PHY_INTERFACE_MODE_1000BASEX || + interface == PHY_INTERFACE_MODE_2500BASEX) + return PHYTMAC_READ_BITS(pdata, PHYTMAC_NETWORK_STATUS, LINK); + else if (interface == PHY_INTERFACE_MODE_USXGMII || + interface == PHY_INTERFACE_MODE_10GBASER) + return PHYTMAC_READ_BITS(pdata, PHYTMAC_USX_LINK_STATUS, USX_LINK); + + return 0; +} + +static unsigned int phytmac_v2_tx_map_desc(struct phytmac_queue *queue, + u32 tx_tail, struct packet_info *packet) +{ + unsigned int i, ctrl; + struct phytmac *pdata = queue->pdata; + struct phytmac_dma_desc *desc; + struct phytmac_tx_skb *tx_skb; + unsigned int eof = 1; + + i = tx_tail; + + do { + i--; + tx_skb = phytmac_get_tx_skb(queue, i); + desc = phytmac_get_tx_desc(queue, i); + + ctrl = (u32)tx_skb->length; + if (eof) { + ctrl |= PHYTMAC_BIT(TXLAST); + eof = 0; + } + + if (unlikely(i == (pdata->tx_ring_size - 1))) + ctrl |= PHYTMAC_BIT(TXWRAP); + + if (i == queue->tx_tail) { + ctrl |= PHYTMAC_BITS(TXLSO, packet->lso); + ctrl |= PHYTMAC_BITS(TXTCP_SEQ_SRC, packet->seq); + if (packet->nocrc) + ctrl |= PHYTMAC_BIT(TXNOCRC); + } else { + ctrl |= PHYTMAC_BITS(MSSMFS, packet->mss); + } + + desc->desc2 = upper_32_bits(tx_skb->addr); + desc->desc0 = lower_32_bits(tx_skb->addr); + /* make newly desc1 to hardware */ + wmb(); + desc->desc1 = ctrl; + } while (i != queue->tx_tail); + + return 0; +} + +static void phytmac_v2_init_rx_map_desc(struct phytmac_queue *queue, + u32 index) +{ + struct phytmac_dma_desc *desc; + + desc = phytmac_get_rx_desc(queue, index); + + desc->desc1 = 0; + /* Make newly descriptor to hardware */ + dma_wmb(); + desc->desc0 |= PHYTMAC_BIT(RXUSED); +} + +static unsigned int phytmac_v2_rx_map_desc(struct phytmac_queue *queue, u32 index, dma_addr_t addr) +{ + struct phytmac *pdata = queue->pdata; + struct phytmac_dma_desc *desc; + + desc = phytmac_get_rx_desc(queue, index); + + if (addr) { + if (unlikely(index == (pdata->rx_ring_size - 1))) + addr |= PHYTMAC_BIT(RXWRAP); + desc->desc1 = 0; + desc->desc2 = upper_32_bits(addr); + /* Make newly descriptor to hardware */ + if (!(pdata->capacities & PHYTMAC_CAPS_RXPTR)) + dma_wmb(); + desc->desc0 = lower_32_bits(addr); + } else { + desc->desc1 = 0; + /* make newly descriptor to hardware */ + if (!(pdata->capacities & PHYTMAC_CAPS_RXPTR)) + dma_wmb(); + desc->desc0 &= ~PHYTMAC_BIT(RXUSED); + } + + return 0; +} + +static unsigned int phytmac_v2_zero_rx_desc_addr(struct phytmac_dma_desc *desc) +{ + desc->desc2 = 0; + desc->desc0 = (desc->desc0 & PHYTMAC_BIT(RXTSVALID)) | PHYTMAC_BIT(RXUSED); + + return 0; +} + +static unsigned int phytmac_v2_zero_tx_desc(struct phytmac_dma_desc *desc) +{ + desc->desc2 = 0; + desc->desc0 = 0; + desc->desc1 &= ~PHYTMAC_BIT(TXUSED); + + return 0; +} + +static void phytmac_v2_update_rx_tail(struct phytmac_queue *queue) +{ + struct phytmac *pdata = queue->pdata; + + if (pdata->capacities & PHYTMAC_CAPS_RXPTR) + PHYTMAC_WRITE(pdata, PHYTMAC_RX_PTR(queue->index), queue->rx_head); +} + +static int phytmac_v2_tx_complete(const struct phytmac_dma_desc *desc) +{ + return PHYTMAC_GET_BITS(desc->desc1, TXUSED); +} + +static bool phytmac_v2_rx_complete(const struct phytmac_dma_desc *desc) +{ + dma_addr_t addr; + bool used; + + used = PHYTMAC_GET_BITS(desc->desc0, RXUSED); + addr = ((u64)(desc->desc2) << 32); + addr |= desc->desc0 & 0xfffffff8; + + if (used != 0 && addr != 0) + return true; + else + return false; +} + +static int phytmac_v2_rx_pkt_len(struct phytmac *pdata, const struct phytmac_dma_desc *desc) +{ + if (pdata->capacities & PHYTMAC_CAPS_JUMBO) + return desc->desc1 & PHYTMAC_RXJFRMLEN_MASK; + else + return desc->desc1 & PHYTMAC_RXFRMLEN_MASK; +} + +static bool phytmac_v2_rx_checksum(const struct phytmac_dma_desc *desc) +{ + u32 value = desc->desc1; + u32 check = value >> PHYTMAC_RXCSUM_INDEX & 0x3; + + return (check == PHYTMAC_RXCSUM_IP_TCP || check == PHYTMAC_RXCSUM_IP_UDP); +} + +static bool phytmac_v2_rx_single_buffer(const struct phytmac_dma_desc *desc) +{ + u32 value = desc->desc1; + + return ((value & PHYTMAC_BIT(RXSOF)) && (value & PHYTMAC_BIT(RXEOF))); +} + +static bool phytmac_v2_rx_sof(const struct phytmac_dma_desc *desc) +{ + u32 value = desc->desc1; + + return (value & PHYTMAC_BIT(RXSOF)); +} + +static bool phytmac_v2_rx_eof(const struct phytmac_dma_desc *desc) +{ + u32 value = desc->desc1; + + return (value & PHYTMAC_BIT(RXEOF)); +} + +static void phytmac_v2_clear_rx_desc(struct phytmac_queue *queue, int begin, int end) +{ + unsigned int frag; + unsigned int tmp = end; + struct phytmac_dma_desc *desc; + + if (begin > end) + tmp = end + queue->pdata->rx_ring_size; + + for (frag = begin; frag != tmp; frag++) { + desc = phytmac_get_rx_desc(queue, frag); + desc->desc0 &= ~PHYTMAC_BIT(RXUSED); + } +} + +static void phytmac_v2_clear_tx_desc(struct phytmac_queue *queue) +{ + struct phytmac *pdata = queue->pdata; + struct phytmac_dma_desc *desc = NULL; + struct phytmac_tx_skb *tx_skb = NULL; + int i; + + for (i = 0; i < queue->pdata->tx_ring_size; i++) { + desc = phytmac_get_tx_desc(queue, i); + tx_skb = phytmac_get_tx_skb(queue, i); + desc->desc2 = upper_32_bits(tx_skb->addr); + desc->desc0 = lower_32_bits(tx_skb->addr); + /* make newly desc to hardware */ + wmb(); + desc->desc1 = PHYTMAC_BIT(TXUSED); + } + desc->desc1 |= PHYTMAC_BIT(TXWRAP); + PHYTMAC_WRITE(pdata, PHYTMAC_TX_PTR(queue->index), queue->tx_tail); +} + +static void phytmac_v2_get_time(struct phytmac *pdata, struct timespec64 *ts) +{ + u32 ns, secl, sech; + + ns = PHYTMAC_READ(pdata, PHYTMAC_TIMER_NSEC); + secl = PHYTMAC_READ(pdata, PHYTMAC_TIMER_SEC); + sech = PHYTMAC_READ(pdata, PHYTMAC_TIMER_MSB_SEC); + + ts->tv_nsec = ns; + ts->tv_sec = (((u64)sech << 32) | secl) & TIMER_SEC_MAX_VAL; +} + +static void phytmac_v2_set_time(struct phytmac *pdata, time64_t sec, long nsec) +{ + u32 secl, sech; + + secl = (u32)sec; + sech = (sec >> 32) & (0xffff); + + PHYTMAC_WRITE(pdata, PHYTMAC_TIMER_NSEC, 0); + PHYTMAC_WRITE(pdata, PHYTMAC_TIMER_MSB_SEC, sech); + PHYTMAC_WRITE(pdata, PHYTMAC_TIMER_SEC, secl); + PHYTMAC_WRITE(pdata, PHYTMAC_TIMER_NSEC, nsec); +} + +static void phytmac_v2_clear_time(struct phytmac *pdata) +{ + u32 value; + + pdata->ts_incr.sub_ns = 0; + pdata->ts_incr.ns = 0; + + value = PHYTMAC_READ(pdata, PHYTMAC_TIMER_INCR); + value = PHYTMAC_SET_BITS(value, INCR_NSEC, 0); + PHYTMAC_WRITE(pdata, PHYTMAC_TIMER_INCR, value); + + value = PHYTMAC_READ(pdata, PHYTMAC_TIMER_INCR_SUB_NSEC); + value = PHYTMAC_SET_BITS(value, INCR_SNSEC, 0); + PHYTMAC_WRITE(pdata, PHYTMAC_TIMER_INCR_SUB_NSEC, value); + + PHYTMAC_WRITE(pdata, PHYTMAC_TIMER_ADJUST, 0); +} + +static int phytmac_v2_set_tsmode(struct phytmac *pdata, struct ts_ctrl *ctrl) +{ + u16 cmd_id, cmd_subid; + struct phytmac_ts_config para; + + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_TS_CONFIG; + para.tx_mode = ctrl->tx_control; + para.rx_mode = ctrl->rx_control; + para.one_step = ctrl->one_step; + phytmac_v2_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); + + return 0; +} + +static int phytmac_v2_set_tsincr(struct phytmac *pdata, struct ts_incr *incr) +{ + u32 value; + + value = PHYTMAC_BITS(INCR_SNSEC, incr->sub_ns); + PHYTMAC_WRITE(pdata, PHYTMAC_TIMER_INCR_SUB_NSEC, value); + PHYTMAC_WRITE(pdata, PHYTMAC_TIMER_INCR, incr->ns); + + return 0; +} + +static void phytmac_v2_ptp_init_hw(struct phytmac *pdata) +{ + struct timespec64 ts; + + ts = ns_to_timespec64(ktime_to_ns(ktime_get_real())); + phytmac_v2_set_time(pdata, ts.tv_sec, ts.tv_nsec); + + phytmac_v2_set_tsincr(pdata, &pdata->ts_incr); +} + +static int phytmac_v2_adjust_fine(struct phytmac *pdata, long ppm, bool negative) +{ + struct ts_incr ts_incr; + u32 tmp; + u64 adj; + + ts_incr.ns = pdata->ts_incr.ns; + ts_incr.sub_ns = pdata->ts_incr.sub_ns; + + /* scaling: unused(8bit) | ns(8bit) | fractions(16bit) */ + tmp = ((u64)ts_incr.ns << PHYTMAC_INCR_SNSECL_INDEX) + ts_incr.sub_ns; + adj = ((u64)ppm * tmp + (USEC_PER_SEC >> 1)) >> PHYTMAC_INCR_SNSECL_INDEX; + + adj = div_u64(adj, USEC_PER_SEC); + adj = negative ? (tmp - adj) : (tmp + adj); + + ts_incr.ns = (adj >> PHYTMAC_INCR_SNSEC_WIDTH) + & ((1 << PHYTMAC_INCR_NSEC_WIDTH) - 1); + ts_incr.sub_ns = adj & ((1 << PHYTMAC_INCR_SNSEC_WIDTH) - 1); + + phytmac_v2_set_tsincr(pdata, &ts_incr); + + return 0; +} + +static int phytmac_v2_adjust_time(struct phytmac *pdata, s64 delta, int neg) +{ + u32 adj; + + if (delta > PHYTMAC_ASEC_MAX) { + struct timespec64 now, then; + + if (neg) + then = ns_to_timespec64(-delta); + else + then = ns_to_timespec64(delta); + phytmac_v2_get_time(pdata, &now); + now = timespec64_add(now, then); + phytmac_v2_set_time(pdata, now.tv_sec, now.tv_nsec); + } else { + adj = (neg << PHYTMAC_AADD_INDEX) | delta; + PHYTMAC_WRITE(pdata, PHYTMAC_TIMER_ADJUST, adj); + } + + return 0; +} + +static int phytmac_v2_ts_valid(struct phytmac *pdata, struct phytmac_dma_desc *desc, int direction) +{ + int ts_valid = 0; + + if (direction == PHYTMAC_TX) + ts_valid = desc->desc1 & PHYTMAC_BIT(TXTSVALID); + else if (direction == PHYTMAC_RX) + ts_valid = desc->desc0 & PHYTMAC_BIT(RXTSVALID); + return ts_valid; +} + +static void phytmac_v2_get_dma_ts(struct phytmac *pdata, u32 ts_1, u32 ts_2, struct timespec64 *ts) +{ + struct timespec64 ts2; + + ts->tv_sec = (PHYTMAC_GET_BITS(ts_2, TS_SECH) << 2) | + PHYTMAC_GET_BITS(ts_1, TS_SECL); + ts->tv_nsec = PHYTMAC_GET_BITS(ts_1, TS_NSEC); + + phytmac_v2_get_time(pdata, &ts2); + + if (((ts->tv_sec ^ ts2.tv_sec) & (PHYTMAC_TS_SEC_TOP >> 1)) != 0) + ts->tv_sec -= PHYTMAC_TS_SEC_TOP; + + ts->tv_sec += (ts2.tv_sec & (~PHYTMAC_TS_SEC_MASK)); +} + +static unsigned int phytmac_v2_get_ts_rate(struct phytmac *pdata) +{ + return 300000000; +} + +struct phytmac_hw_if phytmac_2p0_hw = { + .init_msg_ring = phytmac_v2_init_msg_ring, + .reset_hw = phytmac_v2_reset_hw, + .init_hw = phytmac_v2_init_hw, + .init_ring_hw = phytmac_v2_init_ring_hw, + .get_feature = phytmac_v2_get_feature_all, + .get_regs = phytmac_v2_get_regs, + .get_stats = phytmac_v2_get_hw_stats, + .set_mac_address = phytmac_v2_set_mac_addr, + .get_mac_address = phytmac_v2_get_mac_addr, + .mdio_idle = phytmac_v2_mdio_idle, + .mdio_read = phytmac_v2_mdio_data_read_c22, + .mdio_write = phytmac_v2_mdio_data_write_c22, + .mdio_read_c45 = phytmac_v2_mdio_data_read_c45, + .mdio_write_c45 = phytmac_v2_mdio_data_write_c45, + .poweron = phytmac_v2_powerup_hw, + .set_wol = phytmac_v2_set_wake, + .enable_promise = phytmac_v2_enable_promise, + .enable_multicast = phytmac_v2_enable_multicast, + .set_hash_table = phytmac_v2_set_mc_hash, + .enable_rx_csum = phytmac_v2_enable_rxcsum, + .enable_tx_csum = phytmac_v2_enable_txcsum, + .enable_mdio_control = phytmac_v2_enable_mdio, + .enable_autoneg = phytmac_v2_enable_autoneg, + .enable_pause = phytmac_v2_enable_pause, + .enable_network = phytmac_v2_enable_network, + .add_fdir_entry = phytmac_v2_add_fdir_entry, + .del_fdir_entry = phytmac_v2_del_fdir_entry, + + /* mac config */ + .mac_config = phytmac_v2_interface_config, + .mac_linkup = phytmac_v2_interface_linkup, + .mac_linkdown = phytmac_v2_interface_linkdown, + .pcs_linkup = phytmac_v2_pcs_linkup, + .pcs_linkdown = phytmac_v2_pcs_linkdown, + .get_link = phytmac_v2_pcs_get_link, + + /* irq */ + .enable_irq = phytmac_v2_enable_irq, + .disable_irq = phytmac_v2_disable_irq, + .clear_irq = phytmac_v2_clear_irq, + .get_irq = phytmac_v2_get_irq, + + /* tx and rx */ + .tx_map = phytmac_v2_tx_map_desc, + .transmit = phytmac_v2_tx_start, + .update_rx_tail = phytmac_v2_update_rx_tail, + .tx_complete = phytmac_v2_tx_complete, + .rx_complete = phytmac_v2_rx_complete, + .get_rx_pkt_len = phytmac_v2_rx_pkt_len, + .init_rx_map = phytmac_v2_init_rx_map_desc, + .rx_map = phytmac_v2_rx_map_desc, + .rx_checksum = phytmac_v2_rx_checksum, + .rx_single_buffer = phytmac_v2_rx_single_buffer, + .rx_pkt_start = phytmac_v2_rx_sof, + .rx_pkt_end = phytmac_v2_rx_eof, + .clear_rx_desc = phytmac_v2_clear_rx_desc, + .clear_tx_desc = phytmac_v2_clear_tx_desc, + .zero_rx_desc_addr = phytmac_v2_zero_rx_desc_addr, + .zero_tx_desc = phytmac_v2_zero_tx_desc, + + /* ptp */ + .init_ts_hw = phytmac_v2_ptp_init_hw, + .set_time = phytmac_v2_set_time, + .clear_time = phytmac_v2_clear_time, + .get_time = phytmac_v2_get_time, + .set_ts_config = phytmac_v2_set_tsmode, + .set_incr = phytmac_v2_set_tsincr, + .adjust_fine = phytmac_v2_adjust_fine, + .adjust_time = phytmac_v2_adjust_time, + .ts_valid = phytmac_v2_ts_valid, + .get_timestamp = phytmac_v2_get_dma_ts, + .get_ts_rate = phytmac_v2_get_ts_rate, +}; +EXPORT_SYMBOL_GPL(phytmac_2p0_hw); diff --git a/drivers/net/ethernet/phytium/phytmac_v2.h b/drivers/net/ethernet/phytium/phytmac_v2.h new file mode 100644 index 00000000000000..c9e159b1eb9b64 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_v2.h @@ -0,0 +1,441 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright(c) 2022 - 2025 Phytium Technology Co., Ltd. */ + +#ifndef _PHYTMAC_V2_H +#define _PHYTMAC_V2_H + +extern struct phytmac_hw_if phytmac_2p0_hw; + +#define PHYTMAC_CMD_PRC_SUCCESS 0x1 +#define PHYTMAC_MSG_SRAM_SIZE 4096 +#define PHYTMAC_MSG_HDR_LEN 8 +#define PHYTMAC_MSG_PARA_LEN 56 +#define PHYTMAC_READ_REG_NUM_MAX (PHYTMAC_MSG_PARA_LEN / sizeof(u32)) + +#define PHYTMAC_TX_MSG_HEAD 0x000 +#define PHYTMAC_TX_MSG_TAIL 0x004 +#define PHYTMAC_RX_MSG_HEAD 0x008 +#define PHYTMAC_RX_MSG_TAIL 0x00c +#define PHYTMAC_MSG_IMR 0x020 +#define PHYTMAC_MSG_ISR 0x02c +#define PHYTMAC_TAILPTR_ENABLE 0x038 + +#define PHYTMAC_SIZE 0x0048 +#define PHYTMAC_NETWORK_STATUS 0x0240 +#define PHYTMAC_PCS_AN_LP 0x0244 +#define PHYTMAC_USX_LINK_STATUS 0x0248 +#define PHYTMAC_MDIO 0x0264 +#define PHYTMAC_TIMER_INCR_SUB_NSEC 0x024c +#define PHYTMAC_TIMER_INCR 0x0250 +#define PHYTMAC_TIMER_MSB_SEC 0x0254 +#define PHYTMAC_TIMER_SEC 0x0258 +#define PHYTMAC_TIMER_NSEC 0x025c +#define PHYTMAC_TIMER_ADJUST 0x0260 +#define PHYTMAC_MSG(i) ((i) * sizeof(struct phytmac_msg_info)) +#define PHYTMAC_OCT_TX 0x400 + +#define PHYTMAC_MODULE_ID_GMAC 0x60 +#define PHYTMAC_FLAGS_MSG_COMP 0x1 +#define PHYTMAC_FLAGS_MSG_NOINT 0x2 + +/* Bitfields in PHYTMAC_TX_MSG_TAIL */ +#define PHYTMAC_TX_MSG_INT_INDEX 16 +#define PHYTMAC_TX_MSG_INT_WIDTH 1 + +/* Bitfields in PHYTMAC_MSG_ISR */ +#define PHYTMAC_MSG_COMPLETE_INDEX 0 +#define PHYTMAC_MSG_COMPLETE_WIDTH 1 + +/* Bitfields in PHYTMAC_TAILPTR_ENABLE */ +#define PHYTMAC_TXTAIL_EN_INDEX 0 /* Enable tx tail */ +#define PHYTMAC_TXTAIL_EN_WIDTH 1 +#define PHYTMAC_RXTAIL_EN_INDEX 16 /* Enable rx tail */ +#define PHYTMAC_RXTAIL_EN_WIDTH 1 + +/* Bitfields in PHYTMAC_SIZE */ +#define PHYTMAC_MEM_SIZE_INDEX 0 +#define PHYTMAC_MEM_SIZE_WIDTH 4 +#define PHYTMAC_TXRING_SIZE_INDEX 8 +#define PHYTMAC_TXRING_SIZE_WIDTH 6 + +/* Bitfields in PHYTMAC_TIMER_INCR_SUB_NSEC */ +#define PHYTMAC_INCR_SNSECH_INDEX 0 +#define PHYTMAC_INCR_SNSECH_WIDTH 16 +#define PHYTMAC_INCR_SNSECL_INDEX 24 +#define PHYTMAC_INCR_SNSECL_WIDTH 8 +#define PHYTMAC_INCR_SNSEC_WIDTH 24 + +/* Bitfields in PHYTMAC_TIMER_INCR_SUB_NSEC */ +#define PHYTMAC_INCR_SNSEC_INDEX 0 +#define PHYTMAC_INCR_SNSEC_WIDTH 24 + +/* Bitfields in PHYTMAC_TIMER_INCR */ +#define PHYTMAC_INCR_NSEC_INDEX 0 +#define PHYTMAC_INCR_NSEC_WIDTH 8 + +/* Bitfields in PHYTMAC_TIMER_MSB_SEC */ +#define PHYTMAC_TIMER_SECH_INDEX 0 +#define PHYTMAC_TIMER_SECH_WIDTH 16 + +/* Bitfields in PHYTMAC_TIMER_SEC */ +#define PHYTMAC_TIMER_SECL_INDEX 0 +#define PHYTMAC_TIMER_SECL_WIDTH 32 + +/* Bitfields in PHYTMAC_TIMER_NSEC */ +#define PHYTMAC_TIMER_NSEC_INDEX 0 +#define PHYTMAC_TIMER_NSEC_WIDTH 30 + +/* Bitfields in PHYTMAC_TIMER_ADJUST */ +#define PHYTMAC_ASEC_INDEX 0 +#define PHYTMAC_ASEC_WIDTH 30 +#define PHYTMAC_AADD_INDEX 31 +#define PHYTMAC_AADD_WIDTH 1 +#define PHYTMAC_ASEC_MAX ((1 << PHYTMAC_ASEC_WIDTH) - 1) + +#define PHYTMAC_TIMER_SEC_WIDTH (PHYTMAC_TIMER_SECH_WIDTH + PHYTMAC_TIMER_SECL_WIDTH) +#define TIMER_SEC_MAX_VAL (((u64)1 << PHYTMAC_TIMER_SEC_WIDTH) - 1) +#define TIMER_NSEC_MAX_VAL ((1 << PHYTMAC_TIMER_NSEC_WIDTH) - 1) + +#define PHYTMAC_TX_PTR(i) (0x0100 + ((i) * 4)) +#define PHYTMAC_RX_PTR(i) (0x0030 + ((i) * 4)) +#define PHYTMAC_INT_ER(i) (0x0140 + ((i) * 4)) +#define PHYTMAC_INT_DR(i) (0x0180 + ((i) * 4)) +#define PHYTMAC_INT_MR(i) (0x01c0 + ((i) * 4)) +#define PHYTMAC_INT_SR(i) (0x0200 + ((i) * 4)) + +#define PHYTMAC_LINK_INDEX 0 /* PCS link status */ +#define PHYTMAC_LINK_WIDTH 1 +#define PHYTMAC_MIDLE_INDEX 2 /* Mdio idle */ +#define PHYTMAC_MIDLE_WIDTH 1 + +/* Int stauts/Enable/Disable/Mask Register */ +#define PHYTMAC_RXCOMP_INDEX 1 /* Rx complete */ +#define PHYTMAC_RXCOMP_WIDTH 1 +#define PHYTMAC_RUSED_INDEX 2 /* Rx used bit read */ +#define PHYTMAC_RUSED_WIDTH 1 +#define PHYTMAC_DMA_ERR_INDEX 6 /* AMBA error */ +#define PHYTMAC_DMA_ERR_WIDTH 1 +#define PHYTMAC_TXCOMP_INDEX 7 /* Tx complete */ +#define PHYTMAC_TXCOMP_WIDTH 1 +#define PHYTMAC_RXOVERRUN_INDEX 10 /* Rx overrun */ +#define PHYTMAC_RXOVERRUN_WIDTH 1 +#define PHYTMAC_RESP_ERR_INDEX 11 /* Resp not ok */ +#define PHYTMAC_RESP_ERR_WIDTH 1 + +/* pcs an lp */ +#define PHYTMAC_AUTO_NEG_INDEX 12 +#define PHYTMAC_AUTO_NEG_WIDTH 1 + +/* Bitfields in USX_STATUS. */ +#define PHYTMAC_USX_LINK_INDEX 0 +#define PHYTMAC_USX_LINK_WIDTH 1 + +/* Mdio read/write Register */ +#define PHYTMAC_VALUE_INDEX 0 /* value */ +#define PHYTMAC_VALUE_WIDTH 16 +#define PHYTMAC_CONST_INDEX 16 /* Must Be 10 */ +#define PHYTMAC_CONST_WIDTH 2 +#define PHYTMAC_REGADDR_INDEX 18 /* Register address */ +#define PHYTMAC_REGADDR_WIDTH 5 +#define PHYTMAC_PHYADDR_INDEX 23 /* Phy address */ +#define PHYTMAC_PHYADDR_WIDTH 5 +#define PHYTMAC_MDCOPS_INDEX 28 +#define PHYTMAC_MDCOPS_WIDTH 2 +#define PHYTMAC_CLAUSESEL_INDEX 30 +#define PHYTMAC_CLAUSESEL_WIDTH 1 +#define PHYTMAC_C22 1 +#define PHYTMAC_C45 0 +#define PHYTMAC_C45_ADDR 0 +#define PHYTMAC_C45_WRITE 1 +#define PHYTMAC_C45_READ 3 +#define PHYTMAC_C22_WRITE 1 +#define PHYTMAC_C22_READ 2 + +/* rx dma desc bit */ +/* DMA descriptor bitfields */ +#define PHYTMAC_RXUSED_INDEX 0 +#define PHYTMAC_RXUSED_WIDTH 1 +#define PHYTMAC_RXWRAP_INDEX 1 +#define PHYTMAC_RXWRAP_WIDTH 1 +#define PHYTMAC_RXTSVALID_INDEX 2 +#define PHYTMAC_RXTSVALID_WIDTH 1 +#define PHYTMAC_RXWADDR_INDEX 2 +#define PHYTMAC_RXWADDR_WIDTH 30 + +#define PHYTMAC_RXFRMLEN_INDEX 0 +#define PHYTMAC_RXFRMLEN_WIDTH 12 +#define PHYTMAC_RXINDEX_INDEX 12 +#define PHYTMAC_RXINDEX_WIDTH 2 +#define PHYTMAC_RXSOF_INDEX 14 +#define PHYTMAC_RXSOF_WIDTH 1 +#define PHYTMAC_RXEOF_INDEX 15 +#define PHYTMAC_RXEOF_WIDTH 1 + +#define PHYTMAC_RXFRMLEN_MASK 0x1FFF +#define PHYTMAC_RXJFRMLEN_MASK 0x3FFF + +#define PHYTMAC_RXTYPEID_MATCH_INDEX 22 +#define PHYTMAC_RXTYPEID_MATCH_WIDTH 2 +#define PHYTMAC_RXCSUM_INDEX 22 +#define PHYTMAC_RXCSUM_WIDTH 2 + +/* Buffer descriptor constants */ +#define PHYTMAC_RXCSUM_NONE 0 +#define PHYTMAC_RXCSUM_IP 1 +#define PHYTMAC_RXCSUM_IP_TCP 2 +#define PHYTMAC_RXCSUM_IP_UDP 3 + +#define PHYTMAC_TXFRMLEN_INDEX 0 +#define PHYTMAC_TXFRMLEN_WIDTH 14 +#define PHYTMAC_TXLAST_INDEX 15 +#define PHYTMAC_TXLAST_WIDTH 1 +#define PHYTMAC_TXNOCRC_INDEX 16 +#define PHYTMAC_TXNOCRC_WIDTH 1 +#define PHYTMAC_MSSMFS_INDEX 16 +#define PHYTMAC_MSSMFS_WIDTH 14 +#define PHYTMAC_TXLSO_INDEX 17 +#define PHYTMAC_TXLSO_WIDTH 2 +#define PHYTMAC_TXTCP_SEQ_SRC_INDEX 19 +#define PHYTMAC_TXTCP_SEQ_SRC_WIDTH 1 +#define PHYTMAC_TXTSVALID_INDEX 23 +#define PHYTMAC_TXTSVALID_WIDTH 1 +#define PHYTMAC_TXWRAP_INDEX 30 +#define PHYTMAC_TXWRAP_WIDTH 1 +#define PHYTMAC_TXUSED_INDEX 31 +#define PHYTMAC_TXUSED_WIDTH 1 + +/* dma ts */ +#define PHYTMAC_TS_NSEC_INDEX 0 +#define PHYTMAC_TS_NSEC_WIDTH 30 +#define PHYTMAC_TS_SECL_INDEX 30 +#define PHYTMAC_TS_SECL_WIDTH 2 +#define PHYTMAC_TS_SECH_INDEX 0 +#define PHYTMAC_TS_SECH_WIDTH 4 +#define PHYTMAC_TS_SEC_MASK 0x3f +#define PHYTMAC_TS_SEC_TOP 0x40 + +/* WOL register */ +#define PHYTMAC_ARP_IP_INDEX 0 +#define PHYTMAC_ARP_IP_WIDTH 16 +#define PHYTMAC_MAGIC_INDEX 16 +#define PHYTMAC_MAGIC_WIDTH 1 +#define PHYTMAC_ARP_INDEX 17 +#define PHYTMAC_ARP_WIDTH 1 +#define PHYTMAC_UCAST_INDEX 18 +#define PHYTMAC_UCAST_WIDTH 1 +#define PHYTMAC_MCAST_INDEX 19 +#define PHYTMAC_MCAST_WIDTH 1 + +#define HW_DMA_CAP_64B 0x1 +#define HW_DMA_CAP_CSUM 0x2 +#define HW_DMA_CAP_PTP 0x4 +#define HW_DMA_CAP_DDW32 0x8 +#define HW_DMA_CAP_DDW64 0x10 +#define HW_DMA_CAP_DDW128 0x20 + +#define PHYTMAC_DBW32 1 +#define PHYTMAC_DBW64 2 +#define PHYTMAC_DBW128 4 + +#define PHYTMAC_CLK_DIV8 0 +#define PHYTMAC_CLK_DIV16 1 +#define PHYTMAC_CLK_DIV32 2 +#define PHYTMAC_CLK_DIV48 3 +#define PHYTMAC_CLK_DIV64 4 +#define PHYTMAC_CLK_DIV96 5 +#define PHYTMAC_CLK_DIV128 6 +#define PHYTMAC_CLK_DIV224 7 + +#define PHYTMAC_RETRY_TIMES 50000 + +#define PHYTMAC_READ_NSR(pdata) PHYTMAC_READ(pdata, PHYTMAC_NETWORK_STATUS) + +enum phytmac_msg_cmd_id { + PHYTMAC_MSG_CMD_DEFAULT = 0, + PHYTMAC_MSG_CMD_SET, + PHYTMAC_MSG_CMD_GET, + PHYTMAC_MSG_CMD_DATA, + PHYTMAC_MSG_CMD_REPORT, +}; + +enum phytmac_default_subid { + PHYTMAC_MSG_CMD_DEFAULT_RESET_HW = 1, + PHYTMAC_MSG_CMD_DEFAULT_RESET_TX_QUEUE, + PHYTMAC_MSG_CMD_DEFAULT_RESET_RX_QUEUE, +}; + +enum phytmac_set_subid { + PHYTMAC_MSG_CMD_SET_INIT_ALL = 0, + PHYTMAC_MSG_CMD_SET_INIT_RING = 1, + PHYTMAC_MSG_CMD_SET_INIT_TX_RING = 2, + PHYTMAC_MSG_CMD_SET_INIT_RX_RING = 3, + PHYTMAC_MSG_CMD_SET_INIT_MAC_CONFIG = 4, + PHYTMAC_MSG_CMD_SET_ADDR = 5, + PHYTMAC_MSG_CMD_SET_DMA_RX_BUFSIZE = 6, + PHYTMAC_MSG_CMD_SET_DMA = 7, + PHYTMAC_MSG_CMD_SET_CAPS = 8, + PHYTMAC_MSG_CMD_SET_TS_CONFIG = 9, + PHYTMAC_MSG_CMD_SET_INIT_TX_ENABLE_TRANSMIT = 10, + PHYTMAC_MSG_CMD_SET_INIT_RX_ENABLE_RECEIVE = 11, + PHYTMAC_MSG_CMD_SET_ENABLE_NETWORK = 12, + PHYTMAC_MSG_CMD_SET_DISABLE_NETWORK = 13, + PHYTMAC_MSG_CMD_SET_ENABLE_MDIO = 14, + PHYTMAC_MSG_CMD_SET_DISABLE_MDIO = 15, + PHYTMAC_MSG_CMD_SET_ENABLE_TXCSUM = 16, + PHYTMAC_MSG_CMD_SET_DISABLE_TXCSUM = 17, + PHYTMAC_MSG_CMD_SET_ENABLE_RXCSUM = 18, + PHYTMAC_MSG_CMD_SET_DISABLE_RXCSUM = 19, + PHYTMAC_MSG_CMD_SET_ENABLE_PROMISE = 20, + PHYTMAC_MSG_CMD_SET_DISABLE_PROMISE = 21, + PHYTMAC_MSG_CMD_SET_ENABLE_MC = 22, + PHYTMAC_MSG_CMD_SET_DISABLE_MC = 23, + PHYTMAC_MSG_CMD_SET_ENABLE_HASH_MC = 24, + PHYTMAC_MSG_CMD_SET_ENABLE_PAUSE = 25, + PHYTMAC_MSG_CMD_SET_DISABLE_PAUSE = 26, + PHYTMAC_MSG_CMD_SET_ENABLE_JUMBO = 27, + PHYTMAC_MSG_CMD_SET_DISABLE_JUMBO = 28, + PHYTMAC_MSG_CMD_SET_ENABLE_1536_FRAMES = 29, + PHYTMAC_MSG_CMD_SET_ENABLE_STRIPCRC = 30, + PHYTMAC_MSG_CMD_SET_DISABLE_STRIPCRC = 31, + PHYTMAC_MSG_CMD_SET_PCS_LINK_UP = 32, + PHYTMAC_MSG_CMD_SET_PCS_LINK_DOWN = 33, + PHYTMAC_MSG_CMD_SET_MAC_LINK_CONFIG = 34, + PHYTMAC_MSG_CMD_SET_REG_WRITE = 35, + PHYTMAC_MSG_CMD_SET_ENABLE_BC = 36, + PHYTMAC_MSG_CMD_SET_DISABLE_BC = 37, + PHYTMAC_MSG_CMD_SET_ETH_MATCH = 38, + PHYTMAC_MSG_CMD_SET_ADD_FDIR = 39, + PHYTMAC_MSG_CMD_SET_DEL_FDIR = 40, + PHYTMAC_MSG_CMD_SET_ENABLE_AUTONEG = 41, + PHYTMAC_MSG_CMD_SET_DISABLE_AUTONEG = 42, + PHYTMAC_MSG_CMD_SET_RX_DATA_OFFSET = 43, + PHYTMAC_MSG_CMD_SET_WOL = 44, + PHYTMAC_MSG_CMD_SET_ENABLE_RSC = 45, + PHYTMAC_MSG_CMD_SET_DISABLE_RSC = 46, + PHYTMAC_MSG_CMD_SET_ENABLE_TX_START = 47, + PHYTMAC_MSG_CMD_SET_ENABLE_PCS_RESET = 48, + PHYTMAC_MSG_CMD_SET_DISABLE_PCS_RESET = 49, + PHYTMAC_MSG_CMD_SET_MDC = 50, + PHYTMAC_MSG_CMD_SET_OUTSTANDING = 51, +}; + +enum phytmac_get_subid { + PHYTMAC_MSG_CMD_GET_ADDR, + PHYTMAC_MSG_CMD_GET_QUEUENUMS, + PHYTMAC_MSG_CMD_GET_CAPS, + PHYTMAC_MSG_CMD_GET_BD_PREFETCH, + PHYTMAC_MSG_CMD_GET_STATS, + PHYTMAC_MSG_CMD_GET_REG_READ, + PHYTMAC_MSG_CMD_GET_RX_FLOW, + PHYTMAC_MSG_CMD_GET_REGS_FOR_ETHTOOL, +}; + +struct phytmac_interface_info { + u8 interface; + u8 autoneg; + u16 duplex; + u32 speed; +} __packed; + +struct phytmac_mc_info { + u32 mc_bottom; + u32 mc_top; +} __packed; + +struct phytmac_reg_info { + u32 offset; + u16 regnum; +} __packed; + +struct phytmac_fdir_info { + u32 ip4src; + u32 ip4dst; + u16 srcport; + u16 srcport_mask; + u16 dstport; + u16 dstport_mask; + u8 location; + u8 queue; + u8 ipsrc_en; + u8 ipdst_en; + u8 port_en; +} __packed; + +struct phytmac_ts_config { + u8 tx_mode; + u8 rx_mode; + u8 one_step; +} __packed; + +struct phytmac_ring_info { + u64 addr[4]; + u8 queue_num; + u8 hw_dma_cap; +} __packed; + +struct phytmac_rxbuf_info { + u8 queue_num; + u8 buffer_size; +} __packed; + +struct phytmac_dma_info { + u16 dma_burst_length; + u8 hw_dma_cap; +} __packed; + +struct phytmac_eth_info { + u16 index; + u16 etype; +} __packed; + +struct phytmac_mac { + u32 addrl; + u16 addrh; +} __packed; + +struct phytmac_feature { + u8 irq_read_clear; + u8 dma_data_width; + u8 dma_addr_width; + u8 tx_pkt_buffer; + u8 rx_pkt_buffer; + u8 pbuf_lso; + u8 queue_num; + u8 tx_bd_prefetch; + u8 rx_bd_prefetch; + u8 max_rx_fs; +} __packed; + +struct phytmac_wol { + u32 wol_type; + u8 wake; +} __packed; + +struct phytmac_msg_info { + u8 reserved; + u8 seq; + u8 cmd_type; + u8 cmd_subid; + u16 len; + u8 status1; + u8 status0; + u8 para[PHYTMAC_MSG_PARA_LEN]; +} __packed; + +struct phytmac_ots_config { + u32 axi_rd; + u32 axi_wr; + u8 queuenum; +} __packed; + +struct phytmac_ethtool_reg { + u8 interface; + u8 cnt; +} __packed; + +static inline unsigned int phytmac_v2_tx_ring_wrap(struct phytmac *pdata, unsigned int index) +{ + return index & (pdata->msg_ring.tx_msg_ring_size - 1); +} + +#endif diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c index 791277e750bad4..41f03d972edda8 100644 --- a/drivers/net/ethernet/realtek/r8169_main.c +++ b/drivers/net/ethernet/realtek/r8169_main.c @@ -1273,6 +1273,34 @@ static int r8168g_mdio_read(struct rtl8169_private *tp, int reg) return r8168_phy_ocp_read(tp, tp->ocp_base + reg * 2); } +/* The quirk reflects RTL8116af SerDes status. */ +static int r8116af_mdio_read_quirk(struct rtl8169_private *tp, int reg) +{ + u8 phyStatus = RTL_R8(tp, PHYstatus); + + if (!(phyStatus & LinkStatus)) + return 0; + + /* BMSR */ + if (tp->ocp_base == OCP_STD_PHY_BASE && reg == MII_BMSR) + return BMSR_ANEGCOMPLETE | BMSR_LSTATUS; + + /* PHYSR */ + if (tp->ocp_base == 0xa430 && reg == 0x12) + { if (phyStatus & _1000bpsF) + return 0x0028; + else if (phyStatus & _100bps) + return 0x0018; + } + + return 0; +} + +static int r8116af_mdio_read(struct rtl8169_private *tp, int reg) +{ + return r8168g_mdio_read(tp, reg) | r8116af_mdio_read_quirk(tp, reg); +} + static void mac_mcu_write(struct rtl8169_private *tp, int reg, int value) { if (reg == 0x1f) { @@ -1366,6 +1394,13 @@ static int r8168dp_2_mdio_read(struct rtl8169_private *tp, int reg) return value; } +static bool rtl_is_8116af(struct rtl8169_private *tp) +{ + return tp->mac_version == RTL_GIGA_MAC_VER_52 && + (r8168_mac_ocp_read(tp, 0xdc00) & 0x0078) == 0x0030 && + (r8168_mac_ocp_read(tp, 0xd006) & 0x00ff) == 0x0000; +} + static void rtl_writephy(struct rtl8169_private *tp, int location, int val) { switch (tp->mac_version) { @@ -1389,7 +1424,10 @@ static int rtl_readphy(struct rtl8169_private *tp, int location) case RTL_GIGA_MAC_VER_31: return r8168dp_2_mdio_read(tp, location); case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_LAST: - return r8168g_mdio_read(tp, location); + if (rtl_is_8116af(tp)) + return r8116af_mdio_read(tp, location); + else + return r8168g_mdio_read(tp, location); default: return r8169_mdio_read(tp, location); } diff --git a/drivers/net/ethernet/spacemit/k1_emac.c b/drivers/net/ethernet/spacemit/k1_emac.c index f7f16397a2c2ac..02a009afc921a8 100644 --- a/drivers/net/ethernet/spacemit/k1_emac.c +++ b/drivers/net/ethernet/spacemit/k1_emac.c @@ -446,12 +446,12 @@ static void emac_free_tx_resources(struct emac_priv *priv) emac_clean_tx_desc_ring(priv); - kfree(tr->tx_desc_buf); - tr->tx_desc_buf = NULL; - dma_free_coherent(dev, tr->total_size, tr->desc_addr, tr->desc_dma_addr); tr->desc_addr = NULL; + + kfree(tr->tx_desc_buf); + tr->tx_desc_buf = NULL; } static void emac_free_rx_resources(struct emac_priv *priv) @@ -461,12 +461,12 @@ static void emac_free_rx_resources(struct emac_priv *priv) emac_clean_rx_desc_ring(priv); - kfree(rr->rx_desc_buf); - rr->rx_desc_buf = NULL; - dma_free_coherent(dev, rr->total_size, rr->desc_addr, rr->desc_dma_addr); rr->desc_addr = NULL; + + kfree(rr->rx_desc_buf); + rr->rx_desc_buf = NULL; } static int emac_tx_clean_desc(struct emac_priv *priv) diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig index e3dd5adda5acaf..8fa5343bf65bc5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig @@ -142,6 +142,16 @@ config DWMAC_NUVOTON for the stmmac device driver. The nuvoton-dwmac driver is used for MA35 series SoCs. +config DWMAC_PHYTIUM + tristate "Phytium dwmac support" + default ARCH_PHYTIUM + depends on (OF || ACPI) && (ARCH_PHYTIUM || COMPILE_TEST) + help + Support for GMAC controller on Phytium SoCs. + + This selects the Phytium GMAC glue layer support for the + stmmac device driver. + config DWMAC_QCOM_ETHQOS tristate "Qualcomm ETHQOS support" default ARCH_QCOM diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile index a1cea2f57252e6..a5b812a44701a1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Makefile +++ b/drivers/net/ethernet/stmicro/stmmac/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_DWMAC_LPC18XX) += dwmac-lpc18xx.o obj-$(CONFIG_DWMAC_MEDIATEK) += dwmac-mediatek.o obj-$(CONFIG_DWMAC_MESON) += dwmac-meson.o dwmac-meson8b.o obj-$(CONFIG_DWMAC_NUVOTON) += dwmac-nuvoton.o +obj-$(CONFIG_DWMAC_PHYTIUM) += dwmac-phytium.o obj-$(CONFIG_DWMAC_QCOM_ETHQOS) += dwmac-qcom-ethqos.o obj-$(CONFIG_DWMAC_RENESAS_GBETH) += dwmac-renesas-gbeth.o obj-$(CONFIG_DWMAC_ROCKCHIP) += dwmac-rk.o diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 927ea6230073f4..4446f7f66d811d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -59,10 +59,10 @@ static inline bool dwmac_is_xmac(enum dwmac_core_type core_type) */ #define DMA_MIN_TX_SIZE 64 #define DMA_MAX_TX_SIZE 1024 -#define DMA_DEFAULT_TX_SIZE 512 +#define DMA_DEFAULT_TX_SIZE 1024 #define DMA_MIN_RX_SIZE 64 #define DMA_MAX_RX_SIZE 1024 -#define DMA_DEFAULT_RX_SIZE 512 +#define DMA_DEFAULT_RX_SIZE 1024 #define STMMAC_NEXT_ENTRY(x, size) ((x + 1) & (size - 1)) #undef FRAME_FILTER_DEBUG diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-phytium.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-phytium.c new file mode 100644 index 00000000000000..3c19f212704195 --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-phytium.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium DWMAC specific glue layer + * + * Copyright (c) 2022-2024 Phytium Technology Co., Ltd. + * + * Chen Baozi + */ + +#include +#include +#include +#include +#include +#include + +#include "stmmac.h" +#include "stmmac_platform.h" + +static int phytium_get_mac_mode(struct fwnode_handle *fwnode) +{ + const char *pm; + int err, i; + + err = fwnode_property_read_string(fwnode, "mac-mode", &pm); + if (err < 0) + return err; + + for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++) { + if (!strcasecmp(pm, phy_modes(i))) + return i; + } + + return -ENODEV; +} + +static int phytium_dwmac_acpi_phy(struct plat_stmmacenet_data *plat, + struct fwnode_handle *np, struct device *dev) +{ + plat->mdio_bus_data = devm_kzalloc(dev, sizeof(struct stmmac_mdio_bus_data), GFP_KERNEL); + + if (!plat->mdio_bus_data) + return -ENOMEM; + + return 0; +} + +static int phytium_dwmac_probe(struct platform_device *pdev) +{ + struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev); + struct plat_stmmacenet_data *plat; + struct stmmac_resources stmmac_res; + struct device_node *np = pdev->dev.of_node; + u64 clk_freq; + char clk_name[20]; + int ret; + + plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL); + if (!plat) + return -ENOMEM; + + plat->dma_cfg = devm_kzalloc(&pdev->dev, sizeof(*plat->dma_cfg), GFP_KERNEL); + if (!plat->dma_cfg) + return -ENOMEM; + + plat->axi = devm_kzalloc(&pdev->dev, sizeof(*plat->axi), GFP_KERNEL); + if (!plat->axi) + return -ENOMEM; + + plat->phy_interface = device_get_phy_mode(&pdev->dev); +#ifdef CONFIG_ACPI + static const struct acpi_device_id phytium_old_acpi_id[] = { + { .id = "FTGM0001" }, // compat FT2000/4 id + { } + }; + /* "phy-mode" in phytium platform DSDT is not correct in some old device. + * Force this PHY mode to rgmii-rxid and info of its use. + * If the phy-mode rgmii is realy used, a blacklist may need to be added. + */ + if (acpi_match_device_ids(to_acpi_device(&pdev->dev), phytium_old_acpi_id) && + plat->phy_interface == PHY_INTERFACE_MODE_RGMII) { + plat->phy_interface = PHY_INTERFACE_MODE_RGMII_RXID; + dev_info(&pdev->dev, "phytium workaround: phy-mode from rgmii to rgmii-rxid\n"); + } +#endif + if (plat->phy_interface < 0) + return plat->phy_interface; + + plat->phy_interface = phytium_get_mac_mode(fwnode); + if (plat->phy_interface < 0) + plat->phy_interface = plat->phy_interface; + + /* Configure PHY if using device-tree */ + if (pdev->dev.of_node) + plat->phy_node = of_parse_phandle(np, "phy-handle", 0); + + if (pdev->dev.of_node) { + plat->bus_id = of_alias_get_id(np, "ethernet"); + if (plat->bus_id < 0) + plat->bus_id = 0; + } else if (fwnode_property_read_u32(fwnode, "bus_id", &plat->bus_id)) { + plat->bus_id = 2; + } + + plat->phy_addr = -1; + plat->clk_csr = -1; + plat->core_type = DWMAC_CORE_GMAC; + plat->enh_desc = 1; + plat->bugged_jumbo = 1; + plat->pmt = 1; + plat->force_sf_dma_mode = 1; + + if (fwnode_property_read_u32(fwnode, "max-speed", &plat->max_speed)) + plat->max_speed = -1; + + if (fwnode_property_read_u32(fwnode, "max-frame-size", &plat->maxmtu)) + plat->maxmtu = JUMBO_LEN; + + if (fwnode_property_read_u32(fwnode, "snps,multicast-filter-bins", + &plat->multicast_filter_bins)) + plat->multicast_filter_bins = HASH_TABLE_SIZE; + + if (fwnode_property_read_u32(fwnode, "snps,perfect-filter-entries", + &plat->unicast_filter_entries)) + plat->unicast_filter_entries = 1; + + if (fwnode_property_read_u32(fwnode, "tx-fifo-depth", &plat->tx_fifo_size)) + plat->tx_fifo_size = 0x1000; + + if (fwnode_property_read_u32(fwnode, "rx-fifo-depth", &plat->rx_fifo_size)) + plat->rx_fifo_size = 0x1000; + + if (phytium_dwmac_acpi_phy(plat, fwnode, &pdev->dev)) + return -ENODEV; + + plat->rx_queues_to_use = 1; + plat->tx_queues_to_use = 1; + plat->rx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB; + plat->tx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB; + + if (fwnode_property_read_u64(fwnode, "clock-frequency", &clk_freq)) + clk_freq = 125000000; + + /* Set system clock */ + snprintf(clk_name, sizeof(clk_name), "%s-%d", "stmmaceth", plat->bus_id); + + plat->stmmac_clk = clk_register_fixed_rate(&pdev->dev, clk_name, NULL, 0, clk_freq); + if (IS_ERR(plat->stmmac_clk)) { + dev_warn(&pdev->dev, "Fail to register stmmac-clk\n"); + plat->stmmac_clk = NULL; + } + + ret = clk_prepare_enable(plat->stmmac_clk); + if (ret) { + clk_unregister_fixed_rate(plat->stmmac_clk); + return ret; + } + + plat->clk_ptp_rate = clk_get_rate(plat->stmmac_clk); + plat->clk_ptp_ref = NULL; + + if (fwnode_property_read_u32(fwnode, "snps,pbl", &plat->dma_cfg->pbl)) + plat->dma_cfg->pbl = 16; + + fwnode_property_read_u32(fwnode, "snps,txpbl", &plat->dma_cfg->txpbl); + fwnode_property_read_u32(fwnode, "snps,rxpbl", &plat->dma_cfg->rxpbl); + + plat->dma_cfg->pblx8 = !fwnode_property_read_bool(fwnode, "snps,no-pbl-x8"); + plat->dma_cfg->aal = fwnode_property_read_bool(fwnode, "snps,aal"); + plat->dma_cfg->fixed_burst = fwnode_property_read_bool(fwnode, "snps,fixed-burst"); + plat->dma_cfg->mixed_burst = fwnode_property_read_bool(fwnode, "snps,mixed-burst"); +#ifdef CONFIG_ACPI + /* Some old phytium 2000/4 FTGM0001 cannot auto deferred stmmac DMA settings + * show kernel error 'DMA descriptors allocation failed' + */ + if (acpi_match_device_ids(to_acpi_device(&pdev->dev), phytium_old_acpi_id)) { +#ifdef CONFIG_ARCH_HAS_DMA_OPS + pdev->dev.dma_ops = NULL; // solved set DMA mask Failed +#endif + plat->host_dma_width = 32; + } +#endif + plat->axi->axi_lpi_en = false; + plat->axi->axi_xit_frm = false; + plat->axi->axi_wr_osr_lmt = 7; + plat->axi->axi_rd_osr_lmt = 7; + + memset(&stmmac_res, 0, sizeof(stmmac_res)); + stmmac_res.addr = devm_platform_ioremap_resource(pdev, 0); + stmmac_res.irq = platform_get_irq(pdev, 0); + if (stmmac_res.irq < 0) { + dev_err(&pdev->dev, "IRQ not found.\n"); + return -ENXIO; + } + stmmac_res.wol_irq = stmmac_res.irq; + + return stmmac_dvr_probe(&pdev->dev, plat, &stmmac_res); +} + +static void phytium_dwmac_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct stmmac_priv *priv = netdev_priv(ndev); + struct plat_stmmacenet_data *plat = priv->plat; + + stmmac_pltfr_remove(pdev); + clk_unregister_fixed_rate(plat->stmmac_clk); +} + +#ifdef CONFIG_OF +static const struct of_device_id phytium_dwmac_of_match[] = { + { .compatible = "phytium,gmac" }, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_dwmac_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_dwmac_acpi_ids[] = { + { .id = "FTGM0001" }, // compat FT2000/4 id + { .id = "PHYT0004" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, phytium_dwmac_acpi_ids); +#endif + +static struct platform_driver phytium_dwmac_driver = { + .probe = phytium_dwmac_probe, + .remove = phytium_dwmac_remove, + .driver = { + .name = "phytium-dwmac", + .pm = &stmmac_pltfr_pm_ops, + .of_match_table = of_match_ptr(phytium_dwmac_of_match), + .acpi_match_table = ACPI_PTR(phytium_dwmac_acpi_ids), + }, +}; +module_platform_driver(phytium_dwmac_driver); + +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Phytium DWMAC specific glue layer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c index 223754cc5c79b7..322bdf167a4a68 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c @@ -18,10 +18,12 @@ #include "stmmac_platform.h" /* ctrl register bits */ -#define CTRL_PHY_INTF_RGMII BIT(3) -#define CTRL_PHY_INTF_MII BIT(4) -#define CTRL_WAKE_IRQ_EN BIT(9) -#define CTRL_PHY_IRQ_EN BIT(12) +#define CTRL_PHY_INTF_MODE GENMASK(4, 3) +#define CTRL_PHY_INTF_RMII FIELD_PREP(CTRL_PHY_INTF_MODE, 0) +#define CTRL_PHY_INTF_RGMII FIELD_PREP(CTRL_PHY_INTF_MODE, 1) +#define CTRL_PHY_INTF_MII FIELD_PREP(CTRL_PHY_INTF_MODE, 3) +#define CTRL_LPI_IRQ_EN BIT(9) +#define CTRL_WAKE_IRQ_EN BIT(12) /* dline register bits */ #define RGMII_RX_DLINE_EN BIT(0) @@ -118,7 +120,7 @@ static void spacemit_get_interfaces(struct stmmac_priv *priv, void *bsp_priv, static int spacemit_set_phy_intf_sel(void *bsp_priv, u8 phy_intf_sel) { - unsigned int mask = CTRL_PHY_INTF_MII | CTRL_PHY_INTF_RGMII; + unsigned int mask = CTRL_PHY_INTF_MODE; struct spacmit_dwmac *dwmac = bsp_priv; unsigned int val = 0; @@ -128,6 +130,7 @@ static int spacemit_set_phy_intf_sel(void *bsp_priv, u8 phy_intf_sel) break; case PHY_INTF_SEL_RMII: + val = CTRL_PHY_INTF_RMII; break; case PHY_INTF_SEL_RGMII: diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index c4b613564f87ce..6bb35c9031d087 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -182,6 +182,10 @@ static void ndesc_prepare_tx_desc(struct dma_desc *p, int is_fs, int len, else norm_set_tx_desc_len_on_ring(p, len); + /* The own bit must be the latest setting done when prepare the + * descriptor and then barrier is needed to make sure that all is coherent. + */ + wmb(); if (tx_own) p->des0 |= cpu_to_le32(TDES0_OWN); } diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index 7a578b5aa2ed60..8fde1f24e4ee1a 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -356,6 +357,16 @@ struct marvell_priv { u8 vct_phase; }; +static const struct dmi_system_id skip_config_led_tbl[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Dell EMC"), + DMI_MATCH(DMI_BOARD_NAME, "0d370eed-89ca-4dc0-a365-e9904c4c62bb"), + }, + }, + {} +}; + static int marvell_read_page(struct phy_device *phydev) { return __phy_read(phydev, MII_MARVELL_PHY_PAGE); @@ -845,6 +856,9 @@ static void marvell_config_led(struct phy_device *phydev) u16 def_config; int err; + if (dmi_check_system(skip_config_led_tbl)) + return; + switch (MARVELL_PHY_FAMILY_ID(phydev->phy_id)) { /* Default PHY LED config: LED[0] .. Link, LED[1] .. Activity */ case MARVELL_PHY_FAMILY_ID(MARVELL_PHY_ID_88E1121R): diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c index 7c2939cbde5f05..eb402a3a8c5f88 100644 --- a/drivers/net/wireless/ath/ath10k/core.c +++ b/drivers/net/wireless/ath/ath10k/core.c @@ -41,6 +41,9 @@ static bool fw_diag_log; /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; +static char *override_board = ""; +static char *override_board2 = ""; + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); @@ -53,6 +56,9 @@ module_param(fw_diag_log, bool, 0644); module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); +module_param(override_board, charp, 0644); +module_param(override_board2, charp, 0644); + MODULE_PARM_DESC(debug_mask, "Debugging mask"); MODULE_PARM_DESC(uart_print, "Uart target debugging"); MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); @@ -62,6 +68,9 @@ MODULE_PARM_DESC(frame_mode, MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); +MODULE_PARM_DESC(override_board, "Override for board.bin file"); +MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); + static const struct ath10k_hw_params ath10k_hw_params_list[] = { { .id = QCA988X_HW_2_0_VERSION, @@ -932,6 +941,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) return 0; } +static const char *ath10k_override_board_fw_file(struct ath10k *ar, + const char *file) +{ + if (strcmp(file, "board.bin") == 0) { + if (strcmp(override_board, "") == 0) + return file; + + if (strcmp(override_board, "none") == 0) { + dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); + return NULL; + } + + dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", + override_board); + + return override_board; + } + + if (strcmp(file, "board-2.bin") == 0) { + if (strcmp(override_board2, "") == 0) + return file; + + if (strcmp(override_board2, "none") == 0) { + dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); + return NULL; + } + + dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", + override_board2); + + return override_board2; + } + + return file; +} + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, const char *dir, const char *file) @@ -946,6 +991,18 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, if (dir == NULL) dir = "."; + /* HACK: Override board.bin and board-2.bin files if specified. + * + * Some Surface devices perform better with a different board + * configuration. To this end, one would need to replace the board.bin + * file with the modified config and remove the board-2.bin file. + * Unfortunately, that's not a solution that we can easily package. So + * we add module options to perform these overrides here. + */ + file = ath10k_override_board_fw_file(ar, file); + if (!file) + return ERR_PTR(-ENOENT); + if (ar->board_name) { snprintf(filename, sizeof(filename), "%s/%s/%s", dir, ar->board_name, file); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c index eb3c5a6dd08841..3af3a1641a7035 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -208,6 +208,7 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct pci_device_id iwl_hw_card_ids[] = { {IWL_PCI_DEVICE(0x088E, 0x446A, iwl6030_mac_cfg)}, {IWL_PCI_DEVICE(0x088E, 0x4860, iwl6030_mac_cfg)}, {IWL_PCI_DEVICE(0x088F, 0x5260, iwl6030_mac_cfg)}, + {IWL_PCI_DEVICE(0x088F, 0x526A, iwl6030_mac_cfg)}, /* 105 Series */ {IWL_PCI_DEVICE(0x0894, 0x0022, iwl105_mac_cfg)}, diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c index a760de191fce73..ffd0c1fe922399 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie.c @@ -377,6 +377,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct pcie_service_card *card; + struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); int ret; pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", @@ -418,6 +419,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, return -1; } + /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing + * after suspend + */ + if (card->quirks & QUIRK_NO_BRIDGE_D3) + parent_pdev->bridge_d3 = false; + return 0; } @@ -1702,9 +1709,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) static void mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) { struct pcie_service_card *card = adapter->card; + struct pci_dev *pdev = card->dev; + struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + /* Trigger a function level reset of the PCI bridge device, this makes + * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value + * that prevents the system from entering package C10 and S0ix powersaving + * states. + * We need to do it here because it must happen after firmware + * initialization and this function is called after that is done. + */ + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + pci_reset_function(parent_pdev); + /* Write the RX ring read pointer in to reg->rx_rdptr */ mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap); } diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c index dd6d21f1dbfd77..99b024ecbadeaf 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c @@ -13,7 +13,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Pro 5", @@ -22,7 +24,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Pro 5 (LTE)", @@ -31,7 +35,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Pro 6", @@ -39,7 +45,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Book 1", @@ -47,7 +55,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Book 2", @@ -55,7 +65,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Laptop 1", @@ -63,7 +75,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Laptop 2", @@ -71,7 +85,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, {} }; @@ -89,6 +105,11 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) dev_info(&pdev->dev, "no quirks enabled\n"); if (card->quirks & QUIRK_FW_RST_D3COLD) dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + if (card->quirks & QUIRK_NO_BRIDGE_D3) + dev_info(&pdev->dev, + "quirk no_brigde_d3 enabled\n"); } static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h index d6ff964aec5bfd..c14eb56eb9118b 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h @@ -4,6 +4,8 @@ #include "pcie.h" #define QUIRK_FW_RST_D3COLD BIT(0) +#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) +#define QUIRK_NO_BRIDGE_D3 BIT(2) void mwifiex_initialize_quirks(struct pcie_service_card *card); int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index 43c61b3dc969f2..6470d09f17f3bb 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -3312,6 +3312,10 @@ static bool rtw89_pci_is_dac_compatible_bridge(struct rtw89_dev *rtwdev) if (bridge->device == 0x2806) return true; break; + case PCI_VENDOR_ID_SPACEMIT: + if (bridge->device == PCI_DEVICE_ID_SPACEMIT_K3) + return true; + break; } return false; diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index 50697cc3b07ebe..f9d2fc56fc41c4 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -123,6 +123,13 @@ config OF_OVERLAY_KUNIT_TEST If unsure, say N here, but this option is safe to enable. +config OF_CONFIGFS + bool "Device Tree Overlay ConfigFS interface" + select CONFIGFS_FS + depends on OF_OVERLAY + help + Enable a simple user-space driven DT overlay interface. + config OF_NUMA bool diff --git a/drivers/of/Makefile b/drivers/of/Makefile index 379a0afcbdc0bf..2be9021017ba5a 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_OF_UNITTEST) += unittest.o obj-$(CONFIG_OF_RESERVED_MEM) += of_reserved_mem.o obj-$(CONFIG_OF_RESOLVE) += resolver.o obj-$(CONFIG_OF_OVERLAY) += overlay.o +obj-$(CONFIG_OF_CONFIGFS) += configfs.o obj-$(CONFIG_OF_NUMA) += of_numa.o ifdef CONFIG_KEXEC_FILE diff --git a/drivers/of/configfs.c b/drivers/of/configfs.c new file mode 100644 index 00000000000000..f4f1e1e5135c23 --- /dev/null +++ b/drivers/of/configfs.c @@ -0,0 +1,278 @@ +/* + * Configfs entries for device-tree + * + * Copyright (C) 2013 - Pantelis Antoniou + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "of_private.h" + +struct cfs_overlay_item { + struct config_item item; + + char path[PATH_MAX]; + + const struct firmware *fw; + struct device_node *overlay; + int ov_id; + + void *dtbo; + int dtbo_size; +}; + +static inline struct cfs_overlay_item *to_cfs_overlay_item( + struct config_item *item) +{ + return item ? container_of(item, struct cfs_overlay_item, item) : NULL; +} + +static ssize_t cfs_overlay_item_path_show(struct config_item *item, + char *page) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + return sprintf(page, "%s\n", overlay->path); +} + +static ssize_t cfs_overlay_item_path_store(struct config_item *item, + const char *page, size_t count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + const char *p = page; + char *s; + int err; + + /* if it's set do not allow changes */ + if (overlay->path[0] != '\0' || overlay->dtbo_size > 0) + return -EPERM; + + /* copy to path buffer (and make sure it's always zero terminated */ + count = snprintf(overlay->path, sizeof(overlay->path) - 1, "%s", p); + overlay->path[sizeof(overlay->path) - 1] = '\0'; + + /* strip trailing newlines */ + s = overlay->path + strlen(overlay->path); + while (s > overlay->path && *--s == '\n') + *s = '\0'; + + pr_debug("%s: path is '%s'\n", __func__, overlay->path); + + err = request_firmware(&overlay->fw, overlay->path, NULL); + if (err != 0) + goto out_err; + + err = of_overlay_fdt_apply((void *)overlay->fw->data, + (u32)overlay->fw->size, + &overlay->ov_id, NULL); + if (err != 0) + goto out_err; + + return count; + +out_err: + + release_firmware(overlay->fw); + overlay->fw = NULL; + + overlay->path[0] = '\0'; + return err; +} + +static ssize_t cfs_overlay_item_status_show(struct config_item *item, + char *page) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + return sprintf(page, "%s\n", + overlay->ov_id > 0 ? "applied" : "unapplied"); +} + +CONFIGFS_ATTR(cfs_overlay_item_, path); +CONFIGFS_ATTR_RO(cfs_overlay_item_, status); + +static struct configfs_attribute *cfs_overlay_attrs[] = { + &cfs_overlay_item_attr_path, + &cfs_overlay_item_attr_status, + NULL, +}; + +static ssize_t cfs_overlay_item_dtbo_read(struct config_item *item, + void *buf, size_t max_count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + pr_debug("%s: buf=%p max_count=%zu\n", __func__, + buf, max_count); + + if (overlay->dtbo == NULL) + return 0; + + /* copy if buffer provided */ + if (buf != NULL) { + /* the buffer must be large enough */ + if (overlay->dtbo_size > max_count) + return -ENOSPC; + + memcpy(buf, overlay->dtbo, overlay->dtbo_size); + } + + return overlay->dtbo_size; +} + +static ssize_t cfs_overlay_item_dtbo_write(struct config_item *item, + const void *buf, size_t count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + int err; + + /* if it's set do not allow changes */ + if (overlay->path[0] != '\0' || overlay->dtbo_size > 0) + return -EPERM; + + /* copy the contents */ + overlay->dtbo = kmemdup(buf, count, GFP_KERNEL); + if (overlay->dtbo == NULL) + return -ENOMEM; + + overlay->dtbo_size = count; + + err = of_overlay_fdt_apply(overlay->dtbo, overlay->dtbo_size, + &overlay->ov_id, NULL); + if (err != 0) + goto out_err; + + return count; + +out_err: + kfree(overlay->dtbo); + overlay->dtbo = NULL; + overlay->dtbo_size = 0; + overlay->ov_id = 0; + + return err; +} + +CONFIGFS_BIN_ATTR(cfs_overlay_item_, dtbo, NULL, SZ_1M); + +static struct configfs_bin_attribute *cfs_overlay_bin_attrs[] = { + &cfs_overlay_item_attr_dtbo, + NULL, +}; + +static void cfs_overlay_release(struct config_item *item) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + if (overlay->ov_id > 0) + of_overlay_remove(&overlay->ov_id); + if (overlay->fw) + release_firmware(overlay->fw); + /* kfree with NULL is safe */ + kfree(overlay->dtbo); + kfree(overlay); +} + +static struct configfs_item_operations cfs_overlay_item_ops = { + .release = cfs_overlay_release, +}; + +static struct config_item_type cfs_overlay_type = { + .ct_item_ops = &cfs_overlay_item_ops, + .ct_attrs = cfs_overlay_attrs, + .ct_bin_attrs = cfs_overlay_bin_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *cfs_overlay_group_make_item( + struct config_group *group, const char *name) +{ + struct cfs_overlay_item *overlay; + + overlay = kzalloc(sizeof(*overlay), GFP_KERNEL); + if (!overlay) + return ERR_PTR(-ENOMEM); + + config_item_init_type_name(&overlay->item, name, &cfs_overlay_type); + return &overlay->item; +} + +static void cfs_overlay_group_drop_item(struct config_group *group, + struct config_item *item) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + config_item_put(&overlay->item); +} + +static struct configfs_group_operations overlays_ops = { + .make_item = cfs_overlay_group_make_item, + .drop_item = cfs_overlay_group_drop_item, +}; + +static struct config_item_type overlays_type = { + .ct_group_ops = &overlays_ops, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_group_operations of_cfs_ops = { + /* empty - we don't allow anything to be created */ +}; + +static struct config_item_type of_cfs_type = { + .ct_group_ops = &of_cfs_ops, + .ct_owner = THIS_MODULE, +}; + +struct config_group of_cfs_overlay_group; + +static struct configfs_subsystem of_cfs_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "device-tree", + .ci_type = &of_cfs_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(of_cfs_subsys.su_mutex), +}; + +static int __init of_cfs_init(void) +{ + int ret; + + pr_info("%s\n", __func__); + + config_group_init(&of_cfs_subsys.su_group); + config_group_init_type_name(&of_cfs_overlay_group, "overlays", + &overlays_type); + configfs_add_default_group(&of_cfs_overlay_group, + &of_cfs_subsys.su_group); + + ret = configfs_register_subsystem(&of_cfs_subsys); + if (ret != 0) { + pr_err("%s: failed to register subsys\n", __func__); + goto out; + } + pr_info("%s: OK\n", __func__); +out: + return ret; +} +late_initcall(of_cfs_init); diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index f2fde13107f2e4..53b5004a8af86f 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -439,7 +439,7 @@ config PCIE_SOPHGO_DW Sophgo SoCs. config PCIE_SPACEMIT_K1 - tristate "SpacemiT K1 PCIe controller (host mode)" + tristate "SpacemiT PCIe controller (host mode)" depends on ARCH_SPACEMIT || COMPILE_TEST depends on HAS_IOMEM select PCIE_DW_HOST @@ -447,7 +447,7 @@ config PCIE_SPACEMIT_K1 default ARCH_SPACEMIT help Enables support for the DesignWare based PCIe controller in - the SpacemiT K1 SoC operating in host mode. Three controllers + the SpacemiT SoC operating in host mode. Three controllers are available on the K1 SoC; the first of these shares a PHY with a USB 3.0 host controller (one or the other can be used). diff --git a/drivers/pci/controller/dwc/pcie-spacemit-k1.c b/drivers/pci/controller/dwc/pcie-spacemit-k1.c index be20a520255b68..bf2ab5cb59ce8b 100644 --- a/drivers/pci/controller/dwc/pcie-spacemit-k1.c +++ b/drivers/pci/controller/dwc/pcie-spacemit-k1.c @@ -21,9 +21,6 @@ #include "pcie-designware.h" -#define PCI_VENDOR_ID_SPACEMIT 0x201f -#define PCI_DEVICE_ID_SPACEMIT_K1 0x0001 - /* Offsets and field definitions for link management registers */ #define K1_PHY_AHB_IRQ_EN 0x0000 #define PCIE_INTERRUPT_EN BIT(0) @@ -32,8 +29,18 @@ #define SMLH_LINK_UP BIT(1) #define RDLH_LINK_UP BIT(12) +#define INTR_STATUS 0x0010 + #define INTR_ENABLE 0x0014 #define MSI_CTRL_INT BIT(11) +#define RDLH_LINK_UP_INT BIT(20) + +#define K3_PHY_AHB_IRQSTATUS_INTX 0x0008 + +#define K3_ADDR_INTR_STATUS1 0x0018 + +#define K3_CACHE_MSTR_AWCACHE_MODE GENMASK(14, 11) +#define K3_CACHE_MSTR_AWCACHE_BEHAVIOR 0xf /* Some controls require APMU regmap access */ #define SYSCON_APMU "spacemit,apmu" @@ -48,10 +55,25 @@ #define PCIE_CONTROL_LOGIC 0x0004 #define PCIE_SOFT_RESET BIT(0) +#define PCIE_PERSTN_OE BIT(24) +#define PCIE_PERSTN_OUT BIT(25) +#define PCIE_IGNORE_PERSTN BIT(31) + +struct k1_pcie; + +struct k1_pcie_device_data { + const struct dw_pcie_host_ops *host_ops; + const struct dw_pcie_ops *ops; + int (*parse_port)(struct k1_pcie *k1); + unsigned int max_phy_count; + unsigned int device_id; +}; struct k1_pcie { struct dw_pcie pci; - struct phy *phy; + const struct k1_pcie_device_data *data; + struct phy **phy; + unsigned int phy_count; void __iomem *link; struct regmap *pmu; /* Errors ignored; MMIO-backed regmap */ u32 pmu_off; @@ -110,6 +132,54 @@ static void k1_pcie_disable_resources(struct k1_pcie *k1) clk_bulk_disable_unprepare(ARRAY_SIZE(pci->app_clks), pci->app_clks); } +static int k1_pcie_get_phy_handle(struct k1_pcie *k1, struct device_node *node) +{ + const struct k1_pcie_device_data *data = k1->data; + struct device *dev = k1->pci.dev; + unsigned int i; + + k1->phy = devm_kmalloc_array(dev, data->max_phy_count, + sizeof(*k1->phy), GFP_KERNEL); + if (!k1->phy) + return -ENOMEM; + + for (i = 0; i < data->max_phy_count; i++) { + k1->phy[i] = devm_of_phy_get_by_index(dev, node, i); + if (IS_ERR(k1->phy[i])) { + if (PTR_ERR(k1->phy[i]) == -ENODEV) + break; + + return PTR_ERR(k1->phy[i]); + } + } + + k1->phy_count = i; + if (k1->phy_count == 0) + return -EINVAL; + + return 0; +} + +static int k1_pcie_enable_phy(struct k1_pcie *k1) +{ + unsigned int i; + int ret; + + for (i = 0; i < k1->phy_count; i++) { + ret = phy_init(k1->phy[i]); + if (ret) + goto err_phy; + } + + return 0; + +err_phy: + while (i--) + phy_exit(k1->phy[i]); + + return ret; +} + /* FIXME: Disable ASPM L1 to avoid errors reported on some NVMe drives */ static void k1_pcie_disable_aspm_l1(struct k1_pcie *k1) { @@ -127,6 +197,16 @@ static void k1_pcie_disable_aspm_l1(struct k1_pcie *k1) dw_pcie_dbi_ro_wr_dis(pci); } +static void k1_pcie_set_device_id(struct k1_pcie *k1) +{ + struct dw_pcie *pci = &k1->pci; + + dw_pcie_dbi_ro_wr_en(pci); + dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, PCI_VENDOR_ID_SPACEMIT); + dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, k1->data->device_id); + dw_pcie_dbi_ro_wr_dis(pci); +} + static int k1_pcie_init(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); @@ -142,10 +222,7 @@ static int k1_pcie_init(struct dw_pcie_rp *pp) return ret; /* Set the PCI vendor and device ID */ - dw_pcie_dbi_ro_wr_en(pci); - dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, PCI_VENDOR_ID_SPACEMIT); - dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, PCI_DEVICE_ID_SPACEMIT_K1); - dw_pcie_dbi_ro_wr_dis(pci); + k1_pcie_set_device_id(k1); /* * Start by asserting fundamental reset (drive PERST# low). The @@ -165,7 +242,7 @@ static int k1_pcie_init(struct dw_pcie_rp *pp) */ regmap_set_bits(k1->pmu, reset_ctrl, DEVICE_TYPE_RC | PCIE_AUX_PWR_DET); - ret = phy_init(k1->phy); + ret = k1_pcie_enable_phy(k1); if (ret) { k1_pcie_disable_resources(k1); @@ -185,12 +262,14 @@ static void k1_pcie_deinit(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct k1_pcie *k1 = to_k1_pcie(pci); + int i; /* Assert fundamental reset (drive PERST# low) */ regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CLK_RESET_CONTROL, PCIE_RC_PERST); - phy_exit(k1->phy); + for (i = 0; i < k1->phy_count; i++) + phy_exit(k1->phy[i]); k1_pcie_disable_resources(k1); } @@ -257,35 +336,140 @@ static int k1_pcie_parse_port(struct k1_pcie *k1) { struct device *dev = k1->pci.dev; struct device_node *root_port; - struct phy *phy; + int ret; /* We assume only one root port */ root_port = of_get_next_available_child(dev_of_node(dev), NULL); if (!root_port) return -EINVAL; - phy = devm_of_phy_get(dev, root_port, NULL); + ret = k1_pcie_get_phy_handle(k1, root_port); of_node_put(root_port); - if (IS_ERR(phy)) - return PTR_ERR(phy); + return ret; +} - k1->phy = phy; +static int k3_pcie_init(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct k1_pcie *k1 = to_k1_pcie(pci); + u32 reset_ctrl = k1->pmu_off + PCIE_CLK_RESET_CONTROL; + u32 val; + int ret; + + regmap_clear_bits(k1->pmu, reset_ctrl, LTSSM_EN); + + k1_pcie_toggle_soft_reset(k1); + + ret = k1_pcie_enable_resources(k1); + if (ret) + return ret; + + regmap_set_bits(k1->pmu, reset_ctrl, PCIE_AUX_PWR_DET); + regmap_clear_bits(k1->pmu, reset_ctrl, APP_HOLD_PHY_RST); + + ret = k1_pcie_enable_phy(k1); + if (ret) { + k1_pcie_disable_resources(k1); + return ret; + } + + /* K3: Set IGNORE_PERSTN and drive PERSTN_OE high (assert reset) */ + regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CONTROL_LOGIC, + PCIE_IGNORE_PERSTN | PCIE_PERSTN_OE | PCIE_PERSTN_OUT); + usleep_range(1000, 2000); + regmap_clear_bits(k1->pmu, k1->pmu_off + PCIE_CONTROL_LOGIC, PCIE_PERSTN_OUT); + + msleep(PCIE_T_PVPERL_MS); + + /* + * Put the controller in root complex mode, and indicate that + * Vaux (3.3v) is present. + */ + regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CONTROL_LOGIC, + PCIE_PERSTN_OUT | PCIE_PERSTN_OE); + + val = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF); + val = u32_replace_bits(val, BIT(7), + GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC); + dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, val); + + k1_pcie_set_device_id(k1); + + /* Finally, as a workaround, disable ASPM L1 */ + k1_pcie_disable_aspm_l1(k1); + + return 0; +} + +static int k3_pcie_msi_host_init(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + u32 val; + + dw_pcie_dbi_ro_wr_en(pci); + + val = dw_pcie_readl_dbi(pci, COHERENCY_CONTROL_3_OFF); + val = u32_replace_bits(val, K3_CACHE_MSTR_AWCACHE_BEHAVIOR, + K3_CACHE_MSTR_AWCACHE_MODE); + dw_pcie_writel_dbi(pci, COHERENCY_CONTROL_3_OFF, val); + + dw_pcie_dbi_ro_wr_dis(pci); return 0; } +static const struct dw_pcie_host_ops k3_pcie_host_ops = { + .init = k3_pcie_init, + .deinit = k1_pcie_deinit, + .msi_init = k3_pcie_msi_host_init, +}; + +static const struct dw_pcie_ops k3_pcie_ops = { + .link_up = k1_pcie_link_up, + .start_link = k1_pcie_start_link, + .stop_link = k1_pcie_stop_link, +}; + +static void k3_pcie_clear_irq_status(struct k1_pcie *k1, + u32 *status0, u32 *status1, u32 *status2) +{ + *status0 = readl_relaxed(k1->link + K3_PHY_AHB_IRQSTATUS_INTX); + *status1 = readl_relaxed(k1->link + INTR_STATUS); + *status2 = readl_relaxed(k1->link + K3_ADDR_INTR_STATUS1); + + writel_relaxed(*status0, k1->link + K3_PHY_AHB_IRQSTATUS_INTX); + writel_relaxed(*status1, k1->link + INTR_STATUS); + writel_relaxed(*status2, k1->link + K3_ADDR_INTR_STATUS1); +} + +static int k3_pcie_parse_port(struct k1_pcie *k1) +{ + u32 status0, status1, status2; + + k3_pcie_clear_irq_status(k1, &status0, &status1, &status2); + + return k1_pcie_parse_port(k1); +} + static int k1_pcie_probe(struct platform_device *pdev) { + const struct k1_pcie_device_data *data; struct device *dev = &pdev->dev; struct k1_pcie *k1; int ret; + data = device_get_match_data(dev); + if (!data) + return -ENODEV; + k1 = devm_kzalloc(dev, sizeof(*k1), GFP_KERNEL); if (!k1) return -ENOMEM; + k1->data = data; + k1->pmu = syscon_regmap_lookup_by_phandle_args(dev_of_node(dev), SYSCON_APMU, 1, &k1->pmu_off); @@ -299,11 +483,11 @@ static int k1_pcie_probe(struct platform_device *pdev) "failed to map \"link\" registers\n"); k1->pci.dev = dev; - k1->pci.ops = &k1_pcie_ops; + k1->pci.ops = data->ops; k1->pci.pp.num_vectors = MAX_MSI_IRQS; dw_pcie_cap_set(&k1->pci, REQ_RES); - k1->pci.pp.ops = &k1_pcie_host_ops; + k1->pci.pp.ops = data->host_ops; /* Hold the PHY in reset until we start the link */ regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CLK_RESET_CONTROL, @@ -320,7 +504,7 @@ static int k1_pcie_probe(struct platform_device *pdev) platform_set_drvdata(pdev, k1); - ret = k1_pcie_parse_port(k1); + ret = data->parse_port(k1); if (ret) return dev_err_probe(dev, ret, "failed to parse root port\n"); @@ -338,10 +522,28 @@ static void k1_pcie_remove(struct platform_device *pdev) dw_pcie_host_deinit(&k1->pci.pp); } +static const struct k1_pcie_device_data k1_pcie_device_data = { + .host_ops = &k1_pcie_host_ops, + .ops = &k1_pcie_ops, + .parse_port = k1_pcie_parse_port, + .max_phy_count = 1, + .device_id = PCI_DEVICE_ID_SPACEMIT_K1, +}; + +static const struct k1_pcie_device_data k3_pcie_device_data = { + .host_ops = &k3_pcie_host_ops, + .ops = &k3_pcie_ops, + .parse_port = k3_pcie_parse_port, + .max_phy_count = 6, + .device_id = PCI_DEVICE_ID_SPACEMIT_K3, +}; + static const struct of_device_id k1_pcie_of_match_table[] = { - { .compatible = "spacemit,k1-pcie", }, + { .compatible = "spacemit,k1-pcie", .data = &k1_pcie_device_data}, + { .compatible = "spacemit,k3-pcie", .data = &k3_pcie_device_data}, { } }; +MODULE_DEVICE_TABLE(of, k1_pcie_of_match_table); static struct platform_driver k1_pcie_driver = { .probe = k1_pcie_probe, diff --git a/drivers/pci/controller/pci-loongson.c b/drivers/pci/controller/pci-loongson.c index 9609e6f50b9816..be588feb28e4ba 100644 --- a/drivers/pci/controller/pci-loongson.c +++ b/drivers/pci/controller/pci-loongson.c @@ -212,6 +212,19 @@ static void loongson_pci_bridge_speed_quirk(struct pci_dev *pdev) DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_LOONGSON, 0x3c19, loongson_pci_bridge_speed_quirk); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_LOONGSON, 0x3c29, loongson_pci_bridge_speed_quirk); +/* + * Some devices, for example Intel 750 Series SSD, experiences data loss + * (read timeout) on LS7A1000 PCIe x8 Root Port when ASPM L0s is enabled. + * This seems only affecting the revision 2. + */ +static void loongson_port2_aspm_quirk(struct pci_dev *pdev) +{ + if (pdev->revision == 2) + pcie_aspm_remove_cap(pdev, PCI_EXP_LNKCAP_ASPM_L0S); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_LOONGSON, DEV_LS7A_PCIE_PORT2, + loongson_port2_aspm_quirk); + static struct loongson_pci *pci_bus_to_loongson_pci(struct pci_bus *bus) { struct pci_config_window *cfg; diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c index d4ae250d4bc6d7..3650c6b6038137 100644 --- a/drivers/pci/controller/vmd.c +++ b/drivers/pci/controller/vmd.c @@ -739,6 +739,8 @@ static int vmd_pm_enable_quirk(struct pci_dev *pdev, void *userdata) if (!(features & VMD_FEAT_BIOS_PM_QUIRK)) return 0; + pdev->aspm_os_control = 1; + pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_LTR); if (!pos) goto out_state_change; diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index e3f59001785a17..5790d901033e84 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -543,6 +543,9 @@ static void pci_device_shutdown(struct device *dev) struct pci_dev *pci_dev = to_pci_dev(dev); struct pci_driver *drv = pci_dev->driver; + if (pci_dev->no_shutdown) + return; + pm_runtime_resume(dev); if (drv && drv->shutdown) @@ -555,7 +558,7 @@ static void pci_device_shutdown(struct device *dev) * If it is not a kexec reboot, firmware will hit the PCI * devices with big hammer and stop their DMA any way. */ - if (kexec_in_progress && (pci_dev->current_state <= PCI_D3hot)) + if (kexec_in_progress && !pci_is_bridge(pci_dev) && (pci_dev->current_state <= PCI_D3hot)) pci_clear_master(pci_dev); } diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 925373b98dff0c..7295d037934fd4 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -1472,8 +1472,12 @@ static int __pci_enable_link_state(struct pci_dev *pdev, int state, bool locked) * the _OSC method), we can't honor that request. */ if (aspm_disabled) { - pci_warn(pdev, "can't override BIOS ASPM; OS doesn't have ASPM control\n"); - return -EPERM; + if (aspm_support_enabled && pdev->aspm_os_control) + pci_info(pdev, "BIOS can't program ASPM, let OS control it\n"); + else { + pci_warn(pdev, "can't override BIOS ASPM; OS doesn't have ASPM control\n"); + return -EPERM; + } } if (!locked) diff --git a/drivers/pci/pcie/portdrv.c b/drivers/pci/pcie/portdrv.c index 2d6aa488fe7b64..3386818d200d54 100644 --- a/drivers/pci/pcie/portdrv.c +++ b/drivers/pci/pcie/portdrv.c @@ -685,6 +685,9 @@ static const struct dev_pm_ops pcie_portdrv_pm_ops = { static int pcie_portdrv_probe(struct pci_dev *dev, const struct pci_device_id *id) { + if (dev->dev_flags & PCI_DEV_FLAGS_NO_PORT_SERVICES) + return -ENODEV; + int type = pci_pcie_type(dev); int status; diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index dd0025d3914e60..3c612bdbab517f 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -302,6 +302,21 @@ static void quirk_mmio_always_on(struct pci_dev *dev) DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_BRIDGE_HOST, 8, quirk_mmio_always_on); +/* The BAR0 ~ BAR4 of Marvell 9125 device can't be accessed +* by IO resource file, and need to skip the files +*/ +static void quirk_marvell_mask_bar(struct pci_dev *dev) +{ + int i; + + for (i = 0; i < 5; i++) + if (dev->resource[i].start) + dev->resource[i].start = + dev->resource[i].end = 0; +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MARVELL_EXT, 0x9125, + quirk_marvell_mask_bar); + /* * The Mellanox Tavor device gives false positive parity errors. Disable * parity error reporting. @@ -385,6 +400,36 @@ static void quirk_tigerpoint_bm_sts(struct pci_dev *dev) DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_TGP_LPC, quirk_tigerpoint_bm_sts); #endif +#define DEV_PCIE_PORT_4 0x7a39 +#define DEV_PCIE_PORT_5 0x7a49 +#define DEV_PCIE_PORT_6 0x7a59 +#define DEV_PCIE_PORT_7 0x7a69 +static void loongson_d3_and_link_quirk(struct pci_dev *dev) +{ + struct pci_bus *bus = dev->bus; + struct pci_dev *bridge; + static const struct pci_device_id bridge_devids[] = { + { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_4) }, + { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_5) }, + { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_6) }, + { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_7) }, + { 0, }, + }; + + /* look for the matching bridge */ + while (!pci_is_root_bus(bus)) { + bridge = bus->self; + bus = bus->parent; + if (bridge && pci_match_id(bridge_devids, bridge)) { + dev->dev_flags |= (PCI_DEV_FLAGS_NO_D3 | + PCI_DEV_FLAGS_NO_LINK_SPEED_CHANGE); + dev->no_d1d2 = 1; + break; + } + } +} +DECLARE_PCI_FIXUP_ENABLE(PCI_ANY_ID, PCI_ANY_ID, loongson_d3_and_link_quirk); + /* Chipsets where PCI->PCI transfers vanish or hang */ static void quirk_nopcipci(struct pci_dev *dev) { @@ -6366,3 +6411,87 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); #endif + +/* + * PCI switches integrated into Intel Arc GPUs have BAR0 that prevents + * resizing the BARs of the GPU device due to that bridge BAR0 pinning the + * bridge window it's under in place. Nothing in pcieport requires that + * BAR0. + * + * Release and disable BAR0 permanently by clearing its flags to prevent + * anything from assigning it again. + */ +static void pci_release_bar0(struct pci_dev *pdev) +{ + struct resource *res = pci_resource_n(pdev, 0); + + if (!res->parent) + return; + + pci_release_resource(pdev, 0); + res->flags = 0; +} +DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_INTEL, 0x4fa0, pci_release_bar0); +DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_INTEL, 0x4fa1, pci_release_bar0); +DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_INTEL, 0xe2ff, pci_release_bar0); + +/* + * SG2042's PCIe root ports do not correctly deliver MSI interrupts to + * downstream devices when native PCIe port services are enabled. All + * services including bwctrl must be disabled, equivalent to pcie_ports=compat. + */ +static void quirk_sg2042_no_port_services(struct pci_dev *dev) +{ + pci_info(dev, "SG2042: disabling native PCIe port services\n"); + dev->dev_flags |= PCI_DEV_FLAGS_NO_PORT_SERVICES; +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SOPHGO, 0x2042, quirk_sg2042_no_port_services); + +static const struct dmi_system_id no_shutdown_dmi_table[] = { + /* + * Systems on which some devices should not be touched during shutdown. + */ + { + .ident = "Microsoft Surface Pro 9", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), + }, + }, + { + .ident = "Microsoft Surface Laptop 5", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), + }, + }, + { + .ident = "Microsoft Surface Laptop Studio 2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop Studio 2"), + }, + }, + {} +}; + +static void quirk_no_shutdown(struct pci_dev *dev) +{ + if (!dmi_check_system(no_shutdown_dmi_table)) + return; + + dev->no_shutdown = 1; + pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", + dev->vendor, dev->device); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU + +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa76e, quirk_no_shutdown); // Thunderbolt 4 USB Controller +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa71e, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa73f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa73e, quirk_no_shutdown); // Thunderbolt 4 NHI +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa7a0, quirk_no_shutdown); // GPU diff --git a/drivers/perf/hisilicon/Makefile b/drivers/perf/hisilicon/Makefile index 186be3d02238b1..1009d4f616aa4e 100644 --- a/drivers/perf/hisilicon/Makefile +++ b/drivers/perf/hisilicon/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o hisi_uncore_l3c_pmu.o \ - hisi_uncore_hha_pmu.o hisi_uncore_ddrc_pmu.o hisi_uncore_sllc_pmu.o \ + hisi_uncore_hha_pmu.o hisi_uncore_sllc_pmu.o \ hisi_uncore_pa_pmu.o hisi_uncore_cpa_pmu.o hisi_uncore_uc_pmu.o \ hisi_uncore_noc_pmu.o hisi_uncore_mn_pmu.o diff --git a/drivers/perf/riscv_pmu.c b/drivers/perf/riscv_pmu.c index 8e3cd0f35336fa..751a8651dc3d8e 100644 --- a/drivers/perf/riscv_pmu.c +++ b/drivers/perf/riscv_pmu.c @@ -16,6 +16,7 @@ #include #include +#include #include static bool riscv_perf_user_access(struct perf_event *event) @@ -88,58 +89,21 @@ void arch_perf_update_userpage(struct perf_event *event, userpg->cap_user_time_short = 1; } -static unsigned long csr_read_num(int csr_num) -{ -#define switchcase_csr_read(__csr_num, __val) {\ - case __csr_num: \ - __val = csr_read(__csr_num); \ - break; } -#define switchcase_csr_read_2(__csr_num, __val) {\ - switchcase_csr_read(__csr_num + 0, __val) \ - switchcase_csr_read(__csr_num + 1, __val)} -#define switchcase_csr_read_4(__csr_num, __val) {\ - switchcase_csr_read_2(__csr_num + 0, __val) \ - switchcase_csr_read_2(__csr_num + 2, __val)} -#define switchcase_csr_read_8(__csr_num, __val) {\ - switchcase_csr_read_4(__csr_num + 0, __val) \ - switchcase_csr_read_4(__csr_num + 4, __val)} -#define switchcase_csr_read_16(__csr_num, __val) {\ - switchcase_csr_read_8(__csr_num + 0, __val) \ - switchcase_csr_read_8(__csr_num + 8, __val)} -#define switchcase_csr_read_32(__csr_num, __val) {\ - switchcase_csr_read_16(__csr_num + 0, __val) \ - switchcase_csr_read_16(__csr_num + 16, __val)} - - unsigned long ret = 0; - - switch (csr_num) { - switchcase_csr_read_32(CSR_CYCLE, ret) - switchcase_csr_read_32(CSR_CYCLEH, ret) - default : - break; - } - - return ret; -#undef switchcase_csr_read_32 -#undef switchcase_csr_read_16 -#undef switchcase_csr_read_8 -#undef switchcase_csr_read_4 -#undef switchcase_csr_read_2 -#undef switchcase_csr_read -} - /* * Read the CSR of a corresponding counter. */ unsigned long riscv_pmu_ctr_read_csr(unsigned long csr) { - if (csr < CSR_CYCLE || csr > CSR_HPMCOUNTER31H || - (csr > CSR_HPMCOUNTER31 && csr < CSR_CYCLEH)) { - pr_err("Invalid performance counter csr %lx\n", csr); - return -EINVAL; + unsigned long val; + int rc; + + val = csr_read_num(csr, &rc); + if (rc) { + pr_err("Failed to read performance counter csr %lx (error %d)\n", csr, rc); + return rc; } - return csr_read_num(csr); + return val; } u64 riscv_pmu_ctr_get_width_mask(struct perf_event *event) diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 227b9a4c612e8c..ea1a52e14b8397 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -164,6 +164,7 @@ source "drivers/phy/st/Kconfig" source "drivers/phy/starfive/Kconfig" source "drivers/phy/sunplus/Kconfig" source "drivers/phy/tegra/Kconfig" +source "drivers/phy/thead/Kconfig" source "drivers/phy/ti/Kconfig" source "drivers/phy/xilinx/Kconfig" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index f49d83f00a3d87..4604522548c913 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -48,5 +48,6 @@ obj-$(CONFIG_GENERIC_PHY) += allwinner/ \ starfive/ \ sunplus/ \ tegra/ \ + thead/ \ ti/ \ xilinx/ diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig index 50b0005acf6634..5fdf18fce4995c 100644 --- a/drivers/phy/spacemit/Kconfig +++ b/drivers/phy/spacemit/Kconfig @@ -23,3 +23,19 @@ config PHY_SPACEMIT_K1_USB2 help Enable this to support K1 USB 2.0 PHY driver. This driver takes care of enabling and clock setup and will be used by K1 udc/ehci/otg/xhci driver. + +config PHY_SPACEMIT_K3_COMMON_OPS + tristate + select MFD_SYSCON + select GENERIC_PHY + +config PHY_SPACEMIT_K3_COMBO_PHY + tristate "SpacemiT K3 USB3/PCIe PHY support" + depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF + depends on COMMON_CLK + select PHY_SPACEMIT_K3_COMMON_OPS + help + Enable this to support K3 USB3/PCIe combo PHY driver. This + driver takes care of enabling and clock setup and will be used + by K3 dwc3 driver. + If unsure, say N. diff --git a/drivers/phy/spacemit/Makefile b/drivers/phy/spacemit/Makefile index a821a21d614229..f679c1851b4f0b 100644 --- a/drivers/phy/spacemit/Makefile +++ b/drivers/phy/spacemit/Makefile @@ -1,3 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_PHY_SPACEMIT_K1_PCIE) += phy-k1-pcie.o obj-$(CONFIG_PHY_SPACEMIT_K1_USB2) += phy-k1-usb2.o +obj-$(CONFIG_PHY_SPACEMIT_K3_COMBO_PHY) += phy-k3-combo.o +obj-$(CONFIG_PHY_SPACEMIT_K3_COMMON_OPS) += phy-k3-common.o diff --git a/drivers/phy/spacemit/phy-k1-usb2.c b/drivers/phy/spacemit/phy-k1-usb2.c index e8c1e26428a910..8df12eeb20b184 100644 --- a/drivers/phy/spacemit/phy-k1-usb2.c +++ b/drivers/phy/spacemit/phy-k1-usb2.c @@ -51,6 +51,9 @@ #define PHY_K1_HS_HOST_DISC 0x40 #define PHY_K1_HS_HOST_DISC_CLR BIT(0) +#define PHY_K3_HS_HOST_DISC 0x20 +#define PHY_K3_HS_HOST_DISC_CLR BIT(8) + #define PHY_PLL_DIV_CFG 0x98 #define PHY_FDIV_FRACT_8_15 GENMASK(7, 0) #define PHY_FDIV_FRACT_16_19 GENMASK(11, 8) @@ -144,7 +147,7 @@ static int spacemit_usb2phy_exit(struct phy *phy) return 0; } -static int spacemit_usb2phy_disconnect(struct phy *phy, int port) +static int spacemit_k1_usb2phy_disconnect(struct phy *phy, int port) { struct spacemit_usb2phy *sphy = phy_get_drvdata(phy); @@ -154,10 +157,27 @@ static int spacemit_usb2phy_disconnect(struct phy *phy, int port) return 0; } -static const struct phy_ops spacemit_usb2phy_ops = { +static int spacemit_k3_usb2phy_disconnect(struct phy *phy, int port) +{ + struct spacemit_usb2phy *sphy = phy_get_drvdata(phy); + + regmap_update_bits(sphy->regmap_base, PHY_K3_HS_HOST_DISC, + PHY_K3_HS_HOST_DISC_CLR, PHY_K3_HS_HOST_DISC_CLR); + + return 0; +} + +static const struct phy_ops spacemit_k1_usb2phy_ops = { .init = spacemit_usb2phy_init, .exit = spacemit_usb2phy_exit, - .disconnect = spacemit_usb2phy_disconnect, + .disconnect = spacemit_k1_usb2phy_disconnect, + .owner = THIS_MODULE, +}; + +static const struct phy_ops spacemit_k3_usb2phy_ops = { + .init = spacemit_usb2phy_init, + .exit = spacemit_usb2phy_exit, + .disconnect = spacemit_k3_usb2phy_disconnect, .owner = THIS_MODULE, }; @@ -166,12 +186,15 @@ static int spacemit_usb2phy_probe(struct platform_device *pdev) struct phy_provider *phy_provider; struct device *dev = &pdev->dev; struct spacemit_usb2phy *sphy; + const struct phy_ops *ops; void __iomem *base; sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL); if (!sphy) return -ENOMEM; + ops = device_get_match_data(dev); + sphy->clk = devm_clk_get_prepared(&pdev->dev, NULL); if (IS_ERR(sphy->clk)) return dev_err_probe(dev, PTR_ERR(sphy->clk), "Failed to get clock\n"); @@ -184,7 +207,7 @@ static int spacemit_usb2phy_probe(struct platform_device *pdev) if (IS_ERR(sphy->regmap_base)) return dev_err_probe(dev, PTR_ERR(sphy->regmap_base), "Failed to init regmap\n"); - sphy->phy = devm_phy_create(dev, NULL, &spacemit_usb2phy_ops); + sphy->phy = devm_phy_create(dev, NULL, ops); if (IS_ERR(sphy->phy)) return dev_err_probe(dev, PTR_ERR(sphy->phy), "Failed to create phy\n"); @@ -195,7 +218,8 @@ static int spacemit_usb2phy_probe(struct platform_device *pdev) } static const struct of_device_id spacemit_usb2phy_dt_match[] = { - { .compatible = "spacemit,k1-usb2-phy", }, + { .compatible = "spacemit,k1-usb2-phy", .data = &spacemit_k1_usb2phy_ops }, + { .compatible = "spacemit,k3-usb2-phy", .data = &spacemit_k3_usb2phy_ops }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, spacemit_usb2phy_dt_match); diff --git a/drivers/phy/spacemit/phy-k3-combo.c b/drivers/phy/spacemit/phy-k3-combo.c new file mode 100644 index 00000000000000..0bc08bb6247a80 --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-combo.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * phy-k3-combo.c - SpacemiT K3 PCIe/USB3 Combo PHY Driver + * + * Copyright (c) 2025 SpacemiT Technology Co. Ltd + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "phy-k3-common.h" + +/* + * The PCIE/USB Subsystem on SpacemiT K3 have 3 single lane PIPE3 PHYs + * (PHY2/3/4) shared by PCIE PortC/D and USB3 PortB/C/D. + * + * PMUA_PCIE_SUBSYS_MGMT[4:0] + * + * bit4 = 0 : PCIe A X8 mode, all 8 lanes dedicated to PCIe Port A + * 1 : PHY lanes shared between PCIe or USB according to [3:0] + * + * All PHY matrix combinations according to [4:0]: + * + * 0x0X : PCIe-A X8 + * 0x10 : PCIe-C x2 (PHY2+PHY3) + PCIe-D x1 (PHY4) + * 0x11 : PCIe-C x2 (PHY2+PHY3) + USB-D (PHY4) + * 0x12 : PCIe-C x1 (PHY2) + USB-C (PHY3) + * 0x13 : PCIe-C x1 (PHY2) + USB-C (PHY3) + USB-D (PHY4) + * 0x14 : PCIe-C x1 (PHY3) + USB-B (PHY2) + * 0x15 : PCIe-C x1 (PHY3) + USB-B (PHY2) + USB-D (PHY4) + * 0x16 : USB-B (PHY2) + USB-C (PHY3) + PCIe D x1 (PHY4) + * 0x17 : USB-B (PHY2) + USB-C (PHY3) + USB-D (PHY4) + * + * So any USB Port B/C/D operation requires PCIe A X8 mode to be disabled. + */ +#define PMUA_PCIE_SUBSYS_MGMT 0x1d8 +#define PU_MATRIX_CONF_MASK GENMASK(4, 0) + +#define COMBPHY_MAX_SUBPHYS 6 + +struct k3_combo_phy { + struct device *dev; + struct k3_lane_group groups[COMBPHY_MAX_SUBPHYS]; + void __iomem *base; + struct regmap *apb_spare; +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group0 = { + .lanes = 2, + .config = 0xff, + .mask = 0x00, + .offsets = { + 0x0, 0x400 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group1 = { + .lanes = 2, + .config = 0xff, + .mask = 0x00, + .offsets = { + 0x100000, 0x100400 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group2 = { + .lanes = 1, + .config = 0x14, + .mask = 0x14, + .offsets = { + 0x200000 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group3 = { + .lanes = 1, + .config = 0x12, + .mask = 0x12, + .offsets = { + 0x300000 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group4 = { + .lanes = 1, + .config = 0x11, + .mask = 0x11, + .offsets = { + 0x400000 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group5 = { + .lanes = 1, + .config = 0xff, + .mask = 0x00, + .offsets = { + 0x500000 + }, +}; + +static const struct k3_phy_lane_group_data *k3_combphy_lane_datas[] = { + &k3_combphy_lane_group0, + &k3_combphy_lane_group1, + &k3_combphy_lane_group2, + &k3_combphy_lane_group3, + &k3_combphy_lane_group4, + &k3_combphy_lane_group5, +}; + +static int k3_combo_phy_init_lanes(struct k3_combo_phy *phy, unsigned int config) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(k3_combphy_lane_datas); i++) { + const struct k3_phy_lane_group_data *data = k3_combphy_lane_datas[i]; + struct k3_lane_group *lg = &phy->groups[i]; + const struct phy_ops *ops; + bool is_usb; + + is_usb = (data->mask & config) == data->config; + if (is_usb) + ops = &k3_usb3_phy_ops; + else + ops = &k3_pcie_phy_ops; + + dev_dbg(phy->dev, "phy %d is %s\n", i, is_usb ? "usb" : "pcie"); + + lg->phy = devm_phy_create(phy->dev, NULL, ops); + if (IS_ERR(lg->phy)) + return PTR_ERR(lg->phy); + + lg->is_pcie = !is_usb; + lg->data = data; + lg->base = phy->base; + phy_set_drvdata(lg->phy, lg); + } + + return 0; +} + +static int k3_combo_phy_update_config(struct regmap *apmu, unsigned int config) +{ + if (config & ~PU_MATRIX_CONF_MASK) + return -EINVAL; + + return regmap_update_bits(apmu, PMUA_PCIE_SUBSYS_MGMT, PU_MATRIX_CONF_MASK, config); +} + +static struct phy *k3_combo_phy_xlate(struct device *dev, const struct of_phandle_args *args) +{ + struct k3_combo_phy *phy = dev_get_drvdata(dev); + struct k3_lane_group *lg; + + if (args->args_count != 2) { + dev_err(dev, "Invalid number of arguments\n"); + return ERR_PTR(-EINVAL); + } + + if (args->args[0] >= ARRAY_SIZE(k3_combphy_lane_datas)) { + dev_err(dev, "Invalid PHY id\n"); + return ERR_PTR(-EINVAL); + } + + lg = &phy->groups[args->args[0]]; + + if ((lg->is_pcie && args->args[1] != PHY_TYPE_PCIE) || + (!lg->is_pcie && args->args[1] != PHY_TYPE_USB3)) { + dev_err(dev, "Invalid PHY mode\n"); + return ERR_PTR(-EINVAL); + } + + return lg->phy; +} + +static int k3_combo_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct phy_provider *provider; + struct k3_combo_phy *phy; + struct regmap *apmu; + u32 config = 0; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(phy->base)) + return PTR_ERR(phy->base); + + phy->apb_spare = syscon_regmap_lookup_by_phandle(node, "spacemit,apb-spare"); + if (IS_ERR(phy->apb_spare)) + return dev_err_probe(dev, PTR_ERR(phy->apb_spare), + "Failed to find APB SPARE syscon"); + + apmu = syscon_regmap_lookup_by_phandle_args(node, "spacemit,apmu", 1, &config); + if (IS_ERR(apmu)) + return dev_err_probe(dev, PTR_ERR(apmu), + "Failed to find APMU syscon"); + + ret = k3_combo_phy_update_config(apmu, config); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to set lane configuration"); + + phy->dev = dev; + platform_set_drvdata(pdev, phy); + + ret = k3_phy_calibrate(phy->apb_spare); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to calibrate phy"); + + ret = k3_combo_phy_init_lanes(phy, config); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to init lanes"); + + provider = devm_of_phy_provider_register(dev, k3_combo_phy_xlate); + if (IS_ERR(provider)) + return dev_err_probe(dev, PTR_ERR(provider), + "Failed to register provider\n"); + + return 0; +} + +static const struct of_device_id k3_combo_phy_of_match[] = { + { .compatible = "spacemit,k3-combo-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, k3_combo_phy_of_match); + +static struct platform_driver k3_combo_phy_driver = { + .probe = k3_combo_phy_probe, + .driver = { + .name = "spacemit,k3-combo-phy", + .of_match_table = k3_combo_phy_of_match, + }, +}; +module_platform_driver(k3_combo_phy_driver); + +MODULE_DESCRIPTION("SpacemiT K3 USB3/PCIe combo PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/spacemit/phy-k3-common.c b/drivers/phy/spacemit/phy-k3-common.c new file mode 100644 index 00000000000000..0b289e63db999a --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-common.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "phy-k3-common.h" + +/* PHY Registers */ +#define PHY_VERSION 0x0 + +#define PHY_RESET_CFG 0x04 + +#define PHY_RESET_RXBUF_RST BIT(0) +#define PHY_RESET_SOFT_RST_PCS BIT(1) +#define PHY_RESET_SOFT_RST_AHB BIT(2) +#define PHY_RESET_EN_SD_AFTER_LOCK BIT(6) + +#define PHY_CLK_CFG 0x08 + +#define PHY_CLK_PLL_READY BIT(0) +#define PHY_CLK_TXCLK_INV BIT(2) +#define PHY_CLK_RXCLK_EN BIT(3) +#define PHY_CLK_TXCLK_EN BIT(4) +#define PHY_CLK_PCLK_EN BIT(5) +#define PHY_CLK_PIPE_PCLK_EN BIT(6) +#define PHY_CLK_REFCLK_FREQ GENMASK(10, 7) +#define PHY_CLK_REFCLK_24M 2 +#define PHY_CLK_SW_INIT_DONE BIT(11) +#define PHY_CLK_PU_SSC_OUT BIT(23) + +#define PHY_MODE_CFG 0x0C + +#define PHY_MODE_PCIE_INT_EN BIT(0) +#define PHY_MODE_LFPS_TPERIOD GENMASK(9, 8) +#define PHY_MODE_LFPS_TPERIOD_USB 3 + +#define PHY_PU_SEL 0x40 + +#define PHY_PU_CFG_STATUS BIT(9) +#define PHY_PU_OVRD_STATUS BIT(10) + +#define PHY_PU_CK_REG 0x54 + +#define PHY_PU_REFCLK_100 BIT(25) + +#define PHY_PLL_REG1 0x58 + +#define PHY_PLL_FREF_SEL GENMASK(15, 13) +#define PHY_PLL_FREF_24M 0x1 +#define PHY_PLL_SSC_DEP_SEL GENMASK(27, 24) +#define PHY_PLL_SSC_5000PPM 0xa +#define PHY_PLL_SSC_MODE GENMASK(29, 28) +#define PHY_PLL_SSC_MODE_CENTER_SPREAD 0 +#define PHY_PLL_SSC_MODE_UP_SPREAD 1 +#define PHY_PLL_SSC_MODE_DOWN_SPREAD 2 +#define PHY_PLL_SSC_MODE_DOWN_SPREAD1 3 + +#define PHY_PLL_REG2 0x5c + +#define PHY_PLL_SEL_REF100 BIT(21) + +/* PHY RX Register Definitions */ +#define PHY_RX_REG_A 0x60 + +#define PHY_RX_REG0_MASK GENMASK(7, 0) +#define PHY_RX_REG1_MASK GENMASK(15, 8) +#define PHY_RX_REG2_MASK GENMASK(23, 16) +#define PHY_RX_REG3_MASK GENMASK(31, 24) + +#define PHY_RX_REG0_RLOAD BIT(4) +#define PHY_RX_REG1_RTERM GENMASK(11, 8) +#define PHY_RX_REG1_RC_CALI GENMASK(15, 12) +#define PHY_RX_REG2_CSEL GENMASK(19, 16) +#define PHY_RX_REG2_FORCE_CSEL BIT(20) +#define PHY_RX_REG2_PSEL GENMASK(23, 21) +#define PHY_RX_REG3_I_LOAD GENMASK(26, 24) +#define PHY_RX_REG3_SEL_CBOOST_CODE BIT(27) +#define PHY_RX_REG3_ADJ_BIAS GENMASK(29, 28) +#define PHY_RX_REG3_RDEG1 GENMASK(31, 30) + +#define PHY_RX_REG_B 0x64 + +#define PHY_RX_REG4_MASK GENMASK(7, 0) +#define PHY_RX_REG5_MASK GENMASK(15, 8) +#define PHY_RX_REG6_MASK GENMASK(23, 16) + +#define PHY_RX_REGB_MASK GENMASK(23, 0) + +#define PHY_RX_REG4_RDEG2 GENMASK(2, 1) +#define PHY_RX_REG4_ENVOS BIT(4) +#define PHY_RX_REG4_RTERM_SEL BIT(5) +#define PHY_RX_REG4_MANUAL_CFG BIT(7) +#define PHY_RX_REG5_RCELL_VCM GENMASK(11, 8) +#define PHY_RX_REG5_RCELL_BIAS GENMASK(15, 12) +#define PHY_RX_REG6_H1_REG GENMASK(19, 16) +#define PHY_RX_REG6_ADAPT_GAIN GENMASK(21, 20) +#define PHY_RX_REG6_BYPASS_ADPT BIT(22) + +#define PHY_ADPT_CFG0 0x140 +#define PHY_ADPT_AFE_RST_OVRD_EN BIT(1) +#define PHY_ADPT_AFE_RST_OVRD_VAL BIT(4) + +#define PHY_RXEQ_TIME 0xb4 +#define PHY_RXEQ_TIME_OVRD_POST_C_SOC BIT(21) +#define PHY_RXEQ_TIME_CFG_AMP_SOC GENMASK(23, 22) +#define PHY_RXEQ_TIME_AMP_SOC_650M 0 +#define PHY_RXEQ_TIME_AMP_SOC_800M 1 +#define PHY_RXEQ_TIME_AMP_SOC_870M 2 +#define PHY_RXEQ_TIME_AMP_SOC_900M 3 +#define PHY_RXEQ_TIME_OVRD_AMP_SOC BIT(24) + +#define PCIE_PU_ADDR_CLK_CFG 0x0008 +#define PHY_CLK_PLL_READY BIT(0) +#define PCIE_INITAL_TIMER GENMASK(6, 3) +#define CFG_INTERNAL_TIMER_ADJ GENMASK(10, 7) +#define CFG_SW_PHY_INIT_DONE BIT(11) + +/* Lane RX/TX configuration (per‑lane, at lane_base) */ +#define PCIE_RX_REG1 0x050 +#define PCIE_RX_REFCLK_MODE GENMASK(1, 0) +#define PCIE_RX_REFCLK_MODE_DRIVER 1 +#define PCIE_RX_SEL_TRI_CODE BIT(2) +#define PCIE_RX_LEGACY GENMASK(15, 8) +#define PCIE_RX_LEGACY_DEFAULT 0x65 + +#define PCIE_TX_REG1 0x064 + +#define PCIE_PLL_TIMEOUT 500000 +#define PCIE_POLL_DELAY 500 + +static int k3_usb3phy_init_single(struct k3_lane_group *lg, void __iomem *base) +{ + struct phy *phy = lg->phy; + u32 val, tmp; + int ret; + + /* Do not wait CDR lock before sampling data */ + val = readl(base + PHY_RESET_CFG); + val = u32_replace_bits(val, 0, PHY_RESET_EN_SD_AFTER_LOCK); + writel(val, base + PHY_RESET_CFG); + + /* Power down 100MHz refclk buffer */ + val = readl(base + PHY_PU_CK_REG); + val = u32_replace_bits(val, 0, PHY_PU_REFCLK_100); + writel(val, base + PHY_PU_CK_REG); + + /* Program PLL REG1 configure the SSC */ + val = FIELD_PREP(PHY_PLL_SSC_MODE, PHY_PLL_SSC_MODE_DOWN_SPREAD1) | + FIELD_PREP(PHY_PLL_SSC_DEP_SEL, PHY_PLL_SSC_5000PPM) | + FIELD_PREP(PHY_PLL_FREF_SEL, PHY_PLL_FREF_24M); + writel(val, base + PHY_PLL_REG1); + + /* Un-select 100MHz PLL reference */ + val = readl(base + PHY_PLL_REG2); + val = u32_replace_bits(val, 0, PHY_PLL_SEL_REF100); + writel(val, base + PHY_PLL_REG2); + + /* USB LFPS period configuration */ + val = readl(base + PHY_MODE_CFG); + val = u32_replace_bits(val, PHY_MODE_LFPS_TPERIOD_USB, PHY_MODE_LFPS_TPERIOD); + writel(val, base + PHY_MODE_CFG); + + /* Force AFE adaptation reset */ + val = readl(base + PHY_ADPT_CFG0); + val |= PHY_ADPT_AFE_RST_OVRD_EN | PHY_ADPT_AFE_RST_OVRD_VAL; + writel(val, base + PHY_ADPT_CFG0); + + /* Override driver amplitude value to 900m */ + val = readl(base + PHY_RXEQ_TIME); + val |= PHY_RXEQ_TIME_OVRD_AMP_SOC; + val = u32_replace_bits(val, PHY_RXEQ_TIME_AMP_SOC_900M, PHY_RXEQ_TIME_CFG_AMP_SOC); + writel(val, base + PHY_RXEQ_TIME); + + /* Configure RX parameters */ + val = PHY_RX_REG0_RLOAD | + FIELD_PREP(PHY_RX_REG1_RTERM, 0x8) | + FIELD_PREP(PHY_RX_REG1_RC_CALI, 0x7) | + FIELD_PREP(PHY_RX_REG2_CSEL, 0x8) | + PHY_RX_REG2_FORCE_CSEL | + FIELD_PREP(PHY_RX_REG2_PSEL, 0x4) | + FIELD_PREP(PHY_RX_REG3_I_LOAD, 0x7) | + PHY_RX_REG3_SEL_CBOOST_CODE | + FIELD_PREP(PHY_RX_REG3_ADJ_BIAS, 0x1) | + FIELD_PREP(PHY_RX_REG3_RDEG1, 0x3); + writel(val, base + PHY_RX_REG_A); + + val = readl(base + PHY_RX_REG_B); + tmp = FIELD_PREP(PHY_RX_REG4_RDEG2, 0x2) | + PHY_RX_REG4_ENVOS | PHY_RX_REG4_RTERM_SEL | PHY_RX_REG4_MANUAL_CFG | + FIELD_PREP(PHY_RX_REG5_RCELL_VCM, 0x8) | + FIELD_PREP(PHY_RX_REG5_RCELL_BIAS, 0x8) | + FIELD_PREP(PHY_RX_REG6_H1_REG, 0x8) | + FIELD_PREP(PHY_RX_REG6_ADAPT_GAIN, 0x2); + val = u32_replace_bits(val, tmp, PHY_RX_REGB_MASK); + writel(val, base + PHY_RX_REG_B); + + /* + * Inform PHY that all PLL-related configuration is done. + * PLL will not start locking until PHY_CLK_SW_INIT_DONE is set. + */ + val = PHY_CLK_SW_INIT_DONE | PHY_CLK_PU_SSC_OUT | + FIELD_PREP(PHY_CLK_REFCLK_FREQ, PHY_CLK_REFCLK_24M) | + PHY_CLK_RXCLK_EN | PHY_CLK_TXCLK_EN | + PHY_CLK_PCLK_EN | PHY_CLK_PIPE_PCLK_EN; + writel(val, base + PHY_CLK_CFG); + + ret = readl_poll_timeout(base + PHY_CLK_CFG, val, + (val & PHY_CLK_PLL_READY), + PCIE_POLL_DELAY, PCIE_PLL_TIMEOUT); + if (ret) { + dev_err(&phy->dev, "PHY PLL polling timeout\n"); + return ret; + } + + return 0; +} + +static int k3_usb3phy_init(struct phy *phy) +{ + struct k3_lane_group *lg = phy_get_drvdata(phy); + int ret, i; + + for (i = 0; i < lg->data->lanes; i++) { + ret = k3_usb3phy_init_single(lg, lg->base + lg->data->offsets[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +const struct phy_ops k3_usb3_phy_ops = { + .init = k3_usb3phy_init, + .owner = THIS_MODULE, +}; +EXPORT_SYMBOL_GPL(k3_usb3_phy_ops); + +static int k3_pcie_phy_init(struct phy *phy) +{ + struct k3_lane_group *lg = phy_get_drvdata(phy); + void __iomem *phy_base = lg->base + lg->data->offsets[0]; + u32 val; + int ret; + int i; + + val = readl(phy_base + PHY_PLL_REG1); + val = u32_replace_bits(val, 0x2, GENMASK(15, 12)); + writel(val, phy_base + PHY_PLL_REG1); + + val = readl(phy_base + PHY_PLL_REG2); + val = u32_replace_bits(val, 0, BIT(21)); + writel(val, phy_base + PHY_PLL_REG2); + + for (i = 0; i < lg->data->lanes; i++) { + void __iomem *lane_base = lg->base + lg->data->offsets[i]; + + val = readl(lane_base + PCIE_RX_REG1); + val = u32_replace_bits(val, 0, 0x3); + writel(val, lane_base + PCIE_RX_REG1); + } + + val = readl(phy_base + PHY_PLL_REG2); + val |= BIT(20); + writel(val, phy_base + PHY_PLL_REG2); + + /* The write is needed as clock requires renegotiation */ + val = FIELD_PREP(PCIE_RX_REFCLK_MODE, PCIE_RX_REFCLK_MODE_DRIVER) | + PCIE_RX_SEL_TRI_CODE | + FIELD_PREP(PCIE_RX_LEGACY, PCIE_RX_LEGACY_DEFAULT); + writel(val, phy_base + PCIE_RX_REG1); + + /* pll_reg1 of lane0, disable SSC: pll[27:24] = 0 */ + val = readl(phy_base + PHY_PLL_REG1); + val = u32_replace_bits(val, 0, GENMASK(27, 24)); + writel(val, phy_base + PHY_PLL_REG1); + + for (i = 0; i < lg->data->lanes; i++) { + void __iomem *lane_base = lg->base + lg->data->offsets[i]; + + /* set cfg_tx_send_dummy_data to be 1'b1 for disable dash data */ + val = readl(lane_base + PHY_PU_SEL); + val = u32_replace_bits(val, 1, BIT(13)); + writel(val, lane_base + PHY_PU_SEL); + + /* disable en_sample_data_after_cdr_locked */ + val = readl(lane_base + PHY_RESET_CFG); + val = u32_replace_bits(val, 0, BIT(6)); + writel(val, lane_base + PHY_RESET_CFG); + + /* Dynamic Lock */ + val = readl(lane_base + PHY_MODE_CFG); + val = u32_replace_bits(val, 1, BIT(2)); + writel(val, lane_base + PHY_MODE_CFG); + + val = FIELD_PREP(PHY_RX_REG0_MASK, 0x10) | + FIELD_PREP(PHY_RX_REG1_MASK, 0x78) | + FIELD_PREP(PHY_RX_REG2_MASK, 0x98) | + FIELD_PREP(PHY_RX_REG3_MASK, 0xdf); + writel(val, lane_base + PHY_RX_REG_A); + + val = readl(lane_base + PHY_RX_REG_B); + val &= ~PHY_RX_REGB_MASK; + val |= FIELD_PREP(PHY_RX_REG4_MASK, 0xb4) | + FIELD_PREP(PHY_RX_REG5_MASK, 0x88) | + FIELD_PREP(PHY_RX_REG6_MASK, 0x28); + writel(val, lane_base + PHY_RX_REG_B); + + /* Set init done */ + val = readl(lane_base + PCIE_PU_ADDR_CLK_CFG); + val = u32_replace_bits(val, 1, CFG_SW_PHY_INIT_DONE); + writel(val, lane_base + PCIE_PU_ADDR_CLK_CFG); + } + + ret = readl_poll_timeout(phy_base + PCIE_PU_ADDR_CLK_CFG, val, + (val & PHY_CLK_PLL_READY), PCIE_POLL_DELAY, + PCIE_PLL_TIMEOUT); + if (ret) { + dev_err(&lg->phy->dev, "PHY PLL lock timeout\n"); + return ret; + } + + return 0; +} + +const struct phy_ops k3_pcie_phy_ops = { + .init = k3_pcie_phy_init, + .owner = THIS_MODULE, +}; +EXPORT_SYMBOL_GPL(k3_pcie_phy_ops); + +/* PHY rcal init requires APB_SPARE regmap access */ + +#define APB_SPARE_PU_CAL 0x178 +#define PU_CAL BIT(17) + +#define APB_SPARE_RCAL_HSIO 0x17c +#define APB_SPARE_PU_CAL_DONE BIT(8) +#define RCAL_OVRD_PTRIM GENMASK(23, 20) +#define RCAL_OVRD_NTRIM GENMASK(27, 24) +#define RCAL_OVRD_PTRIM_EN BIT(28) +#define RCAL_OVRD_NTRIM_EN BIT(29) +#define RCAL_OVRD_STABLE_VAL BIT(30) +#define RCAL_OVRD_STABLE_EN BIT(31) + +#define RCAL_OVRD_TRIM_EN (RCAL_OVRD_NTRIM_EN | RCAL_OVRD_PTRIM_EN) +#define RCAL_OVRD_TRIM_MASK (RCAL_OVRD_NTRIM | RCAL_OVRD_PTRIM) + +#define PU_CAL_TIMEOUT 2000000 + +static DEFINE_MUTEX(calibrate_lock); + +int k3_phy_calibrate(struct regmap *apb_spare) +{ + unsigned int val = 0; + int ret; + + guard(mutex)(&calibrate_lock); + + regmap_read(apb_spare, APB_SPARE_RCAL_HSIO, &val); + if (val & APB_SPARE_PU_CAL_DONE) + return 0; + + regmap_update_bits(apb_spare, APB_SPARE_PU_CAL, PU_CAL, + PU_CAL); + + ret = regmap_read_poll_timeout(apb_spare, APB_SPARE_RCAL_HSIO, + val, (val & APB_SPARE_PU_CAL_DONE), PCIE_POLL_DELAY, + PU_CAL_TIMEOUT); + + if (ret) + regmap_update_bits(apb_spare, APB_SPARE_RCAL_HSIO, + RCAL_OVRD_TRIM_EN | RCAL_OVRD_STABLE_VAL | + RCAL_OVRD_TRIM_MASK | RCAL_OVRD_STABLE_EN, + RCAL_OVRD_TRIM_EN | RCAL_OVRD_STABLE_VAL | + FIELD_PREP(RCAL_OVRD_NTRIM, 0x6) | + FIELD_PREP(RCAL_OVRD_PTRIM, 0xa) | + RCAL_OVRD_STABLE_EN); + + return 0; +} +EXPORT_SYMBOL_GPL(k3_phy_calibrate); + +MODULE_DESCRIPTION("SpacemiT K3 PHY common ops"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/spacemit/phy-k3-common.h b/drivers/phy/spacemit/phy-k3-common.h new file mode 100644 index 00000000000000..49009c3c313a29 --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-common.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _PHY_K3_COMMON_H +#define _PHY_K3_COMMON_H + +#include + +struct k3_phy_lane_group_data { + u32 lanes; + u8 config; + u8 mask; + u32 offsets[] __counted_by(lanes); +}; + +struct k3_lane_group { + const struct k3_phy_lane_group_data *data; + void __iomem *base; + struct phy *phy; + bool is_pcie; +}; + +extern const struct phy_ops k3_pcie_phy_ops; +extern const struct phy_ops k3_usb3_phy_ops; + +int k3_phy_calibrate(struct regmap *apb_spare); + +#endif diff --git a/drivers/phy/thead/Kconfig b/drivers/phy/thead/Kconfig new file mode 100644 index 00000000000000..14012db5973c4b --- /dev/null +++ b/drivers/phy/thead/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +config PHY_TH1520_USB + tristate "USB PHY driver for T-Head TH1520 SoC" + depends on ARCH_THEAD || COMPILE_TEST + depends on COMMON_CLK + depends on HAS_IOMEM + depends on OF + depends on RESET_CONTROLLER + select GENERIC_PHY + default ARCH_THEAD + help + Enable support for the USB PHY on the T-Head TH1520 SoC. diff --git a/drivers/phy/thead/Makefile b/drivers/phy/thead/Makefile new file mode 100644 index 00000000000000..5b459bc7004bda --- /dev/null +++ b/drivers/phy/thead/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_PHY_TH1520_USB) += phy-th1520-usb.o diff --git a/drivers/phy/thead/phy-th1520-usb.c b/drivers/phy/thead/phy-th1520-usb.c new file mode 100644 index 00000000000000..c87bd779bbb74b --- /dev/null +++ b/drivers/phy/thead/phy-th1520-usb.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Institute of Software, Chinese Academy of Sciences (ISCAS) + * + * Authors: + * Icenowy Zheng + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_SYSCON_OFFSET 0xf000 + +/* All the below registers are in the USB syscon region */ +#define USB_CLK_GATE_STS 0x0 +#define USB_LOGIC_ANALYZER_TRACE_STS0 0x4 +#define USB_LOGIC_ANALYZER_TRACE_STS1 0x8 +#define USB_GPIO 0xc +#define USB_DEBUG_STS0 0x10 +#define USB_DEBUG_STS1 0x14 +#define USB_DEBUG_STS2 0x18 +#define USBCTL_CLK_CTRL0 0x1c +#define USBPHY_CLK_CTRL1 0x20 +#define USBPHY_TEST_CTRL0 0x24 +#define USBPHY_TEST_CTRL1 0x28 +#define USBPHY_TEST_CTRL2 0x2c +#define USBPHY_TEST_CTRL3 0x30 +#define USB_SSP_EN 0x34 +#define USB_HADDR_SEL 0x38 +#define USB_SYS 0x3c +#define USB_HOST_STATUS 0x40 +#define USB_HOST_CTRL 0x44 +#define USBPHY_HOST_CTRL 0x48 +#define USBPHY_HOST_STATUS 0x4c +#define USB_TEST_REG0 0x50 +#define USB_TEST_REG1 0x54 +#define USB_TEST_REG2 0x58 +#define USB_TEST_REG3 0x5c + +#define USB_SYS_COMMONONN BIT(0) + +#define USB_SSP_EN_REF_SSP_EN BIT(0) + +struct th1520_usb_phy { + struct platform_device *pdev; + struct phy *phy; + struct regmap *regmap; + struct clk *ref_clk; + struct reset_control *phy_reset; +}; + +static int th1520_usb_phy_init(struct phy *phy) +{ + struct th1520_usb_phy *th1520_phy = phy_get_drvdata(phy); + int ret; + + ret = clk_prepare_enable(th1520_phy->ref_clk); + if (ret) + return ret; + + ret = reset_control_assert(th1520_phy->phy_reset); + if (ret) + goto err_disable_clk; + + /* + * Do some initial PHY setup: + * - Set COMMONONN to allow the PHY to automatically power down. + * - Set REF_SSP_EN to enable feeding reference clock to SuperSpeed + * PHY clock PLL. + */ + regmap_set_bits(th1520_phy->regmap, USB_SYS, USB_SYS_COMMONONN); + regmap_set_bits(th1520_phy->regmap, USB_SSP_EN, USB_SSP_EN_REF_SSP_EN); + + ret = reset_control_deassert(th1520_phy->phy_reset); + if (ret) + goto err_disable_clk; + + udelay(10); + + return 0; + +err_disable_clk: + clk_disable_unprepare(th1520_phy->ref_clk); + return ret; +} + +static int th1520_usb_phy_exit(struct phy *phy) +{ + struct th1520_usb_phy *th1520_phy = phy_get_drvdata(phy); + int ret; + + ret = reset_control_assert(th1520_phy->phy_reset); + if (ret) + return ret; + + clk_disable_unprepare(th1520_phy->ref_clk); + + return 0; +} + +static const struct phy_ops th1520_usb_phy_ops = { + .init = th1520_usb_phy_init, + .exit = th1520_usb_phy_exit, + .owner = THIS_MODULE, +}; + +static const struct regmap_config phy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = USB_TEST_REG3, +}; + +static int th1520_usb_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct th1520_usb_phy *th1520_phy; + struct reset_control *bus_reset; + void __iomem *base; + int ret; + + th1520_phy = devm_kzalloc(dev, sizeof(*th1520_phy), GFP_KERNEL); + if (!th1520_phy) + return -ENOMEM; + + th1520_phy->pdev = pdev; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + th1520_phy->ref_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(th1520_phy->ref_clk)) + return PTR_ERR(th1520_phy->ref_clk); + + /* De-assert the bus reset and leave it that way */ + bus_reset = devm_reset_control_get_exclusive_deasserted(dev, "bus"); + if (IS_ERR(bus_reset)) + return PTR_ERR(bus_reset); + + th1520_phy->phy_reset = devm_reset_control_get_exclusive(dev, "phy"); + if (IS_ERR(th1520_phy->phy_reset)) + return PTR_ERR(th1520_phy->phy_reset); + + /* + * Schematics of several boards (Lichee Module 4A/Milk-V Meles) + * describe this power rail as always-on. + */ + ret = devm_regulator_get_enable(dev, "avdd33-usb3"); + if (ret) + return ret; + + th1520_phy->regmap = devm_regmap_init_mmio_clk(dev, "bus", + base + USB_SYSCON_OFFSET, + &phy_regmap_config); + if (IS_ERR(th1520_phy->regmap)) + return dev_err_probe(dev, PTR_ERR(th1520_phy->regmap), + "Failed to init regmap\n"); + + th1520_phy->phy = devm_phy_create(dev, dev->of_node, &th1520_usb_phy_ops); + if (IS_ERR(th1520_phy->phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(th1520_phy->phy); + } + + phy_set_drvdata(th1520_phy->phy, th1520_phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id th1520_usb_phy_of_table[] = { + { .compatible = "thead,th1520-usb-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, th1520_usb_phy_of_table); + +static struct platform_driver th1520_usb_phy_driver = { + .driver = { + .name = "th1520-usb-phy", + .of_match_table = th1520_usb_phy_of_table, + }, + .probe = th1520_usb_phy_probe, +}; + +module_platform_driver(th1520_usb_phy_driver); + +MODULE_DESCRIPTION("T-Head TH1520 USB PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pinctrl/spacemit/pinctrl-k1.c b/drivers/pinctrl/spacemit/pinctrl-k1.c index b0be62b1c8161d..95024e2bb5a587 100644 --- a/drivers/pinctrl/spacemit/pinctrl-k1.c +++ b/drivers/pinctrl/spacemit/pinctrl-k1.c @@ -795,7 +795,7 @@ static int spacemit_pin_set_config(struct spacemit_pinctrl *pctrl, void __iomem *reg; unsigned int mux; - if (!pin) + if (!spin) return -EINVAL; reg = spacemit_pin_to_reg(pctrl, spin->pin); diff --git a/drivers/platform/loongarch/Kconfig b/drivers/platform/loongarch/Kconfig index 447528797d07aa..b87f51c4e4b662 100644 --- a/drivers/platform/loongarch/Kconfig +++ b/drivers/platform/loongarch/Kconfig @@ -16,6 +16,14 @@ menuconfig LOONGARCH_PLATFORM_DEVICES if LOONGARCH_PLATFORM_DEVICES +config CPU_HWMON + bool "Loongson CPU HWMon Driver" + depends on MACH_LOONGSON64 + select HWMON + default y + help + Loongson-3A/3B/3C CPU HWMon (temperature sensor) driver. + config LOONGSON_LAPTOP tristate "Generic Loongson-3 Laptop Driver" depends on ACPI_EC diff --git a/drivers/platform/loongarch/Makefile b/drivers/platform/loongarch/Makefile index f43ab03db1a2d5..8038291bae3aa8 100644 --- a/drivers/platform/loongarch/Makefile +++ b/drivers/platform/loongarch/Makefile @@ -1 +1,2 @@ +obj-$(CONFIG_CPU_HWMON) += cpu_hwmon.o obj-$(CONFIG_LOONGSON_LAPTOP) += loongson-laptop.o diff --git a/drivers/platform/loongarch/cpu_hwmon.c b/drivers/platform/loongarch/cpu_hwmon.c new file mode 100644 index 00000000000000..93a05993745aaa --- /dev/null +++ b/drivers/platform/loongarch/cpu_hwmon.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Loongson Technology Corporation Limited + */ +#include +#include +#include +#include +#include + +#include + +static int nr_packages; +static struct device *cpu_hwmon_dev; + +static int loongson3_cpu_temp(int cpu) +{ + u32 reg; + + reg = iocsr_read32(LOONGARCH_IOCSR_CPUTEMP) & 0xff; + + return (int)((s8)reg) * 1000; +} + +static ssize_t cpu_temp_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int id = (to_sensor_dev_attr(attr))->index - 1; + return sprintf(buf, "CPU %d Temperature\n", id); +} + +static ssize_t get_cpu_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int id = (to_sensor_dev_attr(attr))->index - 1; + int value = loongson3_cpu_temp(id); + return sprintf(buf, "%d\n", value); +} + +static SENSOR_DEVICE_ATTR(temp1_input, 0444, get_cpu_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_label, 0444, cpu_temp_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_input, 0444, get_cpu_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_label, 0444, cpu_temp_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_input, 0444, get_cpu_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_label, 0444, cpu_temp_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp5_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp5_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp7_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp7_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp8_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp8_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp9_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp9_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp10_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp10_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp11_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp11_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp12_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp12_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp13_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp13_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp14_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp14_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp15_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp15_label, 0444, cpu_temp_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp16_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp16_label, 0444, cpu_temp_label, NULL, 4); + +static struct attribute *cpu_hwmon_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_label.dev_attr.attr, + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp6_label.dev_attr.attr, + &sensor_dev_attr_temp7_input.dev_attr.attr, + &sensor_dev_attr_temp7_label.dev_attr.attr, + &sensor_dev_attr_temp8_input.dev_attr.attr, + &sensor_dev_attr_temp8_label.dev_attr.attr, + &sensor_dev_attr_temp9_input.dev_attr.attr, + &sensor_dev_attr_temp9_label.dev_attr.attr, + &sensor_dev_attr_temp10_input.dev_attr.attr, + &sensor_dev_attr_temp10_label.dev_attr.attr, + &sensor_dev_attr_temp11_input.dev_attr.attr, + &sensor_dev_attr_temp11_label.dev_attr.attr, + &sensor_dev_attr_temp12_input.dev_attr.attr, + &sensor_dev_attr_temp12_label.dev_attr.attr, + &sensor_dev_attr_temp13_input.dev_attr.attr, + &sensor_dev_attr_temp13_label.dev_attr.attr, + &sensor_dev_attr_temp14_input.dev_attr.attr, + &sensor_dev_attr_temp14_label.dev_attr.attr, + &sensor_dev_attr_temp15_input.dev_attr.attr, + &sensor_dev_attr_temp15_label.dev_attr.attr, + &sensor_dev_attr_temp16_input.dev_attr.attr, + &sensor_dev_attr_temp16_label.dev_attr.attr, + NULL +}; +static umode_t cpu_hwmon_is_visible(struct kobject *kobj, + struct attribute *attr, int i) +{ + int id = i / 2; + + if (id < nr_packages) + return attr->mode; + return 0; +} + +static struct attribute_group cpu_hwmon_group = { + .attrs = cpu_hwmon_attributes, + .is_visible = cpu_hwmon_is_visible, +}; + +static const struct attribute_group *cpu_hwmon_groups[] = { + &cpu_hwmon_group, + NULL +}; + +static int cpu_initial_threshold = 72000; +static int cpu_thermal_threshold = 96000; +module_param(cpu_thermal_threshold, int, 0644); +MODULE_PARM_DESC(cpu_thermal_threshold, "cpu thermal threshold (96000 (default))"); + +static struct delayed_work thermal_work; + +static void do_thermal_timer(struct work_struct *work) +{ + int i, value, temp_max = 0; + + for (i=0; i temp_max) + temp_max = value; + } + + if (temp_max <= cpu_thermal_threshold) + schedule_delayed_work(&thermal_work, msecs_to_jiffies(5000)); + else + orderly_poweroff(true); +} + +static int __init loongson_hwmon_init(void) +{ + int i, value, temp_max = 0; + + pr_info("Loongson Hwmon Enter...\n"); + + nr_packages = loongson_sysconf.nr_cpus / + loongson_sysconf.cores_per_package; + + cpu_hwmon_dev = hwmon_device_register_with_groups(NULL, "cpu_hwmon", + NULL, cpu_hwmon_groups); + if (IS_ERR(cpu_hwmon_dev)) { + pr_err("Hwmon register fail with %ld!\n", PTR_ERR(cpu_hwmon_dev)); + return PTR_ERR(cpu_hwmon_dev); + } + + for (i = 0; i < nr_packages; i++) { + value = loongson3_cpu_temp(i); + if (value > temp_max) + temp_max = value; + } + + pr_info("Initial CPU temperature is %d (highest).\n", temp_max); + if (temp_max > cpu_initial_threshold) + cpu_thermal_threshold += temp_max - cpu_initial_threshold; + + INIT_DEFERRABLE_WORK(&thermal_work, do_thermal_timer); + schedule_delayed_work(&thermal_work, msecs_to_jiffies(20000)); + + return 0; +} + +static void __exit loongson_hwmon_exit(void) +{ + cancel_delayed_work_sync(&thermal_work); + hwmon_device_unregister(cpu_hwmon_dev); +} + +module_init(loongson_hwmon_init); +module_exit(loongson_hwmon_exit); + +MODULE_AUTHOR("Huacai Chen "); +MODULE_DESCRIPTION("Loongson CPU Hwmon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/loongarch/loongson-laptop.c b/drivers/platform/loongarch/loongson-laptop.c index 61b18ac206c9ee..742a585b0647ce 100644 --- a/drivers/platform/loongarch/loongson-laptop.c +++ b/drivers/platform/loongarch/loongson-laptop.c @@ -200,6 +200,7 @@ static int loongson_hotkey_resume(struct device *dev) bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM); if (bd) { + bl_powered = false; loongson_laptop_backlight_update(bd) ? pr_warn("Loongson_backlight: resume brightness failed") : pr_info("Loongson_backlight: resume brightness %d\n", bd->props.brightness); diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile index 4c71444e453a6c..4dbbc9bd9d87fd 100644 --- a/drivers/platform/mips/Makefile +++ b/drivers/platform/mips/Makefile @@ -2,3 +2,7 @@ obj-$(CONFIG_CPU_HWMON) += cpu_hwmon.o obj-$(CONFIG_RS780E_ACPI) += rs780e-acpi.o obj-$(CONFIG_LS2K_RESET) += ls2k-reset.o + +ifdef CONFIG_MTD +obj-m += pmon_flash.o +endif diff --git a/drivers/platform/mips/cpu_hwmon.c b/drivers/platform/mips/cpu_hwmon.c index 2ac2f31090f96f..93619227f0396e 100644 --- a/drivers/platform/mips/cpu_hwmon.c +++ b/drivers/platform/mips/cpu_hwmon.c @@ -135,9 +135,22 @@ static int __init loongson_hwmon_init(void) { pr_info("Loongson Hwmon Enter...\n"); + /* + * FIXME: Disable CSR-based temperature reading, found only on Loongson + * 3A/B4000 processors. We found that on the Excelsior E23L (3B4000) + * boards, the temperature reading would erratically pin to 239°C, + * triggering immediate thermal shutdown. + * + * We don't yet know about the true cause behind this issue, as both + * boards we own are indeed using non-original heatsinks (but they can + * in fact sustain >= 6 hours of stress testing with s-tui). + * + * For now, disable this feature so that we can keep those systems + * working and Loongson 3A/B4000 can still report their temperatures + * via the Chip Temperature register (loongson_chiptemp). + */ if (cpu_has_csr()) - csr_temp_enable = csr_readl(LOONGSON_CSR_FEATURES) & - LOONGSON_CSRF_TEMP; + csr_temp_enable = 0; if (!csr_temp_enable && !loongson_chiptemp[0]) return -ENODEV; diff --git a/drivers/platform/mips/pmon_flash.c b/drivers/platform/mips/pmon_flash.c new file mode 100644 index 00000000000000..6c5a3c4342b294 --- /dev/null +++ b/drivers/platform/mips/pmon_flash.c @@ -0,0 +1,100 @@ +/* + * Copyright www.lemote.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FLASH_PHYS_ADDR 0x1fc00000 +#define FLASH_SIZE 0x100000 + +#define FLASH_PARTITION0_ADDR 0x00000000 +#define FLASH_PARTITION0_SIZE 0x00100000 + +struct map_info flash_map = { + .name = "flash device", + .size = FLASH_SIZE, + .bankwidth = 1, +}; + +struct mtd_partition flash_parts[] = { + { + .name = "Bootloader", + .offset = FLASH_PARTITION0_ADDR, + .size = FLASH_PARTITION0_SIZE + }, +}; + +#define PARTITION_COUNT ARRAY_SIZE(flash_parts) + +static struct mtd_info *mymtd; + +static int __init init_flash(void) +{ + printk(KERN_NOTICE "Flash flash device: %x at %x\n", + FLASH_SIZE, FLASH_PHYS_ADDR); + + flash_map.phys = FLASH_PHYS_ADDR; + flash_map.virt = ioremap(FLASH_PHYS_ADDR, + FLASH_SIZE); + + if (!flash_map.virt) { + printk("Failed to ioremap\n"); + return -EIO; + } + + simple_map_init(&flash_map); + + mymtd = do_map_probe("jedec_probe", &flash_map); + if (mymtd) { + mtd_device_register(mymtd, flash_parts, PARTITION_COUNT); + printk(KERN_NOTICE "pmon flash device initialized\n"); + return 0; + } + + iounmap((void *)flash_map.virt); + return -ENXIO; +} + +static void __exit cleanup_flash(void) +{ + if (mymtd) { + mtd_device_unregister(mymtd); + map_destroy(mymtd); + } + if (flash_map.virt) { + iounmap((void *)flash_map.virt); + flash_map.virt = 0; + } +} + +module_init(init_flash); +module_exit(cleanup_flash); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yanhua"); +MODULE_DESCRIPTION("MTD map driver for pmon programming module"); diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index f775c6ca1ec1dd..2075e3852053a8 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH Select M or Y here, if you want to provide tablet-mode switch input events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. +config SURFACE_BOOK1_DGPU_SWITCH + tristate "Surface Book 1 dGPU Switch Driver" + depends on SYSFS + help + This driver provides a sysfs switch to set the power-state of the + discrete GPU found on the Microsoft Surface Book 1. + config SURFACE_DTX tristate "Surface DTX (Detachment System) Driver" depends on SURFACE_AGGREGATOR diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 53344330939bf0..7efcd0cdb5329f 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o +obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c index 6c8fb7a4dde444..22797a53f4d8f1 100644 --- a/drivers/platform/surface/surface3-wmi.c +++ b/drivers/platform/surface/surface3-wmi.c @@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), }, }, + { + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), + DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), + DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), + }, + }, #endif { } }; diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index f0881edfb61610..c935d2518615f6 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -474,6 +474,9 @@ static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { /* Surface Laptop 6 */ { "MSHW0530", (unsigned long)ssam_node_group_sl6 }, + /* Surface Laptop 7 */ + { "MSHW0551", (unsigned long)ssam_node_group_sl7 }, + /* Surface Laptop Go 1 */ { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c index b359413903b13c..b4496db79f392e 100644 --- a/drivers/platform/surface/surface_gpe.c +++ b/drivers/platform/surface/surface_gpe.c @@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { {}, }; +static const struct property_entry lid_device_props_l52[] = { + PROPERTY_ENTRY_U32("gpe", 0x52), + {}, +}; + static const struct property_entry lid_device_props_l57[] = { PROPERTY_ENTRY_U32("gpe", 0x57), {}, @@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { }, .driver_data = (void *)lid_device_props_l4B, }, + { + /* + * We match for SKU here due to product name clash with the ARM + * version. + */ + .ident = "Surface Pro 9", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), + }, + .driver_data = (void *)lid_device_props_l52, + }, { .ident = "Surface Book 1", .matches = { diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c new file mode 100644 index 00000000000000..68db237734a13e --- /dev/null +++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +/* MSHW0040/VGBI DSM UUID: 6fd05c69-cde3-49f4-95ed-ab1665498035 */ +static const guid_t dgpu_sw_guid = + GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, + 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); + +#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" +#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" +#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" + +static int sb1_dgpu_sw_dsmcall(void) +{ + union acpi_object *obj; + acpi_handle handle; + acpi_status status; + + status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); + if (status) + return -EINVAL; + + obj = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); + if (!obj) + return -EINVAL; + + ACPI_FREE(obj); + return 0; +} + +static int sb1_dgpu_sw_hgon(struct device *dev) +{ + struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; + acpi_status status; + + status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); + if (status) { + dev_err(dev, "failed to run HGON: %d\n", status); + return -EINVAL; + } + + ACPI_FREE(buf.pointer); + + dev_info(dev, "turned-on dGPU via HGON\n"); + return 0; +} + +static int sb1_dgpu_sw_hgof(struct device *dev) +{ + struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; + acpi_status status; + + status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); + if (status) { + dev_err(dev, "failed to run HGOF: %d\n", status); + return -EINVAL; + } + + ACPI_FREE(buf.pointer); + + dev_info(dev, "turned-off dGPU via HGOF\n"); + return 0; +} + +static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + bool value; + int status; + + status = kstrtobool(buf, &value); + if (status < 0) + return status; + + if (!value) + return 0; + + status = sb1_dgpu_sw_dsmcall(); + + return status < 0 ? status : len; +} +static DEVICE_ATTR_WO(dgpu_dsmcall); + +static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + bool power; + int status; + + status = kstrtobool(buf, &power); + if (status < 0) + return status; + + if (power) + status = sb1_dgpu_sw_hgon(dev); + else + status = sb1_dgpu_sw_hgof(dev); + + return status < 0 ? status : len; +} +static DEVICE_ATTR_WO(dgpu_power); + +static struct attribute *sb1_dgpu_sw_attrs[] = { + &dev_attr_dgpu_dsmcall.attr, + &dev_attr_dgpu_power.attr, + NULL +}; +ATTRIBUTE_GROUPS(sb1_dgpu_sw); + +/* + * The dGPU power seems to be actually handled by MSHW0040. However, that is + * also the power-/volume-button device with a mainline driver. So let's use + * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. + */ +static const struct acpi_device_id sb1_dgpu_sw_match[] = { + { "MSHW0041", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); + +static struct platform_driver sb1_dgpu_sw = { + .driver = { + .name = "surfacebook1_dgpu_switch", + .acpi_match_table = sb1_dgpu_sw_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .dev_groups = sb1_dgpu_sw_groups, + }, +}; +module_platform_driver(sb1_dgpu_sw); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c index 388a3e1a488cb7..f94510dbda44dd 100644 --- a/drivers/platform/surface/surfacepro3_button.c +++ b/drivers/platform/surface/surfacepro3_button.c @@ -149,37 +149,18 @@ static int surface_button_resume(struct device *dev) /* * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device * ID (MSHW0040) for the power/volume buttons. Make sure this is the right - * device by checking for the _DSM method and OEM Platform Revision. + * device by checking for the _DSM method and OEM Platform Revision DSM + * function. * * Returns true if the driver should bind to this device, i.e. the device is * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. */ static bool surface_button_check_MSHW0040(struct device *dev, acpi_handle handle) { - union acpi_object *result; - u64 oem_platform_rev = 0; // valid revisions are nonzero - - // get OEM platform revision - result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, - MSHW0040_DSM_REVISION, - MSHW0040_DSM_GET_OMPR, - NULL, ACPI_TYPE_INTEGER); - - /* - * If evaluating the _DSM fails, the method is not present. This means - * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we - * should use this driver. We use revision 0 indicating it is - * unavailable. - */ - - if (result) { - oem_platform_rev = result->integer.value; - ACPI_FREE(result); - } - - dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); - - return oem_platform_rev == 0; + // make sure that OEM platform revision DSM call does not exist + return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, + MSHW0040_DSM_REVISION, + BIT(MSHW0040_DSM_GET_OMPR)); } diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index f63bc00d9a9b3e..3fba59462b450a 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -158,7 +158,7 @@ static const char * const omen_thermal_profile_boards[] = { "88F7", "88FD", "88FE", "88FF", "8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB", "8A15", "8A42", - "8BAD", + "8BAB", "8BAD", "8C58", "8E41", }; @@ -178,7 +178,7 @@ static const char * const omen_thermal_profile_force_v0_boards[] = { */ static const char * const omen_timed_thermal_profile_boards[] = { "8A15", "8A42", - "8BAD", + "8BAB", "8BAD", }; /* DMI Board names of Victus 16-d laptops */ diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 115bb37577a18d..c1d6c5121a5004 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -237,6 +237,14 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 /* Setups using a handshake pin need 25 ms enable delay */ *enable_time_us = 25 * USEC_PER_MSEC; break; + case 0x08: /* Surface Pro 9 - additional power rail */ + *con_id = "pwr1"; + *gpio_flags = GPIO_ACTIVE_HIGH; + break; + case 0x11: /* Surface Pro 9 - secondary power rail */ + *con_id = "pwr2"; + *gpio_flags = GPIO_ACTIVE_HIGH; + break; default: *con_id = "unknown"; *gpio_flags = GPIO_ACTIVE_HIGH; @@ -345,6 +353,8 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, case INT3472_GPIO_TYPE_POWER_ENABLE: case INT3472_GPIO_TYPE_DOVDD: case INT3472_GPIO_TYPE_HANDSHAKE: + case 0x08: /* Surface Pro 9 power rails */ + case 0x11: gpio = skl_int3472_gpiod_get_from_temp_lookup(int3472, agpio, con_id, gpio_flags); if (IS_ERR(gpio)) { ret = PTR_ERR(gpio); @@ -377,6 +387,19 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, err_msg = "Failed to register regulator\n"; break; + case 0x08: /* Surface Pro 9 - treat as power*/ + dev_info(int3472->dev, "GPIO type 0x%02x detected on pin 0x%02x\n", type, agpio->pin_table[0]); + dev_info(int3472->dev, " con_id=%s, flags=0x%lx\n", con_id, gpio_flags); + ret = skl_int3472_register_regulator(int3472, gpio, + GPIO_REGULATOR_ENABLE_TIME, + con_id, NULL); + dev_info(int3472->dev, " register_regulator returned: %d\n", ret); + if (ret) { + dev_err(int3472->dev, "Failed to register type 0x02x: %d\n", ret); + } + break; + case 0x11: + break; default: /* Never reached */ ret = -EINVAL; break; diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c index a77ed32abe55f1..8dece4261fc4c4 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.c +++ b/drivers/platform/x86/intel/int3472/tps68470.c @@ -17,7 +17,7 @@ #define DESIGNED_FOR_CHROMEOS 1 #define DESIGNED_FOR_WINDOWS 2 -#define TPS68470_WIN_MFD_CELL_COUNT 3 +#define TPS68470_WIN_MFD_CELL_COUNT 4 static const struct mfd_cell tps68470_cros[] = { { .name = "tps68470-gpio" }, @@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) return ret; } + /* Enable I2C daisy chain */ + ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); + if (ret) { + dev_err(dev, "Failed to enable i2c daisy chain\n"); + return ret; + } + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); return 0; @@ -198,6 +205,7 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); cells[2].name = "tps68470-gpio"; cells[2].swnode = board_data->tps68470_gpio_swnode; + cells[3].name = "tps68470-led"; for (i = 0; i < board_data->n_gpiod_lookups; i++) gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index b1b2d9caba7b38..72700da950b117 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c @@ -1416,6 +1416,14 @@ static const struct dmi_system_id ips_blacklist[] = { DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook"), }, }, + { + .callback = ips_blacklist_callback, + .ident = "G60JX", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "G60JX"), + }, + }, { } /* terminating entry */ }; diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index 0f5bdb0e395ea0..80d2fa10919f86 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -161,6 +161,7 @@ static int pwm_probe(struct platform_device *pdev) const struct platform_device_id *id = platform_get_device_id(pdev); struct pwm_chip *chip; struct pxa_pwm_chip *pc; + struct clk *bus_clk; struct device *dev = &pdev->dev; struct reset_control *rst; int ret = 0; @@ -177,7 +178,12 @@ static int pwm_probe(struct platform_device *pdev) return PTR_ERR(chip); pc = to_pxa_pwm_chip(chip); - pc->clk = devm_clk_get(dev, NULL); + bus_clk = devm_clk_get_optional_enabled(dev, "bus"); + if (IS_ERR(bus_clk)) + return dev_err_probe(dev, PTR_ERR(bus_clk), "Failed to get bus clock\n"); + + /* Get named func clk if bus clock is valid */ + pc->clk = devm_clk_get(dev, bus_clk ? "func" : NULL); if (IS_ERR(pc->clk)) return dev_err_probe(dev, PTR_ERR(pc->clk), "Failed to get clock\n"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 87554ab92801df..c52ff9a3214b7e 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1514,9 +1514,8 @@ config REGULATOR_SLG51000 config REGULATOR_SPACEMIT_P1 tristate "SpacemiT P1 regulators" depends on ARCH_SPACEMIT || COMPILE_TEST - depends on I2C - select MFD_SPACEMIT_P1 - default ARCH_SPACEMIT + depends on MFD_SPACEMIT_P1 + default m if MFD_SPACEMIT_P1 help Enable support for regulators implemented by the SpacemiT P1 power controller. The P1 implements 6 high-efficiency buck diff --git a/drivers/reset/spacemit/reset-spacemit-k3.c b/drivers/reset/spacemit/reset-spacemit-k3.c index 9841f5e057b2a0..2e87f320cf1184 100644 --- a/drivers/reset/spacemit/reset-spacemit-k3.c +++ b/drivers/reset/spacemit/reset-spacemit-k3.c @@ -112,7 +112,7 @@ static const struct ccu_reset_data k3_apmu_resets[] = { [RESET_APMU_SDH0] = RESET_DATA(APMU_SDH0_CLK_RES_CTRL, 0, BIT(1)), [RESET_APMU_SDH1] = RESET_DATA(APMU_SDH1_CLK_RES_CTRL, 0, BIT(1)), [RESET_APMU_SDH2] = RESET_DATA(APMU_SDH2_CLK_RES_CTRL, 0, BIT(1)), - [RESET_APMU_USB2_AHB] = RESET_DATA(APMU_USB_CLK_RES_CTRL, 0, BIT(1)), + [RESET_APMU_USB2_AHB] = RESET_DATA(APMU_USB_CLK_RES_CTRL, 0, BIT(0)), [RESET_APMU_USB2_VCC] = RESET_DATA(APMU_USB_CLK_RES_CTRL, 0, BIT(2)), [RESET_APMU_USB2_PHY] = RESET_DATA(APMU_USB_CLK_RES_CTRL, 0, BIT(3)), [RESET_APMU_USB3_A_AHB] = RESET_DATA(APMU_USB_CLK_RES_CTRL, 0, BIT(5)), diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 364afc73f8abd2..8676b35b63aa66 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -410,7 +410,7 @@ config RTC_DRV_SPACEMIT_P1 tristate "SpacemiT P1 RTC" depends on ARCH_SPACEMIT || COMPILE_TEST depends on MFD_SPACEMIT_P1 - default MFD_SPACEMIT_P1 + default m if MFD_SPACEMIT_P1 help Enable support for the RTC function in the SpacemiT P1 PMIC. This driver can also be built as a module, which will be called @@ -1425,6 +1425,13 @@ config RTC_DRV_NTXEC embedded controller found in certain e-book readers designed by the original design manufacturer Netronix. +config RTC_DRV_SURFACE + tristate "Microsoft Surface Aggregator RTC" + depends on SURFACE_AGGREGATOR + depends on SURFACE_AGGREGATOR_BUS + help + TODO + comment "on-CPU RTC drivers" config RTC_DRV_ASM9260 diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 6cf7e066314e13..18bd33b84d1399 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -186,6 +186,7 @@ obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o obj-$(CONFIG_RTC_DRV_SUN6I) += rtc-sun6i.o obj-$(CONFIG_RTC_DRV_SUNPLUS) += rtc-sunplus.o obj-$(CONFIG_RTC_DRV_SUNXI) += rtc-sunxi.o +obj-$(CONFIG_RTC_DRV_SURFACE) += rtc-surface.o obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o obj-$(CONFIG_RTC_DRV_TI_K3) += rtc-ti-k3.o diff --git a/drivers/rtc/rtc-surface.c b/drivers/rtc/rtc-surface.c new file mode 100644 index 00000000000000..f6c17c4e98d5f1 --- /dev/null +++ b/drivers/rtc/rtc-surface.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AC driver for 7th-generation Microsoft Surface devices via Surface System + * Aggregator Module (SSAM). + * + * Copyright (C) 2019-2021 Maximilian Luz + */ + +#include +#include +#include +#include +#include + +#include + +struct surface_rtc { + struct ssam_device *sdev; + struct rtc_device *rtc; +}; + +SSAM_DEFINE_SYNC_REQUEST_R(__ssam_rtc_get_unix_time, __le32, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = SSAM_SSH_TID_SAM, + .instance_id = 0x00, + .command_id = 0x10, +}); + +SSAM_DEFINE_SYNC_REQUEST_W(__ssam_rtc_set_unix_time, __le32, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = SSAM_SSH_TID_SAM, + .instance_id = 0x00, + .command_id = 0x0f, +}); + +static int ssam_rtc_get_unix_time(struct surface_rtc *srtc, u32 *time) +{ + __le32 time_le; + int status; + + status = __ssam_rtc_get_unix_time(srtc->sdev->ctrl, &time_le); + if (status) + return status; + + *time = le32_to_cpu(time_le); + return 0; +} + +static int ssam_rtc_set_unix_time(struct surface_rtc *srtc, u32 time) +{ + __le32 time_le = cpu_to_le32(time); + + return __ssam_rtc_set_unix_time(srtc->sdev->ctrl, &time_le); +} + +static int surface_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct surface_rtc *srtc = dev_get_drvdata(dev); + int status; + u32 time; + + status = ssam_rtc_get_unix_time(srtc, &time); + if (status) + return status; + + rtc_time64_to_tm(time, tm); + return 0; +} + +static int surface_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct surface_rtc *srtc = dev_get_drvdata(dev); + time64_t time = rtc_tm_to_time64(tm); + + return ssam_rtc_set_unix_time(srtc, (u32)time); +} + +static const struct rtc_class_ops surface_rtc_ops = { + .read_time = surface_rtc_read_time, + .set_time = surface_rtc_set_time, +}; + +static int surface_rtc_probe(struct ssam_device *sdev) +{ + struct surface_rtc *srtc; + + srtc = devm_kzalloc(&sdev->dev, sizeof(*srtc), GFP_KERNEL); + if (!srtc) + return -ENOMEM; + + srtc->sdev = sdev; + + srtc->rtc = devm_rtc_allocate_device(&sdev->dev); + if (IS_ERR(srtc->rtc)) + return PTR_ERR(srtc->rtc); + + srtc->rtc->ops = &surface_rtc_ops; + srtc->rtc->range_max = U32_MAX; + + ssam_device_set_drvdata(sdev, srtc); + + return devm_rtc_register_device(srtc->rtc); +} + +static void surface_rtc_remove(struct ssam_device *sdev) +{ + /* Device-managed allocations take care of everything... */ +} + +static const struct ssam_device_id surface_rtc_match[] = { + { SSAM_SDEV(SAM, SAM, 0x00, 0x00) }, + { }, +}; +MODULE_DEVICE_TABLE(ssam, surface_rtc_match); + +static struct ssam_device_driver surface_rtc_driver = { + .probe = surface_rtc_probe, + .remove = surface_rtc_remove, + .match_table = surface_rtc_match, + .driver = { + .name = "surface_rtc", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_ssam_device_driver(surface_rtc_driver); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("RTC driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/dc395x.c b/drivers/scsi/dc395x.c index 6183ce05d8cf61..acf836312912d3 100644 --- a/drivers/scsi/dc395x.c +++ b/drivers/scsi/dc395x.c @@ -94,6 +94,12 @@ #define DC395x_LOCK_IO(dev,flags) spin_lock_irqsave(((struct Scsi_Host *)dev)->host_lock, flags) #define DC395x_UNLOCK_IO(dev,flags) spin_unlock_irqrestore(((struct Scsi_Host *)dev)->host_lock, flags) +/* + * read operations that may trigger side effects in the hardware, + * but the value can or should be discarded. + */ +#define DC395x_peek8(acb,address) (void)(inb(acb->io_port_base + (address))) + #define DC395x_read8(acb,address) (u8)(inb(acb->io_port_base + (address))) #define DC395x_read16(acb,address) (u16)(inw(acb->io_port_base + (address))) #define DC395x_read32(acb,address) (u32)(inl(acb->io_port_base + (address))) @@ -1000,7 +1006,7 @@ static int __dc395x_eh_bus_reset(struct scsi_cmnd *cmd) DC395x_write8(acb, TRM_S1040_DMA_CONTROL, CLRXFIFO); clear_fifo(acb, "eh_bus_reset"); /* Delete pending IRQ */ - DC395x_read8(acb, TRM_S1040_SCSI_INTSTATUS); + DC395x_peek8(acb, TRM_S1040_SCSI_INTSTATUS); set_basic_config(acb); reset_dev_param(acb); @@ -2029,8 +2035,8 @@ static void data_io_transfer(struct AdapterCtlBlk *acb, DC395x_write8(acb, TRM_S1040_SCSI_CONFIG2, CFG2_WIDEFIFO); if (io_dir & DMACMD_DIR) { - DC395x_read8(acb, TRM_S1040_SCSI_FIFO); - DC395x_read8(acb, TRM_S1040_SCSI_FIFO); + DC395x_peek8(acb, TRM_S1040_SCSI_FIFO); + DC395x_peek8(acb, TRM_S1040_SCSI_FIFO); } else { /* Danger, Robinson: If you find KGs * scattered over the wide disk, the driver @@ -2044,7 +2050,7 @@ static void data_io_transfer(struct AdapterCtlBlk *acb, /* Danger, Robinson: If you find a collection of Ks on your disk * something broke :-( */ if (io_dir & DMACMD_DIR) - DC395x_read8(acb, TRM_S1040_SCSI_FIFO); + DC395x_peek8(acb, TRM_S1040_SCSI_FIFO); else DC395x_write8(acb, TRM_S1040_SCSI_FIFO, 'K'); } @@ -2892,7 +2898,7 @@ static void set_basic_config(struct AdapterCtlBlk *acb) DMA_FIFO_HALF_HALF | DMA_ENHANCE /*| DMA_MEM_MULTI_READ */ ; DC395x_write16(acb, TRM_S1040_DMA_CONFIG, wval); /* Clear pending interrupt status */ - DC395x_read8(acb, TRM_S1040_SCSI_INTSTATUS); + DC395x_peek8(acb, TRM_S1040_SCSI_INTSTATUS); /* Enable SCSI interrupt */ DC395x_write8(acb, TRM_S1040_SCSI_INTEN, 0x7F); DC395x_write8(acb, TRM_S1040_DMA_INTEN, EN_SCSIINTR | EN_DMAXFERERROR @@ -3799,7 +3805,7 @@ static void adapter_uninit_chip(struct AdapterCtlBlk *acb) reset_scsi_bus(acb); /* clear any pending interrupt state */ - DC395x_read8(acb, TRM_S1040_SCSI_INTSTATUS); + DC395x_peek8(acb, TRM_S1040_SCSI_INTSTATUS); } diff --git a/drivers/scsi/lpfc/lpfc_compat.h b/drivers/scsi/lpfc/lpfc_compat.h index 43cf46a3a71fea..0cd1e3c82d8759 100644 --- a/drivers/scsi/lpfc/lpfc_compat.h +++ b/drivers/scsi/lpfc/lpfc_compat.h @@ -91,8 +91,8 @@ lpfc_memcpy_to_slim( void __iomem *dest, void *src, unsigned int bytes) static inline void lpfc_memcpy_from_slim( void *dest, void __iomem *src, unsigned int bytes) { - /* actually returns 1 byte past dest */ - memcpy_fromio( dest, src, bytes); + /* convert bytes in argument list to word count for copy function */ + __ioread32_copy(dest, src, bytes / sizeof(uint32_t)); } #endif /* __BIG_ENDIAN */ diff --git a/drivers/scsi/mvsas/mv_64xx.c b/drivers/scsi/mvsas/mv_64xx.c index 1f2b61de8c6326..855d8465b30f24 100644 --- a/drivers/scsi/mvsas/mv_64xx.c +++ b/drivers/scsi/mvsas/mv_64xx.c @@ -376,10 +376,14 @@ static int mvs_64xx_init(struct mvs_info *mvi) * it will make count 0. */ tmp = 0; +#if defined(CONFIG_MIPS) && defined(CONFIG_CPU_LOONGSON64) + mw32(MVS_INT_COAL, (ATA_MAX_QUEUE - 1) | COAL_EN); +#else if (MVS_CHIP_SLOT_SZ > 0x1ff) mw32(MVS_INT_COAL, 0x1ff | COAL_EN); else mw32(MVS_INT_COAL, MVS_CHIP_SLOT_SZ | COAL_EN); +#endif tmp = 0x10000 | interrupt_coalescing; mw32(MVS_INT_COAL_TMOUT, tmp); @@ -748,10 +752,14 @@ static void mvs_64xx_tune_interrupt(struct mvs_info *mvi, u32 time) mw32(MVS_INT_COAL, 0); mw32(MVS_INT_COAL_TMOUT, 0x10000); } else { +#if defined(CONFIG_MIPS) && defined(CONFIG_CPU_LOONGSON64) + mw32(MVS_INT_COAL, (ATA_MAX_QUEUE - 1) | COAL_EN); +#else if (MVS_CHIP_SLOT_SZ > 0x1ff) mw32(MVS_INT_COAL, 0x1ff|COAL_EN); else mw32(MVS_INT_COAL, MVS_CHIP_SLOT_SZ|COAL_EN); +#endif tmp = 0x10000 | time; mw32(MVS_INT_COAL_TMOUT, tmp); diff --git a/drivers/scsi/mvsas/mv_94xx.c b/drivers/scsi/mvsas/mv_94xx.c index fc0b8eb682045f..1d1042ab82c8c0 100644 --- a/drivers/scsi/mvsas/mv_94xx.c +++ b/drivers/scsi/mvsas/mv_94xx.c @@ -515,10 +515,14 @@ static int mvs_94xx_init(struct mvs_info *mvi) * it will make count 0. */ tmp = 0; +#if defined(CONFIG_MIPS) && defined(CONFIG_CPU_LOONGSON64) + mw32(MVS_INT_COAL, (ATA_MAX_QUEUE - 1) | COAL_EN); +#else if (MVS_CHIP_SLOT_SZ > 0x1ff) mw32(MVS_INT_COAL, 0x1ff | COAL_EN); else mw32(MVS_INT_COAL, MVS_CHIP_SLOT_SZ | COAL_EN); +#endif /* default interrupt coalescing time is 128us */ tmp = 0x10000 | interrupt_coalescing; @@ -1027,10 +1031,14 @@ static void mvs_94xx_tune_interrupt(struct mvs_info *mvi, u32 time) mw32(MVS_INT_COAL, 0); mw32(MVS_INT_COAL_TMOUT, 0x10000); } else { +#if defined(CONFIG_MIPS) && defined(CONFIG_CPU_LOONGSON64) + mw32(MVS_INT_COAL, (ATA_MAX_QUEUE - 1) | COAL_EN); +#else if (MVS_CHIP_SLOT_SZ > 0x1ff) mw32(MVS_INT_COAL, 0x1ff|COAL_EN); else mw32(MVS_INT_COAL, MVS_CHIP_SLOT_SZ|COAL_EN); +#endif tmp = 0x10000 | time; mw32(MVS_INT_COAL_TMOUT, tmp); diff --git a/drivers/scsi/mvsas/mv_init.c b/drivers/scsi/mvsas/mv_init.c index 5abc17a2e26109..642ec7f713ac90 100644 --- a/drivers/scsi/mvsas/mv_init.c +++ b/drivers/scsi/mvsas/mv_init.c @@ -10,7 +10,11 @@ #include "mv_sas.h" +#if defined(CONFIG_MIPS) && defined(CONFIG_CPU_LOONGSON64) +int interrupt_coalescing = 0x08; +#else int interrupt_coalescing = 0x80; +#endif static struct scsi_transport_template *mvs_stt; static const struct mvs_chip_info mvs_chips[] = { @@ -32,6 +36,7 @@ static const struct attribute_group *mvst_sdev_groups[]; static const struct scsi_host_template mvs_sht = { LIBSAS_SHT_BASE + .sdev_configure = mvs_sdev_configure, .scan_finished = mvs_scan_finished, .scan_start = mvs_scan_start, .can_queue = 1, diff --git a/drivers/scsi/mvsas/mv_sas.c b/drivers/scsi/mvsas/mv_sas.c index 359226e80eae82..7b4c113e8a41cf 100644 --- a/drivers/scsi/mvsas/mv_sas.c +++ b/drivers/scsi/mvsas/mv_sas.c @@ -255,6 +255,20 @@ static void mvs_bytes_dmaed(struct mvs_info *mvi, int i, gfp_t gfp_flags) sas_notify_port_event(sas_phy, PORTE_BYTES_DMAED, gfp_flags); } +int mvs_sdev_configure(struct scsi_device *sdev, struct queue_limits *lim) +{ + sas_sdev_configure(sdev, lim); + +#if defined(CONFIG_MIPS) && defined(CONFIG_CPU_LOONGSON64) + struct domain_device *dev = sdev_to_domain_dev(sdev); + + if (!dev_is_sata(dev)) + scsi_change_queue_depth(sdev, MVS_QUEUE_SIZE); +#endif + + return 0; +} + void mvs_scan_start(struct Scsi_Host *shost) { int i, j; @@ -309,13 +323,13 @@ static int mvs_task_prep_smp(struct mvs_info *mvi, * DMA-map SMP request, response buffers */ sg_req = &task->smp_task.smp_req; - elem = dma_map_sg(mvi->dev, sg_req, 1, DMA_TO_DEVICE); + elem = dma_map_sg(mvi->dev, sg_req, 1, DMA_BIDIRECTIONAL); if (!elem) return -ENOMEM; req_len = sg_dma_len(sg_req); sg_resp = &task->smp_task.smp_resp; - elem = dma_map_sg(mvi->dev, sg_resp, 1, DMA_FROM_DEVICE); + elem = dma_map_sg(mvi->dev, sg_resp, 1, DMA_BIDIRECTIONAL); if (!elem) { rc = -ENOMEM; goto err_out; @@ -389,10 +403,10 @@ static int mvs_task_prep_smp(struct mvs_info *mvi, err_out_2: dma_unmap_sg(mvi->dev, &tei->task->smp_task.smp_resp, 1, - DMA_FROM_DEVICE); + DMA_BIDIRECTIONAL); err_out: dma_unmap_sg(mvi->dev, &tei->task->smp_task.smp_req, 1, - DMA_TO_DEVICE); + DMA_BIDIRECTIONAL); return rc; } @@ -869,9 +883,9 @@ static void mvs_slot_task_free(struct mvs_info *mvi, struct sas_task *task, switch (task->task_proto) { case SAS_PROTOCOL_SMP: dma_unmap_sg(mvi->dev, &task->smp_task.smp_resp, 1, - DMA_FROM_DEVICE); + DMA_BIDIRECTIONAL); dma_unmap_sg(mvi->dev, &task->smp_task.smp_req, 1, - DMA_TO_DEVICE); + DMA_BIDIRECTIONAL); break; case SAS_PROTOCOL_SATA: diff --git a/drivers/scsi/mvsas/mv_sas.h b/drivers/scsi/mvsas/mv_sas.h index 09ce3f2241f240..6d7831bd9c0148 100644 --- a/drivers/scsi/mvsas/mv_sas.h +++ b/drivers/scsi/mvsas/mv_sas.h @@ -429,6 +429,7 @@ int mvs_phy_control(struct asd_sas_phy *sas_phy, enum phy_func func, void *funcdata); void mvs_set_sas_addr(struct mvs_info *mvi, int port_id, u32 off_lo, u32 off_hi, u64 sas_addr); +int mvs_sdev_configure(struct scsi_device *sdev, struct queue_limits *lim); void mvs_scan_start(struct Scsi_Host *shost); int mvs_scan_finished(struct Scsi_Host *shost, unsigned long time); int mvs_queue_command(struct sas_task *task, gfp_t gfp_flags); diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 85eef401925a27..27c4b02d76d5d4 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -1613,6 +1613,7 @@ static void scsi_complete(struct request *rq) static enum scsi_qc_status scsi_dispatch_cmd(struct scsi_cmnd *cmd) { struct Scsi_Host *host = cmd->device->host; + struct scsi_device *sdev = cmd->device; int rtn = 0; atomic_inc(&cmd->device->iorequest_cnt); @@ -1661,6 +1662,17 @@ static enum scsi_qc_status scsi_dispatch_cmd(struct scsi_cmnd *cmd) goto done; } + /* + * Before we queue this command, check attribute use_192_bytes_for_3f. + * Because transmits data with a length of 0xff bytes via ioctl may + * cause some hard drives to hang and become unusable. + */ + if (cmd->cmnd[0] == MODE_SENSE && sdev->use_192_bytes_for_3f && + cmd->cmnd[2] == 0x3f && cmd->cmnd[4] != 192) { + cmd->result = DID_ABORT << 16; + goto done; + } + if (unlikely(host->shost_state == SHOST_DEL)) { cmd->result = (DID_NO_CONNECT << 16); goto done; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index b563f49e219772..800fafb938d5e2 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -592,12 +592,12 @@ config SPI_LM70_LLP config SPI_LOONGSON_CORE tristate - depends on LOONGARCH || COMPILE_TEST + depends on LOONGARCH || MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST config SPI_LOONGSON_PCI tristate "Loongson SPI Controller PCI Driver Support" select SPI_LOONGSON_CORE - depends on PCI && (LOONGARCH || COMPILE_TEST) + depends on PCI && (LOONGARCH || MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST) help This bus driver supports the Loongson SPI hardware controller in the Loongson platforms and supports to use PCI framework to @@ -608,7 +608,7 @@ config SPI_LOONGSON_PCI config SPI_LOONGSON_PLATFORM tristate "Loongson SPI Controller Platform Driver Support" select SPI_LOONGSON_CORE - depends on OF && (LOONGARCH || COMPILE_TEST) + depends on OF && (LOONGARCH || MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST) help This bus driver supports the Loongson SPI hardware controller in the Loongson platforms and supports to use DTS framework to @@ -1085,6 +1085,15 @@ config SPI_SG2044_NOR also supporting 3Byte address devices and 4Byte address devices. +config SPI_SPACEMIT_K1 + tristate "K1 SPI Controller" + depends on ARCH_SPACEMIT || COMPILE_TEST + depends on OF + imply MMP_PDMA if ARCH_SPACEMIT + default m if ARCH_SPACEMIT + help + Enable support for the SpacemiT K1 SPI controller. + config SPI_SPRD tristate "Spreadtrum SPI controller" depends on ARCH_SPRD || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 9d36190a988481..9fa12498ce8c0e 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -143,6 +143,7 @@ obj-$(CONFIG_SPI_SIFIVE) += spi-sifive.o obj-$(CONFIG_SPI_SLAVE_MT27XX) += spi-slave-mt27xx.o obj-$(CONFIG_SPI_SN_F_OSPI) += spi-sn-f-ospi.o obj-$(CONFIG_SPI_SG2044_NOR) += spi-sg2044-nor.o +obj-$(CONFIG_SPI_SPACEMIT_K1) += spi-spacemit-k1.o obj-$(CONFIG_SPI_SPRD) += spi-sprd.o obj-$(CONFIG_SPI_SPRD_ADI) += spi-sprd-adi.o obj-$(CONFIG_SPI_STM32) += spi-stm32.o diff --git a/drivers/spi/spi-spacemit-k1.c b/drivers/spi/spi-spacemit-k1.c new file mode 100644 index 00000000000000..215fe66d27b4db --- /dev/null +++ b/drivers/spi/spi-spacemit-k1.c @@ -0,0 +1,789 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// SpacemiT K1 SPI controller driver +// +// Copyright (C) 2026, RISCstar Solutions Corporation +// Copyright (C) 2023, SpacemiT Corporation + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internals.h" + +/* This is the range of transfer rates supported by the K1 SoC */ +#define K1_SPI_MIN_SPEED_HZ 6250 +#define K1_SPI_MAX_SPEED_HZ 51200000 + +/* DMA constraints */ +#define K1_SPI_DMA_ALIGNMENT 64 +#define K1_SPI_MAX_DMA_LEN SZ_512K + +/* SSP Top Control Register */ +#define SSP_TOP_CTRL 0x00 +#define TOP_SSE BIT(0) /* Enable port */ +#define TOP_FRF_MASK GENMASK(2, 1) /* Frame format */ +#define TOP_FRF_MOTOROLA 0 /* Motorola SPI */ +#define TOP_DSS_MASK GENMASK(9, 5) /* Data size (1-32) */ +#define TOP_SPO BIT(10) /* Polarity: 0=low */ +#define TOP_SPH BIT(11) /* Half-cycle phase */ +#define TOP_LBM BIT(12) /* Loopback mode */ +#define TOP_TRAIL BIT(13) /* Trailing bytes */ +#define TOP_HOLD_FRAME_LOW BIT(14) /* Chip select */ + +/* SSP FIFO Control Register */ +#define SSP_FIFO_CTRL 0x04 +#define FIFO_TFT_MASK GENMASK(4, 0) /* TX FIFO threshold */ +#define FIFO_RFT_MASK GENMASK(9, 5) /* RX FIFO threshold */ +#define FIFO_TSRE BIT(10) /* TX service request */ +#define FIFO_RSRE BIT(11) /* RX service request */ + +/* SSP Interrupt Enable Register */ +#define SSP_INT_EN 0x08 +#define SSP_INT_EN_TINTE BIT(1) /* RX timeout */ +#define SSP_INT_EN_RIE BIT(2) /* RX FIFO */ +#define SSP_INT_EN_TIE BIT(3) /* TX FIFO */ +#define SSP_INT_EN_RIM BIT(4) /* RX FIFO overrun */ +#define SSP_INT_EN_TIM BIT(5) /* TX FIFO underrun */ +#define SSP_INT_EN_EBCEI BIT(6) /* Bit count error */ + +/* TX interrupts, RX interrupts, and error interrupts */ +#define SSP_INT_EN_TX SSP_INT_EN_TIE +#define SSP_INT_EN_RX \ + (SSP_INT_EN_TINTE | SSP_INT_EN_RIE) +#define SSP_INT_EN_ERROR \ + (SSP_INT_EN_RIM | SSP_INT_EN_TIM | SSP_INT_EN_EBCEI) + +/* SSP Time Out Register */ +#define SSP_TIMEOUT 0x0c +#define SSP_TIMEOUT_MASK GENMASK(23, 0) + +/* SSP Data Register */ +#define SSP_DATAR 0x10 + +/* SSP Status Register */ +#define SSP_STATUS 0x14 +#define SSP_STATUS_BSY BIT(0) /* SPI/I2S busy */ +#define SSP_STATUS_TNF BIT(6) /* TX FIFO not full */ +#define SSP_STATUS_TFL GENMASK(11, 7) /* TX FIFO level */ +#define SSP_STATUS_TUR BIT(12) /* TX FIFO underrun */ +#define SSP_STATUS_RNE BIT(14) /* RX FIFO not empty */ +#define SSP_STATUS_RFL GENMASK(19, 15) /* RX FIFO level */ +#define SSP_STATUS_ROR BIT(20) /* RX FIFO overrun */ +#define SSP_STATUS_BCE BIT(21) /* Bit count error */ + +/* Error status mask */ +#define SSP_STATUS_ERROR \ + (SSP_STATUS_TUR | SSP_STATUS_ROR | SSP_STATUS_BCE) + +/* The FIFO sizes and thresholds are the same for RX and TX */ +#define K1_SPI_FIFO_SIZE 32 +#define K1_SPI_THRESH (K1_SPI_FIFO_SIZE / 2) + +struct k1_spi_driver_data { + struct spi_controller *host; + void __iomem *base; + phys_addr_t base_addr; + unsigned long bus_rate; + struct clk *clk; + unsigned long rate; + int irq; + + /* Current transfer information; not valid if message is null */ + u32 bytes; /* Bytes used for bits_per_word */ + unsigned int rx_resid; /* RX bytes left in transfer */ + unsigned int tx_resid; /* TX bytes left in transfer */ + struct spi_transfer *transfer; /* Current transfer */ + + bool dma_enabled; +}; + +/* Set our registers to a known initial state */ +static void +k1_spi_register_reset(struct k1_spi_driver_data *drv_data, bool initial) +{ + u32 val = 0; + + writel(0, drv_data->base + SSP_TOP_CTRL); + + if (initial) { + /* + * The TX and RX FIFO thresholds are the same no matter + * what the speed or bits per word, so we can just set + * them once. The thresholds are one more than the values + * in the register. + */ + val = FIELD_PREP(FIFO_RFT_MASK, K1_SPI_THRESH - 1); + val |= FIELD_PREP(FIFO_TFT_MASK, K1_SPI_THRESH - 1); + } + writel(val, drv_data->base + SSP_FIFO_CTRL); + + writel(0, drv_data->base + SSP_INT_EN); + writel(0, drv_data->base + SSP_TIMEOUT); + + /* Clear any pending interrupt conditions */ + writel(~0, drv_data->base + SSP_STATUS); +} + +/* + * The client can call the setup function multiple times, and each call + * can specify a different SPI mode (and transfer speed). Each transfer + * can specify its own speed though, and the core code ensures each + * transfer's speed is set to something nonzero and supported by both + * the controller and the device. We just set the speed for each transfer. + */ +static int k1_spi_setup(struct spi_device *spi) +{ + struct k1_spi_driver_data *drv_data; + u32 val; + + drv_data = spi_controller_get_devdata(spi->controller); + + /* + * Configure the message format for this device. We only + * support Motorola SPI format in master mode. + */ + val = FIELD_PREP(TOP_FRF_MASK, TOP_FRF_MOTOROLA); + + /* Translate the mode into the value used to program the hardware. */ + if (spi->mode & SPI_CPHA) + val |= TOP_SPH; /* 1/2 cycle */ + if (spi->mode & SPI_CPOL) + val |= TOP_SPO; /* active low */ + if (spi->mode & SPI_LOOP) + val |= TOP_LBM; /* enable loopback */ + writel(val, drv_data->base + SSP_TOP_CTRL); + + return 0; +} + +static void k1_spi_cleanup(struct spi_device *spi) +{ + struct k1_spi_driver_data *drv_data; + + drv_data = spi_controller_get_devdata(spi->controller); + k1_spi_register_reset(drv_data, false); +} + +static bool k1_spi_can_dma(struct spi_controller *host, struct spi_device *spi, + struct spi_transfer *transfer) +{ + struct k1_spi_driver_data *drv_data = spi_controller_get_devdata(host); + u32 burst_size; + + if (!drv_data->dma_enabled) + return false; + + if (transfer->len > SZ_2K) + return false; + + /* Don't bother with DMA if we can't do even a single burst */ + burst_size = K1_SPI_THRESH * spi_bpw_to_bytes(transfer->bits_per_word); + + return transfer->len >= burst_size; +} + +static void k1_spi_dma_callback(void *param) +{ + struct k1_spi_driver_data *drv_data = param; + u32 val; + + val = readl(drv_data->base + SSP_FIFO_CTRL); + val &= ~(FIFO_TSRE | FIFO_RSRE); + writel(val, drv_data->base + SSP_FIFO_CTRL); + + val = readl(drv_data->base + SSP_TOP_CTRL); + val &= ~TOP_TRAIL; + writel(val, drv_data->base + SSP_TOP_CTRL); + + /* Check for any error conditions */ + val = readl(drv_data->base + SSP_STATUS); + if (val & SSP_STATUS_ERROR) + drv_data->transfer->error |= SPI_TRANS_FAIL_IO; + + /* Disable the port */ + val = readl(drv_data->base + SSP_TOP_CTRL); + val &= ~TOP_SSE; + writel(val, drv_data->base + SSP_TOP_CTRL); + + drv_data->transfer = NULL; + + spi_finalize_current_transfer(drv_data->host); +} + +/* Prepare a descriptor for TX or RX DMA */ +static struct dma_async_tx_descriptor * +k1_spi_dma_prep(struct k1_spi_driver_data *drv_data, + struct spi_transfer *transfer, bool tx) +{ + phys_addr_t addr = drv_data->base_addr + SSP_DATAR; + u32 burst_size = K1_SPI_THRESH * drv_data->bytes; + struct dma_slave_config cfg = { }; + enum dma_transfer_direction dir; + enum dma_slave_buswidth width; + struct dma_chan *chan; + struct sg_table *sgt; + + switch (drv_data->bytes) { + case 1: + width = DMA_SLAVE_BUSWIDTH_1_BYTE; + break; + case 2: + width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + default: /* bytes == 4 */ + width = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + } + + if (tx) { + chan = drv_data->host->dma_tx; + sgt = &transfer->tx_sg; + dir = DMA_MEM_TO_DEV; + + cfg.dst_addr = addr; + cfg.dst_addr_width = width; + cfg.dst_maxburst = burst_size; + } else { + chan = drv_data->host->dma_rx; + sgt = &transfer->rx_sg; + dir = DMA_DEV_TO_MEM; + + cfg.src_addr = addr; + cfg.src_addr_width = width; + cfg.src_maxburst = burst_size; + } + cfg.direction = dir; + + if (dmaengine_slave_config(chan, &cfg)) + return NULL; + + return dmaengine_prep_slave_sg(chan, sgt->sgl, sgt->nents, dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + +} + +static int k1_spi_dma_one(struct spi_controller *host, struct spi_device *spi, + struct spi_transfer *transfer) +{ + struct k1_spi_driver_data *drv_data = spi_controller_get_devdata(host); + struct dma_async_tx_descriptor *desc; + u32 val; + + /* Prepare the TX descriptor and submit it */ + desc = k1_spi_dma_prep(drv_data, transfer, true); + if (!desc) + goto fallback; + dmaengine_submit(desc); + + /* Prepare the RX descriptor and submit it */ + desc = k1_spi_dma_prep(drv_data, transfer, false); + if (!desc) + goto fallback; + + /* When RX is complete we also know TX has completed */ + desc->callback = k1_spi_dma_callback; + desc->callback_param = drv_data; + + dmaengine_submit(desc); + + val = readl(drv_data->base + SSP_TOP_CTRL); + val |= TOP_TRAIL; /* Trailing bytes handled by DMA */ + writel(val, drv_data->base + SSP_TOP_CTRL); + + val = readl(drv_data->base + SSP_FIFO_CTRL); + val |= FIFO_TSRE | FIFO_RSRE; + writel(val, drv_data->base + SSP_FIFO_CTRL); + + /* Start RX first so we're ready the instant we start transmitting */ + dma_async_issue_pending(host->dma_rx); + dma_async_issue_pending(host->dma_tx); + + return 1; +fallback: + transfer->error |= SPI_TRANS_FAIL_NO_START; + + return -EAGAIN; +} + +/* Flush the RX FIFO of any leftover data before processing a message */ +static int k1_spi_prepare_message(struct spi_controller *host, + struct spi_message *message) +{ + struct k1_spi_driver_data *drv_data = spi_controller_get_devdata(host); + u32 val = readl(drv_data->base + SSP_STATUS); + u32 count; + + /* If there's nothing in the FIFO, we're done */ + if (!(val & SSP_STATUS_RNE)) + return 0; + + /* Read and discard what's there (one more than what the field says) */ + count = FIELD_GET(SSP_STATUS_RFL, val) + 1; + do + (void)readl(drv_data->base + SSP_DATAR); + while (--count); + + return 0; +} + +/* Set logic level of chip select line (high=true means CS deasserted) */ +static void k1_spi_set_cs(struct spi_device *spi, bool high) +{ + struct k1_spi_driver_data *drv_data; + u32 val; + + drv_data = spi_controller_get_devdata(spi->controller); + + val = readl(drv_data->base + SSP_TOP_CTRL); + if (high) + val &= ~TOP_HOLD_FRAME_LOW; + else + val |= TOP_HOLD_FRAME_LOW; + writel(val, drv_data->base + SSP_TOP_CTRL); +} + +/* Set the transfer speed; the SPI core code ensures it is supported */ +static int k1_spi_set_speed(struct k1_spi_driver_data *drv_data, + struct spi_transfer *transfer) +{ + struct clk *clk = drv_data->clk; + u64 nsec_per_word; + u64 bus_ticks; + u32 timeout; + u32 val; + int ret; + + ret = clk_set_rate(clk, transfer->speed_hz); + if (ret) + return ret; + + drv_data->rate = clk_get_rate(clk); + + /* No need for RX FIFO timeout if we're not receiving anything */ + if (!transfer->rx_buf) + return 0; + + /* + * Compute the RX FIFO inactivity timeout value that should be used. + * The inactivity timer restarts with each word that lands in the + * FIFO. If several "word transfer times" pass without any new data + * in the RX FIFO, we might as well read what's there. + * + * The rate at which words land in the FIFO is determined by the + * word size and the transfer rate. One bit is transferred per + * clock tick, and 8 (or 16 or 32) bits are transferred per word. + * + * So we can get word transfer time (in nanoseconds) from: + * nsec_per_tick = NSEC_PER_SEC / drv_data->rate; + * ticks_per_word = BITS_PER_BYTE * drv_data->bytes; + * We do the divide last for better accuracy. + */ + nsec_per_word = (u64)NSEC_PER_SEC * BITS_PER_BYTE * drv_data->bytes; + nsec_per_word = DIV_ROUND_UP_ULL(nsec_per_word, drv_data->rate); + + /* + * The timeout (which we'll set to three word transfer times) is + * expressed as a number of APB clock ticks. + * bus_ticks = 3 * nsec * (drv_data->bus_rate / NSEC_PER_SEC) + */ + bus_ticks = 3 * nsec_per_word * drv_data->bus_rate; + timeout = DIV_ROUND_UP_ULL(bus_ticks, NSEC_PER_SEC); + + /* Set the RX timeout period (required for both DMA and PIO) */ + val = FIELD_PREP(SSP_TIMEOUT_MASK, timeout); + writel(val, drv_data->base + SSP_TIMEOUT); + + return 0; +} + +static int k1_spi_transfer_one(struct spi_controller *host, + struct spi_device *spi, + struct spi_transfer *transfer) +{ + struct k1_spi_driver_data *drv_data = spi_controller_get_devdata(host); + u32 ctrl; + u32 val; + int ret; + + /* Bits per word can change on a per-transfer basis */ + drv_data->bytes = spi_bpw_to_bytes(transfer->bits_per_word); + + /* Each transfer can also specify a different rate */ + ret = k1_spi_set_speed(drv_data, transfer); + if (ret) { + dev_err(&host->dev, + "failed to set transfer speed: %d\n", ret); + return ret; + } + + drv_data->rx_resid = transfer->len; + drv_data->tx_resid = transfer->len; + + drv_data->transfer = transfer; + + /* Clear any existing interrupt conditions */ + writel(~0, drv_data->base + SSP_STATUS); + + /* Set the data (word) size, and enable the port */ + ctrl = readl(drv_data->base + SSP_TOP_CTRL); + ctrl &= ~TOP_DSS_MASK; + ctrl |= FIELD_PREP(TOP_DSS_MASK, transfer->bits_per_word - 1); + ctrl |= TOP_SSE; + writel(ctrl, drv_data->base + SSP_TOP_CTRL); + + if (spi_xfer_is_dma_mapped(host, spi, transfer)) + return k1_spi_dma_one(host, spi, transfer); + + /* An interrupt will initiate the transfer */ + val = SSP_INT_EN_TX | SSP_INT_EN_RX | SSP_INT_EN_ERROR; + writel(val, drv_data->base + SSP_INT_EN); + + return 1; /* We will call spi_finalize_current_transfer() */ +} + +static void +k1_spi_handle_err(struct spi_controller *host, struct spi_message *message) +{ + struct k1_spi_driver_data *drv_data = spi_controller_get_devdata(host); + + if (drv_data->dma_enabled) { + dmaengine_terminate_sync(host->dma_rx); + dmaengine_terminate_sync(host->dma_tx); + } +} + +static void k1_spi_write_word(struct k1_spi_driver_data *drv_data) +{ + struct spi_transfer *transfer = drv_data->transfer; + u32 bytes = drv_data->bytes; + u32 val; + + if (transfer->tx_buf) { + const void *buf; + + buf = transfer->tx_buf + (transfer->len - drv_data->tx_resid); + if (bytes == 1) + val = *(u8 *)buf; + else if (bytes == 2) + val = *(u16 *)buf; + else /* bytes == 4 */ + val = *(u32 *)buf; + } else { + val = 0; /* Null writer; write 1, 2, or 4 zero bytes */ + } + /* Fill the next TX FIFO entry */ + writel(val, drv_data->base + SSP_DATAR); + + drv_data->tx_resid -= bytes; +} + +/* The last-read status value is provided; we know SSP_STATUS_TNF is set */ +static bool k1_spi_write(struct k1_spi_driver_data *drv_data, u32 val) +{ + unsigned int count; + + /* Get the number of open slots in the FIFO; zero means all */ + count = FIELD_GET(SSP_STATUS_TFL, val) ? : K1_SPI_FIFO_SIZE; + + /* + * Limit how much we try to send at a time, to reduce the + * chance the other side can overrun our RX FIFO. + */ + count = min3(count, K1_SPI_THRESH, drv_data->tx_resid / drv_data->bytes); + do + k1_spi_write_word(drv_data); + while (--count); + + return !drv_data->tx_resid; +} + +static void k1_spi_read_word(struct k1_spi_driver_data *drv_data) +{ + struct spi_transfer *transfer = drv_data->transfer; + u32 bytes = drv_data->bytes; + u32 val; + + /* Consume the next RX FIFO entry */ + val = readl(drv_data->base + SSP_DATAR); + if (transfer->rx_buf) { + void *buf; + + buf = transfer->rx_buf + (transfer->len - drv_data->rx_resid); + + if (bytes == 1) + *(u8 *)buf = val; + else if (bytes == 2) + *(u16 *)buf = val; + else /* bytes == 4 */ + *(u32 *)buf = val; + } /* Otherwise null reader: discard the data */ + + drv_data->rx_resid -= bytes; +} + +/* The last-read status value is provided; we know SSP_STATUS_RNE is set */ +static bool k1_spi_read(struct k1_spi_driver_data *drv_data, u32 val) +{ + do { + unsigned int count = FIELD_GET(SSP_STATUS_RFL, val) + 1; + + /* Only read what we need */ + count = min(count, drv_data->rx_resid / drv_data->bytes); + do + k1_spi_read_word(drv_data); + while (--count); + + /* If there's no more to read, we're done */ + if (!drv_data->rx_resid) + return true; + + /* Check again in case more became available to read */ + val = readl(drv_data->base + SSP_STATUS); + if (val & SSP_STATUS_RNE) + writel(SSP_STATUS_RNE, drv_data->base + SSP_STATUS); + else + return false; + } while (true); +} + +static irqreturn_t k1_spi_ssp_isr(int irq, void *dev_id) +{ + struct k1_spi_driver_data *drv_data = dev_id; + u32 status; + u32 top_ctrl; + + /* Get status and clear pending interrupts */ + status = readl(drv_data->base + SSP_STATUS); + writel(status, drv_data->base + SSP_STATUS); + + /* If no actionable status bits are set, this is not our interrupt */ + if (!(status & (SSP_STATUS_ERROR | SSP_STATUS_TNF | SSP_STATUS_RNE))) + return IRQ_NONE; + + /* Check for any error conditions first */ + if (status & SSP_STATUS_ERROR) { + if (drv_data->transfer) + drv_data->transfer->error |= SPI_TRANS_FAIL_IO; + goto done; + } + + /* + * For SPI, bytes are transferred in both directions equally, and + * RX always follows TX. Start by writing if there is anything to + * write, then read. Once there's no more to read, we're done. + */ + if (drv_data->tx_resid && (status & SSP_STATUS_TNF)) { + /* If we finish writing, disable TX interrupts */ + if (k1_spi_write(drv_data, status)) + writel(SSP_INT_EN_RX | SSP_INT_EN_ERROR, + drv_data->base + SSP_INT_EN); + } + + /* We're not done unless we've read all that was requested */ + if (drv_data->rx_resid) { + /* Read more if the FIFO is not empty */ + if (status & SSP_STATUS_RNE) + if (k1_spi_read(drv_data, status)) + goto done; + + return IRQ_HANDLED; + } +done: + /* Disable the port */ + top_ctrl = readl(drv_data->base + SSP_TOP_CTRL); + top_ctrl &= ~TOP_SSE; + writel(top_ctrl, drv_data->base + SSP_TOP_CTRL); + + /* Disable all interrupts */ + writel(0, drv_data->base + SSP_INT_EN); + + if (drv_data->transfer) { + drv_data->transfer = NULL; + spi_finalize_current_transfer(drv_data->host); + } + + return IRQ_HANDLED; +} + +static int +k1_spi_dma_setup(struct k1_spi_driver_data *drv_data, struct device *dev) +{ + struct spi_controller *host = drv_data->host; + struct dma_chan *chan; + + chan = dma_request_chan(dev, "tx"); + if (IS_ERR(chan)) + return PTR_ERR(chan); + host->dma_tx = chan; + + chan = dma_request_chan(dev, "rx"); + if (IS_ERR(chan)) { + dma_release_channel(host->dma_tx); + host->dma_tx = NULL; + return PTR_ERR(chan); + } + host->dma_rx = chan; + + drv_data->dma_enabled = true; + + return 0; +} + +static void k1_spi_dma_cleanup(struct device *dev, void *res) +{ + struct k1_spi_driver_data **ptr = res; + struct k1_spi_driver_data *drv_data = *ptr; + struct spi_controller *host = drv_data->host; + + if (!drv_data->dma_enabled) + return; + + drv_data->dma_enabled = false; + + dma_release_channel(host->dma_rx); + host->dma_rx = NULL; + dma_release_channel(host->dma_tx); + host->dma_tx = NULL; +} + +static int +devm_k1_spi_dma_setup(struct k1_spi_driver_data *drv_data, struct device *dev) +{ + struct k1_spi_driver_data **ptr; + int ret; + + if (!IS_ENABLED(CONFIG_MMP_PDMA)) { + dev_info(dev, "DMA not available; using PIO\n"); + return 0; + } + + ptr = devres_alloc(k1_spi_dma_cleanup, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = k1_spi_dma_setup(drv_data, dev); + if (ret) { + devres_free(ptr); + return ret; + } + + *ptr = drv_data; + devres_add(dev, ptr); + + return 0; +} + +static int k1_spi_probe(struct platform_device *pdev) +{ + struct k1_spi_driver_data *drv_data; + struct device *dev = &pdev->dev; + struct reset_control *reset; + struct spi_controller *host; + struct resource *iores; + struct clk *clk_bus; + int ret; + + host = devm_spi_alloc_host(dev, sizeof(*drv_data)); + if (!host) + return -ENOMEM; + drv_data = spi_controller_get_devdata(host); + drv_data->host = host; + platform_set_drvdata(pdev, drv_data); + + ret = devm_k1_spi_dma_setup(drv_data, dev); + if (ret == -EPROBE_DEFER) + return ret; + if (ret) + dev_warn(dev, "DMA setup failed (%d), falling back to PIO\n", ret); + + drv_data->base = devm_platform_get_and_ioremap_resource(pdev, 0, + &iores); + if (IS_ERR(drv_data->base)) + return dev_err_probe(dev, PTR_ERR(drv_data->base), + "error mapping memory\n"); + drv_data->base_addr = iores->start; + + clk_bus = devm_clk_get_enabled(dev, "bus"); + if (IS_ERR(clk_bus)) + return dev_err_probe(dev, PTR_ERR(clk_bus), + "error getting/enabling bus clock\n"); + drv_data->bus_rate = clk_get_rate(clk_bus); + + drv_data->clk = devm_clk_get_enabled(dev, "core"); + if (IS_ERR(drv_data->clk)) + return dev_err_probe(dev, PTR_ERR(drv_data->clk), + "error getting/enabling core clock\n"); + + reset = devm_reset_control_get_exclusive_deasserted(dev, NULL); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), + "error getting/deasserting reset\n"); + + k1_spi_register_reset(drv_data, true); + + drv_data->irq = platform_get_irq(pdev, 0); + if (drv_data->irq < 0) + return dev_err_probe(dev, drv_data->irq, "error getting IRQ\n"); + + ret = devm_request_irq(dev, drv_data->irq, k1_spi_ssp_isr, + IRQF_SHARED, dev_name(dev), drv_data); + if (ret < 0) + return dev_err_probe(dev, ret, "error requesting IRQ\n"); + + /* Initialize the host structure, then register it */ + host->dev.of_node = dev_of_node(dev); + host->dev.parent = dev; + host->num_chipselect = 1; + if (drv_data->dma_enabled) + host->dma_alignment = K1_SPI_DMA_ALIGNMENT; + host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; + host->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); + host->min_speed_hz = K1_SPI_MIN_SPEED_HZ; + host->max_speed_hz = K1_SPI_MAX_SPEED_HZ; + host->flags = SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX; + host->max_dma_len = K1_SPI_MAX_DMA_LEN; + + host->setup = k1_spi_setup; + host->cleanup = k1_spi_cleanup; + host->can_dma = k1_spi_can_dma; + host->prepare_message = k1_spi_prepare_message; + host->set_cs = k1_spi_set_cs; + host->transfer_one = k1_spi_transfer_one; + host->handle_err = k1_spi_handle_err; + + ret = devm_spi_register_controller(dev, host); + if (ret) + dev_err(dev, "error registering controller\n"); + + return ret; +} + +static const struct of_device_id k1_spi_dt_ids[] = { + { .compatible = "spacemit,k1-spi", }, + {} +}; +MODULE_DEVICE_TABLE(of, k1_spi_dt_ids); + +static struct platform_driver k1_spi_driver = { + .probe = k1_spi_probe, + .driver = { + .name = "k1-spi", + .of_match_table = k1_spi_dt_ids, + }, +}; +module_platform_driver(k1_spi_driver); + +MODULE_DESCRIPTION("SpacemiT K1 SPI controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index b10080d618604d..1c4a5cd5a23ee0 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -472,6 +472,8 @@ endmenu source "drivers/thermal/renesas/Kconfig" +source "drivers/thermal/spacemit/Kconfig" + source "drivers/thermal/tegra/Kconfig" config GENERIC_ADC_THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index bb21e7ea7fc6b7..3b249195c088ef 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -65,6 +65,7 @@ obj-y += mediatek/ obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o +obj-y += spacemit/ obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL) += khadas_mcu_fan.o obj-$(CONFIG_LOONGSON2_THERMAL) += loongson2_thermal.o diff --git a/drivers/thermal/spacemit/Kconfig b/drivers/thermal/spacemit/Kconfig new file mode 100644 index 00000000000000..de7b5ece5af2b7 --- /dev/null +++ b/drivers/thermal/spacemit/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "SpacemiT thermal drivers" +depends on ARCH_SPACEMIT || COMPILE_TEST + +config SPACEMIT_K1_TSENSOR + tristate "SpacemiT K1 thermal sensor driver" + depends on THERMAL_OF + help + This driver provides support for the thermal sensor + integrated in the SpacemiT K1 SoC. + + The thermal sensor monitors temperatures for five thermal zones: + soc, package, gpu, cluster0, and cluster1. It supports reporting + temperature values and handling high/low threshold interrupts. + + Say Y here if you want to enable thermal monitoring on SpacemiT K1. + If compiled as a module, it will be called k1_tsensor. + +endmenu diff --git a/drivers/thermal/spacemit/Makefile b/drivers/thermal/spacemit/Makefile new file mode 100644 index 00000000000000..82b30741e4ecfc --- /dev/null +++ b/drivers/thermal/spacemit/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_SPACEMIT_K1_TSENSOR) += k1_tsensor.o diff --git a/drivers/thermal/spacemit/k1_tsensor.c b/drivers/thermal/spacemit/k1_tsensor.c new file mode 100644 index 00000000000000..79222d233129dc --- /dev/null +++ b/drivers/thermal/spacemit/k1_tsensor.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Thermal sensor driver for SpacemiT K1 SoC + * + * Copyright (C) 2026 Shuwei Wu + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../thermal_hwmon.h" + +#define K1_TSENSOR_PCTRL_REG 0x00 +#define K1_TSENSOR_PCTRL_ENABLE BIT(0) +#define K1_TSENSOR_PCTRL_TEMP_MODE BIT(3) +#define K1_TSENSOR_PCTRL_RAW_SEL BIT(7) + +#define K1_TSENSOR_PCTRL_CTUNE GENMASK(11, 8) +#define K1_TSENSOR_PCTRL_SW_CTRL GENMASK(21, 18) +#define K1_TSENSOR_PCTRL_HW_AUTO_MODE BIT(23) + +#define K1_TSENSOR_EN_REG 0x08 +#define K1_TSENSOR_EN_ALL GENMASK(MAX_SENSOR_NUMBER - 1, 0) + +#define K1_TSENSOR_TIME_REG 0x0C +#define K1_TSENSOR_TIME_WAIT_REF_CNT GENMASK(3, 0) +#define K1_TSENSOR_TIME_ADC_CNT_RST GENMASK(7, 4) +#define K1_TSENSOR_TIME_FILTER_PERIOD GENMASK(21, 20) +#define K1_TSENSOR_TIME_MASK GENMASK(23, 0) + +#define K1_TSENSOR_INT_CLR_REG 0x10 +#define K1_TSENSOR_INT_EN_REG 0x14 +#define K1_TSENSOR_INT_STA_REG 0x18 + +#define K1_TSENSOR_INT_EN_MASK BIT(0) +#define K1_TSENSOR_INT_MASK(x) (GENMASK(2, 1) << ((x) * 2)) + +#define K1_TSENSOR_DATA_BASE_REG 0x20 +#define K1_TSENSOR_DATA_REG(x) (K1_TSENSOR_DATA_BASE_REG + ((x) / 2) * 4) +#define K1_TSENSOR_DATA_LOW_MASK GENMASK(15, 0) +#define K1_TSENSOR_DATA_HIGH_MASK GENMASK(31, 16) + +#define K1_TSENSOR_THRSH_BASE_REG 0x40 +#define K1_TSENSOR_THRSH_REG(x) (K1_TSENSOR_THRSH_BASE_REG + ((x) * 4)) +#define K1_TSENSOR_THRSH_LOW_MASK GENMASK(15, 0) +#define K1_TSENSOR_THRSH_HIGH_MASK GENMASK(31, 16) + +#define MAX_SENSOR_NUMBER 5 + +/* Hardware offset value required for temperature calculation */ +#define TEMPERATURE_OFFSET 278 + +struct k1_tsensor_channel { + struct k1_tsensor *ts; + struct thermal_zone_device *tzd; + int id; +}; + +struct k1_tsensor { + void __iomem *base; + struct k1_tsensor_channel ch[MAX_SENSOR_NUMBER]; +}; + +static void k1_tsensor_init(struct k1_tsensor *ts) +{ + u32 val; + + /* Disable all the interrupts */ + writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG); + + /* Configure ADC sampling time and filter period */ + val = readl(ts->base + K1_TSENSOR_TIME_REG); + val &= ~K1_TSENSOR_TIME_MASK; + val |= K1_TSENSOR_TIME_FILTER_PERIOD | + K1_TSENSOR_TIME_ADC_CNT_RST | + K1_TSENSOR_TIME_WAIT_REF_CNT; + writel(val, ts->base + K1_TSENSOR_TIME_REG); + + /* + * Enable all sensors' auto mode, enable dither control, + * consecutive mode, and power up sensor. + */ + val = readl(ts->base + K1_TSENSOR_PCTRL_REG); + val &= ~K1_TSENSOR_PCTRL_SW_CTRL; + val &= ~K1_TSENSOR_PCTRL_CTUNE; + val |= K1_TSENSOR_PCTRL_RAW_SEL | + K1_TSENSOR_PCTRL_TEMP_MODE | + K1_TSENSOR_PCTRL_HW_AUTO_MODE | + K1_TSENSOR_PCTRL_ENABLE; + writel(val, ts->base + K1_TSENSOR_PCTRL_REG); + + /* Enable each sensor */ + val = readl(ts->base + K1_TSENSOR_EN_REG); + val |= K1_TSENSOR_EN_ALL; + writel(val, ts->base + K1_TSENSOR_EN_REG); +} + +static void k1_tsensor_enable_irq(struct k1_tsensor_channel *ch) +{ + struct k1_tsensor *ts = ch->ts; + u32 val; + + val = readl(ts->base + K1_TSENSOR_INT_CLR_REG); + val |= K1_TSENSOR_INT_MASK(ch->id); + writel(val, ts->base + K1_TSENSOR_INT_CLR_REG); + + val = readl(ts->base + K1_TSENSOR_INT_EN_REG); + val &= ~K1_TSENSOR_INT_MASK(ch->id); + writel(val, ts->base + K1_TSENSOR_INT_EN_REG); + + /* Enable thermal interrupt */ + val = readl(ts->base + K1_TSENSOR_INT_EN_REG); + val |= K1_TSENSOR_INT_EN_MASK; + writel(val, ts->base + K1_TSENSOR_INT_EN_REG); +} + +/* + * The conversion formula used is: + * T(m°C) = (((raw_value & mask) >> shift) - TEMPERATURE_OFFSET) * 1000 + */ +static int k1_tsensor_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz); + struct k1_tsensor *ts = ch->ts; + u32 val; + + val = readl(ts->base + K1_TSENSOR_DATA_REG(ch->id)); + if (ch->id % 2) + *temp = FIELD_GET(K1_TSENSOR_DATA_HIGH_MASK, val); + else + *temp = FIELD_GET(K1_TSENSOR_DATA_LOW_MASK, val); + + *temp -= TEMPERATURE_OFFSET; + *temp *= 1000; + + return 0; +} + +/* + * For each sensor, the hardware threshold register is 32 bits: + * - Lower 16 bits [15:0] configure the low threshold temperature. + * - Upper 16 bits [31:16] configure the high threshold temperature. + */ +static int k1_tsensor_set_trips(struct thermal_zone_device *tz, int low, int high) +{ + struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz); + struct k1_tsensor *ts = ch->ts; + u32 val; + + if (low >= high) + return -EINVAL; + + low = clamp_val(low / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET, + FIELD_MAX(K1_TSENSOR_THRSH_LOW_MASK)); + high = clamp_val(high / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET, + FIELD_MAX(K1_TSENSOR_THRSH_HIGH_MASK)); + + val = readl(ts->base + K1_TSENSOR_THRSH_REG(ch->id)); + + val &= ~(K1_TSENSOR_THRSH_LOW_MASK | K1_TSENSOR_THRSH_HIGH_MASK); + val |= FIELD_PREP(K1_TSENSOR_THRSH_LOW_MASK, low); + val |= FIELD_PREP(K1_TSENSOR_THRSH_HIGH_MASK, high); + + writel(val, ts->base + K1_TSENSOR_THRSH_REG(ch->id)); + + return 0; +} + +static const struct thermal_zone_device_ops k1_tsensor_ops = { + .get_temp = k1_tsensor_get_temp, + .set_trips = k1_tsensor_set_trips, +}; + +static irqreturn_t k1_tsensor_irq_thread(int irq, void *data) +{ + struct k1_tsensor *ts = (struct k1_tsensor *)data; + int mask, status, i; + + status = readl(ts->base + K1_TSENSOR_INT_STA_REG); + + for (i = 0; i < MAX_SENSOR_NUMBER; i++) { + if (status & K1_TSENSOR_INT_MASK(i)) { + mask = readl(ts->base + K1_TSENSOR_INT_CLR_REG); + mask |= K1_TSENSOR_INT_MASK(i); + writel(mask, ts->base + K1_TSENSOR_INT_CLR_REG); + thermal_zone_device_update(ts->ch[i].tzd, THERMAL_EVENT_UNSPECIFIED); + } + } + + return IRQ_HANDLED; +} + +static int k1_tsensor_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct k1_tsensor *ts; + struct reset_control *reset; + struct clk *clk; + int i, irq, ret; + + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ts->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ts->base)) + return dev_err_probe(dev, PTR_ERR(ts->base), "Failed to get reg\n"); + + reset = devm_reset_control_get_exclusive_deasserted(dev, NULL); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), "Failed to get/deassert reset control\n"); + + clk = devm_clk_get_enabled(dev, "core"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get core clock\n"); + + clk = devm_clk_get_enabled(dev, "bus"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get bus clock\n"); + + k1_tsensor_init(ts); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, NULL, + k1_tsensor_irq_thread, + IRQF_ONESHOT, "k1_tsensor", ts); + if (ret < 0) + return ret; + + for (i = 0; i < MAX_SENSOR_NUMBER; ++i) { + ts->ch[i].id = i; + ts->ch[i].ts = ts; + ts->ch[i].tzd = devm_thermal_of_zone_register(dev, i, ts->ch + i, &k1_tsensor_ops); + if (IS_ERR(ts->ch[i].tzd)) + return PTR_ERR(ts->ch[i].tzd); + + /* Attach sysfs hwmon attributes for userspace monitoring */ + ret = devm_thermal_add_hwmon_sysfs(dev, ts->ch[i].tzd); + if (ret) + dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); + + k1_tsensor_enable_irq(ts->ch + i); + } + + platform_set_drvdata(pdev, ts); + + return 0; +} + +static const struct of_device_id k1_tsensor_dt_ids[] = { + { .compatible = "spacemit,k1-tsensor" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, k1_tsensor_dt_ids); + +static struct platform_driver k1_tsensor_driver = { + .driver = { + .name = "k1_tsensor", + .of_match_table = k1_tsensor_dt_ids, + }, + .probe = k1_tsensor_probe, +}; +module_platform_driver(k1_tsensor_driver); + +MODULE_DESCRIPTION("SpacemiT K1 Thermal Sensor Driver"); +MODULE_AUTHOR("Shuwei Wu "); +MODULE_LICENSE("GPL"); diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig index 964ae70e7390d0..9b57772faf5f83 100644 --- a/drivers/ufs/host/Kconfig +++ b/drivers/ufs/host/Kconfig @@ -132,6 +132,18 @@ config SCSI_UFS_VARIABLE_SG_ENTRY_SIZE bool default y if SCSI_UFS_EXYNOS && SCSI_UFS_CRYPTO +config SCSI_UFS_SPACEMIT + tristate "SpacemiT UFS controller driver" + depends on SCSI_UFSHCD_PLATFORM && ARCH_SPACEMIT + help + This selects the SpacemiT specific additions to UFSHCD platform driver. + UFS host on SpacemiT needs some vendor specific configuration before + accessing the hardware which includes PHY configuration and vendor + specific registers. + + Select this if you have UFS controller on SpacemiT chipset. + If unsure, say N. + config SCSI_UFS_SPRD tristate "Unisoc specific hooks to UFS controller platform driver" depends on SCSI_UFSHCD_PLATFORM && (ARCH_SPRD || COMPILE_TEST) diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile index 65d8bb23ab7b3b..151bf43dfba774 100644 --- a/drivers/ufs/host/Makefile +++ b/drivers/ufs/host/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_SCSI_UFS_HISI) += ufs-hisi.o obj-$(CONFIG_SCSI_UFS_MEDIATEK) += ufs-mediatek.o obj-$(CONFIG_SCSI_UFS_RENESAS) += ufs-renesas.o obj-$(CONFIG_SCSI_UFS_ROCKCHIP) += ufs-rockchip.o +obj-$(CONFIG_SCSI_UFS_SPACEMIT) += ufs-spacemit.o obj-$(CONFIG_SCSI_UFS_SPRD) += ufs-sprd.o obj-$(CONFIG_SCSI_UFS_TI_J721E) += ti-j721e-ufs.o obj-$(CONFIG_SCSI_UFS_AMD_VERSAL2) += ufs-amd-versal2.o ufshcd-dwc.o diff --git a/drivers/ufs/host/ufs-spacemit.c b/drivers/ufs/host/ufs-spacemit.c new file mode 100644 index 00000000000000..be475f5566a839 --- /dev/null +++ b/drivers/ufs/host/ufs-spacemit.c @@ -0,0 +1,931 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026 SpacemiT (Hangzhou) Technology Co. Ltd + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ufshcd-pltfrm.h" +#include "ufs-spacemit.h" + +/* PA Layer Gettable and settable M-PHY Specific Attributes */ +#define PA_TXHSG1SYNCLENGTH 0x1552 +#define PA_TXHSG1PREPARELENGTH 0x1553 +#define PA_TXHSG2SYNCLENGTH 0x1554 +#define PA_TXHSG2PREPARELENGTH 0x1555 +#define PA_TXHSG3SYNCLENGTH 0x1556 +#define PA_TXHSG3PREPARELENGTH 0x1557 +#define PA_TXMK2EXTENSION 0x155A +#define PA_PEERSCRAMBLING 0x155B +#define PA_TXSKIP 0x155C +#define PA_TXSKIPPERIOD 0x155D +#define PA_PEER_TX_LCC_ENABLE 0x155F + +#define PA_SCRAMBLING 0x1585 +#define PA_MK2EXTENSIONGUARDBAND 0x15AB + +/*special TX/RX Configuration Attributes*/ +#define RX_LS_PRE_LEN_CAP 0x008D +#define RX_LANE_HB8_BKDOOR_ATTR 0x00F4 +#define RX_PWRM_CLOSURE_LEN_CAP 0x008E +#define RX_MIN_STALL_CAP 0x0088 +#define RX_LANE_SOF_BKDOOR_ATT 0x00F2 +#define RX_GARBAGE_COUNT_OFFSET 0x00F2 + +/*special analog reg*/ +#define ANA_EQ_CTRL_REG_ATTR 0x00CD +#define ANA_HSGEAR_CTRL_ATTR 0x00C1 + +/* + * Keep UFS ACLK at a lower parent rate (409.6MHz) for stable init. + * This mirrors the "ufs-low-aclk-freq" change from the other environment. + */ +#define UFS_ACLK_LOW_FREQ_HZ 409600000UL + +/* PHY register magic values */ +#define MPHY_PU_ALL 0x87f +#define MPHY_PU_WITH_HB8_RESET 0xb7f +#define MPHY_DEVICE_RESET_DEASSERT 0x101 +#define MPHY_DEVICE_RESET_ASSERT 0x001 +#define MPHY_PLL_LOCK_BIT BIT(31) +#define MPHY_PLL_LOCK_TIMEOUT_US 10000 + +/* FSM states */ +#define FSM_STATE_HIBERN8 0x1 +#define FSM_STATE_ACTIVE 0x3 +#define FSM_STATE_LS_BURST 0x5 + +struct ufs_reg_snapshot { + u32 reg_utrlba; + u32 reg_utrlbau; + u32 reg_utrmlba; + u32 reg_utrmlbau; + + u32 reg_sys1clk; + u32 reg_tx_symbol_clk; + u32 reg_retry_timer; + u32 reg_pa_link; + u32 reg_cfg1; +}; + +#define VENDOR_DUMP_BUF_SIZE 2048 +#define VENDOR_MAX_OFFSET 0xE0 + +static void ufs_spacemit_dump_host_regs(struct ufs_hba *hba) +{ + int i, offset; + size_t len = 0; + u8 *buf; + u32 val; + u32 host_reg[] = { + (UFS_PHY_MNG_BASE + UFS_MPHY_RST_CTRL), + (UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL), + (UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL), + 0xFFF, + }; + + buf = kzalloc(VENDOR_DUMP_BUF_SIZE, GFP_ATOMIC); + if (!buf) + return; + + len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, "vendor specific registers:"); + + for (offset = 0xC0, i = 0; offset < VENDOR_MAX_OFFSET; offset += 4, i++) { + val = ufshcd_readl(hba, offset); + if (i % 4 == 0) + len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, "\n"); + len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, " 0x%03x: 0x%08x ", + offset, val); + } + + len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, "\n mphy and atop registers:"); + + for (i = 0; host_reg[i] != 0xFFF; i++) { + val = ufshcd_readl(hba, host_reg[i]); + if (i % 4 == 0) + len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, "\n"); + len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, " 0x%03x: 0x%08x ", + host_reg[i], val); + } + len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, "\n"); + + dev_warn(hba->dev, "%s", buf); + + kfree(buf); +} + +/* M-PHY FSM states */ +#define MPHY_RX_FSM_STATE 0xC1 +#define MPHY_TX_FSM_STATE 0x41 + +static bool is_fsm_state_valid(u32 state) +{ + return (state == FSM_STATE_ACTIVE || state == FSM_STATE_LS_BURST); +} + +static int ufs_spacemit_check_hibern8(struct ufs_hba *hba) +{ + u32 tx_fsm_val_0 = 0; + u32 tx_fsm_val_1 = 0; + int retries = DIV_ROUND_UP(HBRN8_POLL_TOUT_MS * 1000, 100); + int err = 0; + + do { + err = ufshcd_dme_get(hba, + UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, + UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), + &tx_fsm_val_0); + err |= ufshcd_dme_get(hba, + UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, + UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), + &tx_fsm_val_1); + if (err || (tx_fsm_val_0 == TX_FSM_HIBERN8 && + tx_fsm_val_1 == TX_FSM_HIBERN8)) + break; + + usleep_range(100, 200); + } while (--retries > 0); + + if (!err && retries <= 0) { + err = ufshcd_dme_get(hba, + UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, + UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), + &tx_fsm_val_0); + err |= ufshcd_dme_get(hba, + UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, + UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), + &tx_fsm_val_1); + } + + if (err) { + dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err %d\n", + __func__, err); + } else if (tx_fsm_val_0 != TX_FSM_HIBERN8 || + tx_fsm_val_1 != TX_FSM_HIBERN8) { + err = -ETIMEDOUT; + dev_err(hba->dev, + "%s: invalid TX_FSM_STATE, lane0 = %u, lane1 = %u\n", + __func__, tx_fsm_val_0, tx_fsm_val_1); + } + + return err; +} + +static void ufs_spacemit_dump_fsm_state(struct ufs_hba *hba) +{ + u32 tx0_fsm_val, tx1_fsm_val, rx0_fsm_val, rx1_fsm_val; + int err; + + err = ufshcd_dme_get(hba, + UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), + &tx0_fsm_val); + if (err) + return; + + usleep_range(100, 200); + + err = ufshcd_dme_get(hba, + UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), + &tx1_fsm_val); + if (err) + return; + + usleep_range(100, 200); + + err = ufshcd_dme_get(hba, + UIC_ARG_MIB_SEL(MPHY_RX_FSM_STATE, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), + &rx0_fsm_val); + if (err) + return; + + usleep_range(100, 200); + + err = ufshcd_dme_get(hba, + UIC_ARG_MIB_SEL(MPHY_RX_FSM_STATE, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), + &rx1_fsm_val); + if (err) + return; + + usleep_range(100, 200); + + if (!is_fsm_state_valid(tx0_fsm_val) || !is_fsm_state_valid(tx1_fsm_val) || + !is_fsm_state_valid(rx0_fsm_val) || !is_fsm_state_valid(rx1_fsm_val)) { + dev_warn(hba->dev, "FSM state invalid - TX:[0x%x, 0x%x], RX:[0x%x, 0x%x]\n", + tx0_fsm_val, tx1_fsm_val, rx0_fsm_val, rx1_fsm_val); + } +} + +static int ufs_spacemit_get_connected_tx_lanes(struct ufs_hba *hba, u32 *tx_lanes) +{ + int err; + + err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_CONNECTEDTXDATALANES), tx_lanes); + if (err) + dev_err(hba->dev, "%s: couldn't read PA_CONNECTEDTXDATALANES %d\n", __func__, err); + + return err; +} + +static u32 ufs_spacemit_get_sys1clk_1us(struct ufs_hba *hba) +{ + struct ufs_clk_info *clki, *ufs_aclk = NULL; + struct list_head *head = &hba->clk_list_head; + unsigned long rate_hz = 0; + + if (!list_empty(head)) { + list_for_each_entry(clki, head, list) { + if (clki->name && !strcmp(clki->name, "aclk") && clki->clk) { + ufs_aclk = clki; + break; + } + } + } + + if (ufs_aclk && ufs_aclk->clk) + rate_hz = clk_get_rate(ufs_aclk->clk); + + if (!rate_hz) + return 0; + + return DIV_ROUND_CLOSEST(rate_hz, 1000000); +} + +static int ufs_spacemit_wait_mphy_pll_lock(struct ufs_hba *hba) +{ + int timeout = MPHY_PLL_LOCK_TIMEOUT_US; + u32 val; + + while (timeout-- > 0) { + val = ufshcd_readl(hba, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL); + if (val & MPHY_PLL_LOCK_BIT) + return 0; + + udelay(1); + } + + dev_err(hba->dev, "M-PHY PLL lock timeout\n"); + return -ETIMEDOUT; +} + +/** + * ufs_spacemit_mphy_init + * @hba: host controller instance + */ +static int ufs_spacemit_mphy_init(struct ufs_hba *hba) +{ + int ret; + + /* reset all mphy logical */ + ufshcd_writel(hba, 0x003, UFS_PHY_MNG_BASE + 0x0); + mdelay(1); + + /* power up all */ + ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + 0x4); + mdelay(1); + + /* asserted ana_rx_hb8_reset */ + ufshcd_writel(hba, 0xb7f, UFS_PHY_MNG_BASE + 0x4); + mdelay(1); + + /* deasserted ana_rx_hb8_reset */ + ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + 0x4); + mdelay(1); + + /* deasserted ufs device reset & refer clk output enable */ + ufshcd_writel(hba, 0x101, UFS_PHY_MNG_BASE + 0xC); + mdelay(1); + + ret = ufs_spacemit_wait_mphy_pll_lock(hba); + if (ret < 0) + return ret; + + dev_dbg(hba->dev, "M-PHY PLL locked successfully\n"); + + ufshcd_writel(hba, 0x1, UFS_PHY_MNG_BASE + 0x08); + udelay(5); + + ufshcd_writel(hba, 0x40, UFS_ATOP_BASE + (0xC2 << 2)); + udelay(5); + + ufshcd_writel(hba, 0x0, UFS_PHY_MNG_BASE + 0x08); + + mdelay(2); + + dev_dbg(hba->dev, "M-PHY init completed\n"); + + return 0; +} + +static int ufs_spacemit_uniprov1p6_init(struct ufs_hba *hba) +{ + /* PA_TXHSG1SYNCLENGTH */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x1552), 0x4f); + + /* PA_TXHSG1PREPARELENGTH */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x1553), 0xf); + + /* PA_TXHSG2SYNCLENGTH */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x1554), 0x4f); + + /* PA_TXHSG2PREPARELENGTH */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x1555), 0xf); + + /* PA_TXHSG3SYNCLENGTH */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x1556), 0x4f); + + /* PA_TXHSG3PREPARELENGTH */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x1557), 0xf); + + /* PA_TXMK2EXTENSION */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x155A), 0x0); + + /* PA_PEERSCRAMBLING */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x155B), 0x1); + + /* PA_TXSKIP */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x155C), 0x1); + + /* PA_TXSKIPPERIOD */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x155D), 250); + + /* PA_LOCAL_TX_LCC_ENABLE */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x155E), 0x0); + + /* PA_PEER_TX_LCC_ENABLE */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x155F), 0x0); + + /* PA_SCRAMBLING */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x1585), 0x1); + + /* PA_GRANULARITY */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x15AA), 0x1); + + /* PA_MK2EXTENSIONGUARDBAND */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x15AB), 0x0); + + /* PA_STALLNOCONFIGTIME */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x15A3), 15); + + /* PA_TACTIVATE */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x15A8), 0x64); + + /* PA_TXTRAILINGCLOCKS */ + ufshcd_dme_set(hba, UIC_ARG_MIB(0x1564), 0x64); + + /* RX_LS_PREPARELEN_TIME RX0 */ + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008D, 4), 0x0B); + + /* RX_LS_PREPARELEN_TIME RX1 */ + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008D, 5), 0X0B); + + /* RX_HIBERNATE_BKEN RX0 */ + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x00F4, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x9F); + + /* RX_HIBERNATE_BKEN RX1 */ + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x00F4, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x9F); + + /* PWM_BURST_closure_length */ + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008E, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 15); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008E, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 15); + + /* min_stall_not_config_time*/ + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0088, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0xFF); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0088, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0xFF); + + /* TX HB8_TIME CAP */ + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x000F, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x64); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x000F, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), 0x64); + + /*RX HB8_TIME CAP*/ + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0092, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x64); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0092, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x64); + + /*TX EQ 3DB*/ + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x00CD, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x5); + + /*RX garbage cnt = 32 SI*/ + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x00F2, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x9F); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x00F2, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x9F); + + dev_info(hba->dev, "UniPro v1.6 init completed\n"); + + return 0; +} + +static void ufs_spacemit_set_dev_cap(struct ufs_host_params *host_params, u32 pwr_hs) +{ + if (!host_params) + return; + + memset(host_params, 0, sizeof(struct ufs_host_params)); + host_params->tx_lanes = UFS_SPACEMIT_K3_LIMIT_NUM_LANES_TX; + host_params->rx_lanes = UFS_SPACEMIT_K3_LIMIT_NUM_LANES_RX; + host_params->hs_rx_gear = UFS_SPACEMIT_K3_LIMIT_HSGEAR_RX; + host_params->hs_tx_gear = UFS_SPACEMIT_K3_LIMIT_HSGEAR_TX; + host_params->pwm_rx_gear = UFS_SPACEMIT_K3_LIMIT_PWMGEAR_RX; + host_params->pwm_tx_gear = UFS_SPACEMIT_K3_LIMIT_PWMGEAR_TX; + host_params->rx_pwr_pwm = UFS_SPACEMIT_K3_LIMIT_RX_PWR_PWM; + host_params->tx_pwr_pwm = UFS_SPACEMIT_K3_LIMIT_TX_PWR_PWM; + host_params->rx_pwr_hs = pwr_hs; + host_params->tx_pwr_hs = pwr_hs; + host_params->hs_rate = UFS_SPACEMIT_K3_LIMIT_HS_RATE; + host_params->desired_working_mode = UFS_HS_MODE; +} + +static int ufs_spacemit_link_startup_pre_change(struct ufs_hba *hba) +{ + u32 value, sys1clk_1us; + + ufs_spacemit_mphy_init(hba); + + ufs_spacemit_uniprov1p6_init(hba); + + /* config sysclk and tx symbol clk before link startup */ + value = UFS_MAX_LINKSTARTUP_TIMER; + + /* clear bit0~bit3, select b0 design */ + value &= ~0xf; + + ufshcd_writel(hba, value, UFS_PA_LINK_STARTUP_TIMER); + + sys1clk_1us = ufs_spacemit_get_sys1clk_1us(hba); + if (!sys1clk_1us) + sys1clk_1us = DIV_ROUND_CLOSEST(UFS_ACLK_LOW_FREQ_HZ, 1000000); + ufshcd_writel(hba, sys1clk_1us, UFS_SYS1CLK_1US); + ufshcd_writel(hba, UFS_TX_SYMBO_CLK, UFS_TX_SYMBOL_CLK_NS_US); + + dev_dbg(hba->dev, "REG_UFS_SYS1CLK_1US: 0x%x\n", ufshcd_readl(hba, UFS_SYS1CLK_1US)); + dev_dbg(hba->dev, "REG_UFS_TX_SYMBOL_CLK_NS_US: 0x%x\n", + ufshcd_readl(hba, UFS_TX_SYMBOL_CLK_NS_US)); + + return 0; +} + +static int ufs_spacemit_link_startup_post_change(struct ufs_hba *hba) +{ + u32 tx_lanes; + + /* add 0xe8 make UFS2.1 run GEAR3 + 2Lane@409M */ + mdelay(5); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x97); + mdelay(1); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0xd7); + mdelay(1); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x17); + + ufshcd_dme_set(hba, UIC_ARG_MIB(DL_AFC0REQTIMEOUTVAL), UFS_DL_AFC0REQTIMEOUTVAL_MAX); + + return ufs_spacemit_get_connected_tx_lanes(hba, &tx_lanes); +} + +static int ufs_spacemit_link_startup_notify(struct ufs_hba *hba, + enum ufs_notify_change_status status) +{ + switch (status) { + case PRE_CHANGE: + ufs_spacemit_link_startup_pre_change(hba); + break; + case POST_CHANGE: + ufs_spacemit_link_startup_post_change(hba); + break; + default: + break; + } + + return 0; +} + +static int ufs_spacemit_negotiate_pwr_mode(struct ufs_hba *hba, + const struct ufs_pa_layer_attr *dev_max_params, + struct ufs_pa_layer_attr *dev_req_params) +{ + struct ufs_host_params host_params; + int ret; + + ufs_spacemit_set_dev_cap(&host_params, FAST_MODE); + ret = ufshcd_negotiate_pwr_params(&host_params, dev_max_params, + dev_req_params); + if (ret) { + dev_err(hba->dev, "Failed to negotiate power params: %d\n", ret); + return ret; + } + + dev_dbg(hba->dev, + "Power mode config - gear_rx:%d, gear_tx:%d, lane_rx:%d, lane_tx:%d, pwr_rx:%d, pwr_tx:%d, hs_rate:%d\n", + dev_req_params->gear_rx, dev_req_params->gear_tx, dev_req_params->lane_rx, + dev_req_params->lane_tx, dev_req_params->pwr_rx, dev_req_params->pwr_tx, + dev_req_params->hs_rate); + + return ret; +} + +static int ufs_spacemit_pwr_change_notify(struct ufs_hba *hba, + enum ufs_notify_change_status status, + struct ufs_pa_layer_attr *dev_req_params) +{ + struct ufs_spacemit_host *host = ufshcd_get_variant(hba); + int ret = 0; + + if (!dev_req_params) { + dev_err(hba->dev, "Invalid Parameters\n"); + return -EINVAL; + } + + switch (status) { + case PRE_CHANGE: + break; + case POST_CHANGE: + /* cache the power mode parameters to use internally */ + memcpy(&host->dev_req_params, dev_req_params, sizeof(*dev_req_params)); + ret = ufs_spacemit_wait_mphy_pll_lock(hba); + if (ret < 0) + return ret; + + dev_dbg(hba->dev, "M-PHY PLL locked after power mode change\n"); + /*set ANA_HSGEAR_CTRL_ATTR back to default value*/ + ufshcd_dme_set(hba, UIC_ARG_MIB(ANA_HSGEAR_CTRL_ATTR), 0x00); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int ufs_spacemit_quirk_host_pa_saveconfigtime(struct ufs_hba *hba) +{ + int err; + u32 pa_vs_config_reg1; + + err = ufshcd_dme_get(hba, UIC_ARG_MIB(UFS_PA_VS_CONFIG_REG1), &pa_vs_config_reg1); + if (err) + return err; + + /* Allow extension of MSB bits of PA_SaveConfigTime attribute */ + return ufshcd_dme_set(hba, UIC_ARG_MIB(UFS_PA_VS_CONFIG_REG1), + (pa_vs_config_reg1 | (1 << 12))); +} + +static int ufs_spacemit_apply_dev_quirks(struct ufs_hba *hba) +{ + if (hba->dev_quirks & UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME) + ufs_spacemit_quirk_host_pa_saveconfigtime(hba); + + if (hba->dev_info.wmanufacturerid == UFS_VENDOR_WDC) + hba->dev_quirks |= UFS_DEVICE_QUIRK_HOST_PA_TACTIVATE; + + /*LCC_DISABLE*/ + mdelay(50); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_LCC_ENABLE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0); + mdelay(1); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_LCC_ENABLE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), 0); + + /*TX_Min_ActivateTime*/ + mdelay(1); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_MIN_ACTIVATETIME, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), + 0x0); + mdelay(1); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_MIN_ACTIVATETIME, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), + 0x0); + mdelay(10); + + /*use backdoor reg to pre-set TX RATE/GEAR to let PLL lock before set_power_mode switch*/ + ufshcd_dme_set(hba, UIC_ARG_MIB(ANA_HSGEAR_CTRL_ATTR), 0x25); + mdelay(10); + + return ufs_spacemit_wait_mphy_pll_lock(hba); +} + +/** + * ufs_spacemit_advertise_quirks - advertise the known Spacemit UFS controller quirks + * @hba: host controller instance + * + * Spacemit UFS host controller might have some non standard behaviours (quirks) + * than what is specified by UFSHCI specification. Advertise all such + * quirks to standard UFS host controller driver so standard takes them into + * account. + */ +static void ufs_spacemit_advertise_quirks(struct ufs_hba *hba) +{ + hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8; +} + +/** + * ufs_spacemit_fsm_dump_work - Deferred work to dump FSM state + * @work: work structure + * + * This function is called from a workqueue context (not interrupt context), + * allowing safe execution of blocking operations like ufshcd_dme_get(). + */ +static void ufs_spacemit_fsm_dump_work(struct work_struct *work) +{ + struct ufs_spacemit_host *host = container_of(work, struct ufs_spacemit_host, + fsm_dump_work); + struct ufs_hba *hba = host->hba; + + /* Safe to call blocking functions in workqueue context */ + if (ufshcd_is_link_active(hba)) + ufs_spacemit_dump_fsm_state(hba); +} + +/** + * ufs_spacemit_init - init phy and prepare clk + * @hba: host controller instance + */ +static int ufs_spacemit_init(struct ufs_hba *hba) +{ + int err = 0; + struct device *dev = hba->dev; + struct ufs_spacemit_host *host; + + host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + host->rst = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL); + if (IS_ERR(host->rst)) + return dev_err_probe(dev, PTR_ERR(host->rst), + "Failed to get reset control\n"); + + host->hba = hba; + ufshcd_set_variant(hba, host); + ufs_spacemit_advertise_quirks(hba); + + /* Initialize workqueue for deferred FSM state dump */ + INIT_WORK(&host->fsm_dump_work, ufs_spacemit_fsm_dump_work); + + err = ufshcd_vops_phy_initialization(host->hba); + return err; +} + +/** + * ufs_spacemit_device_reset - Toggle device reset line + * @hba: per-adapter instance + * + * Toggles the reset line to reset the attached UFS device. + * + * Returns: 0 on success + */ +static int ufs_spacemit_device_reset(struct ufs_hba *hba) +{ + /* Stop device ref_clk & asserted ufs device reset */ + ufshcd_writel(hba, 0x000, UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL); + mdelay(1); + + /* Enable device ref_clk & de-asserted ufs device reset */ + ufshcd_writel(hba, MPHY_DEVICE_RESET_DEASSERT, UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL); + mdelay(1); + + return 0; +} + +/** + * ufs_spacemit_event_notify - Handle UFS error events + * @hba: host controller instance + * @evt: event type + * @data: event-specific data + * + * Handles error events from UFS core, dumps registers immediately + * and schedules FSM state dump for later execution in workqueue context. + */ +static void ufs_spacemit_event_notify(struct ufs_hba *hba, enum ufs_event_type evt, void *data) +{ + struct ufs_spacemit_host *host = ufshcd_get_variant(hba); + bool dump_regs = false; + + switch (evt) { + case UFS_EVT_PA_ERR: + if (data) + dev_warn(hba->dev, "PA error event, INT errors:0x%x, PA_ERR_CODE:0x%x\n", + hba->errors, *(u32 *)data); + + dump_regs = true; + break; + + case UFS_EVT_DL_ERR: + if (data) + dev_warn(hba->dev, "DL error event, INT errors:0x%x, DL_ERR:0x%x\n", + hba->errors, *(u32 *)data); + + dump_regs = true; + break; + + case UFS_EVT_ABORT: + dev_warn(hba->dev, "Abort event, INT errors:0x%x\n", hba->errors); + break; + + default: + break; + } + + /* Dump registers if error occurred (safe in interrupt context) */ + if (hba->errors || dump_regs) + ufs_spacemit_dump_host_regs(hba); + + /* Schedule FSM state dump in workqueue context (not in interrupt context) */ + if (ufshcd_is_link_active(hba) && host) + queue_work(system_wq, &host->fsm_dump_work); +} + +/** + * ufs_spacemit_hibern8_notify - Handle hibernate enter/exit + * @hba: host controller instance + * @cmd: UIC command (HIBER_ENTER or HIBER_EXIT) + * @status: notification status + * + * Manages M-PHY power state during hibernate transitions. + */ +static void ufs_spacemit_hibern8_notify(struct ufs_hba *hba, enum uic_cmd_dme cmd, + enum ufs_notify_change_status status) +{ + int ret; + + dev_dbg(hba->dev, "Hibern8 notify: cmd=%d, status=%d\n", cmd, status); + if (status == PRE_CHANGE) { + if (cmd == UIC_CMD_DME_HIBER_EXIT) { + mdelay(1); + + /* Enable reference clock */ + ufshcd_writel(hba, MPHY_DEVICE_RESET_DEASSERT, + UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL); + mdelay(1); + + /* Power up all */ + ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL); + mdelay(1); + + /* Assert ana_rx_hb8_reset */ + ufshcd_writel(hba, MPHY_PU_WITH_HB8_RESET, + UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL); + mdelay(1); + + /* Deassert ana_rx_hb8_reset */ + ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL); + + ret = ufs_spacemit_wait_mphy_pll_lock(hba); + if (ret < 0) + return; + + mdelay(1); + ufshcd_dme_set(hba, UIC_ARG_MIB(0xdd), 0x57); + mdelay(1); + ufshcd_dme_set(hba, UIC_ARG_MIB(0xe8), 0x57); + } + } + + if (status == PRE_CHANGE) { + if (cmd == UIC_CMD_DME_HIBER_ENTER) { + ufshcd_dme_set(hba, + UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), + 0x84); + ufshcd_dme_set(hba, + UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), + 0x84); + ufshcd_dme_set(hba, + UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), + 0x85); + ufshcd_dme_set(hba, + UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), + 0x85); + } + } + + if (status == POST_CHANGE) { + if (cmd == UIC_CMD_DME_HIBER_ENTER) { + ufs_spacemit_check_hibern8(hba); + ufshcd_dme_set(hba, + UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), + 0x84); + ufshcd_dme_set(hba, + UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), + 0x84); + ufshcd_dme_set(hba, + UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), + 0x80); + ufshcd_dme_set(hba, + UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), + 0x80); + + mdelay(1); + ufshcd_dme_set(hba, UIC_ARG_MIB(0xdd), 0x57); + mdelay(1); + ufshcd_dme_set(hba, UIC_ARG_MIB(0xdd), 0xd7); + mdelay(1); + ufshcd_dme_set(hba, UIC_ARG_MIB(0xe8), 0x57); + mdelay(1); + ufshcd_dme_set(hba, UIC_ARG_MIB(0xe8), 0xd7); + mdelay(1); + + /* Power down M-PHY */ + ufshcd_writel(hba, 0x0, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL); + mdelay(1); + + /* Keep reference clock enabled, assert device reset */ + ufshcd_writel(hba, MPHY_DEVICE_RESET_ASSERT, + UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL); + } + } +} + +/** + * ufs_spacemit_hce_enable_notify - Configure HCE enable sequence + * @hba: host controller instance + * @status: notification status (PRE_CHANGE or POST_CHANGE) + * + * Configures host controller enable with proper sequencing. + * Handles crypto enable if supported. + * + * Returns: 0 on success + */ +static int ufs_spacemit_hce_enable_notify(struct ufs_hba *hba, + enum ufs_notify_change_status status) +{ + struct ufs_spacemit_host *host = ufshcd_get_variant(hba); + u32 enable_val, val; + + if (status == PRE_CHANGE) { + enable_val = CONTROLLER_ENABLE; + + if (hba->caps & UFSHCD_CAP_CRYPTO) + enable_val = CRYPTO_GENERAL_ENABLE | CONTROLLER_ENABLE; + + if (!host->first_hce_done) { + host->first_hce_done = true; + dev_dbg(hba->dev, "First HCE enable\n"); + } else { + val = ufshcd_readl(hba, REG_CONTROLLER_ENABLE); + if (val == enable_val) { + ufshcd_writel(hba, enable_val & (1 << CONTROLLER_ENABLE), + REG_CONTROLLER_ENABLE); + + while (ufshcd_readl(hba, REG_CONTROLLER_ENABLE) == + (enable_val & (1 << CONTROLLER_ENABLE))) + ; + } + } + } + + return 0; +} + +/** + * struct ufs_hba_spacemit_vops - UFS Spacemit specific variant operations + * + * The variant operations configure the necessary controller and PHY + * handshake during initialization. + */ +static const struct ufs_hba_variant_ops ufs_hba_spacemit_vops = { + .name = "ufshcd-spacemit", + .init = ufs_spacemit_init, + .link_startup_notify = ufs_spacemit_link_startup_notify, + .negotiate_pwr_mode = ufs_spacemit_negotiate_pwr_mode, + .pwr_change_notify = ufs_spacemit_pwr_change_notify, + .device_reset = ufs_spacemit_device_reset, + .event_notify = ufs_spacemit_event_notify, + .apply_dev_quirks = ufs_spacemit_apply_dev_quirks, + .hibern8_notify = ufs_spacemit_hibern8_notify, + .hce_enable_notify = ufs_spacemit_hce_enable_notify, +}; + +static const struct of_device_id ufs_spacemit_of_match[] = { + { .compatible = "spacemit,k3-ufshc" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ufs_spacemit_of_match); + +static int ufs_spacemit_probe(struct platform_device *pdev) +{ + return ufshcd_pltfrm_init(pdev, &ufs_hba_spacemit_vops); +} + +static void ufs_spacemit_remove(struct platform_device *pdev) +{ + ufshcd_pltfrm_remove(pdev); +} + +static struct platform_driver ufs_spacemit_pltform = { + .probe = ufs_spacemit_probe, + .remove = ufs_spacemit_remove, + .driver = { + .name = "ufshcd-spacemit", + .of_match_table = of_match_ptr(ufs_spacemit_of_match), + }, +}; +module_platform_driver(ufs_spacemit_pltform); + +MODULE_DESCRIPTION("SpacemiT UFS Host Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ufs/host/ufs-spacemit.h b/drivers/ufs/host/ufs-spacemit.h new file mode 100644 index 00000000000000..6ae3c263a3601f --- /dev/null +++ b/drivers/ufs/host/ufs-spacemit.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * SpacemiT UFS Host Controller driver + * + * Copyright (c) 2026 SpacemiT (Hangzhou) Technology Co. Ltd + */ + +#ifndef _UFS_SPACEMIT_H_ +#define _UFS_SPACEMIT_H_ + +#include +#include +#include +#include +#include + +/* Spacemit K3 UFS host controller vendor specific registers */ +#define UFS_SYS1CLK_1US 0xC0 +#define UFS_TX_SYMBOL_CLK_NS_US 0xC4 +#define UFS_LOCAL_PORT_ID_REG 0xC8 +#define UFS_PA_ERR_CODE 0xCC +#define UFS_RETRY_TIMER_REG 0xD0 +#define UFS_PA_LINK_STARTUP_TIMER 0xD8 +#define UFS_CFG1 0xDC + +/* MPHY control registers */ +#define UFS_PHY_MNG_BASE 0x1B00 +#define UFS_MPHY_RST_CTRL 0x0 +#define UFS_MPHY_PU_CTRL 0x4 +#define UFS_MPHY_BKDR_CTRL 0x8 +#define UFS_DEVICE_IO_CTRL 0xC + +/* ATOP base*/ +#define UFS_ATOP_BASE 0x1C00 + +#define UFS_SYSCLK 499 +#define UFS_TX_SYMBO_CLK 0x800 +#define UFS_MAX_LINKSTARTUP_TIMER 0xFFFFFFFF +#define UFS_DL_AFC0REQTIMEOUTVAL_MAX 0xFFFF + +#define MPHY_TX_FSM_STATE 0x41 +#define TX_FSM_HIBERN8 0x1 +#define HBRN8_POLL_TOUT_MS 100 +#define DEFAULT_CLK_RATE_HZ 1000000 +#define BUS_VECTOR_NAME_LEN 32 + +#define UFS_HW_VER_MAJOR_SHFT 28 +#define UFS_HW_VER_MAJOR_MASK (0x000F << UFS_HW_VER_MAJOR_SHFT) +#define UFS_HW_VER_MINOR_SHFT 16 +#define UFS_HW_VER_MINOR_MASK (0x0FFF << UFS_HW_VER_MINOR_SHFT) +#define UFS_HW_VER_STEP_SHFT 0 +#define UFS_HW_VER_STEP_MASK (0xFFFF << UFS_HW_VER_STEP_SHFT) + +#define UFS_SPACEMIT_K3_LIMIT_NUM_LANES_RX 2 +#define UFS_SPACEMIT_K3_LIMIT_NUM_LANES_TX 2 +#define UFS_SPACEMIT_K3_LIMIT_HSGEAR_RX UFS_HS_G3 +#define UFS_SPACEMIT_K3_LIMIT_HSGEAR_TX UFS_HS_G3 +#define UFS_SPACEMIT_K3_LIMIT_PWMGEAR_RX UFS_PWM_G4 +#define UFS_SPACEMIT_K3_LIMIT_PWMGEAR_TX UFS_PWM_G4 +#define UFS_SPACEMIT_K3_LIMIT_RX_PWR_PWM SLOW_MODE +#define UFS_SPACEMIT_K3_LIMIT_TX_PWR_PWM SLOW_MODE +#define UFS_SPACEMIT_K3_LIMIT_RX_PWR_HS FAST_MODE +#define UFS_SPACEMIT_K3_LIMIT_TX_PWR_HS FAST_MODE +#define UFS_SPACEMIT_K3_LIMIT_HS_RATE PA_HS_MODE_B +#define UFS_SPACEMIT_K3_LIMIT_DESIRED_MODE 2 + +#define UFS_PA_VS_CONFIG_REG1 0x9000 +#define UFS_DME_VS_CORE_CLK_CTRL 0xD002 + +#define UFS_HCLKDIV_REG 0xFC + +struct ufs_spacemit_host { + struct ufs_hba *hba; + struct ufs_pa_layer_attr dev_req_params; + struct reset_control *rst; + u32 caps; + u32 lpm_qos; + u32 unipro_ver; + u32 remote_unipro_ver; + u8 prev_request_crypto; + + bool is_lane_clks_on; + bool first_init_done; + bool first_hce_done; + + /* Workqueue for deferred FSM state dump */ + struct work_struct fsm_dump_work; +}; + +#endif /* _UFS_SPACEMIT_H_ */ diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 24960ba9caa915..69b5f376889b33 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3523,6 +3523,9 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) if (PMSG_IS_AUTO(msg)) goto err_wakeup; } +#if defined(CONFIG_MACH_LOONGSON64) + usb_enable_remote_wakeup(udev->bus->root_hub); +#endif } /* disable USB2 hardware LPM */ @@ -3586,8 +3589,12 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) /* Try to enable USB2 hardware LPM again */ usb_enable_usb2_hardware_lpm(udev); - if (udev->do_remote_wakeup) + if (udev->do_remote_wakeup) { (void) usb_disable_remote_wakeup(udev); +#if defined(CONFIG_MACH_LOONGSON64) + (void) usb_disable_remote_wakeup(udev->bus->root_hub); +#endif + } err_wakeup: /* System sleep transitions should never fail */ diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 87ee2d938bc038..5e8ee248da9bdb 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -228,6 +228,9 @@ static const struct usb_device_id usb_quirk_list[] = { /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + /* Microsoft Surface Go 3 Type-Cover */ + { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c index 00443a7beaeb05..4e027e6ea5ed52 100644 --- a/drivers/usb/phy/phy-tegra-usb.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -238,6 +238,7 @@ struct tegra_xtal_freq { u8 active_delay; u8 utmi_xtal_freq_count; u16 hsic_xtal_freq_count; + u16 xtal_freq_count; u16 debounce; }; @@ -278,6 +279,14 @@ static const struct tegra_xtal_freq tegra_freq_table[] = { .hsic_xtal_freq_count = 0x3E0, .debounce = 0xFDE8, }, + { + .freq = 38400000, + .enable_delay = 0x00, + .stable_count = 0x00, + .active_delay = 0x18, + .xtal_freq_count = 0x177, + .debounce = 0xBB80, + }, }; static inline struct tegra_usb_phy *to_tegra_usb_phy(struct usb_phy *u_phy) diff --git a/include/asm-generic/rwonce.h b/include/asm-generic/rwonce.h index 52b969c7cef935..4e2d941f15a11b 100644 --- a/include/asm-generic/rwonce.h +++ b/include/asm-generic/rwonce.h @@ -50,10 +50,12 @@ __READ_ONCE(x); \ }) +#ifndef __WRITE_ONCE #define __WRITE_ONCE(x, val) \ do { \ *(volatile typeof(x) *)&(x) = (val); \ } while (0) +#endif #define WRITE_ONCE(x, val) \ do { \ diff --git a/include/drm/ttm/ttm_caching.h b/include/drm/ttm/ttm_caching.h index a18f43e93abab4..f92d7911f50e4d 100644 --- a/include/drm/ttm/ttm_caching.h +++ b/include/drm/ttm/ttm_caching.h @@ -47,7 +47,8 @@ enum ttm_caching { /** * @ttm_cached: Fully cached like normal system memory, requires that - * devices snoop the CPU cache on accesses. + * devices snoop the CPU cache on accesses. Downgraded to + * ttm_write_combined when the snooping capaiblity is missing. */ ttm_cached }; diff --git a/include/drm/ttm/ttm_device.h b/include/drm/ttm/ttm_device.h index 5618aef462f21b..5b7c6a7a0e3b34 100644 --- a/include/drm/ttm/ttm_device.h +++ b/include/drm/ttm/ttm_device.h @@ -231,6 +231,15 @@ struct ttm_device { */ const struct ttm_device_funcs *funcs; +#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \ + defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL) + /** + * @dma_coherent: if the device backed is dma-coherent. + */ + bool dma_coherent; +#endif + /** * @sysman: Resource manager for the system domain. * Access via ttm_manager_type. diff --git a/include/dt-bindings/clock/spacemit,k3-clocks.h b/include/dt-bindings/clock/spacemit,k3-clocks.h index b22336f3ae4078..4f1099f5da1c4b 100644 --- a/include/dt-bindings/clock/spacemit,k3-clocks.h +++ b/include/dt-bindings/clock/spacemit,k3-clocks.h @@ -145,6 +145,9 @@ #define CLK_MPMU_I2S3_SYSCLK 48 #define CLK_MPMU_I2S4_SYSCLK 49 #define CLK_MPMU_I2S5_SYSCLK 50 +#define CLK_MPMU_I2S_SYSCLK 51 +#define CLK_MPMU_I2S_BCLK_FACTOR 52 +#define CLK_MPMU_I2S1_SYSCLK_SRC 53 /* APBC clocks */ #define CLK_APBC_UART0 0 @@ -380,6 +383,12 @@ #define CLK_APMU_ISIM_VCLK1 86 #define CLK_APMU_ISIM_VCLK2 87 #define CLK_APMU_ISIM_VCLK3 88 +#define CLK_APMU_PCIE_PORTA_DBI 89 +#define CLK_APMU_PCIE_PORTB_DBI 90 +#define CLK_APMU_PCIE_PORTC_DBI 91 +#define CLK_APMU_PCIE_PORTD_DBI 92 +#define CLK_APMU_PCIE_PORTE_DBI 93 +#define CLK_APMU_UFS_REFCLK 94 /* DCIU clocks */ #define CLK_DCIU_HDMA 0 diff --git a/include/dt-bindings/clock/thead,th1520-clk-ap.h b/include/dt-bindings/clock/thead,th1520-clk-ap.h index 68b35cc6120413..642c2a69a57971 100644 --- a/include/dt-bindings/clock/thead,th1520-clk-ap.h +++ b/include/dt-bindings/clock/thead,th1520-clk-ap.h @@ -128,4 +128,14 @@ #define CLK_MIPIDSI1_PIXCLK 29 #define CLK_HDMI_PIXCLK 30 +/* MISC clocks */ +#define CLK_MISCSYS_ACLK 0 +#define CLK_USB 1 +#define CLK_USB_CTL_REF 2 +#define CLK_USB_PHY_REF 3 +#define CLK_USB_SUSPEND 4 +#define CLK_EMMC 5 +#define CLK_SDIO0 6 +#define CLK_SDIO1 7 + #endif diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 67effb91fa9837..613d4b49ae3834 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -810,6 +810,16 @@ int acpi_mrrm_max_mem_region(void); extern bool cmos_rtc_platform_device_present; +#ifndef ACPI_HAVE_ARCH_TABLE_OVERRIDE +static inline void acpi_arch_os_table_override (struct acpi_table_header *existing_table, struct acpi_table_header **new_table){ +} +#endif +#ifndef ACPI_HAVE_ARCH_TABLE_INIT_COMPLETE +static inline void acpi_arch_table_init_complete(void) +{ +} +#endif + #else /* !CONFIG_ACPI */ #define acpi_disabled 1 diff --git a/include/linux/audit_arch.h b/include/linux/audit_arch.h index 2b8153791e6a5d..2594c9b1c78917 100644 --- a/include/linux/audit_arch.h +++ b/include/linux/audit_arch.h @@ -17,6 +17,9 @@ enum auditsc_class_t { AUDITSC_SOCKETCALL, AUDITSC_EXECVE, AUDITSC_OPENAT2, +#ifdef CONFIG_MIPS + AUDITSC_MIPS_N32, +#endif AUDITSC_NVALS /* count */ }; diff --git a/include/linux/generic_pt/common.h b/include/linux/generic_pt/common.h index fc5d0b5edadc08..2683e5b3899874 100644 --- a/include/linux/generic_pt/common.h +++ b/include/linux/generic_pt/common.h @@ -188,6 +188,10 @@ enum { * Support the 64k contiguous page size following the Svnapot extension. */ PT_FEAT_RISCV_SVNAPOT_64K = PT_FEAT_FMT_START, + /* + * Support Svpbmt extension: encode page-based memory type (PBMT) in PTEs. + */ + PT_FEAT_RISCV_SVPBMT, }; diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h index 7807fa329db001..2d2abb25b944f4 100644 --- a/include/linux/mfd/tps68470.h +++ b/include/linux/mfd/tps68470.h @@ -34,6 +34,7 @@ #define TPS68470_REG_SGPO 0x22 #define TPS68470_REG_GPDI 0x26 #define TPS68470_REG_GPDO 0x27 +#define TPS68470_REG_ILEDCTL 0x28 #define TPS68470_REG_VCMVAL 0x3C #define TPS68470_REG_VAUX1VAL 0x3D #define TPS68470_REG_VAUX2VAL 0x3E @@ -94,4 +95,8 @@ #define TPS68470_GPIO_MODE_OUT_CMOS 2 #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 +#define TPS68470_ILEDCTL_ENA BIT(2) +#define TPS68470_ILEDCTL_ENB BIT(6) +#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) + #endif /* __LINUX_MFD_TPS68470_H */ diff --git a/include/linux/pci-acpi.h b/include/linux/pci-acpi.h index c0c54baadf04ed..99fc8051585b09 100644 --- a/include/linux/pci-acpi.h +++ b/include/linux/pci-acpi.h @@ -134,6 +134,10 @@ static inline void pci_acpi_remove_edr_notifier(struct pci_dev *pdev) { } int pci_acpi_set_companion_lookup_hook(struct acpi_device *(*func)(struct pci_dev *)); void pci_acpi_clear_companion_lookup_hook(void); +#ifndef ACPI_HAVE_ARCH_PCI_ROOT_RES_FILTER +static inline void acpi_arch_pci_probe_root_dev_filter(struct resource_entry *entry) { } +#endif + #else /* CONFIG_ACPI */ static inline void acpi_pci_add_bus(struct pci_bus *bus) { } static inline void acpi_pci_remove_bus(struct pci_bus *bus) { } diff --git a/include/linux/pci.h b/include/linux/pci.h index 2c4454583c115b..bfae14bc5e62a1 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -261,6 +261,9 @@ enum pci_dev_flags { * integrated with the downstream devices and doesn't use real PCI. */ PCI_DEV_FLAGS_PCI_BRIDGE_NO_ALIAS = (__force pci_dev_flags_t) (1 << 14), + /* Do not use native PCIe port services (equivalent to pcie_ports=compat) */ + PCI_DEV_FLAGS_NO_PORT_SERVICES = (__force pci_dev_flags_t) (1 << 15), + PCI_DEV_FLAGS_NO_LINK_SPEED_CHANGE = (__force pci_dev_flags_t) (1 << 16), }; enum pci_irq_reroute_variant { @@ -509,6 +512,8 @@ struct pci_dev { unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ unsigned int non_mappable_bars:1; /* BARs can't be mapped to user-space */ + unsigned int aspm_os_control:1; /* Display of ROM attribute enabled? */ + unsigned int no_shutdown:1; /* Do not touch device on shutdown */ pci_dev_flags_t dev_flags; atomic_t enable_cnt; /* pci_enable_device has been called */ diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 1c9d40e09107dc..ab797f414f19bd 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2632,6 +2632,8 @@ #define PCI_VENDOR_ID_CXL 0x1e98 +#define PCI_VENDOR_ID_SOPHGO 0x1f1c + #define PCI_VENDOR_ID_TEHUTI 0x1fc9 #define PCI_DEVICE_ID_TEHUTI_3009 0x3009 #define PCI_DEVICE_ID_TEHUTI_3010 0x3010 @@ -2640,6 +2642,10 @@ #define PCI_VENDOR_ID_SUNIX 0x1fd4 #define PCI_DEVICE_ID_SUNIX_1999 0x1999 +#define PCI_VENDOR_ID_SPACEMIT 0x201f +#define PCI_DEVICE_ID_SPACEMIT_K1 0x0001 +#define PCI_DEVICE_ID_SPACEMIT_K3 0x0002 + #define PCI_VENDOR_ID_HINT 0x3388 #define PCI_DEVICE_ID_HINT_VXPROII_IDE 0x8013 @@ -3267,4 +3273,6 @@ #define PCI_VENDOR_ID_NCUBE 0x10ff +#define PCI_VENDOR_ID_PHYTIUM 0x1db7 + #endif /* _LINUX_PCI_IDS_H */ diff --git a/include/soc/spacemit/k3-syscon.h b/include/soc/spacemit/k3-syscon.h index 0299bea065a05d..959d8d0786fe03 100644 --- a/include/soc/spacemit/k3-syscon.h +++ b/include/soc/spacemit/k3-syscon.h @@ -45,7 +45,8 @@ #define POSR_PLL7_LOCK BIT(30) #define POSR_PLL8_LOCK BIT(31) #define MPMU_SUCCR 0x0014 -#define MPMU_ISCCR 0x0044 +#define MPMU_ISCCR0 0x0040 +#define MPMU_ISCCR1 0x0044 #define MPMU_WDTPCR 0x0200 #define MPMU_RIPCCR 0x0210 #define MPMU_ACGR 0x1024 @@ -168,8 +169,8 @@ #define APMU_CPU_C2_CLK_CTRL 0x394 #define APMU_CPU_C3_CLK_CTRL 0x208 #define APMU_PCIE_CLK_RES_CTRL_A 0x1f0 -#define APMU_PCIE_CLK_RES_CTRL_B 0x1c8 -#define APMU_PCIE_CLK_RES_CTRL_C 0x1d0 +#define APMU_PCIE_CLK_RES_CTRL_B 0x1d0 +#define APMU_PCIE_CLK_RES_CTRL_C 0x1c8 #define APMU_PCIE_CLK_RES_CTRL_D 0x1e0 #define APMU_PCIE_CLK_RES_CTRL_E 0x1e8 #define APMU_EMAC0_CLK_RES_CTRL 0x3e4 diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 6a42812bba8cad..df010a91b35059 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -17,6 +17,7 @@ struct snd_pcm_substream; struct snd_soc_dapm_widget; struct snd_compr_stream; +struct clk; /* * DAI hardware audio formats. @@ -188,6 +189,8 @@ int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int snd_soc_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio); +void snd_soc_dai_set_bclk_clk(struct snd_soc_dai *dai, struct clk *bclk); + /* Digital Audio interface formatting */ int snd_soc_dai_get_fmt_max_priority(const struct snd_soc_pcm_runtime *rtd); u64 snd_soc_dai_get_fmt(const struct snd_soc_dai *dai, int priority); @@ -473,6 +476,10 @@ struct snd_soc_dai { unsigned int symmetric_channels; unsigned int symmetric_sample_bits; + /* shared BCLK clock for cross-DAI rate constraints */ + struct clk *bclk; + unsigned int bclk_ratio; /* BCLK = rate * bclk_ratio (0 = use channels * sample_bits) */ + /* parent platform/codec */ struct snd_soc_component *component; diff --git a/include/uapi/drm/xe_drm.h b/include/uapi/drm/xe_drm.h index ae2fda23ce7c7c..642caac28544ae 100644 --- a/include/uapi/drm/xe_drm.h +++ b/include/uapi/drm/xe_drm.h @@ -413,7 +413,7 @@ struct drm_xe_query_mem_regions { * if a queue can be creaed with * %DRM_XE_EXEC_QUEUE_SET_DISABLE_STATE_CACHE_PERF_FIX * - %DRM_XE_QUERY_CONFIG_MIN_ALIGNMENT - Minimal memory alignment - * required by this device, typically SZ_4K or SZ_64K + * required by this device, typically PAGE_SIZE. * - %DRM_XE_QUERY_CONFIG_VA_BITS - Maximum bits of a virtual address * - %DRM_XE_QUERY_CONFIG_MAX_EXEC_QUEUE_PRIORITY - Value of the highest * available exec queue priority diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h index e8f5ce677df734..c2e65021467fd2 100644 --- a/include/uapi/linux/audit.h +++ b/include/uapi/linux/audit.h @@ -193,7 +193,11 @@ * AUDIT_LIST commands must be implemented. */ #define AUDIT_MAX_FIELDS 64 #define AUDIT_MAX_KEY_LEN 256 +#ifdef __mips__ +#define AUDIT_BITMASK_SIZE 256 +#else #define AUDIT_BITMASK_SIZE 64 +#endif #define AUDIT_WORD(nr) ((__u32)((nr)/32)) #define AUDIT_BIT(nr) (1U << ((nr) - AUDIT_WORD(nr)*32)) @@ -209,6 +213,12 @@ #define AUDIT_CLASS_SIGNAL 8 #define AUDIT_CLASS_SIGNAL_32 9 +#define AUDIT_CLASS_DIR_WRITE_N32 10 +#define AUDIT_CLASS_CHATTR_N32 11 +#define AUDIT_CLASS_READ_N32 12 +#define AUDIT_CLASS_WRITE_N32 13 +#define AUDIT_CLASS_SIGNAL_N32 14 + /* This bitmask is used to validate user input. It represents all bits that * are currently used in an audit field constant understood by the kernel. * If you are adding a new #define AUDIT_, please ensure that diff --git a/init/Kconfig b/init/Kconfig index 826a7d768ca368..32c84c02c88d3a 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1012,6 +1012,7 @@ config NUMA_BALANCING depends on ARCH_SUPPORTS_NUMA_BALANCING depends on !ARCH_WANT_NUMA_VARIABLE_LOCALITY depends on SMP && NUMA_MIGRATION && !PREEMPT_RT + depends on TRANSPARENT_HUGEPAGE || !MIPS help This option adds support for automatic NUMA aware memory/task placement. The mechanism is quite primitive and is based on migrating memory when diff --git a/kernel/auditsc.c b/kernel/auditsc.c index abdf8da3be9340..680b1db10aedb5 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c @@ -189,6 +189,19 @@ static int audit_match_perm(struct audit_context *ctx, int mask) return mask & AUDIT_PERM_EXEC; case AUDITSC_OPENAT2: return mask & ACC_MODE((u32)ctx->openat2.flags); +#ifdef CONFIG_MIPS + case AUDITSC_MIPS_N32: /* for N32 */ + if ((mask & AUDIT_PERM_WRITE) && + audit_match_class(AUDIT_CLASS_WRITE_N32, n)) + return 1; + if ((mask & AUDIT_PERM_READ) && + audit_match_class(AUDIT_CLASS_READ_N32, n)) + return 1; + if ((mask & AUDIT_PERM_ATTR) && + audit_match_class(AUDIT_CLASS_CHATTR_N32, n)) + return 1; + return 0; +#endif default: return 0; } diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c index 55df6d37145e87..06496a92efe4f9 100644 --- a/kernel/rcu/tree.c +++ b/kernel/rcu/tree.c @@ -4452,15 +4452,11 @@ void rcutree_migrate_callbacks(int cpu) struct rcu_data *rdp = per_cpu_ptr(&rcu_data, cpu); bool needwake; - if (rcu_rdp_is_offloaded(rdp)) - return; - - raw_spin_lock_irqsave(&rcu_state.barrier_lock, flags); - if (rcu_segcblist_empty(&rdp->cblist)) { - raw_spin_unlock_irqrestore(&rcu_state.barrier_lock, flags); + if (rcu_rdp_is_offloaded(rdp) || + rcu_segcblist_empty(&rdp->cblist)) return; /* No callbacks to migrate. */ - } + raw_spin_lock_irqsave(&rcu_state.barrier_lock, flags); WARN_ON_ONCE(rcu_rdp_cpu_online(rdp)); rcu_barrier_entrain(rdp); my_rdp = this_cpu_ptr(&rcu_data); diff --git a/lib/audit.c b/lib/audit.c index bc07fbd3a6983f..7063bd38f2c635 100644 --- a/lib/audit.c +++ b/lib/audit.c @@ -29,6 +29,7 @@ static unsigned signal_class[] = { ~0U }; +#if !defined(CONFIG_MIPS) int audit_classify_arch(int arch) { if (audit_is_compat(arch)) @@ -68,6 +69,7 @@ int audit_classify_syscall(int abi, unsigned syscall) return AUDITSC_NATIVE; } } +#endif static int __init audit_classes_init(void) { diff --git a/lib/tests/string_kunit.c b/lib/tests/string_kunit.c index 0819ace5b02757..8e2e8980aa75a4 100644 --- a/lib/tests/string_kunit.c +++ b/lib/tests/string_kunit.c @@ -155,6 +155,16 @@ static void string_test_strnlen(struct kunit *test) for (size_t offset = 0; offset < STRING_TEST_MAX_OFFSET; offset++) { for (size_t len = 0; len <= STRING_TEST_MAX_LEN; len++) { + /* Test strings without NUL terminator */ + s = buf + buf_size - offset - len; + if (len > 0) + KUNIT_EXPECT_EQ(test, strnlen(s, len - 1), len - 1); + if (len > 1) + KUNIT_EXPECT_EQ(test, strnlen(s, len - 2), len - 2); + + KUNIT_EXPECT_EQ(test, strnlen(s, len), len); + + /* Test strings with NUL terminator */ s = buf + buf_size - 1 - offset - len; s[len] = '\0'; @@ -169,6 +179,9 @@ static void string_test_strnlen(struct kunit *test) KUNIT_EXPECT_EQ(test, strnlen(s, len + 2), len); KUNIT_EXPECT_EQ(test, strnlen(s, len + 10), len); + /* Test Count overflow fallback */ + KUNIT_EXPECT_EQ(test, strnlen(s, SSIZE_MAX), len); + s[len] = 'A'; } } diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c index 6eadb9d116e433..b86064d3dd0745 100644 --- a/mm/sparse-vmemmap.c +++ b/mm/sparse-vmemmap.c @@ -564,6 +564,8 @@ struct page * __meminit __populate_section_memmap(unsigned long pfn, if (r < 0) return NULL; + flush_cache_vmap(start, end); + return pfn_to_page(pfn); } diff --git a/rust/Makefile b/rust/Makefile index b9e9f512cec314..cbf8289d7de0d9 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -396,17 +396,8 @@ bindgen_skip_c_flags := -mno-fp-ret-in-387 -mpreferred-stack-boundary=% \ -fstrict-flex-arrays=% -fmin-function-alignment=% \ -fzero-init-padding-bits=% -mno-fdpic \ -fdiagnostics-show-context -fdiagnostics-show-context=% \ - --param=% --param asan-% -fno-isolate-erroneous-paths-dereference - -# Derived from `scripts/Makefile.clang`. -BINDGEN_TARGET_x86 := x86_64-linux-gnu -BINDGEN_TARGET_arm64 := aarch64-linux-gnu -BINDGEN_TARGET_arm := arm-linux-gnueabi -BINDGEN_TARGET_loongarch := loongarch64-linux-gnusf -# This is only for i386 UM builds, which need the 32-bit target not -m32 -BINDGEN_TARGET_i386 := i386-linux-gnu -BINDGEN_TARGET_um := $(BINDGEN_TARGET_$(SUBARCH)) -BINDGEN_TARGET := $(BINDGEN_TARGET_$(SRCARCH)) + --param=% --param asan-% -fno-isolate-erroneous-paths-dereference \ + -mno-riscv-attribute -fpatchable-function-entry=% # All warnings are inhibited since GCC builds are very experimental, # many GCC warnings are not supported by Clang, they may only appear in diff --git a/scripts/Kconfig.include b/scripts/Kconfig.include index fc10671c297cf3..d381cf4b40e608 100644 --- a/scripts/Kconfig.include +++ b/scripts/Kconfig.include @@ -76,3 +76,8 @@ rustc-llvm-version := $(shell,$(srctree)/scripts/rustc-llvm-version.sh $(RUSTC)) # If you are testing for unstable features, consider testing RUSTC_VERSION # instead, as features may have different completeness while available. rustc-option = $(success,trap "rm -rf .tmp_$$" EXIT; mkdir .tmp_$$; $(RUSTC) $(1) --crate-type=rlib /dev/null --out-dir=.tmp_$$ -o .tmp_$$/tmp.rlib) + +# $(bindgen-backend-option,) +# Return y if bindgen backend supports , n otherwise +# For now, the backend refers only to libclang, so more specifically, this function tests whether the given flag is recognized by the libclang used by bindgen. +bindgen-backend-option = $(success,trap "rm -f .tmp_$$.h" EXIT; touch .tmp_$$.h; $(BINDGEN) .tmp_$$.h -- --target=$(BINDGEN_TARGET) $(1)) diff --git a/scripts/Makefile.dtbs b/scripts/Makefile.dtbs index c4e4663902844d..8c03bd8a9bc43c 100644 --- a/scripts/Makefile.dtbs +++ b/scripts/Makefile.dtbs @@ -111,6 +111,11 @@ else DTC_FLAGS += -Wunique_unit_address_if_enabled endif +ifeq ($(CONFIG_OF_OVERLAY),y) +# enable creation of __symbols__ node +DTC_FLAGS += -@ +endif + ifneq ($(findstring 2,$(KBUILD_EXTRA_WARN)),) DTC_FLAGS += -Wnode_name_chars_strict \ -Wproperty_name_chars_strict \ diff --git a/scripts/Makefile.rust b/scripts/Makefile.rust new file mode 100644 index 00000000000000..ae51e68dcd59fd --- /dev/null +++ b/scripts/Makefile.rust @@ -0,0 +1,13 @@ +# Derived from `scripts/Makefile.clang`. +BINDGEN_TARGET_x86 := x86_64-linux-gnu +BINDGEN_TARGET_arm64 := aarch64-linux-gnu +BINDGEN_TARGET_arm := arm-linux-gnueabi +BINDGEN_TARGET_loongarch := loongarch64-linux-gnusf +BINDGEN_TARGET_powerpc := powerpc64le-linux-gnu +# This is only for i386 UM builds, which need the 32-bit target not -m32 +BINDGEN_TARGET_i386 := i386-linux-gnu +BINDGEN_TARGET_um := $(BINDGEN_TARGET_$(SUBARCH)) +BINDGEN_TARGET_riscv := riscv64-linux-gnu +BINDGEN_TARGET := $(BINDGEN_TARGET_$(SRCARCH)) + +export BINDGEN_TARGET diff --git a/scripts/generate_rust_target.rs b/scripts/generate_rust_target.rs index 16f7e855e012a4..d9bc275e44ed84 100644 --- a/scripts/generate_rust_target.rs +++ b/scripts/generate_rust_target.rs @@ -260,6 +260,30 @@ fn main() { } } else if cfg.has("LOONGARCH") { panic!("loongarch uses the builtin rustc loongarch64-unknown-none-softfloat target"); + } else if cfg.has("PPC64") { + ts.push("arch", "powerpc64"); + ts.push( + "data-layout", + if cfg.has("CPU_LITTLE_ENDIAN") { + "e-m:e-Fn32-i64:64-n32:64-S128-v256:256:256-v512:512:512" + } else { + "E-m:e-Fn32-i64:64-n32:64-S128-v256:256:256-v512:512:512" + }, + ); + ts.push("features", "-mma,-altivec,-vsx,-hard-float"); + ts.push( + "llvm-target", + if cfg.has("CPU_LITTLE_ENDIAN") { + "powerpc64le-linux-gnu" + } else { + "powerpc64-linux-gnu" + }, + ); + if cfg.rustc_version_atleast(1, 91, 0) { + ts.push("target-pointer-width", 64); + } else { + ts.push("target-pointer-width", "64"); + } } else { panic!("Unsupported architecture"); } diff --git a/scripts/package/install-extmod-build b/scripts/package/install-extmod-build index f12e1ffe409eb0..4430379b991106 100755 --- a/scripts/package/install-extmod-build +++ b/scripts/package/install-extmod-build @@ -11,7 +11,8 @@ is_enabled() { find_in_scripts() { find scripts \ - \( -name atomic -o -name dtc -o -name kconfig -o -name package \) -prune -o \ + \( -name atomic -o -name kconfig -o -name package -o \ + \( -path '*/dtc/*' -a ! -path '*/libfdt*' \) \) -prune -o \ ! -name unifdef -a ! -name mk_elfconfig -a \( -type f -o -type l \) -print } diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index f7701b8d0d3c7e..0708c2fe8f6102 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3793,6 +3793,15 @@ static const struct dmi_system_id dmi_platform_data[] = { }, .driver_data = (void *)&intel_braswell_platform_data, }, + { + .ident = "Microsoft Surface 3", + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), + DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), + DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, { /* * Match for the GPDwin which unfortunately uses somewhat diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c index e4c3492a0c2824..0b930c91bccb33 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c @@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), }, }, + { + .callback = cht_surface_quirk_cb, + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), + DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), + DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), + }, + }, { } }; diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 2f370fda12665d..1719ddcefa4b03 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -116,10 +116,28 @@ int snd_soc_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) dai->driver->ops->set_bclk_ratio) ret = dai->driver->ops->set_bclk_ratio(dai, ratio); + if (!ret) + dai->bclk_ratio = ratio; + return soc_dai_ret(dai, ret); } EXPORT_SYMBOL_GPL(snd_soc_dai_set_bclk_ratio); +/** + * snd_soc_dai_set_bclk_clk - set the BCLK clock for shared clock detection + * @dai: DAI + * @bclk: BCLK clock pointer (or NULL to clear) + * + * When multiple DAIs share the same physical BCLK (detected via + * clk_is_match()), the ASoC core will automatically constrain their + * hw_params so that the resulting BCLK rates are compatible. + */ +void snd_soc_dai_set_bclk_clk(struct snd_soc_dai *dai, struct clk *bclk) +{ + dai->bclk = bclk; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_bclk_clk); + int snd_soc_dai_get_fmt_max_priority(const struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *dai; diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 9b12eedb77c331..0e49290a8c903b 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -26,6 +27,9 @@ #include #include + +DEFINE_GUARD(snd_soc_card_mutex, struct snd_soc_card *, + snd_soc_card_mutex_lock(_T), snd_soc_card_mutex_unlock(_T)) #define soc_pcm_ret(rtd, ret) _soc_pcm_ret(rtd, __func__, ret) static inline int _soc_pcm_ret(struct snd_soc_pcm_runtime *rtd, const char *func, int ret) @@ -467,6 +471,114 @@ static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream, return 0; } +/* + * Shared BCLK constraint: when multiple DAIs share the same physical BCLK, + * constrain hw_params so that the BCLK rate (rate * channels * sample_bits, + * or rate * slots * slot_width for TDM) remains consistent. + */ + +static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_soc_dai *dai = rule->private; + struct snd_soc_card *card = dai->component->card; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *other_dai; + unsigned long active_bclk_rate = 0; + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval constraint = { .empty = 1 }; + unsigned int target_rate; + int i; + + /* Protect the rtd list traversal with the ASoC card mutex helper. */ + guard(snd_soc_card_mutex)(card); + + /* Scan all DAIs on the card for an active peer sharing the same BCLK */ + for_each_card_rtds(card, rtd) { + for_each_rtd_cpu_dais(rtd, i, other_dai) { + if (other_dai == dai) + continue; + if (!other_dai->bclk) + continue; + if (!snd_soc_dai_active(other_dai)) + continue; + /* + * Skip peers whose hw_params hasn't run yet. + * symmetric_rate is set by soc_pcm_set_dai_params() + * after snd_soc_dai_hw_params(), so non-zero means + * the DAI's clk_set_rate() has already executed. + */ + if (!other_dai->symmetric_rate) + continue; + if (!clk_is_match(dai->bclk, other_dai->bclk)) + continue; + + active_bclk_rate = clk_get_rate(other_dai->bclk); + if (active_bclk_rate) + goto found; + } + } + + return 0; + +found: + if (dai->bclk_ratio) { + /* + * Driver has set an explicit BCLK ratio (e.g. for TDM where + * BCLK = rate * slots * slot_width). The only valid rate is + * active_bclk_rate / bclk_ratio. + */ + target_rate = active_bclk_rate / dai->bclk_ratio; + + constraint.min = target_rate; + constraint.max = target_rate; + } else { + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *sample_bits = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS); + + /* + * Default: BCLK = rate * channels * sample_bits. + * Calculate the range of valid rates given the current + * channel and sample_bits intervals. + */ + if (!channels->min || !sample_bits->min) + return 0; + + constraint.max = active_bclk_rate / + ((unsigned long)channels->min * sample_bits->min); + + if (channels->max && sample_bits->max) + constraint.min = active_bclk_rate / + ((unsigned long)channels->max * sample_bits->max); + else + constraint.min = constraint.max; + } + + constraint.integer = 1; + constraint.empty = 0; + + return snd_interval_refine(rate, &constraint); +} + +static int soc_pcm_apply_shared_bclk(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (!dai->bclk) + return 0; + + dev_dbg(dai->dev, + "ASoC: registering shared BCLK rate constraint\n"); + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + soc_pcm_shared_bclk_rule_rate, dai, + SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + -1); +} + static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -903,6 +1015,13 @@ static int __soc_pcm_open(struct snd_soc_pcm_runtime *rtd, if (ret != 0) goto err; } + + /* Shared BCLK constraint across DAIs on the same card */ + for_each_rtd_cpu_dais(rtd, i, dai) { + ret = soc_pcm_apply_shared_bclk(substream, dai); + if (ret != 0) + goto err; + } dynamic: snd_soc_runtime_activate(rtd, substream->stream); ret = 0; diff --git a/sound/soc/spacemit/k1_i2s.c b/sound/soc/spacemit/k1_i2s.c index 5420ca2aefbd87..d35b57566829a0 100644 --- a/sound/soc/spacemit/k1_i2s.c +++ b/sound/soc/spacemit/k1_i2s.c @@ -53,6 +53,9 @@ struct spacemit_i2s_dev { struct clk *sysclk; struct clk *bclk; struct clk *sspa_clk; + struct clk *sysclk_div; + struct clk *c_sysclk; + struct clk *c_bclk; struct snd_dmaengine_dai_dma_data capture_dma_data; struct snd_dmaengine_dai_dma_data playback_dma_data; @@ -206,6 +209,14 @@ static int spacemit_i2s_hw_params(struct snd_pcm_substream *substream, params_rate(params) * data_bits; + ret = clk_set_rate(i2s->c_sysclk, bclk_rate * 2); + if (ret) + return ret; + + ret = clk_set_rate(i2s->c_bclk, bclk_rate); + if (ret) + return ret; + ret = clk_set_rate(i2s->bclk, bclk_rate); if (ret) return ret; @@ -217,10 +228,17 @@ static int spacemit_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { struct spacemit_i2s_dev *i2s = dev_get_drvdata(cpu_dai->dev); + int ret; if (freq == 0) return 0; + if (i2s->sysclk_div) { + ret = clk_set_rate(i2s->sysclk_div, freq); + if (ret) + return ret; + } + return clk_set_rate(i2s->sysclk, freq); } @@ -305,6 +323,8 @@ static int spacemit_i2s_dai_probe(struct snd_soc_dai *dai) spacemit_i2s_init(i2s); + snd_soc_dai_set_bclk_clk(dai, i2s->c_bclk); + return 0; } @@ -436,6 +456,21 @@ static int spacemit_i2s_probe(struct platform_device *pdev) return dev_err_probe(i2s->dev, PTR_ERR(i2s->sspa_clk), "failed to enable sspa clock\n"); + i2s->sysclk_div = devm_clk_get_optional_enabled(i2s->dev, "sysclk_div"); + if (IS_ERR(i2s->sysclk_div)) + return dev_err_probe(i2s->dev, PTR_ERR(i2s->sysclk_div), + "failed to enable sysclk_div clock\n"); + + i2s->c_sysclk = devm_clk_get_optional_enabled(i2s->dev, "c_sysclk"); + if (IS_ERR(i2s->c_sysclk)) + return dev_err_probe(i2s->dev, PTR_ERR(i2s->c_sysclk), + "failed to enable c_sysclk clock\n"); + + i2s->c_bclk = devm_clk_get_optional_enabled(i2s->dev, "c_bclk"); + if (IS_ERR(i2s->c_bclk)) + return dev_err_probe(i2s->dev, PTR_ERR(i2s->c_bclk), + "failed to enable c_bclk clock\n"); + i2s->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(i2s->base)) return dev_err_probe(i2s->dev, PTR_ERR(i2s->base), "failed to map registers\n"); @@ -462,6 +497,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) static const struct of_device_id spacemit_i2s_of_match[] = { { .compatible = "spacemit,k1-i2s", }, + { .compatible = "spacemit,k3-i2s", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, spacemit_i2s_of_match); @@ -476,4 +512,4 @@ static struct platform_driver spacemit_i2s_driver = { module_platform_driver(spacemit_i2s_driver); MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("I2S bus driver for SpacemiT K1 SoC"); +MODULE_DESCRIPTION("I2S bus driver for SpacemiT K1/K3 SoC"); diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile index 0febf60e1b6465..f7b90e64ce64dc 100644 --- a/tools/bpf/bpftool/Makefile +++ b/tools/bpf/bpftool/Makefile @@ -297,8 +297,8 @@ clean: $(LIBBPF)-clean $(LIBBPF_BOOTSTRAP)-clean feature-detect-clean install-bin: $(OUTPUT)bpftool $(call QUIET_INSTALL, bpftool) - $(Q)$(INSTALL) -m 0755 -d $(DESTDIR)$(prefix)/sbin - $(Q)$(INSTALL) $(OUTPUT)bpftool $(DESTDIR)$(prefix)/sbin/bpftool + $(Q)$(INSTALL) -m 0755 -d $(DESTDIR)$(prefix)/bin + $(Q)$(INSTALL) $(OUTPUT)bpftool $(DESTDIR)$(prefix)/bin/bpftool install: install-bin $(Q)$(INSTALL) -m 0755 -d $(DESTDIR)$(bash_compdir) @@ -306,7 +306,7 @@ install: install-bin uninstall: $(call QUIET_UNINST, bpftool) - $(Q)$(RM) -- $(DESTDIR)$(prefix)/sbin/bpftool + $(Q)$(RM) -- $(DESTDIR)$(prefix)/bin/bpftool $(Q)$(RM) -- $(DESTDIR)$(bash_compdir)/bpftool doc: diff --git a/tools/perf/arch/riscv/util/header.c b/tools/perf/arch/riscv/util/header.c index 4b839203d4a54d..891984e909bd8f 100644 --- a/tools/perf/arch/riscv/util/header.c +++ b/tools/perf/arch/riscv/util/header.c @@ -19,7 +19,7 @@ static char *_get_field(const char *line) { - char *line2, *nl; + const char *line2, *nl; line2 = strrchr(line, ' '); if (!line2) diff --git a/tools/testing/selftests/rseq/Makefile b/tools/testing/selftests/rseq/Makefile index 50d69e22ee7a67..aba6317f6cb8e5 100644 --- a/tools/testing/selftests/rseq/Makefile +++ b/tools/testing/selftests/rseq/Makefile @@ -5,9 +5,13 @@ CLANG_FLAGS += -no-integrated-as endif top_srcdir = ../../../.. +include $(top_srcdir)/scripts/subarch.include +ARCH ?= $(SUBARCH) +LINUX_TOOL_ARCH_INCLUDE = $(top_srcdir)/tools/arch/$(ARCH)/include CFLAGS += -O2 -Wall -g -I./ $(KHDR_INCLUDES) -L$(OUTPUT) -Wl,-rpath=./ \ - $(CLANG_FLAGS) -I$(top_srcdir)/tools/include + $(CLANG_FLAGS) -I$(top_srcdir)/tools/include \ + -I$(LINUX_TOOL_ARCH_INCLUDE) LDLIBS += -lpthread -ldl # Own dependencies because we only want to build against 1st prerequisite, but diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 881f92d7a469e7..50511430f0f854 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -5568,7 +5568,7 @@ static struct miscdevice kvm_dev = { }; #ifdef CONFIG_KVM_GENERIC_HARDWARE_ENABLING -bool __ro_after_init enable_virt_at_load = true; +bool __ro_after_init enable_virt_at_load = false; module_param(enable_virt_at_load, bool, 0444); EXPORT_SYMBOL_FOR_KVM_INTERNAL(enable_virt_at_load);