diff --git a/Documentation/arch/loongarch/irq-chip-model.rst b/Documentation/arch/loongarch/irq-chip-model.rst index 7988f41923639dd3e2fc93e1709b2a5f4abe3405..6dd48256e39f74ca4f36638ce8c865da6f9c3849 100644 --- a/Documentation/arch/loongarch/irq-chip-model.rst +++ b/Documentation/arch/loongarch/irq-chip-model.rst @@ -85,6 +85,38 @@ to CPUINTC directly:: | Devices | +---------+ +Advanced Extended IRQ model +=========================== + +In this model, IPI (Inter-Processor Interrupt) and CPU Local Timer interrupt go +to CPUINTC directly, CPU UARTS interrupts go to LIOINTC, PCH-MSI interrupts go +to AVECINTC, and then go to CPUINTC directly, while all other devices interrupts +go to PCH-PIC/PCH-LPC and gathered by EIOINTC, and then go to CPUINTC directly:: + + +-----+ +-----------------------+ +-------+ + | IPI | --> | CPUINTC | <-- | Timer | + +-----+ +-----------------------+ +-------+ + ^ ^ ^ + | | | + +---------+ +----------+ +---------+ +-------+ + | EIOINTC | | AVECINTC | | LIOINTC | <-- | UARTs | + +---------+ +----------+ +---------+ +-------+ + ^ ^ + | | + +---------+ +---------+ + | PCH-PIC | | PCH-MSI | + +---------+ +---------+ + ^ ^ ^ + | | | + +---------+ +---------+ +---------+ + | Devices | | PCH-LPC | | Devices | + +---------+ +---------+ +---------+ + ^ + | + +---------+ + | Devices | + +---------+ + ACPI-related definitions ======================== diff --git a/Documentation/devicetree/bindings/dma/phytium-ddma.yaml b/Documentation/devicetree/bindings/dma/phytium-ddma.yaml new file mode 100644 index 0000000000000000000000000000000000000000..098dbd867a1994c08c23e1ccf89e19ef01dc18d6 --- /dev/null +++ b/Documentation/devicetree/bindings/dma/phytium-ddma.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +title: Phytium DDMA Controller bindings + +description: +The Phytium DDMA is a general-purpose direct memory access +controller capable of supporting 8 independent DMA channels. +Each channel can have up to 32 requests.DMA clients connected +to the Phytium DDMA controller must use the format described +in the dma.txt file, using a two-cell specifier for each +channel: + a phandle to the DMA controller plus the following two integer cells: + 1. The channel id + 2. The request line number + +maintainers: + - Huang Jie + +allOf: + - $ref: "dma-controller.yaml#" + +properties: + "#dma-cells": + const: 2 + + compatible: + const: phytium,ddma + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + dma-channels: + minItems: 1 + maxItems: 8 + description: it indicates that the number of channels are used + +required: + - compatible + - reg + - interrupts + - dma-channels + +unevaluatedProperties: false + +examples: + ddma0: ddma@28003000 { + compatible = "phytium,ddma"; + reg = <0x0 0x28003000 0x0 0x1000>; + interrupts = ; + #dma-cells = <2>; + dma-channels = <8>; + }; +... diff --git a/Documentation/devicetree/bindings/edac/phytium,pe220x-edac.yaml b/Documentation/devicetree/bindings/edac/phytium,pe220x-edac.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8bb94a303b9759a7a2aa97dcc69eaf93dc83d107 --- /dev/null +++ b/Documentation/devicetree/bindings/edac/phytium,pe220x-edac.yaml @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/edac/phytium,pe220x-edac.yaml +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium Pe220x SoC EDAC node +maintainers: + - Zhu Honglei + +description: | + EDAC node is defined to describe on-chip error detection and correction. + +properties: + compatible: + const: phytium,pe220x-edac + + reg: + maxItems: 3 + + interrupts: + minItems: 2 + items: + - description: Interrupt-specifier for RAS error IRQ(s). + +required: + - compatible: Shall be "phytium,pe220x-edac". + - reg: Shall be the Pe220x RAS resource. + - interrupts: Interrupt-specifier for RAS error IRQ(s). + +examples: + - | + edac: edac@32b28000 { + compatible = "phytium,pe220x-edac"; + reg = <0x0 0x32b28000 0x0 0x1000>, + <0x0 0x31400000 0x0 0x1000>, + <0x0 0x31401000 0x0 0x1000>; + interrupts = , + , + }; diff --git a/Documentation/devicetree/bindings/gpio/phytium,gpio.yaml b/Documentation/devicetree/bindings/gpio/phytium,gpio.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4a2d586d1880393340279bbb23bd4e491e87d975 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/phytium,gpio.yaml @@ -0,0 +1,114 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/phytium,gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium GPIO controller + +description: | + Phytium GPIO controllers have one or two configurable ports, each of which + are intended to be represented as child nodes with the generic GPIO-controller + properties as desribed in this bindings file. + +maintainers: + - Chen Baozi + +properties: + $nodename: + pattern: "^gpio@[0-9a-f]+$" + + compatible: + const: phytium,gpio + + reg: + maxItems: 1 + + gpio-controller: true + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + '#gpio-cells': + const: 2 + + interrupts: + description: | + The interrupts to the parent controller raised when GPIOs generate + the interrupts. If the controller provides one combined interrupt + for all GPIOs, specify a single interrupt. If the controller provides + one interrupt for each GPIO, provide a list of interrupts that + correspond to each of the GPIO pins. + minItems: 1 + maxItems: 32 + + interrupt-controller: true + + '#interrupt-cells': + const: 2 + +patternProperties: + "^gpio-(port|controller)@[0-9a-f]+$": + type: object + properties: + compatible: + const: phytium,gpio-port + + reg: + maxItems: 1 + + nr-gpios: + $ref: /schemas/types.yaml#/definitions/uint32 + description: The number of GPIO pins exported by the port. + default: 32 + minimum: 1 + maximum: 32 + + required: + - compatible + - reg + - gpio-controller + - '#gpio-cells' + + dependencies: + interrupt-controller: [ interrupts ] + + additionalProperties: false + +additionalProperties: false + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + +examples: + - | + gpio: gpio@28004000 { + compatible = "phytium,gpio"; + reg = <0x0 0x28004000 0x0 0x1000>; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; + interrupt-controller; + #interrupt-cells = <2>; + + porta: gpio-port@0 { + compatible = "phytium,gpio-port"; + reg = <0>; + nr-gpios = <8>; + }; + + portb: gpio-port@1 { + compatible = "phytium,gpio-port"; + reg = <1>; + nr-gpios = <8>; + }; +}; +... diff --git a/Documentation/devicetree/bindings/gpio/phytium,sgpio.yaml b/Documentation/devicetree/bindings/gpio/phytium,sgpio.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fed5c2b6346831b60c19065383c49bb6617749d0 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/phytium,sgpio.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/phytium,sgpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium SGPIO controller + +description: | + This SGPIO controller is for Phytium Pe220x SoCs, which supports up to + 96 (32x3) Serial GPIOs. + +maintainers: + - Chen Baozi + +properties: + compatible: + const: phytium,sgpio + + reg: + maxItems: 1 + description: Address and length of the register set for the device. + + gpio-controller: true + + '#gpio-cells': + const: 2 + description: | + The first cell is the pin number and the second cell is used to specify + the gpio polarity. + 0 = active high + 1 = active low + + interrupts: + maxItems: 1 + + ngpios: true + + bus-frequency: true + + clocks: + maxItems: 1 + +additionalProperties: false + +required: + - compatible + - reg + - gpio-controller + - '#gpio-cells' + - interrupts + - ngpios + - clocks + - bus-frequency + +examples: + - | + sgpio: sgpio@2807d000 { + compatible = "phytium,sgpio"; + reg = <0x0 0x2807d000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_48mhz>; + ngpios = <96>; + bus-frequency = <48000>; + gpio-controller; + #gpio-cells = <2>; + }; diff --git a/Documentation/devicetree/bindings/gpu/phytium,dc.yaml b/Documentation/devicetree/bindings/gpu/phytium,dc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5be348f6e23f7b37d345e1fe568a33d8e1239d7f --- /dev/null +++ b/Documentation/devicetree/bindings/gpu/phytium,dc.yaml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dc/snps,dc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium Display Controller + +maintainers: + - Chen Baozi + +allOf: + - $ref: /schemas/dc/display-controller.yaml# + +properties: + compatible: + const: phytium,dc + + reg: + minItems: 1 + items: + - description: Offset and length of the memory mapped registers + + interrupts: + maxItems: 1 + + clocks: + minItems: 1 + items: + - description:Display controller reference clock source + +unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + +Example: + /memreserve/ 0xf4000000 0x4000000; // (optional) + + dc0@32000000 { + compatible = "phytium,dc"; + reg = <0x0 0x32000000 0x0 0x8000>, + <0x0 0xf4000000 0x0 0x4000000>; // (optional) + interrupts = ; + pipe_mask = 0x3 + edp_mask = 0x0; + }; diff --git a/Documentation/devicetree/bindings/hwlock/phytium,hwspinlock.yaml b/Documentation/devicetree/bindings/hwlock/phytium,hwspinlock.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d9567c14d0a4247d4537534fac45927cd73c955b --- /dev/null +++ b/Documentation/devicetree/bindings/hwlock/phytium,hwspinlock.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: (GPL-2.0-only or BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwlock/phytium,hwspinlock.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium HwSpinlock Driver + +maintainers: + - Chen Baozi + +properties: + compatible: + const: phytium,hwspinlock + + reg: + maxItems: 1 + description: Contains the hwspinlock module register address space. + + "#hwlock-cells": + const: 1 + description: | + The Phytium hwspinlock users will use a 0-indexed relative hwlock number as + the argument specifier value for requesting a specific hwspinlock within + a hwspinlock bank. + + Please look at the generic hwlock binding for usage information for + consumers, "Documentation/devicetree/bindings/hwlock/hwlock.txt" + + nr-locks: + $ref: /schemas/types.yaml#/definitions/uint32 + description: The number of locks in the device. + +required: + - compatible + - reg + - "#hwlock-cells" + - nr-locks + +additionalProperties: false + +examples: + + - | + hwspinlock: spinlock@40000000 { + compatible = "phytium,hwspinlock"; + reg = <0x40000000 0x1000>; + #hwlock-cells = <1>; + nr-locks = <32>; + }; diff --git a/Documentation/devicetree/bindings/hwmon/phytium,tacho.yaml b/Documentation/devicetree/bindings/hwmon/phytium,tacho.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d2443e023a6bbb21b2c3476e220380a4ec7a2359 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/phytium,tacho.yaml @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/phytium,tacho.yaml +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium Fan Tacho and capture counter controller device driver + +maintainers: + - Chen Baozi + +description: | + The controller can support one input signal. The function of controller is to + measure the speed of fan and the edge number of input signal. The function + can be selected by devicetree setting. The edging mode and anti-jitter level + can also setted in devicetree. + +properties: + compatible: + const: phytium,tacho + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 1 + + tacho: + $ref: /schemas/types.yaml#/definitions/flag + description: + set the controller work as fan tachometer, which is a default option. + + capture: + $ref: /schemas/types.yaml#/definitions/flag + description: + set the controller work as capture counter. + + up: + $ref: /schemas/types.yaml#/definitions/flag + description: + set the input edging mode as ascending, which is a default option. + + down: + $ref: /schemas/types.yaml#/definitions/flag + description: + set the input edging mode as descending. + + double: + $ref: /schemas/types.yaml#/definitions/flag + description: + set the input edging mode as doule-edging. + + debounce-level: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3] + +required: + - compatible + - reg + - clocks + - '#address-cells' + - '#size-cells' + +examples: + - | + tacho: tacho@28054000 { + #address-cells = <1>; + #size-cells = <1>; + reg = <0x0 0x28054000 0x0 0x1000>; + compatible = "phytium,tacho"; + clocks = <&sysclk>; + tacho; + up; + debounce-level = <2>; + }; diff --git a/Documentation/devicetree/bindings/i2c/phytium,i2c.yaml b/Documentation/devicetree/bindings/i2c/phytium,i2c.yaml new file mode 100644 index 0000000000000000000000000000000000000000..992a4d7cbb47e211d2213c58da860ebd08bc0f7e --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/phytium,i2c.yaml @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/snps,designware-i2c.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium I2C/SMBus Controller + +maintainers: + - Chen Baozi + +allOf: + - $ref: /schemas/i2c/i2c-controller.yaml# + +properties: + compatible: + const: phytium,i2c + + reg: + minItems: 1 + items: + - description: Offset and length of the memory mapped registers + + interrupts: + maxItems: 1 + + interrupt-names: + const: smbus_alert + description: should be "smbus_alert" if SMBus alert interrupt is supported + + clocks: + minItems: 1 + items: + - description: I2C controller reference clock source + +unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + +examples: + - | + i2c0: i2c@28011000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28011000 0x0 0x1000>; + interrupts = ; + interrupt-names = "smbus_alert"; + clocks = <&sysclk_48mhz>; + }; diff --git a/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml index cc40d25358ecfb6e7d95cc6983575f42dfe57263..ebc572300e3b7a85292a773534e713075c874421 100644 --- a/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml +++ b/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml @@ -14,7 +14,9 @@ allOf: properties: compatible: - const: cdns,i3c-master + enum: + - cdns,i3c-master + - phytium,cdns-i3c-master reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/i3c/phytium,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/phytium,i3c-master.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0e004735fd38d3988248f832b52dd08651516765 --- /dev/null +++ b/Documentation/devicetree/bindings/i3c/phytium,i3c-master.yaml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause + +* Phytium I3C controller + +This I3C controller is for Phytium Soc. + +Required properties: +- compatible: Shall be "phytium,cdns-i3c-master" +- clocks: Shall reference the pclk and sysclk +- clock-names: Shall contain "pclk" and "sysclk" +- interrupts: The interrupt line connected to this I3C master +- reg: I3C master registers +- #address-cells: Shall be set to 1 +- #size-cells: Shall be set to 0 +- i2c-scl-hz: I2C CLK frequency +- i3c-scl-hz: I3C CLK frequency + +Example: + + i3c-master@28045000 { + compatible = "phytium,cdns-i3c-master"; + reg = <0x0 0x28045000 0x0 0x1000>; + interrupts = ; + clocks = <&coreclock>, <&i3csysclock>; + clock-names = "pclk", "sysclk"; + #address-cells = <1>; + #size-cells = <0>; + i2c-scl-hz = <400000>; + i3c-scl-hz = <1000000>; + + nunchuk: nunchuk@52 { + compatible = "nintendo,nunchuk"; + reg = <0x52 0x0 0x10>; + }; + }; diff --git a/Documentation/devicetree/bindings/iio/adc/phytium,adc.yaml b/Documentation/devicetree/bindings/iio/adc/phytium,adc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cfbf6554a105950f253d3b9ee7eaa4f2ea834ab1 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/phytium,adc.yaml @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright 2019 Analog Devices Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/phytium,adi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium ADC + +maintainers: + - Chen Baozi + +description: | + This device is a 10-bit converter for 8 voltage channels. All inputs are + single ended. + +properties: + compatible: + const: phytium,adc + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + description: Input clock used to derive the sample clock. + + interrupts: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + +required: + - compatible + - reg + - clocks + - interrupts + - '#address-cells' + - '#size-cells' + +examples: + - | + adc0: adc@2807b000 { + compatible = "phytium,adc"; + reg = <0x0 0x2807b000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_48mhz>; + + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + }; + channel@1 { + reg = <1>; + }; + channel@2 { + reg = <2>; + }; + channel@3 { + reg = <3>; + }; + channel@4 { + reg = <4>; + }; + channel@5 { + reg = <5>; + }; + channel@6 { + reg = <5>; + }; + channel@7 { + reg = <7>; + }; + }; diff --git a/Documentation/devicetree/bindings/input/phytium,keypad.yaml b/Documentation/devicetree/bindings/input/phytium,keypad.yaml new file mode 100644 index 0000000000000000000000000000000000000000..122fad370973f48e430e74d1a72118f8d3d9fc5e --- /dev/null +++ b/Documentation/devicetree/bindings/input/phytium,keypad.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/phytium,keypad.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium Keypad Port + +maintainers: + - Chen Baozi + +allOf: + - $ref: "/schemas/input/matrix-keymap.yaml#" + +description: | + The keypad port is designed to interface with a keypad matrix, which + simplify the software task of scanning a keypad matrix. It is capable + of detecting, debouncing, and decoding one or multiple keys pressed + simultaneously on a keypad. + + +properties: + compatible: + - const: phytium,keypad + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - linux,keymap + +unevaluatedProperties: false + +examples: + - | + keypad: keypad@2807a000 { + compatible = "phytium,keypad"; + reg = <0x 0x2807a000 0x0 0x1000>; + interrupts = ; + keypad,num-rows = <4>; + keypad,num-columns = <4>; + linux,keymap = <0x00000067 /* KEY_UP */ + 0x0001006c /* KEY_DOWN */ + 0x00020072 /* KEY_VOLUMEDOWN */ + 0x00030066 /* KEY_HOME */ + 0x0100006a /* KEY_RIGHT */ + 0x01010069 /* KEY_LEFT */ + 0x0102001c /* KEY_ENTER */ + 0x01030073 /* KEY_VOLUMEUP */ + 0x02000040 /* KEY_F6 */ + 0x02010042 /* KEY_F8 */ + 0x02020043 /* KEY_F9 */ + 0x02030044 /* KEY_F10 */ + 0x0300003b /* KEY_F1 */ + 0x0301003c /* KEY_F2 */ + 0x0302003d /* KEY_F3 */ + 0x03030074>; /* KEY_POWER */ + }; diff --git a/Documentation/devicetree/bindings/interrupt-controller/phytium,ixic.yaml b/Documentation/devicetree/bindings/interrupt-controller/phytium,ixic.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ba7fb2749af4fceeeb37bdeba4b1122331fb0a67 --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/phytium,ixic.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interrupt-controller/phytium,ixic.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium INTx interrupt controller (IXIC) + +description: | + This is a pseudo interrupt controller to handle PCI legacy interrupt on + some Phytium SoCs, which sits between the PCI INTx devices and the GIC + and forwards the 4 INTx input signals to 4 adjacent GICv3 SPIs. + + +maintainers: + - Chen Baozi + +allOf: + - $ref: /schemas/interrupt-controller.yaml# + +properties: + compatible: + const: phytium,ixic + + reg: + description: | + Specifies two regions of the register set, which are called + 'ctr' and 'hpb' + minItems: 2 + maxItems: 2 + + interrupt-controller: true + + '#interrupt-cells': + description: | + Specifies the number of cells needed to encode an interrupt source. + const: 3 + + intx-spi-base: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + The SPI number of the first SPI of the 4 adjacent ones the IXIC + forwards its interrupts to. + +required: + - compatible + - reg + - interrupt-controller + - '#interrupt-cells' + - intx-spi-base + +additionalProperties: false + +examples: + - | + ixic: interrupt-controller@29000000 { + compatible = "phytium,ixic"; + reg-names = "ctr", "hpb"; + reg = <0x0 0x29000000 0x0 0x00060000>, + <0x0 0x29100000 0x0 0x00002000>; + interrupt-controller; + interrupt-parent = <&gic>; + #interrupt-cells = <3>; + intx-spi-base = <28>; + }; diff --git a/Documentation/devicetree/bindings/ipmi/phytium,bt-bmc.yaml b/Documentation/devicetree/bindings/ipmi/phytium,bt-bmc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8b62bb0eddfada810c40367d16283874de2ca2a3 --- /dev/null +++ b/Documentation/devicetree/bindings/ipmi/phytium,bt-bmc.yaml @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/ipmi/phytium,bt-bmc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium BT (Block Transfer) IPMI interface + +maintainers: + - Chen Baozi + +description: | + The Phytium E-series SOCs can be used in BMC which may have a BT + interface used to perform in-band IPMI communication with their host. + +properties: + compatible: + const: phytium,bt-bmc + + interrupts: + maxItems: 1 + + reg: + maxItems: 1 + +required: + - compatible + - interrupts + - reg + +additionalProperties: false + +examples: + - | + bt: bt@250 { + compatible = "phytium,bt-bmc"; + reg = <0x250 0x1c>; + interrupts = ; + }; diff --git a/Documentation/devicetree/bindings/ipmi/phytium,kcs-bmc.yaml b/Documentation/devicetree/bindings/ipmi/phytium,kcs-bmc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..335b1886ad80ea43f002959bec2c6c3a43036c4c --- /dev/null +++ b/Documentation/devicetree/bindings/ipmi/phytium,kcs-bmc.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/ipmi/phytium,kcs-bmc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium KCS (Keyboard Controller Style) IPMI interface + +maintainers: + - Chen Baozi + +description: | + The Phytium E-series SOC can be used in BMC which have the KCS interface to + perform in-band IPMI communication with their host. + +properties: + compatible: + const: phytium,kcs-bmc + + interrupts: + maxItems: 1 + + reg: + # maxItems: 3 + items: + - description: IDR register + - description: ODR register + - description: STR register + + kcs_chan: + deprecated: true + $ref: '/schemas/types.yaml#/definitions/uint32' + description: The LPC channel number in the controller + + kcs_addr: + deprecated: true + $ref: '/schemas/types.yaml#/definitions/uint32' + description: The host CPU IO map address + +required: + - compatible + - reg + - interrupts + - kcs_chan + - kcs_addr + +additionalProperties: false + +examples: + - | + kcs0: kcs@24 { + compatible = "phytium,kcs-bmc"; + reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>; + interrupts = ; + kcs_chan = <1>; + kcs_addr = <0xca0>; + }; diff --git a/Documentation/devicetree/bindings/leds/phytnet_led.yaml b/Documentation/devicetree/bindings/leds/phytnet_led.yaml new file mode 100644 index 0000000000000000000000000000000000000000..24efebc9ba30d25b7ff6b95e3636ce54a0f7a7a0 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/phytnet_led.yaml @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/phytnet_led.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium mac led controller + +maintainers: + - LongShixiang + +description: + This modules is used to control mac led. + +properties: + compatible: + const: phytium,net_led + net_dev: + maxItems: 1 + description: Phandler of specified Net device + led-gpios: + minItems: 1 + maxItems: 2 + description: |- + the gpios used for led control based on net_dev condition. + One represents LINK condition, another represents ACT condition. + +required: + - compatible + - net_dev + - led-gpios + +examples: + - | + gpiochip0: gpop_controller{ + ... + } + eth0: ethernet{ + ... + } + phytium_net_led0 { + compatible = "phytium,net_led"; + net_dev = <ð1>; + led-gpios = <&gpiochip0 9 GPIO_ACTIVE_HIGH>, /* link */ + <&gpiochip0 11 GPIO_ACTIVE_HIGH>; /* act */ + }; diff --git a/Documentation/devicetree/bindings/mailbox/phytium,mbox.yaml b/Documentation/devicetree/bindings/mailbox/phytium,mbox.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d875b08b55320146ae7d08caf55702b79bdcdbe8 --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/phytium,mbox.yaml @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mailbox/phytium,mbox.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium Mailbox Driver + +maintainers: + - Chen Baozi + +description: | + The Phytium mailbox controller that has a channel/link to communicate + with the remote end. A link raises interrupt for any received data. However, + there is no specified way of knowing if the sent data has been read by the + remote. This driver assumes the sender polls STAT register and the remote + clears it after having read the data. + +properties: + compatible: + const: phytium,mbox + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + '#mbox-cells': + const: 1 + description: the index of the channel needed + +required: + - compatible + - reg + - interrupts + - '#mbox-cells' + +additionalProperties: false + +examples: + - | + mbox: mailbox@2a000000 { + compatible = "phytium,mbox"; + reg = <0x0 0x2a000000 0x0 0x1000>; + #mbox-cells = <1>; + interrupts = <0 48 4>; + }; diff --git a/Documentation/devicetree/bindings/media/phytium,jpeg.yaml b/Documentation/devicetree/bindings/media/phytium,jpeg.yaml new file mode 100644 index 0000000000000000000000000000000000000000..71fd143130c941c7459764721d3ed742bff03f87 --- /dev/null +++ b/Documentation/devicetree/bindings/media/phytium,jpeg.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/phytium,jpeg.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium JPEG Engine + +maintainers: + - Chen Baozi + +description: |- + The JPEG Engine embedded in the Phytium SOCs can capture + and compress video data from digital or analog sources. + +properties: + compatible: + const: phytium,jpeg + + reg: + maxItems: 1 + description: | + Contains the offset and length of the JPEG Engine memory region. + + interrupts: + maxItems: 1 + description: | + The interrupt associated with the VE on this platform. + + phytium,ocm-buf-addr: + $ref: /schemas/types.yaml#/definitions/uint64 + description: | + The physical address used to storage the inputing video data. + + +required: + - compatible + - reg + - interrupts + - phytium,ocm-buf-addr + +additionalProperties: false + +examples: + - | + jpeg: jpeg@32b32000 { + compatible = "phytium,jpeg"; + reg = <0x0 0x32b32000 0 0x1000>; + interrupts = <0 41 4>; + phytium,ocm-buf-addr = <0x30c40000 0x30c60000>; + status = "okay"; + }; diff --git a/Documentation/devicetree/bindings/misc/phytium,lpc-snoop.yaml b/Documentation/devicetree/bindings/misc/phytium,lpc-snoop.yaml new file mode 100644 index 0000000000000000000000000000000000000000..642026be79c33ac72fde02c1c532e9b549973b27 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/phytium,lpc-snoop.yaml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/misc/phytium,lpc-snoop.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium lpc-snoop + +maintainers: + - Lan Hengyu + +description: + The LPC snoop interface allows the BMC to listen on and record the data + bytes written by the Host to the targeted LPC I/O pots. + +properties: + compatible: + items: + - enum: + - phytium,lpc-snoop + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + snoop-ports: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: The LPC I/O ports to snoop + +required: + - compatible + - interrupts + - snoop-ports + +examples: + - | + lpc: lpc@28010000 { + compatible = "simple-mfd", "syscon"; + reg = <0x0 0x28010000 0x1000>; + reg-io-width = <4>; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x0 0x28010000 0x1000>; + + lpc_snoop: lpc-snoop@90 { + compatible = "phytium,lpc-snoop"; + reg = <0x90 0x8>; + interrupts = ; + snoop-ports = <0x80>; + }; + }; diff --git a/Documentation/devicetree/bindings/mmc/phytium,mci.yaml b/Documentation/devicetree/bindings/mmc/phytium,mci.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a0748c70af37b0d69d3ac7b582ca69d6ec232963 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/phytium,mci.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/phytium,mci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium Multimedia Card Interface controller + +description: | + The highspeed MMC host controller on Phytium SoCs provides an interface + for MMC, SD and SDIO types of memory cards. + +maintainers: + - Chen Baozi + +allOf: + - $ref: "mmc-controller.yaml" + +properties: + compatible: + const: phytium,mci + + reg: + maxItems: 1 + description: mmc controller base registers. + + interrupts: + maxItems: 1 + description: mmc controller interrupt. + + clocks: + maxItems: 1 + description: phandles to input clocks. + + clock-names: + items: + - const: phytium_mci_clk + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +examples: + - | + mmc0: mmc@28000000 { + compatible = "phytium,mci"; + reg = <0x0 0x28000000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_1200mhz>; + clock-names = "phytium_mci_clk"; + status = "disabled"; + }; + + &mmc0 { + bus-width = <4>; + max-frequency = <50000000>; + cap-sdio-irq; + cap-sd-highspeed; + sd-uhs-sdr12; + sd-uhs-sdr25; + sd-uhs-sdr50; + no-mmc; + status = "ok"; + }; diff --git a/Documentation/devicetree/bindings/mmc/phytium,sdci.yaml b/Documentation/devicetree/bindings/mmc/phytium,sdci.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9c56e5fe01870379d967958e498da36a1743940c --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/phytium,sdci.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/phytium,sdci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium SDCI Controller Binding + +maintainers: + - Chen Baozi + +allOf: + - $ref: mmc-controller.yaml# + +properties: + compatible: + enum: + - phytium,sdci + + reg: + maxItems: 1 + + interrupts: + minItems: 3 + maxItems: 3 + + clocks: + minItems: 1 + items: + - description: core clock + + clock-names: + minItems: 1 + items: + - const: phytium_sdc_clk + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +unevaluatedProperties: false + +examples: + - | + sdci: sdci@28207c00 { + compatible = "phytium,sdci"; + reg = <0x0 0x28207c00 0x0 0x100>; + interrupts = , + , + ; + clocks = <&sysclk_600mhz>; + clock-names = "phytium_sdc_clk"; + }; + +... diff --git a/Documentation/devicetree/bindings/mtd/phytium,nfc.yaml b/Documentation/devicetree/bindings/mtd/phytium,nfc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4d3bac96d5846c5b2a7febdecc3feea0b8f6bad2 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/phytium,nfc.yaml @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mtd/phytium,nfc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium Nand Flash controller + +maintainers: + - Chen Baozi + +properties: + compatible: + const: phytium,nfc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + nand-ecc-strength: + const: 8 + + nand-ecc-step-size: + const: 512 + +allOf: + - $ref: "nand-controller.yaml#" + +required: + - compatible + - reg + - interrupts + +examples: + - | + nand0: nand@28002000 { + compatible = "phytium,nfc"; + reg = <0x0 0x28002000 0x0 0x1000>; + interrupts = ; + }; diff --git a/Documentation/devicetree/bindings/net/can/phytium,can.yaml b/Documentation/devicetree/bindings/net/can/phytium,can.yaml new file mode 100644 index 0000000000000000000000000000000000000000..dd4b8000830ca3fb5432e303a82b3ff25e45cc7e --- /dev/null +++ b/Documentation/devicetree/bindings/net/can/phytium,can.yaml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/can/phytium,can.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: + Phytium CAN controller + +maintainers: + - Chen Baozi + +properties: + compatible: + enum: + - phytium,can + - phytium,canfd + + reg: + maxItems: 1 + description: Should contain the controller registers location and length + + interrupts: + maxItems: 1 + description: Should contain IRQ line for the controller + + clocks: + maxItems: 1 + description: Input clock used by the controller + + clock-names: + maxItems: 1 + description: Input clock name, should be "can_clk" + const: can_clk + + tx-fifo-depth: + $ref: "/schemas/types.yaml#/definitions/uint32" + description: Indicates the length of TX FIFO + + rx-fifo-depth: + $ref: "/schemas/types.yaml#/definitions/uint32" + description: Indicates the length of RX FIFO + + extend_brp: + description: | + Indicates to apply the extend BRP parameter of bit timming for + early version of CAN controller + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - tx-fifo-depth + - rx-fifo-depth + +allOf: + - $ref: can-controller.yaml# + +examples: + - | + + can0: can@2800a000 { + compatible = "phytium,canfd"; + reg = <0x0 0x2800a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_600mhz>; + clock-names = "can_clk"; + tx-fifo-depth = <64>; + rx-fifo-depth = <64>; + }; diff --git a/Documentation/devicetree/bindings/net/cdns,macb.yaml b/Documentation/devicetree/bindings/net/cdns,macb.yaml index bf8894a0257e9d051b605c3b015f453f5280f37b..5cbe36040a8a9e6ef7a51d31ccecb94b9113c34c 100644 --- a/Documentation/devicetree/bindings/net/cdns,macb.yaml +++ b/Documentation/devicetree/bindings/net/cdns,macb.yaml @@ -58,6 +58,8 @@ properties: - cdns,emac # Generic - cdns,gem # Generic - cdns,macb # Generic + - cdns,phytium-gem-1.0 # GEM version 1.0 on Phytium SoCs + - cdns,phytium-gem-2.0 # GEM version 2.0 on Phytium SoCs reg: minItems: 1 diff --git a/Documentation/devicetree/bindings/net/phytmac.yaml b/Documentation/devicetree/bindings/net/phytmac.yaml new file mode 100644 index 0000000000000000000000000000000000000000..33947bac5225d853ac57f65f98e2eea6322790f4 --- /dev/null +++ b/Documentation/devicetree/bindings/net/phytmac.yaml @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +* Phytium xgmac Ethernet controller + +Required properties: +- compatible: Should be "phytium,gmac-[version]" + Use "phytium,gmac-1.0" for gmac version 1.0 on Phytium SoCs + Use "phytium,gmac-2.0" for gmac version 2.0 on Phytium SoCs + +- reg: Address and length of the register set for the device +- interrupts: Should contain phytmac interrupt +- queue-number: The number of queues for the device +- phy-mode: See ethernet.txt file in the same directory +- fixed-link:See ethernet.txt file in the same directory +- dma-coherent: Boolean property, must only be present if memory + accesses performed by the device are cache coherent. + +The MAC address will be determined using the optional properties +defined in ethernet.txt. + +Examples: + + eth0@36ce0000 { + compatible = "phytium,gmac-1.0"; + reg = <0x00 0x36ce0000 0x00 0x2000>; + interrupts = <0x00 0x20 0x04 0x00 0x21 0x04 0x00 0x22 0x04 0x00 0x23 0x04>; + queue-number = <0x04>; + magic-packet; + dma-coherent; + phy-mode = "usxgmii"; + status = "okay"; + + fixed-link { + speed = <0x2710>; + full-duplex; + }; + }; diff --git a/Documentation/devicetree/bindings/pwm/phytium,pwm.yaml b/Documentation/devicetree/bindings/pwm/phytium,pwm.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b142aa6aecf44a819b7cb2072faf523f1eb61147 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/phytium,pwm.yaml @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/phytium,pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium PWM controller + +maintainers: + - Chen Baozi + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + const: phytium,pwm + + reg: + maxItems: 1 + + clocks: + description: Clock specifiers for both ipg and per clocks. + + interrupts: + maxItems: 1 + + phytium,db: + description: | + One or two to describe dead-band configurations. + "cntmod" indicates the counter mode (0 for modulo, 1 for up-and-down). + "dutymod" indicdates which duty to compare with (0 for PMW_CCR, 1 for FIFO). + "div" selects the clock divider value, from 0 to 1023. + "updbcly" selects the rising edge delay cycles. + "dbpolarity" selects the polarity for dead-band. + $ref: /schemas/types.yaml#/definitions/uint32-array + uniqueItems: true + items: + minimum: 1 + maximum: 2 + + +required: + - compatible + - reg + - clocks + - interrupts + - phytium,db + +additionalProperties: false + +examples: + - | + pwm0: pwm@2804a000 { + compatible = "phytium,pwm"; + reg= <0x0 0x2804a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_48mhz>; + phytium,db = <0 0 0 1000 0>; + }; diff --git a/Documentation/devicetree/bindings/rng/phytium,rng.yaml b/Documentation/devicetree/bindings/rng/phytium,rng.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e32fc39ea86c0cc2f5e6df3aa0a1d9bbb673ddde --- /dev/null +++ b/Documentation/devicetree/bindings/rng/phytium,rng.yaml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rng/phytium,rng.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium Random Number Generator + +maintainers: + - Chen Baozi + +properties: + compatible: + const: phytium,rng + + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + rng@0x31a06000 { + compatible = "phytium,rng"; + reg = <0x0 0x31a06000 0x0 0x1000>; + }; diff --git a/Documentation/devicetree/bindings/security/loongson_se/se.yaml b/Documentation/devicetree/bindings/security/loongson_se/se.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c79313e8e3cc4dca937e0af64d84ba0dc0be35d0 --- /dev/null +++ b/Documentation/devicetree/bindings/security/loongson_se/se.yaml @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/security/loongson_se/se.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Loongson SE module + +description: + Loongson SE module +maintainers: + - Qunqin Zhao + +allOf: + - $ref: se.yaml# + +properties: + compatible: + const: loongson,ls3c6000se + + reg: + maxItems: 2 + + interrupts: + maxItems: 2 + +required: + - compatible + - reg + - interrupts + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/sound/phytium,hda.yaml b/Documentation/devicetree/bindings/sound/phytium,hda.yaml new file mode 100644 index 0000000000000000000000000000000000000000..eb6394c523ec774430a405e10fd8e34ad815a2c4 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/phytium,hda.yaml @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/phytium,hda.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium HDA controller + +description: | + The High Definition Audio (HDA) block provides a serial interface to + audio codec. It supports multiple input and output streams. + +maintainers: + - Chen Baozi + +properties: + compatible: + - const: phytium,hda + + reg: + maxItems: 1 + + interrupts: + description: The interrupt from the HDA controller + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: phytium_hda_clk + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + hda: hda@28206000 { + compatible = "phytium,hda"; + reg = <0 0x28206000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_48mhz>; + clock-names = "phytium_hda_clk"; + }; diff --git a/Documentation/devicetree/bindings/sound/phytium,i2s.yaml b/Documentation/devicetree/bindings/sound/phytium,i2s.yaml new file mode 100644 index 0000000000000000000000000000000000000000..06e5613cdbcfe5d3a352eb5538372a9f02032039 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/phytium,i2s.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/phytium,i2s.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium I2S controller + +maintainers: + - Chen Baozi + +properties: + compatible: + const: phytium,i2s + + reg: + maxItems: 2 + description: | + It contains two register region. The first one is for physical base + address and length of I2S controller. The second one is for physical + base address and length of DMA_BDL controller. + + interrupts: + description: | + The interrupt line number for the I2S controller. It should contain + the DMA_BDL interrupt. + maxItems: 1 + + clocks: + description: Sampling rate reference clock + maxItems: 1 + + dai-name: + $ref: /schemas/types.yaml#/definitions/string + description: it will set dai's name used in driver. + +required: + - compatible + - reg + - interrupts + - clocks + - dai-name + +examples: + - | + i2s@28009000 { + compatible = "phytium,i2s"; + reg = <0x0 0x28009000 0x0 0x1000>, <0x0 0x28005000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_600mhz>; + dai-name = "phytium-i2s-lsd"; + }; diff --git a/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml b/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8ff781a1453135223723f9967dfacff48981d5ad --- /dev/null +++ b/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/phytium,qspi-nor.yaml# +$schema: http://devicetree.org/schemas/meta-schemas/core.yaml# + +title: Phytium Quad Serial Peripheral Interface (QSPI) bindings + +maintainers: + - Chen Baozi + +allOf: + - $ref: "spi-controller.yaml#" + +properties: + compatible: + const: phytium,qspi-nor + + reg: + items: + - description: registers + - description: memory mapping region + + reg-names: + items: + - const: qspi + - const: qspi_mm + + clocks: + maxItems: 1 + + no-direct-mapping: + $ref: /schemas/types.yaml#/definitions/flag + description: + Indicates if we can use direct mapping to access the flash + +required: + - compatible + - reg + - reg-names + - clocks + +unevaluateProperties: false + +examples: + - | + qspi: qspi@28014000 { + compatible = "phytium,qspi-nor"; + reg = <0x0 0x28014000 0x0 0x1000>, + <0x0 0x0 0x0 0x02000000>; + reg-names = "qspi", "qspi_mm"; + clocks = <&sysclk_600mhz>; + + #address-cells = <1>; + #size-cells = <0>; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-rx-bus-width = <4>; + spi-max-frequency = <600000000>; + }; + + flash@1 { + compatible = "jedec,spi-nor"; + reg = <1>; + spi-rx-bus-width = <4>; + spi-max-frequency = <600000000>; + }; + }; diff --git a/Documentation/devicetree/bindings/spi/phytium,spi.yaml b/Documentation/devicetree/bindings/spi/phytium,spi.yaml new file mode 100644 index 0000000000000000000000000000000000000000..983c592bae44493ba1f6b9dad1c0dd93aa59f855 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/phytium,spi.yaml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/phytium,spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium SPI controller + +maintainers: + - Chen Baozi + +allOf: + - $ref: spi-controller.yaml# + +properties: + compatible: + const: phytium,spi + + reg: + minItems: 1 + description: address and length of the spi master registers + + interrupts: + maxItems: 1 + description: should contain one interrupt + + clocks: + maxItems: 1 + description: spi clock phandle + +required: + - compatible + - "#address-cells" + - "#size-cells" + - reg + - interrupts + - clocks + - num-cs + +examples: + - | + + spi0: spi@2800c000 { + compatible = "phytium,spi"; + interrupts = ; + reg = <0x0 0x2800c000 0x0 0x1000>; + clocks = <&sysclk_48mhz>; + num-cs = <4>; + }; diff --git a/Documentation/devicetree/bindings/usb/generic-xhci.yaml b/Documentation/devicetree/bindings/usb/generic-xhci.yaml index 594ebb3ee432037f98adaa3e7cfcdaa4fae81ff2..ef24fa3bf977df9c55f577f07c880fccd3b152f0 100644 --- a/Documentation/devicetree/bindings/usb/generic-xhci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-xhci.yaml @@ -29,6 +29,11 @@ properties: enum: - brcm,xhci-brcm-v2 - brcm,bcm7445-xhci + - description: Phytium Pe220x SoC with xHCI + items: + - enum: + - phytium,pe220x-xhci + - const: generic-xhci - description: Generic xHCI device const: xhci-platform deprecated: true diff --git a/Documentation/devicetree/bindings/usb/phytium,usb2.yaml b/Documentation/devicetree/bindings/usb/phytium,usb2.yaml new file mode 100644 index 0000000000000000000000000000000000000000..caf762039f04dc578790dd7e7d68223b363c1f9e --- /dev/null +++ b/Documentation/devicetree/bindings/usb/phytium,usb2.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/phytium,usb2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium USBHS-DRD controller bindings + +maintainers: + - Chen Baozi + +properties: + compatible: + const: phytium,usb2 + + reg: + items: + - description: USB controller registers + - description: PHY registers + + interrupts: + maxItems: 1 + + dr_mode: + enum: [host, otg, peripheral] + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + usb2_0: usb2@31800000 { + compatible = "phytium,usb2"; + reg = <0x0 0x31800000 0x0 0x80000>, + <0x0 0x31990000 0x0 0x10000>; + interrupts = ; + }; diff --git a/Documentation/devicetree/bindings/w1/phytium,w1.yaml b/Documentation/devicetree/bindings/w1/phytium,w1.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ea4b266cd2924b3aed3188eb918744626e3808be --- /dev/null +++ b/Documentation/devicetree/bindings/w1/phytium,w1.yaml @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/w1/phytium,w1.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium 1-wire bus master controller + +maintainers: + - Chen Baozi + +properties: + compatible: + const: phytium,w1 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +examples: + - | + onewire0: onewire@2803f000 { + compatible = "phytium,w1"; + reg = <0x0 0x2803f000 0x0 0x1000>; + interrupts = ; + }; diff --git a/Documentation/translations/zh_CN/arch/loongarch/irq-chip-model.rst b/Documentation/translations/zh_CN/arch/loongarch/irq-chip-model.rst index f1e9ab18206c33c5d888ed6ff8fad5bb1b845533..472761938682c0703cf18b862f82a701cd1beb3a 100644 --- a/Documentation/translations/zh_CN/arch/loongarch/irq-chip-model.rst +++ b/Documentation/translations/zh_CN/arch/loongarch/irq-chip-model.rst @@ -87,6 +87,38 @@ PCH-LPC/PCH-MSI,然后被EIOINTC统一收集,再直接到达CPUINTC:: | Devices | +---------+ +高级扩展IRQ模型 +=============== + +在这种模型里面,IPI(Inter-Processor Interrupt)和CPU本地时钟中断直接发送到CPUINTC, +CPU串口(UARTs)中断发送到LIOINTC,PCH-MSI中断发送到AVECINTC,而后通过AVECINTC直接 +送达CPUINTC,而其他所有设备的中断则分别发送到所连接的PCH-PIC/PCH-LPC,然后由EIOINTC +统一收集,再直接到达CPUINTC:: + + +-----+ +-----------------------+ +-------+ + | IPI | --> | CPUINTC | <-- | Timer | + +-----+ +-----------------------+ +-------+ + ^ ^ ^ + | | | + +---------+ +----------+ +---------+ +-------+ + | EIOINTC | | AVECINTC | | LIOINTC | <-- | UARTs | + +---------+ +----------+ +---------+ +-------+ + ^ ^ + | | + +---------+ +---------+ + | PCH-PIC | | PCH-MSI | + +---------+ +---------+ + ^ ^ ^ + | | | + +---------+ +---------+ +---------+ + | Devices | | PCH-LPC | | Devices | + +---------+ +---------+ +---------+ + ^ + | + +---------+ + | Devices | + +---------+ + ACPI相关的定义 ============== diff --git a/MAINTAINERS b/MAINTAINERS index 0aa0a23ec904cef05dd512072b676bc02bb4c6e4..2c57d8f66ab6513fd14ab77fcfc248494a36590b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7046,6 +7046,7 @@ L: dri-devel@lists.freedesktop.org S: Supported T: git git://anongit.freedesktop.org/drm/drm-misc F: drivers/gpu/drm/loongson/ +F: drivers/gpu/drm/loongson/ast_old/ DRM DRIVERS FOR MEDIATEK M: Chun-Kuang Hu @@ -12436,6 +12437,7 @@ F: Documentation/arch/loongarch/ F: Documentation/translations/zh_CN/arch/loongarch/ F: arch/loongarch/ F: drivers/*/*loongarch* +F: drivers/*/*loongson* LOONGSON GPIO DRIVER M: Yinbo Zhu @@ -12491,6 +12493,14 @@ S: Maintained F: Documentation/devicetree/bindings/pinctrl/loongson,ls2k-pinctrl.yaml F: drivers/pinctrl/pinctrl-loongson2.c +LOONGSON SE DRIVER +M: Qunqin Zhao +S: Maintained +F: Documentation/devicetree/bindings/security/loongson_se/se.yaml +F: arch/loongarch/include/asm/se.h +F: drivers/char/loongson_se.c +F: drivers/char/lsse_sdf_cdev.c + LOONGSON-2 SOC SERIES THERMAL DRIVER M: zhanghongchen M: Yinbo Zhu @@ -17518,7 +17528,9 @@ ARM/PHYTIUM SOC SUPPORT M: Wang Yinfeng S: Maintained W: https://gerrit.b.cpu.ac/c/linux +F: Documentation/devicetree/bindings/gpio/phytium,gpio.yaml F: arch/arm64/boot/dts/phytium/* +F: drivers/gpio/gpio-phytium* QAT DRIVER M: Giovanni Cabiddu diff --git a/Makefile b/Makefile index b718cfcad597b282aaf5250bf6e8fad9fddd819e..e7f7ba9538d817fce622ce728bc731e1a66ba40c 100644 --- a/Makefile +++ b/Makefile @@ -423,6 +423,11 @@ ifeq ($(ARCH),parisc64) SRCARCH := parisc endif +# Additional ARCH settings for loongarch +ifeq ($(ARCH),loong64) + SRCARCH := loongarch +endif + export cross_compiling := ifneq ($(SRCARCH),$(SUBARCH)) cross_compiling := 1 diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 9040497a8f70c1c09a17e724342d72ab9b1072f3..f65126acf56c05af8325ef2d1535156e85a96a3e 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -246,6 +246,7 @@ config ARCH_NPCM config ARCH_PHYTIUM bool "Phytium SoC Family" + select ARCH_MIGHT_HAVE_PC_SERIO select ARM_GIC_PHYTIUM_2500 help This enables support for Phytium ARMv8 SoC family, including: diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 60af93c04b45a7397573af970c302826af850eda..4809043132f6ea2d7be446c51fef7f7d73fd45f7 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -25,6 +25,7 @@ CONFIG_CGROUP_DEVICE=y CONFIG_CGROUP_CPUACCT=y CONFIG_CGROUP_PERF=y CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y CONFIG_USER_NS=y CONFIG_SCHED_AUTOGROUP=y CONFIG_BLK_DEV_INITRD=y diff --git a/arch/arm64/configs/openkylin_generic_defconfig b/arch/arm64/configs/openkylin_generic_defconfig index 445375e2b02d3747b44d46f307270bded4b05f48..d4d04063f02bdfc3a6975db30f5afc374ae41f3f 100644 --- a/arch/arm64/configs/openkylin_generic_defconfig +++ b/arch/arm64/configs/openkylin_generic_defconfig @@ -41,6 +41,7 @@ CONFIG_CGROUP_CPUACCT=y CONFIG_CGROUP_PERF=y CONFIG_CGROUP_BPF=y CONFIG_CGROUP_MISC=y +CONFIG_NAMESPACES=y CONFIG_USER_NS=y CONFIG_CHECKPOINT_RESTORE=y CONFIG_EXPERT=y @@ -852,6 +853,11 @@ CONFIG_NFP=m # CONFIG_NET_VENDOR_NVIDIA is not set # CONFIG_NET_VENDOR_PACKET_ENGINES is not set CONFIG_IONIC=m +CONFIG_PHYTMAC=m +CONFIG_PHYTMAC_ENABLE_PTP=y +CONFIG_PHYTMAC_PLATFORM=m +CONFIG_PHYTMAC_PCI=m +CONFIG_DWMAC_PHYTIUM=m CONFIG_QLA3XXX=m CONFIG_NETXEN_NIC=m CONFIG_QED=m @@ -1016,7 +1022,10 @@ CONFIG_NETDEVSIM=m CONFIG_INPUT_SPARSEKMAP=m CONFIG_INPUT_MOUSEDEV=y CONFIG_INPUT_EVDEV=y -# CONFIG_MOUSE_PS2 is not set +CONFIG_INPUT_MOUSE=y +CONFIG_MOUSE_PS2=y +CONFIG_MOUSE_PS2_ELANTECH=y +CONFIG_MOUSE_PS2_ELANTECH_SMBUS=y CONFIG_MOUSE_ELAN_I2C=m CONFIG_MOUSE_ELAN_I2C_SMBUS=y CONFIG_MOUSE_VSXXXAA=m @@ -1225,6 +1234,34 @@ CONFIG_LOGO=y # CONFIG_LOGO_LINUX_VGA16 is not set CONFIG_SOUND=m CONFIG_SND=m +CONFIG_SND_HDA_PHYTIUM=m +CONFIG_SND_HDA_INTEL=m +CONFIG_SND_HDA_HWDEP=y +CONFIG_SND_HDA_INPUT_BEEP=y +CONFIG_SND_HDA_INPUT_BEEP_MODE=0 +CONFIG_SND_HDA_PATCH_LOADER=y +CONFIG_SND_HDA_CODEC_REALTEK=m +CONFIG_SND_HDA_CODEC_ANALOG=m +CONFIG_SND_HDA_CODEC_SIGMATEL=m +CONFIG_SND_HDA_CODEC_VIA=m +CONFIG_SND_HDA_CODEC_HDMI=m +CONFIG_SND_HDA_CODEC_CIRRUS=m +CONFIG_SND_HDA_CODEC_CONEXANT=m +CONFIG_SND_HDA_CODEC_CA0110=m +CONFIG_SND_HDA_CODEC_CA0132=m +CONFIG_SND_HDA_CODEC_CMEDIA=m +CONFIG_SND_HDA_CODEC_SI3054=m +CONFIG_SND_USB_AUDIO=m +CONFIG_SND_USB_UA101=m +CONFIG_SND_USB_CAIAQ=m +CONFIG_SND_USB_CAIAQ_INPUT=y +CONFIG_SND_USB_6FIRE=m +CONFIG_SND_USB_HIFACE=m +CONFIG_SND_BCD2000=m +CONFIG_SND_USB_POD=m +CONFIG_SND_USB_PODHD=m +CONFIG_SND_USB_TONEPORT=y +CONFIG_SND_USB_VARIAX=m CONFIG_HID_BATTERY_STRENGTH=y CONFIG_HIDRAW=y CONFIG_UHID=m diff --git a/arch/arm64/include/asm/phytium_platform.h b/arch/arm64/include/asm/phytium_platform.h index 88f49d5ea19cb46f0b2b2e3c75031ef39a6b835b..dba0e1b8ae4f4c7181e13108a9d0dd47bd5e1d2b 100644 --- a/arch/arm64/include/asm/phytium_platform.h +++ b/arch/arm64/include/asm/phytium_platform.h @@ -54,4 +54,12 @@ #define PHYTIUM_CPU_E2000 0x000700 #define PHYTIUM_CPU_D3000 0x000900 +#define PHYTIUM_LPC_SIRQ_BIT_KBD 1 +#define PHYTIUM_LPC_SIRQ_BIT_EC 11 +#define PHYTIUM_LPC_SIRQ_BIT_AUX 12 + +u8 ft_lpc_read(u8 addr); +u8 ft_lpc_write(u8 value, u8 addr); +int phytium_lpc_irq_find_mapping(u32 offset); + #endif // __PHYTIUM_PLATFORM_H_ diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 655766491a9e3db0aaa0a6e54868a5483be4b701..c6a5b99f11c5c67dab521c545ea139821e5d2d99 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -78,6 +78,7 @@ config LOONGARCH select GENERIC_ENTRY select GENERIC_GETTIMEOFDAY select GENERIC_IOREMAP if !ARCH_IOREMAP + select GENERIC_IRQ_MATRIX_ALLOCATOR select GENERIC_IRQ_MULTI_HANDLER select GENERIC_IRQ_PROBE select GENERIC_IRQ_SHOW diff --git a/arch/loongarch/configs/loongson3_defconfig b/arch/loongarch/configs/loongson3_defconfig index ec3cdb92b3db701ca3c649e4add376ab3236c816..3bfd2700ccedfc812b996532ad1827981ea57d5a 100644 --- a/arch/loongarch/configs/loongson3_defconfig +++ b/arch/loongarch/configs/loongson3_defconfig @@ -687,7 +687,7 @@ CONFIG_DRM_AMDGPU_USERPTR=y CONFIG_DRM_AST=y CONFIG_DRM_QXL=m CONFIG_DRM_VIRTIO_GPU=m -CONFIG_DRM_LOONGSON=y +CONFIG_DRM_LOONGSON=m CONFIG_FB=y CONFIG_FB_EFI=y CONFIG_FB_RADEON=y diff --git a/arch/loongarch/configs/openkylin_generic_defconfig b/arch/loongarch/configs/openkylin_generic_defconfig index fa3266988a86c0d77133a12cce07ccba9f925b65..69150069e181a0f4ace31018d2d8cee4e0a6c438 100644 --- a/arch/loongarch/configs/openkylin_generic_defconfig +++ b/arch/loongarch/configs/openkylin_generic_defconfig @@ -50,7 +50,6 @@ CONFIG_CPU_HAS_LBT=y CONFIG_CPU_FREQ=y CONFIG_CPU_FREQ_STAT=y CONFIG_CPU_FREQ_GOV_POWERSAVE=y -CONFIG_LOONGSON3_ACPI_CPUFREQ=y CONFIG_HIBERNATION=y CONFIG_PM_DEBUG=y CONFIG_ACPI_SPCR_TABLE=y @@ -168,7 +167,6 @@ CONFIG_TCP_CONG_DCTCP=m CONFIG_TCP_CONG_CDG=m CONFIG_TCP_CONG_BBR=m CONFIG_TCP_MD5SIG=y -CONFIG_IPV6=y CONFIG_IPV6_ROUTER_PREF=y CONFIG_IPV6_ROUTE_INFO=y CONFIG_IPV6_OPTIMISTIC_DAD=y @@ -191,7 +189,6 @@ CONFIG_IPV6_SEG6_HMAC=y CONFIG_IPV6_RPL_LWTUNNEL=y CONFIG_NETLABEL=y CONFIG_MPTCP=y -CONFIG_MPTCP_IPV6=y # CONFIG_MPTCP_KUNIT_TEST is not set CONFIG_NETWORK_PHY_TIMESTAMPING=y CONFIG_NETFILTER=y @@ -670,6 +667,7 @@ CONFIG_MTD_SPI_NOR=m CONFIG_MTD_UBI=m CONFIG_MTD_UBI_GLUEBI=m CONFIG_MTD_UBI_BLOCK=y +CONFIG_OF_OVERLAY=y CONFIG_PARPORT=m CONFIG_PARPORT_PC=m CONFIG_PARPORT_SERIAL=m @@ -689,7 +687,7 @@ CONFIG_CDROM_PKTCDVD=m CONFIG_VIRTIO_BLK=m CONFIG_BLK_DEV_RBD=m CONFIG_BLK_DEV_UBLK=m -CONFIG_BLK_DEV_NVME=m +CONFIG_BLK_DEV_NVME=y CONFIG_NVME_MULTIPATH=y CONFIG_NVME_RDMA=m CONFIG_NVME_FC=m @@ -721,7 +719,7 @@ CONFIG_UACCE=m CONFIG_PVPANIC=y CONFIG_PVPANIC_MMIO=m CONFIG_PVPANIC_PCI=m -CONFIG_BLK_DEV_SD=m +CONFIG_BLK_DEV_SD=y CONFIG_CHR_DEV_ST=m CONFIG_BLK_DEV_SR=m CONFIG_CHR_DEV_SG=m @@ -1537,9 +1535,9 @@ CONFIG_VIDEO_UPD64031A=m CONFIG_VIDEO_UPD64083=m CONFIG_VIDEO_M52790=m CONFIG_DRM=y +# CONFIG_DRM_KUNIT_TEST is not set CONFIG_DRM_PANIC=y CONFIG_DRM_PANIC_DEBUG=y -# CONFIG_DRM_KUNIT_TEST is not set CONFIG_DRM_LOAD_EDID_FIRMWARE=y CONFIG_DRM_DP_AUX_CHARDEV=y CONFIG_DRM_DP_CEC=y @@ -1554,11 +1552,11 @@ CONFIG_DRM_AMD_ACP=y CONFIG_DRM_NOUVEAU=m CONFIG_DRM_VKMS=m CONFIG_DRM_UDL=m -CONFIG_DRM_AST=y +CONFIG_DRM_AST_LOONGSON=y CONFIG_DRM_MGAG200=m CONFIG_DRM_QXL=m CONFIG_DRM_VIRTIO_GPU=m -CONFIG_DRM_LOONGSON=y +CONFIG_DRM_LOONGSON=m CONFIG_DRM_BOCHS=m CONFIG_DRM_CIRRUS_QEMU=m CONFIG_DRM_GM12U320=m @@ -2171,7 +2169,7 @@ CONFIG_AUTOFS_FS=y CONFIG_FUSE_FS=m CONFIG_CUSE=m CONFIG_VIRTIO_FS=m -CONFIG_OVERLAY_FS=y +CONFIG_OVERLAY_FS=m # CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW is not set CONFIG_OVERLAY_FS_INDEX=y CONFIG_OVERLAY_FS_XINO_AUTO=y @@ -2408,7 +2406,6 @@ CONFIG_SECONDARY_TRUSTED_KEYRING=y CONFIG_SYSTEM_BLACKLIST_KEYRING=y CONFIG_SYSTEM_REVOCATION_LIST=y CONFIG_PRIME_NUMBERS=m -CONFIG_CRC_T10DIF=y CONFIG_CRC_ITU_T=y CONFIG_CRC7=m CONFIG_QRLIB=y @@ -2419,7 +2416,6 @@ CONFIG_PRINTK_CALLER=y CONFIG_BOOT_PRINTK_DELAY=y CONFIG_DYNAMIC_DEBUG=y CONFIG_DEBUG_INFO_DWARF4=y -CONFIG_DEBUG_INFO_BTF=y CONFIG_GDB_SCRIPTS=y CONFIG_FRAME_WARN=4096 CONFIG_STRIP_ASM_SYMS=y diff --git a/arch/loongarch/include/asm/cpu-features.h b/arch/loongarch/include/asm/cpu-features.h index 2eafe6a6aca8189c88617c18da45c373294a6aa8..16a716f88a5ca2c2d0117184d4a6b55046fe44d7 100644 --- a/arch/loongarch/include/asm/cpu-features.h +++ b/arch/loongarch/include/asm/cpu-features.h @@ -65,5 +65,6 @@ #define cpu_has_guestid cpu_opt(LOONGARCH_CPU_GUESTID) #define cpu_has_hypervisor cpu_opt(LOONGARCH_CPU_HYPERVISOR) #define cpu_has_ptw cpu_opt(LOONGARCH_CPU_PTW) +#define cpu_has_avecint cpu_opt(LOONGARCH_CPU_AVECINT) #endif /* __ASM_CPU_FEATURES_H */ diff --git a/arch/loongarch/include/asm/cpu.h b/arch/loongarch/include/asm/cpu.h index 48b9f7168bcca03f92a63f891a346f88055b7bfc..843f9c4ec98071b818c615272734d2cc8428b7e5 100644 --- a/arch/loongarch/include/asm/cpu.h +++ b/arch/loongarch/include/asm/cpu.h @@ -99,6 +99,7 @@ enum cpu_type_enum { #define CPU_FEATURE_GUESTID 24 /* CPU has GuestID feature */ #define CPU_FEATURE_HYPERVISOR 25 /* CPU has hypervisor (running in VM) */ #define CPU_FEATURE_PTW 26 /* CPU has hardware page table walker */ +#define CPU_FEATURE_AVECINT 27 /* CPU has avec interrupt */ #define LOONGARCH_CPU_CPUCFG BIT_ULL(CPU_FEATURE_CPUCFG) #define LOONGARCH_CPU_LAM BIT_ULL(CPU_FEATURE_LAM) @@ -127,5 +128,6 @@ enum cpu_type_enum { #define LOONGARCH_CPU_GUESTID BIT_ULL(CPU_FEATURE_GUESTID) #define LOONGARCH_CPU_HYPERVISOR BIT_ULL(CPU_FEATURE_HYPERVISOR) #define LOONGARCH_CPU_PTW BIT_ULL(CPU_FEATURE_PTW) +#define LOONGARCH_CPU_AVECINT BIT_ULL(CPU_FEATURE_AVECINT) #endif /* _ASM_CPU_H */ diff --git a/arch/loongarch/include/asm/hardirq.h b/arch/loongarch/include/asm/hardirq.h index b26d596a73aa249daff90e2603259a22440001d5..5f70cb77b54dadcd54bf529c4e295e6353608f5b 100644 --- a/arch/loongarch/include/asm/hardirq.h +++ b/arch/loongarch/include/asm/hardirq.h @@ -15,8 +15,9 @@ extern void ack_bad_irq(unsigned int irq); enum ipi_msg_type { IPI_RESCHEDULE, IPI_CALL_FUNCTION, + IPI_CLEAR_VECTOR, }; -#define NR_IPI 2 +#define NR_IPI 3 typedef struct { unsigned int ipi_irqs[NR_IPI]; diff --git a/arch/loongarch/include/asm/hw_irq.h b/arch/loongarch/include/asm/hw_irq.h index af4f4e8fbd858f701490f4e590e7ec18c51085e1..8156ffb67415918d497a04413ab3c6fd6e913e15 100644 --- a/arch/loongarch/include/asm/hw_irq.h +++ b/arch/loongarch/include/asm/hw_irq.h @@ -9,6 +9,8 @@ extern atomic_t irq_err_count; +#define ARCH_IRQ_INIT_FLAGS IRQ_NOPROBE + /* * interrupt-retrigger: NOP for now. This may not be appropriate for all * machines, we'll see ... diff --git a/arch/loongarch/include/asm/irq.h b/arch/loongarch/include/asm/irq.h index 79f4c834ffe8b3fd7f6bf78eb8764f027a1153a5..78253da69954e726bae854419b50fb05f2374f3a 100644 --- a/arch/loongarch/include/asm/irq.h +++ b/arch/loongarch/include/asm/irq.h @@ -39,12 +39,23 @@ void spurious_interrupt(void); #define NR_IRQS_LEGACY 16 +/* + * 256 Vectors Mapping for AVECINTC: + * + * 0 - 15: Mapping classic IPs, e.g. IP0-12. + * 16 - 255: Mapping vectors for external IRQ. + * + */ +#define NR_VECTORS 256 +#define NR_LEGACY_VECTORS 16 +#define IRQ_MATRIX_BITS NR_VECTORS + #define arch_trigger_cpumask_backtrace arch_trigger_cpumask_backtrace extern bool arch_trigger_cpumask_backtrace(const cpumask_t *mask, int exclude_cpu); -#define MAX_IO_PICS 2 -#define NR_IRQS (64 + (256 * MAX_IO_PICS)) +#define MAX_IO_PICS 16 +#define NR_IRQS (64 + NR_VECTORS * (NR_CPUS + MAX_IO_PICS)) struct acpi_vector_group { int node; @@ -67,7 +78,7 @@ extern struct acpi_vector_group msi_group[MAX_IO_PICS]; #define LOONGSON_LPC_LAST_IRQ (LOONGSON_LPC_IRQ_BASE + 15) #define LOONGSON_CPU_IRQ_BASE 16 -#define LOONGSON_CPU_LAST_IRQ (LOONGSON_CPU_IRQ_BASE + 14) +#define LOONGSON_CPU_LAST_IRQ (LOONGSON_CPU_IRQ_BASE + 15) #define LOONGSON_PCH_IRQ_BASE 64 #define LOONGSON_PCH_ACPI_IRQ (LOONGSON_PCH_IRQ_BASE + 47) @@ -90,20 +101,8 @@ struct acpi_madt_bio_pic; struct acpi_madt_msi_pic; struct acpi_madt_lpc_pic; -int liointc_acpi_init(struct irq_domain *parent, - struct acpi_madt_lio_pic *acpi_liointc); -int eiointc_acpi_init(struct irq_domain *parent, - struct acpi_madt_eio_pic *acpi_eiointc); - -int htvec_acpi_init(struct irq_domain *parent, - struct acpi_madt_ht_pic *acpi_htvec); -int pch_lpc_acpi_init(struct irq_domain *parent, - struct acpi_madt_lpc_pic *acpi_pchlpc); -int pch_msi_acpi_init(struct irq_domain *parent, - struct acpi_madt_msi_pic *acpi_pchmsi); -int pch_pic_acpi_init(struct irq_domain *parent, - struct acpi_madt_bio_pic *acpi_pchpic); -int find_pch_pic(u32 gsi); +void complete_irq_moving(void); + struct fwnode_handle *get_pch_msi_handle(int pci_segment); extern struct acpi_madt_lio_pic *acpi_liointc; diff --git a/arch/loongarch/include/asm/loongarch.h b/arch/loongarch/include/asm/loongarch.h index 6829c7bf0548dd9ccea9eab296975d1b6d4fe310..b4c628543b4f6d700e973f32489396ffd7a0c7d9 100644 --- a/arch/loongarch/include/asm/loongarch.h +++ b/arch/loongarch/include/asm/loongarch.h @@ -255,8 +255,8 @@ #define CSR_ESTAT_EXC_WIDTH 6 #define CSR_ESTAT_EXC (_ULCAST_(0x3f) << CSR_ESTAT_EXC_SHIFT) #define CSR_ESTAT_IS_SHIFT 0 -#define CSR_ESTAT_IS_WIDTH 14 -#define CSR_ESTAT_IS (_ULCAST_(0x3fff) << CSR_ESTAT_IS_SHIFT) +#define CSR_ESTAT_IS_WIDTH 15 +#define CSR_ESTAT_IS (_ULCAST_(0x7fff) << CSR_ESTAT_IS_SHIFT) #define LOONGARCH_CSR_ERA 0x6 /* ERA */ @@ -651,6 +651,13 @@ #define LOONGARCH_CSR_CTAG 0x98 /* TagLo + TagHi */ +#define LOONGARCH_CSR_ISR0 0xa0 +#define LOONGARCH_CSR_ISR1 0xa1 +#define LOONGARCH_CSR_ISR2 0xa2 +#define LOONGARCH_CSR_ISR3 0xa3 + +#define LOONGARCH_CSR_IRR 0xa4 + #define LOONGARCH_CSR_PRID 0xc0 /* Shadow MCSR : 0xc0 ~ 0xff */ @@ -1005,7 +1012,7 @@ /* * CSR_ECFG IM */ -#define ECFG0_IM 0x00001fff +#define ECFG0_IM 0x00005fff #define ECFGB_SIP0 0 #define ECFGF_SIP0 (_ULCAST_(1) << ECFGB_SIP0) #define ECFGB_SIP1 1 @@ -1048,6 +1055,7 @@ #define IOCSRF_EIODECODE BIT_ULL(9) #define IOCSRF_FLATMODE BIT_ULL(10) #define IOCSRF_VM BIT_ULL(11) +#define IOCSRF_AVEC BIT_ULL(15) #define LOONGARCH_IOCSR_VENDOR 0x10 @@ -1058,6 +1066,7 @@ #define LOONGARCH_IOCSR_MISC_FUNC 0x420 #define IOCSR_MISC_FUNC_TIMER_RESET BIT_ULL(21) #define IOCSR_MISC_FUNC_EXT_IOI_EN BIT_ULL(48) +#define IOCSR_MISC_FUNC_AVEC_EN BIT_ULL(51) #define LOONGARCH_IOCSR_CPUTEMP 0x428 @@ -1378,9 +1387,10 @@ __BUILD_CSR_OP(tlbidx) #define INT_TI 11 /* Timer */ #define INT_IPI 12 #define INT_NMI 13 +#define INT_AVEC 14 /* ExcCodes corresponding to interrupts */ -#define EXCCODE_INT_NUM (INT_NMI + 1) +#define EXCCODE_INT_NUM (INT_AVEC + 1) #define EXCCODE_INT_START 64 #define EXCCODE_INT_END (EXCCODE_INT_START + EXCCODE_INT_NUM - 1) diff --git a/arch/loongarch/include/asm/se.h b/arch/loongarch/include/asm/se.h new file mode 100644 index 0000000000000000000000000000000000000000..d0f9d43d60d2f924a5e0e03d0904b7b4f8aacac2 --- /dev/null +++ b/arch/loongarch/include/asm/se.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2012 IBM Corporation + * + * Copyright 2023 Loongson Technology, Inc. + * Yinggang Gu + * + * Device driver for Loongson SE module. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + */ +#ifndef __LOONGSON_SE_H__ +#define __LOONGSON_SE_H__ + +#define SE_MAILBOX_S 0x0 +#define SE_MAILBOX_L 0x20 +#define SE_S2LINT_STAT 0x88 +#define SE_S2LINT_EN 0x8c +#define SE_S2LINT_SET 0x90 +#define SE_S2LINT_CL 0x94 +#define SE_L2SINT_STAT 0x98 +#define SE_L2SINT_EN 0x9c +#define SE_L2SINT_SET 0xa0 +#define SE_L2SINT_CL 0xa4 + +/* INT bit definition */ +#define SE_INT_SETUP BIT(0) +#define SE_INT_SM2 BIT(0) +#define SE_INT_SM3 BIT(0) +#define SE_INT_SM4 BIT(0) +#define SE_INT_RNG BIT(0) +#define SE_INT_TPM BIT(5) +#define SE_INT_ALL 0xffffffff + +#define SE_CMD_START 0x0 +#define SE_CMD_STOP 0x1 +#define SE_CMD_GETVER 0x2 +#define SE_CMD_SETBUF 0x3 +#define SE_CMD_SETMSG 0x4 + +#define SE_CMD_RNG 0x100 + +#define SE_CMD_SM2_SIGN 0x200 +#define SE_CMD_SM2_VSIGN 0x201 + +#define SE_CMD_SM3_DIGEST 0x300 +#define SE_CMD_SM3_UPDATE 0x301 +#define SE_CMD_SM3_FINISH 0x302 + +#define SE_CMD_SM4_ECB_ENCRY 0x400 +#define SE_CMD_SM4_ECB_DECRY 0x401 +#define SE_CMD_SM4_CBC_ENCRY 0x402 +#define SE_CMD_SM4_CBC_DECRY 0x403 +#define SE_CMD_SM4_CTR 0x404 + +#define SE_CMD_TPM 0x500 +#define SE_CMD_ZUC_INIT_READ 0x600 +#define SE_CMD_ZUC_READ 0x601 + +#define SE_CMD_SDF 0x700 + +#define SE_CH_MAX 32 + +#define SE_CH_RNG 1 +#define SE_CH_SM2 2 +#define SE_CH_SM3 3 +#define SE_CH_SM4 4 +#define SE_CH_TPM 5 +#define SE_CH_ZUC 6 +#define SE_CH_SDF 7 + +struct se_msg { + u32 cmd; + u32 data_off; + u32 data_len; + u32 info[5]; +}; + +struct se_cmd { + u32 cmd; + u32 info[7]; +}; + +struct se_res { + u32 cmd; + u32 cmd_ret; + u32 info[6]; +}; + +struct se_mailbox_data { + u32 int_bit; + union { + u32 mailbox[8]; + struct se_cmd gcmd; + struct se_res res; + } u; +}; + +struct lsse_ch { + u32 id; + u32 int_bit; + struct loongson_se *se; + void *priv; + spinlock_t ch_lock; + void *smsg; + void *rmsg; + int msg_size; + void *data_buffer; + dma_addr_t data_addr; + int data_size; + + void (*complete)(struct lsse_ch *se_ch); +}; + +struct loongson_se { + struct device *dev; + void __iomem *base; + u32 version; + u32 ch_status; + spinlock_t cmd_lock; + spinlock_t dev_lock; + + /* Interaction memory */ + void *mem_base; + dma_addr_t mem_addr; + unsigned long *mem_map; + int mem_map_size; + void *smsg; + void *rmsg; + + /* Synchronous CMD */ + struct completion cmd_completion; + + /* Virtual Channel */ + struct lsse_ch chs[SE_CH_MAX]; +}; + +struct lsse_ch *se_init_ch(int id, int data_size, int msg_size, void *priv, + void (*complete)(struct lsse_ch *se_ch)); +void se_deinit_ch(struct lsse_ch *ch); +int se_send_ch_requeset(struct lsse_ch *ch); + +#endif diff --git a/arch/loongarch/include/asm/smp.h b/arch/loongarch/include/asm/smp.h index 75d30529748c944748f2d70f8389c7fee509f69c..630e5ebec21cb3700dc1d22b003111ef69906daa 100644 --- a/arch/loongarch/include/asm/smp.h +++ b/arch/loongarch/include/asm/smp.h @@ -67,9 +67,11 @@ extern int __cpu_logical_map[NR_CPUS]; #define ACTION_BOOT_CPU 0 #define ACTION_RESCHEDULE 1 #define ACTION_CALL_FUNCTION 2 +#define ACTION_CLEAR_VECTOR 3 #define SMP_BOOT_CPU BIT(ACTION_BOOT_CPU) #define SMP_RESCHEDULE BIT(ACTION_RESCHEDULE) #define SMP_CALL_FUNCTION BIT(ACTION_CALL_FUNCTION) +#define SMP_CLEAR_VECTOR BIT(ACTION_CLEAR_VECTOR) struct secondary_data { unsigned long stack; diff --git a/arch/loongarch/kernel/cpu-probe.c b/arch/loongarch/kernel/cpu-probe.c index 55320813ee0819f0f23429361b7fc9722b710d65..14f0449f54520ac70db6a7e9d8636b391933b7af 100644 --- a/arch/loongarch/kernel/cpu-probe.c +++ b/arch/loongarch/kernel/cpu-probe.c @@ -106,7 +106,6 @@ static void cpu_probe_common(struct cpuinfo_loongarch *c) elf_hwcap |= HWCAP_LOONGARCH_CRC32; } - config = read_cpucfg(LOONGARCH_CPUCFG2); if (config & CPUCFG2_LAM) { c->options |= LOONGARCH_CPU_LAM; @@ -174,6 +173,8 @@ static void cpu_probe_common(struct cpuinfo_loongarch *c) c->options |= LOONGARCH_CPU_FLATMODE; if (config & IOCSRF_EIODECODE) c->options |= LOONGARCH_CPU_EIODECODE; + if (config & IOCSRF_AVEC) + c->options |= LOONGARCH_CPU_AVECINT; if (config & IOCSRF_VM) c->options |= LOONGARCH_CPU_HYPERVISOR; diff --git a/arch/loongarch/kernel/irq.c b/arch/loongarch/kernel/irq.c index a8aaaaeebf48d9855a056176ca3a57444dae723c..657e1a7a6a5cd78b1689d5a6da67bebecb8d4211 100644 --- a/arch/loongarch/kernel/irq.c +++ b/arch/loongarch/kernel/irq.c @@ -122,6 +122,19 @@ void fixup_irqs(void) } #endif +int __init arch_probe_nr_irqs(void) +{ + int nr_io_pics = bitmap_weight(loongson_sysconf.cores_io_master, NR_CPUS); + + if (!cpu_has_avecint) + nr_irqs = (64 + NR_VECTORS * nr_io_pics); + else + nr_irqs = (64 + NR_VECTORS * (nr_cpu_ids + nr_io_pics)); + + return NR_IRQS_LEGACY; +} + + void __init init_IRQ(void) { int i, ret; @@ -150,9 +163,6 @@ void __init init_IRQ(void) smp_ops.init_ipi(); #endif - for (i = 0; i < NR_IRQS; i++) - irq_set_noprobe(i); - for_each_possible_cpu(i) { page = alloc_pages_node(cpu_to_node(i), GFP_KERNEL, order); diff --git a/arch/loongarch/kernel/legacy_boot.h b/arch/loongarch/kernel/legacy_boot.h index 9a127aea817c3afc4fca187ee7f9b175fb283d14..65a3d14bfc6081cab4e345dae3aa350aadbdfc8c 100644 --- a/arch/loongarch/kernel/legacy_boot.h +++ b/arch/loongarch/kernel/legacy_boot.h @@ -4,6 +4,7 @@ #include #include #include +#include #define ADDRESS_TYPE_SYSRAM 1 #define ADDRESS_TYPE_RESERVED 2 #define ADDRESS_TYPE_ACPI 3 @@ -88,4 +89,14 @@ extern int __init pch_msi_parse_madt(union acpi_subtable_headers *header, const unsigned long end); extern struct irq_domain *get_pchpic_irq_domain(void); +extern __init void fw_init_cmdline(unsigned long argc, unsigned long cmdp); + +extern int liointc_acpi_init(struct irq_domain *parent, + struct acpi_madt_lio_pic *acpi_liointc); +extern int eiointc_acpi_init(struct irq_domain *parent, + struct acpi_madt_eio_pic *acpi_eiointc); +extern int htvec_acpi_init(struct irq_domain *parent, + struct acpi_madt_ht_pic *acpi_htvec); +extern int pch_lpc_acpi_init(struct irq_domain *parent, + struct acpi_madt_lpc_pic *acpi_pchlpc); #endif diff --git a/arch/loongarch/kernel/paravirt.c b/arch/loongarch/kernel/paravirt.c index 9abe8b71aa48774e03081cb691fa0957a8c584a2..0268076cd78927a2123362c9e8fd4a3c0862fae2 100644 --- a/arch/loongarch/kernel/paravirt.c +++ b/arch/loongarch/kernel/paravirt.c @@ -173,9 +173,9 @@ int __init pv_ipi_init(void) return 0; #ifdef CONFIG_SMP - mp_ops.init_ipi = pv_init_ipi; - mp_ops.send_ipi_single = pv_send_ipi_single; - mp_ops.send_ipi_mask = pv_send_ipi_mask; + smp_ops.init_ipi = pv_init_ipi; + smp_ops.send_ipi_single = pv_send_ipi_single; + smp_ops.send_ipi_mask = pv_send_ipi_mask; #endif return 0; diff --git a/arch/loongarch/kernel/relocate.c b/arch/loongarch/kernel/relocate.c index 1acfa704c8d09b95b625da9fd047833b181d35f7..aaa27823ea29d7d9c473c8877bdd2f233b693f91 100644 --- a/arch/loongarch/kernel/relocate.c +++ b/arch/loongarch/kernel/relocate.c @@ -15,6 +15,7 @@ #include #include #include +#include "legacy_boot.h" #define RELOCATED(x) ((void *)((long)x + reloc_offset)) #define RELOCATED_KASLR(x) ((void *)((long)x + random_offset)) @@ -172,7 +173,10 @@ unsigned long __init relocate_kernel(void) void *location_new = _text; /* Default to original kernel start */ char *cmdline = early_ioremap(fw_arg1, COMMAND_LINE_SIZE); /* Boot command line is passed in fw_arg1 */ - strscpy(boot_command_line, cmdline, COMMAND_LINE_SIZE); + if (fw_arg0 < 2) + strscpy(boot_command_line, cmdline, COMMAND_LINE_SIZE); + else + fw_init_cmdline(fw_arg0, TO_CACHE(fw_arg1)); /* OLD BPI parameters */ #ifdef CONFIG_RANDOMIZE_BASE location_new = determine_relocation_address(); diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index 963dd32f79f474f0e992b651680c2936b716b6e2..035560315af5103a183c04a575301a6d12ee799e 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -71,6 +71,7 @@ static DEFINE_PER_CPU(int, cpu_state); static const char *ipi_types[NR_IPI] __tracepoint_string = { [IPI_RESCHEDULE] = "Rescheduling interrupts", [IPI_CALL_FUNCTION] = "Function call interrupts", + [IPI_CLEAR_VECTOR] = "Clear vector interrupts", }; void show_ipi_list(struct seq_file *p, int prec) @@ -245,6 +246,11 @@ static irqreturn_t loongson_ipi_interrupt(int irq, void *dev) per_cpu(irq_stat, cpu).ipi_irqs[IPI_CALL_FUNCTION]++; } + if (action & SMP_CLEAR_VECTOR) { + complete_irq_moving(); + per_cpu(irq_stat, cpu).ipi_irqs[IPI_CLEAR_VECTOR]++; + } + return IRQ_HANDLED; } diff --git a/arch/x86/configs/openkylin_generic_defconfig b/arch/x86/configs/openkylin_generic_defconfig index 9d17fdfe5f3cf371589c916f5bc8906d57089161..f8851db44a8f6c74977b5d1f9d2f045ceea7457e 100644 --- a/arch/x86/configs/openkylin_generic_defconfig +++ b/arch/x86/configs/openkylin_generic_defconfig @@ -42,6 +42,7 @@ CONFIG_CGROUP_CPUACCT=y CONFIG_CGROUP_PERF=y CONFIG_CGROUP_BPF=y CONFIG_CGROUP_MISC=y +CONFIG_NAMESPACES=y CONFIG_USER_NS=y CONFIG_CHECKPOINT_RESTORE=y CONFIG_EXPERT=y diff --git a/arch/x86/events/zhaoxin/uncore.c b/arch/x86/events/zhaoxin/uncore.c index c311ab588b705a83a459a5ab56bf04c1a0d089d7..2e444994ffce6c04cdef49ebada6a8f257c1f24a 100644 --- a/arch/x86/events/zhaoxin/uncore.c +++ b/arch/x86/events/zhaoxin/uncore.c @@ -932,56 +932,6 @@ static const struct pci_device_id kh40000_uncore_pci_ids[] = { .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_MC0, 0), }, - { /* PCIE D2F0 */ - PCI_DEVICE(0x1D17, 0x0717), - .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_PCI, 0), - }, - - { /* PCIE D2F1 */ - PCI_DEVICE(0x1D17, 0x0718), - .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_PCI, 1), - }, - - { /* PCIE D3F0 */ - PCI_DEVICE(0x1D17, 0x0719), - .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_PCI, 2), - }, - - { /* PCIE D3F1 */ - PCI_DEVICE(0x1D17, 0x071A), - .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_PCI, 3), - }, - - { /* PCIE D3F2 */ - PCI_DEVICE(0x1D17, 0x071B), - .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_PCI, 4), - }, - - { /* PCIE D4F0 */ - PCI_DEVICE(0x1D17, 0x071C), - .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_PCI, 5), - }, - - { /* PCIE D4F1 */ - PCI_DEVICE(0x1D17, 0x071D), - .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_PCI, 6), - }, - - { /* PCIE D5F0 */ - PCI_DEVICE(0x1D17, 0x071E), - .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_PCI, 7), - }, - - { /* PCIE D5F1 */ - PCI_DEVICE(0x1D17, 0x0731), - .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_PCI, 8), - }, - - { /* PCIE D5F2 */ - PCI_DEVICE(0x1D17, 0x0732), - .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_PCI, 9), - }, - { /* ZPI_DLL */ PCI_DEVICE(0x1D17, 0x91c1), .driver_data = UNCORE_PCI_DEV_DATA(KH40000_PCI_UNCORE_ZPI_DLL, 0), @@ -1272,91 +1222,6 @@ static const struct pci_device_id kx7000_uncore_pci_ids[] = { .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_MC_A0, 0), }, - { /* PCIE D2F0 */ - PCI_DEVICE(0x1D17, 0x0717), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 0), - }, - - { /* PCIE D2F1 */ - PCI_DEVICE(0x1D17, 0x0718), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 1), - }, - - { /* PCIE D2F2 */ - PCI_DEVICE(0x1D17, 0x0733), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 2), - }, - - { /* PCIE D2F3 */ - PCI_DEVICE(0x1D17, 0x0734), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 3), - }, - - { /* PCIE D3F0 */ - PCI_DEVICE(0x1D17, 0x0719), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 4), - }, - - { /* PCIE D3F1 */ - PCI_DEVICE(0x1D17, 0x0735), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 5), - }, - - { /* PCIE D3F2 */ - PCI_DEVICE(0x1D17, 0x0739), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 6), - }, - - { /* PCIE D3F3 */ - PCI_DEVICE(0x1D17, 0x073A), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 7), - }, - - { /* PCIE D4F0 */ - PCI_DEVICE(0x1D17, 0x071B), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 8), - }, - - { /* PCIE D4F1 */ - PCI_DEVICE(0x1D17, 0x071C), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 9), - }, - - { /* PCIE D4F2 */ - PCI_DEVICE(0x1D17, 0x0736), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 10), - }, - - { /* PCIE D4F3 */ - PCI_DEVICE(0x1D17, 0x0737), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 11), - }, - - { /* PCIE D4F4 */ - PCI_DEVICE(0x1D17, 0x0738), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 12), - }, - - { /* PCIE D5F0 */ - PCI_DEVICE(0x1D17, 0x071D), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 13), - }, - - { /* PCIE D5F1 */ - PCI_DEVICE(0x1D17, 0x071E), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 14), - }, - - { /* PCIE D5F2 */ - PCI_DEVICE(0x1D17, 0x0732), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 15), - }, - - { /* PCIE D5F3 */ - PCI_DEVICE(0x1D17, 0x073B), - .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PCI, 16), - }, - { /* PXPTRF */ PCI_DEVICE(0x1D17, 0x31B4), .driver_data = UNCORE_PCI_DEV_DATA(KX7000_PCI_UNCORE_PXPTRF, 0), diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index f6ef401dfa6523281b0805330c9642a2cdb34059..6a166b96279dc52b0e96606a25c528184e952be5 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -2115,10 +2115,12 @@ static __always_inline void exc_machine_check_kernel(struct pt_regs *regs) static __always_inline void exc_machine_check_user(struct pt_regs *regs) { - irqentry_enter_from_user_mode(regs); + irqentry_state_t irq_state; + irqentry_enter_from_user_mode(regs); + irq_state = irqentry_nmi_enter(regs); do_machine_check(regs); - + irqentry_nmi_exit(regs, irq_state); irqentry_exit_to_user_mode(regs); } diff --git a/debian.master/control.d/linux-libc-dev.stub b/debian.master/control.d/linux-libc-dev.stub index fb045233d40178a93f0a75ab77063e3c1ca23854..a77f6ee5e13378168a64bea440c1b2019c05ed87 100644 --- a/debian.master/control.d/linux-libc-dev.stub +++ b/debian.master/control.d/linux-libc-dev.stub @@ -1,5 +1,5 @@ Package: linux-libc-dev -Architecture: amd64 armhf arm64 i386 ppc64el riscv64 s390x +Architecture: amd64 armhf arm64 i386 riscv64 loong64 Depends: ${misc:Depends} Conflicts: linux-kernel-headers Replaces: linux-kernel-headers diff --git a/debian.master/control.d/vars.generic b/debian.master/control.d/vars.generic index ca144cfac8962b31a10b32dceb22164d5437bb56..2212b2cf536b356771b3dd0beef86c6714b2f8e4 100644 --- a/debian.master/control.d/vars.generic +++ b/debian.master/control.d/vars.generic @@ -1,4 +1,4 @@ -arch="amd64 armhf arm64 ppc64el s390x" +arch="amd64 armhf arm64 loong64" supported="Generic" target="Geared toward desktop and server systems." desc="=HUMAN= SMP" diff --git a/debian.master/control.stub.in b/debian.master/control.stub.in index 4adb02906d0b3fa191a199cb9745571b67cb5175..78c459426cd79b3db591db40fbb9102a089389f4 100644 --- a/debian.master/control.stub.in +++ b/debian.master/control.stub.in @@ -36,7 +36,10 @@ Build-Depends: dkms , curl , zstd [amd64 s390x] , - pahole [amd64 arm64 armhf ppc64el s390x riscv64] | dwarves (>= 1.21) [amd64 arm64 armhf ppc64el s390x riscv64] , + dwarves (>= 1.21) [amd64 arm64 armhf ppc64el s390x riscv64] | pahole [amd64 arm64 armhf ppc64el s390x riscv64] , + clang , + libtraceevent-dev , + python3-dev , Build-Depends-Indep: xmlto , docbook-utils , @@ -110,7 +113,7 @@ Description: Linux kernel version specific tools for version PKGVER Package: SRCPKGNAME-tools-PKGVER-ABINUM Build-Profiles: -Architecture: amd64 armhf arm64 ppc64el s390x +Architecture: amd64 armhf arm64 loong64 Section: devel Priority: optional Depends: ${misc:Depends}, ${shlibs:Depends}, linux-tools-common diff --git a/debian.master/rules.d/loong64.mk b/debian.master/rules.d/loong64.mk new file mode 100644 index 0000000000000000000000000000000000000000..0a2d7fd238a202737c228eb7a54f1422304af364 --- /dev/null +++ b/debian.master/rules.d/loong64.mk @@ -0,0 +1,18 @@ +human_arch = loongarch64 +build_arch = loongarch +header_arch = loongarch +defconfig = defconfig +flavours = generic +build_image = vmlinux vmlinuz.efi +kernel_file = vmlinux +install_file = vmlinuz +no_dumpfile = true +elf_signed = true + +do_tools_usbip = true +do_tools_cpupower = true +do_tools_perf = true +do_extras_package = true +do_cloud_tools = true +do_libc_dev_package = true +do_doc_package = true diff --git a/debian/rules.d/0-common-vars.mk b/debian/rules.d/0-common-vars.mk index 6fb6a2e499d442b677637ef4963e6763ef08f368..0253abd5b67d4e32a96fb4454889f19acf7dc7d9 100644 --- a/debian/rules.d/0-common-vars.mk +++ b/debian/rules.d/0-common-vars.mk @@ -114,7 +114,7 @@ DEB_BUILD_ARCH = $(shell dpkg-architecture -qDEB_BUILD_ARCH) arch := $(DEB_HOST_ARCH) CROSS_COMPILE ?= $(DEB_HOST_GNU_TYPE)- -Arch=$(shell echo $(arch) | sed -e s/i.86/x86/ -e s/x86_64/x86/ -e s/amd64/x86/ -e s/aarch64.*/arm64/ -e s/mips64el/mips/) +Arch=$(shell echo $(arch) | sed -e s/i.86/x86/ -e s/x86_64/x86/ -e s/amd64/x86/ -e s/aarch64.*/arm64/ -e s/mips64el/mips/ -e s/loongarch64/loongarch/ -e s/loong64/loongarch/) # # Set consistent toolchain diff --git a/debian/rules.d/2-binary-arch.mk b/debian/rules.d/2-binary-arch.mk index e3471430a74534ac4dc5aefc2f92c7b1b3ade7f4..58c369f2b85159db779675b0d3354cda826268f1 100644 --- a/debian/rules.d/2-binary-arch.mk +++ b/debian/rules.d/2-binary-arch.mk @@ -105,6 +105,13 @@ endif ifeq ($(compress_file),) install -m600 -D $(builddir)/build-$*/$(kernfile) \ $(pkgdir_bin)/boot/$(instfile)-$(abi_release)-$* +ifeq ($(build_arch),loongarch) + strip $(pkgdir_bin)/boot/$(instfile)-$(abi_release)-$* + install -m600 -D $(builddir)/build-$*/arch/loongarch/boot/vmlinuz.efi \ + $(pkgdir_bin)/boot/loongarch-$(instfile)-$(abi_release)-$*.efi + mv $(pkgdir_bin)/boot/loongarch-$(instfile)-$(abi_release)-$*.efi \ + $(pkgdir_bin)/boot/$(instfile)-$(abi_release)-$* +endif else install -d $(pkgdir_bin)/boot gzip -c9v $(builddir)/build-$*/$(kernfile) > \ diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 8456d48ba702dbba92dce458444bcf92a7708ea2..8818b4b54a2ca77ccef0738267acf40eb897c65c 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -11,6 +11,7 @@ menuconfig ACPI depends on ARCH_SUPPORTS_ACPI select PNP select NLS + select FT_LPC if ARCH_PHYTIUM select CRC32 default y if X86 help diff --git a/drivers/acpi/acpi_apd.c b/drivers/acpi/acpi_apd.c index 80f945cbec8a7cf12502400575a02a455594ed10..e0fe87feb1757c4ce332f1cff7ba3f597a719b45 100644 --- a/drivers/acpi/acpi_apd.c +++ b/drivers/acpi/acpi_apd.c @@ -162,6 +162,11 @@ static const struct apd_device_desc hip08_lite_i2c_desc = { .fixed_clk_rate = 125000000, }; +static const struct apd_device_desc phytium_i2c_desc = { + .setup = acpi_apd_setup, + .fixed_clk_rate = 200000000, +}; + static const struct apd_device_desc thunderx2_i2c_desc = { .setup = acpi_apd_setup, .fixed_clk_rate = 125000000, @@ -246,6 +251,7 @@ static const struct acpi_device_id acpi_apd_device_ids[] = { { "HISI02A3", APD_ADDR(hip08_lite_i2c_desc) }, { "HISI0173", APD_ADDR(hip08_spi_desc) }, { "NXP0001", APD_ADDR(nxp_i2c_desc) }, + { "PHYT0003", APD_ADDR(phytium_i2c_desc) }, #endif { } }; diff --git a/drivers/acpi/pci_mcfg.c b/drivers/acpi/pci_mcfg.c index 96eccc4b16782e6e1c86d86d8bf59c7244c21842..ffda7c0e024377b748d564e83e7a2060dc9de327 100644 --- a/drivers/acpi/pci_mcfg.c +++ b/drivers/acpi/pci_mcfg.c @@ -185,6 +185,18 @@ static struct mcfg_fixup mcfg_quirks[] = { LOONGSON_ECAM_MCFG("LOONGSON", 0), LOONGSON_ECAM_MCFG("\0", 1), LOONGSON_ECAM_MCFG("LOONGSON", 1), + LOONGSON_ECAM_MCFG("\0", 2), + LOONGSON_ECAM_MCFG("LOONGSON", 2), + LOONGSON_ECAM_MCFG("\0", 3), + LOONGSON_ECAM_MCFG("LOONGSON", 3), + LOONGSON_ECAM_MCFG("\0", 4), + LOONGSON_ECAM_MCFG("LOONGSON", 4), + LOONGSON_ECAM_MCFG("\0", 5), + LOONGSON_ECAM_MCFG("LOONGSON", 5), + LOONGSON_ECAM_MCFG("\0", 6), + LOONGSON_ECAM_MCFG("LOONGSON", 6), + LOONGSON_ECAM_MCFG("\0", 7), + LOONGSON_ECAM_MCFG("LOONGSON", 7), #endif /* LOONGARCH */ }; diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 625af75833fc37047bbb93228dbf7131404a64a5..e27cc5ef59986f79820dcfc104f738c1b3179ae2 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -391,6 +391,26 @@ config UV_MMTIMER The uv_mmtimer device allows direct userspace access to the UV system timer. +config LOONGSON_SE + tristate "LOONGSON SECURITY MODULE Interface" + depends on LOONGARCH + default m + help + If you have LOONGSON security module (SE) support say Yes and it + will be accessible from within Linux. + To compile this driver as a module, choose M here,the module will + be called loongson-se. + +config LOONGSON_SE_SDF + tristate "LOONGSON SECURITY MODULE SDF Interface" + depends on LOONGARCH && LOONGSON_SE + default m + help + If you want to use LOONGSON security module (SE) as SDF say Yes + and it will be accessible from within Linux. + To compile this driver as a module, choose M here,the module will + be called loongson-se. + source "drivers/char/tpm/Kconfig" config TELCLOCK @@ -422,4 +442,6 @@ config ADI and SSM (Silicon Secured Memory). Intended consumers of this driver include crash and makedumpfile. +source "drivers/char/phytnetled/Kconfig" + endmenu diff --git a/drivers/char/Makefile b/drivers/char/Makefile index c5f532e412f1a4b93100ad51e5662563d7f3ab25..a2d2abcd6f98ab242e781f035a984e8977508760 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -32,6 +32,8 @@ obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o obj-$(CONFIG_PC8736x_GPIO) += pc8736x_gpio.o obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o obj-$(CONFIG_TELCLOCK) += tlclk.o +obj-$(CONFIG_LOONGSON_SE) += loongson_se.o +obj-$(CONFIG_LOONGSON_SE_SDF) += lsse_sdf_cdev.o obj-$(CONFIG_MWAVE) += mwave/ obj-y += agp/ @@ -44,3 +46,4 @@ obj-$(CONFIG_PS3_FLASH) += ps3flash.o obj-$(CONFIG_XILLYBUS_CLASS) += xillybus/ obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o obj-$(CONFIG_ADI) += adi.o +obj-$(CONFIG_PHYTNET_LED) += phytnetled/ diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index d0d86e351b07f8a45a227a188ba8a4dbfaf65dea..bc62e528234c8546564d0c229f377f22a7b9fe7c 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -586,6 +586,18 @@ config HW_RANDOM_JH7110 To compile this driver as a module, choose M here. The module will be called jh7110-trng. +config HW_RANDOM_PHYTIUM + tristate "Phytium Random Number Generator support" + depends on ARCH_PHYTIUM || COMPILE_TEST + help + This driver provides kernel-side support for the Random Number + Generator hardware found on Phytium SoCs. + + To compile this driver as a module, choose M here: the + module will be called phytium-rng. + + If unsure, say Y. + endif # HW_RANDOM config UML_RANDOM diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index ef5b3ae0794dd87089709a5b9548fc427ebfd253..50c40454758ba22401b0cb37b304073f44554478 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -50,3 +50,4 @@ obj-$(CONFIG_HW_RANDOM_ARM_SMCCC_TRNG) += arm_smccc_trng.o obj-$(CONFIG_HW_RANDOM_CN10K) += cn10k-rng.o obj-$(CONFIG_HW_RANDOM_POLARFIRE_SOC) += mpfs-rng.o obj-$(CONFIG_HW_RANDOM_JH7110) += jh7110-trng.o +obj-$(CONFIG_HW_RANDOM_PHYTIUM) += phytium-rng.o diff --git a/drivers/char/hw_random/phytium-rng.c b/drivers/char/hw_random/phytium-rng.c new file mode 100644 index 0000000000000000000000000000000000000000..ef4c137c17cccfefc268c71d545836236eaa543f --- /dev/null +++ b/drivers/char/hw_random/phytium-rng.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SoC RNG Driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TRNG_CR 0x00 +#define TRNG_CR_RNGEN BIT(0) +#define TRNG_CR_ROSEN_MASK GENMASK(7, 4) +#define TRNG_CR_DIEN BIT(16) +#define TRNG_CR_ERIEN BIT(17) +#define TRNG_CR_IRQEN BIT(24) +#define TRNG_MSEL 0x04 +#define TRNG_MSEL_MSEL BIT(0) +#define TRNG_SR 0x08 +#define TRNG_SR_HTF BIT(0) +#define TRNG_SR_DRDY BIT(1) +#define TRNG_SR_ERERR BIT(3) +#define TRNG_DR 0x0C +#define TRNG_RESEED 0x40 +#define TRNG_RESEED_RSED BIT(0) + +#define DELAY 10 +#define TIMEOUT 100 + +static int msel; +module_param(msel, int, 0444); +MODULE_PARM_DESC(msel, "Phytium RNG mode selection: 0 - TRNG. 1 - PRNG."); + +struct phytium_rng { + struct hwrng rng; + void __iomem *base; +}; + +static int phytium_rng_init(struct hwrng *rng) +{ + struct phytium_rng *priv = container_of(rng, struct phytium_rng, rng); + u32 reg; + + /* Mode Selection */ + reg = msel ? TRNG_MSEL_MSEL : 0; + writel(reg, priv->base + TRNG_MSEL); + + /* If PRGN mode is on, do reseed operations */ + if (msel) + writel(TRNG_RESEED_RSED, priv->base + TRNG_RESEED); + + /* Clear status */ + writel(0x7, priv->base + TRNG_SR); + + /* Enable TRNG */ + reg = readl(priv->base + TRNG_CR) | TRNG_CR_ROSEN_MASK | TRNG_CR_RNGEN; + writel(reg, priv->base + TRNG_CR); + + return 0; +} + +static void phytium_rng_cleanup(struct hwrng *rng) +{ + struct phytium_rng *priv = container_of(rng, struct phytium_rng, rng); + + writel(0x7, priv->base + TRNG_SR); +} + +static int phytium_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + struct phytium_rng *priv = container_of(rng, struct phytium_rng, rng); + u32 reg; + int ret = 0; + + /* TRNG can generate at most 8*32bit random number per time */ + max = max > 8 ? 8 : max; + + reg = readl(priv->base + TRNG_SR); + if (!(reg & TRNG_SR_DRDY) && wait) { + ret = readl_poll_timeout(priv->base + TRNG_SR, reg, + reg & TRNG_SR_DRDY, DELAY, TIMEOUT); + if (ret) { + dev_err((struct device *)priv->rng.priv, + "%s: timeout %x!\n", __func__, reg); + return -EIO; + } + } + + while (max > 4) { + *(u32 *)buf = readl(priv->base + TRNG_DR); + + ret += sizeof(u32); + buf += sizeof(u32); + max -= sizeof(u32); + } + + /* Clear DRDY by writing 1 */ + writel(reg | TRNG_SR_DRDY, priv->base + TRNG_SR); + + return ret; +} + +static int scto_rng_probe(struct platform_device *pdev) +{ + struct phytium_rng *priv; + struct resource *mem; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->rng.name = pdev->name; + priv->rng.init = phytium_rng_init; + priv->rng.cleanup = phytium_rng_cleanup; + priv->rng.read = phytium_rng_read; + priv->rng.priv = (unsigned long)&pdev->dev; + + return devm_hwrng_register(&pdev->dev, &priv->rng); +} + +static const struct of_device_id phytium_rng_dt_ids[] = { + { .compatible = "phytium,rng" }, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_rng_dt_ids); + +static struct platform_driver phytium_rng_driver = { + .probe = scto_rng_probe, + .driver = { + .name = "phytium-rng", + .of_match_table = of_match_ptr(phytium_rng_dt_ids), + } +}; +module_platform_driver(phytium_rng_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium random number generator driver"); +MODULE_AUTHOR("Chen Baozi "); diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index f4adc6feb3b2291d6b4ce44f35e058345d217eb4..fe3b159c193b467d0b5afa60349312aa3e9d82f3 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig @@ -118,6 +118,27 @@ config ASPEED_KCS_IPMI_BMC The driver implements the BMC side of the KCS contorller, it provides the access of KCS IO space for BMC side. +config PHYTIUM_KCS_IPMI_BMC + depends on ARCH_PHYTIUM + select IPMI_KCS_BMC + select REGMAP_MMIO + tristate "PHYTIUM KCS IPMI BMC driver" + help + Provides a driver for the KCS (Keyboard Controller Style) IPMI + interface found on Phytium SOCs. + + The driver implements the BMC side of the KCS contorller, it + provides the access of KCS IO space for BMC side. + +config PHYTIUM_BT_IPMI_BMC + depends on ARCH_PHYTIUM + depends on REGMAP && REGMAP_MMIO && MFD_SYSCON + tristate "PHYTIUM BT BMC driver" + help + Provides a driver for the BT (Block Transfer) interface found + on Phytium SOCs. The driver implements the BMC side of + the BT interface. + config NPCM7XX_KCS_IPMI_BMC depends on ARCH_NPCM || COMPILE_TEST select IPMI_KCS_BMC diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile index bc9c6506fd59e6225eb4e76d9da022c78493bbe4..c32fcf1010b9ebf95eadc1c5abbaee37d93b475b 100644 --- a/drivers/char/ipmi/Makefile +++ b/drivers/char/ipmi/Makefile @@ -35,3 +35,5 @@ obj-$(CONFIG_ASPEED_KCS_IPMI_BMC) += kcs_bmc_aspeed.o obj-$(CONFIG_NPCM7XX_KCS_IPMI_BMC) += kcs_bmc_npcm7xx.o obj-$(CONFIG_IPMB_DEVICE_INTERFACE) += ipmb_dev_int.o obj-$(CONFIG_SSIF_IPMI_BMC) += ssif_bmc.o +obj-$(CONFIG_PHYTIUM_KCS_IPMI_BMC) += kcs_bmc_phytium.o +obj-$(CONFIG_PHYTIUM_BT_IPMI_BMC) += bt_bmc_phytium.o diff --git a/drivers/char/ipmi/bt_bmc_phytium.c b/drivers/char/ipmi/bt_bmc_phytium.c new file mode 100644 index 0000000000000000000000000000000000000000..7777e7a6bb9956f4a120f6f0eb95c19aca6c4a5e --- /dev/null +++ b/drivers/char/ipmi/bt_bmc_phytium.c @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2021-2023, Phytium Technology, Co., Ltd. + * + * Derived from drivers/char/ipmi/bt-bmc.c + * Copyright (c) 2015-2016, IBM Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * This is a BMC device used to communicate to the host + */ +#define DEVICE_NAME "ipmi-bt-host" + +#define BT_IO_BASE 0xe4 +#define BT_IRQ 10 + +#define LPC_HICR0 0x00 +#define LPC_HICR0_LPC3E BIT(7) +#define LPC_HICR0_LPC2E BIT(6) +#define LPC_HICR0_LPC1E BIT(5) +#define LPC_HICR0_SDWNE BIT(3) /* 0:disabl,1:enable for HICR1_SDWNB */ +#define LPC_HICR0_PMEE BIT(2) /* 0:disabl,1:enable for HICR1_PMEB */ + +#define LPC_HICR2 0x08 +#define LPC_HICR2_IBFIF3 BIT(3) /* 0:normal,1:enable irq 3*/ + +#define LPC_HICR4 0x10 +#define LPC_HICR4_LADR12AS BIT(7) +#define LPC_HICR4_KCSENBL BIT(2) +#define LPC_BTENABLE BIT(0) /* share bt channel enable,0:disable,1:enable */ +#define LPC_LADR3H_BT 0x014 +#define LPC_LADR3L_BT 0x018 + +#define BT_CR1 0x4C +#define BT_CR1_IRQ_H2B 0x04 +#define BT_CR1_IRQ_HWRST 0x40 + +#define BT_CSR1 0x54 +#define BT_CSR1_IRQ_H2B 0x04 +#define BT_CSR1_IRQ_HWRST 0x40 + +#define BT_CTRL 0x58 +#define BT_CTRL_B_BUSY 0x80 +#define BT_CTRL_H_BUSY 0x40 +#define BT_CTRL_OEM0 0x20 +#define BT_CTRL_SMS_ATN 0x10 +#define BT_CTRL_B2H_ATN 0x08 +#define BT_CTRL_H2B_ATN 0x04 +#define BT_CTRL_CLR_RD_PTR 0x02 +#define BT_CTRL_CLR_WR_PTR 0x01 +#define BT_BMC2HOST 0x5C +#define BT_HOST2BMC 0x98 +#define BT_INTMASK 0x60 +#define BT_INTMASK_B2H_IRQEN 0x01 +#define BT_INTMASK_B2H_IRQ 0x02 +#define BT_INTMASK_BMC_HWRST 0x80 + +#define BT_BMC_BUFFER_SIZE 256 + +struct bt_bmc { + struct device dev; + struct miscdevice miscdev; + struct regmap *map; + int irq; + wait_queue_head_t queue; + struct timer_list poll_timer; + struct mutex mutex; +}; + +static atomic_t open_count = ATOMIC_INIT(0); + +static const struct regmap_config bt_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static u8 bt_inb(struct bt_bmc *bt_bmc, int reg) +{ + uint32_t val = 0; + int rc; + + rc = regmap_read(bt_bmc->map, reg, &val); + WARN(rc != 0, "regmap_read() failed: %d\n", rc); + + return rc == 0 ? (u8) val : 0; +} + +static void bt_outb(struct bt_bmc *bt_bmc, u8 data, int reg) +{ + int rc; + + rc = regmap_write(bt_bmc->map, reg, data); + WARN(rc != 0, "regmap_write() failed: %d\n", rc); +} + +static void clr_rd_ptr(struct bt_bmc *bt_bmc) +{ + bt_outb(bt_bmc, BT_CTRL_CLR_RD_PTR, BT_CTRL); +} + +static void clr_wr_ptr(struct bt_bmc *bt_bmc) +{ + bt_outb(bt_bmc, BT_CTRL_CLR_WR_PTR, BT_CTRL); +} + +static void clr_h2b_atn(struct bt_bmc *bt_bmc) +{ + bt_outb(bt_bmc, 0, BT_CTRL); +} + +static void set_b_busy(struct bt_bmc *bt_bmc) +{ + if (!(bt_inb(bt_bmc, BT_CTRL) & BT_CTRL_B_BUSY)) + bt_outb(bt_bmc, BT_CTRL_B_BUSY, BT_CTRL); +} + +static void clr_b_busy(struct bt_bmc *bt_bmc) +{ + if (bt_inb(bt_bmc, BT_CTRL) & BT_CTRL_B_BUSY) + bt_outb(bt_bmc, 0, BT_CTRL); +} + +static void set_b2h_atn(struct bt_bmc *bt_bmc) +{ + bt_outb(bt_bmc, BT_CTRL_B2H_ATN, BT_CTRL); +} + +static u8 bt_read(struct bt_bmc *bt_bmc) +{ + return bt_inb(bt_bmc, BT_HOST2BMC); +} + +static ssize_t bt_readn(struct bt_bmc *bt_bmc, u8 *buf, size_t n) +{ + int i; + + for (i = 0; i < n; i++) + buf[i] = bt_read(bt_bmc); + return n; +} + +static void bt_write(struct bt_bmc *bt_bmc, u8 c) +{ + bt_outb(bt_bmc, c, BT_BMC2HOST); +} + +static ssize_t bt_writen(struct bt_bmc *bt_bmc, u8 *buf, size_t n) +{ + int i; + + for (i = 0; i < n; i++) + bt_write(bt_bmc, buf[i]); + return n; +} + +static void set_sms_atn(struct bt_bmc *bt_bmc) +{ + bt_outb(bt_bmc, BT_CTRL_SMS_ATN, BT_CTRL); +} + +static struct bt_bmc *file_bt_bmc(struct file *file) +{ + return container_of(file->private_data, struct bt_bmc, miscdev); +} + +static int bt_bmc_open(struct inode *inode, struct file *file) +{ + struct bt_bmc *bt_bmc = file_bt_bmc(file); + + if (atomic_inc_return(&open_count) == 1) { + clr_b_busy(bt_bmc); + return 0; + } + + atomic_dec(&open_count); + + return -EBUSY; +} + +/* + * The BT (Block Transfer) interface means that entire messages are + * buffered by the host before a notification is sent to the BMC that + * there is data to be read. The first byte is the length and the + * message data follows. The read operation just tries to capture the + * whole before returning it to userspace. + * + * BT Message format : + * + * Byte 1 Byte 2 Byte 3 Byte 4 Byte 5:N + * Length NetFn/LUN Seq Cmd Data + * + */ +static ssize_t bt_bmc_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct bt_bmc *bt_bmc = file_bt_bmc(file); + u8 len; + int len_byte = 1; + u8 kbuffer[BT_BMC_BUFFER_SIZE]; + ssize_t ret = 0; + ssize_t nread; + + WARN_ON(*ppos); + + if (wait_event_interruptible(bt_bmc->queue, + bt_inb(bt_bmc, BT_CTRL) & BT_CTRL_H2B_ATN)) + return -ERESTARTSYS; + + mutex_lock(&bt_bmc->mutex); + + if (unlikely(!(bt_inb(bt_bmc, BT_CTRL) & BT_CTRL_H2B_ATN))) { + ret = -EIO; + goto out_unlock; + } + + set_b_busy(bt_bmc); + clr_h2b_atn(bt_bmc); + clr_rd_ptr(bt_bmc); + + /* + * The BT frames start with the message length, which does not + * include the length byte. + */ + kbuffer[0] = bt_read(bt_bmc); + len = kbuffer[0]; + + /* We pass the length back to userspace as well */ + if (len + 1 > count) + len = count - 1; + + while (len) { + nread = min_t(ssize_t, len, sizeof(kbuffer) - len_byte); + + bt_readn(bt_bmc, kbuffer + len_byte, nread); + + if (copy_to_user(buf, kbuffer, nread + len_byte)) { + ret = -EFAULT; + break; + } + len -= nread; + buf += nread + len_byte; + ret += nread + len_byte; + len_byte = 0; + } + + clr_b_busy(bt_bmc); + +out_unlock: + mutex_unlock(&bt_bmc->mutex); + return ret; +} + +/* + * BT Message response format : + * + * Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6:N + * Length NetFn/LUN Seq Cmd Code Data + */ +static ssize_t bt_bmc_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct bt_bmc *bt_bmc = file_bt_bmc(file); + u8 kbuffer[BT_BMC_BUFFER_SIZE]; + ssize_t ret = 0; + ssize_t nwritten; + + /* + * send a minimum response size + */ + if (count < 5) + return -EINVAL; + + WARN_ON(*ppos); + + /* + * There's no interrupt for clearing bmc busy so we have to + * poll + */ + if (wait_event_interruptible(bt_bmc->queue, + !(bt_inb(bt_bmc, BT_CTRL) & + (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN)))) + return -ERESTARTSYS; + + mutex_lock(&bt_bmc->mutex); + + if (unlikely(bt_inb(bt_bmc, BT_CTRL) & + (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN))) { + ret = -EIO; + goto out_unlock; + } + + clr_wr_ptr(bt_bmc); + + while (count) { + nwritten = min_t(ssize_t, count, sizeof(kbuffer)); + if (copy_from_user(&kbuffer, buf, nwritten)) { + ret = -EFAULT; + break; + } + + bt_writen(bt_bmc, kbuffer, nwritten); + + count -= nwritten; + buf += nwritten; + ret += nwritten; + } + + set_b2h_atn(bt_bmc); + +out_unlock: + mutex_unlock(&bt_bmc->mutex); + return ret; +} + +static long bt_bmc_ioctl(struct file *file, unsigned int cmd, + unsigned long param) +{ + struct bt_bmc *bt_bmc = file_bt_bmc(file); + + switch (cmd) { + case BT_BMC_IOCTL_SMS_ATN: + set_sms_atn(bt_bmc); + return 0; + } + return -EINVAL; +} + +static int bt_bmc_release(struct inode *inode, struct file *file) +{ + struct bt_bmc *bt_bmc = file_bt_bmc(file); + + atomic_dec(&open_count); + set_b_busy(bt_bmc); + return 0; +} + +static __poll_t bt_bmc_poll(struct file *file, poll_table *wait) +{ + struct bt_bmc *bt_bmc = file_bt_bmc(file); + __poll_t mask = 0; + u8 ctrl; + + poll_wait(file, &bt_bmc->queue, wait); + + ctrl = bt_inb(bt_bmc, BT_CTRL); + + if (ctrl & BT_CTRL_H2B_ATN) + mask |= EPOLLIN; + + if (!(ctrl & (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN))) + mask |= EPOLLOUT; + + return mask; +} + +static const struct file_operations bt_bmc_fops = { + .owner = THIS_MODULE, + .open = bt_bmc_open, + .read = bt_bmc_read, + .write = bt_bmc_write, + .release = bt_bmc_release, + .poll = bt_bmc_poll, + .unlocked_ioctl = bt_bmc_ioctl, +}; + +static void poll_timer(struct timer_list *t) +{ + struct bt_bmc *bt_bmc = from_timer(bt_bmc, t, poll_timer); + + bt_bmc->poll_timer.expires += msecs_to_jiffies(500); + wake_up(&bt_bmc->queue); + add_timer(&bt_bmc->poll_timer); +} + +static irqreturn_t bt_bmc_irq(int irq, void *arg) +{ + struct bt_bmc *bt_bmc = arg; + u32 reg, intmsk; + int rc; + + rc = regmap_read(bt_bmc->map, BT_CR1, ®); + if (rc) + return IRQ_NONE; + + reg &= BT_CR1_IRQ_H2B | BT_CR1_IRQ_HWRST; + if (!reg) + return IRQ_NONE; + + /* ack pending IRQs */ + regmap_write(bt_bmc->map, BT_CR1, 0); + + if (reg & BT_CR1_IRQ_HWRST) { + regmap_read(bt_bmc->map, BT_INTMASK, &intmsk); + if (intmsk & BT_INTMASK_BMC_HWRST) + regmap_write(bt_bmc->map, BT_INTMASK, 0); + } + + wake_up(&bt_bmc->queue); + return IRQ_HANDLED; +} + +static int bt_bmc_config_irq(struct bt_bmc *bt_bmc, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int rc; + + bt_bmc->irq = platform_get_irq(pdev, 0); + if (!bt_bmc->irq) + return -ENODEV; + + rc = devm_request_irq(dev, bt_bmc->irq, bt_bmc_irq, IRQF_SHARED, + DEVICE_NAME, bt_bmc); + if (rc < 0) { + dev_warn(dev, "Unable to request IRQ %d\n", bt_bmc->irq); + bt_bmc->irq = 0; + return rc; + } + + /* + * Configure IRQs on the bmc clearing the H2B and HBUSY bits; + * H2B will be asserted when the bmc has data for us; HBUSY + * will be cleared (along with B2H) when we can write the next + * message to the BT buffer + */ + rc = regmap_update_bits(bt_bmc->map, BT_CSR1, + BT_CSR1_IRQ_H2B | BT_CSR1_IRQ_HWRST, + BT_CSR1_IRQ_H2B | BT_CSR1_IRQ_HWRST); + + return rc; +} + +static int bt_bmc_probe(struct platform_device *pdev) +{ + struct bt_bmc *bt_bmc; + struct device *dev; + int rc; + + if (!pdev || !pdev->dev.of_node) + return -ENODEV; + + dev = &pdev->dev; + dev_info(dev, "Found bt bmc device\n"); + + bt_bmc = devm_kzalloc(dev, sizeof(*bt_bmc), GFP_KERNEL); + if (!bt_bmc) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, bt_bmc); + + bt_bmc->map = syscon_node_to_regmap(pdev->dev.parent->of_node); + + mutex_init(&bt_bmc->mutex); + init_waitqueue_head(&bt_bmc->queue); + + bt_bmc->miscdev.minor = MISC_DYNAMIC_MINOR, + bt_bmc->miscdev.name = DEVICE_NAME, + bt_bmc->miscdev.fops = &bt_bmc_fops, + bt_bmc->miscdev.parent = dev; + rc = misc_register(&bt_bmc->miscdev); + if (rc) { + dev_err(dev, "Unable to register misc device\n"); + return rc; + } + + bt_bmc_config_irq(bt_bmc, pdev); + + if (bt_bmc->irq) { + dev_info(dev, "Using IRQ %d\n", bt_bmc->irq); + } else { + dev_info(dev, "No IRQ; using timer\n"); + timer_setup(&bt_bmc->poll_timer, poll_timer, 0); + bt_bmc->poll_timer.expires = jiffies + msecs_to_jiffies(10); + add_timer(&bt_bmc->poll_timer); + } + + regmap_update_bits(bt_bmc->map, LPC_HICR0, LPC_HICR0_LPC3E, LPC_HICR0_LPC3E); + regmap_update_bits(bt_bmc->map, LPC_HICR4, LPC_BTENABLE, LPC_BTENABLE); + regmap_write(bt_bmc->map, LPC_LADR3H_BT, BT_IO_BASE >> 8); + regmap_write(bt_bmc->map, LPC_LADR3L_BT, BT_IO_BASE); + regmap_update_bits(bt_bmc->map, LPC_HICR2, LPC_HICR2_IBFIF3, LPC_HICR2_IBFIF3); + clr_b_busy(bt_bmc); + + return 0; +} + +static int bt_bmc_remove(struct platform_device *pdev) +{ + struct bt_bmc *bt_bmc = dev_get_drvdata(&pdev->dev); + + misc_deregister(&bt_bmc->miscdev); + if (!bt_bmc->irq) + del_timer_sync(&bt_bmc->poll_timer); + + return 0; +} + +static const struct of_device_id bt_bmc_match[] = { + { .compatible = "phytium,bt-bmc" }, + { }, +}; +MODULE_DEVICE_TABLE(of, bt_bmc_match); + +static struct platform_driver bt_bmc_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = bt_bmc_match, + }, + .probe = bt_bmc_probe, + .remove = bt_bmc_remove, +}; + +module_platform_driver(bt_bmc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cheng Quan +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kcs_bmc_device.h" + +#define DEVICE_NAME "phytium-kcs-bmc" + +#define KCS_CHANNEL_MAX 4 + +/* mapped to lpc-bmc@0 IO space */ +#define LPC_HICR0 0x000 +#define LPC_HICR0_LPC3E BIT(7) +#define LPC_HICR0_LPC2E BIT(6) +#define LPC_HICR0_LPC1E BIT(5) +#define LPC_HICR0_SDWNE BIT(3) /* 0:disabl,1:enable for HICR1_SDWNB */ +#define LPC_HICR0_PMEE BIT(2) /* 0:disabl,1:enable for HICR1_PMEB */ +#define LPC_HICR1 0x004 +#define LPC_HICR1_LPCBSY BIT(7) /* 0:idle,1:trans busy */ +#define LPC_HICR1_IRQBSY BIT(5) /* 0:idle,1:trans data */ +#define LPC_HICR1_LPCSWRST BIT(4) /* 0:normal,1:reset */ +#define LPC_HICR1_SDWNB BIT(3) /* 0:normal,1:software shutdown */ +#define LPC_HICR1_PMEB BIT(2) /* 0:LPCPD low,1:LPCPD high */ +#define LPC_HICR2 0x008 +#define LPC_LPCSWRST_ERRIRQ BIT(6) /* 0:normal,1:reset irq */ +#define LPC_SDWN_ERRIRQ BIT(5) /* 0:normal,1:lpcpd irq */ +#define LPC_ABRT_ERRIRQ BIT(4) /* 0:normal,1:lframe low when busy*/ +#define LPC_HICR2_IBFIF3 BIT(3) /* 0:normal,1:enable irq 3*/ +#define LPC_HICR2_IBFIF2 BIT(2) +#define LPC_HICR2_IBFIF1 BIT(1) +/* 0:normal,1:enable err irq-reset,power down,abort */ +#define LPC_HICR2_ERRIE BIT(0) +#define LPC_HICR3 0x00C +#define LPC_LFRAME_STATUS BIT(7) /* R */ +#define LPC_SERIRQ_STATUS BIT(5) /* R */ +#define LPC_LREST_STATUS BIT(4) /* R */ +#define LPC_LPCPD_STATUS BIT(3) /* R */ +#define LPC_PME_STATUS BIT(2) /* R */ +#define LPC_HICR4 0x010 +#define LPC_HICR4_LADR12AS BIT(7) +#define LPC_HICR4_KCSENBL BIT(2) +#define LPC_BTENABLE BIT(0) /* share bt channel enable,0:disable,1:enable */ +#define LPC_LADR3H 0x014 +#define LPC_LADR3L 0x018 +#define LPC_LADR12H 0x01C +#define LPC_LADR12L 0x020 +#define LPC_IDR1 0x024 +#define LPC_IDR2 0x028 +#define LPC_IDR3 0x02C +#define LPC_ODR1 0x030 +#define LPC_ODR2 0x034 +#define LPC_ODR3 0x038 +#define LPC_STR1 0x03C +#define LPC_STR2 0x040 +#define LPC_STR3 0x044 + +#define LPC_HICRB 0x80 +#define LPC_HICRB_IBFIF4 BIT(1) +#define LPC_HICRB_LPC4E BIT(0) +#define LPC_LADR4 0x88 +#define LPC_IDR4 0x8c +#define LPC_ODR4 0x90 +#define LPC_STR4 0x94 + +#define OBE_POLL_PERIOD (HZ / 2) +struct phytium_kcs_bmc { + struct kcs_bmc_device kcs_bmc; + + struct regmap *map; + + struct { + spinlock_t lock; + bool remove; + struct timer_list timer; + } obe; +}; + +struct phytium_kcs_of_ops { + int (*get_channel)(struct platform_device *pdev); + int (*get_io_address)(struct platform_device *pdev, u32 addr); +}; + +static inline struct phytium_kcs_bmc *to_phytium_kcs_bmc + (struct kcs_bmc_device *kcs_bmc) +{ + return container_of(kcs_bmc, struct phytium_kcs_bmc, kcs_bmc); +} + +static u8 phytium_kcs_inb(struct kcs_bmc_device *kcs_bmc, u32 reg) +{ + struct phytium_kcs_bmc *priv = to_phytium_kcs_bmc(kcs_bmc); + u32 val = 0; + int rc; + + rc = regmap_read(priv->map, reg, &val); + WARN(rc != 0, "regmap_read() failed: %d\n", rc); + + return rc == 0 ? (u8) val : 0; +} + +static void phytium_kcs_outb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 data) +{ + struct phytium_kcs_bmc *priv = to_phytium_kcs_bmc(kcs_bmc); + int rc; + + rc = regmap_write(priv->map, reg, data); + WARN(rc != 0, "regmap_write() failed: %d\n", rc); +} + +static void phytium_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask, u8 val) +{ + struct phytium_kcs_bmc *priv = to_phytium_kcs_bmc(kcs_bmc); + int rc; + + rc = regmap_update_bits(priv->map, reg, mask, val); + WARN(rc != 0, "regmap_update_bits() failed: %d\n", rc); +} +/* + * Background: + * we note D for Data, and C for Cmd/Status, default rules are + * A. KCS1 / KCS2 ( D / C:X / X+4 ) + * D / C : CA0h / CA4h + * D / C : CA8h / CACh + * B. KCS3 ( D / C:XX2h / XX3h ) + * D / C : CA2h / CA3h + * D / C : CB2h / CB3h -use + * C. KCS4 + * D / C : CA4h / CA5h + * D / C : CB0h / CB1h -use + */ +static int phytium_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u16 addr) +{ + struct phytium_kcs_bmc *priv = to_phytium_kcs_bmc(kcs_bmc); + + switch (priv->kcs_bmc.channel) { + case 1: + regmap_update_bits(priv->map, LPC_HICR4, LPC_HICR4_LADR12AS, 0); + regmap_write(priv->map, LPC_LADR12H, addr >> 8); + regmap_write(priv->map, LPC_LADR12L, addr & 0xFF); + break; + case 2: + regmap_update_bits(priv->map, LPC_HICR4, + LPC_HICR4_LADR12AS, LPC_HICR4_LADR12AS); + regmap_write(priv->map, LPC_LADR12H, addr >> 8); + regmap_write(priv->map, LPC_LADR12L, addr & 0xFF); + break; + case 3: + regmap_write(priv->map, LPC_LADR3H, addr >> 8); + regmap_write(priv->map, LPC_LADR3L, addr & 0xFF); + break; + case 4: + regmap_write(priv->map, LPC_LADR4, ((addr + 1) << 16) | + addr); + break; + default: + break; + } + return 0; +} + +static void phytium_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enable) +{ + struct phytium_kcs_bmc *priv = to_phytium_kcs_bmc(kcs_bmc); + + switch (kcs_bmc->channel) { + case 1: + if (enable) { + regmap_update_bits(priv->map, LPC_HICR0, + LPC_HICR0_LPC1E, LPC_HICR0_LPC1E); + } else { + regmap_update_bits(priv->map, LPC_HICR0, + LPC_HICR0_LPC1E, 0); + } + break; + case 2: + if (enable) { + regmap_update_bits(priv->map, LPC_HICR0, + LPC_HICR0_LPC2E, LPC_HICR0_LPC2E); + } else { + regmap_update_bits(priv->map, LPC_HICR0, + LPC_HICR0_LPC2E, 0); + } + break; + case 3: + if (enable) { + regmap_update_bits(priv->map, LPC_HICR0, + LPC_HICR0_LPC3E, LPC_HICR0_LPC3E); + regmap_update_bits(priv->map, LPC_HICR4, + LPC_HICR4_KCSENBL, LPC_HICR4_KCSENBL); + } else { + regmap_update_bits(priv->map, LPC_HICR0, + LPC_HICR0_LPC3E, 0); + regmap_update_bits(priv->map, LPC_HICR4, + LPC_HICR4_KCSENBL, 0); + } + break; + case 4: + if (enable) + regmap_update_bits(priv->map, LPC_HICRB, + LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E, + LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E); + else + regmap_update_bits(priv->map, LPC_HICRB, + LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E, + 0); + break; + default: + pr_warn("%s: Unsupported channel: %d", __func__, kcs_bmc->channel); + return; + } +} + +static void phytium_kcs_check_obe(struct timer_list *timer) +{ + struct phytium_kcs_bmc *priv = container_of(timer, struct phytium_kcs_bmc, obe.timer); + unsigned long flags; + u8 str; + + spin_lock_irqsave(&priv->obe.lock, flags); + if (priv->obe.remove) { + spin_unlock_irqrestore(&priv->obe.lock, flags); + return; + } + + str = phytium_kcs_inb(&priv->kcs_bmc, priv->kcs_bmc.ioreg.str); + if (str & KCS_BMC_STR_OBF) { + mod_timer(timer, jiffies + OBE_POLL_PERIOD); + spin_unlock_irqrestore(&priv->obe.lock, flags); + return; + } + spin_unlock_irqrestore(&priv->obe.lock, flags); + + kcs_bmc_handle_event(&priv->kcs_bmc); +} + +static void phytium_kcs_irq_mask_update(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 state) +{ + struct phytium_kcs_bmc *priv = to_phytium_kcs_bmc(kcs_bmc); + + if (mask & KCS_BMC_EVENT_TYPE_OBE) { + if (KCS_BMC_EVENT_TYPE_OBE & state) + mod_timer(&priv->obe.timer, jiffies + OBE_POLL_PERIOD); + else + del_timer(&priv->obe.timer); + } + + if (mask & KCS_BMC_EVENT_TYPE_IBF) { + const bool enable = !!(state & KCS_BMC_EVENT_TYPE_IBF); + + switch (kcs_bmc->channel) { + case 1: + regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF1, + enable * LPC_HICR2_IBFIF1); + return; + case 2: + regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF2, + enable * LPC_HICR2_IBFIF2); + return; + case 3: + regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF3, + enable * LPC_HICR2_IBFIF3); + return; + case 4: + regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_IBFIF4, + enable * LPC_HICRB_IBFIF4); + return; + default: + pr_warn("%s: Unsupported channel: %d", __func__, kcs_bmc->channel); + return; + } + } +} + + +static const struct kcs_bmc_device_ops phytium_kcs_ops = { + .irq_mask_update = phytium_kcs_irq_mask_update, + .io_inputb = phytium_kcs_inb, + .io_outputb = phytium_kcs_outb, + .io_updateb = phytium_kcs_updateb, +}; + +static irqreturn_t phytium_kcs_irq(int irq, void *arg) +{ + struct kcs_bmc_device *kcs_bmc = arg; + + return kcs_bmc_handle_event(kcs_bmc); +} + +static int phytium_kcs_config_irq(struct kcs_bmc_device *kcs_bmc, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + return devm_request_irq(dev, irq, phytium_kcs_irq, IRQF_SHARED, + dev_name(dev), kcs_bmc); +} + +static const struct kcs_ioreg phytium_kcs_bmc_ioregs[KCS_CHANNEL_MAX] = { + { .idr = LPC_IDR1, .odr = LPC_ODR1, .str = LPC_STR1 }, + { .idr = LPC_IDR2, .odr = LPC_ODR2, .str = LPC_STR2 }, + { .idr = LPC_IDR3, .odr = LPC_ODR3, .str = LPC_STR3 }, + { .idr = LPC_IDR4, .odr = LPC_ODR4, .str = LPC_STR4 }, + +}; + +static int phytium_kcs_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phytium_kcs_bmc *priv; + struct kcs_bmc_device *kcs_bmc; + struct device_node *np; + u32 chan, addr; + int rc; + + rc = of_property_read_u32(dev->of_node, "kcs_chan", &chan); + if ((rc != 0) || (chan == 0 || chan > KCS_CHANNEL_MAX)) { + dev_err(dev, "no valid 'kcs_chan' configured\n"); + return -ENODEV; + } + rc = of_property_read_u32(dev->of_node, "kcs_addr", &addr); + if (rc) { + dev_err(dev, "no valid 'kcs_addr' configured\n"); + return -ENODEV; + } + + np = pdev->dev.of_node; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + kcs_bmc = &priv->kcs_bmc; + kcs_bmc->dev = &pdev->dev; + kcs_bmc->channel = chan; + kcs_bmc->ioreg = phytium_kcs_bmc_ioregs[chan - 1]; + kcs_bmc->ops = &phytium_kcs_ops; + + priv->map = syscon_node_to_regmap(pdev->dev.parent->of_node); + if (IS_ERR(priv->map)) { + dev_err(&pdev->dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + spin_lock_init(&priv->obe.lock); + priv->obe.remove = false; + timer_setup(&priv->obe.timer, phytium_kcs_check_obe, 0); + + rc = phytium_kcs_set_address(kcs_bmc, addr); + if (rc) + return rc; + + rc = phytium_kcs_config_irq(kcs_bmc, pdev); + if (rc) + return rc; + + platform_set_drvdata(pdev, priv); + + phytium_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0); + + phytium_kcs_enable_channel(kcs_bmc, true); + + rc = kcs_bmc_add_device(&priv->kcs_bmc); + if (rc) { + dev_warn(&pdev->dev, "Failed to register channel %d: %d\n", + kcs_bmc->channel, rc); + return rc; + } + + dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n", + kcs_bmc->channel, addr); + + return 0; +} + +static int phytium_kcs_remove(struct platform_device *pdev) +{ + struct phytium_kcs_bmc *priv = platform_get_drvdata(pdev); + struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc; + + kcs_bmc_remove_device(kcs_bmc); + phytium_kcs_enable_channel(kcs_bmc, false); + phytium_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0); + + spin_lock_irq(&priv->obe.lock); + priv->obe.remove = true; + spin_unlock_irq(&priv->obe.lock); + del_timer_sync(&priv->obe.timer); + return 0; +} + +static const struct of_device_id phytium_kcs_bmc_match[] = { + { .compatible = "phytium,kcs-bmc", }, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_kcs_bmc_match); + +static struct platform_driver phytium_kcs_bmc_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = phytium_kcs_bmc_match, + }, + .probe = phytium_kcs_probe, + .remove = phytium_kcs_remove, +}; +module_platform_driver(phytium_kcs_bmc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cheng Quan "); +MODULE_DESCRIPTION("Phytium device interface to the KCS BMC device"); diff --git a/drivers/char/loongson_se.c b/drivers/char/loongson_se.c new file mode 100644 index 0000000000000000000000000000000000000000..40d2d65182659752449ea3da255f5d7cea8f6a56 --- /dev/null +++ b/drivers/char/loongson_se.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int se_mem_size = 0x800000; +module_param(se_mem_size, int, 0444); +MODULE_PARM_DESC(se_mem_size, "LOONGSON SE shared memory size"); + +static int se_mem_page = PAGE_SIZE; +module_param(se_mem_page, int, 0444); +MODULE_PARM_DESC(se_mem_page, "LOONGSON SE shared memory page size"); + +static struct loongson_se se_dev; + +static int lsse_open(struct inode *inode, struct file *filp) +{ + return 0; +} + +static ssize_t lsse_write(struct file *filp, const char __user *buf, + size_t cnt, loff_t *offt) +{ + return 0; +} + +static const struct file_operations lsse_fops = { + .owner = THIS_MODULE, + .open = lsse_open, + .write = lsse_write, +}; + +static const struct miscdevice lsse_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "loongson-se", + .fops = &lsse_fops, +}; + +static inline u32 se_readl(u64 addr) +{ + return readl(se_dev.base + addr); +} + +static inline void se_writel(u32 val, u64 addr) +{ + writel(val, se_dev.base + addr); +} + +static inline bool se_ch_status(struct loongson_se *se, u32 int_bit) +{ + return !!(se->ch_status & int_bit) == 1; +} + +static void se_enable_int(struct loongson_se *se, u32 int_bit) +{ + unsigned long flag; + u32 tmp; + + if (!int_bit) + return; + + spin_lock_irqsave(&se->dev_lock, flag); + + tmp = se_readl(SE_S2LINT_EN); + tmp |= int_bit; + se_writel(tmp, SE_S2LINT_EN); + + spin_unlock_irqrestore(&se->dev_lock, flag); +} + +static void se_disable_int(struct loongson_se *se, u32 int_bit) +{ + unsigned long flag; + u32 tmp; + + if (!int_bit) + return; + + spin_lock_irqsave(&se->dev_lock, flag); + + tmp = se_readl(SE_S2LINT_EN); + tmp &= ~(int_bit); + se_writel(tmp, SE_S2LINT_EN); + + spin_unlock_irqrestore(&se->dev_lock, flag); +} + +static int se_send_requeset(struct loongson_se *se, + struct se_mailbox_data *req) +{ + unsigned long flag; + u32 status; + int err = 0; + int i; + + if (!se || !req) + return -EINVAL; + + if (se_readl(SE_L2SINT_STAT) || + !(se_readl(SE_L2SINT_EN) & req->int_bit)) + return -EBUSY; + + spin_lock_irqsave(&se->cmd_lock, flag); + + for (i = 0; i < ARRAY_SIZE(req->u.mailbox); i++) + se_writel(req->u.mailbox[i], SE_MAILBOX_S + i * 4); + + se_writel(req->int_bit, SE_L2SINT_SET); + + err = readl_relaxed_poll_timeout_atomic(se->base + SE_L2SINT_STAT, status, + !(status & req->int_bit), 10, 10000); + + spin_unlock_irqrestore(&se->cmd_lock, flag); + + return err; +} + +static int se_get_response(struct loongson_se *se, + struct se_mailbox_data *res) +{ + unsigned long flag; + int i; + + if (!se || !res) + return -EINVAL; + + if ((se_readl(SE_S2LINT_STAT) & res->int_bit) == 0) + return -EBUSY; + + spin_lock_irqsave(&se->cmd_lock, flag); + + for (i = 0; i < ARRAY_SIZE(res->u.mailbox); i++) + res->u.mailbox[i] = se_readl(SE_MAILBOX_L + i * 4); + + se_writel(res->int_bit, SE_S2LINT_CL); + + spin_unlock_irqrestore(&se->cmd_lock, flag); + + return 0; +} + +static int loongson_se_get_res(struct loongson_se *se, u32 int_bit, u32 cmd, + struct se_mailbox_data *res) +{ + int err = 0; + + res->int_bit = int_bit; + + if (se_get_response(se, res)) { + dev_err(se->dev, "Int 0x%x get response fail.\n", int_bit); + return -EFAULT; + } + + /* Check response */ + if (res->u.res.cmd == cmd) + err = 0; + else { + dev_err(se->dev, "Response cmd is 0x%x, not expect cmd 0x%x.\n", + res->u.res.cmd, cmd); + err = -EFAULT; + } + + return err; +} + +static int se_send_genl_cmd(struct loongson_se *se, struct se_mailbox_data *req, + struct se_mailbox_data *res, int retry) +{ + int err = 0, cnt = 0; + +try_again: + if (cnt++ >= retry) { + err = -ETIMEDOUT; + goto out; + } + + dev_dbg(se->dev, "%d time send cmd 0x%x\n", cnt, req->u.gcmd.cmd); + + err = se_send_requeset(se, req); + if (err) + goto try_again; + + if (!wait_for_completion_timeout(&se->cmd_completion, + msecs_to_jiffies(0x1000))) { + se_enable_int(se, req->int_bit); + goto try_again; + } + + err = loongson_se_get_res(se, req->int_bit, req->u.gcmd.cmd, res); + if (err || res->u.res.cmd_ret) { + se_enable_int(se, req->int_bit); + goto try_again; + } + +out: + se_enable_int(se, req->int_bit); + + return err; +} + +static int loongson_se_set_msg(struct lsse_ch *ch) +{ + struct loongson_se *se = ch->se; + struct se_mailbox_data req = {0}; + struct se_mailbox_data res = {0}; + int err; + + req.int_bit = SE_INT_SETUP; + req.u.gcmd.cmd = SE_CMD_SETMSG; + /* MSG off */ + req.u.gcmd.info[0] = ch->id; + req.u.gcmd.info[1] = ch->smsg - se->mem_base; + req.u.gcmd.info[2] = ch->msg_size; + + dev_dbg(se->dev, "Set Channel %d msg off 0x%x, msg size %d\n", ch->id, + req.u.gcmd.info[1], req.u.gcmd.info[2]); + + err = se_send_genl_cmd(se, &req, &res, 5); + if (res.u.res.cmd_ret) + return res.u.res.cmd_ret; + + return err; +} + +static irqreturn_t loongson_se_irq(int irq, void *dev_id) +{ + struct loongson_se *se = (struct loongson_se *)dev_id; + struct lsse_ch *ch; + u32 int_status; + + int_status = se_readl(SE_S2LINT_STAT); + + dev_dbg(se->dev, "%s int status is 0x%x\n", __func__, int_status); + + se_disable_int(se, int_status); + + if (int_status & SE_INT_SETUP) { + complete(&se->cmd_completion); + int_status &= ~SE_INT_SETUP; + } + + while (int_status) { + int id = __ffs(int_status); + + ch = &se->chs[id]; + if (ch->complete) + ch->complete(ch); + int_status &= ~BIT(id); + se_writel(BIT(id), SE_S2LINT_CL); + } + + return IRQ_HANDLED; +} + +static int se_init_hw(struct loongson_se *se) +{ + struct se_mailbox_data req = {0}; + struct se_mailbox_data res = {0}; + struct device *dev = se->dev; + int err, retry = 5; + u64 size; + + size = se_mem_size; + + if (size & (size - 1)) { + size = roundup_pow_of_two(size); + se_mem_size = size; + } + + se_enable_int(se, SE_INT_SETUP); + + /* Start engine */ + memset(&req, 0, sizeof(struct se_mailbox_data)); + memset(&res, 0, sizeof(struct se_mailbox_data)); + req.int_bit = SE_INT_SETUP; + req.u.gcmd.cmd = SE_CMD_START; + err = se_send_genl_cmd(se, &req, &res, retry); + if (err) + return err; + + /* Get Version */ + memset(&req, 0, sizeof(struct se_mailbox_data)); + memset(&res, 0, sizeof(struct se_mailbox_data)); + req.int_bit = SE_INT_SETUP; + req.u.gcmd.cmd = SE_CMD_GETVER; + err = se_send_genl_cmd(se, &req, &res, retry); + if (err) + return err; + + se->version = res.u.res.info[0]; + + /* Setup data buffer */ + se->mem_base = dmam_alloc_coherent(dev, size, + &se->mem_addr, GFP_KERNEL); + if (!se->mem_base) + return -ENOMEM; + + memset(se->mem_base, 0, size); + + memset(&req, 0, sizeof(struct se_mailbox_data)); + memset(&res, 0, sizeof(struct se_mailbox_data)); + req.int_bit = SE_INT_SETUP; + req.u.gcmd.cmd = SE_CMD_SETBUF; + /* MMAP */ + req.u.gcmd.info[0] = (se->mem_addr & 0xffffffff) | 0x80; + req.u.gcmd.info[1] = se->mem_addr >> 32; + /* MASK */ + req.u.gcmd.info[2] = ~(size - 1); + req.u.gcmd.info[3] = 0xffffffff; + + pr_debug("Set win mmap 0x%llx, mask 0x%llx\n", + ((u64)req.u.gcmd.info[1] << 32) | req.u.gcmd.info[0], + ((u64)req.u.gcmd.info[3] << 32) | req.u.gcmd.info[2]); + + err = se_send_genl_cmd(se, &req, &res, retry); + if (err) + return err; + + se->mem_map_size = size / se_mem_page; + se->mem_map = bitmap_zalloc(se->mem_map_size, GFP_KERNEL); + if (!se->mem_map) + return -ENOMEM; + + dev_info(se->dev, "SE module setup down, shared memory size is 0x%x bytes, memory page size is 0x%x bytes\n", + se_mem_size, se_mem_page); + + return err; +} + +static void loongson_se_disable_hw(struct loongson_se *se) +{ + struct se_mailbox_data req = {0}; + struct se_mailbox_data res = {0}; + int retry = 5; + + /* Stop engine */ + req.int_bit = SE_INT_SETUP; + req.u.gcmd.cmd = SE_CMD_STOP; + se_send_genl_cmd(se, &req, &res, retry); + + se_disable_int(se, SE_INT_ALL); + kfree(se->mem_map); +} + +int se_send_ch_requeset(struct lsse_ch *ch) +{ + struct loongson_se *se; + u32 status, int_bit; + int err = 0; + + if (!ch) + return -EINVAL; + + se = ch->se; + int_bit = ch->int_bit; + + if ((se_readl(SE_L2SINT_STAT) & int_bit) || + !(se_readl(SE_L2SINT_EN) & int_bit)) + return -EBUSY; + + se_enable_int(se, int_bit); + se_writel(int_bit, SE_L2SINT_SET); + + err = readl_relaxed_poll_timeout_atomic(se->base + SE_L2SINT_STAT, status, + !(status & int_bit), 10, 10000); + + return err; +} +EXPORT_SYMBOL_GPL(se_send_ch_requeset); + +struct lsse_ch *se_init_ch(int id, int data_size, int msg_size, void *priv, + void (*complete)(struct lsse_ch *se_ch)) +{ + struct loongson_se *se = &se_dev; + struct lsse_ch *ch; + unsigned long flag; + int data_first, data_nr; + int msg_first, msg_nr; + + if (!se) { + pr_err("SE has bot been initialized\n"); + return NULL; + } + + if (id == 0 || id > SE_CH_MAX) { + dev_err(se->dev, "Channel number %d is invalid\n", id); + return NULL; + } + + if (se_ch_status(se, BIT(id))) { + dev_err(se->dev, "Channel number %d has been initialized\n", id); + return NULL; + } + + spin_lock_irqsave(&se->dev_lock, flag); + + ch = &se_dev.chs[id]; + ch->se = se; + ch->id = id; + ch->int_bit = BIT(id); + se->ch_status |= BIT(id); + + data_nr = round_up(data_size, se_mem_page) / se_mem_page; + data_first = bitmap_find_next_zero_area(se->mem_map, se->mem_map_size, + 0, data_nr, 0); + if (data_first >= se->mem_map_size) { + dev_err(se->dev, "Insufficient memory space\n"); + spin_unlock_irqrestore(&se->dev_lock, flag); + return NULL; + } + + bitmap_set(se->mem_map, data_first, data_nr); + ch->data_buffer = se->mem_base + data_first * se_mem_page; + ch->data_addr = se->mem_addr + data_first * se_mem_page; + ch->data_size = data_size; + + msg_nr = round_up(msg_size, se_mem_page) / se_mem_page; + msg_first = bitmap_find_next_zero_area(se->mem_map, se->mem_map_size, + 0, msg_nr, 0); + if (msg_first >= se->mem_map_size) { + dev_err(se->dev, "Insufficient memory space\n"); + bitmap_clear(se->mem_map, data_first, data_nr); + spin_unlock_irqrestore(&se->dev_lock, flag); + return NULL; + } + + bitmap_set(se->mem_map, msg_first, msg_nr); + ch->smsg = se->mem_base + msg_first * se_mem_page; + ch->rmsg = ch->smsg + msg_size / 2; + ch->msg_size = msg_size; + + ch->complete = complete; + ch->priv = priv; + + spin_lock_init(&ch->ch_lock); + + spin_unlock_irqrestore(&se->dev_lock, flag); + + if (loongson_se_set_msg(ch)) { + dev_err(se->dev, "Channel %d setup message address failed\n", id); + return NULL; + } + + se_enable_int(se, ch->int_bit); + + return ch; +} +EXPORT_SYMBOL_GPL(se_init_ch); + +void se_deinit_ch(struct lsse_ch *ch) +{ + struct loongson_se *se = &se_dev; + unsigned long flag; + int first, nr; + int id = ch->id; + + if (!se) { + pr_err("SE has bot been initialized\n"); + return; + } + + if (id == 0 || id > SE_CH_MAX) { + dev_err(se->dev, "Channel number %d is invalid\n", id); + return; + } + + if (!se_ch_status(se, BIT(id))) { + dev_err(se->dev, "Channel number %d has not been initialized\n", id); + return; + } + + spin_lock_irqsave(&se->dev_lock, flag); + + se->ch_status &= ~BIT(ch->id); + + first = (ch->data_buffer - se->mem_base) / se_mem_page; + nr = round_up(ch->data_size, se_mem_page) / se_mem_page; + bitmap_clear(se->mem_map, first, nr); + + first = (ch->smsg - se->mem_base) / se_mem_page; + nr = round_up(ch->msg_size, se_mem_page) / se_mem_page; + bitmap_clear(se->mem_map, first, nr); + + spin_unlock_irqrestore(&se->dev_lock, flag); + + se_disable_int(se, ch->int_bit); +} +EXPORT_SYMBOL_GPL(se_deinit_ch); + +static struct platform_device lsse_sdf_pdev = { + .name = "loongson-sdf", + .id = -1, +}; + +static const struct of_device_id loongson_se_of_match[] = { + { .compatible = "loongson,ls3c6000se", }, + {} +}; +MODULE_DEVICE_TABLE(of, loongson_se_of_match); + +static int loongson_se_probe(struct platform_device *pdev) +{ + struct loongson_se *se = &se_dev; + struct resource *res; + struct device *dev = &pdev->dev; + int nr_irq, err, i; + int irq[8]; + + nr_irq = platform_irq_count(pdev); + if (nr_irq < 0) + return -ENODEV; + + for (i = 0; i < nr_irq; i++) { + irq[i] = platform_get_irq(pdev, i); + if (irq[i] < 0) + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + se->base = devm_ioremap_resource(dev, res); + if (IS_ERR(se->base)) + return PTR_ERR(se->base); + + se->dev = &pdev->dev; + platform_set_drvdata(pdev, se); + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + init_completion(&se->cmd_completion); + spin_lock_init(&se->cmd_lock); + spin_lock_init(&se->dev_lock); + + for (i = 0; i < nr_irq; i++) { + err = devm_request_irq(dev, irq[i], loongson_se_irq, 0, + "loongson-se", se); + if (err) + goto out; + } + + err = se_init_hw(se); + if (err) + goto disable_hw; + + err = misc_register(&lsse_miscdev); + if (err) + goto disable_hw; + + err = platform_device_register(&lsse_sdf_pdev); + if (err) + pr_err("register sdf device failed\n"); + + return 0; + +disable_hw: + loongson_se_disable_hw(se); +out: + for ( ; i >= 0; i--) + devm_free_irq(dev, irq[i], se); + + return err; +} + +static int loongson_se_remove(struct platform_device *pdev) +{ + struct loongson_se *se = platform_get_drvdata(pdev); + + misc_deregister(&lsse_miscdev); + loongson_se_disable_hw(se); + platform_device_unregister(&lsse_sdf_pdev); + + return 0; +} + +static struct platform_driver loongson_se_driver = { + .probe = loongson_se_probe, + .remove = loongson_se_remove, + .driver = { + .name = "loongson-se", + .of_match_table = loongson_se_of_match, + }, +}; + +module_platform_driver(loongson_se_driver); + +MODULE_AUTHOR("Yinggang Gu"); +MODULE_DESCRIPTION("Loongson SE driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/lsse_sdf_cdev.c b/drivers/char/lsse_sdf_cdev.c new file mode 100644 index 0000000000000000000000000000000000000000..a4806fbf08d1e72343b2ff29a8c9fad12d34484c --- /dev/null +++ b/drivers/char/lsse_sdf_cdev.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SE_SDF_BUFSIZE (PAGE_SIZE * 2) +#define SDF_OPENSESSION 0x204 +#define SDF_CLOSESESSION 0x205 + +struct lsse_sdf_dev { + struct lsse_ch *se_ch; + struct mutex data_lock; + bool processing_cmd; + + /* Synchronous CMD */ + wait_queue_head_t wq; +}; + +struct se_sdf_msg { + u32 cmd; + u32 data_off; + u32 data_len; + u32 info[5]; +}; + +struct sdf_command_header { + int command; + union { + int param_cnt; + int ret; + } u; + int param_len[14]; +}; + +struct sdf_kernel_command { + struct sdf_command_header header; + void *handle; +}; + +#define KERNEL_COMMAND_SIZE (sizeof(struct sdf_kernel_command)) + +struct sdf_handle { + struct list_head handle_list; + void *handle; +}; + +struct sdf_file_pvt_data { + struct lsse_sdf_dev *se; + struct list_head handle_list; + struct sdf_kernel_command skc; + struct sdf_handle *ph; +}; + +static struct lsse_sdf_dev *se_sdf_dev; + +static void lsse_sdf_complete(struct lsse_ch *ch) +{ + struct lsse_sdf_dev *se = (struct lsse_sdf_dev *)ch->priv; + + se->processing_cmd = false; + wake_up(&se->wq); +} + +static int se_send_sdf_cmd(struct lsse_sdf_dev *se, int len, int retry) +{ + struct se_sdf_msg *smsg = (struct se_sdf_msg *)se->se_ch->smsg; + unsigned long flag; + int err; + + spin_lock_irqsave(&se->se_ch->ch_lock, flag); + + smsg->cmd = SE_CMD_SDF; + /* One time one cmd */ + smsg->data_off = se->se_ch->data_buffer - se->se_ch->se->mem_base; + smsg->data_len = len; + +try_again: + if (!retry--) + goto out; + + pr_debug("Send sdf cmd, last retry %d times\n", retry); + + err = se_send_ch_requeset(se->se_ch); + if (err) { + udelay(5); + goto try_again; + } + +out: + spin_unlock_irqrestore(&se->se_ch->ch_lock, flag); + + return err; +} + +static int lsse_sdf_recv(struct sdf_file_pvt_data *pvt, char *buf, + size_t size, int user, int *se_ret) +{ + int len, time, ret = 0; + struct se_sdf_msg *rmsg; + struct sdf_kernel_command *skc; + struct sdf_handle *ph; + struct lsse_sdf_dev *se = pvt->se; + + if (!se->se_ch->rmsg) { + pr_err("se device is not ready\n"); + return -EBUSY; + } + + time = wait_event_timeout(se->wq, !se->processing_cmd, HZ*30); + if (!time) + return -ETIME; + + rmsg = (struct se_sdf_msg *)se->se_ch->rmsg; + if (rmsg->cmd != SE_CMD_SDF) { + pr_err("se get wrong response\n"); + return -EIO; + } + len = rmsg->data_len; + + if ((!user && len > KERNEL_COMMAND_SIZE) || len > SE_SDF_BUFSIZE + || (size && len > size)) + return -E2BIG; + + if (user) { + ret = copy_to_user((char __user *)buf, + se->se_ch->data_buffer + rmsg->data_off, len); + if (!se_ret) + return ret; + + skc = (struct sdf_kernel_command *) + (se->se_ch->data_buffer + rmsg->data_off); + *se_ret = skc->header.u.ret; + if (skc->header.command == SDF_OPENSESSION && !*se_ret) { + ph = kmalloc(sizeof(*ph), GFP_KERNEL); + if (!ph) + return -ENOMEM; + ph->handle = skc->handle; + list_add(&ph->handle_list, &pvt->handle_list); + } + } else + memcpy(buf, se->se_ch->data_buffer + rmsg->data_off, len); + + return ret; +} + +static struct sdf_handle *find_sdf_handle(void *handle, + struct sdf_file_pvt_data *pvt) +{ + struct sdf_handle *ph; + + list_for_each_entry(ph, &pvt->handle_list, handle_list) { + if (ph->handle == handle) + return ph; + } + + return NULL; +} + +static int lsse_sdf_send(struct sdf_file_pvt_data *pvt, const char *buf, + size_t count, int user) +{ + int ret, se_ret; + struct sdf_handle *ph = NULL; + struct sdf_kernel_command *skc; + struct lsse_sdf_dev *se = pvt->se; + + if (!se->se_ch->smsg) { + pr_err("se device is not ready\n"); + return 0; + } + + if (count > se->se_ch->data_size) { + pr_err("Invalid size in send: count=%zd, size=%d\n", + count, se->se_ch->data_size); + return -EIO; + } + + if (user) { + ret = mutex_lock_interruptible(&se->data_lock); + if (ret) + goto out; + } else + mutex_lock(&se->data_lock); + + if (user) { + ret = copy_from_user(se->se_ch->data_buffer, buf, count); + if (ret) { + ret = -EFAULT; + goto out_unlock; + } + skc = (struct sdf_kernel_command *)se->se_ch->data_buffer; + if (skc->header.command == SDF_CLOSESESSION) + ph = find_sdf_handle(skc->handle, pvt); + } else + memcpy(se->se_ch->data_buffer, buf, count); + + se->processing_cmd = true; + ret = se_send_sdf_cmd(se, count, 5); + if (ret) { + pr_err("se_send_sdf_cmd failed\n"); + goto out_unlock; + } + + ret = lsse_sdf_recv(pvt, (char *)buf, 0, user, &se_ret); + if (ret) { + pr_err("recv failed ret: %x\n", ret); + goto out_unlock; + } + if (ph && !se_ret) { + list_del(&ph->handle_list); + kfree(ph); + } +out_unlock: + mutex_unlock(&se->data_lock); +out: + return ret; +} + +static ssize_t lsse_sdf_write(struct file *filp, const char __user *buf, + size_t cnt, loff_t *offt) +{ + struct sdf_file_pvt_data *pvt = filp->private_data; + + if (cnt > SE_SDF_BUFSIZE) + return -E2BIG; + + if (lsse_sdf_send(pvt, buf, cnt, 1)) + return -EFAULT; + + return cnt; +} + +static ssize_t lsse_sdf_read(struct file *filp, char __user *buf, + size_t size, loff_t *off) +{ + return lsse_sdf_recv(filp->private_data, buf, size, 1, NULL); +} + +static int close_one_handle(struct sdf_file_pvt_data *pvt, struct sdf_handle *ph) +{ + struct sdf_kernel_command *skc = &pvt->skc; + + skc->header.command = 0x205; + skc->header.u.param_cnt = 1; + skc->header.param_len[0] = 8; + skc->handle = ph->handle; + /* close one session */ + lsse_sdf_send(pvt, (char *)&pvt->skc, KERNEL_COMMAND_SIZE, 0); + if (skc->header.u.ret) { + pr_err("Auto Close Session failed, session handle: %llx, ret: %d\n", + (u64)ph->handle, skc->header.u.ret); + return skc->header.u.ret; + } + kfree(ph); + + return 0; +} + +static int close_all_handle(struct sdf_file_pvt_data *pvt) +{ + int ret = 0; + struct sdf_handle *ph, *tmp; + + list_for_each_entry_safe(ph, tmp, &pvt->handle_list, handle_list) { + list_del(&ph->handle_list); + ret = close_one_handle(pvt, ph); + if (ret) + return ret; + } + + return 0; +} + +static int lsse_sdf_release(struct inode *inode, struct file *filp) +{ + int ret; + struct sdf_file_pvt_data *pvt = filp->private_data; + + ret = close_all_handle(pvt); + filp->private_data = NULL; + kfree(pvt); + + if (ret) + ret = -EFAULT; + return ret; +} + +static int lsse_sdf_open(struct inode *inode, struct file *filp) +{ + struct sdf_file_pvt_data *pvt = kmalloc(sizeof(*pvt), GFP_KERNEL); + + if (!pvt) + return -ENOMEM; + + INIT_LIST_HEAD(&pvt->handle_list); + pvt->se = se_sdf_dev; + filp->private_data = pvt; + + return 0; +} + +static const struct file_operations lsse_sdf_fops = { + .owner = THIS_MODULE, + .open = lsse_sdf_open, + .write = lsse_sdf_write, + .read = lsse_sdf_read, + .release = lsse_sdf_release, +}; + +static const struct miscdevice lsse_sdf_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "lsse_sdf", + .fops = &lsse_sdf_fops, +}; + +static int lsse_sdf_probe(struct platform_device *pdev) +{ + int msg_size; + int ret; + + se_sdf_dev = kzalloc(sizeof(*se_sdf_dev), GFP_KERNEL); + if (IS_ERR_OR_NULL(se_sdf_dev)) + return PTR_ERR(se_sdf_dev); + + mutex_init(&se_sdf_dev->data_lock); + init_waitqueue_head(&se_sdf_dev->wq); + se_sdf_dev->processing_cmd = false; + + msg_size = 2 * sizeof(struct se_sdf_msg); + se_sdf_dev->se_ch = se_init_ch(SE_CH_SDF, SE_SDF_BUFSIZE, msg_size, + se_sdf_dev, lsse_sdf_complete); + + ret = misc_register(&lsse_sdf_miscdev); + if (ret < 0) { + pr_err("register sdf dev failed!\n"); + goto out; + } + + return 0; + +out: + kfree(se_sdf_dev); + + return ret; +} + +static int lsse_sdf_remove(struct platform_device *pdev) +{ + misc_deregister(&lsse_sdf_miscdev); + se_deinit_ch(se_sdf_dev->se_ch); + kfree(se_sdf_dev); + + return 0; +} + +static struct platform_driver loongson_sdf_driver = { + .probe = lsse_sdf_probe, + .remove = lsse_sdf_remove, + .driver = { + .name = "loongson-sdf", + }, +}; +module_platform_driver(loongson_sdf_driver); + +MODULE_ALIAS("platform:loongson-sdf"); +MODULE_AUTHOR("Yinggang Gu"); +MODULE_DESCRIPTION("Loongson SE sdf driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/phytnetled/Kconfig b/drivers/char/phytnetled/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..26691906dbf4db150ccd0a133dee4c1c1ec4872b --- /dev/null +++ b/drivers/char/phytnetled/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# PCMCIA character device configuration +# +config PHYTNET_LED + tristate "Phytium mac led control module" + depends on PHYTMAC + depends on GPIO_PHYTIUM_PLAT + default m + help + If you have a network (Ethernet) controller of this type and + want to use it control port led say Y or M here. + diff --git a/drivers/char/phytnetled/Makefile b/drivers/char/phytnetled/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..781bc90c1b9c30d7f909b8fad54cd88eb2a01894 --- /dev/null +++ b/drivers/char/phytnetled/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_PHYTNET_LED) += phytnet_led.o \ No newline at end of file diff --git a/drivers/char/phytnetled/phytnet_led.c b/drivers/char/phytnetled/phytnet_led.c new file mode 100644 index 0000000000000000000000000000000000000000..e1cc79688eb0366761c2dcb89230b2d7eca64a9c --- /dev/null +++ b/drivers/char/phytnetled/phytnet_led.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022-2023 Phytium Technology Co.,Ltd. + * + */ +#include +#include +#include +#include +#include +#include +#include "phytnet_led.h" + +#define DRIVER_NAME "phytnet_led" +#define DRIVER_VERSION "1.0" +#define DRIVER_AUTHOR "LongShixiang " +#define DRIVER_DESC "net device led control module" +#define NET_DEV_PROPNAME "net_dev" +#define LED_OF_NAME "led" +#define CHECK_INTERVAL 125 /* Unit: ms */ +#define NDEV_CHECK_DELAY 30000 /* Unit: 30s */ +#define LED_ON 1 +#define LED_OFF 0 +#define LINK_OFFSET 0 +#define ACT_OFFSET 1 + +#if defined(CONFIG_OF) +static const struct of_device_id phytnet_led_of_ids[] = { + { .compatible = "phytium,net_led"}, + {} +}; + +MODULE_DEVICE_TABLE(of, phytnet_led_of_ids); +#endif /* CONFIG_OF */ + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytnet_acpi_ids[] = { + { .id = "PHYT800C"}, + {} +}; +MODULE_DEVICE_TABLE(acpi, phytnet_acpi_ids); +#else +#define phytnet_acpi_ids NULL +#endif /* CONFIG_ACPI */ + +static void +led_on(struct gpio_desc *gd) +{ + gpiod_set_value(gd, LED_ON); +} + +static void +led_off(struct gpio_desc *gd) +{ + gpiod_set_value(gd, LED_OFF); +} + +static void +led_blink(struct led_data *phytnet_led) +{ + phytnet_led->act_val = !phytnet_led->act_val; + gpiod_set_value(phytnet_led->act, phytnet_led->act_val); +} + +static int +port_is_linkup(struct led_data *phytnet_led) +{ + if (netif_carrier_ok(phytnet_led->ndev)) + return true; + else + return false; +} + +static bool +port_is_act(struct led_data *phytnet_led) +{ + bool ret = false; + + if (phytnet_led->ndev_rx != phytnet_led->ndev->stats.rx_packets) { + phytnet_led->ndev_rx = phytnet_led->ndev->stats.rx_packets; + ret = true; + } + + if (phytnet_led->ndev_tx != phytnet_led->ndev->stats.tx_packets) { + phytnet_led->ndev_tx = phytnet_led->ndev->stats.tx_packets; + ret = true; + } + + return ret; +} + +static void +led_control(struct led_data *phytnet_led) +{ + while (!phytnet_led->led_stop) { + msleep(CHECK_INTERVAL); + + if (!netif_running(phytnet_led->ndev)) { + led_off(phytnet_led->link); + led_off(phytnet_led->act); + continue; + } + + if (port_is_linkup(phytnet_led)) + led_on(phytnet_led->link); + else + led_off(phytnet_led->link); + + if (port_is_act(phytnet_led)) + led_blink(phytnet_led); + else + led_off(phytnet_led->act); + } +} + +static int +of_ndev_init(struct led_data *phytnet_led) +{ + struct device_node *net_node; + + net_node = of_parse_phandle(phytnet_led->pdev->dev.of_node, NET_DEV_PROPNAME, 0); + if (!net_node) { + dev_err(&phytnet_led->pdev->dev, "Failed to get netdev ofnode from device tree\n"); + return -ENODEV; + } + + phytnet_led->ndev = of_find_net_device_by_node(net_node); + + if (!phytnet_led->ndev) { + dev_err(&phytnet_led->pdev->dev, "Failed to get acpi ndev\n"); + return -ENODEV; + } + + dev_info(&phytnet_led->pdev->dev, "Successfully get ndev...\n"); + dev_hold(phytnet_led->ndev); + + return 0; +} + + +static int +acpi_ndev_init(struct led_data *phytnet_led) +{ + int err; + struct net_device *find_ndev; + const char *ndev_acpi_path; + acpi_handle net_handler; + struct acpi_device *adev; + acpi_status status; + struct device *find_dev; + + err = device_property_read_string(&phytnet_led->pdev->dev, + NET_DEV_PROPNAME, + &ndev_acpi_path); + if (err) { + dev_err(&phytnet_led->pdev->dev, "Failed to read net_dev property!\n"); + return -ENODEV; + } + + status = acpi_get_handle(NULL, (acpi_string)ndev_acpi_path, &net_handler); + if (ACPI_FAILURE(status)) { + dev_err(&phytnet_led->pdev->dev, "Failed to get acpi handler path: %s\n", + ndev_acpi_path); + return -ENODEV; + } + + adev = acpi_get_acpi_dev(net_handler); + if (!err) { + dev_err(&phytnet_led->pdev->dev, "Failed to get adev dev\n"); + return -ENODEV; + } + + for_each_netdev(&init_net, find_ndev) { + if (find_ndev->dev.parent != NULL) { + find_dev = find_ndev->dev.parent; + if (&adev->fwnode == find_dev->fwnode) + phytnet_led->ndev = find_ndev; + } + } + + if (!phytnet_led->ndev) { + dev_err(&phytnet_led->pdev->dev, "Failed to get acpi ndev\n"); + return -ENODEV; + } + + dev_info(&phytnet_led->pdev->dev, "Successfully get ndev...\n"); + dev_hold(phytnet_led->ndev); + + return 0; +} + +static int +gpio_init(struct led_data *phytnet_led) +{ + int err; + + phytnet_led->link = gpiod_get_index(&phytnet_led->pdev->dev, + LED_OF_NAME, + LINK_OFFSET, + GPIOD_OUT_HIGH); + if (IS_ERR(phytnet_led->link)) { + dev_err(&phytnet_led->pdev->dev, "Failed to get link gpio, err: %ld\n", + PTR_ERR(phytnet_led->link)); + return PTR_ERR(phytnet_led->link); + } + + err = gpiod_direction_output(phytnet_led->link, LED_OFF); + if (err) { + dev_err(&phytnet_led->pdev->dev, "Failed to set link dir, err: %ld\n", + PTR_ERR(phytnet_led->link)); + return err; + } + + phytnet_led->act = gpiod_get_index(&phytnet_led->pdev->dev, + LED_OF_NAME, + ACT_OFFSET, + GPIOD_OUT_HIGH); + if (IS_ERR(phytnet_led->act)) { + dev_err(&phytnet_led->pdev->dev, "Failed to get act gpio, err:%d\n", err); + return PTR_ERR(phytnet_led->act); + } + + err = gpiod_direction_output(phytnet_led->act, LED_OFF); + if (err) { + dev_err(&phytnet_led->pdev->dev, "Failed to set act dir, err: %d\n", err); + return err; + } + + return 0; +} + +static void +led_init_and_control(struct work_struct *work) +{ + int err = -1; + struct led_data *phytnet_led = container_of(work, struct led_data, led_control_work.work); + + if (phytnet_led->pdev->dev.of_node) + err = of_ndev_init(phytnet_led); + else if (has_acpi_companion(&phytnet_led->pdev->dev)) + err = acpi_ndev_init(phytnet_led); + + if (err) { + dev_err(&phytnet_led->pdev->dev, "ndev init wrong\n"); + return; + } + + err = gpio_init(phytnet_led); + if (err) { + dev_err(&phytnet_led->pdev->dev, "gpio init wrong\n"); + return; + } + + led_control(phytnet_led); +} + +static int +net_led_probe(struct platform_device *pdev) +{ + struct led_data *phytnet_led = devm_kzalloc(&pdev->dev, + sizeof(struct led_data), + GFP_KERNEL); + + if (!phytnet_led) + return -ENOMEM; + + platform_set_drvdata(pdev, phytnet_led); + + phytnet_led->act = LED_OFF; + phytnet_led->pdev = pdev; + phytnet_led->led_stop = 0; + + INIT_DELAYED_WORK(&phytnet_led->led_control_work, led_init_and_control); + schedule_delayed_work(&phytnet_led->led_control_work, msecs_to_jiffies(NDEV_CHECK_DELAY)); + + return 0; +} + +static int +net_led_remove(struct platform_device *pdev) +{ + struct led_data *phytnet_led = platform_get_drvdata(pdev); + + phytnet_led->led_stop = 1; + cancel_delayed_work_sync(&phytnet_led->led_control_work); + + if (phytnet_led->ndev) + dev_put(phytnet_led->ndev); + + if (phytnet_led->link) { + led_off(phytnet_led->link); + gpiod_put(phytnet_led->link); + } + + if (phytnet_led->act) { + led_off(phytnet_led->act); + gpiod_put(phytnet_led->act); + } + + if (&pdev->dev) + devm_kfree(&pdev->dev, phytnet_led); + + return 0; +} + +static struct platform_driver net_led_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(phytnet_led_of_ids), + .acpi_match_table = ACPI_PTR(phytnet_acpi_ids), + }, + .probe = net_led_probe, + .remove = net_led_remove, +}; + +module_platform_driver(net_led_driver); + +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/char/phytnetled/phytnet_led.h b/drivers/char/phytnetled/phytnet_led.h new file mode 100644 index 0000000000000000000000000000000000000000..8f2fbadde0f8b0ceb336a45d883da5c4580c09e4 --- /dev/null +++ b/drivers/char/phytnetled/phytnet_led.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2022-2023 Phytium Technology Co.,Ltd. + * + */ +struct led_data { + struct platform_device *pdev; + struct net_device *ndev; + unsigned long ndev_rx, ndev_tx; + struct gpio_desc *link, *act; + struct delayed_work led_control_work; + int led_stop; + int act_val; +}; diff --git a/drivers/cpufreq/loongson3-acpi-cpufreq.c b/drivers/cpufreq/loongson3-acpi-cpufreq.c index 018b529a0cf91a83b6ce9c78321c4bbde69c2877..67e48763e3f1d899b4a28279d802c22a0e1e6e4a 100644 --- a/drivers/cpufreq/loongson3-acpi-cpufreq.c +++ b/drivers/cpufreq/loongson3-acpi-cpufreq.c @@ -1241,6 +1241,8 @@ static int loongson3_cpufreq_cpu_init(struct cpufreq_policy *policy) if (has_boost_freq() && boost_supported()) loongson3_cpufreq_attr[1] = &cpufreq_freq_attr_scaling_boost_freqs; + policy->cur = core->normal_max_freq * 1000; + pr_info("CPU%u - ACPI performance management activated.\n", cpu); for (i = 0; i < perf->state_count; i++) pr_debug(" %cP%d: %d MHz, %d mW, %d uS %d level\n", diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index e36506471a4f671fc12d6474c2a56096a35c07fd..253d3d58ebd659e4aa20e1a8d88074b52a3d8018 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -733,6 +733,14 @@ config XILINX_ZYNQMP_DPDMA driver provides the dmaengine required by the DisplayPort subsystem display driver. +config PHYTIUM_DDMA + bool "Phytium PE220x DDMA support" + depends on (ARCH_PHYTIUM || COMPILE_TEST) + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + help + Enable support for Phytium PE220x DDMA controller. + # driver files source "drivers/dma/bestcomm/Kconfig" diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 83553a97a010e157b285ad6c47557df31c8bc835..a1b72ed958919c5c522e138e27be7089c26859ee 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -87,3 +87,4 @@ obj-y += mediatek/ obj-y += qcom/ obj-y += ti/ obj-y += xilinx/ +obj-y += phytium/ diff --git a/drivers/dma/phytium/Makefile b/drivers/dma/phytium/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..71ba9b9fcd4062f7841be505dde68dde679288e0 --- /dev/null +++ b/drivers/dma/phytium/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_PHYTIUM_DDMA) += phytium-ddmac.o diff --git a/drivers/dma/phytium/phytium-ddmac.c b/drivers/dma/phytium/phytium-ddmac.c new file mode 100644 index 0000000000000000000000000000000000000000..62bfbeb9c4778269a13ccc39fde3c89fffc52a2a --- /dev/null +++ b/drivers/dma/phytium/phytium-ddmac.c @@ -0,0 +1,960 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium Device DDMA Controller driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phytium-ddmac.h" + + +static inline struct phytium_ddma_device *to_ddma_device + (struct dma_chan *chan) +{ + return container_of(chan->device, + struct phytium_ddma_device, dma_dev); +} + +static inline struct phytium_ddma_chan *to_ddma_chan + (struct dma_chan *chan) +{ + return container_of(chan, + struct phytium_ddma_chan, vchan.chan); +} + +static inline struct phytium_ddma_desc *to_ddma_desc + (struct virt_dma_desc *vd) +{ + return container_of(vd, + struct phytium_ddma_desc, vdesc); +} + +static inline struct device *chan_to_dev + (struct phytium_ddma_chan *chan) +{ + return chan->vchan.chan.device->dev; +} + +static inline struct phytium_ddma_device *chan_to_ddma + (struct phytium_ddma_chan *chan) +{ + return to_ddma_device(&chan->vchan.chan); +} + +static inline void phytium_ddma_iowrite32 + (const struct phytium_ddma_device *ddma, + const u32 reg, const u32 val) +{ + iowrite32(val, ddma->base + reg); +} + +static inline u32 phytium_ddma_ioread32 + (const struct phytium_ddma_device *ddma, + const u32 reg) +{ + return ioread32(ddma->base + reg); +} + +static inline void phytium_chan_iowrite32 + (const struct phytium_ddma_chan *chan, + const u32 reg, const u32 val) +{ + iowrite32(val, chan->base + reg); +} + +static inline u32 phytium_chan_ioread32 + (const struct phytium_ddma_chan *chan, + const u32 reg) +{ + return ioread32(chan->base + reg); +} + +static void phytium_ddma_disable + (const struct phytium_ddma_device *ddma) +{ + dev_dbg(ddma->dev, "ddma disable\n"); + phytium_ddma_iowrite32(ddma, DMA_CTL, !DMA_CTL_EN); +} + +static void phytium_ddma_enable + (const struct phytium_ddma_device *ddma) +{ + dev_dbg(ddma->dev, "ddma enable\n"); + phytium_ddma_iowrite32(ddma, DMA_CTL, DMA_CTL_EN); +} + +static void phytium_ddma_reset + (const struct phytium_ddma_device *ddma) +{ + u32 val = 0; + + dev_dbg(ddma->dev, "dma reset\n"); + val = phytium_ddma_ioread32(ddma, DMA_CTL); + val |= DMA_CTL_SRST; + phytium_ddma_iowrite32(ddma, DMA_CTL, val); + + udelay(10); + val &= ~DMA_CTL_SRST; + phytium_ddma_iowrite32(ddma, DMA_CTL, val); +} + +static void phytium_ddma_irq_disable + (const struct phytium_ddma_device *ddma) +{ + u32 val = 0; + + dev_dbg(ddma->dev, "ddma irq disable\n"); + val = phytium_ddma_ioread32(ddma, DMA_MASK_INT); + val |= DMA_INT_EN; + phytium_ddma_iowrite32(ddma, DMA_MASK_INT, val); +} + +static void phytium_ddma_irq_enable + (const struct phytium_ddma_device *ddma) +{ + u32 val = 0; + + dev_dbg(ddma->dev, "ddma irq enable\n"); + val = phytium_ddma_ioread32(ddma, DMA_MASK_INT); + val &= ~DMA_INT_EN; + phytium_ddma_iowrite32(ddma, DMA_MASK_INT, val); +} + +static u32 phytium_ddma_irq_read + (const struct phytium_ddma_device *ddma) +{ + u32 val = 0; + + val = phytium_ddma_ioread32(ddma, DMA_STAT); + + return val; +} + +static void phytium_chan_irq_disable + (struct phytium_ddma_chan *chan) +{ + u32 val = 0; + + dev_dbg(chan_to_dev(chan), "channel %d irq disable\n", chan->id); + val = phytium_ddma_ioread32(chan_to_ddma(chan), DMA_MASK_INT); + val |= DMA_INT_CHAL_EN(chan->id); + phytium_ddma_iowrite32(chan_to_ddma(chan), DMA_MASK_INT, val); +} + +static void phytium_chan_irq_enable(struct phytium_ddma_chan *chan) +{ + u32 val = 0; + + dev_dbg(chan_to_dev(chan), "channel %d irq enable\n", chan->id); + val = phytium_ddma_ioread32(chan_to_ddma(chan), DMA_MASK_INT); + val &= ~DMA_INT_CHAL_EN(chan->id); + phytium_ddma_iowrite32(chan_to_ddma(chan), DMA_MASK_INT, val); +} + +static void phytium_chan_irq_clear(struct phytium_ddma_chan *chan) +{ + u32 val = 0; + + dev_dbg(chan_to_dev(chan), "channel %d irq clear\n", chan->id); + val = DMA_STAT_CHAL(chan->id); + phytium_ddma_iowrite32(chan_to_ddma(chan), DMA_STAT, val); +} + +static int phytium_chan_disable(struct phytium_ddma_chan *chan) +{ + u32 val = 0; + int ret = 0; + u32 value1; + + dev_dbg(chan_to_dev(chan), "channel %d disable\n", chan->id); + val = phytium_chan_ioread32(chan, DMA_CHALX_CTL); + value1 = val |= DMA_CHAL_EN; + if (value1) { + val &= ~DMA_CHAL_EN; + phytium_chan_iowrite32(chan, DMA_CHALX_CTL, val); + + ret = readl_relaxed_poll_timeout_atomic + (chan->base + DMA_CHALX_CTL, val, + !(val & DMA_CHAL_EN), 0, 100000); + } + return ret; +} + +static void phytium_chan_enable(struct phytium_ddma_chan *chan) +{ + u32 val = 0; + + dev_dbg(chan_to_dev(chan), "channel %d enable\n", chan->id); + val = phytium_chan_ioread32(chan, DMA_CHALX_CTL); + val |= DMA_CHAL_EN; + phytium_chan_iowrite32(chan, DMA_CHALX_CTL, val); +} + +static bool phytium_chan_is_running(const struct phytium_ddma_chan *chan) +{ + u32 val; + + val = phytium_chan_ioread32(chan, DMA_CHALX_CTL); + + if (val & DMA_CHAL_EN) + return true; + else + return false; +} + +static void phytium_chan_reset(struct phytium_ddma_chan *chan) +{ + u32 val = 0; + + dev_dbg(chan_to_dev(chan), "channel %d reset\n", chan->id); + val = phytium_chan_ioread32(chan, DMA_CHALX_CTL); + val |= DMA_CHAL_SRST; + phytium_chan_iowrite32(chan, DMA_CHALX_CTL, val); + + udelay(10); + val &= ~DMA_CHAL_SRST; + phytium_chan_iowrite32(chan, DMA_CHALX_CTL, val); +} + +static void phytium_ddma_vdesc_free(struct virt_dma_desc *vd) +{ + kfree(to_ddma_desc(vd)); +} + +static int phytium_chan_pause(struct dma_chan *chan) +{ + struct phytium_ddma_chan *pchan = to_ddma_chan(chan); + int ret = 0; + + ret = phytium_chan_disable(pchan); + pchan->busy = false; + pchan->is_pasued = true; + + return ret; +} + +static int phytium_chan_resume(struct dma_chan *chan) +{ + struct phytium_ddma_chan *pchan = to_ddma_chan(chan); + + phytium_chan_enable(pchan); + pchan->is_pasued = false; + + return 0; +} + +static void phytium_chan_start_xfer(struct phytium_ddma_chan *chan) +{ + struct virt_dma_desc *vdesc = NULL; + struct phytium_ddma_sg_req *sg_req = NULL; + char *tmp = NULL; + int i = 0; + unsigned long flags = 0; + + /* chan first xfer settings */ + if (!chan->desc) { + vdesc = vchan_next_desc(&chan->vchan); + if (!vdesc) + return; + + list_del(&vdesc->node); + chan->desc = to_ddma_desc(vdesc); + chan->next_sg = 0; + chan->current_sg = NULL; + dev_dbg(chan_to_dev(chan), "xfer start\n"); + } + + if (chan->next_sg == chan->desc->num_sgs) + chan->next_sg = 0; + + sg_req = &chan->desc->sg_req[chan->next_sg]; + chan->current_sg = sg_req; + /* fill to 4 bytes */ + switch (sg_req->direction) { + case DMA_MEM_TO_DEV: + tmp = phys_to_virt(sg_req->mem_addr_l); + memset(chan->buf, 0, sg_req->len * 4); + for (i = 0; i < sg_req->len; i++) + chan->buf[i * 4] = tmp[i]; + break; + + case DMA_DEV_TO_MEM: + memset(chan->buf, 0, sg_req->len * 4); + break; + + default: + break; + } + + /* start transfer */ + phytium_chan_iowrite32(chan, DMA_CHALX_DDR_LWADDR, + chan->paddr & 0xFFFFFFFF); + phytium_chan_iowrite32(chan, DMA_CHALX_DDR_UPADDR, + (chan->paddr >> 32) & 0xFFFFFFFF); + phytium_chan_iowrite32(chan, DMA_CHALX_DEV_ADDR, sg_req->dev_addr); + phytium_chan_iowrite32(chan, DMA_CHALX_TS, sg_req->len * 4); + + spin_lock_irqsave(&chan_to_ddma(chan)->lock, flags); + phytium_chan_irq_enable(chan); + spin_unlock_irqrestore(&chan_to_ddma(chan)->lock, flags); + phytium_chan_enable(chan); + + chan->next_sg++; + chan->busy = true; +} + +static void phytium_chan_xfer_done(struct phytium_ddma_chan *chan) +{ + struct phytium_ddma_sg_req *sg_req = chan->current_sg; + char *tmp = NULL; + int i = 0; + + if (chan->desc) { + if (sg_req->direction == DMA_DEV_TO_MEM) { + tmp = phys_to_virt(sg_req->mem_addr_l); + for (i = 0; i < sg_req->len; i++) + tmp[i] = chan->buf[i * 4]; + } + + chan->busy = false; + if (chan->next_sg == chan->desc->num_sgs) { + dev_dbg(chan_to_dev(chan), "xfer complete\n"); + vchan_cookie_complete(&chan->desc->vdesc); + chan->desc = NULL; + chan->current_sg = NULL; + } + phytium_chan_disable(chan); + phytium_chan_irq_clear(chan); + phytium_chan_start_xfer(chan); + } +} + +static void phytium_dma_hw_init(struct phytium_ddma_device *ddma) +{ + u32 i = 0; + int ret = 0; + + phytium_ddma_disable(ddma); + phytium_ddma_reset(ddma); + phytium_ddma_irq_enable(ddma); + phytium_ddma_enable(ddma); + + for (i = 0; i < ddma->dma_channels; i++) { + phytium_chan_irq_disable(&ddma->chan[i]); + ret = phytium_chan_disable(&ddma->chan[i]); + if (ret) + dev_err(ddma->dev, "can't disable channel %d\n", i); + + } +} + +static size_t phytium_ddma_desc_residue(struct phytium_ddma_chan *chan) +{ + u32 trans_cnt = 0; + u32 residue = 0; + int i = 0; + + trans_cnt = phytium_chan_ioread32(chan, DMA_CHALX_TRANS_CNT); + residue = chan->current_sg->len - trans_cnt; + + for (i = chan->next_sg; i < chan->desc->num_sgs; i++) + residue += chan->desc->sg_req[i].len; + + return residue; +} + +static enum dma_status phytium_ddma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, + struct dma_tx_state *txstate) +{ + struct phytium_ddma_chan *pchan = to_ddma_chan(chan); + struct virt_dma_desc *vd = NULL; + enum dma_status ret = 0; + unsigned long flags = 0; + size_t residue = 0; + + ret = dma_cookie_status(chan, cookie, txstate); + if ((ret == DMA_COMPLETE) || !txstate) + return ret; + + spin_lock_irqsave(&pchan->vchan.lock, flags); + vd = vchan_find_desc(&pchan->vchan, cookie); + if (pchan->desc && cookie == pchan->desc->vdesc.tx.cookie) + residue = phytium_ddma_desc_residue(pchan); + + dma_set_residue(txstate, residue); + spin_unlock_irqrestore(&pchan->vchan.lock, flags); + + if (pchan->is_pasued && ret == DMA_IN_PROGRESS) + ret = DMA_PAUSED; + + return ret; +} + +static void phytium_ddma_issue_pending(struct dma_chan *chan) +{ + struct phytium_ddma_chan *pchan = to_ddma_chan(chan); + unsigned long flags = 0; + + spin_lock_irqsave(&pchan->vchan.lock, flags); + + if (vchan_issue_pending(&pchan->vchan) && !pchan->desc && !pchan->busy) + phytium_chan_start_xfer(pchan); + + spin_unlock_irqrestore(&pchan->vchan.lock, flags); +} + +static int phytium_ddma_terminate_all(struct dma_chan *chan) +{ + struct phytium_ddma_chan *pchan = to_ddma_chan(chan); + unsigned long flags = 0; + LIST_HEAD(head); + + spin_lock_irqsave(&pchan->vchan.lock, flags); + if (pchan->desc) { + vchan_terminate_vdesc(&pchan->desc->vdesc); + if (pchan->busy) { + u32 tmp_ctl, timeout; + + phytium_chan_disable(pchan); + /* save some registers, reset will clear it */ + timeout = phytium_chan_ioread32(pchan, + DMA_CHALX_TIMEOUT_CNT); + tmp_ctl = phytium_chan_ioread32(pchan, + DMA_CHALX_CTL); + spin_lock(&chan_to_ddma(pchan)->lock); + phytium_chan_irq_disable(pchan); + spin_unlock(&chan_to_ddma(pchan)->lock); + /* need reset when terminate */ + phytium_chan_reset(pchan); + phytium_chan_irq_clear(pchan); + /* recover it */ + phytium_chan_iowrite32(pchan, + DMA_CHALX_CTL, tmp_ctl); + phytium_chan_iowrite32(pchan, + DMA_CHALX_TIMEOUT_CNT, timeout); + pchan->busy = false; + } + pchan->desc = NULL; + } + + vchan_get_all_descriptors(&pchan->vchan, &head); + spin_unlock_irqrestore(&pchan->vchan.lock, flags); + vchan_dma_desc_free_list(&pchan->vchan, &head); + + return 0; +} + +static int phytium_ddma_alloc_chan_resources(struct dma_chan *chan) +{ + struct phytium_ddma_device *ddma = to_ddma_device(chan); + struct phytium_ddma_chan *pchan = to_ddma_chan(chan); + u32 bind_status = 0; + int ret = 0; + unsigned long flags = 0; + + bind_status = phytium_ddma_ioread32(ddma, DMA_CHAL_BIND); + + if ((pchan->is_used) || (bind_status & BIT(pchan->id))) { + dev_err(ddma->dev, "channel %d already used\n", pchan->id); + ret = -EBUSY; + goto out; + } + + /* prepare channel */ + ret = phytium_chan_disable(pchan); + if (ret) { + dev_err(ddma->dev, "can't disable channel %d\n", pchan->id); + goto out; + } + phytium_chan_reset(pchan); + phytium_chan_irq_clear(pchan); + + /* channel bind */ + spin_lock_irqsave(&chan_to_ddma(pchan)->lock, flags); + bind_status |= BIT(pchan->id); + phytium_ddma_iowrite32(ddma, DMA_CHAL_BIND, bind_status); + pchan->is_used = true; + spin_unlock_irqrestore(&chan_to_ddma(pchan)->lock, flags); + + /* alloc dma memory */ + pchan->buf = dma_alloc_coherent(ddma->dev, 4 * PAGE_SIZE, + &pchan->paddr, GFP_KERNEL); + if (!pchan->buf) { + ret = -EBUSY; + dev_err(ddma->dev, "failed to alloc dma memory\n"); + } + + dev_info(ddma->dev, "alloc channel %d\n", pchan->id); + +out: + return ret; +} + +static void phytium_ddma_free_chan_resources(struct dma_chan *chan) +{ + struct phytium_ddma_device *ddma = to_ddma_device(chan); + struct phytium_ddma_chan *pchan = to_ddma_chan(chan); + u32 bind_status = 0; + unsigned long flags = 0; + + if (!pchan->is_used) + return; + + dev_dbg(ddma->dev, "free channel %d\n", pchan->id); + spin_lock_irqsave(&chan_to_ddma(pchan)->lock, flags); + bind_status = phytium_ddma_ioread32(ddma, DMA_CHAL_BIND); + bind_status &= ~BIT(pchan->id); + phytium_ddma_iowrite32(ddma, DMA_CHAL_BIND, bind_status); + spin_unlock_irqrestore(&chan_to_ddma(pchan)->lock, flags); + + phytium_chan_disable(pchan); + + spin_lock_irqsave(&chan_to_ddma(pchan)->lock, flags); + phytium_chan_irq_disable(pchan); + spin_unlock_irqrestore(&chan_to_ddma(pchan)->lock, flags); + + vchan_free_chan_resources(to_virt_chan(chan)); + pchan->is_used = false; + + if (pchan->buf) + dma_free_coherent(ddma->dev, 4 * PAGE_SIZE, + pchan->buf, pchan->paddr); +} + +static int phytium_ddma_slave_config(struct dma_chan *chan, + struct dma_slave_config *config) +{ + struct phytium_ddma_chan *pchan = to_ddma_chan(chan); + u32 chal_cfg = 0; + u32 req_mode = 0; + const u32 timeout = 0xffff; + unsigned long flag = 0; + + /* Check if chan will be configured for slave transfers */ + if (!is_slave_direction(config->direction)) + return -EINVAL; + + memcpy(&pchan->dma_config, config, sizeof(*config)); + + /* set channel config reg */ + spin_lock_irqsave(&chan_to_ddma(pchan)->lock, flag); + if (pchan->id > 3) { + chal_cfg = phytium_ddma_ioread32(chan_to_ddma(pchan), + DMA_CHAL_CFG_H); + chal_cfg &= ~(0xFF << ((pchan->id - 4) * 8)); + chal_cfg |= DMA_CHAL_SEL((pchan->id - 4), pchan->request_line); + chal_cfg |= DMA_CHAL_SEL_EN(pchan->id - 4); + phytium_ddma_iowrite32(chan_to_ddma(pchan), + DMA_CHAL_CFG_H, chal_cfg); + } else { + chal_cfg = phytium_ddma_ioread32(chan_to_ddma(pchan), + DMA_CHAL_CFG_L); + chal_cfg &= ~(0xFF << (pchan->id * 8)); + chal_cfg |= DMA_CHAL_SEL((pchan->id), pchan->request_line); + chal_cfg |= DMA_CHAL_SEL_EN(pchan->id); + phytium_ddma_iowrite32(chan_to_ddma(pchan), + DMA_CHAL_CFG_L, chal_cfg); + } + spin_unlock_irqrestore(&chan_to_ddma(pchan)->lock, flag); + + /* set channel mode */ + req_mode = + (config->direction == DMA_DEV_TO_MEM) ? DMA_RX_REQ : DMA_TX_REQ; + phytium_chan_iowrite32(pchan, DMA_CHALX_CTL, req_mode << 2); + + /* set channel timeout */ + phytium_chan_iowrite32(pchan, DMA_CHALX_TIMEOUT_CNT, + timeout | DMA_CHAL_TIMEOUT_EN); + + return 0; +} + +static struct dma_async_tx_descriptor *phytium_ddma_prep_slave_sg( + struct dma_chan *chan, struct scatterlist *sgl, + u32 sg_len, enum dma_transfer_direction direction, + unsigned long flags, void *context) +{ + struct phytium_ddma_device *ddma = to_ddma_device(chan); + struct phytium_ddma_chan *pchan = to_ddma_chan(chan); + struct dma_slave_config *sconfig = &pchan->dma_config; + struct phytium_ddma_desc *desc = NULL; + struct scatterlist *sg = NULL; + int i = 0; + char *tmp; + + if (unlikely(!is_slave_direction(direction))) { + dev_err(ddma->dev, "invalid dma direction\n"); + return NULL; + } + + if (unlikely(sg_len < 1)) { + dev_err(ddma->dev, "invalid segment length: %d\n", sg_len); + return NULL; + } + + desc = kzalloc(struct_size(desc, sg_req, sg_len), GFP_NOWAIT); + if (!desc) + return NULL; + + /* set sg list */ + for_each_sg(sgl, sg, sg_len, i) { + tmp = phys_to_virt(sg_dma_address(sg)); + desc->sg_req[i].direction = direction; + + switch (direction) { + case DMA_MEM_TO_DEV: + desc->sg_req[i].len = sg_dma_len(sg); + desc->sg_req[i].mem_addr_l = sg_dma_address(sg) & 0xFFFFFFFF; + desc->sg_req[i].mem_addr_h = + (sg_dma_address(sg) >> 32) & 0xFFFFFFFF; + desc->sg_req[i].dev_addr = sconfig->dst_addr & 0xFFFFFFFF; + break; + + case DMA_DEV_TO_MEM: + desc->sg_req[i].len = sg_dma_len(sg); + desc->sg_req[i].mem_addr_l = sg_dma_address(sg) & 0xFFFFFFFF; + desc->sg_req[i].mem_addr_h = + (sg_dma_address(sg) >> 32) & 0xFFFFFFFF; + desc->sg_req[i].dev_addr = sconfig->src_addr & 0xFFFFFFFF; + break; + + default: + return NULL; + } + } + + desc->num_sgs = sg_len; + + return vchan_tx_prep(&pchan->vchan, &desc->vdesc, flags); +} + +static irqreturn_t phytium_dma_interrupt(int irq, void *dev_id) +{ + struct phytium_ddma_device *ddma = dev_id; + struct phytium_ddma_chan *chan; + u32 irq_status = 0; + u32 i = 0; + u32 val = 0; + + phytium_ddma_irq_disable(ddma); + + irq_status = phytium_ddma_irq_read(ddma); + val = phytium_ddma_ioread32(ddma, DMA_CTL); + + /* Poll, clear and process every chanel interrupt status */ + for (i = 0; i < ddma->dma_channels; i++) { + if (!(irq_status & BIT(i * 4))) + continue; + + chan = &ddma->chan[i]; + phytium_chan_xfer_done(chan); + } + + phytium_ddma_irq_enable(ddma); + + return IRQ_HANDLED; +} + + +static struct dma_chan *phytium_ddma_of_xlate( + struct of_phandle_args *dma_spec, + struct of_dma *ofdma) +{ + struct phytium_ddma_device *ddma = ofdma->of_dma_data; + struct device *dev = ddma->dev; + struct phytium_ddma_chan *chan = NULL; + struct dma_chan *c = NULL; + u32 channel_id = 0; + + channel_id = dma_spec->args[0]; + + if (channel_id > ddma->dma_channels) { + dev_err(dev, "bad channel %d\n", channel_id); + return NULL; + } + + chan = &ddma->chan[channel_id]; + chan->request_line = dma_spec->args[1]; + c = dma_get_slave_channel(&chan->vchan.chan); + if (!c) { + dev_err(dev, "no more channels available\n"); + return NULL; + } + + return c; +} + +static int phytium_ddma_probe(struct platform_device *pdev) +{ + struct phytium_ddma_device *ddma; + struct dma_device *dma_dev; + struct resource *mem; + u32 i = 0; + int ret = 0; + u32 nr_channels = 0; + + ddma = devm_kzalloc(&pdev->dev, sizeof(*ddma), GFP_KERNEL); + if (!ddma) { + ret = -ENOMEM; + goto out; + } + + dma_dev = &ddma->dma_dev; + ddma->dev = &pdev->dev; + + spin_lock_init(&ddma->lock); + + ddma->irq = platform_get_irq(pdev, 0); + if (ddma->irq < 0) { + dev_err(&pdev->dev, "no irq resource\n"); + ret = -EINVAL; + goto out; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ddma->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(ddma->base)) { + dev_err(&pdev->dev, "no resource address"); + ret = PTR_ERR(ddma->base); + goto out; + } + + ret = of_property_read_u32(pdev->dev.of_node, "dma-channels", + &nr_channels); + if (ret < 0) { + dev_err(&pdev->dev, "can't get the number of dma channels: %d\n", + ret); + goto out; + } + + if (nr_channels > DDMA_MAX_NR_PCHANNELS) { + dev_warn(&pdev->dev, "over the max number of channels\n"); + nr_channels = DDMA_MAX_NR_PCHANNELS; + } + + ddma->dma_channels = DDMA_MAX_NR_PCHANNELS; + + ret = devm_request_irq(&pdev->dev, ddma->irq, phytium_dma_interrupt, + IRQF_SHARED, dev_name(&pdev->dev), ddma); + if (ret) { + dev_err(&pdev->dev, "could not to request irq %d", ddma->irq); + goto out; + } + + /* Set capabilities */ + dma_cap_set(DMA_SLAVE, ddma->dma_dev.cap_mask); + + /* DMA capabilities */ + dma_dev->dev = ddma->dev; + dma_dev->chancnt = ddma->dma_channels; + dma_dev->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + dma_dev->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + dma_dev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); + dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR; + + /* function callback */ + dma_dev->device_tx_status = phytium_ddma_tx_status; + dma_dev->device_issue_pending = phytium_ddma_issue_pending; + dma_dev->device_terminate_all = phytium_ddma_terminate_all; + dma_dev->device_alloc_chan_resources = phytium_ddma_alloc_chan_resources; + dma_dev->device_free_chan_resources = phytium_ddma_free_chan_resources; + dma_dev->device_config = phytium_ddma_slave_config; + dma_dev->device_prep_slave_sg = phytium_ddma_prep_slave_sg; + dma_dev->device_pause = phytium_chan_pause; + dma_dev->device_resume = phytium_chan_resume; + + /* init dma physical channels */ + INIT_LIST_HEAD(&dma_dev->channels); + ddma->chan = devm_kcalloc(ddma->dev, ddma->dma_channels, + sizeof(*ddma->chan), GFP_KERNEL); + if (!ddma->chan) { + ret = -ENOMEM; + goto out; + } + for (i = 0; i < ddma->dma_channels; i++) { + ddma->chan[i].id = i; + ddma->chan[i].buf = NULL; + ddma->chan[i].base = ddma->base + DMA_REG_LEN + i * CHAN_REG_LEN; + ddma->chan[i].vchan.desc_free = phytium_ddma_vdesc_free; + ddma->chan[i].desc = NULL; + ddma->chan[i].current_sg = NULL; + vchan_init(&ddma->chan[i].vchan, dma_dev); + } + + phytium_dma_hw_init(ddma); + + ret = dma_async_device_register(dma_dev); + if (ret) + goto out; + + ret = of_dma_controller_register(pdev->dev.of_node, + phytium_ddma_of_xlate, ddma); + if (ret < 0) { + dev_err(&pdev->dev, "phytium ddma of register failed %d\n", + ret); + goto err_unregister; + } + + platform_set_drvdata(pdev, ddma); + dev_info(ddma->dev, "phytium DDMA Controller registered\n"); + + return 0; + +err_unregister: + dma_async_device_unregister(dma_dev); + +out: + return ret; +} + +static void phytium_ddma_chan_remove(struct phytium_ddma_chan *chan) +{ + phytium_chan_irq_disable(chan); + phytium_chan_disable(chan); + + if (chan->buf) + dma_free_coherent(chan_to_dev(chan), 4 * PAGE_SIZE, chan->buf, + chan->paddr); + + tasklet_kill(&chan->vchan.task); + list_del(&chan->vchan.chan.device_node); +} + +static int phytium_ddma_remove(struct platform_device *pdev) +{ + struct phytium_ddma_device *ddma = platform_get_drvdata(pdev); + struct phytium_ddma_chan *chan = NULL; + int i = 0; + + of_dma_controller_free(pdev->dev.of_node); + dma_async_device_unregister(&ddma->dma_dev); + + for (i = 0; i < ddma->dma_channels; i++) { + chan = &ddma->chan[i]; + phytium_ddma_chan_remove(chan); + } + + phytium_ddma_irq_disable(ddma); + phytium_ddma_disable(ddma); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int phytium_ddma_suspend(struct device *dev) +{ + struct phytium_ddma_device *ddma = dev_get_drvdata(dev); + int i = 0; + + for (i = 0; i < ddma->dma_channels; i++) { + if (phytium_chan_is_running(&ddma->chan[i])) { + dev_warn(dev, "suspend is prevented by channel %d\n", i); + return -EBUSY; + } + } + + ddma->dma_reg.dma_chal_cfg0 = + phytium_ddma_ioread32(ddma, DMA_CHAL_CFG_L); + ddma->dma_reg.dma_chal_bind = + phytium_ddma_ioread32(ddma, DMA_CHAL_BIND); + ddma->dma_reg.dma_chal_cfg1 = + phytium_ddma_ioread32(ddma, DMA_CHAL_CFG_H); + + for (i = 0; i < ddma->dma_channels; i++) { + struct phytium_ddma_chan *chan = &ddma->chan[i]; + + if (!chan->is_used) + continue; + ddma->dma_chal_reg[i].dma_chalx_ctl = + phytium_chan_ioread32(chan, DMA_CHALX_CTL); + ddma->dma_chal_reg[i].dma_chalx_timeout_cnt = + phytium_chan_ioread32(chan, DMA_CHALX_TIMEOUT_CNT); + } + + phytium_ddma_irq_disable(ddma); + phytium_ddma_disable(ddma); + pm_runtime_force_suspend(dev); + + return 0; +} + +static int phytium_ddma_resume(struct device *dev) +{ + struct phytium_ddma_device *ddma = dev_get_drvdata(dev); + u32 i = 0; + int ret = 0; + + phytium_dma_hw_init(ddma); + phytium_ddma_iowrite32(ddma, DMA_CHAL_CFG_L, + ddma->dma_reg.dma_chal_cfg0); + phytium_ddma_iowrite32(ddma, DMA_CHAL_BIND, + ddma->dma_reg.dma_chal_bind); + phytium_ddma_iowrite32(ddma, DMA_CHAL_CFG_H, + ddma->dma_reg.dma_chal_cfg1); + + for (i = 0; i < ddma->dma_channels; i++) { + struct phytium_ddma_chan *chan = &ddma->chan[i]; + + if (!chan->is_used) + continue; + phytium_chan_iowrite32(chan, DMA_CHALX_CTL, + ddma->dma_chal_reg[i].dma_chalx_ctl); + phytium_chan_iowrite32(chan, DMA_CHALX_TIMEOUT_CNT, + ddma->dma_chal_reg[i].dma_chalx_timeout_cnt); + } + + ret = pm_runtime_force_resume(dev); + + return ret; +} +#endif + +static const struct dev_pm_ops phytium_ddma_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(phytium_ddma_suspend, + phytium_ddma_resume) +}; + +static const struct of_device_id phytium_dma_of_id_table[] = { + { .compatible = "phytium,ddma" }, + {} +}; +MODULE_DEVICE_TABLE(of, phytium_dma_of_id_table); + +static struct platform_driver phytium_driver = { + .probe = phytium_ddma_probe, + .remove = phytium_ddma_remove, + .driver = { + .name = "phytium-ddma", + .of_match_table = of_match_ptr(phytium_dma_of_id_table), + .pm = &phytium_ddma_pm_ops, + }, +}; + +module_platform_driver(phytium_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium DDMA Controller platform driver"); +MODULE_AUTHOR("HuangJie "); diff --git a/drivers/dma/phytium/phytium-ddmac.h b/drivers/dma/phytium/phytium-ddmac.h new file mode 100644 index 0000000000000000000000000000000000000000..d88fda6b0844de78f1fd61a6026440169fbbe43a --- /dev/null +++ b/drivers/dma/phytium/phytium-ddmac.h @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL */ +/* + * Phytium Device DMA Controller driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd + * + */ + +#ifndef _PHYTIUM_DDMAC_H +#define _PHYTIUM_DDMAC_H + +#include +#include +#include +#include +#include +#include "../virt-dma.h" + +/* the number of physical channel */ +#define DDMA_MAX_NR_PCHANNELS 8 + +#define DMAC_MAX_MASTERS 1 +#define DMAC_MAX_BLK_SIZE PAGE_SIZE + +#define CHAN_REG_LEN 0x40 +#define DMA_REG_LEN 0x40 + +#define DMA_CTL 0x00 +#define DMA_CHAL_CFG_L 0x04 +#define DMA_CHAL_CFG_H 0x28 +#define DMA_STAT 0x08 +#define DMA_MASK_INT 0x0C +#define DMA_CHAL_BIND 0x20 +#define DMA_GCAP 0x24 + +#define DMA_CHALX_DDR_UPADDR 0x00 +#define DMA_CHALX_DDR_LWADDR 0x04 +#define DMA_CHALX_DEV_ADDR 0x08 +#define DMA_CHALX_TS 0x0C +#define DMA_CHALX_CRT_UPADDR 0x10 +#define DMA_CHALX_CRT_LWADDR 0x14 +#define DMA_CHALX_CTL 0x18 +#define DMA_CHALX_STS 0x1C +#define DMA_CHALX_TIMEOUT_CNT 0x20 +#define DMA_CHALX_TRANS_CNT 0x24 + +#define DMA_CTL_EN BIT(0) +#define DMA_CTL_SRST BIT(1) + +#define DMA_CHAL_SEL(id, x) (min_t(unsigned int, x, 0x7F) << ((id) * 8)) +#define DMA_CHAL_SEL_EN(id) BIT((id) * 8 + 7) + +#define DMA_STAT_CHAL(id) BIT((id) * 4) + +#define DMA_INT_EN BIT(31) +#define DMA_INT_CHAL_EN(id) BIT(id) + +#define DMA_CHAL_EN BIT(0) +#define DMA_CHAL_SRST BIT(1) +#define DMA_CHAL_MODE BIT(2) + +#define DMA_RX_REQ 1 +#define DMA_TX_REQ 0 + +#define DMA_CHAL_TIMEOUT_EN BIT(31) +#define DMA_CHAL_TIMEOUT_CNT(x) min_t(unsigned int, x, 0xFFFFF) + +#define DMA_TIMEOUT 10 + +/** + * struct phytium_ddma_sg_req - scatter-gatter list data info + * @len: number of bytes to transform + * @mem_addr_l: bus address low 32bit + * @mem_addr_h: bus address high 32bit + * @dev_addr: dma cousumer data reg addr + * @direction: dma transmit direction + */ +struct phytium_ddma_sg_req { + u32 len; + u32 mem_addr_l; + u32 mem_addr_h; + u32 dev_addr; + enum dma_transfer_direction direction; +}; + +/** + * struct phytium_ddma_desc - + * the struct holding info describing ddma request + * descriptor + * @vdesc: ddma request descriptor + * @num_sgs: the size of scatter-gatter list + * @sg_req: use to save scatter-gatter list info + */ +struct phytium_ddma_desc { + struct virt_dma_desc vdesc; + u32 num_sgs; + struct phytium_ddma_sg_req sg_req[]; +}; + +/** + * struct phytium_ddma_chan - + * the struct holding info describing dma channel + * @vchan: virtual dma channel + * @base: the mapped register I/O of dma physical channel + * @id: the id of ddma physical channel + * @request_line: the request line of ddma channel + * @desc: the transform request descriptor + * @dma_config: config parameters for dma channel + * @busy: the channel busy flag, this flag set when channel is tansferring + * @is_used: the channel bind flag, this flag set when channel binded + * @next_sg: the index of next scatter-gatter + * @current_sg: use to save the current transfer scatter-gatter info + * @paddr: use to align data between dma provider and consumer + */ +struct phytium_ddma_chan { + struct virt_dma_chan vchan; + void __iomem *base; + u32 id; + u32 request_line; + struct phytium_ddma_desc *desc; + struct dma_slave_config dma_config; + bool busy; + bool is_used; + bool is_pasued; + u32 next_sg; + struct phytium_ddma_sg_req *current_sg; + dma_addr_t paddr; + char *buf; +}; + +struct global_reg { + u32 dma_chal_cfg0; + u32 dma_chal_bind; + u32 dma_chal_cfg1; +}; + +struct channel_reg { + u32 dma_chalx_ctl; + u32 dma_chalx_timeout_cnt; +}; + +/** + * struct phytium_ddma_device - + * the struct holding info describing DDMA device + * @dma_dev: an instance for struct dma_device + * @irq: the irq that DDMA using + * @base: the mapped register I/O base of this DDMA + * @core_clk: DDMA clock + * @dma_channels: the number of DDMA physical channels + * @chan: the phyical channels of DDMA + * @lock: spinlock to lock when set global registers + * @dma_reg: store global register value which need recover after resume + * @dma_chal_reg: store channel register value which need recover after resume + */ +struct phytium_ddma_device { + struct dma_device dma_dev; + struct device *dev; + int irq; + void __iomem *base; + struct clk *core_clk; + u32 dma_channels; + struct phytium_ddma_chan *chan; + spinlock_t lock; + struct global_reg dma_reg; + struct channel_reg dma_chal_reg[DDMA_MAX_NR_PCHANNELS]; +}; + +#endif /* _PHYTIUM_DDMAC_H */ diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index 1679ff3eab2e808354709231dc296f04521e1baf..1728187693154c0a9f90a19a236022bb5cf0403d 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -515,6 +515,13 @@ config EDAC_TI help Support for error detection and correction on the TI SoCs. +config EDAC_PHYTIUM + tristate "Phytium Pe220x SoC" + depends on (ARM64) + help + Support for error detection and correction on the + Phytium Pe220x family of SOCs. + config EDAC_QCOM tristate "QCOM EDAC Controller" depends on ARCH_QCOM && QCOM_LLCC diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index 349b25fde664cbbc365a5074880bde12d5f8a4cb..d5230317d6d41d728b080a8def637307e1bdf7da 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -83,6 +83,7 @@ obj-$(CONFIG_EDAC_ARMADA_XP) += armada_xp_edac.o obj-$(CONFIG_EDAC_SYNOPSYS) += synopsys_edac.o obj-$(CONFIG_EDAC_XGENE) += xgene_edac.o obj-$(CONFIG_EDAC_TI) += ti_edac.o +obj-$(CONFIG_EDAC_PHYTIUM) += phytium_edac.o obj-$(CONFIG_EDAC_QCOM) += qcom_edac.o obj-$(CONFIG_EDAC_ASPEED) += aspeed_edac.o obj-$(CONFIG_EDAC_BLUEFIELD) += bluefield_edac.o diff --git a/drivers/edac/phytium_edac.c b/drivers/edac/phytium_edac.c new file mode 100644 index 0000000000000000000000000000000000000000..af6a729816f4afb8212c85f7ee3db1ae0e78c18d --- /dev/null +++ b/drivers/edac/phytium_edac.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium Pe220x EDAC (error detection and correction) + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "edac_module.h" + +#define EDAC_MOD_STR "phytium_edac" + +/* register offset */ +#define ERR_STATUS(n) (0x10 + ((n) * 64)) +#define ERR_CTLR(n) (0x08 + ((n) * 64)) +#define ERR_MISC0(n) (0x20 + ((n) * 64)) +#define ERR_INJECT 0x7C +#define ERR_DEVID 0xFC8 +#define ERR_GSR 0xE00 + +#define CTLR_ED BIT(0) +#define CTLR_UI BIT(2) +#define CTLR_CFI BIT(8) + +#define MISC0_CEC(x) ((u64)(x) << 32) + +#define ERR_STATUS_CLEAR GENMASK(31, 0) + +#define CORRECTED_ERROR 0 +#define UNCORRECTED_ERROR 1 + +#define MAX_ERR_GROUP 3 + +struct phytium_edac { + struct device *dev; + void __iomem **ras_base; + struct dentry *dfs; + struct edac_device_ctl_info *edac_dev; +}; + +struct ras_error_info { + u32 index; + u32 error_type; + const char *error_str; +}; + +/* error severity definition */ +enum { + SEV_NO = 0x0, + SEV_CORRECTED = 0x1, + SEV_RECOVERABLE = 0x2, + SEV_PANIC = 0x3, +}; + +/* soc error record */ +static const struct ras_error_info pe220x_ras_soc_error[] = { + { 0, UNCORRECTED_ERROR, "lsd_nfc_ras_error" }, + { 1, UNCORRECTED_ERROR, "lsd_lpc_ras_long_wait_to" }, + { 2, UNCORRECTED_ERROR, "lsd_lpc_ras_short_wait_to" }, + { 3, UNCORRECTED_ERROR, "lsd_lpc_ras_sync_err" }, + { 4, UNCORRECTED_ERROR, "lsd_lbc_ras_err" }, + { 5, UNCORRECTED_ERROR, "usb3_err_0" }, + { 6, UNCORRECTED_ERROR, "usb3_err_1" }, + { 7, UNCORRECTED_ERROR, "gsd_gmu_mac0_asf_nonfatal_int" }, + { 8, UNCORRECTED_ERROR, "gsd_gmu_mac0_asf_fatal_int" }, + { 9, UNCORRECTED_ERROR, "gsd_gmu_mac0_asf_trans_to_err" }, + { 10, UNCORRECTED_ERROR, "gsd_gmu_mac0_asf_protocol_err" }, + { 11, UNCORRECTED_ERROR, "gsd_gmu_mac1_asf_nonfatal_int" }, + { 12, UNCORRECTED_ERROR, "gsd_gmu_mac1_asf_fatal_int" }, + { 13, UNCORRECTED_ERROR, "gsd_gmu_mac1_asf_trans_to_err" }, + { 14, UNCORRECTED_ERROR, "gsd_gmu_mac1_asf_protocol_err" }, + { 15, UNCORRECTED_ERROR, "gsd_gmu_mac2_asf_nonfatal_int" }, + { 16, UNCORRECTED_ERROR, "gsd_gmu_mac2_asf_fatal_int" }, + { 17, UNCORRECTED_ERROR, "gsd_gmu_mac2_asf_trans_to_err" }, + { 18, UNCORRECTED_ERROR, "gsd_gmu_mac2_asf_protocol_err" }, + { 19, UNCORRECTED_ERROR, "gsd_gmu_mac3_asf_nonfatal_int" }, + { 20, UNCORRECTED_ERROR, "gsd_gmu_mac3_asf_fatal_int" }, + { 21, UNCORRECTED_ERROR, "gsd_gmu_mac3_asf_trans_to_err" }, + { 22, UNCORRECTED_ERROR, "gsd_gmu_mac3_asf_protocol_err" }, + { 23, CORRECTED_ERROR, "dmu_ras_ecc_corrected_error" }, + { 24, UNCORRECTED_ERROR, "dmu_ras_ecc_uncorrected_error" }, + { 25, UNCORRECTED_ERROR, "cci_ras_nERRIRQ" }, + { 26, UNCORRECTED_ERROR, "smmu_tcu_ras_irpt" }, + { 27, UNCORRECTED_ERROR, "smmu_tbu0_ras_irpt" }, + { 28, UNCORRECTED_ERROR, "smmu_tbu1_ras_irpt" }, + { 29, UNCORRECTED_ERROR, "smmu_tbu2_ras_irpt" }, + { 30, UNCORRECTED_ERROR, "ocm_sram_ue" }, + { 31, CORRECTED_ERROR, "ocm_sram_ce" }, + { 32, UNCORRECTED_ERROR, "int_axim_err" }, + { 33, UNCORRECTED_ERROR, "int_fatal_error" }, + { 34, UNCORRECTED_ERROR, "nEXTERRIRQ_clust0" }, + { 35, UNCORRECTED_ERROR, "nINTERRIRQ_clust0" }, + { 36, UNCORRECTED_ERROR, "nEXTERRIRQ_clust1" }, + { 37, UNCORRECTED_ERROR, "nINTERRIRQ_clust1" }, + { 38, UNCORRECTED_ERROR, "nEXTERRIRQ_clust2" }, + { 39, UNCORRECTED_ERROR, "nINTERRIRQ_clust2" }, + { 40, UNCORRECTED_ERROR, "ams_ame0_ras_err" }, + { 41, UNCORRECTED_ERROR, "ams_ame1_ras_err" }, + { 42, UNCORRECTED_ERROR, "ams_amer_ras_err" }, + { 43, UNCORRECTED_ERROR, "ras_err_ame1" }, +}; + +/* pcie controller error record */ +static const struct ras_error_info pe220x_ras_peu_psu_error[] = { + { 0, CORRECTED_ERROR, "pio_rd_addr_error" }, + { 1, UNCORRECTED_ERROR, "pio_wr_addr_error" }, + { 2, CORRECTED_ERROR, "pio_rd_timeout" }, + { 3, CORRECTED_ERROR, "pio_wr_timeout" }, + { 4, CORRECTED_ERROR, "axi_b_rsp_error" }, + { 5, CORRECTED_ERROR, "axi_r_rsp_error" }, +}; + +static const struct ras_error_info pe220x_ras_peu_error[] = { + { 0, CORRECTED_ERROR, "pio_rd_addr_error" }, + { 1, UNCORRECTED_ERROR, "pio_wr_addr_error" }, + { 2, CORRECTED_ERROR, "pio_rd_timeout" }, + { 3, CORRECTED_ERROR, "pio_wr_timeout" }, + { 4, CORRECTED_ERROR, "axi_b_rsp_error" }, + { 5, CORRECTED_ERROR, "axi_r_rsp_error" }, +}; + +static const struct ras_error_info *pe220x_ras_error[] = { + pe220x_ras_soc_error, pe220x_ras_peu_psu_error, pe220x_ras_peu_error +}; + +static inline unsigned int get_error_num(const struct phytium_edac *edac, + int err_group) +{ + unsigned int error_num = 0; + + error_num = readl(edac->ras_base[err_group] + ERR_DEVID); + + return error_num; +} + +static inline void phytium_ras_setup(const struct phytium_edac *edac) +{ + u64 val = 0; + unsigned int i = 0; + /* + * enable error report and generate interrupt for corrected error event + * first error record owned by node present the node configuration + */ + for (i = 0; i < MAX_ERR_GROUP; i++) { + val = readq(edac->ras_base[i] + ERR_CTLR(0)); + val |= CTLR_ED | CTLR_UI | CTLR_CFI; + writeq(val, edac->ras_base[i] + ERR_CTLR(0)); + } +} + +static ssize_t phytium_edac_inject_ctrl_write(struct file *filp, + const char __user *buf, + size_t size, loff_t *ppos) +{ + int ret = 0; + int res = 0; + unsigned int error_group = 0; + unsigned int error_id = 0; + unsigned int error_num = 0; + struct phytium_edac *edac = filp->private_data; + char str[255]; + char *p_str = str; + char *tmp = NULL; + + if (size > 255) { + ret = -EFAULT; + goto out; + } + + if (copy_from_user(str, buf, size)) { + ret = -EFAULT; + goto out; + } else { + *ppos += size; + ret = size; + } + str[size] = '\0'; + + tmp = strsep(&p_str, ","); + if (!tmp) + goto out; + + res = kstrtouint(tmp, 0, &error_group); + if (res || error_group >= MAX_ERR_GROUP) { + dev_err(edac->dev, "invalid error group parameters"); + goto out; + } + + res = kstrtouint(p_str, 0, &error_id); + if (res) { + dev_err(edac->dev, "invalid error id parameters"); + goto out; + } + + error_num = get_error_num(edac, error_group); + if (error_id >= error_num) { + dev_err(edac->dev, "invalid ras error id.\n"); + goto out; + } + + dev_dbg(edac->dev, "inject group%d, error_id: %d\n", + error_group, error_id); + + if (pe220x_ras_error[error_group][error_id].error_type + == CORRECTED_ERROR) { + writeq(MISC0_CEC(0xFF), + edac->ras_base[error_group] + ERR_MISC0(error_id)); + } + + writel(error_id, edac->ras_base[error_group] + ERR_INJECT); + +out: + return ret; +} + +static const struct file_operations phytium_edac_debug_inject_fops[] = { + { + .open = simple_open, + .write = phytium_edac_inject_ctrl_write, + .llseek = generic_file_llseek, }, + { } +}; + +static void phytium_edac_create_debugfs_nodes(struct phytium_edac *edac) +{ + if (!IS_ENABLED(CONFIG_EDAC_DEBUG) || !edac->dfs) { + dev_info(edac->dev, "edac debug is disable"); + return; + } + + edac_debugfs_create_file("error_inject_ctrl", 0x0200, edac->dfs, edac, + &phytium_edac_debug_inject_fops[0]); +} + +static int phytium_edac_device_add(struct phytium_edac *edac) +{ + struct edac_device_ctl_info *edac_dev; + int res = 0; + + edac_dev = edac_device_alloc_ctl_info( + sizeof(struct edac_device_ctl_info), + "ras", 1, "soc", 1, 0, NULL, + 0, edac_device_alloc_index()); + if (!edac_dev) + res = -ENOMEM; + + edac_dev->dev = edac->dev; + edac_dev->mod_name = EDAC_MOD_STR; + edac_dev->ctl_name = "phytium ras"; + edac_dev->dev_name = "soc"; + + phytium_edac_create_debugfs_nodes(edac); + + res = edac_device_add_device(edac_dev); + if (res > 0) { + dev_err(edac->dev, "edac_device_add_device failed\n"); + goto err_free; + } + + edac->edac_dev = edac_dev; + dev_info(edac->dev, "phytium edac device registered\n"); + return 0; + +err_free: + edac_device_free_ctl_info(edac_dev); + return res; +} + +static int phytium_edac_device_remove(struct phytium_edac *edac) +{ + struct edac_device_ctl_info *edac_dev = edac->edac_dev; + + debugfs_remove_recursive(edac->dfs); + edac_device_del_device(edac_dev->dev); + edac_device_free_ctl_info(edac_dev); + return 0; +} + +static int get_error_id(struct phytium_edac *edac, int *error_id, + int *error_group) +{ + unsigned int error_num = 0; + u64 error_bit = 0; + int ret = 0; + int i = 0; + int err_id = 0; + + /* Iterate over the ras node to check error status */ + for (i = 0; i < MAX_ERR_GROUP; i++) { + error_num = get_error_num(edac, i); + error_bit = readq(edac->ras_base[i] + ERR_GSR); + for (err_id = 0; err_id < error_num; err_id++) { + if (!(error_bit & BIT(err_id))) + continue; + else + break; + } + if (err_id < error_num) { + *error_id = err_id; + *error_group = i; + break; + } + } + + if (i >= MAX_ERR_GROUP) { + ret = -1; + dev_warn(edac->dev, "no error detect.\n"); + } + + return ret; +} + +static void phytium_edac_error_report(struct phytium_edac *edac, + const int error_id, const int error_group) +{ + const struct ras_error_info *err_info = + pe220x_ras_error[error_group]; + + if (err_info[error_id].error_type == UNCORRECTED_ERROR) { + edac_printk(KERN_CRIT, EDAC_MOD_STR, "uncorrected error: %s\n", + err_info[error_id].error_str); + edac_device_handle_ue(edac->edac_dev, 0, 0, + err_info[error_id].error_str); + /* Report the error via the trace interface */ + if (IS_ENABLED(CONFIG_RAS)) + trace_non_standard_event(&NULL_GUID, &NULL_GUID, + EDAC_MOD_STR, SEV_RECOVERABLE, + err_info[error_id].error_str, + strlen(err_info[error_id].error_str)); + } else { + edac_printk(KERN_CRIT, EDAC_MOD_STR, "corrected error: %s\n", + err_info[error_id].error_str); + edac_device_handle_ce(edac->edac_dev, 0, 0, + err_info[error_id].error_str); + if (IS_ENABLED(CONFIG_RAS)) + trace_non_standard_event(&NULL_GUID, &NULL_GUID, + EDAC_MOD_STR, SEV_CORRECTED, + err_info[error_id].error_str, + strlen(err_info[error_id].error_str)); + } +} + +/* + * clear error status and set correct error counter to 0xFE for trigger + * interrupt when next correct error event + */ +static void phytium_edac_clear_error_status(struct phytium_edac *edac, + const int error_id, const int error_group) +{ + writeq(MISC0_CEC(0XFE), edac->ras_base[error_group] + + ERR_MISC0(error_id)); + writeq(GENMASK(31, 0), edac->ras_base[error_group] + + ERR_STATUS(error_id)); +} + +static irqreturn_t phytium_edac_isr(int irq, void *dev_id) +{ + struct phytium_edac *edac = dev_id; + int ret = 0; + int error_group; + int error_id; + + ret = get_error_id(edac, &error_id, &error_group); + if (ret < 0) + goto out; + + phytium_edac_error_report(edac, error_id, error_group); + phytium_edac_clear_error_status(edac, error_id, error_group); + +out: + return IRQ_HANDLED; +} + +static int phytium_edac_probe(struct platform_device *pdev) +{ + struct phytium_edac *edac; + struct resource *res; + int ret = 0; + int irq_cnt = 0; + int irq = 0; + int i = 0; + + edac = devm_kzalloc(&pdev->dev, sizeof(*edac), GFP_KERNEL); + if (!edac) { + ret = -ENOMEM; + goto out; + } + + edac->dev = &pdev->dev; + platform_set_drvdata(pdev, edac); + + edac->ras_base = devm_kcalloc(&pdev->dev, 3, + sizeof(*edac->ras_base), GFP_KERNEL); + if (!edac->ras_base) { + return -ENOMEM; + goto out; + } + + for (i = 0; i < MAX_ERR_GROUP; i++) { + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + edac->ras_base[i] = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(edac->ras_base[i])) { + dev_err(&pdev->dev, "no resource address\n"); + ret = PTR_ERR(edac->ras_base[i]); + goto out; + } + } + + edac->dfs = edac_debugfs_create_dir(EDAC_MOD_STR); + + ret = phytium_edac_device_add(edac); + if (ret) { + dev_err(&pdev->dev, "can't add edac device"); + goto out; + } + + phytium_ras_setup(edac); + + irq_cnt = platform_irq_count(pdev); + if (irq_cnt < 0) { + dev_err(&pdev->dev, "no irq resource\n"); + ret = -EINVAL; + goto out; + } + + for (i = 0; i < irq_cnt; i++) { + irq = platform_get_irq(pdev, i); + if (irq < 0) { + dev_err(&pdev->dev, "invalid irq resource\n"); + ret = -EINVAL; + goto out; + } + ret = devm_request_irq(&pdev->dev, irq, + phytium_edac_isr, IRQF_SHARED, + EDAC_MOD_STR, edac); + if (ret) { + dev_err(&pdev->dev, + "could not request irq %d\n", irq); + goto out; + } + } + +out: + return ret; +} + +static int phytium_edac_remove(struct platform_device *pdev) +{ + struct phytium_edac *edac = dev_get_drvdata(&pdev->dev); + + phytium_edac_device_remove(edac); + + return 0; +} + +static const struct of_device_id phytium_edac_of_match[] = { + { .compatible = "phytium,pe220x-edac" }, + {}, +}; +MODULE_DEVICE_TABLE(of, phytium_edac_of_match); + +static struct platform_driver phytium_edac_driver = { + .probe = phytium_edac_probe, + .remove = phytium_edac_remove, + .driver = { + .name = "phytium-edac", + .of_match_table = phytium_edac_of_match, + }, +}; + +module_platform_driver(phytium_edac_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Huangjie "); diff --git a/drivers/firmware/arm_scmi/mailbox.c b/drivers/firmware/arm_scmi/mailbox.c index b8d470417e8f99bb6408aba541bc4b89541ddf7c..fa2ac4611d0845aad5ebc8d029da77c0c8268b89 100644 --- a/drivers/firmware/arm_scmi/mailbox.c +++ b/drivers/firmware/arm_scmi/mailbox.c @@ -167,6 +167,7 @@ static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, struct mbox_client *cl; resource_size_t size; struct resource res; + struct of_phandle_args args; ret = mailbox_chan_validate(cdev, &a2p_rx_chan, &p2a_chan); if (ret) @@ -226,6 +227,16 @@ static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, } } + ret = of_parse_phandle_with_args(cdev->of_node, "mboxes", + "#mbox-cells", 1, &args); + if (ret) { + dev_err(cdev, "failed to get SCMI %s mailbox\n", desc); + return ret; + } + + if (of_device_is_compatible(args.np, "phytium,mbox")) + cinfo->no_completion_irq = true; + cinfo->transport_info = smbox; smbox->cinfo = cinfo; diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index ebd4e113dc265411ea9317821dd0e0fee7e9e7d1..f068798efa63b307d0a51180ec525ba96924a487 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -105,6 +105,10 @@ config GPIO_REGMAP # put drivers in the right section, in alphabetical order +# This symbol is selected by both MMIO and PCI expanders +config GPIO_PHYTIUM_CORE + tristate + # This symbol is selected by both I2C and SPI expanders config GPIO_MAX730X tristate @@ -495,6 +499,27 @@ config GPIO_OMAP help Say yes here to enable GPIO support for TI OMAP SoCs. +config GPIO_PHYTIUM_PLAT + tristate "Phytium GPIO Platform support" + default y if ARCH_PHYTIUM + depends on ARM64 + select GPIO_PHYTIUM_CORE + select IRQ_DOMAIN + select GENERIC_IRQ_CHIP + select GPIOLIB_IRQCHIP + help + Say yes here to support the on-chip GPIO controller for the + Phytium SoC family. + +config GPIO_PHYTIUM_SGPIO + tristate "Phytium SGPIO support" + default y if ARCH_PHYTIUM + depends on ARM64 + select IRQ_DOMAIN + select GENERIC_IRQ_CHIP + help + Say yes here to enable SGPIO support for Phytium SoCs. + config GPIO_PL061 tristate "PrimeCell PL061 GPIO support" depends on ARM_AMBA @@ -1680,6 +1705,19 @@ config GPIO_PCIE_IDIO_24 Input filter control is not supported by this driver, and the input filters are deactivated by this driver. +config GPIO_PHYTIUM_PCI + tristate "Phytium GPIO PCI support" + select GPIO_PHYTIUM_CORE + select IRQ_DOMAIN + select GENERIC_IRQ_CHIP + select GPIOLIB_IRQCHIP + help + Say Y here to support Phytium PCI GPIO controller on Px210 chipset. + An interrupt is generated when any of the inputs change state + (low to high or high to low). + + This driver can be used for Phytium Px210. + config GPIO_RDC321X tristate "RDC R-321x GPIO support" select MFD_CORE diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index eb73b5d633ebad2dc397d7894901421e845ee390..1b4888e64afc9912c04e921ed791d0bd468248d3 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -125,6 +125,10 @@ obj-$(CONFIG_GPIO_PCF857X) += gpio-pcf857x.o obj-$(CONFIG_GPIO_PCH) += gpio-pch.o obj-$(CONFIG_GPIO_PCIE_IDIO_24) += gpio-pcie-idio-24.o obj-$(CONFIG_GPIO_PCI_IDIO_16) += gpio-pci-idio-16.o +obj-$(CONFIG_GPIO_PHYTIUM_CORE) += gpio-phytium-core.o +obj-$(CONFIG_GPIO_PHYTIUM_PCI) += gpio-phytium-pci.o +obj-$(CONFIG_GPIO_PHYTIUM_PLAT) += gpio-phytium-platform.o +obj-$(CONFIG_GPIO_PHYTIUM_SGPIO) += gpio-phytium-sgpio.o obj-$(CONFIG_GPIO_PISOSR) += gpio-pisosr.o obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o obj-$(CONFIG_GPIO_PMIC_EIC_SPRD) += gpio-pmic-eic-sprd.o diff --git a/drivers/gpio/gpio-phytium-core.c b/drivers/gpio/gpio-phytium-core.c new file mode 100644 index 0000000000000000000000000000000000000000..dd9ce4aeb1389b553e3e2fa67029665b36ff5005 --- /dev/null +++ b/drivers/gpio/gpio-phytium-core.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#include "gpio-phytium-core.h" + +static int get_pin_location(struct phytium_gpio *gpio, unsigned int offset, + struct pin_loc *pl) +{ + int ret; + + if (offset < gpio->ngpio[0]) { + pl->port = 0; + pl->offset = offset; + ret = 0; + } else if (offset < (gpio->ngpio[0] + gpio->ngpio[1])) { + pl->port = 1; + pl->offset = offset - gpio->ngpio[0]; + ret = 0; + } else { + ret = -EINVAL; + } + + return ret; +} + +static void phytium_gpio_toggle_trigger(struct phytium_gpio *gpio, + unsigned int offset) +{ + struct gpio_chip *gc; + u32 pol; + int val; + + /* Only port A can provide interrupt source */ + if (offset >= gpio->ngpio[0]) + return; + + gc = &gpio->gc; + + pol = readl(gpio->regs + GPIO_INT_POLARITY); + /* Just read the current value right out of the data register */ + val = gc->get(gc, offset); + if (val) + pol &= ~BIT(offset); + else + pol |= BIT(offset); + + writel(pol, gpio->regs + GPIO_INT_POLARITY); +} + +int phytium_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct phytium_gpio *gpio = gpiochip_get_data(gc); + struct pin_loc loc; + void __iomem *dat; + + if (get_pin_location(gpio, offset, &loc)) + return -EINVAL; + + dat = gpio->regs + GPIO_EXT_PORTA + (loc.port * GPIO_PORT_STRIDE); + + return !!(readl(dat) & BIT(loc.offset)); +} +EXPORT_SYMBOL_GPL(phytium_gpio_get); + +void phytium_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct phytium_gpio *gpio = gpiochip_get_data(gc); + struct pin_loc loc; + void __iomem *dr; + unsigned long flags; + u32 mask; + + if (get_pin_location(gpio, offset, &loc)) + return; + dr = gpio->regs + GPIO_SWPORTA_DR + (loc.port * GPIO_PORT_STRIDE); + + raw_spin_lock_irqsave(&gpio->lock, flags); + + if (value) + mask = readl(dr) | BIT(loc.offset); + else + mask = readl(dr) & ~BIT(loc.offset); + + writel(mask, dr); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); +} +EXPORT_SYMBOL_GPL(phytium_gpio_set); + +int phytium_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + struct phytium_gpio *gpio = gpiochip_get_data(gc); + struct pin_loc loc; + unsigned long flags; + void __iomem *ddr; + + if (get_pin_location(gpio, offset, &loc)) + return -EINVAL; + ddr = gpio->regs + GPIO_SWPORTA_DDR + (loc.port * GPIO_PORT_STRIDE); + + raw_spin_lock_irqsave(&gpio->lock, flags); + + writel(readl(ddr) & ~(BIT(loc.offset)), ddr); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_gpio_direction_input); + +int phytium_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct phytium_gpio *gpio = gpiochip_get_data(gc); + struct pin_loc loc; + unsigned long flags; + void __iomem *ddr; + + if (get_pin_location(gpio, offset, &loc)) + return -EINVAL; + ddr = gpio->regs + GPIO_SWPORTA_DDR + (loc.port * GPIO_PORT_STRIDE); + + raw_spin_lock_irqsave(&gpio->lock, flags); + + writel(readl(ddr) | BIT(loc.offset), ddr); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); + + phytium_gpio_set(gc, offset, value); + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_gpio_direction_output); + +void phytium_gpio_irq_ack(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct phytium_gpio *gpio = gpiochip_get_data(gc); + u32 val = BIT(irqd_to_hwirq(d)); + + raw_spin_lock(&gpio->lock); + + writel(val, gpio->regs + GPIO_PORTA_EOI); + + raw_spin_unlock(&gpio->lock); +} +EXPORT_SYMBOL_GPL(phytium_gpio_irq_ack); + +void phytium_gpio_irq_mask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct phytium_gpio *gpio = gpiochip_get_data(gc); + u32 val; + + /* Only port A can provide interrupt source */ + if (irqd_to_hwirq(d) >= gpio->ngpio[0]) + return; + + raw_spin_lock(&gpio->lock); + + val = readl(gpio->regs + GPIO_INTMASK); + val |= BIT(irqd_to_hwirq(d)); + writel(val, gpio->regs + GPIO_INTMASK); + + raw_spin_unlock(&gpio->lock); + + gpiochip_disable_irq(gc, irqd_to_hwirq(d)); +} +EXPORT_SYMBOL_GPL(phytium_gpio_irq_mask); + +void phytium_gpio_irq_unmask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct phytium_gpio *gpio = gpiochip_get_data(gc); + u32 val; + + /* Only port A can provide interrupt source */ + if (irqd_to_hwirq(d) >= gpio->ngpio[0]) + return; + + gpiochip_enable_irq(gc, irqd_to_hwirq(d)); + + raw_spin_lock(&gpio->lock); + + val = readl(gpio->regs + GPIO_INTMASK); + val &= ~BIT(irqd_to_hwirq(d)); + writel(val, gpio->regs + GPIO_INTMASK); + + raw_spin_unlock(&gpio->lock); +} +EXPORT_SYMBOL_GPL(phytium_gpio_irq_unmask); + +int phytium_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct phytium_gpio *gpio = gpiochip_get_data(gc); + int hwirq = irqd_to_hwirq(d); + unsigned long flags, lvl, pol; + + if (hwirq < 0 || hwirq >= gpio->ngpio[0]) + return -EINVAL; + + if ((flow_type & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) && + (flow_type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))) { + dev_err(gc->parent, + "trying to configure line %d for both level and edge detection, choose one!\n", + hwirq); + return -EINVAL; + } + + raw_spin_lock_irqsave(&gpio->lock, flags); + + lvl = readl(gpio->regs + GPIO_INTTYPE_LEVEL); + pol = readl(gpio->regs + GPIO_INT_POLARITY); + + switch (flow_type) { + case IRQ_TYPE_EDGE_BOTH: + lvl |= BIT(hwirq); + phytium_gpio_toggle_trigger(gpio, hwirq); + irq_set_handler_locked(d, handle_edge_irq); + dev_dbg(gc->parent, "line %d: IRQ on both edges\n", hwirq); + break; + case IRQ_TYPE_EDGE_RISING: + lvl |= BIT(hwirq); + pol |= BIT(hwirq); + irq_set_handler_locked(d, handle_edge_irq); + dev_dbg(gc->parent, "line %d: IRQ on RISING edge\n", hwirq); + break; + case IRQ_TYPE_EDGE_FALLING: + lvl |= BIT(hwirq); + pol &= ~BIT(hwirq); + irq_set_handler_locked(d, handle_edge_irq); + dev_dbg(gc->parent, "line %d: IRQ on FALLING edge\n", hwirq); + break; + case IRQ_TYPE_LEVEL_HIGH: + lvl &= ~BIT(hwirq); + pol |= BIT(hwirq); + irq_set_handler_locked(d, handle_level_irq); + dev_dbg(gc->parent, "line %d: IRQ on HIGH level\n", hwirq); + break; + case IRQ_TYPE_LEVEL_LOW: + lvl &= ~BIT(hwirq); + pol &= ~BIT(hwirq); + irq_set_handler_locked(d, handle_level_irq); + dev_dbg(gc->parent, "line %d: IRQ on LOW level\n", hwirq); + break; + } + + writel(lvl, gpio->regs + GPIO_INTTYPE_LEVEL); + if (flow_type != IRQ_TYPE_EDGE_BOTH) + writel(pol, gpio->regs + GPIO_INT_POLARITY); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_gpio_irq_set_type); + +void phytium_gpio_irq_enable(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct phytium_gpio *gpio = gpiochip_get_data(gc); + unsigned long flags; + u32 val; + + /* Only port A can provide interrupt source */ + if (irqd_to_hwirq(d) >= gpio->ngpio[0]) + return; + + gpiochip_enable_irq(gc, irqd_to_hwirq(d)); + + raw_spin_lock_irqsave(&gpio->lock, flags); + + val = readl(gpio->regs + GPIO_INTEN); + val |= BIT(irqd_to_hwirq(d)); + writel(val, gpio->regs + GPIO_INTEN); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); + + gpiochip_disable_irq(gc, irqd_to_hwirq(d)); +} +EXPORT_SYMBOL_GPL(phytium_gpio_irq_enable); + +void phytium_gpio_irq_disable(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct phytium_gpio *gpio = gpiochip_get_data(gc); + unsigned long flags; + u32 val; + + /* Only port A can provide interrupt source */ + if (irqd_to_hwirq(d) >= gpio->ngpio[0]) + return; + + raw_spin_lock_irqsave(&gpio->lock, flags); + + val = readl(gpio->regs + GPIO_INTEN); + val &= ~BIT(irqd_to_hwirq(d)); + writel(val, gpio->regs + GPIO_INTEN); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); +} +EXPORT_SYMBOL_GPL(phytium_gpio_irq_disable); + +void phytium_gpio_irq_print_chip(struct irq_data *data, struct seq_file *p) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + + seq_printf(p, dev_name(gc->parent)); +} +EXPORT_SYMBOL_GPL(phytium_gpio_irq_print_chip); + +void phytium_gpio_irq_handler(struct irq_desc *desc) +{ + struct gpio_chip *gc = irq_desc_get_handler_data(desc); + struct phytium_gpio *gpio = gpiochip_get_data(gc); + struct irq_chip *irqchip = irq_desc_get_chip(desc); + unsigned long pending; + int offset; + + chained_irq_enter(irqchip, desc); + + pending = readl(gpio->regs + GPIO_INTSTATUS); + if (pending) { + for_each_set_bit(offset, &pending, gpio->ngpio[0]) { + int gpio_irq = irq_find_mapping(gc->irq.domain, + offset); + generic_handle_irq(gpio_irq); + + if ((irq_get_trigger_type(gpio_irq) & + IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH) + phytium_gpio_toggle_trigger(gpio, offset); + } + } + + chained_irq_exit(irqchip, desc); +} +EXPORT_SYMBOL_GPL(phytium_gpio_irq_handler); + +int phytium_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct phytium_gpio *gpio = gpiochip_get_data(gc); + struct pin_loc loc; + void __iomem *ddr; + + if (get_pin_location(gpio, offset, &loc)) + return -EINVAL; + ddr = gpio->regs + GPIO_SWPORTA_DDR + (loc.port * GPIO_PORT_STRIDE); + + return !(readl(ddr) & BIT(loc.offset)); +} +EXPORT_SYMBOL_GPL(phytium_gpio_get_direction); + +int phytium_gpio_irq_set_affinity(struct irq_data *d, const struct cpumask *mask_val, bool force) +{ + int hwirq = irqd_to_hwirq(d); + struct gpio_chip *chip_data = irq_data_get_irq_chip_data(d); + struct irq_chip *chip = irq_get_chip(chip_data->irq.parents[hwirq]); + struct irq_data *data = irq_get_irq_data(chip_data->irq.parents[hwirq]); + + if (chip && chip->irq_set_affinity) + return chip->irq_set_affinity(data, mask_val, force); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(phytium_gpio_irq_set_affinity); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium GPIO Controller core"); diff --git a/drivers/gpio/gpio-phytium-core.h b/drivers/gpio/gpio-phytium-core.h new file mode 100644 index 0000000000000000000000000000000000000000..5dc4610b061140eb4b7cc4c72bbb8f0ca2ac33a5 --- /dev/null +++ b/drivers/gpio/gpio-phytium-core.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef _GPIO_PHYTIUM_H +#define _GPIO_PHYTIUM_H + +#include +#include + +#include "gpiolib.h" + +#define GPIO_SWPORTA_DR 0x00 /* WR Port A Output Data Register */ +#define GPIO_SWPORTA_DDR 0x04 /* WR Port A Data Direction Register */ +#define GPIO_EXT_PORTA 0x08 /* RO Port A Input Data Register */ +#define GPIO_SWPORTB_DR 0x0c /* WR Port B Output Data Register */ +#define GPIO_SWPORTB_DDR 0x10 /* WR Port B Data Direction Register */ +#define GPIO_EXT_PORTB 0x14 /* RO Port B Input Data Register */ + +#define GPIO_INTEN 0x18 /* WR Port A Interrput Enable Register */ +#define GPIO_INTMASK 0x1c /* WR Port A Interrupt Mask Register */ +#define GPIO_INTTYPE_LEVEL 0x20 /* WR Port A Interrupt Level Register */ +#define GPIO_INT_POLARITY 0x24 /* WR Port A Interrupt Polarity Register */ +#define GPIO_INTSTATUS 0x28 /* RO Port A Interrupt Status Register */ +#define GPIO_RAW_INTSTATUS 0x2c /* RO Port A Raw Interrupt Status Register */ +#define GPIO_LS_SYNC 0x30 /* WR Level-sensitive Synchronization Enable Register */ +#define GPIO_DEBOUNCE 0x34 /* WR Debounce Enable Register */ +#define GPIO_PORTA_EOI 0x38 /* WO Port A Clear Interrupt Register */ + +#define MAX_NPORTS 2 +#define NGPIO_DEFAULT 8 +#define NGPIO_MAX 32 +#define GPIO_PORT_STRIDE (GPIO_EXT_PORTB - GPIO_EXT_PORTA) + +struct pin_loc { + unsigned int port; + unsigned int offset; +}; + +#ifdef CONFIG_PM_SLEEP +struct phytium_gpio_ctx { + u32 swporta_dr; + u32 swporta_ddr; + u32 ext_porta; + u32 swportb_dr; + u32 swportb_ddr; + u32 ext_portb; + u32 inten; + u32 intmask; + u32 inttype_level; + u32 int_polarity; + u32 intstatus; + u32 raw_intstatus; + u32 ls_sync; + u32 debounce; +}; +#endif + +struct phytium_gpio { + raw_spinlock_t lock; + void __iomem *regs; + struct gpio_chip gc; + unsigned int ngpio[2]; + int irq[32]; +#ifdef CONFIG_PM_SLEEP + struct phytium_gpio_ctx ctx; +#endif +}; + +int phytium_gpio_get(struct gpio_chip *gc, unsigned int offset); +void phytium_gpio_set(struct gpio_chip *gc, unsigned int offset, int value); + +int phytium_gpio_get_direction(struct gpio_chip *gc, unsigned int offset); +int phytium_gpio_direction_input(struct gpio_chip *gc, unsigned int offset); +int phytium_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value); + +void phytium_gpio_irq_ack(struct irq_data *d); +void phytium_gpio_irq_mask(struct irq_data *d); +void phytium_gpio_irq_unmask(struct irq_data *d); +int phytium_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type); +void phytium_gpio_irq_print_chip(struct irq_data *data, struct seq_file *p); +void phytium_gpio_irq_enable(struct irq_data *d); +void phytium_gpio_irq_disable(struct irq_data *d); +void phytium_gpio_irq_handler(struct irq_desc *desc); +int phytium_gpio_irq_set_affinity(struct irq_data *d, const struct cpumask *mask_val, bool force); +#endif diff --git a/drivers/gpio/gpio-phytium-pci.c b/drivers/gpio/gpio-phytium-pci.c new file mode 100644 index 0000000000000000000000000000000000000000..b5e2092729c92c866452782b347a5a8cda8a2f03 --- /dev/null +++ b/drivers/gpio/gpio-phytium-pci.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "gpio-phytium-core.h" + +static const struct irq_chip phytium_gpio_irq_chip = { + .irq_ack = phytium_gpio_irq_ack, + .irq_mask = phytium_gpio_irq_mask, + .irq_unmask = phytium_gpio_irq_unmask, + .irq_set_type = phytium_gpio_irq_set_type, + .irq_print_chip = phytium_gpio_irq_print_chip, + .irq_enable = phytium_gpio_irq_enable, + .irq_disable = phytium_gpio_irq_disable, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static int phytium_gpio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct device *dev = &pdev->dev; + struct phytium_gpio *gpio; + struct gpio_irq_chip *girq; + int err; + + gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + err = pcim_enable_device(pdev); + if (err) { + dev_err(dev, "Failed to enable PCI device: err %d\n", err); + goto out; + } + + err = pcim_iomap_regions(pdev, 1 << 0, pci_name(pdev)); + if (err) { + dev_err(dev, "Failed to iomap PCI device: err %d\n", err); + goto out; + } + + gpio->regs = pcim_iomap_table(pdev)[0]; + if (!gpio->regs) { + dev_err(dev, "Cannot map PCI resource\n"); + err = -ENOMEM; + goto out; + } + + err = pci_enable_msi(pdev); + if (err < 0) + goto out; + + pci_set_master(pdev); + + gpio->irq[0] = pdev->irq; + if (gpio->irq[0] < 0) + dev_warn(dev, "no irq is found.\n"); + + /* There is only one group of Pins at the moment. */ + gpio->ngpio[0] = NGPIO_MAX; + + /* irq_chip support */ + raw_spin_lock_init(&gpio->lock); + + gpio->gc.base = -1; + gpio->gc.get_direction = phytium_gpio_get_direction; + gpio->gc.direction_input = phytium_gpio_direction_input; + gpio->gc.direction_output = phytium_gpio_direction_output; + gpio->gc.get = phytium_gpio_get; + gpio->gc.set = phytium_gpio_set; + gpio->gc.ngpio = gpio->ngpio[0] + gpio->ngpio[1]; + gpio->gc.label = dev_name(dev); + gpio->gc.parent = dev; + gpio->gc.owner = THIS_MODULE; + + girq = &gpio->gc.irq; + girq->handler = handle_bad_irq; + girq->default_type = IRQ_TYPE_NONE; + + girq->num_parents = 1; + girq->parents = devm_kcalloc(&pdev->dev, girq->num_parents, + sizeof(*girq->parents), GFP_KERNEL); + if (!girq->parents) + return -ENOMEM; + girq->parents[0] = gpio->irq[0]; + girq->parent_handler = phytium_gpio_irq_handler; + + gpio_irq_chip_set_chip(girq, &phytium_gpio_irq_chip); + + err = devm_gpiochip_add_data(dev, &gpio->gc, gpio); + if (err) + goto out; + + dev_info(dev, "Phytium PCI GPIO controller @%pa registered\n", + &gpio->regs); + + pci_set_drvdata(pdev, gpio); + +out: + return err; +} + +static const struct pci_device_id phytium_gpio_pci_ids[] = { + { PCI_DEVICE(0x1DB7, 0xDC31) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, phytium_gpio_pci_ids); + +#ifdef CONFIG_PM_SLEEP +static int phytium_gpio_pci_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct phytium_gpio *gpio = pci_get_drvdata(pdev); + unsigned long flags; + + raw_spin_lock_irqsave(&gpio->lock, flags); + + gpio->ctx.swporta_dr = readl(gpio->regs + GPIO_SWPORTA_DR); + gpio->ctx.swporta_ddr = readl(gpio->regs + GPIO_SWPORTA_DDR); + gpio->ctx.ext_porta = readl(gpio->regs + GPIO_EXT_PORTA); + gpio->ctx.swportb_dr = readl(gpio->regs + GPIO_SWPORTB_DR); + gpio->ctx.swportb_ddr = readl(gpio->regs + GPIO_SWPORTB_DDR); + gpio->ctx.ext_portb = readl(gpio->regs + GPIO_EXT_PORTB); + + gpio->ctx.inten = readl(gpio->regs + GPIO_INTEN); + gpio->ctx.intmask = readl(gpio->regs + GPIO_INTMASK); + gpio->ctx.inttype_level = readl(gpio->regs + GPIO_INTTYPE_LEVEL); + gpio->ctx.int_polarity = readl(gpio->regs + GPIO_INT_POLARITY); + gpio->ctx.debounce = readl(gpio->regs + GPIO_DEBOUNCE); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); + + return 0; +} + +static int phytium_gpio_pci_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct phytium_gpio *gpio = pci_get_drvdata(pdev); + unsigned long flags; + + raw_spin_lock_irqsave(&gpio->lock, flags); + + writel(gpio->ctx.swporta_dr, gpio->regs + GPIO_SWPORTA_DR); + writel(gpio->ctx.swporta_ddr, gpio->regs + GPIO_SWPORTA_DDR); + writel(gpio->ctx.ext_porta, gpio->regs + GPIO_EXT_PORTA); + writel(gpio->ctx.swportb_dr, gpio->regs + GPIO_SWPORTB_DR); + writel(gpio->ctx.swportb_ddr, gpio->regs + GPIO_SWPORTB_DDR); + writel(gpio->ctx.ext_portb, gpio->regs + GPIO_EXT_PORTB); + + writel(gpio->ctx.inten, gpio->regs + GPIO_INTEN); + writel(gpio->ctx.intmask, gpio->regs + GPIO_INTMASK); + writel(gpio->ctx.inttype_level, gpio->regs + GPIO_INTTYPE_LEVEL); + writel(gpio->ctx.int_polarity, gpio->regs + GPIO_INT_POLARITY); + writel(gpio->ctx.debounce, gpio->regs + GPIO_DEBOUNCE); + + writel(0xffffffff, gpio->regs + GPIO_PORTA_EOI); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_gpio_pci_pm_ops, + phytium_gpio_pci_suspend, + phytium_gpio_pci_resume); + +static struct pci_driver phytium_gpio_pci_driver = { + .name = "gpio-phytium-pci", + .id_table = phytium_gpio_pci_ids, + .probe = phytium_gpio_pci_probe, + .driver = { + .pm = &phytium_gpio_pci_pm_ops, + }, +}; + +module_pci_driver(phytium_gpio_pci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cheng Quan "); +MODULE_DESCRIPTION("Phytium GPIO PCI Driver"); diff --git a/drivers/gpio/gpio-phytium-platform.c b/drivers/gpio/gpio-phytium-platform.c new file mode 100644 index 0000000000000000000000000000000000000000..b95f0315cf981f53181c2b19050927a725b56d9d --- /dev/null +++ b/drivers/gpio/gpio-phytium-platform.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support functions for Phytium GPIO + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + * + * Derived from drivers/gpio/gpio-pl061.c + * Copyright (C) 2008, 2009 Provigent Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpio-phytium-core.h" + +static const struct of_device_id phytium_gpio_of_match[] = { + { .compatible = "phytium,gpio", }, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_gpio_of_match); + +static const struct acpi_device_id phytium_gpio_acpi_match[] = { + { "PHYT0001", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, phytium_gpio_acpi_match); + +static const struct irq_chip phytium_gpio_irq_chip = { + .irq_ack = phytium_gpio_irq_ack, + .irq_mask = phytium_gpio_irq_mask, + .irq_unmask = phytium_gpio_irq_unmask, + .irq_set_type = phytium_gpio_irq_set_type, + .irq_print_chip = phytium_gpio_irq_print_chip, + .irq_enable = phytium_gpio_irq_enable, + .irq_disable = phytium_gpio_irq_disable, + .irq_set_affinity = phytium_gpio_irq_set_affinity, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static int phytium_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct phytium_gpio *gpio; + struct gpio_irq_chip *girq; + struct fwnode_handle *fwnode; + int err, irq_count; + + gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + gpio->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(gpio->regs)) + return PTR_ERR(gpio->regs); + + if (!device_get_child_node_count(dev)) + return -ENODEV; + + device_for_each_child_node(dev, fwnode) { + int idx; + + if (fwnode_property_read_u32(fwnode, "reg", &idx) || + idx >= MAX_NPORTS) { + dev_err(dev, "missing/invalid port index\n"); + fwnode_handle_put(fwnode); + return -EINVAL; + } + + if (fwnode_property_read_u32(fwnode, "ngpios", + &gpio->ngpio[idx]) + && fwnode_property_read_u32(fwnode, "nr-gpios", + &gpio->ngpio[idx])) { + dev_info(dev, + "failed to get number of gpios for Port%c\n", + idx ? 'B' : 'A'); + gpio->ngpio[idx] = NGPIO_DEFAULT; + } + } + + /* irq_chip support */ + raw_spin_lock_init(&gpio->lock); + + gpio->gc.base = -1; + gpio->gc.get_direction = phytium_gpio_get_direction; + gpio->gc.direction_input = phytium_gpio_direction_input; + gpio->gc.direction_output = phytium_gpio_direction_output; + gpio->gc.get = phytium_gpio_get; + gpio->gc.set = phytium_gpio_set; + gpio->gc.ngpio = gpio->ngpio[0] + gpio->ngpio[1]; + gpio->gc.label = dev_name(dev); + gpio->gc.parent = dev; + gpio->gc.owner = THIS_MODULE; + + girq = &gpio->gc.irq; + girq->handler = handle_bad_irq; + girq->default_type = IRQ_TYPE_NONE; + + for (irq_count = 0; irq_count < platform_irq_count(pdev); irq_count++) { + gpio->irq[irq_count] = -ENXIO; + gpio->irq[irq_count] = platform_get_irq(pdev, irq_count); + if (gpio->irq[irq_count] < 0) { + dev_warn(dev, "no irq is found.\n"); + break; + } + }; + + girq->num_parents = irq_count; + girq->parents = gpio->irq; + girq->parent_handler = phytium_gpio_irq_handler; + + gpio_irq_chip_set_chip(girq, &phytium_gpio_irq_chip); + + err = devm_gpiochip_add_data(dev, &gpio->gc, gpio); + if (err) + return err; + + platform_set_drvdata(pdev, gpio); + dev_info(dev, "Phytium GPIO controller @%pa registered\n", + &res->start); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int phytium_gpio_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct phytium_gpio *gpio = platform_get_drvdata(pdev); + unsigned long flags; + + raw_spin_lock_irqsave(&gpio->lock, flags); + + gpio->ctx.swporta_dr = readl(gpio->regs + GPIO_SWPORTA_DR); + gpio->ctx.swporta_ddr = readl(gpio->regs + GPIO_SWPORTA_DDR); + gpio->ctx.ext_porta = readl(gpio->regs + GPIO_EXT_PORTA); + gpio->ctx.swportb_dr = readl(gpio->regs + GPIO_SWPORTB_DR); + gpio->ctx.swportb_ddr = readl(gpio->regs + GPIO_SWPORTB_DDR); + gpio->ctx.ext_portb = readl(gpio->regs + GPIO_EXT_PORTB); + + gpio->ctx.inten = readl(gpio->regs + GPIO_INTEN); + gpio->ctx.intmask = readl(gpio->regs + GPIO_INTMASK); + gpio->ctx.inttype_level = readl(gpio->regs + GPIO_INTTYPE_LEVEL); + gpio->ctx.int_polarity = readl(gpio->regs + GPIO_INT_POLARITY); + gpio->ctx.debounce = readl(gpio->regs + GPIO_DEBOUNCE); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); + + return 0; +} + +static int phytium_gpio_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct phytium_gpio *gpio = platform_get_drvdata(pdev); + unsigned long flags; + + raw_spin_lock_irqsave(&gpio->lock, flags); + + writel(gpio->ctx.swporta_dr, gpio->regs + GPIO_SWPORTA_DR); + writel(gpio->ctx.swporta_ddr, gpio->regs + GPIO_SWPORTA_DDR); + writel(gpio->ctx.ext_porta, gpio->regs + GPIO_EXT_PORTA); + writel(gpio->ctx.swportb_dr, gpio->regs + GPIO_SWPORTB_DR); + writel(gpio->ctx.swportb_ddr, gpio->regs + GPIO_SWPORTB_DDR); + writel(gpio->ctx.ext_portb, gpio->regs + GPIO_EXT_PORTB); + + writel(gpio->ctx.inten, gpio->regs + GPIO_INTEN); + writel(gpio->ctx.intmask, gpio->regs + GPIO_INTMASK); + writel(gpio->ctx.inttype_level, gpio->regs + GPIO_INTTYPE_LEVEL); + writel(gpio->ctx.int_polarity, gpio->regs + GPIO_INT_POLARITY); + writel(gpio->ctx.debounce, gpio->regs + GPIO_DEBOUNCE); + + writel(0xffffffff, gpio->regs + GPIO_PORTA_EOI); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_gpio_pm_ops, phytium_gpio_suspend, + phytium_gpio_resume); + +static struct platform_driver phytium_gpio_driver = { + .driver = { + .name = "gpio-phytium-platform", + .pm = &phytium_gpio_pm_ops, + .of_match_table = of_match_ptr(phytium_gpio_of_match), + .acpi_match_table = ACPI_PTR(phytium_gpio_acpi_match), + }, + .probe = phytium_gpio_probe, +}; + +module_platform_driver(phytium_gpio_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Phytium GPIO driver"); diff --git a/drivers/gpio/gpio-phytium-sgpio.c b/drivers/gpio/gpio-phytium-sgpio.c new file mode 100644 index 0000000000000000000000000000000000000000..bc16f8d4a0d802491c0637beb6f241d9326091a6 --- /dev/null +++ b/drivers/gpio/gpio-phytium-sgpio.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SGPIO Driver + * + * Copyright (c) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SGPIO_CTL0_REG 0x00 +#define SGPIO_CTL0_REG_ENABLE BIT(0) +#define SGPIO_CTL0_REG_RX_DISABLE BIT(1) +#define SGPIO_CTL0_REG_L3_L0 GENMASK(11, 8) +#define SGPIO_CTL0_REG_CLK_DIV_NUM GENMASK(31, 12) +#define SGPIO_CTL1_REG 0x04 +#define SGPIO_CTL1_REG_READY BIT(0) +#define SGPIO_CTL1_REG_W_UPDATA BIT(1) +#define SGPIO_CTL1_REG_OP_MODE BIT(2) +#define SGPIO_CTL1_REG_OP_STATE BIT(3) +#define SGPIO_CTL1_REG_BIT_NUM GENMASK(14, 8) +#define SGPIO_CTL1_REG_INTERVAL_TIMER GENMASK(31, 16) +#define SGPIO_SOFT_RESET_REG 0x08 +#define SGPIO_SOFT_RESET_REG_MASK BIT(0) +#define SGPIO_IRQ_REG 0x0c +#define SGPIO_IRQ_REG_MASK BIT(0) +#define SGPIO_IRQ_M_REG 0x10 +#define SGPIO_IRQ_M_REG_MASK BIT(0) +#define SGPIO_WDATA0_REG 0x14 +#define SGPIO_WDATA_REG(x) (SGPIO_WDATA0_REG + (x) * 4) +#define SGPIO_RDATA0_REG 0x24 +#define SGPIO_RDATA_REG(x) (SGPIO_RDATA0_REG + (x) * 4) + +#define DEFAULT_L3_L0 0 +#define DEFAULT_CLK 50000000 + +#define GPIO_GROUP(x) ((x) >> 6) +#define GPIO_OFFSET(x) ((x) & GENMASK(5, 0)) +#define GPIO_BIT(x) BIT(GPIO_OFFSET(x) >> 1) + +struct phytium_sgpio { + struct gpio_chip gc; + void __iomem *regs; + unsigned int ngpios; + struct clk *pclk; + + struct mutex lock; + struct completion completion; +}; + +static bool phytium_sgpio_is_input(unsigned int offset) +{ + return !(offset % 2); +} + +static int sgpio_set_value(struct gpio_chip *gc, unsigned int offset, int val) +{ + struct phytium_sgpio *gpio = gpiochip_get_data(gc); + u32 reg; + int rc = 0; + + if (phytium_sgpio_is_input(offset)) + return -EINVAL; + + reinit_completion(&gpio->completion); + + /* + * Since this is an output, read the cached value from rdata, + * then update value. + */ + reg = readl(gpio->regs + SGPIO_RDATA_REG(GPIO_GROUP(offset))); + if (val) + reg |= GPIO_BIT(offset); + else + reg &= GPIO_BIT(offset); + writel(reg, gpio->regs + SGPIO_WDATA_REG(GPIO_GROUP(offset))); + + /* Start transmission and wait for completion */ + writel(readl(gpio->regs + SGPIO_CTL1_REG) | SGPIO_CTL1_REG_W_UPDATA, + gpio->regs + SGPIO_CTL1_REG); + if (!wait_for_completion_timeout(&gpio->completion, msecs_to_jiffies(1000))) + rc = -EINVAL; + + return rc; +} + +static int phytium_sgpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + return phytium_sgpio_is_input(offset) ? 0 : -EINVAL; +} + +static int phytium_sgpio_direction_output(struct gpio_chip *gc, unsigned int offset, int val) +{ + struct phytium_sgpio *gpio = gpiochip_get_data(gc); + int rc; + + mutex_lock(&gpio->lock); + + /* + * No special action is required for setting the direction; we'll + * error-out in sgpio_set_value if this isn't an output GPIO + */ + rc = sgpio_set_value(&gpio->gc, offset, val); + + mutex_unlock(&gpio->lock); + + return rc; +} + +static int phytium_sgpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return !!phytium_sgpio_is_input(offset); +} + +static int phytium_sgpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct phytium_sgpio *gpio = gpiochip_get_data(gc); + int rc = 0; + u32 val, ctl0; + + mutex_lock(&gpio->lock); + + if (!phytium_sgpio_is_input(offset)) { + val = readl(gpio->regs + SGPIO_WDATA_REG(GPIO_GROUP(offset))); + rc = !!(val & GPIO_BIT(offset)); + mutex_unlock(&gpio->lock); + return rc; + } + + reinit_completion(&gpio->completion); + + /* Enable Rx */ + ctl0 = readl(gpio->regs + SGPIO_CTL0_REG); + writel(ctl0 & ~SGPIO_CTL0_REG_RX_DISABLE, gpio->regs + SGPIO_CTL0_REG); + + /* Start reading transaction and wait for completion */ + writel(readl(gpio->regs + SGPIO_CTL1_REG) | SGPIO_CTL1_REG_W_UPDATA, + gpio->regs + SGPIO_CTL1_REG); + if (!wait_for_completion_timeout(&gpio->completion, msecs_to_jiffies(1000))) { + rc = -EINVAL; + goto err; + } + + val = readl(gpio->regs + SGPIO_RDATA_REG(GPIO_GROUP(offset))); + rc = !!(val & GPIO_BIT(offset)); + +err: + /* Disalbe Rx to hold the value */ + writel(ctl0 | SGPIO_CTL0_REG_RX_DISABLE, gpio->regs + SGPIO_CTL0_REG); + mutex_unlock(&gpio->lock); + + return rc; +} + +static void phytium_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val) +{ + struct phytium_sgpio *gpio = gpiochip_get_data(gc); + + mutex_lock(&gpio->lock); + + sgpio_set_value(gc, offset, val); + + mutex_unlock(&gpio->lock); +} + +static irqreturn_t phytium_sgpio_irq_handler(int irq, void *data) +{ + struct phytium_sgpio *gpio = data; + + if (!readl(gpio->regs + SGPIO_IRQ_REG)) + return IRQ_NONE; + + /* Clear the interrupt */ + writel(0, gpio->regs + SGPIO_IRQ_REG); + + /* Check if tx/rx has been done */ + if (!(readl(gpio->regs + SGPIO_CTL1_REG) & SGPIO_CTL1_REG_OP_STATE)) + complete(&gpio->completion); + + return IRQ_HANDLED; +} + +static int phytium_sgpio_probe(struct platform_device *pdev) +{ + u32 pclk_freq, sclk_freq, clk_div; + struct phytium_sgpio *gpio; + struct resource *res; + struct device *dev = &pdev->dev; + int rc; + + gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + gpio->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(gpio->regs)) + return PTR_ERR(gpio->regs); + + if (devm_request_irq(dev, platform_get_irq(pdev, 0), + phytium_sgpio_irq_handler, + IRQF_SHARED, dev_name(dev), gpio)) { + dev_err(dev, "failed to request IRQ\n"); + return -ENOENT; + } + + rc = fwnode_property_read_u32(dev_fwnode(dev), "ngpios", &gpio->ngpios); + if (rc < 0) { + dev_err(dev, "Could not read ngpios property\n"); + return -EINVAL; + } else if (gpio->ngpios % 32) { + dev_err(&pdev->dev, "Number of GPIOs not multiple of 32: %d\n", + gpio->ngpios); + return -EINVAL; + } + + rc = fwnode_property_read_u32(dev_fwnode(dev), "bus-frequency", &sclk_freq); + if (rc < 0) { + dev_err(dev, "Could not read bus-frequency property\n"); + return -EINVAL; + } + + if (has_acpi_companion(dev)) { + device_property_read_u32(dev, "pclk_freq", &pclk_freq); + if (!pclk_freq || (pclk_freq != 50000000)) { + dev_err(dev, "Could not get APB clock property from acpi, use default clk!\n"); + pclk_freq = DEFAULT_CLK; + } + + } else { + gpio->pclk = devm_clk_get(dev, NULL); + if (IS_ERR(gpio->pclk)) { + dev_err(dev, "Could not get the APB clock property\n"); + return PTR_ERR(gpio->pclk); + } + rc = clk_prepare_enable(gpio->pclk); + if (rc) { + dev_err(dev, "failed to enable pclk: %d\n", rc); + return rc; + } + pclk_freq = clk_get_rate(gpio->pclk); + } + + /* + * From the datasheet: + * (pclk / 2) / (clk_div + 1) = sclk + */ + if (sclk_freq == 0) { + dev_err(dev, "SCLK should not be 0\n"); + return -EINVAL; + } + + clk_div = (pclk_freq / (sclk_freq * 2)) - 1; + if (clk_div > (1 << 20) - 1) { + dev_err(dev, "clk_div is overflow\n"); + return -EINVAL; + } + + writel(FIELD_PREP(SGPIO_CTL0_REG_CLK_DIV_NUM, clk_div) | + FIELD_PREP(SGPIO_CTL0_REG_L3_L0, DEFAULT_L3_L0) | + SGPIO_CTL0_REG_RX_DISABLE | SGPIO_CTL0_REG_ENABLE, + gpio->regs + SGPIO_CTL0_REG); + + writel(FIELD_PREP(SGPIO_CTL1_REG_BIT_NUM, gpio->ngpios) | + SGPIO_CTL1_REG_READY, gpio->regs + SGPIO_CTL1_REG); + + mutex_init(&gpio->lock); + init_completion(&gpio->completion); + platform_set_drvdata(pdev, gpio); + + gpio->gc.parent = dev; + gpio->gc.base = -1; + gpio->gc.ngpio = gpio->ngpios * 2; + gpio->gc.label = dev_name(dev); + gpio->gc.direction_input = phytium_sgpio_direction_input; + gpio->gc.direction_output = phytium_sgpio_direction_output; + gpio->gc.get_direction = phytium_sgpio_get_direction; + gpio->gc.get = phytium_sgpio_get; + gpio->gc.set = phytium_sgpio_set; + + return devm_gpiochip_add_data(dev, &gpio->gc, gpio); +} + +static const struct of_device_id phytium_sgpio_of_match[] = { + { .compatible = "phytium,sgpio", }, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_sgpio_of_match); + +static const struct acpi_device_id phytium_sgpio_acpi_match[] = { + { "PHYT0031", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, phytium_sgpio_acpi_match); + +static struct platform_driver phytium_sgpio_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = of_match_ptr(phytium_sgpio_of_match), + .acpi_match_table = ACPI_PTR(phytium_sgpio_acpi_match), + }, + .probe = phytium_sgpio_probe, +}; +module_platform_driver(phytium_sgpio_driver); + +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Phytium SGPIO driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 3a5b9aae49757d874504dc088555518ea5fdd642..0a846b3141641f10b86cc605cdbd5e469ddeddd7 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -437,6 +437,8 @@ source "drivers/gpu/drm/sprd/Kconfig" source "drivers/gpu/drm/arise/Kconfig" +source "drivers/gpu/drm/phytium/Kconfig" + config DRM_HYPERV tristate "DRM Support for Hyper-V synthetic video device" depends on DRM && PCI && MMU && HYPERV diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 16d13e0f40f5cc263309e0b6df59c6bf8aacfd2b..26b46b04c83115674e577c4fdaffb6a61e355294 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -201,3 +201,4 @@ obj-y += solomon/ obj-$(CONFIG_DRM_SPRD) += sprd/ obj-$(CONFIG_DRM_LOONGSON) += loongson/ obj-$(CONFIG_DRM_ARISE) += arise/ +obj-$(CONFIG_DRM_PHYTIUM) += phytium/ diff --git a/drivers/gpu/drm/arise/Makefile b/drivers/gpu/drm/arise/Makefile index 793986b02ddaeab62dd571626980fde2cf91fe4e..17364abbe928ed468052afb95c3401d5804fec74 100644 --- a/drivers/gpu/drm/arise/Makefile +++ b/drivers/gpu/drm/arise/Makefile @@ -1,12 +1,11 @@ CHIP?=E3k DRIVER_NAME?=arise -PRO_DRIVER_NAME=$(DRIVER_NAME) TARGET_ARCH?=x86_64 DEBUG?=0 VIDEO_ONLY_FPGA?=0 RUN_HW_NULL?=0 HW_NULL?=0 -CONFIG-GFGPU=m +CONFIG_DRM_ARISE?=m ifeq ("$(M)", "") CHECK_GCC_VERSION?=0 else @@ -80,4 +79,4 @@ endif include $(GFGPU_FULL_PATH)/core/Makefile include $(GFGPU_FULL_PATH)/cbios/cbios.mk include $(GFGPU_FULL_PATH)/linux/Makefile -obj-$(CONFIG_DRM_ARISE) := $(PRO_DRIVER_NAME).o +obj-$(CONFIG_DRM_ARISE) := $(DRIVER_NAME).o diff --git a/drivers/gpu/drm/arise/cbios/Device/CBIOSVER.H b/drivers/gpu/drm/arise/cbios/Device/CBIOSVER.H old mode 100755 new mode 100644 diff --git a/drivers/gpu/drm/arise/cbios/Device/CBiosDevice.c b/drivers/gpu/drm/arise/cbios/Device/CBiosDevice.c index 3eac1727a2f3192f29cd41d159f9d8c3f48a8caa..299da5aea8f8155f9d956e9d782260cc8a645944 100644 --- a/drivers/gpu/drm/arise/cbios/Device/CBiosDevice.c +++ b/drivers/gpu/drm/arise/cbios/Device/CBiosDevice.c @@ -385,7 +385,10 @@ CBIOS_STATUS cbDevGetEdidFromBuffer(PCBIOS_VOID pvcbe, PCBIOS_DEVICE_COMMON pDev else { bRetStatus = CBIOS_ER_EDID_INVALID; - cbDebugPrint((MAKE_LEVEL(GENERIC, DEBUG), "%s: Not a valid EDID!\n", FUNCTION_NAME)); + if(pDevCommon->DeviceType != CBIOS_TYPE_CRT) + { + cbDebugPrint((MAKE_LEVEL(GENERIC, WARNING), "Can't get valid EDID for device 0x%x!\n", pDevCommon->DeviceType)); + } } return bRetStatus; diff --git a/drivers/gpu/drm/arise/cbios/Device/CBiosShare.h b/drivers/gpu/drm/arise/cbios/Device/CBiosShare.h index d0e5290069c8c2ef808d8e51439a6629b9a40efa..dbda809f6d186428fd865f8bb15e434270375ccd 100644 --- a/drivers/gpu/drm/arise/cbios/Device/CBiosShare.h +++ b/drivers/gpu/drm/arise/cbios/Device/CBiosShare.h @@ -224,7 +224,7 @@ static inline CBIOS_U32 cb_swab32(CBIOS_U32 x) #define cbmemset(s1, v, n) CBIOS_NULL #define cbmemcpy(s1, s2, n) CBIOS_NULL #define cbmemcmp(s1, s2, n) 0 -#define cbdo_div(a, b) 0 +#define cbdo_div(a, b) 0 #define cbvsprintf(s, f, ...) 0 #else diff --git a/drivers/gpu/drm/arise/cbios/Device/Monitor/CBiosCRTMonitor.c b/drivers/gpu/drm/arise/cbios/Device/Monitor/CBiosCRTMonitor.c index 7597ec0c546b23708a98cbc1a3bb8805aba28604..d0452a209b18597c4239d2a1fb84c6db20918345 100644 --- a/drivers/gpu/drm/arise/cbios/Device/Monitor/CBiosCRTMonitor.c +++ b/drivers/gpu/drm/arise/cbios/Device/Monitor/CBiosCRTMonitor.c @@ -32,6 +32,7 @@ CBIOS_BOOL cbCRTMonitor_Detect(PCBIOS_VOID pvcbe, PCBIOS_CRT_MONITOR_CONTEXT pCr CBIOS_BOOL bGotEdid = CBIOS_FALSE; CBIOS_BOOL IsDevChanged = CBIOS_FALSE; CBIOS_BOOL bConnected = CBIOS_FALSE; + CBIOS_BOOL bPrevEdidValid = CBIOS_FALSE; if (bHardcodeDetected) { @@ -40,6 +41,8 @@ CBIOS_BOOL cbCRTMonitor_Detect(PCBIOS_VOID pvcbe, PCBIOS_CRT_MONITOR_CONTEXT pCr goto EXIT; } + bPrevEdidValid = cbEDIDModule_IsEDIDValid(pDevCommon->EdidData); + bGotEdid = cbGetDeviceEDID(pcbe, pDevCommon, &IsDevChanged, FullDetect); if ((*(pDevCommon->EdidData + EDID_VIDEO_INPUT_DEF_BYTE_OFFSET)) & EDID_VIDEO_INPUT_DEF_DIGITAL) @@ -67,8 +70,8 @@ CBIOS_BOOL cbCRTMonitor_Detect(PCBIOS_VOID pvcbe, PCBIOS_CRT_MONITOR_CONTEXT pCr //then after resume,driver will not enter some hdmi module related codes,so monitor can't light //so not memset pDevCommon->EdidStruct when device is not connected cbClearEdidRelatedData(pcbe, pDevCommon); - - if (cbDIU_CRT_DACSense(pcbe, pCrtMonitorContext)) + + if(cbDIU_CRT_DACSense(pcbe, pDevCommon, bPrevEdidValid)) { pDevCommon->CurrentMonitorType = CBIOS_MONITOR_TYPE_CRT; bConnected = CBIOS_TRUE; diff --git a/drivers/gpu/drm/arise/cbios/Display/CBiosMode.c b/drivers/gpu/drm/arise/cbios/Display/CBiosMode.c index d8be23b67a230b8922c94dc11e9c34e719b82150..7f80e3c982556200c00f67766c3b59d3dd971525 100644 --- a/drivers/gpu/drm/arise/cbios/Display/CBiosMode.c +++ b/drivers/gpu/drm/arise/cbios/Display/CBiosMode.c @@ -5667,6 +5667,14 @@ CBIOS_VOID cbMode_GetFilterPara(PCBIOS_VOID pvcbe, CBIOS_ACTIVE_TYPE Device, PCB break; } + if((pcbe->ChipID == CHIPID_ARISE2030) || (pcbe->ChipID == CHIPID_ARISE2020)) + { + if((Device == CBIOS_TYPE_DP2) && (MonitorType == CBIOS_MONITOR_TYPE_HDMI)) + { + pFilter->MaxDclk = 3400000; + } + } + if(pFilter->MaxDclk > pcbe->ChipLimits.ulMaxIGAClock) { pFilter->MaxDclk = pcbe->ChipLimits.ulMaxIGAClock; diff --git a/drivers/gpu/drm/arise/cbios/Display/CBiosPathManager.c b/drivers/gpu/drm/arise/cbios/Display/CBiosPathManager.c index 173d7e6cef701d51c8b65f942b28445562a54e0a..8b9279ba346500d20a0310be93e7eef5ef490005 100644 --- a/drivers/gpu/drm/arise/cbios/Display/CBiosPathManager.c +++ b/drivers/gpu/drm/arise/cbios/Display/CBiosPathManager.c @@ -34,14 +34,15 @@ CBIOS_STATUS cbPathMgrGetDevComb(PCBIOS_VOID pvcbe, PCBIOS_GET_DEV_COMB pDevComb PCBIOS_EXTENSION_COMMON pcbe = (PCBIOS_EXTENSION_COMMON)pvcbe; PCBIOS_DEVICE_COMB pDeviceComb = pDevComb->pDeviceComb; CBIOS_ACTIVE_TYPE Devices = pDeviceComb->Devices; - - pDeviceComb->Iga1Dev = CBIOS_TYPE_NONE; - pDeviceComb->Iga2Dev = CBIOS_TYPE_NONE; - pDeviceComb->Iga3Dev = CBIOS_TYPE_NONE; - pDeviceComb->Iga4Dev = CBIOS_TYPE_NONE; + CBIOS_ACTIVE_TYPE TempDev = CBIOS_TYPE_NONE; + CBIOS_GET_IGA_MASK GetIgaMask = {0}; + CBIOS_U32 IgaMask = 0, IgaIndex = 0; + CBIOS_U32 DevOnIga[CBIOS_IGACOUNTS] = {0}; + CBIOS_BOOL bAssigned = CBIOS_FALSE; if (Devices == CBIOS_TYPE_NONE) { + cbDebugPrint((MAKE_LEVEL(GENERIC, INFO), "%s: Devices is NONE ! \n", FUNCTION_NAME)); return CBIOS_OK; } @@ -58,51 +59,54 @@ CBIOS_STATUS cbPathMgrGetDevComb(PCBIOS_VOID pvcbe, PCBIOS_GET_DEV_COMB pDevComb pDevComb->bSupported = CBIOS_FALSE; return CBIOS_ER_INVALID_PARAMETER; } - else - { - if(Devices & CBIOS_TYPE_DP1) - { - pDevComb->pDeviceComb->Iga1Dev = CBIOS_TYPE_DP1; - } - if(pcbe->DispMgr.IgaCount == 2) - { - if(Devices & CBIOS_TYPE_DP2) - { - pDevComb->pDeviceComb->Iga2Dev = CBIOS_TYPE_DP2; - } - else if(Devices & CBIOS_TYPE_CRT) - { - pDevComb->pDeviceComb->Iga2Dev = CBIOS_TYPE_CRT; - } - } - else if(pcbe->DispMgr.IgaCount == 3) + GetIgaMask.Size = sizeof(CBIOS_GET_IGA_MASK); + while(Devices) + { + bAssigned = CBIOS_FALSE; + + //select a high priority device if more than one device + TempDev = cbDevGetPrimaryDevice(Devices); + Devices &= ~TempDev; + GetIgaMask.DeviceId = (CBIOS_U32)TempDev; + cbPathMgrGetIgaMask(pcbe, &GetIgaMask); + + IgaMask = GetIgaMask.IgaMask; + while(IgaMask) { - if(Devices & CBIOS_TYPE_DP3) - { - pDevComb->pDeviceComb->Iga3Dev = CBIOS_TYPE_DP3; - } - else if(Devices & CBIOS_TYPE_CRT) + IgaIndex = cbGetLastBitIndex(IgaMask); + IgaMask &= ~(1 << IgaIndex); + if(DevOnIga[IgaIndex] == CBIOS_TYPE_NONE) { - pDevComb->pDeviceComb->Iga3Dev = CBIOS_TYPE_CRT; + DevOnIga[IgaIndex] = TempDev; + bAssigned = CBIOS_TRUE; + break; } } - else if(pcbe->DispMgr.IgaCount == 4) + + if (!bAssigned) { - if(Devices & CBIOS_TYPE_DP4) - { - pDevComb->pDeviceComb->Iga4Dev = CBIOS_TYPE_DP4; - } - else if(Devices & CBIOS_TYPE_CRT) - { - pDevComb->pDeviceComb->Iga4Dev = CBIOS_TYPE_CRT; - } + cbDebugPrint((MAKE_LEVEL(GENERIC, ERROR), "%s: Can't assign IGA for device: 0x%x\n", FUNCTION_NAME, TempDev)); + break; } + } + + if(bAssigned) + { + pDeviceComb->Iga1Dev = DevOnIga[IGA1]; + pDeviceComb->Iga2Dev = DevOnIga[IGA2]; + pDeviceComb->Iga3Dev = DevOnIga[IGA3]; + pDeviceComb->Iga4Dev = DevOnIga[IGA4]; pDevComb->bSupported = CBIOS_TRUE; + return CBIOS_OK; } - - return CBIOS_OK; + else + { + pDevComb->bSupported = CBIOS_FALSE; + return CBIOS_ER_INVALID_PARAMETER; + } + } CBIOS_STATUS cbPathMgrGetIgaMask(PCBIOS_VOID pvcbe, PCBIOS_GET_IGA_MASK pGetIgaMask) diff --git a/drivers/gpu/drm/arise/cbios/Hw/Arise/CBios_Arise.c b/drivers/gpu/drm/arise/cbios/Hw/Arise/CBios_Arise.c index da5c7f17cc8eb9871cc2e4ba0fe9e6d51d57e8b1..35b95fa9dd9c71b25101ff96b79ccb03727fe581 100644 --- a/drivers/gpu/drm/arise/cbios/Hw/Arise/CBios_Arise.c +++ b/drivers/gpu/drm/arise/cbios/Hw/Arise/CBios_Arise.c @@ -689,6 +689,12 @@ CBIOS_VOID cbSetSRTimingReg_Arise(PCBIOS_EXTENSION_COMMON pcbe, { cbBiosMMIOWriteReg(pcbe, CR_8F, Value, (CBIOS_U8)~0xc0, IGAIndex); } + + //Fix Arise1020/2030 VGA Hsync Timing issue + if(pcbe->DispMgr.ActiveDevices[IGAIndex] == CBIOS_TYPE_CRT) + { + cbMMIOWriteReg(pcbe, SR_18, 0x80, 0x7F); + } } diff --git a/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosDIU_CRT.c b/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosDIU_CRT.c index 75f0fbfa68b24a55d817bada7826882b9d19ace3..538fedd06a7e4f68b622d6df3cc39074da8ee375 100644 --- a/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosDIU_CRT.c +++ b/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosDIU_CRT.c @@ -29,12 +29,12 @@ static CBREGISTER NewCRTDetectEnv[] = { //notes: CBE has reserved 128 Byte array:pcbe->SavedReg[128] //Per Ping, DAC phy need clock to trigger DATA into DAC, //thus we should enable clock before setting sense data. - {CR_B,(CBIOS_U8)~0x01,0xFC, 0x00 }, //Turn on DCLK1 + //{CR_B,(CBIOS_U8)~0x01,0xFC, 0x00 }, //Turn on DCLK1 {SR,(CBIOS_U8)~0x01,0x0B, 0x00 }, //Turn on DCLK2 {SR,(CBIOS_U8)~0x20,0x18, 0x20 }, //Turn on CRT Dac1 {SR,(CBIOS_U8)~0x02,0x21, 0x02 }, //Turn on CRT Dac1 Sense power - {SR,(CBIOS_U8)~0x02,0x20, 0x00 }, //CRT DAC not off in Standby mode - {SR,(CBIOS_U8)~0x40,0x31, 0x40 }, //DAC1 Sense Data Source Select + {SR,(CBIOS_U8)~0x02,0x20, 0x00 }, //CRT DAC not off in Standby mode + {SR,(CBIOS_U8)~0x4C,0x31, 0x44 }, //DAC1 Sense Data Source Select {SR, 0x00,0x4B, 0x94 }, // R sense data {SR, 0x00,0x4C, 0x94 }, // G sense data {SR, 0x00,0x4D, 0x94 }, // B sense data @@ -175,30 +175,104 @@ CBIOS_VOID cbDIU_CRT_SyncOnOff(PCBIOS_VOID pvcbe, CBIOS_BOOL bOn) } } -CBIOS_BOOL cbDIU_CRT_DACSense(PCBIOS_VOID pvcbe, PCBIOS_CRT_MONITOR_CONTEXT pCrtMonitorContext) +typedef struct _CBIOS_DAC_SENSE_PARA +{ + CBIOS_IN CBIOS_U8 PowerState; + CBIOS_IN CBIOS_U8 PrevEdidValid; + CBIOS_OUT CBIOS_U8 Connected; // if don't need dac sense, need return connect status + CBIOS_OUT CBIOS_U8 UseNewSense; // if need dac sense, need return sense path(new or old) +}CBIOS_DAC_SENSE_PARA; + +static CBIOS_BOOL cbIsNeedDacSense(PCBIOS_EXTENSION_COMMON pcbe, CBIOS_DAC_SENSE_PARA *pSensePara) +{ + CBIOS_BOOL bNeedSense = CBIOS_FALSE; + CBIOS_BOOL bRetConnected = CBIOS_FALSE; + CBIOS_BOOL bUseNewSense = CBIOS_FALSE; + REG_SR31_Pair RegSR31Value; + + if(!pcbe || !pSensePara) + { + return CBIOS_FALSE; + } + + pSensePara->Connected = 0; + pSensePara->UseNewSense = 0; + + if(pcbe->ChipID != CHIPID_ARISE2030 && pcbe->ChipID != CHIPID_ARISE2020) + { + pSensePara->UseNewSense = (pSensePara->PowerState == CBIOS_PM_ON)? 1 : 0; + return CBIOS_TRUE; + } + + //patch for arise2030, check CRT's power and connect status + if(pSensePara->PowerState == CBIOS_PM_ON && (pcbe->DeviceMgr.ConnectedDevices & CBIOS_TYPE_CRT)) + { + //CRT is connected and turned on, check its binded IGA + RegSR31Value.Value = cbMMIOReadReg(pcbe, SR_31); + if(RegSR31Value.DAC1_Source_Select == 0x2) //source is IGA3 + { + //It's binded to IGA3, can't do dac sense, check whether previous EDID is valid + if(pSensePara->PrevEdidValid) + { + //previous EDID is valid, and can't read current EDID, it's edid-monitor plug out case + bNeedSense = CBIOS_FALSE; + bRetConnected = CBIOS_FALSE; + } + else + { + //can't detect plug out of non-EDID-monitor + bNeedSense = CBIOS_FALSE; + bRetConnected = CBIOS_TRUE; + } + } + else + { + //not bind to IGA3, do normal new dac sense + bNeedSense = CBIOS_TRUE; + bUseNewSense = CBIOS_TRUE; + } + } + else + { + //not connected or not turned on, can switch to IGA1/IGA2 to do old dac sense + bNeedSense = CBIOS_TRUE; + bUseNewSense = CBIOS_FALSE; + } + + if(bNeedSense) + { + pSensePara->UseNewSense = (bUseNewSense)? 1 : 0; + } + else + { + pSensePara->Connected = (bRetConnected)? 1 : 0; + } + + return bNeedSense; +} + +CBIOS_BOOL cbDIU_CRT_DACSense(PCBIOS_VOID pvcbe, PCBIOS_DEVICE_COMMON pDevCommon, CBIOS_BOOL bPrevEdidValid) { PCBIOS_EXTENSION_COMMON pcbe = (PCBIOS_EXTENSION_COMMON)pvcbe; - PCBIOS_DEVICE_COMMON pDevCommon = pCrtMonitorContext->pDevCommon; CBIOS_U32 IGAIndex = pDevCommon->DispSource.ModuleList.IGAModule.Index; - CBIOS_U32 bStatus = CBIOS_FALSE; + CBIOS_BOOL bStatus = CBIOS_FALSE; CBIOS_U8 by3C2; - REG_SR21 RegSR21Value; - REG_SR21 RegSR21Mask; - REG_SR31_Pair RegSR31Value; - REG_SR31_Pair RegSR31Mask; - REG_SR3F RegSR3FValue; - REG_SR3F RegSR3FMask; - REG_CR71_Pair RegCR71Value; - REG_CR71_Pair RegCR71Mask; - REG_SR4B RegSR4BValue; - REG_SR4B RegSR4BMask; - REG_SR4C RegSR4CValue; - REG_SR4C RegSR4CMask; - REG_SR4D RegSR4DValue; - REG_SR4D RegSR4DMask; - - if(pDevCommon->PowerState == CBIOS_PM_ON) + REG_SR21 RegSR21Value, RegSR21Mask; + REG_SR31_Pair RegSR31Value, RegSR31Mask; + REG_SR3F RegSR3FValue, RegSR3FMask; + REG_CR71_Pair RegCR71Value, RegCR71Mask; + CBIOS_DAC_SENSE_PARA DacSensePara = {0}; + + DacSensePara.PowerState = pDevCommon->PowerState; + DacSensePara.PrevEdidValid = (bPrevEdidValid)? 1 : 0; + + if(!cbIsNeedDacSense(pcbe, &DacSensePara)) { + return (DacSensePara.Connected)? CBIOS_TRUE : CBIOS_FALSE; + } + + if(DacSensePara.UseNewSense) + { //Use new DAC1 sense logic when CRT is on. RegSR21Value.Value = 0; RegSR21Value.DAC1_SENSE_Power_Down_Enable = 0; @@ -227,23 +301,11 @@ CBIOS_BOOL cbDIU_CRT_DACSense(PCBIOS_VOID pvcbe, PCBIOS_CRT_MONITOR_CONTEXT pCrt RegCR71Mask.SENSEL = 0; RegCR71Mask.SENWIDTH = 0; cbMMIOWriteReg(pcbe,CR_71, RegCR71Value.Value, RegCR71Mask.Value); - - RegSR4BValue.Value = 0; - RegSR4BValue.R_SENSE = 0x7A; - RegSR4BMask.Value = 0xFF; - RegSR4BMask.R_SENSE = 0; - cbMMIOWriteReg(pcbe,SR_4B, RegSR4BValue.Value, RegSR4BMask.Value); - RegSR4CValue.Value = 0; - RegSR4CValue.G_SENSE = 0x7A; - RegSR4CMask.Value = 0xFF; - RegSR4CMask.G_SENSE = 0; - cbMMIOWriteReg(pcbe,SR_4C, RegSR4CValue.Value, RegSR4CMask.Value); - RegSR4DValue.Value = 0; - RegSR4DValue.B_SENSE = 0x7A; - RegSR4DMask.Value = 0xFF; - RegSR4DMask.B_SENSE = 0; - cbMMIOWriteReg(pcbe,SR_4D, RegSR4DValue.Value, RegSR4DMask.Value); - + + cbMMIOWriteReg(pcbe,SR_4B, 0x7A, 0x00); //R sense + cbMMIOWriteReg(pcbe,SR_4C, 0x7A, 0x00); // G sense + cbMMIOWriteReg(pcbe,SR_4D, 0x7A, 0x00); // B sense + RegSR3FValue.Value = 0; RegSR3FValue.B_Sense_1to0 = 3; RegSR3FValue.G_Sense_1to0 = 3; @@ -279,7 +341,7 @@ CBIOS_BOOL cbDIU_CRT_DACSense(PCBIOS_VOID pvcbe, PCBIOS_CRT_MONITOR_CONTEXT pCrt } else { - //Use old DAC1 sense logic when CRT is off. + //Use old DAC1 sense logic when CRT is off. Use IGA2 to sense cbSaveRegTableU8(pcbe, NewCRTDetectEnv, sizeofarray(NewCRTDetectEnv), pcbe->SavedReg); RegSR21Value.Value = 0; @@ -292,21 +354,10 @@ CBIOS_BOOL cbDIU_CRT_DACSense(PCBIOS_VOID pvcbe, PCBIOS_CRT_MONITOR_CONTEXT pCrt RegSR3FMask.Value = 0xFF; RegSR3FMask.SENSE_Mode = 0; cbMMIOWriteReg(pcbe,SR_3F, RegSR3FValue.Value, RegSR3FMask.Value); - RegSR4BValue.Value = 0; - RegSR4BValue.R_SENSE = 0x7A; - RegSR4BMask.Value = 0xFF; - RegSR4BMask.R_SENSE = 0; - cbMMIOWriteReg(pcbe,SR_4B, RegSR4BValue.Value, RegSR4BMask.Value); - RegSR4CValue.Value = 0; - RegSR4CValue.G_SENSE = 0x7A; - RegSR4CMask.Value = 0xFF; - RegSR4CMask.G_SENSE = 0; - cbMMIOWriteReg(pcbe,SR_4C, RegSR4CValue.Value, RegSR4CMask.Value); - RegSR4DValue.Value = 0; - RegSR4DValue.B_SENSE = 0x7A; - RegSR4DMask.Value = 0xFF; - RegSR4DMask.B_SENSE = 0; - cbMMIOWriteReg(pcbe,SR_4D, RegSR4DValue.Value, RegSR4DMask.Value); + + cbMMIOWriteReg(pcbe,SR_4B, 0x7A, 0x00); + cbMMIOWriteReg(pcbe,SR_4C, 0x7A, 0x00); + cbMMIOWriteReg(pcbe,SR_4D, 0x7A, 0x00); cb_DelayMicroSeconds(570); } @@ -319,7 +370,7 @@ CBIOS_BOOL cbDIU_CRT_DACSense(PCBIOS_VOID pvcbe, PCBIOS_CRT_MONITOR_CONTEXT pCrt } // Restore register - if(pDevCommon->PowerState == CBIOS_PM_ON) + if(DacSensePara.UseNewSense) { //Use new DAC1 sense logic when CRT is on. RegSR21Value.Value = 0; diff --git a/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosDIU_CRT.h b/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosDIU_CRT.h index 4555f452d351ec056e00b2c669ac01a3729ae47f..328b9d4ec5f98ac0b06455a1bfeefa44cebdeaae 100644 --- a/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosDIU_CRT.h +++ b/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosDIU_CRT.h @@ -31,6 +31,6 @@ CBIOS_VOID cbDIU_CRT_SetHVSync(PCBIOS_VOID pvcbe, CBIOS_U8 HVPolarity, CBIOS_U8 CBIOS_VOID cbDIU_CRT_SetDacCsc(PCBIOS_VOID pvcbe, CSC_FORMAT InFmt, CSC_FORMAT OutFmt); CBIOS_VOID cbDIU_CRT_DACOnOff(PCBIOS_VOID pvcbe, CBIOS_BOOL bOn, CBIOS_U8 IGAIndex); CBIOS_VOID cbDIU_CRT_SyncOnOff(PCBIOS_VOID pvcbe, CBIOS_BOOL bOn); -CBIOS_BOOL cbDIU_CRT_DACSense(PCBIOS_VOID pvcbe, PCBIOS_CRT_MONITOR_CONTEXT pCrtMonitorContext); +CBIOS_BOOL cbDIU_CRT_DACSense(PCBIOS_VOID pvcbe, PCBIOS_DEVICE_COMMON pDevCommon, CBIOS_BOOL bPrevEdidValid); #endif diff --git a/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosPHY_DP.c b/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosPHY_DP.c index 1243daae0c9e22276644bbf3e5921921c62fc656..b7f7545508669d0f0dd18fa191a73f7f92f6815e 100644 --- a/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosPHY_DP.c +++ b/drivers/gpu/drm/arise/cbios/Hw/HwBlock/CBiosPHY_DP.c @@ -1579,7 +1579,7 @@ static CBIOS_BOOL cbPHY_DP_SelectTMDSModeSource(PCBIOS_EXTENSION_COMMON pcbe, CB else if(MonitorType == CBIOS_MONITOR_TYPE_DVI) { RegSR3AValue.Value = 0; - RegSR3AValue.DP_PHY_Source_Sel = 0; + RegSR3AValue.DP_PHY_Source_Sel = 4; //PS background overlay will affect DE of DVI timing, force to HDMI mode can fix this issue } RegSR3AMask.Value = 0xFF; RegSR3AMask.DP_PHY_Source_Sel = 0; diff --git a/drivers/gpu/drm/arise/cbios/Hw/HwUtil/CBiosUtilHw.c b/drivers/gpu/drm/arise/cbios/Hw/HwUtil/CBiosUtilHw.c index 872d0a7afa51c8297382ee80749f11e72921766d..9f2fff71d02b8446e195574a41a60a91d65224ed 100644 --- a/drivers/gpu/drm/arise/cbios/Hw/HwUtil/CBiosUtilHw.c +++ b/drivers/gpu/drm/arise/cbios/Hw/HwUtil/CBiosUtilHw.c @@ -1236,11 +1236,18 @@ CBIOS_U32 cbGetProgClock(PCBIOS_EXTENSION_COMMON pcbe, CBIOS_U32 *ClockFreq, CBI { CBIOS_CLOCK_INFO ClkInfo = {0}; CBIOS_U32 RegD130 = 0; + CBIOS_U32 D300_Value = 0; + CBIOS_BOOL bUseNewMclk = CBIOS_FALSE; if(ClockType >= CBIOS_INVALID_CLK) { return -1; } + if (pcbe->ChipID == CHIPID_ARISE1020 && ClockType == CBIOS_MCLKTYPE) + { + D300_Value = cb_ReadU32(pcbe->pAdapterContext, 0xD300); + bUseNewMclk = ((D300_Value & 0xF) == 0xA) ? CBIOS_TRUE : CBIOS_FALSE; + } switch (ClockType) { @@ -1295,17 +1302,25 @@ CBIOS_U32 cbGetProgClock(PCBIOS_EXTENSION_COMMON pcbe, CBIOS_U32 *ClockFreq, CBI } break; case CBIOS_MCLKTYPE: - RegD130 = cb_ReadU32(pcbe->pAdapterContext, 0xd130); - ClkInfo.Integer = ((RegD130 >> 0x7) & 0x7F) | ((RegD130 & 0x10)? 0x80:0); - ClkInfo.R = (RegD130 >> 0x11) & 0x3; - ClkInfo.Fraction = 0; - ClkInfo.PLLDiv = 0; + if(bUseNewMclk) + { + *ClockFreq = (D300_Value & 0xFFF00)>>8; + } + else + { + RegD130 = cb_ReadU32(pcbe->pAdapterContext, 0xd130); + ClkInfo.Integer = ((RegD130 >> 0x7) & 0x7F) | ((RegD130 & 0x10)? 0x80:0); + ClkInfo.R = (RegD130 >> 0x11) & 0x3; + ClkInfo.Fraction = 0; + ClkInfo.PLLDiv = 0; + } break; default: break; } - if (pcbe->ChipID == CHIPID_ARISE10C0T && (ClockType == CBIOS_ECLKTYPE || ClockType == CBIOS_VCLKTYPE)) + if ((pcbe->ChipID == CHIPID_ARISE10C0T && (ClockType == CBIOS_ECLKTYPE || ClockType == CBIOS_VCLKTYPE)) || + (pcbe->ChipID == CHIPID_ARISE1020 && ClockType == CBIOS_MCLKTYPE && bUseNewMclk)) { (*ClockFreq) *= 10000; } diff --git a/drivers/gpu/drm/arise/cbios/Util/CBiosEDID.c b/drivers/gpu/drm/arise/cbios/Util/CBiosEDID.c index 0f598022c130900b641633cb3a8558e7b488c3da..a6b7e6a5da54715aca8c2eb6a3d933d245508d5f 100644 --- a/drivers/gpu/drm/arise/cbios/Util/CBiosEDID.c +++ b/drivers/gpu/drm/arise/cbios/Util/CBiosEDID.c @@ -2542,13 +2542,16 @@ CBIOS_BOOL cbEDIDModule_IsEDIDValid(CBIOS_U8 *pEDID) { byTemp = byTemp + pEDID[i]; //if checksum of 128 or 256 bytes is 0, success. - if(((i == 127) && (byTemp == 0)) || - ((i == 255) && (byTemp == 0))) + if(((i == 127) && (byTemp == 0)) ||((i == 255) && (byTemp == 0))) + { break; + } } if(byTemp == 0) + { bRet = CBIOS_TRUE; + } } return bRet; diff --git a/drivers/gpu/drm/arise/cbios/cbios.mk b/drivers/gpu/drm/arise/cbios/cbios.mk index 26c3ab960dcff768fb4905cd777600cdd2ec2751..c464f777b68c590c894e217d3938854ff55fc961 100644 --- a/drivers/gpu/drm/arise/cbios/cbios.mk +++ b/drivers/gpu/drm/arise/cbios/cbios.mk @@ -60,5 +60,5 @@ cbios-objs := \ Hw/Arise/CBios_Arise.o \ Hw/Arise/CBiosVCP_Arise.o -$(PRO_DRIVER_NAME)-objs += $(addprefix cbios/, $(cbios-objs)) +$(DRIVER_NAME)-objs += $(addprefix cbios/, $(cbios-objs)) diff --git a/drivers/gpu/drm/arise/core/Makefile b/drivers/gpu/drm/arise/core/Makefile index 8a17bf8c5e4f163fb799e4dc34ecc9c87d23c6c0..821a86b70c9c149dd13eb3d6be4f83bcf97bcfa4 100644 --- a/drivers/gpu/drm/arise/core/Makefile +++ b/drivers/gpu/drm/arise/core/Makefile @@ -58,7 +58,7 @@ core-objs := \ $(CORE_OBJ) \ $(PERF_EVENT_OBJ) -$(PRO_DRIVER_NAME)-objs += $(addprefix core/, $(core-objs)) +$(DRIVER_NAME)-objs += $(addprefix core/, $(core-objs)) ifeq ($(CHIP), E3k) include $(GFGPU_FULL_PATH)/core/e3k/Makefile diff --git a/drivers/gpu/drm/arise/core/e3k/Makefile b/drivers/gpu/drm/arise/core/e3k/Makefile index 86b00c06997b774c5ff13392f68a20e5a1cba4a2..442b6fddfbb532bfccf95e07bccd43d1027fda57 100644 --- a/drivers/gpu/drm/arise/core/e3k/Makefile +++ b/drivers/gpu/drm/arise/core/e3k/Makefile @@ -35,4 +35,4 @@ core_e3k-objs := \ $(PERFEVENT_OBJ) \ $(GLOBAL_OBJ) -$(PRO_DRIVER_NAME)-objs += $(addprefix core/e3k/, $(core_e3k-objs)) +$(DRIVER_NAME)-objs += $(addprefix core/e3k/, $(core_e3k-objs)) diff --git a/drivers/gpu/drm/arise/core/e3k/include/Chip/registercommands.h b/drivers/gpu/drm/arise/core/e3k/include/Chip/registercommands.h index 926edb6fc1893aa2b09df5272421338ba251dc5e..f910263704b06cc21dae299fa3c2c5db2bc0867b 100644 --- a/drivers/gpu/drm/arise/core/e3k/include/Chip/registercommands.h +++ b/drivers/gpu/drm/arise/core/e3k/include/Chip/registercommands.h @@ -255,7 +255,7 @@ __inline DWORD SET_REGISTER_ADDR_E3K(DWORD Block, DWORD Offset,BOOL AddressMode) return Cmd.uint; } -_inline DWORD SET_REGISTER_ADDR_LOW_E3K(DWORD LowAddress) +__inline DWORD SET_REGISTER_ADDR_LOW_E3K(DWORD LowAddress) { Cmd_Set_Register_Addr_Dword1 Cmd = {0}; @@ -264,7 +264,7 @@ _inline DWORD SET_REGISTER_ADDR_LOW_E3K(DWORD LowAddress) return *(DWORD*)&Cmd; } -_inline DWORD SET_REGISTER_ADDR_HIGH_AND_REGCNT_E3K(DWORD HighAddress, DWORD RegCnt) +__inline DWORD SET_REGISTER_ADDR_HIGH_AND_REGCNT_E3K(DWORD HighAddress, DWORD RegCnt) { Cmd_Set_Register_Addr_Dword2 Cmd = {0}; @@ -273,7 +273,7 @@ _inline DWORD SET_REGISTER_ADDR_HIGH_AND_REGCNT_E3K(DWORD HighAddress, DWORD Re return *(DWORD*)&Cmd; } -_inline DWORD SET_REGISTER_ADDR_HIGH_AND_REGCNT_AND_L2_E3K(DWORD HighAddress, DWORD RegCnt, DWORD L2cacheable) +__inline DWORD SET_REGISTER_ADDR_HIGH_AND_REGCNT_AND_L2_E3K(DWORD HighAddress, DWORD RegCnt, DWORD L2cacheable) { Cmd_Set_Register_Addr_Dword2 Cmd = {0}; @@ -283,7 +283,7 @@ _inline DWORD SET_REGISTER_ADDR_HIGH_AND_REGCNT_AND_L2_E3K(DWORD HighAddress, D return *(DWORD*)&Cmd; } -DWORD _inline SEND_SKIP_E3K(DWORD SkipCount) +__inline DWORD SEND_SKIP_E3K(DWORD SkipCount) { Csp_Opcodes_cmd SkipCmd = {0}; SkipCmd.cmd_Skip.Dwc = (DWORD)(SkipCount - 1); @@ -292,7 +292,7 @@ DWORD _inline SEND_SKIP_E3K(DWORD SkipCount) return SkipCmd.uint; } -_inline DWORD SEND_TBR_INDICATOR_COMMAND_E3K(DWORD value) +__inline DWORD SEND_TBR_INDICATOR_COMMAND_E3K(DWORD value) { Csp_Opcodes_cmd indicator = {0}; @@ -303,7 +303,7 @@ _inline DWORD SEND_TBR_INDICATOR_COMMAND_E3K(DWORD value) return *((DWORD*)&indicator); } -DWORD _inline SEND_FFCACHE_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_FFCACHE_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd Inv = {0}; @@ -317,7 +317,7 @@ DWORD _inline SEND_FFCACHE_INVALIDATE_COMMAND_E3K(void) return Inv.uint; } -DWORD _inline SEND_UCACHE_3DFE_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_UCACHE_3DFE_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd Inv = {0}; @@ -331,7 +331,7 @@ DWORD _inline SEND_UCACHE_3DFE_INVALIDATE_COMMAND_E3K(void) return Inv.uint; } -DWORD _inline SEND_UCACHE_3DBE_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_UCACHE_3DBE_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd Inv = {0}; @@ -345,7 +345,7 @@ DWORD _inline SEND_UCACHE_3DBE_INVALIDATE_COMMAND_E3K(void) return Inv.uint; } -DWORD _inline SEND_UCACHE_CSL_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_UCACHE_CSL_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd Inv = {0}; @@ -359,7 +359,7 @@ DWORD _inline SEND_UCACHE_CSL_INVALIDATE_COMMAND_E3K(void) return Inv.uint; } -DWORD _inline SEND_UCACHE_CSH_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_UCACHE_CSH_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd Inv = {0}; @@ -373,7 +373,7 @@ DWORD _inline SEND_UCACHE_CSH_INVALIDATE_COMMAND_E3K(void) return Inv.uint; } -DWORD _inline SEND_DCACHE_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_DCACHE_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd Inv = {0}; @@ -386,7 +386,7 @@ DWORD _inline SEND_DCACHE_INVALIDATE_COMMAND_E3K(void) return Inv.uint; } -DWORD _inline SEND_2D_ONLY_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_2D_ONLY_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd Inv = {0}; @@ -399,7 +399,7 @@ DWORD _inline SEND_2D_ONLY_INVALIDATE_COMMAND_E3K(void) return Inv.uint; } -_inline DWORD SEND_DEPTH_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_DEPTH_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd Inv = {0}; @@ -412,7 +412,7 @@ _inline DWORD SEND_DEPTH_INVALIDATE_COMMAND_E3K(void) return Inv.uint; } -_inline DWORD SEND_STENCIL_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_STENCIL_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd Inv = {0}; @@ -425,7 +425,7 @@ _inline DWORD SEND_STENCIL_INVALIDATE_COMMAND_E3K(void) return Inv.uint; } -_inline DWORD SEND_FLAGBUFFER_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_FLAGBUFFER_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd FbInvCmd = {0}; @@ -437,12 +437,12 @@ _inline DWORD SEND_FLAGBUFFER_INVALIDATE_COMMAND_E3K(void) return FbInvCmd.uint; } -_inline DWORD SEND_GMCACHE_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_GMCACHE_INVALIDATE_COMMAND_E3K(void) { return 0; } -_inline DWORD SEND_TUFE_CSL_L1CACHE_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_TUFE_CSL_L1CACHE_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd TexInvCmd = {0}; @@ -455,7 +455,7 @@ _inline DWORD SEND_TUFE_CSL_L1CACHE_INVALIDATE_COMMAND_E3K(void) return TexInvCmd.uint; } -_inline DWORD SEND_TUFE_CSH_L1CACHE_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_TUFE_CSH_L1CACHE_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd TexInvCmd = {0}; @@ -468,7 +468,7 @@ _inline DWORD SEND_TUFE_CSH_L1CACHE_INVALIDATE_COMMAND_E3K(void) return TexInvCmd.uint; } -_inline DWORD SEND_TUFE_L1CACHE_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_TUFE_L1CACHE_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd TexInvCmd = {0}; @@ -481,7 +481,7 @@ _inline DWORD SEND_TUFE_L1CACHE_INVALIDATE_COMMAND_E3K(void) return TexInvCmd.uint; } -_inline DWORD SEND_TUBE_L1CACHE_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_TUBE_L1CACHE_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd TexInvCmd = {0}; @@ -494,7 +494,7 @@ _inline DWORD SEND_TUBE_L1CACHE_INVALIDATE_COMMAND_E3K(void) return TexInvCmd.uint; } -_inline DWORD SEND_FFCACHE_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_FFCACHE_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd Flush = {0}; @@ -508,7 +508,7 @@ _inline DWORD SEND_FFCACHE_FLUSH_COMMAND_E3K(void) return Flush.uint; } -_inline DWORD SEND_DSIGBUF_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_DSIGBUF_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd DSigBufFlushCmd = {0}; @@ -521,7 +521,7 @@ _inline DWORD SEND_DSIGBUF_FLUSH_COMMAND_E3K(void) return DSigBufFlushCmd.uint; } -_inline DWORD SEND_UCACHE_3DFE_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_UCACHE_3DFE_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd Flush = {0}; @@ -535,7 +535,7 @@ _inline DWORD SEND_UCACHE_3DFE_FLUSH_COMMAND_E3K(void) return Flush.uint; } -_inline DWORD SEND_UCACHE_3DBE_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_UCACHE_3DBE_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd Flush = {0}; @@ -549,7 +549,7 @@ _inline DWORD SEND_UCACHE_3DBE_FLUSH_COMMAND_E3K(void) return Flush.uint; } -_inline DWORD SEND_UCACHE_CSL_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_UCACHE_CSL_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd Flush = {0}; @@ -563,7 +563,7 @@ _inline DWORD SEND_UCACHE_CSL_FLUSH_COMMAND_E3K(void) return Flush.uint; } -_inline DWORD SEND_UCACHE_CSH_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_UCACHE_CSH_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd Flush = {0}; @@ -577,7 +577,7 @@ _inline DWORD SEND_UCACHE_CSH_FLUSH_COMMAND_E3K(void) return Flush.uint; } -_inline DWORD SEND_UCACHE_3DL_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_UCACHE_3DL_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd Flush = {0}; @@ -590,7 +590,7 @@ _inline DWORD SEND_UCACHE_3DL_FLUSH_COMMAND_E3K(void) return Flush.uint; } -_inline DWORD SEND_DCACHE_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_DCACHE_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd Flush = {0}; @@ -603,7 +603,7 @@ _inline DWORD SEND_DCACHE_FLUSH_COMMAND_E3K(void) return Flush.uint; } -_inline DWORD SEND_2D_ONLY_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_2D_ONLY_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd Flush = {0}; @@ -616,7 +616,7 @@ _inline DWORD SEND_2D_ONLY_FLUSH_COMMAND_E3K(void) return Flush.uint; } -_inline DWORD SEND_DEPTH_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_DEPTH_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd Flush = {0}; @@ -629,7 +629,7 @@ _inline DWORD SEND_DEPTH_FLUSH_COMMAND_E3K(void) return Flush.uint; } -_inline DWORD SEND_STENCIL_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_STENCIL_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd Flush = {0}; @@ -642,7 +642,7 @@ _inline DWORD SEND_STENCIL_FLUSH_COMMAND_E3K(void) return Flush.uint; } -_inline DWORD SEND_FLAGBUFFER_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_FLAGBUFFER_FLUSH_COMMAND_E3K(void) { Csp_Opcodes_cmd FbInvCmd = {0}; @@ -654,12 +654,12 @@ _inline DWORD SEND_FLAGBUFFER_FLUSH_COMMAND_E3K(void) return FbInvCmd.uint; } -_inline DWORD SEND_GMCACHE_FLUSH_COMMAND_E3K(void) +__inline DWORD SEND_GMCACHE_FLUSH_COMMAND_E3K(void) { return 0; } -DWORD _inline SEND_BL_CLEAR_COMMAND_E3K(DWORD dwCounter) +__inline DWORD SEND_BL_CLEAR_COMMAND_E3K(DWORD dwCounter) { Csp_Opcodes_cmd clearCmd={0}; @@ -671,7 +671,7 @@ DWORD _inline SEND_BL_CLEAR_COMMAND_E3K(DWORD dwCounter) return (DWORD)clearCmd.uint; } -_inline DWORD SEND_RT_CLEAR_COMMAND_E3K(BOOL bPredicate,BOOL bTiled) +__inline DWORD SEND_RT_CLEAR_COMMAND_E3K(BOOL bPredicate,BOOL bTiled) { Csp_Opcodes_cmd Cmd = {0}; @@ -689,7 +689,7 @@ _inline DWORD SEND_RT_CLEAR_COMMAND_E3K(BOOL bPredicate,BOOL bTiled) return (DWORD)Cmd.uint; } -_inline DWORD SEND_DEPTH_CLEAR_COMMAND_E3K(BOOL bPredicate) +__inline DWORD SEND_DEPTH_CLEAR_COMMAND_E3K(BOOL bPredicate) { Csp_Opcodes_cmd Zc={0}; @@ -704,7 +704,7 @@ _inline DWORD SEND_DEPTH_CLEAR_COMMAND_E3K(BOOL bPredicate) return (DWORD)Zc.uint; } -_inline DWORD SEND_STENCIL_CLEAR_COMMAND_E3K(BOOL bPredicate) +__inline DWORD SEND_STENCIL_CLEAR_COMMAND_E3K(BOOL bPredicate) { Csp_Opcodes_cmd Sc={0}; @@ -719,7 +719,7 @@ _inline DWORD SEND_STENCIL_CLEAR_COMMAND_E3K(BOOL bPredicate) return (DWORD)Sc.uint; } -_inline DWORD SEND_MCE_RT_CLEAR_COMMAND_E3K(BOOL bTiled) +__inline DWORD SEND_MCE_RT_CLEAR_COMMAND_E3K(BOOL bTiled) { Csp_Opcodes_cmd Cmd = {0}; @@ -737,7 +737,7 @@ _inline DWORD SEND_MCE_RT_CLEAR_COMMAND_E3K(BOOL bTiled) return (DWORD)Cmd.uint; } -_inline DWORD SEND_MCE_DEPTH_CLEAR_COMMAND_E3K(void) +__inline DWORD SEND_MCE_DEPTH_CLEAR_COMMAND_E3K(void) { Csp_Opcodes_cmd Zc={0}; @@ -752,7 +752,7 @@ _inline DWORD SEND_MCE_DEPTH_CLEAR_COMMAND_E3K(void) return (DWORD)Zc.uint; } -_inline DWORD SEND_MCE_STENCIL_CLEAR_COMMAND_E3K(void) +__inline DWORD SEND_MCE_STENCIL_CLEAR_COMMAND_E3K(void) { Csp_Opcodes_cmd Sc={0}; @@ -784,7 +784,7 @@ __inline DWORD SEND_2DCOPY_COMMAND_E3K(BOOL bPredicate, BOOL bOverlay) } -_inline DWORD SEND_GRADIENTFILL_COMMAND_E3K(void) +__inline DWORD SEND_GRADIENTFILL_COMMAND_E3K(void) { Csp_Opcodes_cmd Cmd = {0}; @@ -843,7 +843,7 @@ _inline DWORD SEND_COPY_IMM_COMMAND_E3K(DWORD Dwf, BOOL bPredicate) return (DWORD)CopyImm.uint; } -_inline DWORD SEND_DP_LINE_COMMAND_E3K(BOOL ColorIncluded, DWORD lineNum) +__inline DWORD SEND_DP_LINE_COMMAND_E3K(BOOL ColorIncluded, DWORD lineNum) { Csp_Opcodes_cmd DPLine={0}; DWORD dwc = ColorIncluded ? (1 + lineNum*2) : (lineNum*2); @@ -857,7 +857,7 @@ _inline DWORD SEND_DP_LINE_COMMAND_E3K(BOOL ColorIncluded, DWORD lineNum) return (DWORD)DPLine.uint; } -_inline DWORD SEND_DRAWAUTO_COMMAND_E3K(DWORD P_Type, BOOL bInstanceMode, BOOL bPredicate) +__inline DWORD SEND_DRAWAUTO_COMMAND_E3K(DWORD P_Type, BOOL bInstanceMode, BOOL bPredicate) { Csp_Opcodes_cmd DIPCmd={0}; @@ -870,7 +870,7 @@ _inline DWORD SEND_DRAWAUTO_COMMAND_E3K(DWORD P_Type, BOOL bInstanceMode, BOOL b return (DWORD)DIPCmd.uint; } -_inline DWORD SEND_DRAW_COMMAND_E3K(DWORD P_Type, DWORD IndexSize, BOOL bInstanceMode) +__inline DWORD SEND_DRAW_COMMAND_E3K(DWORD P_Type, DWORD IndexSize, BOOL bInstanceMode) { Csp_Opcodes_cmd DIPCmd={0}; DWORD Index_Mode; @@ -1260,7 +1260,7 @@ __inline DWORD SEND_FS_CFG_COMMAND_E3K(DWORD* pCmd) return 0; } -DWORD __inline SEND_FS_L1I_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_FS_L1I_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd BlockCommandEu = {0}; @@ -1273,7 +1273,7 @@ DWORD __inline SEND_FS_L1I_INVALIDATE_COMMAND_E3K(void) return (DWORD)BlockCommandEu.uint; } -DWORD __inline SEND_CS_L1I_INVALIDATE_COMMAND_E3K(void) +__inline DWORD SEND_CS_L1I_INVALIDATE_COMMAND_E3K(void) { Csp_Opcodes_cmd BlockCommandEu = {0}; diff --git a/drivers/gpu/drm/arise/core/e3k/vidmm/vidmm_e3k.c b/drivers/gpu/drm/arise/core/e3k/vidmm/vidmm_e3k.c index bb6b700fcfdb03736bc8ddfbdd736603239065a5..598b5fa44f9d35ba58c8426f20439c05cc317584 100644 --- a/drivers/gpu/drm/arise/core/e3k/vidmm/vidmm_e3k.c +++ b/drivers/gpu/drm/arise/core/e3k/vidmm/vidmm_e3k.c @@ -38,17 +38,21 @@ void vidmm_init_mem_settings_e3k(adapter_t *adapter) unsigned int pending_buf_len = (adapter->chip_id < CHIP_ARISE1020) ? 0x4 : 0x40; //vcp0 decouple disable 0x4c910 0x1 - pRegAddr = adapter->mmio + 0x4c910; - gf_write32(pRegAddr, 0x1); - - //CHIP_ARISE1020 only has one vcp core - if(adapter->chip_id < CHIP_ARISE1020) + //if miu numble is larger than 1, we enable decouple, others disable. + if(adapter->chip_id >= CHIP_ARISE1020 && adapter->chip_id != CHIP_ARISE2030) { - //vcp1 decouple disable 0x4a910 0x1 - pRegAddr = adapter->mmio + 0x4a910; + pRegAddr = adapter->mmio + 0x4c910; gf_write32(pRegAddr, 0x1); } + //CHIP_ARISE1020 only has one vcp core + //if(adapter->chip_id < CHIP_ARISE1020) + //{ + //vcp1 decouple disable 0x4a910 0x1 + // pRegAddr = adapter->mmio + 0x4a910; + // gf_write32(pRegAddr, 0x1); + //} + reg_Mmu_Mode.reg.Vmen = 0;//use PA mode reg_Mmu_Mode.reg.Video_Size = (adapter->Real_vram_size>> 28) - 1; diff --git a/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_engine_submit_e3k.c b/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_engine_submit_e3k.c index 7a9d33101a82c5748191f9d2576c0e9cadb5eac2..69f6b44c01e8d64a9488fe8a9a1503014d095aec 100644 --- a/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_engine_submit_e3k.c +++ b/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_engine_submit_e3k.c @@ -601,6 +601,7 @@ static int enginei_common_submit_e3k(engine_e3k_t *engine, task_dma_t *task) RINGBUFFER_COMMANDS_E3K *pRb = NULL; unsigned int RbCase; Cmd_Dma *p; + unsigned int slice_mask = 0; #if DBG_HW_RING_BUFFER enginei_idle_ring_buffer_e3k(engine); @@ -707,6 +708,7 @@ static int enginei_common_submit_e3k(engine_e3k_t *engine, task_dma_t *task) pRbCmds->c0.CommandDMA_Address_L = dma_phy_addr.low32; pRbCmds->c0.CommandDMA_Address_H = dma_phy_addr.high32 & 0xFF; } + //when move RT to PCIE and open backdoor , 2 slice's performance is better than 1 slice #if 0 if (adapter->vcp_index_cnt[VCP0_INDEX] || adapter->vcp_index_cnt[VCP1_INDEX]) { @@ -714,15 +716,23 @@ static int enginei_common_submit_e3k(engine_e3k_t *engine, task_dma_t *task) } else { slice_mask = adapter->hw_caps.chip_slice_mask; } +#endif - if (adapter->current_slice_mask != slice_mask) { - adapter->current_slice_mask = slice_mask; - if (RbCase == 0) { - gf_memcpy(&pRbCmds->c0.SliceSwitchInitCommands, &share->SliceSwitchInitCommands[0].c0, sizeof(pRbCmds->c0.SliceSwitchInitCommands)); - } else if (RbCase == 1) { - gf_memcpy(&pRbCmds->c1.SliceSwitchInitCommands, &share->SliceSwitchInitCommands[1].c1, sizeof(pRbCmds->c1.SliceSwitchInitCommands)); + //force 2 slice for occlusion query counter limitation +#if 1 + if (adapter->chip_id >= CHIP_ARISE2030) + { + slice_mask = task->desc.Flags.ForceSlice ? 0x3 : adapter->hw_caps.chip_slice_mask; + + if (adapter->current_slice_mask != slice_mask) { + adapter->current_slice_mask = slice_mask; + if (RbCase == 0) { + gf_memcpy(&pRbCmds->c0.SliceSwitchInitCommands, &share->SliceSwitchInitCommands[0].c0, sizeof(pRbCmds->c0.SliceSwitchInitCommands)); + } else if (RbCase == 1) { + gf_memcpy(&pRbCmds->c1.SliceSwitchInitCommands, &share->SliceSwitchInitCommands[1].c1, sizeof(pRbCmds->c1.SliceSwitchInitCommands)); + } + //gf_info("switch slice mask to 0x%x\n", adapter->current_slice_mask); } - gf_info("switch slice mask to 0x%x\n", adapter->current_slice_mask); } #endif pRbCmds->c1.trigger_Dw.Slice_Mask = adapter->current_slice_mask; diff --git a/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_render_e3k.c b/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_render_e3k.c index 2d8b9a9ae7cae5a8d5bc2d957f80b039e2b45637..ea37e668aaed4db1b48fdc9b698fcba52fe77e33 100644 --- a/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_render_e3k.c +++ b/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_render_e3k.c @@ -38,7 +38,7 @@ int vidsch_render_e3k(gpu_context_t *gpu_context, task_dma_t *task_dma) static unsigned long long start_time = 0; /* I saw many small garbage flash in screen after reset about 10? times when use wrong cmd, so add this counter and notes */ static unsigned int hang_reset_counters = 0; - static unsigned int max_hang_reset_counters = 1; + static unsigned int max_hang_reset_counters = 99999999; /* only reset twice and need reboot to restart reset. */ if (/*adapter->ctl_flags.recovery_enable &&*/ (hang_reset_counters < max_hang_reset_counters)) diff --git a/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_setup_e3k.c b/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_setup_e3k.c old mode 100755 new mode 100644 index 3093430ad828a9ca4aa01e31f9ff620455ef5b7a..176b5150b41daf23aa5decac37f7377b76d6e15f --- a/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_setup_e3k.c +++ b/drivers/gpu/drm/arise/core/e3k/vidsch/vidsch_setup_e3k.c @@ -502,11 +502,16 @@ static void vidsch_read_miu_reg_e3k(adapter_t *adapter,gf_query_info_t *info) static void vidsch_dump_info_e3k(struct os_seq_file *seq_file, adapter_t *adapter) { -#define GPC_COUNT 3 EngineSatus_e3k engine_status = {0}; unsigned int *status =(unsigned int*) &engine_status; + unsigned int power_status = 0; + unsigned int gpc_count = (CHIP_ARISE1020 <= adapter->chip_id) ? 1 : 3; int i; + power_status = gf_read32(adapter->mmio + MMIO_CSP_START_ADDRESS + Reg_Pwr_Mgr_Status0_Offset *4); + + gf_seq_printf(seq_file, "engine power/clock status:0x%x\n", power_status); + for( i = 0; i <7; i++) { *(status + i) = gf_read32(adapter->mmio + MMIO_CSP_START_ADDRESS + (Reg_Block_Busy_Bits_Top_Offset + i)*4); @@ -525,7 +530,7 @@ static void vidsch_dump_info_e3k(struct os_seq_file *seq_file, adapter_t *adapte gf_seq_printf(seq_file,"\t Tasbe_Busy :%d \n", engine_status.Top.reg.Tasbe_Busy); gf_seq_printf(seq_file,"\t Hub_Busy :%d \n", engine_status.Top.reg.Hub_Busy); - for (i = 0; i < GPC_COUNT; i++) + for (i = 0; i < gpc_count; i++) { Reg_Block_Busy_Bits_Gpc0_0 *s0 = (Reg_Block_Busy_Bits_Gpc0_0 *)(status + i * 2 + 1); Reg_Block_Busy_Bits_Gpc0_1 *s1 = (Reg_Block_Busy_Bits_Gpc0_1 *)(status + i * 2 + 2); diff --git a/drivers/gpu/drm/arise/core/include/kernel_interface.h b/drivers/gpu/drm/arise/core/include/kernel_interface.h index cdbcf0b724c1960c82bf8ad6db27034387aa8c49..e0b4da82336c876e0627aea76795ac62bcfc8a46 100644 --- a/drivers/gpu/drm/arise/core/include/kernel_interface.h +++ b/drivers/gpu/drm/arise/core/include/kernel_interface.h @@ -160,6 +160,7 @@ typedef struct void (*ctl_flags_set)(void *data,unsigned int num,unsigned int mask,unsigned int value); int (*hwq_process_vsync_event)(void *data, unsigned long long time); void (*task_timeout_update)(void* data, unsigned long long *value, int update); + void (*reset_dvfs_power_flag)(void* data); } core_interface_t; extern core_interface_t *gf_core_interface; diff --git a/drivers/gpu/drm/arise/core/kernel_interface.c b/drivers/gpu/drm/arise/core/kernel_interface.c index 90f59bfaed8557338df8a136f133660aec39ace7..4ed13fb43b163fea56c1e9fd0d2fa1f4561e7c60 100644 --- a/drivers/gpu/drm/arise/core/kernel_interface.c +++ b/drivers/gpu/drm/arise/core/kernel_interface.c @@ -137,7 +137,7 @@ static void krnl_init_adapter(void* adp, int reserved_vmem, void *disp_info) gf_info("power caps: ClockGating:%d, PowerGating:%d\n", adapter->pwm_level.EnableClockGating, adapter->pwm_level.EnablePowerGating); - gf_info("Ctrl: Recovery:%d, WK-thread:%d, Hang-Dump:%d, RunOnQT:%d, PwmMode:%d, NonsnoopEnable:%d\n", + gf_info("Ctrl: Recovery:%d, WK-thread:%d, Hang-Dump:%d, RunOnQT:%d, PwmMode:0x%x, NonsnoopEnable:%d\n", adapter->ctl_flags.recovery_enable, adapter->ctl_flags.worker_thread_enable, adapter->ctl_flags.hang_dump, adapter->ctl_flags.run_on_qt, adapter->pm_caps.pwm_mode, adapter->hw_caps.snoop_only ? 0 : 1); @@ -1314,6 +1314,13 @@ static void krnl_task_timeout_update(void* data, unsigned long long *value, int *value = gf_do_div(adapter->hw_hang_fast_timeout_ns, 1000000); } +static void krnl_reset_dvfs_power_flag(void* data) +{ + adapter_t *adapter = data; + + vidsch_dvfs_power_flag_reset(adapter); +} + static core_interface_t gfe3k_gpu_core = { #define INTERFACE(item) .item = krnl_##item INTERFACE(pre_init_adapter), @@ -1373,6 +1380,7 @@ static core_interface_t gfe3k_gpu_core = { INTERFACE(ctl_flags_set), INTERFACE(hwq_process_vsync_event), INTERFACE(task_timeout_update), + INTERFACE(reset_dvfs_power_flag), #undef INTERFACE }; diff --git a/drivers/gpu/drm/arise/core/perfevent/perfevent.c b/drivers/gpu/drm/arise/core/perfevent/perfevent.c old mode 100755 new mode 100644 diff --git a/drivers/gpu/drm/arise/core/perfevent/perfevent.h b/drivers/gpu/drm/arise/core/perfevent/perfevent.h old mode 100755 new mode 100644 diff --git a/drivers/gpu/drm/arise/core/vidsch/vidsch.c b/drivers/gpu/drm/arise/core/vidsch/vidsch.c old mode 100755 new mode 100644 index 33f14c77d84d8cf2d70399f271333f90e678af6a..8372a647eec56f71739b748121abab36d3bd7325 --- a/drivers/gpu/drm/arise/core/vidsch/vidsch.c +++ b/drivers/gpu/drm/arise/core/vidsch/vidsch.c @@ -324,12 +324,12 @@ static int vidsch_is_fence_back_condition(void *argu) return vidsch_is_fence_back(wait_fence->adapter, wait_fence->engine, wait_fence->fence_id); } -int inline vidschi_allocation_in_cmd_queue(unsigned char engine_index, vidmm_allocation_t *allocation) +inline int vidschi_allocation_in_cmd_queue(unsigned char engine_index, vidmm_allocation_t *allocation) { return allocation->render_count[engine_index] == 0 ? FALSE : TRUE; } -int inline vidschi_allocation_in_write_cmd_queue(unsigned char engine_index, vidmm_allocation_t *allocation) +inline int vidschi_allocation_in_write_cmd_queue(unsigned char engine_index, vidmm_allocation_t *allocation) { return allocation->write_render_count[engine_index] == 0 ? FALSE : TRUE; } @@ -679,6 +679,21 @@ void vidsch_restore(adapter_t *adapter) } } +void vidsch_dvfs_power_flag_reset(adapter_t *adapter) +{ + vidsch_mgr_t *vidsch = NULL; + int i; + + for (i = 0; i < adapter->active_engine_count; i++) + { + vidsch = adapter->sch_mgr[i]; + if (vidsch == NULL) + continue; + + vidsch->engine_dvfs_power_on = FALSE; + } +} + static vidsch_fence_buffer_t *vidschi_create_fence_buffer(adapter_t *adapter, unsigned int segment_id, int buffer_size) { vidsch_fence_buffer_t *fence_buf = gf_calloc(sizeof(vidsch_fence_buffer_t)); diff --git a/drivers/gpu/drm/arise/core/vidsch/vidsch.h b/drivers/gpu/drm/arise/core/vidsch/vidsch.h index 6afdea3e1cf5539bc94dcd0323b0459f79bf8e26..32efc28e2ef520ee7030ba6b134c5c3e091885ad 100644 --- a/drivers/gpu/drm/arise/core/vidsch/vidsch.h +++ b/drivers/gpu/drm/arise/core/vidsch/vidsch.h @@ -226,7 +226,7 @@ extern void vidsch_wait_engine_idle(adapter_t *adapter, int idx); extern int vidsch_save(adapter_t *adapter); extern void vidsch_restore(adapter_t *adapter); - +extern void vidsch_dvfs_power_flag_reset(adapter_t *adapter); extern task_dma_t *vidsch_allocate_task_dma(adapter_t *adapter, unsigned int engine_index, vidsch_allocate_task_dma_t *dma_arg); extern task_paging_t *vidsch_allocate_paging_task(adapter_t *adapter, int dma_size, int allocation_num); diff --git a/drivers/gpu/drm/arise/core/vidsch/vidsch_task.c b/drivers/gpu/drm/arise/core/vidsch/vidsch_task.c old mode 100755 new mode 100644 diff --git a/drivers/gpu/drm/arise/core/vidsch/vidsch_workerthread.c b/drivers/gpu/drm/arise/core/vidsch/vidsch_workerthread.c old mode 100755 new mode 100644 diff --git a/drivers/gpu/drm/arise/gf_version.h b/drivers/gpu/drm/arise/gf_version.h index efea970bc9fef83d6960d88b5b6ef95b75e75125..3a17036542c8e6114da7a81aea992a7db1de8329 100644 --- a/drivers/gpu/drm/arise/gf_version.h +++ b/drivers/gpu/drm/arise/gf_version.h @@ -1,13 +1,13 @@ -#define DRIVER_DATE "06/05/2024" +#define DRIVER_DATE "08/27/2024" #define DRIVER_MAJOR 0x25 #define DRIVER_MINOR 0x00 -#define DRIVER_PATCHLEVEL 0x30 +#define DRIVER_PATCHLEVEL 0x35 #define DRIVER_CLASS "" #define DRIVER_NAME arise #define DRIVER_VENDOR "Glenfly Tech Co., Ltd." #define DRIVER_LICENSE "Glenfly" #define DRIVER_VERSION ((DRIVER_MAJOR<<24)|(DRIVER_MINOR<<16)|DRIVER_PATCHLEVEL) -#define DRIVER_VERSION_CHAR "25.00.30" +#define DRIVER_VERSION_CHAR "25.00.35" #define OS_VERSION "" #define CC_VERSION "" #define LD_VERSION "" diff --git a/drivers/gpu/drm/arise/linux/Makefile b/drivers/gpu/drm/arise/linux/Makefile index 0b1243c386bf6c41008e4c49bb7c7c4f320d06b3..df09d6d4816162f8179d381364fe0053edbf027e 100644 --- a/drivers/gpu/drm/arise/linux/Makefile +++ b/drivers/gpu/drm/arise/linux/Makefile @@ -55,4 +55,4 @@ else linux-objs += gf_pcie.o endif -$(PRO_DRIVER_NAME)-objs += $(addprefix linux/, $(linux-objs)) +$(DRIVER_NAME)-objs += $(addprefix linux/, $(linux-objs)) diff --git a/drivers/gpu/drm/arise/linux/e3k/gf_irq_e3k.c b/drivers/gpu/drm/arise/linux/e3k/gf_irq_e3k.c old mode 100755 new mode 100644 index 73b1f173ec3ee228a99cb61b212cc3829974ff8c..a4ff24290ac1c725a5092a9940ba581942f29f82 --- a/drivers/gpu/drm/arise/linux/e3k/gf_irq_e3k.c +++ b/drivers/gpu/drm/arise/linux/e3k/gf_irq_e3k.c @@ -40,7 +40,7 @@ static void gf_translate_interrupt_bits(disp_info_t* disp_info, int sw2hw, intr_ info->biu_intr_bits |= (*masks & INT_VIP2)? VIP2_INT : 0; info->biu_intr_bits |= (*masks & INT_VIP3)? VIP3_INT : 0; info->biu_intr_bits |= (*masks & INT_VIP4)? VIP4_INT : 0; - if(adapter->non_simul_chip) + if((adapter->chip_id == CHIP_ARISE1020) && adapter->non_simul_chip) { info->biu_intr_bits |= (*masks & INT_HDCODEC)? HDCODEC_INT_1020 : 0; } @@ -75,7 +75,7 @@ static void gf_translate_interrupt_bits(disp_info_t* disp_info, int sw2hw, intr_ *masks |= (info->biu_intr_bits & VIP2_INT)? INT_VIP2 : 0; *masks |= (info->biu_intr_bits & VIP3_INT)? INT_VIP3 : 0; *masks |= (info->biu_intr_bits & VIP4_INT)? INT_VIP4 : 0; - if(adapter->non_simul_chip) + if((adapter->chip_id == CHIP_ARISE1020) && adapter->non_simul_chip) { *masks |= (info->biu_intr_bits & HDCODEC_INT_1020)? INT_HDCODEC : 0; } diff --git a/drivers/gpu/drm/arise/linux/gf.h b/drivers/gpu/drm/arise/linux/gf.h index 52f1f4241fe324fe723675b63c968e311f350560..04bf482bb6303a1d85746207f52c729f6621f367 100644 --- a/drivers/gpu/drm/arise/linux/gf.h +++ b/drivers/gpu/drm/arise/linux/gf.h @@ -55,6 +55,8 @@ #include #include #include +#include +#include #ifndef DRM_VERSION_CODE #define DRM_VERSION_CODE LINUX_VERSION_CODE diff --git a/drivers/gpu/drm/arise/linux/gf_atomic.c b/drivers/gpu/drm/arise/linux/gf_atomic.c index c11a460c40baad2bf99b33347e2ca63d218a1d73..bf74af8ffaa195464758d0d6c34caaaaadc6a64e 100644 --- a/drivers/gpu/drm/arise/linux/gf_atomic.c +++ b/drivers/gpu/drm/arise/linux/gf_atomic.c @@ -16,6 +16,7 @@ #include "gf_kms.h" #include "gf_sink.h" #include "gf_splice.h" +#include "gf_pm.h" #if DRM_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) @@ -245,6 +246,9 @@ static void gf_update_crtc_sink(struct drm_atomic_state *old_state) void gf_atomic_helper_commit_tail(struct drm_atomic_state *old_state) { struct drm_device *dev = old_state->dev; + struct drm_crtc *crtc; + struct drm_crtc_state *old_crtc_state, *new_crtc_state; + int i; #if DRM_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) uint32_t flags = DRM_PLANE_COMMIT_NO_DISABLE_AFTER_MODESET; @@ -256,7 +260,24 @@ void gf_atomic_helper_commit_tail(struct drm_atomic_state *old_state) drm_atomic_helper_commit_modeset_enables(dev, old_state); - gf_update_crtc_sink(old_state); +#if DRM_VERSION_CODE < KERNEL_VERSION(4, 12, 0) + for_each_crtc_in_state(old_state, crtc, old_crtc_state, i) + { + new_crtc_state = crtc->state; +#else + for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) + { +#endif + if (old_crtc_state->active && !new_crtc_state->active) + { + gf_rpm_put_noidle(dev->dev); + } + + if (new_crtc_state->active && !old_crtc_state->active) + { + gf_rpm_get_noresume(dev->dev); + } + } drm_atomic_helper_commit_planes(dev, old_state, flags); @@ -275,6 +296,8 @@ void gf_atomic_helper_commit_tail(struct drm_atomic_state *old_state) #endif drm_atomic_helper_cleanup_planes(dev, old_state); + + gf_rpm_mark_last_busy(dev->dev); } diff --git a/drivers/gpu/drm/arise/linux/gf_cbios.c b/drivers/gpu/drm/arise/linux/gf_cbios.c index 7ec8cb38e8bb71169040175dc96d078490727062..02f096e1cc94fece45458371b70e133e1ddbd8fe 100644 --- a/drivers/gpu/drm/arise/linux/gf_cbios.c +++ b/drivers/gpu/drm/arise/linux/gf_cbios.c @@ -1755,12 +1755,17 @@ int disp_cbios_get_clock(disp_info_t *disp_info, unsigned int type, unsigned int { *output = (gf_do_div(*output, 10000) + 1) * 10000; - if(adapter_info->chip_id >= CHIP_ARISE1020 && type == GF_QUERY_CORE_CLOCK) + if((adapter_info->chip_id >= CHIP_ARISE1020) && (adapter_info->chip_id < CHIP_ARISE2030) && (type == GF_QUERY_CORE_CLOCK)) { *output *= 2; } } + if ((adapter_info->chip_id >= CHIP_ARISE2030) && (type == GF_QUERY_VCLK)) + { + *output = (gf_do_div(*output, 10000) + 1) * 10000 * 2; + } + return (status == CBIOS_OK) ? DISP_OK : DISP_FAIL; } diff --git a/drivers/gpu/drm/arise/linux/gf_cbios.h b/drivers/gpu/drm/arise/linux/gf_cbios.h index 97609533fb8812d46408926a6b84ce47755f9669..b626271908deb4f1b146f3e6af9b6ad25612868f 100644 --- a/drivers/gpu/drm/arise/linux/gf_cbios.h +++ b/drivers/gpu/drm/arise/linux/gf_cbios.h @@ -16,8 +16,6 @@ #define __GF_CBIOS_H__ #include "CBios.h" -#define MAX_CRTC_NUM CBIOS_MAX_CRTCS - enum { FAMILY_CMODEL, diff --git a/drivers/gpu/drm/arise/linux/gf_connector.c b/drivers/gpu/drm/arise/linux/gf_connector.c index e296ad249bbe42305c84f81d5586d77c41ba6518..dbbb8b87f35dbb941205ed134c355738920b10d7 100644 --- a/drivers/gpu/drm/arise/linux/gf_connector.c +++ b/drivers/gpu/drm/arise/linux/gf_connector.c @@ -20,6 +20,30 @@ #include "gf_sink.h" #include "gf_splice.h" +gf_connector_t* gf_get_connector_by_device_id(disp_info_t* disp_info, int device_id) +{ + gf_card_t *gf_card = (gf_card_t *)disp_info->gf_card; + struct drm_device *drm = gf_card->drm_dev; + gf_connector_t *gf_conn = NULL; + struct drm_connector *connector = NULL; + + if(!drm || !device_id) + { + return gf_conn; + } + + list_for_each_entry(connector, &drm->mode_config.connector_list, head) + { + if((to_gf_connector(connector))->output_type == device_id) + { + gf_conn = to_gf_connector(connector); + break; + } + } + + return gf_conn; +} + enum drm_connector_status gf_connector_detect_internal(struct drm_connector *connector, bool force, int full_detect) { @@ -572,7 +596,13 @@ struct drm_connector* disp_connector_init(disp_info_t* disp_info, disp_output_ty connector->stereo_allowed = FALSE; connector->interlace_allowed = FALSE; } - else if (output & DISP_OUTPUT_DP_TYPES) + else if (output == DISP_OUTPUT_DP2) + { + conn_type = DRM_MODE_CONNECTOR_DisplayPort; + connector->stereo_allowed = TRUE; + connector->interlace_allowed = TRUE; + } + else if (output & (DISP_OUTPUT_DP_TYPES & (~DISP_OUTPUT_DP2))) { conn_type = DRM_MODE_CONNECTOR_HDMIA; connector->stereo_allowed = TRUE; diff --git a/drivers/gpu/drm/arise/linux/gf_crtc.c b/drivers/gpu/drm/arise/linux/gf_crtc.c index 6621cf9c01119cff8e087b20b5784897bddaab20..3b1df0cc7f3d83d228a6395709b9aea3eafa9ca6 100644 --- a/drivers/gpu/drm/arise/linux/gf_crtc.c +++ b/drivers/gpu/drm/arise/linux/gf_crtc.c @@ -128,8 +128,6 @@ void gf_crtc_helper_enable(struct drm_crtc *crtc) drm_crtc_vblank_on(crtc); gf_crtc_dpms_onoff_helper(crtc, 1); - - } #if DRM_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) diff --git a/drivers/gpu/drm/arise/linux/gf_disp.c b/drivers/gpu/drm/arise/linux/gf_disp.c index 7c8f5b93c9b95bc9978a8472f61d9f6ee0a58236..261b7214c9988aa34df2dcc157ec3db99dc2caa6 100644 --- a/drivers/gpu/drm/arise/linux/gf_disp.c +++ b/drivers/gpu/drm/arise/linux/gf_disp.c @@ -73,6 +73,14 @@ static char* plane_name[] = { "FS", }; +static const unsigned int vsync_int_tbl[] = { + INT_VSYNC1, + INT_VSYNC2, + INT_VSYNC3, + INT_VSYNC4, +}; +#define VSYNC_INT_TABLE_LEN (sizeof(vsync_int_tbl)/sizeof(vsync_int_tbl[0])) + static char* cursor_name = "cursor"; #if DRM_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) @@ -672,6 +680,13 @@ static int disp_crtc_init(disp_info_t* disp_info, unsigned int index) crtc_state->base_cstate.crtc = &gf_crtc->base_crtc; gf_crtc->crtc_dpms = 0; + if (index >= VSYNC_INT_TABLE_LEN) + { + gf_error("index excceds vsync int table length\n"); + goto fail; + } + gf_crtc->vsync_int = vsync_int_tbl[index]; + gf_crtc->support_scale = disp_info->scale_support; gf_crtc->plane_cnt = disp_info->num_plane[index]; @@ -742,6 +757,13 @@ static int disp_crtc_init(disp_info_t* disp_info, unsigned int index) gf_crtc->crtc_dpms = 0; + if (index >= VSYNC_INT_TABLE_LEN) + { + gf_error("index excceds vsync int table length\n"); + goto fail; + } + gf_crtc->vsync_int = vsync_int_tbl[index]; + ret = drm_crtc_init(drm, &gf_crtc->base_crtc, &gf_crtc_funcs); if(ret) @@ -831,54 +853,12 @@ static void disp_hotplug_init(disp_info_t* disp_info) { gf_card_t* gf_card = disp_info->gf_card; struct drm_device* drm = gf_card->drm_dev; - struct drm_connector* connector = NULL; - gf_connector_t* gf_connector = NULL; - unsigned int hpd_int_bits = 0; -#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) - struct drm_connector_list_iter conn_iter; -#endif //at boot/resume stage, no hpd event for all output, we need poll the hpd outputs once drm_helper_hpd_irq_event(drm); - mutex_lock(&drm->mode_config.mutex); - -#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) - drm_connector_list_iter_begin(drm, &conn_iter); -#endif - -#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) - drm_for_each_connector_iter(connector, &conn_iter) -#else - -#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) - drm_for_each_connector(connector, drm) -#else - list_for_each_entry(connector, &drm->mode_config.connector_list, head) -#endif - -#endif - { - //mark status to enable for all outputs that support hot plug - gf_connector = to_gf_connector(connector); - - if((connector->polled == DRM_CONNECTOR_POLL_HPD) && gf_connector->hpd_int_bit) - { - gf_connector->hpd_enable = 1; - hpd_int_bits |= gf_connector->hpd_int_bit; - } - } -#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) - drm_connector_list_iter_end(&conn_iter); -#endif - - mutex_unlock(&drm->mode_config.mutex); - //enable hot plug interrupt - if(hpd_int_bits) - { - gf_hot_plug_intr_ctrl(disp_info, hpd_int_bits, 1); - } + gf_hot_plug_intr_onoff(disp_info, 1); } static void disp_polling_init(disp_info_t* disp_info) @@ -984,13 +964,15 @@ void gf_disp_suspend_helper(struct drm_device *dev) int disp_suspend(struct drm_device *dev) { - gf_card_t *gf = dev->dev_private; + gf_card_t *gf = dev->dev_private; disp_info_t *disp_info = (disp_info_t *)gf->disp_info; int ret = 0; #if DRM_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) struct drm_atomic_state *state; #endif + gf_hot_plug_intr_onoff(disp_info, 0); + gf_poll_disable(disp_info); #if DRM_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) @@ -1142,6 +1124,8 @@ void disp_post_resume(struct drm_device *dev) gf_poll_enable(disp_info); disp_probe_connector_after_resume(dev); + + gf_hot_plug_intr_onoff(disp_info, 1); } static int disp_mode_config_init(disp_info_t* disp_info) @@ -1243,11 +1227,11 @@ static void disp_info_print(disp_info_t* disp_info) } if (DISP_OK == disp_cbios_get_clock(disp_info, GF_QUERY_ENGINE_CLOCK, &value)) { - gf_info("displayinfo Eclk:%dMHz\n", value / 1000); + //gf_info("displayinfo Eclk:%dMHz\n", value / 1000); } if(DISP_OK == disp_cbios_get_clock(disp_info, GF_QUERY_VCLK, &value)) { - gf_info("displayinfo Vclk:%dMHz\n", (value + 500)/1000); + //gf_info("displayinfo Vclk:%dMHz\n", (value + 500)/1000); } if(DISP_OK == disp_cbios_get_clock(disp_info, GF_QUERY_MCLK, &value)) { @@ -1631,7 +1615,7 @@ int gf_debugfs_clock_dump(struct seq_file* file, struct drm_device* dev) if(DISP_OK == disp_cbios_get_clock(disp_info, GF_QUERY_ENGINE_CLOCK, &value)) { - seq_printf(file, "Engine clock = %dMHz.\n", value/10000); + seq_printf(file, "Engine clock = %dMHz.\n", value/1000); } return 0; diff --git a/drivers/gpu/drm/arise/linux/gf_disp.h b/drivers/gpu/drm/arise/linux/gf_disp.h index 05f830d3ccbb873cedb06bf5ebb1c439796f6db7..80999f75fbc974ff46cd5b5b71eef9a80e30405a 100644 --- a/drivers/gpu/drm/arise/linux/gf_disp.h +++ b/drivers/gpu/drm/arise/linux/gf_disp.h @@ -234,6 +234,7 @@ typedef struct struct os_spinlock *hda_lock; struct os_spinlock *hdcp_lock; int irq_enabled; + int poll_running; atomic_t atomic_irq_lock; struct work_struct hotplug_work; struct work_struct dp_irq_work; @@ -394,4 +395,6 @@ void disp_probe_connector_after_resume(struct drm_device *dev); void disp_create_plane_property(struct drm_device* dev, gf_plane_t* gf_plane); +gf_connector_t* gf_get_connector_by_device_id(disp_info_t *disp_info, int device_id); + #endif diff --git a/drivers/gpu/drm/arise/linux/gf_driver.c b/drivers/gpu/drm/arise/linux/gf_driver.c index 1b46fe59d165d9a717f14022c762af6a92837a65..dae02806e37b27273ce97f55048e3034244e393d 100644 --- a/drivers/gpu/drm/arise/linux/gf_driver.c +++ b/drivers/gpu/drm/arise/linux/gf_driver.c @@ -285,8 +285,6 @@ static krnl_import_func_list_t gf_export = .find_next_zero_bit = gf_find_next_zero_bit, .set_bit = gf_set_bit, .clear_bit = gf_clear_bit, - .getsecs = gf_getsecs, - .get_nsecs = gf_get_nsecs, .create_thread = gf_create_thread, .destroy_thread = gf_destroy_thread, .thread_should_stop = gf_thread_should_stop, @@ -329,7 +327,6 @@ static krnl_import_func_list_t gf_export = .thread_wait = gf_thread_wait, .create_wait_queue = gf_create_wait_queue, .thread_wake_up = gf_thread_wake_up, - .dump_stack = gf_dump_stack, .try_to_freeze = gf_try_to_freeze, .freezable = gf_freezable, .clear_freezable = gf_clear_freezable, @@ -449,7 +446,7 @@ static ssize_t gf_gpuinfo_proc_read(struct file *filp, char *buf, size_t count, int ret = 0; size_t len = 0; static int print_flag = 1; - char *buffer = NULL, *memory_type = "DDR4", *pmp_version = NULL, *product_name = NULL; + char *buffer = NULL, *memory_type = "DDR4", *pmp_version = NULL, *product_name = NULL, *type_dp = "\0"; char *output_type_vga_hdmi = "VGA, HDMI", *output_type_full = "VGA, HDMI, DP", *hdmi_resolution = "3840 x 2160", *vga_resolution = "1920 x 1080", *output_type = output_type_vga_hdmi; unsigned int mclk, coreclk, eclk, free_mem, mem_usage, usage_3d, usage_vcp, usage_vpp; unsigned int subsystemid, technology, pixel_fillrate, texture_fillrate; @@ -579,20 +576,24 @@ static ssize_t gf_gpuinfo_proc_read(struct file *filp, char *buf, size_t count, hdmi_fps = 60; subsystemid = 0x2030; technology = 28; - pixel_fillrate = 96 * eclk; + pixel_fillrate = 32 * eclk; texture_fillrate = pixel_fillrate *2; product_name = "Arise2030"; + output_cnt = 3; output_type = output_type_full; + type_dp = "/DP"; break; case 0x3d08: hdmi_fps = 60; subsystemid = 0x2020; technology = 28; - pixel_fillrate = 96 * eclk; + pixel_fillrate = 32 * eclk; texture_fillrate = pixel_fillrate *2; product_name = "Arise2020"; + output_cnt = 3; output_type = output_type_full; + type_dp = "/DP"; break; default: @@ -638,11 +639,11 @@ static ssize_t gf_gpuinfo_proc_read(struct file *filp, char *buf, size_t count, len += sprintf(buffer + len, "Memory ddr Remain Size : %d MB\n", free_mem); len += sprintf(buffer + len, "Memory Clock Freq : %d MHz\n", mclk / 2); len += sprintf(buffer + len, "Memory Transfer Rates : %d MT/s\n", mclk); - len += sprintf(buffer + len, "GPU Work Frequency : Core %d MHz / Aux %d MHz\n", coreclk, eclk); + len += sprintf(buffer + len, "GPU Work Frequency : %d MHz\n", coreclk); len += sprintf(buffer + len, "Realtime Temperature : %d Degree\n", temp); len += sprintf(buffer + len, "Max Display Port : %d\n", output_cnt); len += sprintf(buffer + len, "Support Display Type : %s\n", output_type); - len += sprintf(buffer + len, "HDMI Max Display Resolution : %s@%dHz\n", hdmi_resolution, hdmi_fps); + len += sprintf(buffer + len, "HDMI%s Max Display Resolution : %s@%dHz\n", type_dp, hdmi_resolution, hdmi_fps); len += sprintf(buffer + len, "VGA Max Display Resolution : %s@60Hz\n", vga_resolution); len += sprintf(buffer + len, "GPU Memory Usage : %d%%\n", mem_usage); @@ -1008,6 +1009,7 @@ int gf_card_init(gf_card_t *gf, void *pdev) gf->misc_control_flag = gf_modparams.misc_control_flag; gf->allocation_trace_tags = 0; gf->video_irq_info_all = 0; + gf->runtime_pm = gf_modparams.gf_runtime_pm; return ret; } diff --git a/drivers/gpu/drm/arise/linux/gf_driver.h b/drivers/gpu/drm/arise/linux/gf_driver.h index daa56ae499dad5a4e46bf7a36626d7079c3f6cd4..5242b8bc2e9641488d985bfed64fe4ffafdc6d50 100644 --- a/drivers/gpu/drm/arise/linux/gf_driver.h +++ b/drivers/gpu/drm/arise/linux/gf_driver.h @@ -73,6 +73,7 @@ typedef struct unsigned int misc_control_flag; unsigned long allocation_trace_tags; // allocation trace tags, 0 to disable. unsigned int video_irq_info_all; + int runtime_pm; }gf_card_t; struct gf_file diff --git a/drivers/gpu/drm/arise/linux/gf_fence.c b/drivers/gpu/drm/arise/linux/gf_fence.c index 12c35a2f463bb3f15779f77b6cb9a7ecfe71d9f2..44cfc82bc344959343d45233e54b33b6a8e5ff99 100644 --- a/drivers/gpu/drm/arise/linux/gf_fence.c +++ b/drivers/gpu/drm/arise/linux/gf_fence.c @@ -523,7 +523,7 @@ long gf_gem_fence_await_reservation(struct reservation_object *resv, int exclude kfree(fences); if (timeout <= 0) - gf_debug("wait fences timeout or interrupted:%d\n", timeout); + gf_warning("wait fences timeout or interrupted:%d\n", timeout); return timeout; } diff --git a/drivers/gpu/drm/arise/linux/gf_i2c.c b/drivers/gpu/drm/arise/linux/gf_i2c.c index 6edf4fd8f55b1fa453dfa3c1948bc2a98c5534fd..8fcaa2a3a46729366ccbc9d5e03d2a05d15eaf7f 100644 --- a/drivers/gpu/drm/arise/linux/gf_i2c.c +++ b/drivers/gpu/drm/arise/linux/gf_i2c.c @@ -24,12 +24,18 @@ static int gf_i2c_xfer(struct i2c_adapter *i2c_adapter, struct i2c_msg *msgs, in disp_info_t *disp_info = (disp_info_t *)gf_card->disp_info; gf_i2c_param_t i2c_param; int i = 0, retval = 0; + + if (gf_connector->base_connector.status != connector_status_connected) + { + DRM_DEBUG_DRIVER("invalid i2c request as connector(0x%x) is not connected", + gf_connector->output_type); + return -EIO; + } + for (i = 0; i < num; i++) { - DRM_DEBUG_KMS("xfer: num: %d/%d, len:%d, flags:%#x\n\n", i + 1, + DRM_DEBUG_DRIVER("xfer: num: %d/%d, len:%d, flags:%#x\n\n", i + 1, num, msgs[i].len, msgs[i].flags); - /*gf_info("xfer: num MUTEX: %d/%d, len:%d, flags:%#x\n\n", i + 1, - num, msgs[i].len, msgs[i].flags);*/ gf_memset(&i2c_param, 0, sizeof(gf_i2c_param_t)); i2c_param.use_dev_type = 1; i2c_param.device_id = gf_connector->output_type; diff --git a/drivers/gpu/drm/arise/linux/gf_irq.c b/drivers/gpu/drm/arise/linux/gf_irq.c old mode 100755 new mode 100644 index 8432933059afb21ad46029986ba0877a4e85a2ca..4ca07ced29b64892d7fd82f6dd619a70869a9634 --- a/drivers/gpu/drm/arise/linux/gf_irq.c +++ b/drivers/gpu/drm/arise/linux/gf_irq.c @@ -19,6 +19,7 @@ #include "gf_kms.h" #include "gf_splice.h" #include "gf_trace.h" +#include "gf_pm.h" #if DRM_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) /* get_scanout_position() return flags */ @@ -32,7 +33,6 @@ #define RESET_TIME_MINUTE_interval 10 - static int video_irq_info_count[VIDEO_ERROR_INFO_NUM] = {0}; static ktime_t video_irq_info_time[VIDEO_ERROR_INFO_NUM] = {0}; static int video_irq_mask[VIDEO_ERROR_INFO_NUM] = {INT_FE_HANG_VD0, INT_BE_HANG_VD0, INT_FE_HANG_VD1, INT_BE_HANG_VD1, @@ -41,9 +41,6 @@ static char* video_irq_name[VIDEO_ERROR_INFO_NUM] = {"CORE0_FE_HANG", "CORE0_BE_ "CORE0_FE_ERROR", "CORE0_BE_ERROR", "CORE1_FE_ERROR", "CORE1_BE_ERROR"}; static int video_reg_offset[VIDEO_ERROR_INFO_NUM] = {0x4C81C, 0x4C81C, 0x4A81C, 0x4A81C, 0x4C81C, 0x4C81C, 0x4A81C, 0x4A81C}; - - - static struct drm_crtc* gf_get_crtc_by_pipe(struct drm_device *dev, pipe_t pipe) { struct drm_crtc *crtc = NULL; @@ -586,15 +583,15 @@ int gf_irq_install(struct drm_device *drm_dev) static void gf_vblank_intrr_handle(struct drm_device* dev, unsigned int intrr) { - unsigned int index = 0; - unsigned int vsync[MAX_CRTC_NUM] = {INT_VSYNC1, INT_VSYNC2, INT_VSYNC3, INT_VSYNC4}; - struct drm_crtc* crtc = NULL; gf_card_t *gf = dev->dev_private; - disp_info_t* disp_info = (disp_info_t *)gf->disp_info; + disp_info_t *disp_info = (disp_info_t *)gf->disp_info; gf_splice_manager_t *splice_manager = disp_info->splice_manager; gf_splice_target_t *target = NULL; gf_splice_source_t *source = NULL; struct drm_crtc *splice_source_crtc = NULL, *splice_target_crtc = NULL; + struct drm_crtc *crtc = NULL; + gf_crtc_t *gf_crtc = NULL; + unsigned int crtc_idx = 0; if (splice_manager != NULL) { @@ -608,48 +605,46 @@ static void gf_vblank_intrr_handle(struct drm_device* dev, unsigned int intrr) } } - if(intrr & INT_VSYNCS) + list_for_each_entry(crtc, &(dev->mode_config.crtc_list), head) { - for (index = 0; index < MAX_CRTC_NUM; index++) + gf_crtc = to_gf_crtc(crtc); + + if (intrr & gf_crtc->vsync_int) { - if (intrr & vsync[index]) - { - gf_perf_event_t perf_event = {0, }; - gf_get_counter_t get_cnt = {0, }; - unsigned int vblcnt = 0; - unsigned long long timestamp; + gf_perf_event_t perf_event = {0, }; + gf_get_counter_t get_cnt = {0, }; + unsigned int vblcnt = 0; + unsigned long long timestamp; - crtc = gf_get_crtc_by_pipe(dev, index); + if (splice_source_crtc == crtc) + { + drm_crtc_handle_vblank(splice_target_crtc); + } - //TODO: support splice combination with stand along connector - if (splice_source_crtc == crtc) - { - drm_crtc_handle_vblank(splice_target_crtc); - } + if (gf_crtc->enabled) + { + drm_crtc_handle_vblank(crtc); + } - if (to_gf_crtc(crtc)->enabled) - { - drm_crtc_handle_vblank(crtc); - } + crtc_idx = drm_get_crtc_index(crtc); - get_cnt.crtc_index = index; - get_cnt.vblk = &vblcnt; - disp_cbios_get_counter(disp_info, &get_cnt); + get_cnt.crtc_index = crtc_idx; + get_cnt.vblk = &vblcnt; + disp_cbios_get_counter(disp_info, &get_cnt); - trace_gfx_vblank_intrr(gf->index << 16 | index, vblcnt); + trace_gfx_vblank_intrr(gf->index << 16 | crtc_idx, vblcnt); - gf_get_nsecs(×tamp); - perf_event.header.timestamp_high = timestamp >> 32; - perf_event.header.timestamp_low = timestamp & 0xffffffff; - perf_event.header.size = sizeof(gf_perf_event_vsync_t); - perf_event.header.type = GF_PERF_EVENT_VSYNC; - perf_event.vsync_event.iga_idx = index + 1; - perf_event.vsync_event.vsync_cnt_low = vblcnt; - perf_event.vsync_event.vsync_cnt_high = 0; + gf_get_nsecs(×tamp); + perf_event.header.timestamp_high = timestamp >> 32; + perf_event.header.timestamp_low = timestamp & 0xffffffff; + perf_event.header.size = sizeof(gf_perf_event_vsync_t); + perf_event.header.type = GF_PERF_EVENT_VSYNC; + perf_event.vsync_event.iga_idx = crtc_idx + 1; + perf_event.vsync_event.vsync_cnt_low = vblcnt; + perf_event.vsync_event.vsync_cnt_high = 0; - gf_core_interface->perf_event_add_isr_event(gf->adapter, &perf_event); - //gf_core_interface->hwq_process_vsync_event(gf->adapter, timestamp); - } + gf_core_interface->perf_event_add_isr_event(gf->adapter, &perf_event); + //gf_core_interface->hwq_process_vsync_event(gf->adapter, timestamp); } } } @@ -935,7 +930,7 @@ irqreturn_t gf_irq_handle(int irq, void *arg) gf_hdaudio_handle(dev); } - if(intrr & INT_HOTPLUG) + if (intrr & INT_HOTPLUG) { gf_hpd_handle(dev, intrr & INT_HOTPLUG); } @@ -960,6 +955,7 @@ irqreturn_t gf_irq_handle(int irq, void *arg) { intrr |= INT_FENCE; } + if(intrr & INT_FENCE) { tasklet_schedule(&gf_card->fence_notify); @@ -969,16 +965,18 @@ irqreturn_t gf_irq_handle(int irq, void *arg) atomic_set(&disp_info->atomic_irq_lock, 0); + gf_rpm_mark_last_busy(dev->dev); + return IRQ_HANDLED; } -void gf_hot_plug_intr_ctrl(disp_info_t* disp_info, unsigned int intr, int enable) +static void gf_hot_plug_intr_ctrl(disp_info_t* disp_info, unsigned int intr, int enable) { irq_chip_funcs_t* chip_func = (irq_chip_funcs_t*)disp_info->irq_chip_func; unsigned long flags = 0; unsigned int intr_en = 0; - if(!chip_func || !chip_func->get_intr_enable_mask || !chip_func->set_intr_enable_mask) + if (!chip_func || !chip_func->get_intr_enable_mask || !chip_func->set_intr_enable_mask) { return; } @@ -1018,9 +1016,60 @@ void gf_hot_plug_intr_ctrl(disp_info_t* disp_info, unsigned int intr, int enable gf_spin_unlock_irqrestore(disp_info->intr_lock, flags); } +void gf_hot_plug_intr_onoff(disp_info_t* disp_info, int on) +{ + gf_card_t* gf_card = disp_info->gf_card; + struct drm_device* drm = gf_card->drm_dev; + struct drm_connector* connector = NULL; + gf_connector_t* gf_connector = NULL; + unsigned int hpd_int_bits = 0; +#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + struct drm_connector_list_iter conn_iter; +#endif + + mutex_lock(&drm->mode_config.mutex); + +#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) + drm_connector_list_iter_begin(drm, &conn_iter); +#endif + +#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + drm_for_each_connector_iter(connector, &conn_iter) +#else + +#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + drm_for_each_connector(connector, drm) +#else + list_for_each_entry(connector, &drm->mode_config.connector_list, head) +#endif + +#endif + { + //mark status to enable for all outputs that support hot plug + gf_connector = to_gf_connector(connector); + + if ((connector->polled == DRM_CONNECTOR_POLL_HPD) && gf_connector->hpd_int_bit) + { + gf_connector->hpd_enable = on; + hpd_int_bits |= gf_connector->hpd_int_bit; + } + } +#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) + drm_connector_list_iter_end(&conn_iter); +#endif + + mutex_unlock(&drm->mode_config.mutex); + + if (hpd_int_bits) + { + gf_hot_plug_intr_ctrl(disp_info, hpd_int_bits, on); + } +} + void gf_dp_irq_work_func(struct work_struct *work) { - disp_info_t* disp_info = container_of(work, disp_info_t, dp_irq_work); + disp_info_t *disp_info = container_of(work, disp_info_t, dp_irq_work); + gf_connector_t *gf_connector = NULL; unsigned long irq = 0; int device = 0, int_type = 0, detect_devices = 0, comp_edid_devs = 0; int empty = 0, need_detect = 0, need_comp_edid = 0; @@ -1047,9 +1096,16 @@ void gf_dp_irq_work_func(struct work_struct *work) break; } + gf_connector = gf_get_connector_by_device_id(disp_info, device); + if(!gf_connector) + { + continue; + } need_detect = need_comp_edid = 0; + gf_mutex_lock(gf_connector->conn_mutex); disp_cbios_handle_dp_irq(disp_info, device, int_type, &need_detect, &need_comp_edid); + gf_mutex_unlock(gf_connector->conn_mutex); if(need_detect) { @@ -1079,6 +1135,54 @@ void gf_dp_irq_work_func(struct work_struct *work) } } +static void gf_poll_enable_locked(struct drm_device *dev) +{ + int poll = 0; + struct drm_connector *connector = NULL; +#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + struct drm_connector_list_iter conn_iter; +#endif + + WARN_ON(!mutex_is_locked(&dev->mode_config.mutex)); + + if (!dev->mode_config.poll_enabled) + { + return; + } + +#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) + drm_connector_list_iter_begin(dev, &conn_iter); +#endif + +#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + drm_for_each_connector_iter(connector, &conn_iter) +#else + +#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + drm_for_each_connector(connector, dev) +#else + list_for_each_entry(connector, &dev->mode_config.connector_list, head) +#endif + +#endif + { + if (connector->polled & (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT)) + { + poll = true; + break; + } + } +#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) + drm_connector_list_iter_end(&conn_iter); +#endif + + if (poll) + { + schedule_delayed_work(&dev->mode_config.output_poll_work, OUTPUT_POLL_PERIOD); + } +} + +#define INIT_POLLING_TIME 10 void gf_hotplug_work_func(struct work_struct *work) { @@ -1089,7 +1193,7 @@ void gf_hotplug_work_func(struct work_struct *work) struct drm_connector* connector = NULL; gf_connector_t* gf_connector = NULL; unsigned long irq = 0; - unsigned int hpd_outputs = 0, changed = 0, comp_edid_outputs = 0; + unsigned int hpd_outputs = 0, changed = 0, comp_edid_outputs = 0, need_poll = 0; unsigned int plug_out = 0, plug_in = 0, cur_output = 0; enum drm_connector_status old_status; #if DRM_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) @@ -1156,9 +1260,26 @@ void gf_hotplug_work_func(struct work_struct *work) { plug_in |= gf_connector->output_type; } - if((old_status != connector->status) || (gf_connector->compare_edid && gf_connector->edid_changed)) + + if(old_status != connector->status) { changed = 1; + gf_connector->polling_time = 0; + } + else if(gf_connector->compare_edid && gf_connector->edid_changed) + { + changed = 1; + gf_connector->polling_time = 0; + } + else if(old_status == connector_status_connected && UT_OUTPUT_TYPE_HDMI == gf_connector->monitor_type) + { + //HDMI plug out INT happen, but no change can be detected, use polling + gf_info("Polling work will detect connector 0x%x for %d times.\n", gf_connector->output_type, INIT_POLLING_TIME); + gf_connector->polling_time = INIT_POLLING_TIME; + if(!disp_info->poll_running) + { + need_poll = 1; + } } gf_connector->compare_edid = 0; } @@ -1191,6 +1312,11 @@ void gf_hotplug_work_func(struct work_struct *work) drm_connector_list_iter_end(&conn_iter); #endif + if(need_poll) + { + gf_poll_enable_locked(drm); + } + mutex_unlock(&mode_config->mutex); if(changed) @@ -1358,53 +1484,6 @@ void gf_irq_uninstall (struct drm_device *dev) gf_disp_disable_interrupt(disp_info, 1); } -static void gf_poll_enable_locked(struct drm_device *dev) -{ - int poll = 0; - struct drm_connector *connector = NULL; -#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) - struct drm_connector_list_iter conn_iter; -#endif - - WARN_ON(!mutex_is_locked(&dev->mode_config.mutex)); - - if (!dev->mode_config.poll_enabled) - { - return; - } - -#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) - drm_connector_list_iter_begin(dev, &conn_iter); -#endif - -#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) - drm_for_each_connector_iter(connector, &conn_iter) -#else - -#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) - drm_for_each_connector(connector, dev) -#else - list_for_each_entry(connector, &dev->mode_config.connector_list, head) -#endif - -#endif - { - if (connector->polled & (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT)) - { - poll = true; - break; - } - } -#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) - drm_connector_list_iter_end(&conn_iter); -#endif - - if (poll) - { - schedule_delayed_work(&dev->mode_config.output_poll_work, OUTPUT_POLL_PERIOD); - } -} - void gf_poll_enable(disp_info_t* disp_info) { gf_card_t* gf_card = disp_info->gf_card; @@ -1466,7 +1545,7 @@ void gf_output_poll_work_func(struct work_struct *work) /* Ignore forced connectors. */ if ((connector->force) || gf_connector->output_type == DISP_OUTPUT_SPLICE || - (!connector->polled || connector->polled == DRM_CONNECTOR_POLL_HPD)) + (!connector->polled || (connector->polled == DRM_CONNECTOR_POLL_HPD && !gf_connector->polling_time))) { continue; } @@ -1474,6 +1553,7 @@ void gf_output_poll_work_func(struct work_struct *work) old_status = connector->status; /* if we are connected and don't want to poll for disconnect skip it */ if (old_status == connector_status_connected && + connector->polled != DRM_CONNECTOR_POLL_HPD && !(connector->polled & DRM_CONNECTOR_POLL_DISCONNECT)) { continue; @@ -1516,16 +1596,30 @@ void gf_output_poll_work_func(struct work_struct *work) } changed = 1; + gf_connector->polling_time = 0; } - else + else if(gf_connector->polling_time > 0) { - changed = gf_connector->edid_changed; + if (gf_connector->edid_changed) + { + changed = 1; + gf_connector->polling_time = 0; + } + else + { + gf_connector->polling_time--; + } } } #if DRM_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) drm_connector_list_iter_end(&conn_iter); #endif + disp_info->poll_running = 0; + if(repoll) + { + disp_info->poll_running = 1; + } mutex_unlock(&dev->mode_config.mutex); out: diff --git a/drivers/gpu/drm/arise/linux/gf_irq.h b/drivers/gpu/drm/arise/linux/gf_irq.h index 4c54ebd5979dc56cedf838610bc57d1f74eea97e..d7445c37492c5eed04ea7f8f54e69078c74703f0 100644 --- a/drivers/gpu/drm/arise/linux/gf_irq.h +++ b/drivers/gpu/drm/arise/linux/gf_irq.h @@ -98,7 +98,7 @@ irqreturn_t gf_irq_handle(int irq, void *arg); void gf_irq_uninstall (struct drm_device *dev); -void gf_hot_plug_intr_ctrl(disp_info_t* disp_info, unsigned int intr, int enable); +void gf_hot_plug_intr_onoff(disp_info_t* disp_info, int on); void gf_hotplug_work_func(struct work_struct *work); diff --git a/drivers/gpu/drm/arise/linux/gf_kms.h b/drivers/gpu/drm/arise/linux/gf_kms.h index 2c47e6a9bdc9109434461ea15753b544f2bc954c..0ab10fe55cb1c7b2f45b28c1a9cffa69cab6ea46 100644 --- a/drivers/gpu/drm/arise/linux/gf_kms.h +++ b/drivers/gpu/drm/arise/linux/gf_kms.h @@ -102,6 +102,7 @@ typedef struct unsigned int lut_entry[256]; //(b | g << 8 | r << 16) for 8bit lut or (b | g << 10 | r << 20) for 10bit lut #endif unsigned int enabled; + unsigned int vsync_int; }gf_crtc_t; typedef struct @@ -123,6 +124,7 @@ typedef struct int hda_codec_index; int hpd_int_bit; int hpd_enable; + int polling_time; struct os_mutex* conn_mutex; #if DRM_VERSION_CODE < KERNEL_VERSION(4, 8, 0) gf_encoder_t* new_encoder; diff --git a/drivers/gpu/drm/arise/linux/gf_params.c b/drivers/gpu/drm/arise/linux/gf_params.c index 3e8014dc547898176ca94d24b02aa65120e31c95..b8ce204caf51b6d9c778b20ccc7f70aed129d2ee 100644 --- a/drivers/gpu/drm/arise/linux/gf_params.c +++ b/drivers/gpu/drm/arise/linux/gf_params.c @@ -22,7 +22,7 @@ struct gf_params gf_modparams __read_mostly = { #else .gf_fb = 1, #endif - .gf_pwm_mode = 1, + .gf_pwm_mode = 0x1, .gf_dfs_mode = 0, .gf_worker_thread_enable = 1, .gf_recovery_enable = 1, @@ -41,6 +41,7 @@ struct gf_params gf_modparams __read_mostly = { .gf_local_size_m = 0, .gf_pcie_size_g = 0,//adjust pcie memory size, 0 use real pcie memory .gf_pcie_size_m = 0, + .gf_runtime_pm = 0, .debugfs_mask = 0x1, .misc_control_flag = 0x111, }; @@ -66,6 +67,7 @@ gf_param_named(gf_local_size_g, int, 0444, "manual set the local vram size, uint gf_param_named(gf_local_size_m, int, 0444, "manual set the local vram size, uint in MB, the size should not larger than real vram size"); gf_param_named(gf_pcie_size_g, int, 0444, "manual set the pcie vram size, uint in GB, the size should not larger than real vram size"); gf_param_named(gf_pcie_size_m, int, 0444, "manual set the pcie vram size, uint in MB, the size should not larger than real vram size"); +gf_param_named(gf_runtime_pm, int, 0444, "0-disable, 1-enable"); gf_param_named(debugfs_mask, int, 0444, "debugfs control bits"); gf_param_named(misc_control_flag, int, 0444, "misc control flag"); diff --git a/drivers/gpu/drm/arise/linux/gf_params.h b/drivers/gpu/drm/arise/linux/gf_params.h index 991dc4990b5fca53224044e58cdb238f955f076b..b1a2e80614f88abf36e0b4a960ee1e53f9fa6983 100644 --- a/drivers/gpu/drm/arise/linux/gf_params.h +++ b/drivers/gpu/drm/arise/linux/gf_params.h @@ -41,6 +41,7 @@ struct gf_params { int gf_local_size_m; int gf_pcie_size_g; int gf_pcie_size_m; + int gf_runtime_pm; int debugfs_mask; //debugfs mask, bit0: gem_enable int misc_control_flag; }; diff --git a/drivers/gpu/drm/arise/linux/gf_pcie.c b/drivers/gpu/drm/arise/linux/gf_pcie.c index 0dba5d64d0f660ad75cf8135e3f4d2e0594f2d86..9167ce336ee44ae48e09ca98f34442ddd2bb7924 100644 --- a/drivers/gpu/drm/arise/linux/gf_pcie.c +++ b/drivers/gpu/drm/arise/linux/gf_pcie.c @@ -24,6 +24,7 @@ #include "gf_irq.h" #include "gf_fbdev.h" #include "gf_params.h" +#include "gf_pm.h" #if DRM_VERSION_CODE >= KERNEL_VERSION(5,5,0) #if DRM_VERSION_CODE < KERNEL_VERSION(5,8,0) @@ -37,7 +38,11 @@ #include #if DRM_VERSION_CODE >= KERNEL_VERSION(6, 2, 0) +#if DRM_VERSION_CODE < KERNEL_VERSION(6,11,0) #include +#else +#include +#endif #endif #define DRIVER_DESC "Glenfly DRM Pro" @@ -134,7 +139,7 @@ static void gf_pcie_shutdown(struct pci_dev *pdev) } #endif -static int gf_drm_suspend(struct drm_device *dev, pm_message_t state) +static int __gf_drm_suspend(struct drm_device *dev, pm_message_t state, bool fbcon) { gf_card_t* gf = dev->dev_private; int ret; @@ -143,12 +148,15 @@ static int gf_drm_suspend(struct drm_device *dev, pm_message_t state) disp_suspend(dev); gf_info("drm suspend: save display status finished.\n"); -#if DRM_VERSION_CODE < KERNEL_VERSION(4, 19, 0) - gf_fbdev_set_suspend(gf, 1); -#else - drm_fb_helper_set_suspend_unlocked(dev->fb_helper, true); -#endif - gf_info("drm suspend: save drmfb status finished.\n"); + if (fbcon) + { + #if DRM_VERSION_CODE < KERNEL_VERSION(4, 19, 0) + gf_fbdev_set_suspend(gf, 1); + #else + drm_fb_helper_set_suspend_unlocked(dev->fb_helper, true); + #endif + gf_info("drm suspend: save drmfb status finished.\n"); + } ret = gf_core_interface->save_state(gf->adapter, state.event == PM_EVENT_FREEZE); @@ -188,7 +196,19 @@ static int gf_drm_suspend(struct drm_device *dev, pm_message_t state) return 0; } -static int gf_drm_resume(struct drm_device *dev) +static int gf_drm_suspend(struct drm_device *dev, pm_message_t state) +{ + return __gf_drm_suspend(dev, state, true); +} + +static int gf_runtime_suspend(struct drm_device *dev, pm_message_t state) +{ + //TODO + return 0; + //return __gf_drm_suspend(dev, state, false); +} + +static int __gf_drm_resume(struct drm_device *dev, bool fbcon) { gf_card_t* gf = dev->dev_private; int ret = 0; @@ -212,6 +232,8 @@ static int gf_drm_resume(struct drm_device *dev) disp_vblank_restore(dev); #endif + gf_core_interface->reset_dvfs_power_flag(gf->adapter); + tasklet_enable(&gf->fence_notify); gf_enable_interrupt(gf->pdev); gf_info("drm resume: re-enable irq finished.\n"); @@ -223,12 +245,15 @@ static int gf_drm_resume(struct drm_device *dev) return -1; } -#if DRM_VERSION_CODE < KERNEL_VERSION(4, 19, 0) - gf_fbdev_set_suspend(gf, 0); -#else - drm_fb_helper_set_suspend_unlocked(dev->fb_helper, false); -#endif - gf_info("drm resume: restore drmfb status finished.\n"); + if (fbcon) + { + #if DRM_VERSION_CODE < KERNEL_VERSION(4, 19, 0) + gf_fbdev_set_suspend(gf, 0); + #else + drm_fb_helper_set_suspend_unlocked(dev->fb_helper, false); + #endif + gf_info("drm resume: restore drmfb status finished.\n"); + } disp_post_resume(dev); gf_info("drm resume: restore display status finished.\n"); @@ -236,6 +261,18 @@ static int gf_drm_resume(struct drm_device *dev) return 0; } +static int gf_drm_resume(struct drm_device *dev) +{ + return __gf_drm_resume(dev, true); +} + +static int gf_runtime_resume(struct drm_device *dev) +{ + //TODO + return 0; + //return __gf_drm_resume(dev, false); +} + static __inline__ int gf_backdoor_available(void) { #if defined(__i386__) || defined(__x86_64__) @@ -279,6 +316,12 @@ static int gf_kick_out_firmware_fb(struct pci_dev *pdev) { struct apertures_struct *ap; bool primary = false; +#if defined(__loongarch__) + int i = 0; + struct fb_info *pfb_info = NULL; + unsigned long long fix_fb_base; + struct platform_device *pd = NULL; +#endif ap = alloc_apertures(1); if (!ap) @@ -291,11 +334,41 @@ static int gf_kick_out_firmware_fb(struct pci_dev *pdev) primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW; #endif +#if defined(__loongarch__) + if ((ap->ranges[0].base >> 32) != 0) + { + fix_fb_base = ap->ranges[0].base & 0xFFFFFFFF; + for (i = 0; i < num_registered_fb; i++) + { + pfb_info = registered_fb[i]; + if (!pfb_info) + { + continue; + } + + pd = to_platform_device(pfb_info->device); + if (!pd) + { + continue; + } + + if (gf_strcmp(pd->name, "efi-framebuffer") == 0 && + pfb_info->fix.smem_start == fix_fb_base) + { + ap->ranges[0].base = fix_fb_base; + gf_info("reassign aperture base address to remove firmware framebuffer."); + break; + } + } + } +#endif + #if DRM_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) drm_fb_helper_remove_conflicting_framebuffers(ap, "arisedrmfb", primary); #else remove_conflicting_framebuffers(ap, "arisedrmfb", primary); #endif + gf_free(ap); return 0; @@ -341,13 +414,6 @@ static int gf_drm_load_kms(struct drm_device *dev, unsigned long flags) gf_error("remove conflicting framebuffer failed. ret:%x.\n", ret); } - - ret = pci_request_regions(pdev, gf_driver.name); - if(ret) - { - gf_error("pci_request_regions() failed. ret:%x.\n", ret); - } - pci_set_master(pdev); //fpga only @@ -388,6 +454,17 @@ static int gf_drm_load_kms(struct drm_device *dev, unsigned long flags) gf_fbdev_init(gf); #endif + if (gf->runtime_pm) + { + gf_rpm_set_driver_flags(dev->dev); + gf_rpm_use_autosuspend(dev->dev); + gf_rpm_set_autosuspend_delay(dev->dev, 5000); //5s + gf_rpm_set_active(dev->dev); + gf_rpm_allow(dev->dev); //-1 + gf_rpm_mark_last_busy(dev->dev); + gf_rpm_put_autosuspend(dev->dev); + } + return ret; } @@ -402,9 +479,18 @@ static int gf_drm_open(struct drm_device *dev, struct drm_file *file) return err; } + err = gf_rpm_get_sync(dev->dev); + if (err < 0) + { + goto err_pm_put; + } + priv = gf_calloc(sizeof(gf_file_t)); if (!priv) - return -ENOMEM; + { + err = -ENOMEM; + goto err_suspend_out; + } file->driver_priv = priv; priv->parent_file = file; @@ -419,7 +505,12 @@ static int gf_drm_open(struct drm_device *dev, struct drm_file *file) priv->debug = gf_debugfs_add_device_node(gf->debugfs_dev, gf_get_current_pid(), priv->gpu_device); } - return 0; +err_suspend_out: + gf_rpm_mark_last_busy(dev->dev); +err_pm_put: + gf_rpm_put_autosuspend(dev->dev); + + return err; } static void gf_drm_postclose(struct drm_device *dev, struct drm_file *file) @@ -432,6 +523,8 @@ static void gf_drm_postclose(struct drm_device *dev, struct drm_file *file) gf_mutex_unlock(gf->lock); } + gf_rpm_get_sync(dev->dev); + if(priv->gpu_device) { if(gf->debugfs_dev) @@ -448,6 +541,9 @@ static void gf_drm_postclose(struct drm_device *dev, struct drm_file *file) gf_free(priv); file->driver_priv = NULL; + + gf_rpm_mark_last_busy(dev->dev); + gf_rpm_put_autosuspend(dev->dev); } static void gf_drm_last_close(struct drm_device* dev) @@ -523,6 +619,27 @@ int gf_mmap(struct file *filp, struct vm_area_struct *vma) return ret; } +static long gf_drm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_device *dev = file_priv->minor->dev ; + long ret; + + ret = gf_rpm_get_sync(dev->dev); + if (ret < 0) + { + goto out; + } + + ret = drm_ioctl(filp, cmd, arg); + + gf_rpm_mark_last_busy(dev->dev); +out: + gf_rpm_put_autosuspend(dev->dev); + return ret; +} + + #if defined(CONFIG_COMPAT) static long gf_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { @@ -535,7 +652,7 @@ static long gf_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long a } else { - ret = drm_ioctl(filp, cmd, arg); + ret = gf_drm_ioctl(filp, cmd, arg); } return ret; @@ -565,7 +682,7 @@ static struct file_operations gf_drm_fops = { .owner = THIS_MODULE, .open = drm_open, .release = drm_release, - .unlocked_ioctl = drm_ioctl, + .unlocked_ioctl = gf_drm_ioctl, #if defined(__x86_64__) && defined(CONFIG_COMPAT) .compat_ioctl = gf_compat_ioctl, #endif @@ -714,7 +831,20 @@ static int gf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *ent) gf_debugfs_init(gf); +#if DRM_VERSION_CODE < KERNEL_VERSION(6, 11, 0) drm_fbdev_generic_setup(dev, 32); +#else + drm_fbdev_ttm_setup(dev, 32); +#endif + +#if DRM_VERSION_CODE < KERNEL_VERSION(5, 2, 0) + //skip_vt_switch is not set in drm_fb_helper_alloc_fbi function under kernel version of 5.2.0, patch it here + if (dev->fb_helper && dev->fb_helper->fbdev && + dev->fb_helper->fbdev->dev) + { + pm_vt_switch_required(dev->fb_helper->fbdev->dev, false); + } +#endif return 0; @@ -732,20 +862,41 @@ static int gf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *ent) } #if DRM_VERSION_CODE >= KERNEL_VERSION(3,10,52) +#if DRM_VERSION_CODE >= KERNEL_VERSION(6,11,0) +static void gf_pcie_cleanup(struct pci_dev *pdev) +#else static void __exit gf_pcie_cleanup(struct pci_dev *pdev) +#endif { struct drm_device *dev = pci_get_drvdata(pdev); + gf_card_t *gf_card = (gf_card_t *)dev->dev_private; + gf_info("pcie_cleanup.\n"); + + if (gf_card->runtime_pm) + { + gf_rpm_get_sync(dev->dev); + gf_rpm_forbid(dev->dev); + } + #if DRM_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) drm_dev_unplug(dev); + +#if DRM_VERSION_CODE >= KERNEL_VERSION(5, 2, 0) + drm_dev_put(dev); +#endif + #else gf_drm_unload_kms(dev); -#endif + drm_put_dev(dev); +#endif - pci_set_drvdata(pdev, NULL); +#if DRM_VERSION_CODE >= KERNEL_VERSION(4, 19 ,0) + pci_disable_device(pdev); +#endif - pci_release_regions(pdev); + pci_set_drvdata(pdev, NULL); } #endif @@ -764,9 +915,18 @@ static int gf_pmops_suspend(struct device *dev) static int gf_pmops_resume(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + gf_card_t *gf_card = (gf_card_t *)drm_dev->dev_private; gf_info("pci device(vendor:0x%X, device:0x%X) pm resume\n", pdev->vendor, pdev->device); + if (gf_card->runtime_pm) + { + gf_rpm_disable(dev); + gf_rpm_set_active(dev); + gf_rpm_enable(dev); + } + #if DRM_VERSION_CODE >= KERNEL_VERSION(4,0,0) gf_drm_resume(pci_get_drvdata(pdev)); #endif @@ -835,27 +995,73 @@ static int gf_pmops_restore(struct device *dev) static int gf_pmops_runtime_suspend(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + gf_card_t *gf_card = (gf_card_t *)drm_dev->dev_private; + + if (!gf_card->runtime_pm) + { + gf_rpm_forbid(dev); + return -EBUSY; + } gf_info("pci device(vendor:0x%X, device:0x%X) pm runtime_suspend\n", pdev->vendor, pdev->device); +#if DRM_VERSION_CODE >= KERNEL_VERSION(4,0,0) + gf_runtime_suspend(drm_dev, PMSG_AUTO_SUSPEND); +#endif + return 0; } static int gf_pmops_runtime_resume(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = dev_get_drvdata(dev); + gf_card_t *gf_card = (gf_card_t *)drm_dev->dev_private; + + if (!gf_card->runtime_pm) + { + return -EINVAL; + } gf_info("pci device(vendor:0x%X, device:0x%X) pm runtime_resume\n", pdev->vendor, pdev->device); +#if DRM_VERSION_CODE >= KERNEL_VERSION(4,0,0) + gf_runtime_resume(drm_dev); +#endif + return 0; } + static int gf_pmops_runtime_idle(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = dev_get_drvdata(dev); + gf_card_t *gf_card = (gf_card_t *)drm_dev->dev_private; + struct drm_crtc *crtc; + + if (!gf_card->runtime_pm) + { + gf_rpm_forbid(dev); + return -EBUSY; + } gf_info("pci device(vendor:0x%X, device:0x%X) pm runtime_idle\n", pdev->vendor, pdev->device); - return 0; + list_for_each_entry(crtc, &drm_dev->mode_config.crtc_list, head) + { + if (crtc->enabled) + { + DRM_DEBUG_DRIVER("fail to power off - crtc active\n"); + return -EBUSY; + } + } + + gf_rpm_mark_last_busy(dev); + gf_rpm_autosuspend(dev); + + /* use autosuspend instead of calling rpm_suspend directly in main rpm_idle */ + return 1; } static void gf_shutdown(struct pci_dev *pdev) @@ -925,7 +1131,6 @@ int gf_write_command_status32(void *dev, unsigned int command) return pci_write_config_dword((struct pci_dev*)dev, 0x4, command); } - int gf_get_bar1(void *dev, unsigned int *bar1) { return pci_read_config_dword((struct pci_dev*)dev, 0x14, bar1); diff --git a/drivers/gpu/drm/arise/linux/gf_pm.h b/drivers/gpu/drm/arise/linux/gf_pm.h new file mode 100644 index 0000000000000000000000000000000000000000..a01544874bcb1d7354caf16a2dd0b5b9a073cec2 --- /dev/null +++ b/drivers/gpu/drm/arise/linux/gf_pm.h @@ -0,0 +1,128 @@ +//***************************************************************************** +// Copyright (c) 2021 Glenfly Tech Co., Ltd.. +// All Rights Reserved. +// +// This is UNPUBLISHED PROPRIETARY SOURCE CODE of Glenfly Tech Co., Ltd..; +// the contents of this file may not be disclosed to third parties, copied or +// duplicated in any form, in whole or in part, without the prior written +// permission of Glenfly Tech Co., Ltd.. +// +// The copyright of the source code is protected by the copyright laws of the People's +// Republic of China and the related laws promulgated by the People's Republic of China +// and the international covenant(s) ratified by the People's Republic of China. +//***************************************************************************** + +#ifndef __GF_PM_H__ +#define __GF_PM_H__ + +#include + +static inline int gf_rpm_get_sync(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + return pm_runtime_get_sync(dev); +#else + return 0; +#endif +} + +static inline int gf_rpm_put_autosuspend(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + return pm_runtime_put_autosuspend(dev); +#else + return 0; +#endif +} + +static inline void gf_rpm_use_autosuspend(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + pm_runtime_use_autosuspend(dev); +#endif +} + +static inline void gf_rpm_set_autosuspend_delay(struct device *dev, int delay) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + pm_runtime_set_autosuspend_delay(dev, delay); //5s +#endif +} + +static inline int gf_rpm_set_active(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + return pm_runtime_set_active(dev); +#else + return 0; +#endif +} + +static inline void gf_rpm_allow(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + pm_runtime_allow(dev); //-1 +#endif +} + +static inline void gf_rpm_forbid(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + pm_runtime_forbid(dev); +#endif +} + +static inline void gf_rpm_mark_last_busy(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + pm_runtime_mark_last_busy(dev); +#endif +} + +static inline void gf_rpm_enable(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + pm_runtime_enable(dev); +#endif +} + +static inline void gf_rpm_disable(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + pm_runtime_disable(dev); +#endif +} + +static inline int gf_rpm_autosuspend(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + return pm_runtime_autosuspend(dev); +#else + return 0; +#endif +} + +static inline void gf_rpm_get_noresume(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + pm_runtime_get_noresume(dev); +#endif +} + +static inline void gf_rpm_put_noidle(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + pm_runtime_put_noidle(dev); +#endif +} + +static inline void gf_rpm_set_driver_flags(struct device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) + dev_pm_set_driver_flags(dev, DPM_FLAG_NO_DIRECT_COMPLETE); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + dev_pm_set_driver_flags(dev, DPM_FLAG_NEVER_SKIP); +#endif +} + +#endif diff --git a/drivers/gpu/drm/arise/linux/gf_splice.c b/drivers/gpu/drm/arise/linux/gf_splice.c index c75ab4594856e92068483b324496cd4624576b7a..a64e3e2d9074fce716b42909480df0b35f37b892 100644 --- a/drivers/gpu/drm/arise/linux/gf_splice.c +++ b/drivers/gpu/drm/arise/linux/gf_splice.c @@ -2035,6 +2035,7 @@ static void gf_splice_init_crtc_and_planes(gf_splice_manager_t *splice_manager) gf_crtc->crtc_dpms = 0; gf_crtc->support_scale = disp_info->scale_support; gf_crtc->plane_cnt = GF_SPLICE_PLANE_NUM; + gf_crtc->vsync_int = 0; ret = drm_crtc_init_with_planes(drm, &gf_crtc->base_crtc, &gf_plane->base_plane, diff --git a/drivers/gpu/drm/arise/linux/gf_sysfs.c b/drivers/gpu/drm/arise/linux/gf_sysfs.c old mode 100755 new mode 100644 diff --git a/drivers/gpu/drm/arise/shared/gf_perf.h b/drivers/gpu/drm/arise/shared/gf_perf.h old mode 100755 new mode 100644 diff --git a/drivers/gpu/drm/arise/shared/gf_types.h b/drivers/gpu/drm/arise/shared/gf_types.h index b97c484490072507515dbfd2aeb4821c1aef8ba9..6a36a4d5a5148bc6697de158f3d570b14ca44c52 100644 --- a/drivers/gpu/drm/arise/shared/gf_types.h +++ b/drivers/gpu/drm/arise/shared/gf_types.h @@ -239,21 +239,31 @@ typedef struct unsigned int HWCtxBufIndex; unsigned int Pad; // for alignment request unsigned int GF_Interface_Version; - unsigned int Contain3dCmd :1; // keep consistent with source_new, not used for elite - unsigned int ContainLpdpCmd :1; - unsigned int CLCSOnly :1; // opencl without 2D/3D blt/L2 invalidate - unsigned int Flag2dCmd :1; // Xserver 2d command - unsigned int Flag3dbltCmd :1; // Xserver composite 3d blt command - unsigned int DvfsForceLevel :3; - unsigned int InitializeContext :1; - unsigned int ContainDIP :1; - unsigned int TraceDMA :1; - - unsigned int resize_command_buffer :1; /* resize command buffer after render */ - unsigned int resize_allocation_list :1; /* resize allocation list after render */ - unsigned int resize_patch_location_list :1; /* resize patch location list after render */ - unsigned int null_rendering :1; /* null rendering, refer to WDK */ - unsigned int normal_recovery :1; /* recovery normally if engine hang */ + + union + { + struct + { + unsigned int Contain3dCmd :1; // keep consistent with source_new, not used for elite + unsigned int ContainLpdpCmd :1; + unsigned int CLCSOnly :1; // opencl without 2D/3D blt/L2 invalidate + unsigned int Flag2dCmd :1; // Xserver 2d command + unsigned int Flag3dbltCmd :1; // Xserver composite 3d blt command + unsigned int DvfsForceLevel :3; + unsigned int InitializeContext :1; + unsigned int ContainDIP :1; + unsigned int TraceDMA :1; + + unsigned int resize_command_buffer :1; /* resize command buffer after render */ + unsigned int resize_allocation_list :1; /* resize allocation list after render */ + unsigned int resize_patch_location_list :1; /* resize patch location list after render */ + unsigned int null_rendering :1; /* null rendering, refer to WDK */ + unsigned int normal_recovery :1; /* recovery normally if engine hang */ + unsigned int ForceSlice :1; /* ForceSlice */ + unsigned int Reserved :15; + }; + unsigned int flags; + }; } gf_render_flags_t; typedef struct gf_allocation_list diff --git a/drivers/gpu/drm/loongson/Kconfig b/drivers/gpu/drm/loongson/Kconfig index df6946d505facf45f1a877c4696a577f27b0f2f7..a9c1526a1c8614c5f7bd404f15cdd332a3c5c884 100644 --- a/drivers/gpu/drm/loongson/Kconfig +++ b/drivers/gpu/drm/loongson/Kconfig @@ -15,3 +15,5 @@ config DRM_LOONGSON If "M" is selected, the module will be called loongson. If in doubt, say "N". + +source "drivers/gpu/drm/loongson/ast_old/Kconfig" diff --git a/drivers/gpu/drm/loongson/Makefile b/drivers/gpu/drm/loongson/Makefile index 91e72bd900c1056184851b04565ddc759d649386..a7f05d3fa0cacc9276ab24009d904f9041741e90 100644 --- a/drivers/gpu/drm/loongson/Makefile +++ b/drivers/gpu/drm/loongson/Makefile @@ -20,3 +20,4 @@ loongson-y += loongson_device.o \ loongson_module.o obj-$(CONFIG_DRM_LOONGSON) += loongson.o +obj-$(CONFIG_DRM_AST_LOONGSON) += ast_old/ diff --git a/drivers/gpu/drm/loongson/ast_old/Kconfig b/drivers/gpu/drm/loongson/ast_old/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..40af6934ac3666ee2134f2fc78eb9008550c4570 --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_AST_LOONGSON + tristate "AST server chips for Loongson Platform" + depends on DRM && PCI && MMU && LOONGARCH + select DRM_KMS_HELPER + select DRM_VRAM_HELPER + select DRM_TTM + select DRM_TTM_HELPER + help + Say yes for experimental AST GPU driver. Do not enable + this driver without having a working -modesetting, + and a version of AST that knows to fail if KMS + is bound to the driver. These GPUs are commonly found + in server chipsets. diff --git a/drivers/gpu/drm/loongson/ast_old/Makefile b/drivers/gpu/drm/loongson/ast_old/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..02d40f992f5ad84b9b18d16e9739e954c302ea07 --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the drm device driver. This driver provides support for the +# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. + +ast-y := ast_drv.o ast_i2c.o ast_main.o ast_mm.o ast_mode.o ast_post.o ast_dp501.o ast_dp.o + +obj-$(CONFIG_DRM_AST_LOONGSON) := ast.o diff --git a/drivers/gpu/drm/loongson/ast_old/ast_dp.c b/drivers/gpu/drm/loongson/ast_old/ast_dp.c new file mode 100644 index 0000000000000000000000000000000000000000..b7e1f51d558b3029d7f22d25edf0135fd5e76bc7 --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/ast_dp.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021, ASPEED Technology Inc. +// Authors: KuoHsiang Chou + +#include +#include +#include +#include "ast_drv.h" + +int ast_astdp_read_edid(struct drm_device *dev, u8 *ediddata) +{ + struct ast_private *ast = to_ast_private(dev); + u8 i = 0, j = 0; + + /* + * CRD1[b5]: DP MCU FW is executing + * CRDC[b0]: DP link success + * CRDF[b0]: DP HPD + * CRE5[b0]: Host reading EDID process is done + */ + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, + ASTDP_MCU_FW_EXECUTING) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDC, + ASTDP_LINK_SUCCESS) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, ASTDP_HPD) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, + ASTDP_HOST_EDID_READ_DONE_MASK))) { + goto err_astdp_edid_not_ready; + } + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, + (u8)~ASTDP_HOST_EDID_READ_DONE_MASK, 0x00); + + for (i = 0; i < 32; i++) { + /* + * CRE4[7:0]: Read-Pointer for EDID (Unit: 4bytes); valid range: 0~64 + */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE4, + ASTDP_AND_CLEAR_MASK, (u8)i); + j = 0; + + /* + * CRD7[b0]: valid flag for EDID + * CRD6[b0]: mirror read pointer for EDID + */ + while ((ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD7, + ASTDP_EDID_VALID_FLAG_MASK) != + 0x01) || + (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD6, + ASTDP_EDID_READ_POINTER_MASK) != + i)) { + /* + * Delay are getting longer with each retry. + * 1. The Delays are often 2 loops when users request "Display Settings" + * of right-click of mouse. + * 2. The Delays are often longer a lot when system resume from S3/S4. + */ + mdelay(j + 1); + + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, + 0xD1, + ASTDP_MCU_FW_EXECUTING) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, + 0xDC, + ASTDP_LINK_SUCCESS) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, + 0xDF, ASTDP_HPD))) { + goto err_astdp_jump_out_loop_of_edid; + } + + j++; + if (j > 200) + goto err_astdp_jump_out_loop_of_edid; + } + + *(ediddata) = ast_get_index_reg_mask( + ast, AST_IO_CRTC_PORT, 0xD8, ASTDP_EDID_READ_DATA_MASK); + *(ediddata + 1) = ast_get_index_reg_mask( + ast, AST_IO_CRTC_PORT, 0xD9, ASTDP_EDID_READ_DATA_MASK); + *(ediddata + 2) = ast_get_index_reg_mask( + ast, AST_IO_CRTC_PORT, 0xDA, ASTDP_EDID_READ_DATA_MASK); + *(ediddata + 3) = ast_get_index_reg_mask( + ast, AST_IO_CRTC_PORT, 0xDB, ASTDP_EDID_READ_DATA_MASK); + + if (i == 31) { + /* + * For 128-bytes EDID_1.3, + * 1. Add the value of Bytes-126 to Bytes-127. + * The Bytes-127 is Checksum. Sum of all 128bytes should + * equal 0 (mod 256). + * 2. Modify Bytes-126 to be 0. + * The Bytes-126 indicates the Number of extensions to + * follow. 0 represents noextensions. + */ + *(ediddata + 3) = *(ediddata + 3) + *(ediddata + 2); + *(ediddata + 2) = 0; + } + + ediddata += 4; + } + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, + (u8)~ASTDP_HOST_EDID_READ_DONE_MASK, + ASTDP_HOST_EDID_READ_DONE); + + return 0; + +err_astdp_jump_out_loop_of_edid: + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, + (u8)~ASTDP_HOST_EDID_READ_DONE_MASK, + ASTDP_HOST_EDID_READ_DONE); + return (~(j + 256) + 1); + +err_astdp_edid_not_ready: + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, + ASTDP_MCU_FW_EXECUTING))) + return (~0xD1 + 1); + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDC, + ASTDP_LINK_SUCCESS))) + return (~0xDC + 1); + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, ASTDP_HPD))) + return (~0xDF + 1); + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, + ASTDP_HOST_EDID_READ_DONE_MASK))) + return (~0xE5 + 1); + + return 0; +} + +/* + * Launch Aspeed DP + */ +void ast_dp_launch(struct drm_device *dev, u8 bPower) +{ + u32 i = 0, j = 0, WaitCount = 1; + u8 bDPTX = 0; + u8 bDPExecute = 1; + + struct ast_private *ast = to_ast_private(dev); + // S3 come back, need more time to wait BMC ready. + if (bPower) + WaitCount = 300; + + // Wait total count by different condition. + for (j = 0; j < WaitCount; j++) { + bDPTX = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, + TX_TYPE_MASK); + + if (bDPTX) + break; + + msleep(100); + } + + // 0xE : ASTDP with DPMCU FW handling + if (bDPTX == ASTDP_DPMCU_TX) { + // Wait one second then timeout. + i = 0; + + while (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, + COPROCESSOR_LAUNCH) != + COPROCESSOR_LAUNCH) { + i++; + // wait 100 ms + msleep(100); + + if (i >= 10) { + // DP would not be ready. + bDPExecute = 0; + break; + } + } + + if (bDPExecute) + ast->tx_chip_types |= BIT(AST_TX_ASTDP); + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, + (u8)~ASTDP_HOST_EDID_READ_DONE_MASK, + ASTDP_HOST_EDID_READ_DONE); + } +} + +void ast_dp_power_on_off(struct drm_device *dev, bool on) +{ + struct ast_private *ast = to_ast_private(dev); + // Read and Turn off DP PHY sleep + u8 bE3 = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE3, + AST_DP_VIDEO_ENABLE); + + // Turn on DP PHY sleep + if (!on) + bE3 |= AST_DP_PHY_SLEEP; + + // DP Power on/off + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE3, + (u8)~AST_DP_PHY_SLEEP, bE3); +} + +void ast_dp_set_on_off(struct drm_device *dev, bool on) +{ + struct ast_private *ast = to_ast_private(dev); + u8 video_on_off = on; + + // Video On/Off + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE3, + (u8)~AST_DP_VIDEO_ENABLE, on); + + // If DP plug in and link successful then check video on / off status + if (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDC, + ASTDP_LINK_SUCCESS) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, ASTDP_HPD)) { + video_on_off <<= 4; + while (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, + ASTDP_MIRROR_VIDEO_ENABLE) != + video_on_off) { + // wait 1 ms + mdelay(1); + } + } +} + +void ast_dp_set_mode(struct drm_crtc *crtc, + struct ast_vbios_mode_info *vbios_mode) +{ + struct ast_private *ast = to_ast_private(crtc->dev); + + u32 ulRefreshRateIndex; + u8 ModeIdx; + + ulRefreshRateIndex = vbios_mode->enh_table->refresh_rate_index - 1; + + switch (crtc->mode.crtc_hdisplay) { + case 320: + ModeIdx = ASTDP_320x240_60; + break; + case 400: + ModeIdx = ASTDP_400x300_60; + break; + case 512: + ModeIdx = ASTDP_512x384_60; + break; + case 640: + ModeIdx = (ASTDP_640x480_60 + (u8)ulRefreshRateIndex); + break; + case 800: + ModeIdx = (ASTDP_800x600_56 + (u8)ulRefreshRateIndex); + break; + case 1024: + ModeIdx = (ASTDP_1024x768_60 + (u8)ulRefreshRateIndex); + break; + case 1152: + ModeIdx = ASTDP_1152x864_75; + break; + case 1280: + if (crtc->mode.crtc_vdisplay == 800) + ModeIdx = + (ASTDP_1280x800_60_RB - (u8)ulRefreshRateIndex); + else // 1024 + ModeIdx = (ASTDP_1280x1024_60 + (u8)ulRefreshRateIndex); + break; + case 1360: + case 1366: + ModeIdx = ASTDP_1366x768_60; + break; + case 1440: + ModeIdx = (ASTDP_1440x900_60_RB - (u8)ulRefreshRateIndex); + break; + case 1600: + if (crtc->mode.crtc_vdisplay == 900) + ModeIdx = + (ASTDP_1600x900_60_RB - (u8)ulRefreshRateIndex); + else //1200 + ModeIdx = ASTDP_1600x1200_60; + break; + case 1680: + ModeIdx = (ASTDP_1680x1050_60_RB - (u8)ulRefreshRateIndex); + break; + case 1920: + if (crtc->mode.crtc_vdisplay == 1080) + ModeIdx = ASTDP_1920x1080_60; + else //1200 + ModeIdx = ASTDP_1920x1200_60; + break; + default: + return; + } + + /* + * CRE0[7:0]: MISC0 ((0x00: 18-bpp) or (0x20: 24-bpp) + * CRE1[7:0]: MISC1 (default: 0x00) + * CRE2[7:0]: video format index (0x00 ~ 0x20 or 0x40 ~ 0x50) + */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE0, + ASTDP_AND_CLEAR_MASK, ASTDP_MISC0_24bpp); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE1, + ASTDP_AND_CLEAR_MASK, ASTDP_MISC1); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE2, + ASTDP_AND_CLEAR_MASK, ModeIdx); +} diff --git a/drivers/gpu/drm/loongson/ast_old/ast_dp501.c b/drivers/gpu/drm/loongson/ast_old/ast_dp501.c new file mode 100644 index 0000000000000000000000000000000000000000..39474bff1aeaf53a4bb4f3644b5c68789497cba6 --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/ast_dp501.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include + +#include "ast_drv.h" + +MODULE_FIRMWARE("ast_dp501_fw.bin"); + +static void ast_release_firmware(void *data) +{ + struct ast_private *ast = data; + + release_firmware(ast->dp501_fw); + ast->dp501_fw = NULL; +} + +static int ast_load_dp501_microcode(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + int ret; + + ret = request_firmware(&ast->dp501_fw, "ast_dp501_fw.bin", dev->dev); + if (ret) + return ret; + + return devm_add_action_or_reset(dev->dev, ast_release_firmware, ast); +} + +static void send_ack(struct ast_private *ast) +{ + u8 sendack; + + sendack = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, 0xff); + sendack |= 0x80; + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, 0x00, sendack); +} + +static void send_nack(struct ast_private *ast) +{ + u8 sendack; + + sendack = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, 0xff); + sendack &= ~0x80; + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, 0x00, sendack); +} + +static bool wait_ack(struct ast_private *ast) +{ + u8 waitack; + u32 retry = 0; + + do { + waitack = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd2, + 0xff); + waitack &= 0x80; + udelay(100); + } while ((!waitack) && (retry++ < 1000)); + + if (retry < 1000) + return true; + else + return false; +} + +static bool wait_nack(struct ast_private *ast) +{ + u8 waitack; + u32 retry = 0; + + do { + waitack = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd2, + 0xff); + waitack &= 0x80; + udelay(100); + } while ((waitack) && (retry++ < 1000)); + + if (retry < 1000) + return true; + else + return false; +} + +static void set_cmd_trigger(struct ast_private *ast) +{ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, ~0x40, 0x40); +} + +static void clear_cmd_trigger(struct ast_private *ast) +{ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, ~0x40, 0x00); +} + +static bool ast_write_cmd(struct drm_device *dev, u8 data) +{ + struct ast_private *ast = to_ast_private(dev); + int retry = 0; + + if (wait_nack(ast)) { + send_nack(ast); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9a, 0x00, data); + send_ack(ast); + set_cmd_trigger(ast); + do { + if (wait_ack(ast)) { + clear_cmd_trigger(ast); + send_nack(ast); + return true; + } + } while (retry++ < 100); + } + clear_cmd_trigger(ast); + send_nack(ast); + return false; +} + +static bool ast_write_data(struct drm_device *dev, u8 data) +{ + struct ast_private *ast = to_ast_private(dev); + + if (wait_nack(ast)) { + send_nack(ast); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9a, 0x00, data); + send_ack(ast); + if (wait_ack(ast)) { + send_nack(ast); + return true; + } + } + send_nack(ast); + return false; +} + +void ast_set_dp501_video_output(struct drm_device *dev, u8 mode) +{ + ast_write_cmd(dev, 0x40); + ast_write_data(dev, mode); + + /* + * msleep < 20ms can sleep for up to 20ms; + * see Documentation/timers/timers-howto.rst + */ + msleep(20); +} + +static u32 get_fw_base(struct ast_private *ast) +{ + return ast_mindwm(ast, 0x1e6e2104) & 0x7fffffff; +} + +bool ast_backup_fw(struct drm_device *dev, u8 *addr, u32 size) +{ + struct ast_private *ast = to_ast_private(dev); + u32 i, data; + u32 boot_address; + + if (ast->config_mode != ast_use_p2a) + return false; + + data = ast_mindwm(ast, 0x1e6e2100) & 0x01; + if (data) { + boot_address = get_fw_base(ast); + for (i = 0; i < size; i += 4) + *(u32 *)(addr + i) = ast_mindwm(ast, boot_address + i); + return true; + } + return false; +} + +static bool ast_launch_m68k(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + u32 i, data, len = 0; + u32 boot_address; + u8 *fw_addr = NULL; + u8 jreg; + + if (ast->config_mode != ast_use_p2a) + return false; + + data = ast_mindwm(ast, 0x1e6e2100) & 0x01; + if (!data) { + if (ast->dp501_fw_addr) { + fw_addr = ast->dp501_fw_addr; + len = 32 * 1024; + } else { + if (!ast->dp501_fw && ast_load_dp501_microcode(dev) < 0) + return false; + + fw_addr = (u8 *)ast->dp501_fw->data; + len = ast->dp501_fw->size; + } + /* Get BootAddress */ + ast_moutdwm(ast, 0x1e6e2000, 0x1688a8a8); + data = ast_mindwm(ast, 0x1e6e0004); + switch (data & 0x03) { + case 0: + boot_address = 0x44000000; + break; + default: + case 1: + boot_address = 0x48000000; + break; + case 2: + boot_address = 0x50000000; + break; + case 3: + boot_address = 0x60000000; + break; + } + boot_address -= 0x200000; /* -2MB */ + + /* copy image to buffer */ + for (i = 0; i < len; i += 4) { + data = *(u32 *)(fw_addr + i); + ast_moutdwm(ast, boot_address + i, data); + } + + /* Init SCU */ + ast_moutdwm(ast, 0x1e6e2000, 0x1688a8a8); + + /* Launch FW */ + ast_moutdwm(ast, 0x1e6e2104, 0x80000000 + boot_address); + ast_moutdwm(ast, 0x1e6e2100, 1); + + /* Update Scratch */ + data = ast_mindwm(ast, 0x1e6e2040) & + 0xfffff1ff; /* D[11:9] = 100b: UEFI handling */ + data |= 0x800; + ast_moutdwm(ast, 0x1e6e2040, data); + + jreg = ast_get_index_reg_mask( + ast, AST_IO_CRTC_PORT, 0x99, + 0xfc); /* D[1:0]: Reserved Video Buffer */ + jreg |= 0x02; + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x99, jreg); + } + return true; +} + +bool ast_dp501_read_edid(struct drm_device *dev, u8 *ediddata) +{ + struct ast_private *ast = to_ast_private(dev); + u32 i, boot_address, offset, data; + u32 *pEDIDidx; + + if (ast->config_mode == ast_use_p2a) { + boot_address = get_fw_base(ast); + + /* validate FW version */ + offset = AST_DP501_GBL_VERSION; + data = ast_mindwm(ast, boot_address + offset); + if ((data & AST_DP501_FW_VERSION_MASK) != + AST_DP501_FW_VERSION_1) + return false; + + /* validate PnP Monitor */ + offset = AST_DP501_PNPMONITOR; + data = ast_mindwm(ast, boot_address + offset); + if (!(data & AST_DP501_PNP_CONNECTED)) + return false; + + /* Read EDID */ + offset = AST_DP501_EDID_DATA; + for (i = 0; i < 128; i += 4) { + data = ast_mindwm(ast, boot_address + offset + i); + pEDIDidx = (u32 *)(ediddata + i); + *pEDIDidx = data; + } + } else { + if (!ast->dp501_fw_buf) + return false; + + /* dummy read */ + offset = 0x0000; + data = readl(ast->dp501_fw_buf + offset); + + /* validate FW version */ + offset = AST_DP501_GBL_VERSION; + data = readl(ast->dp501_fw_buf + offset); + if ((data & AST_DP501_FW_VERSION_MASK) != + AST_DP501_FW_VERSION_1) + return false; + + /* validate PnP Monitor */ + offset = AST_DP501_PNPMONITOR; + data = readl(ast->dp501_fw_buf + offset); + if (!(data & AST_DP501_PNP_CONNECTED)) + return false; + + /* Read EDID */ + offset = AST_DP501_EDID_DATA; + for (i = 0; i < 128; i += 4) { + data = readl(ast->dp501_fw_buf + offset + i); + pEDIDidx = (u32 *)(ediddata + i); + *pEDIDidx = data; + } + } + + return true; +} + +static bool ast_init_dvo(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + u8 jreg; + u32 data; + + ast_write32(ast, 0xf004, 0x1e6e0000); + ast_write32(ast, 0xf000, 0x1); + ast_write32(ast, 0x12000, 0x1688a8a8); + + jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd0, 0xff); + if (!(jreg & 0x80)) { + /* Init SCU DVO Settings */ + data = ast_read32(ast, 0x12008); + /* delay phase */ + data &= 0xfffff8ff; + data |= 0x00000500; + ast_write32(ast, 0x12008, data); + + if (ast->chip == AST2300) { + data = ast_read32(ast, 0x12084); + /* multi-pins for DVO single-edge */ + data |= 0xfffe0000; + ast_write32(ast, 0x12084, data); + + data = ast_read32(ast, 0x12088); + /* multi-pins for DVO single-edge */ + data |= 0x000fffff; + ast_write32(ast, 0x12088, data); + + data = ast_read32(ast, 0x12090); + /* multi-pins for DVO single-edge */ + data &= 0xffffffcf; + data |= 0x00000020; + ast_write32(ast, 0x12090, data); + } else { /* AST2400 */ + data = ast_read32(ast, 0x12088); + /* multi-pins for DVO single-edge */ + data |= 0x30000000; + ast_write32(ast, 0x12088, data); + + data = ast_read32(ast, 0x1208c); + /* multi-pins for DVO single-edge */ + data |= 0x000000cf; + ast_write32(ast, 0x1208c, data); + + data = ast_read32(ast, 0x120a4); + /* multi-pins for DVO single-edge */ + data |= 0xffff0000; + ast_write32(ast, 0x120a4, data); + + data = ast_read32(ast, 0x120a8); + /* multi-pins for DVO single-edge */ + data |= 0x0000000f; + ast_write32(ast, 0x120a8, data); + + data = ast_read32(ast, 0x12094); + /* multi-pins for DVO single-edge */ + data |= 0x00000002; + ast_write32(ast, 0x12094, data); + } + } + + /* Force to DVO */ + data = ast_read32(ast, 0x1202c); + data &= 0xfffbffff; + ast_write32(ast, 0x1202c, data); + + /* Init VGA DVO Settings */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa3, 0xcf, 0x80); + return true; +} + +static void ast_init_analog(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + u32 data; + + /* + * Set DAC source to VGA mode in SCU2C via the P2A + * bridge. First configure the P2U to target the SCU + * in case it isn't at this stage. + */ + ast_write32(ast, 0xf004, 0x1e6e0000); + ast_write32(ast, 0xf000, 0x1); + + /* Then unlock the SCU with the magic password */ + ast_write32(ast, 0x12000, 0x1688a8a8); + ast_write32(ast, 0x12000, 0x1688a8a8); + ast_write32(ast, 0x12000, 0x1688a8a8); + + /* Finally, clear bits [17:16] of SCU2c */ + data = ast_read32(ast, 0x1202c); + data &= 0xfffcffff; + ast_write32(ast, 0, data); + + /* Disable DVO */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa3, 0xcf, 0x00); +} + +void ast_init_3rdtx(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + u8 jreg; + + if (ast->chip == AST2300 || ast->chip == AST2400) { + jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd1, + 0xff); + switch (jreg & 0x0e) { + case 0x04: + ast_init_dvo(dev); + break; + case 0x08: + ast_launch_m68k(dev); + break; + case 0x0c: + ast_init_dvo(dev); + break; + default: + if (ast->tx_chip_types & BIT(AST_TX_SIL164)) + ast_init_dvo(dev); + else + ast_init_analog(dev); + } + } +} diff --git a/drivers/gpu/drm/loongson/ast_old/ast_dram_tables.h b/drivers/gpu/drm/loongson/ast_old/ast_dram_tables.h new file mode 100644 index 0000000000000000000000000000000000000000..114b1de15c1e714f24ab26165f7b14f8ae4576a4 --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/ast_dram_tables.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef AST_DRAM_TABLES_H +#define AST_DRAM_TABLES_H + +/* DRAM timing tables */ +struct ast_dramstruct { + u16 index; + u32 data; +}; + +static const struct ast_dramstruct ast2000_dram_table_data[] = { + { 0x0108, 0x00000000 }, { 0x0120, 0x00004a21 }, { 0xFF00, 0x00000043 }, + { 0x0000, 0xFFFFFFFF }, { 0x0004, 0x00000089 }, { 0x0008, 0x22331353 }, + { 0x000C, 0x0d07000b }, { 0x0010, 0x11113333 }, { 0x0020, 0x00110350 }, + { 0x0028, 0x1e0828f0 }, { 0x0024, 0x00000001 }, { 0x001C, 0x00000000 }, + { 0x0014, 0x00000003 }, { 0xFF00, 0x00000043 }, { 0x0018, 0x00000131 }, + { 0x0014, 0x00000001 }, { 0xFF00, 0x00000043 }, { 0x0018, 0x00000031 }, + { 0x0014, 0x00000001 }, { 0xFF00, 0x00000043 }, { 0x0028, 0x1e0828f1 }, + { 0x0024, 0x00000003 }, { 0x002C, 0x1f0f28fb }, { 0x0030, 0xFFFFFE01 }, + { 0xFFFF, 0xFFFFFFFF } +}; + +static const struct ast_dramstruct ast1100_dram_table_data[] = { + { 0x2000, 0x1688a8a8 }, { 0x2020, 0x000041f0 }, { 0xFF00, 0x00000043 }, + { 0x0000, 0xfc600309 }, { 0x006C, 0x00909090 }, { 0x0064, 0x00050000 }, + { 0x0004, 0x00000585 }, { 0x0008, 0x0011030f }, { 0x0010, 0x22201724 }, + { 0x0018, 0x1e29011a }, { 0x0020, 0x00c82222 }, { 0x0014, 0x01001523 }, + { 0x001C, 0x1024010d }, { 0x0024, 0x00cb2522 }, { 0x0038, 0xffffff82 }, + { 0x003C, 0x00000000 }, { 0x0040, 0x00000000 }, { 0x0044, 0x00000000 }, + { 0x0048, 0x00000000 }, { 0x004C, 0x00000000 }, { 0x0050, 0x00000000 }, + { 0x0054, 0x00000000 }, { 0x0058, 0x00000000 }, { 0x005C, 0x00000000 }, + { 0x0060, 0x032aa02a }, { 0x0064, 0x002d3000 }, { 0x0068, 0x00000000 }, + { 0x0070, 0x00000000 }, { 0x0074, 0x00000000 }, { 0x0078, 0x00000000 }, + { 0x007C, 0x00000000 }, { 0x0034, 0x00000001 }, { 0xFF00, 0x00000043 }, + { 0x002C, 0x00000732 }, { 0x0030, 0x00000040 }, { 0x0028, 0x00000005 }, + { 0x0028, 0x00000007 }, { 0x0028, 0x00000003 }, { 0x0028, 0x00000001 }, + { 0x000C, 0x00005a08 }, { 0x002C, 0x00000632 }, { 0x0028, 0x00000001 }, + { 0x0030, 0x000003c0 }, { 0x0028, 0x00000003 }, { 0x0030, 0x00000040 }, + { 0x0028, 0x00000003 }, { 0x000C, 0x00005a21 }, { 0x0034, 0x00007c03 }, + { 0x0120, 0x00004c41 }, { 0xffff, 0xffffffff }, +}; + +static const struct ast_dramstruct ast2100_dram_table_data[] = { + { 0x2000, 0x1688a8a8 }, { 0x2020, 0x00004120 }, { 0xFF00, 0x00000043 }, + { 0x0000, 0xfc600309 }, { 0x006C, 0x00909090 }, { 0x0064, 0x00070000 }, + { 0x0004, 0x00000489 }, { 0x0008, 0x0011030f }, { 0x0010, 0x32302926 }, + { 0x0018, 0x274c0122 }, { 0x0020, 0x00ce2222 }, { 0x0014, 0x01001523 }, + { 0x001C, 0x1024010d }, { 0x0024, 0x00cb2522 }, { 0x0038, 0xffffff82 }, + { 0x003C, 0x00000000 }, { 0x0040, 0x00000000 }, { 0x0044, 0x00000000 }, + { 0x0048, 0x00000000 }, { 0x004C, 0x00000000 }, { 0x0050, 0x00000000 }, + { 0x0054, 0x00000000 }, { 0x0058, 0x00000000 }, { 0x005C, 0x00000000 }, + { 0x0060, 0x0f2aa02a }, { 0x0064, 0x003f3005 }, { 0x0068, 0x02020202 }, + { 0x0070, 0x00000000 }, { 0x0074, 0x00000000 }, { 0x0078, 0x00000000 }, + { 0x007C, 0x00000000 }, { 0x0034, 0x00000001 }, { 0xFF00, 0x00000043 }, + { 0x002C, 0x00000942 }, { 0x0030, 0x00000040 }, { 0x0028, 0x00000005 }, + { 0x0028, 0x00000007 }, { 0x0028, 0x00000003 }, { 0x0028, 0x00000001 }, + { 0x000C, 0x00005a08 }, { 0x002C, 0x00000842 }, { 0x0028, 0x00000001 }, + { 0x0030, 0x000003c0 }, { 0x0028, 0x00000003 }, { 0x0030, 0x00000040 }, + { 0x0028, 0x00000003 }, { 0x000C, 0x00005a21 }, { 0x0034, 0x00007c03 }, + { 0x0120, 0x00005061 }, { 0xffff, 0xffffffff }, +}; + +/* + * AST2500 DRAM settings modules + */ +#define REGTBL_NUM 17 +#define REGIDX_010 0 +#define REGIDX_014 1 +#define REGIDX_018 2 +#define REGIDX_020 3 +#define REGIDX_024 4 +#define REGIDX_02C 5 +#define REGIDX_030 6 +#define REGIDX_214 7 +#define REGIDX_2E0 8 +#define REGIDX_2E4 9 +#define REGIDX_2E8 10 +#define REGIDX_2EC 11 +#define REGIDX_2F0 12 +#define REGIDX_2F4 13 +#define REGIDX_2F8 14 +#define REGIDX_RFC 15 +#define REGIDX_PLL 16 + +static const u32 ast2500_ddr3_1600_timing_table[REGTBL_NUM] = { + 0x64604D38, /* 0x010 */ + 0x29690599, /* 0x014 */ + 0x00000300, /* 0x018 */ + 0x00000000, /* 0x020 */ + 0x00000000, /* 0x024 */ + 0x02181E70, /* 0x02C */ + 0x00000040, /* 0x030 */ + 0x00000024, /* 0x214 */ + 0x02001300, /* 0x2E0 */ + 0x0E0000A0, /* 0x2E4 */ + 0x000E001B, /* 0x2E8 */ + 0x35B8C105, /* 0x2EC */ + 0x08090408, /* 0x2F0 */ + 0x9B000800, /* 0x2F4 */ + 0x0E400A00, /* 0x2F8 */ + 0x9971452F, /* tRFC */ + 0x000071C1 /* PLL */ +}; + +static const u32 ast2500_ddr4_1600_timing_table[REGTBL_NUM] = { + 0x63604E37, /* 0x010 */ + 0xE97AFA99, /* 0x014 */ + 0x00019000, /* 0x018 */ + 0x08000000, /* 0x020 */ + 0x00000400, /* 0x024 */ + 0x00000410, /* 0x02C */ + 0x00000101, /* 0x030 */ + 0x00000024, /* 0x214 */ + 0x03002900, /* 0x2E0 */ + 0x0E0000A0, /* 0x2E4 */ + 0x000E001C, /* 0x2E8 */ + 0x35B8C106, /* 0x2EC */ + 0x08080607, /* 0x2F0 */ + 0x9B000900, /* 0x2F4 */ + 0x0E400A00, /* 0x2F8 */ + 0x99714545, /* tRFC */ + 0x000071C1 /* PLL */ +}; + +#endif diff --git a/drivers/gpu/drm/loongson/ast_old/ast_drv.c b/drivers/gpu/drm/loongson/ast_old/ast_drv.c new file mode 100644 index 0000000000000000000000000000000000000000..2e069fe979392a79f4cadd048b5039b0f2af076a --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/ast_drv.c @@ -0,0 +1,231 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: Dave Airlie + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ast_drv.h" + +static int ast_modeset = -1; + +MODULE_PARM_DESC(modeset, "Disable/Enable modesetting"); +module_param_named(modeset, ast_modeset, int, 0400); + +/* + * DRM driver + */ + +DEFINE_DRM_GEM_FOPS(ast_fops); + +static const struct drm_driver ast_driver = { .driver_features = DRIVER_ATOMIC | + DRIVER_GEM | + DRIVER_MODESET, + + .fops = &ast_fops, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, + + DRM_GEM_VRAM_DRIVER }; + +/* + * PCI driver + */ + +#define PCI_VENDOR_ASPEED 0x1a03 + +#define AST_VGA_DEVICE(id, info) \ + { .class = PCI_BASE_CLASS_DISPLAY << 16, \ + .class_mask = 0xff0000, \ + .vendor = PCI_VENDOR_ASPEED, \ + .device = id, \ + .subvendor = PCI_ANY_ID, \ + .subdevice = PCI_ANY_ID, \ + .driver_data = (unsigned long)info } + +static const struct pci_device_id ast_pciidlist[] = { + AST_VGA_DEVICE(PCI_CHIP_AST2000, NULL), + AST_VGA_DEVICE(PCI_CHIP_AST2100, NULL), + { 0, 0, 0 }, +}; + +MODULE_DEVICE_TABLE(pci, ast_pciidlist); + +static int ast_remove_conflicting_framebuffers(struct pci_dev *pdev) +{ + resource_size_t base, size; + + base = pci_resource_start(pdev, 0); + size = pci_resource_len(pdev, 0); + + return drm_aperture_remove_conflicting_framebuffers(base, size, + &ast_driver); +} + +static int ast_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct ast_private *ast; + struct drm_device *dev; + int ret; + + ret = ast_remove_conflicting_framebuffers(pdev); + if (ret) + return ret; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + ast = ast_device_create(&ast_driver, pdev, ent->driver_data); + if (IS_ERR(ast)) + return PTR_ERR(ast); + dev = &ast->base; + + ret = drm_dev_register(dev, ent->driver_data); + if (ret) + return ret; + + drm_fbdev_generic_setup(dev, 32); + + return 0; +} + +static void ast_pci_remove(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + + drm_dev_unregister(dev); + drm_atomic_helper_shutdown(dev); +} + +static int ast_drm_freeze(struct drm_device *dev) +{ + int error; + + error = drm_mode_config_helper_suspend(dev); + if (error) + return error; + pci_save_state(to_pci_dev(dev->dev)); + return 0; +} + +static int ast_drm_thaw(struct drm_device *dev) +{ + ast_post_gpu(dev); + + return drm_mode_config_helper_resume(dev); +} + +static int ast_drm_resume(struct drm_device *dev) +{ + if (pci_enable_device(to_pci_dev(dev->dev))) + return -EIO; + + return ast_drm_thaw(dev); +} + +static int ast_pm_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *ddev = pci_get_drvdata(pdev); + int error; + + error = ast_drm_freeze(ddev); + if (error) + return error; + + pci_disable_device(pdev); + pci_set_power_state(pdev, PCI_D3hot); + return 0; +} + +static int ast_pm_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *ddev = pci_get_drvdata(pdev); + + return ast_drm_resume(ddev); +} + +static int ast_pm_freeze(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *ddev = pci_get_drvdata(pdev); + + return ast_drm_freeze(ddev); +} + +static int ast_pm_thaw(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *ddev = pci_get_drvdata(pdev); + + return ast_drm_thaw(ddev); +} + +static int ast_pm_poweroff(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *ddev = pci_get_drvdata(pdev); + + return ast_drm_freeze(ddev); +} + +static const struct dev_pm_ops ast_pm_ops = { + .suspend = ast_pm_suspend, + .resume = ast_pm_resume, + .freeze = ast_pm_freeze, + .thaw = ast_pm_thaw, + .poweroff = ast_pm_poweroff, + .restore = ast_pm_resume, +}; + +static struct pci_driver ast_pci_driver = { + .name = DRIVER_NAME, + .id_table = ast_pciidlist, + .probe = ast_pci_probe, + .remove = ast_pci_remove, + .driver.pm = &ast_pm_ops, +}; + +drm_module_pci_driver_if_modeset(ast_pci_driver, ast_modeset); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/gpu/drm/loongson/ast_old/ast_drv.h b/drivers/gpu/drm/loongson/ast_old/ast_drv.h new file mode 100644 index 0000000000000000000000000000000000000000..29a2965080ef37f658a22eb8ed36308863f6bf23 --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/ast_drv.h @@ -0,0 +1,528 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: Dave Airlie + */ +#ifndef __AST_DRV_H__ +#define __AST_DRV_H__ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "Dave Airlie" + +#define DRIVER_NAME "ast" +#define DRIVER_DESC "AST" +#define DRIVER_DATE "20120228" + +#define DRIVER_MAJOR 0 +#define DRIVER_MINOR 1 +#define DRIVER_PATCHLEVEL 0 + +#define PCI_CHIP_AST2000 0x2000 +#define PCI_CHIP_AST2100 0x2010 + +enum ast_chip { + AST2000, + AST2100, + AST1100, + AST2200, + AST2150, + AST2300, + AST2400, + AST2500, + AST2600, +}; + +enum ast_tx_chip { + AST_TX_NONE, + AST_TX_SIL164, + AST_TX_DP501, + AST_TX_ASTDP, +}; + +#define AST_TX_NONE_BIT BIT(AST_TX_NONE) +#define AST_TX_SIL164_BIT BIT(AST_TX_SIL164) +#define AST_TX_DP501_BIT BIT(AST_TX_DP501) +#define AST_TX_ASTDP_BIT BIT(AST_TX_ASTDP) + +#define AST_DRAM_512Mx16 0 +#define AST_DRAM_1Gx16 1 +#define AST_DRAM_512Mx32 2 +#define AST_DRAM_1Gx32 3 +#define AST_DRAM_2Gx16 6 +#define AST_DRAM_4Gx16 7 +#define AST_DRAM_8Gx16 8 + +/* + * Hardware cursor + */ + +#define AST_MAX_HWC_WIDTH 64 +#define AST_MAX_HWC_HEIGHT 64 + +#define AST_HWC_SIZE (AST_MAX_HWC_WIDTH * AST_MAX_HWC_HEIGHT * 2) +#define AST_HWC_SIGNATURE_SIZE 32 + +/* define for signature structure */ +#define AST_HWC_SIGNATURE_CHECKSUM 0x00 +#define AST_HWC_SIGNATURE_SizeX 0x04 +#define AST_HWC_SIGNATURE_SizeY 0x08 +#define AST_HWC_SIGNATURE_X 0x0C +#define AST_HWC_SIGNATURE_Y 0x10 +#define AST_HWC_SIGNATURE_HOTSPOTX 0x14 +#define AST_HWC_SIGNATURE_HOTSPOTY 0x18 + +/* + * Planes + */ + +struct ast_plane { + struct drm_plane base; + + struct drm_gem_vram_object *gbo; + struct iosys_map map; + u64 off; +}; + +static inline struct ast_plane *to_ast_plane(struct drm_plane *plane) +{ + return container_of(plane, struct ast_plane, base); +} + +/* + * Connector with i2c channel + */ + +struct ast_i2c_chan { + struct i2c_adapter adapter; + struct drm_device *dev; + struct i2c_algo_bit_data bit; +}; + +struct ast_vga_connector { + struct drm_connector base; + struct ast_i2c_chan *i2c; +}; + +static inline struct ast_vga_connector * +to_ast_vga_connector(struct drm_connector *connector) +{ + return container_of(connector, struct ast_vga_connector, base); +} + +struct ast_sil164_connector { + struct drm_connector base; + struct ast_i2c_chan *i2c; +}; + +static inline struct ast_sil164_connector * +to_ast_sil164_connector(struct drm_connector *connector) +{ + return container_of(connector, struct ast_sil164_connector, base); +} + +/* + * Device + */ + +struct ast_private { + struct drm_device base; + + struct mutex ioregs_lock; /* Protects access to I/O registers in ioregs */ + void __iomem *regs; + void __iomem *ioregs; + void __iomem *dp501_fw_buf; + + enum ast_chip chip; + bool vga2_clone; + uint32_t dram_bus_width; + uint32_t dram_type; + uint32_t mclk; + + struct drm_plane primary_plane; + struct ast_plane cursor_plane; + struct drm_crtc crtc; + struct { + struct { + struct drm_encoder encoder; + struct ast_vga_connector vga_connector; + } vga; + struct { + struct drm_encoder encoder; + struct ast_sil164_connector sil164_connector; + } sil164; + struct { + struct drm_encoder encoder; + struct drm_connector connector; + } dp501; + struct { + struct drm_encoder encoder; + struct drm_connector connector; + } astdp; + } output; + + bool support_wide_screen; + enum { ast_use_p2a, ast_use_dt, ast_use_defaults } config_mode; + + unsigned long tx_chip_types; /* bitfield of enum ast_chip_type */ + u8 *dp501_fw_addr; + const struct firmware *dp501_fw; /* dp501 fw */ +}; + +static inline struct ast_private *to_ast_private(struct drm_device *dev) +{ + return container_of(dev, struct ast_private, base); +} + +struct ast_private *ast_device_create(const struct drm_driver *drv, + struct pci_dev *pdev, + unsigned long flags); + +#define AST_IO_AR_PORT_WRITE (0x40) +#define AST_IO_MISC_PORT_WRITE (0x42) +#define AST_IO_VGA_ENABLE_PORT (0x43) +#define AST_IO_SEQ_PORT (0x44) +#define AST_IO_DAC_INDEX_READ (0x47) +#define AST_IO_DAC_INDEX_WRITE (0x48) +#define AST_IO_DAC_DATA (0x49) +#define AST_IO_GR_PORT (0x4E) +#define AST_IO_CRTC_PORT (0x54) +#define AST_IO_INPUT_STATUS1_READ (0x5A) +#define AST_IO_MISC_PORT_READ (0x4C) + +#define AST_IO_MM_OFFSET (0x380) + +#define AST_IO_VGAIR1_VREFRESH BIT(3) + +#define AST_IO_VGACRCB_HWC_ENABLED BIT(1) +#define AST_IO_VGACRCB_HWC_16BPP \ + BIT(0) /* set: ARGB4444, cleared: 2bpp palette */ + +static inline u8 ast_read8(struct ast_private *ast, u32 reg) +{ + u8 val = 0; + + val = ioread8(ast->regs + reg); + return val; +} + +static inline u16 ast_read16(struct ast_private *ast, u32 reg) +{ + u16 val = 0; + + val = ioread16(ast->regs + reg); + return val; +} + +static inline u32 ast_read32(struct ast_private *ast, u32 reg) +{ + u32 val = 0; + + val = ioread32(ast->regs + reg); + return val; +} + +static inline u8 ast_io_read8(struct ast_private *ast, u32 reg) +{ + u8 val = 0; + + val = ioread8(ast->ioregs + reg); + return val; +} + +static inline u16 ast_io_read16(struct ast_private *ast, u32 reg) +{ + u16 val = 0; + + val = ioread16(ast->ioregs + reg); + return val; +} + +static inline u32 ast_io_read32(struct ast_private *ast, u32 reg) +{ + u32 val = 0; + + val = ioread32(ast->ioregs + reg); + return val; +} + +#define __ast_write(x) \ + static inline void ast_write##x(struct ast_private *ast, u32 reg, \ + u##x val) \ + { \ + iowrite##x(val, ast->regs + reg); \ + } + +__ast_write(8); +__ast_write(16); +__ast_write(32); + +#define __ast_io_write(x) \ + static inline void ast_io_write##x(struct ast_private *ast, u32 reg, \ + u##x val) \ + { \ + iowrite##x(val, ast->ioregs + reg); \ + } + +__ast_io_write(8); +__ast_io_write(16); +#undef __ast_io_write + +static inline void ast_set_index_reg(struct ast_private *ast, uint32_t base, + uint8_t index, uint8_t val) +{ + ast_io_write16(ast, base, ((u16)val << 8) | index); +} + +void ast_set_index_reg_mask(struct ast_private *ast, uint32_t base, + uint8_t index, uint8_t mask, uint8_t val); +uint8_t ast_get_index_reg(struct ast_private *ast, uint32_t base, + uint8_t index); +uint8_t ast_get_index_reg_mask(struct ast_private *ast, uint32_t base, + uint8_t index, uint8_t mask); + +static inline void ast_open_key(struct ast_private *ast) +{ + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x80, 0xA8); +} + +#define AST_VIDMEM_SIZE_8M 0x00800000 +#define AST_VIDMEM_SIZE_16M 0x01000000 +#define AST_VIDMEM_SIZE_32M 0x02000000 +#define AST_VIDMEM_SIZE_64M 0x04000000 +#define AST_VIDMEM_SIZE_128M 0x08000000 + +#define AST_VIDMEM_DEFAULT_SIZE AST_VIDMEM_SIZE_8M + +struct ast_vbios_stdtable { + u8 misc; + u8 seq[4]; + u8 crtc[25]; + u8 ar[20]; + u8 gr[9]; +}; + +struct ast_vbios_enhtable { + u32 ht; + u32 hde; + u32 hfp; + u32 hsync; + u32 vt; + u32 vde; + u32 vfp; + u32 vsync; + u32 dclk_index; + u32 flags; + u32 refresh_rate; + u32 refresh_rate_index; + u32 mode_id; +}; + +struct ast_vbios_dclk_info { + u8 param1; + u8 param2; + u8 param3; +}; + +struct ast_vbios_mode_info { + const struct ast_vbios_stdtable *std_table; + const struct ast_vbios_enhtable *enh_table; +}; + +struct ast_crtc_state { + struct drm_crtc_state base; + + /* Last known format of primary plane */ + const struct drm_format_info *format; + + struct ast_vbios_mode_info vbios_mode_info; +}; + +#define to_ast_crtc_state(state) \ + container_of(state, struct ast_crtc_state, base) + +int ast_mode_config_init(struct ast_private *ast); + +#define AST_MM_ALIGN_SHIFT 4 +#define AST_MM_ALIGN_MASK ((1 << AST_MM_ALIGN_SHIFT) - 1) + +#define AST_DP501_FW_VERSION_MASK GENMASK(7, 4) +#define AST_DP501_FW_VERSION_1 BIT(4) +#define AST_DP501_PNP_CONNECTED BIT(1) + +#define AST_DP501_DEFAULT_DCLK 65 + +#define AST_DP501_GBL_VERSION 0xf000 +#define AST_DP501_PNPMONITOR 0xf010 +#define AST_DP501_LINKRATE 0xf014 +#define AST_DP501_EDID_DATA 0xf020 + +/* Define for Soc scratched reg */ +#define COPROCESSOR_LAUNCH BIT(5) + +/* + * Display Transmitter Type: + */ +#define TX_TYPE_MASK GENMASK(3, 1) +#define NO_TX (0 << 1) +#define ITE66121_VBIOS_TX (1 << 1) +#define SI164_VBIOS_TX (2 << 1) +#define CH7003_VBIOS_TX (3 << 1) +#define DP501_VBIOS_TX (4 << 1) +#define ANX9807_VBIOS_TX (5 << 1) +#define TX_FW_EMBEDDED_FW_TX (6 << 1) +#define ASTDP_DPMCU_TX (7 << 1) + +#define AST_VRAM_INIT_STATUS_MASK GENMASK(7, 6) +//#define AST_VRAM_INIT_BY_BMC BIT(7) +//#define AST_VRAM_INIT_READY BIT(6) + +/* Define for Soc scratched reg used on ASTDP */ +#define AST_DP_PHY_SLEEP BIT(4) +#define AST_DP_VIDEO_ENABLE BIT(0) + +#define AST_DP_POWER_ON true +#define AST_DP_POWER_OFF false + +/* + * CRD1[b5]: DP MCU FW is executing + * CRDC[b0]: DP link success + * CRDF[b0]: DP HPD + * CRE5[b0]: Host reading EDID process is done + */ +#define ASTDP_MCU_FW_EXECUTING BIT(5) +#define ASTDP_LINK_SUCCESS BIT(0) +#define ASTDP_HPD BIT(0) +#define ASTDP_HOST_EDID_READ_DONE BIT(0) +#define ASTDP_HOST_EDID_READ_DONE_MASK GENMASK(0, 0) + +/* + * CRB8[b1]: Enable VSYNC off + * CRB8[b0]: Enable HSYNC off + */ +#define AST_DPMS_VSYNC_OFF BIT(1) +#define AST_DPMS_HSYNC_OFF BIT(0) + +/* + * CRDF[b4]: Mirror of AST_DP_VIDEO_ENABLE + * Precondition: A. ~AST_DP_PHY_SLEEP && + * B. DP_HPD && + * C. DP_LINK_SUCCESS + */ +#define ASTDP_MIRROR_VIDEO_ENABLE BIT(4) + +#define ASTDP_EDID_READ_POINTER_MASK GENMASK(7, 0) +#define ASTDP_EDID_VALID_FLAG_MASK GENMASK(0, 0) +#define ASTDP_EDID_READ_DATA_MASK GENMASK(7, 0) + +/* + * ASTDP setmode registers: + * CRE0[7:0]: MISC0 ((0x00: 18-bpp) or (0x20: 24-bpp) + * CRE1[7:0]: MISC1 (default: 0x00) + * CRE2[7:0]: video format index (0x00 ~ 0x20 or 0x40 ~ 0x50) + */ +#define ASTDP_MISC0_24bpp BIT(5) +#define ASTDP_MISC1 0 +#define ASTDP_AND_CLEAR_MASK 0x00 + +/* + * ASTDP resoultion table: + * EX: ASTDP_A_B_C: + * A: Resolution + * B: Refresh Rate + * C: Misc information, such as CVT, Reduce Blanked + */ +#define ASTDP_640x480_60 0x00 +#define ASTDP_640x480_72 0x01 +#define ASTDP_640x480_75 0x02 +#define ASTDP_640x480_85 0x03 +#define ASTDP_800x600_56 0x04 +#define ASTDP_800x600_60 0x05 +#define ASTDP_800x600_72 0x06 +#define ASTDP_800x600_75 0x07 +#define ASTDP_800x600_85 0x08 +#define ASTDP_1024x768_60 0x09 +#define ASTDP_1024x768_70 0x0A +#define ASTDP_1024x768_75 0x0B +#define ASTDP_1024x768_85 0x0C +#define ASTDP_1280x1024_60 0x0D +#define ASTDP_1280x1024_75 0x0E +#define ASTDP_1280x1024_85 0x0F +#define ASTDP_1600x1200_60 0x10 +#define ASTDP_320x240_60 0x11 +#define ASTDP_400x300_60 0x12 +#define ASTDP_512x384_60 0x13 +#define ASTDP_1920x1200_60 0x14 +#define ASTDP_1920x1080_60 0x15 +#define ASTDP_1280x800_60 0x16 +#define ASTDP_1280x800_60_RB 0x17 +#define ASTDP_1440x900_60 0x18 +#define ASTDP_1440x900_60_RB 0x19 +#define ASTDP_1680x1050_60 0x1A +#define ASTDP_1680x1050_60_RB 0x1B +#define ASTDP_1600x900_60 0x1C +#define ASTDP_1600x900_60_RB 0x1D +#define ASTDP_1366x768_60 0x1E +#define ASTDP_1152x864_75 0x1F + +int ast_mm_init(struct ast_private *ast); + +/* ast post */ +void ast_enable_vga(struct drm_device *dev); +void ast_enable_mmio(struct drm_device *dev); +bool ast_is_vga_enabled(struct drm_device *dev); +void ast_post_gpu(struct drm_device *dev); +u32 ast_mindwm(struct ast_private *ast, u32 r); +void ast_moutdwm(struct ast_private *ast, u32 r, u32 v); +void ast_patch_ahb_2500(struct ast_private *ast); +/* ast dp501 */ +void ast_set_dp501_video_output(struct drm_device *dev, u8 mode); +bool ast_backup_fw(struct drm_device *dev, u8 *addr, u32 size); +bool ast_dp501_read_edid(struct drm_device *dev, u8 *ediddata); +u8 ast_get_dp501_max_clk(struct drm_device *dev); +void ast_init_3rdtx(struct drm_device *dev); + +/* ast_i2c.c */ +struct ast_i2c_chan *ast_i2c_create(struct drm_device *dev); + +/* aspeed DP */ +int ast_astdp_read_edid(struct drm_device *dev, u8 *ediddata); +void ast_dp_launch(struct drm_device *dev, u8 bPower); +void ast_dp_power_on_off(struct drm_device *dev, bool no); +void ast_dp_set_on_off(struct drm_device *dev, bool no); +void ast_dp_set_mode(struct drm_crtc *crtc, + struct ast_vbios_mode_info *vbios_mode); + +#endif diff --git a/drivers/gpu/drm/loongson/ast_old/ast_i2c.c b/drivers/gpu/drm/loongson/ast_old/ast_i2c.c new file mode 100644 index 0000000000000000000000000000000000000000..a3daabe3b6a6a890fd78ef77d7348707e7e03cc0 --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/ast_i2c.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT +/* + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + */ + +#include +#include + +#include "ast_drv.h" + +static void ast_i2c_setsda(void *i2c_priv, int data) +{ + struct ast_i2c_chan *i2c = i2c_priv; + struct ast_private *ast = to_ast_private(i2c->dev); + int i; + u8 ujcrb7, jtemp; + + for (i = 0; i < 0x10000; i++) { + ujcrb7 = ((data & 0x01) ? 0 : 1) << 2; + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb7, 0xf1, + ujcrb7); + jtemp = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb7, + 0x04); + if (ujcrb7 == jtemp) + break; + } +} + +static void ast_i2c_setscl(void *i2c_priv, int clock) +{ + struct ast_i2c_chan *i2c = i2c_priv; + struct ast_private *ast = to_ast_private(i2c->dev); + int i; + u8 ujcrb7, jtemp; + + for (i = 0; i < 0x10000; i++) { + ujcrb7 = ((clock & 0x01) ? 0 : 1); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb7, 0xf4, + ujcrb7); + jtemp = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb7, + 0x01); + if (ujcrb7 == jtemp) + break; + } +} + +static int ast_i2c_getsda(void *i2c_priv) +{ + struct ast_i2c_chan *i2c = i2c_priv; + struct ast_private *ast = to_ast_private(i2c->dev); + uint32_t val, val2, count, pass; + + count = 0; + pass = 0; + val = (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb7, 0x20) >> 5) & + 0x01; + do { + val2 = (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb7, + 0x20) >> + 5) & + 0x01; + if (val == val2) { + pass++; + } else { + pass = 0; + val = (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, + 0xb7, 0x20) >> + 5) & + 0x01; + } + } while ((pass < 5) && (count++ < 0x10000)); + + return val & 1 ? 1 : 0; +} + +static int ast_i2c_getscl(void *i2c_priv) +{ + struct ast_i2c_chan *i2c = i2c_priv; + struct ast_private *ast = to_ast_private(i2c->dev); + uint32_t val, val2, count, pass; + + count = 0; + pass = 0; + val = (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb7, 0x10) >> 4) & + 0x01; + do { + val2 = (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb7, + 0x10) >> + 4) & + 0x01; + if (val == val2) { + pass++; + } else { + pass = 0; + val = (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, + 0xb7, 0x10) >> + 4) & + 0x01; + } + } while ((pass < 5) && (count++ < 0x10000)); + + return val & 1 ? 1 : 0; +} + +static void ast_i2c_release(struct drm_device *dev, void *res) +{ + struct ast_i2c_chan *i2c = res; + + i2c_del_adapter(&i2c->adapter); + kfree(i2c); +} + +struct ast_i2c_chan *ast_i2c_create(struct drm_device *dev) +{ + struct ast_i2c_chan *i2c; + int ret; + + i2c = kzalloc(sizeof(struct ast_i2c_chan), GFP_KERNEL); + if (!i2c) + return NULL; + + i2c->adapter.owner = THIS_MODULE; + i2c->adapter.class = I2C_CLASS_DDC; + i2c->adapter.dev.parent = dev->dev; + i2c->dev = dev; + i2c_set_adapdata(&i2c->adapter, i2c); + snprintf(i2c->adapter.name, sizeof(i2c->adapter.name), + "AST i2c bit bus"); + i2c->adapter.algo_data = &i2c->bit; + + i2c->bit.udelay = 20; + i2c->bit.timeout = 2; + i2c->bit.data = i2c; + i2c->bit.setsda = ast_i2c_setsda; + i2c->bit.setscl = ast_i2c_setscl; + i2c->bit.getsda = ast_i2c_getsda; + i2c->bit.getscl = ast_i2c_getscl; + ret = i2c_bit_add_bus(&i2c->adapter); + if (ret) { + drm_err(dev, "Failed to register bit i2c\n"); + goto out_kfree; + } + + ret = drmm_add_action_or_reset(dev, ast_i2c_release, i2c); + if (ret) + return NULL; + return i2c; + +out_kfree: + kfree(i2c); + return NULL; +} diff --git a/drivers/gpu/drm/loongson/ast_old/ast_main.c b/drivers/gpu/drm/loongson/ast_old/ast_main.c new file mode 100644 index 0000000000000000000000000000000000000000..ab6195b61b952943ac6e7c4ab7a4994acd2b8e68 --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/ast_main.c @@ -0,0 +1,486 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: Dave Airlie + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "ast_drv.h" + +void ast_set_index_reg_mask(struct ast_private *ast, uint32_t base, + uint8_t index, uint8_t mask, uint8_t val) +{ + u8 tmp; + + ast_io_write8(ast, base, index); + tmp = (ast_io_read8(ast, base + 1) & mask) | val; + ast_set_index_reg(ast, base, index, tmp); +} + +uint8_t ast_get_index_reg(struct ast_private *ast, uint32_t base, uint8_t index) +{ + uint8_t ret; + + ast_io_write8(ast, base, index); + ret = ast_io_read8(ast, base + 1); + return ret; +} + +uint8_t ast_get_index_reg_mask(struct ast_private *ast, uint32_t base, + uint8_t index, uint8_t mask) +{ + uint8_t ret; + + ast_io_write8(ast, base, index); + ret = ast_io_read8(ast, base + 1) & mask; + return ret; +} + +static void ast_detect_config_mode(struct drm_device *dev, u32 *scu_rev) +{ + struct device_node *np = dev->dev->of_node; + struct ast_private *ast = to_ast_private(dev); + struct pci_dev *pdev = to_pci_dev(dev->dev); + uint32_t data, jregd0, jregd1; + + /* Defaults */ + ast->config_mode = ast_use_defaults; + *scu_rev = 0xffffffff; + + /* Check if we have device-tree properties */ + if (np && + !of_property_read_u32(np, "aspeed,scu-revision-id", scu_rev)) { + /* We do, disable P2A access */ + ast->config_mode = ast_use_dt; + drm_info(dev, "Using device-tree for configuration\n"); + return; + } + + /* Not all families have a P2A bridge */ + if (pdev->device != PCI_CHIP_AST2000) + return; + + /* + * The BMC will set SCU 0x40 D[12] to 1 if the P2 bridge + * is disabled. We force using P2A if VGA only mode bit + * is set D[7] + */ + jregd0 = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd0, 0xff); + jregd1 = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd1, 0xff); + if (!(jregd0 & 0x80) || !(jregd1 & 0x10)) { + /* Patch AST2500 */ + if (((pdev->revision & 0xF0) == 0x40) && + ((jregd0 & AST_VRAM_INIT_STATUS_MASK) == 0)) + ast_patch_ahb_2500(ast); + + /* Double check it's actually working */ + data = ast_read32(ast, 0xf004); + if ((data != 0xFFFFFFFF) && (data != 0x00)) { + /* P2A works, grab silicon revision */ + ast->config_mode = ast_use_p2a; + + drm_info(dev, "Using P2A bridge for configuration\n"); + + /* Read SCU7c (silicon revision register) */ + ast_write32(ast, 0xf004, 0x1e6e0000); + ast_write32(ast, 0xf000, 0x1); + *scu_rev = ast_read32(ast, 0x1207c); + return; + } + } + + /* We have a P2A bridge but it's disabled */ + drm_info(dev, "P2A bridge disabled, using default configuration\n"); +} + +static int ast_detect_chip(struct drm_device *dev, bool *need_post) +{ + struct ast_private *ast = to_ast_private(dev); + struct pci_dev *pdev = to_pci_dev(dev->dev); + uint32_t jreg, scu_rev; + + /* + * If VGA isn't enabled, we need to enable now or subsequent + * access to the scratch registers will fail. We also inform + * our caller that it needs to POST the chip + * (Assumption: VGA not enabled -> need to POST) + */ + if (!ast_is_vga_enabled(dev)) { + ast_enable_vga(dev); + drm_info(dev, + "VGA not enabled on entry, requesting chip POST\n"); + *need_post = true; + } else + *need_post = false; + + /* Enable extended register access */ + ast_open_key(ast); + ast_enable_mmio(dev); + + /* Find out whether P2A works or whether to use device-tree */ + ast_detect_config_mode(dev, &scu_rev); + + /* Identify chipset */ + if (pdev->revision >= 0x50) { + ast->chip = AST2600; + drm_info(dev, "AST 2600 detected\n"); + } else if (pdev->revision >= 0x40) { + ast->chip = AST2500; + drm_info(dev, "AST 2500 detected\n"); + } else if (pdev->revision >= 0x30) { + ast->chip = AST2400; + drm_info(dev, "AST 2400 detected\n"); + } else if (pdev->revision >= 0x20) { + ast->chip = AST2300; + drm_info(dev, "AST 2300 detected\n"); + } else if (pdev->revision >= 0x10) { + switch (scu_rev & 0x0300) { + case 0x0200: + ast->chip = AST1100; + drm_info(dev, "AST 1100 detected\n"); + break; + case 0x0100: + ast->chip = AST2200; + drm_info(dev, "AST 2200 detected\n"); + break; + case 0x0000: + ast->chip = AST2150; + drm_info(dev, "AST 2150 detected\n"); + break; + default: + ast->chip = AST2100; + drm_info(dev, "AST 2100 detected\n"); + break; + } + ast->vga2_clone = false; + } else { + ast->chip = AST2000; + drm_info(dev, "AST 2000 detected\n"); + } + + /* Check if we support wide screen */ + switch (ast->chip) { + case AST2000: + ast->support_wide_screen = false; + break; + default: + jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd0, + 0xff); + if (!(jreg & 0x80)) + ast->support_wide_screen = true; + else if (jreg & 0x01) + ast->support_wide_screen = true; + else { + ast->support_wide_screen = false; + if (ast->chip == AST2300 && + (scu_rev & 0x300) == 0x0) /* ast1300 */ + ast->support_wide_screen = true; + if (ast->chip == AST2400 && + (scu_rev & 0x300) == 0x100) /* ast1400 */ + ast->support_wide_screen = true; + if (ast->chip == AST2500 && + scu_rev == 0x100) /* ast2510 */ + ast->support_wide_screen = true; + if (ast->chip == AST2600) /* ast2600 */ + ast->support_wide_screen = true; + } + break; + } + + /* Check 3rd Tx option (digital output afaik) */ + ast->tx_chip_types |= AST_TX_NONE_BIT; + + /* + * VGACRA3 Enhanced Color Mode Register, check if DVO is already + * enabled, in that case, assume we have a SIL164 TMDS transmitter + * + * Don't make that assumption if we the chip wasn't enabled and + * is at power-on reset, otherwise we'll incorrectly "detect" a + * SIL164 when there is none. + */ + if (!*need_post) { + jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa3, + 0xff); + if (jreg & 0x80) + ast->tx_chip_types = AST_TX_SIL164_BIT; + } + + if ((ast->chip == AST2300) || (ast->chip == AST2400) || + (ast->chip == AST2500)) { + /* + * On AST2300 and 2400, look the configuration set by the SoC in + * the SOC scratch register #1 bits 11:8 (interestingly marked + * as "reserved" in the spec) + */ + jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd1, + 0xff); + switch (jreg) { + case 0x04: + ast->tx_chip_types = AST_TX_SIL164_BIT; + break; + case 0x08: + ast->dp501_fw_addr = + drmm_kzalloc(dev, 32 * 1024, GFP_KERNEL); + if (ast->dp501_fw_addr) { + /* backup firmware */ + if (ast_backup_fw(dev, ast->dp501_fw_addr, + 32 * 1024)) { + drmm_kfree(dev, ast->dp501_fw_addr); + ast->dp501_fw_addr = NULL; + } + } + fallthrough; + case 0x0c: + ast->tx_chip_types = AST_TX_DP501_BIT; + } + } else if (ast->chip == AST2600) + ast_dp_launch(&ast->base, 0); + + /* Print stuff for diagnostic purposes */ + if (ast->tx_chip_types & AST_TX_NONE_BIT) + drm_info(dev, "Using analog VGA\n"); + if (ast->tx_chip_types & AST_TX_SIL164_BIT) + drm_info(dev, "Using Sil164 TMDS transmitter\n"); + if (ast->tx_chip_types & AST_TX_DP501_BIT) + drm_info(dev, "Using DP501 DisplayPort transmitter\n"); + + return 0; +} + +static int ast_get_dram_info(struct drm_device *dev) +{ + struct device_node *np = dev->dev->of_node; + struct ast_private *ast = to_ast_private(dev); + uint32_t mcr_cfg, mcr_scu_mpll, mcr_scu_strap; + uint32_t denum, num, div, ref_pll, dsel; + + switch (ast->config_mode) { + case ast_use_dt: + /* + * If some properties are missing, use reasonable + * defaults for AST2400 + */ + if (of_property_read_u32(np, "aspeed,mcr-configuration", + &mcr_cfg)) + mcr_cfg = 0x00000577; + if (of_property_read_u32(np, "aspeed,mcr-scu-mpll", + &mcr_scu_mpll)) + mcr_scu_mpll = 0x000050C0; + if (of_property_read_u32(np, "aspeed,mcr-scu-strap", + &mcr_scu_strap)) + mcr_scu_strap = 0; + break; + case ast_use_p2a: + ast_write32(ast, 0xf004, 0x1e6e0000); + ast_write32(ast, 0xf000, 0x1); + mcr_cfg = ast_read32(ast, 0x10004); + mcr_scu_mpll = ast_read32(ast, 0x10120); + mcr_scu_strap = ast_read32(ast, 0x10170); + break; + case ast_use_defaults: + default: + ast->dram_bus_width = 16; + ast->dram_type = AST_DRAM_1Gx16; + if (ast->chip == AST2500) + ast->mclk = 800; + else + ast->mclk = 396; + return 0; + } + + if (mcr_cfg & 0x40) + ast->dram_bus_width = 16; + else + ast->dram_bus_width = 32; + + if (ast->chip == AST2500) { + switch (mcr_cfg & 0x03) { + case 0: + ast->dram_type = AST_DRAM_1Gx16; + break; + default: + case 1: + ast->dram_type = AST_DRAM_2Gx16; + break; + case 2: + ast->dram_type = AST_DRAM_4Gx16; + break; + case 3: + ast->dram_type = AST_DRAM_8Gx16; + break; + } + } else if (ast->chip == AST2300 || ast->chip == AST2400) { + switch (mcr_cfg & 0x03) { + case 0: + ast->dram_type = AST_DRAM_512Mx16; + break; + default: + case 1: + ast->dram_type = AST_DRAM_1Gx16; + break; + case 2: + ast->dram_type = AST_DRAM_2Gx16; + break; + case 3: + ast->dram_type = AST_DRAM_4Gx16; + break; + } + } else { + switch (mcr_cfg & 0x0c) { + case 0: + case 4: + ast->dram_type = AST_DRAM_512Mx16; + break; + case 8: + if (mcr_cfg & 0x40) + ast->dram_type = AST_DRAM_1Gx16; + else + ast->dram_type = AST_DRAM_512Mx32; + break; + case 0xc: + ast->dram_type = AST_DRAM_1Gx32; + break; + } + } + + if (mcr_scu_strap & 0x2000) + ref_pll = 14318; + else + ref_pll = 12000; + + denum = mcr_scu_mpll & 0x1f; + num = (mcr_scu_mpll & 0x3fe0) >> 5; + dsel = (mcr_scu_mpll & 0xc000) >> 14; + switch (dsel) { + case 3: + div = 0x4; + break; + case 2: + case 1: + div = 0x2; + break; + default: + div = 0x1; + break; + } + ast->mclk = ref_pll * (num + 2) / ((denum + 2) * (div * 1000)); + return 0; +} + +/* + * Run this function as part of the HW device cleanup; not + * when the DRM device gets released. + */ +static void ast_device_release(void *data) +{ + struct ast_private *ast = data; + + /* enable standard VGA decode */ + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa1, 0x04); +} + +struct ast_private *ast_device_create(const struct drm_driver *drv, + struct pci_dev *pdev, unsigned long flags) +{ + struct drm_device *dev; + struct ast_private *ast; + bool need_post; + int ret = 0; + + ast = devm_drm_dev_alloc(&pdev->dev, drv, struct ast_private, base); + if (IS_ERR(ast)) + return ast; + dev = &ast->base; + + pci_set_drvdata(pdev, dev); + + ret = drmm_mutex_init(dev, &ast->ioregs_lock); + if (ret) + return ERR_PTR(ret); + + ast->regs = pcim_iomap(pdev, 1, 0); + if (!ast->regs) + return ERR_PTR(-EIO); + + /* + * If we don't have IO space at all, use MMIO now and + * assume the chip has MMIO enabled by default (rev 0x20 + * and higher). + */ + if (!(pci_resource_flags(pdev, 2) & IORESOURCE_IO)) { + drm_info(dev, "platform has no IO space, trying MMIO\n"); + ast->ioregs = ast->regs + AST_IO_MM_OFFSET; + } + + /* "map" IO regs if the above hasn't done so already */ + if (!ast->ioregs) { + ast->ioregs = pcim_iomap(pdev, 2, 0); + if (!ast->ioregs) + return ERR_PTR(-EIO); + } + + ast_detect_chip(dev, &need_post); + + ret = ast_get_dram_info(dev); + if (ret) + return ERR_PTR(ret); + + drm_info(dev, "dram MCLK=%u Mhz type=%d bus_width=%d\n", ast->mclk, + ast->dram_type, ast->dram_bus_width); + + if (need_post) + ast_post_gpu(dev); + + ret = ast_mm_init(ast); + if (ret) + return ERR_PTR(ret); + + /* map reserved buffer */ + ast->dp501_fw_buf = NULL; + if (dev->vram_mm->vram_size < pci_resource_len(pdev, 0)) { + ast->dp501_fw_buf = + pci_iomap_range(pdev, 0, dev->vram_mm->vram_size, 0); + if (!ast->dp501_fw_buf) + drm_info(dev, "failed to map reserved buffer!\n"); + } + + ret = ast_mode_config_init(ast); + if (ret) + return ERR_PTR(ret); + + ret = devm_add_action_or_reset(dev->dev, ast_device_release, ast); + if (ret) + return ERR_PTR(ret); + + return ast; +} diff --git a/drivers/gpu/drm/loongson/ast_old/ast_mm.c b/drivers/gpu/drm/loongson/ast_old/ast_mm.c new file mode 100644 index 0000000000000000000000000000000000000000..6e999408dda9a25acbe492329eb3c94bf40d2b28 --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/ast_mm.c @@ -0,0 +1,101 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: Dave Airlie + */ + +#include + +#include +#include +#include + +#include "ast_drv.h" + +static u32 ast_get_vram_size(struct ast_private *ast) +{ + u8 jreg; + u32 vram_size; + + ast_open_key(ast); + + vram_size = AST_VIDMEM_DEFAULT_SIZE; + jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xaa, 0xff); + switch (jreg & 3) { + case 0: + vram_size = AST_VIDMEM_SIZE_8M; + break; + case 1: + vram_size = AST_VIDMEM_SIZE_16M; + break; + case 2: + vram_size = AST_VIDMEM_SIZE_32M; + break; + case 3: + vram_size = AST_VIDMEM_SIZE_64M; + break; + } + + jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x99, 0xff); + switch (jreg & 0x03) { + case 1: + vram_size -= 0x100000; + break; + case 2: + vram_size -= 0x200000; + break; + case 3: + vram_size -= 0x400000; + break; + } + + return vram_size; +} + +int ast_mm_init(struct ast_private *ast) +{ + struct drm_device *dev = &ast->base; + struct pci_dev *pdev = to_pci_dev(dev->dev); + resource_size_t base, size; + u32 vram_size; + int ret; + + base = pci_resource_start(pdev, 0); + size = pci_resource_len(pdev, 0); + + /* Don't fail on errors, but performance might be reduced. */ + devm_arch_io_reserve_memtype_wc(dev->dev, base, size); + devm_arch_phys_wc_add(dev->dev, base, size); + + vram_size = ast_get_vram_size(ast); + + ret = drmm_vram_helper_init(dev, base, vram_size); + if (ret) { + drm_err(dev, "Error initializing VRAM MM; %d\n", ret); + return ret; + } + + return 0; +} diff --git a/drivers/gpu/drm/loongson/ast_old/ast_mode.c b/drivers/gpu/drm/loongson/ast_old/ast_mode.c new file mode 100644 index 0000000000000000000000000000000000000000..5374fc38757f93c7ce9bc2618a780800dc5237d7 --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/ast_mode.c @@ -0,0 +1,1881 @@ +/* + * Copyright 2012 Red Hat Inc. + * Parts based on xf86-video-ast + * Copyright (c) 2005 ASPEED Technology Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: Dave Airlie + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ast_drv.h" +#include "ast_tables.h" + +#define AST_LUT_SIZE 256 + +static inline void ast_load_palette_index(struct ast_private *ast, u8 index, + u8 red, u8 green, u8 blue) +{ + ast_io_write8(ast, AST_IO_DAC_INDEX_WRITE, index); + ast_io_read8(ast, AST_IO_SEQ_PORT); + ast_io_write8(ast, AST_IO_DAC_DATA, red); + ast_io_read8(ast, AST_IO_SEQ_PORT); + ast_io_write8(ast, AST_IO_DAC_DATA, green); + ast_io_read8(ast, AST_IO_SEQ_PORT); + ast_io_write8(ast, AST_IO_DAC_DATA, blue); + ast_io_read8(ast, AST_IO_SEQ_PORT); +} + +static void ast_crtc_set_gamma_linear(struct ast_private *ast, + const struct drm_format_info *format) +{ + int i; + + switch (format->format) { + case DRM_FORMAT_C8: /* In this case, gamma table is used as color palette */ + case DRM_FORMAT_RGB565: + case DRM_FORMAT_XRGB8888: + for (i = 0; i < AST_LUT_SIZE; i++) + ast_load_palette_index(ast, i, i, i, i); + break; + default: + drm_warn_once(&ast->base, + "Unsupported format %p4cc for gamma correction\n", + &format->format); + break; + } +} + +static void ast_crtc_set_gamma(struct ast_private *ast, + const struct drm_format_info *format, + struct drm_color_lut *lut) +{ + int i; + + switch (format->format) { + case DRM_FORMAT_C8: /* In this case, gamma table is used as color palette */ + case DRM_FORMAT_RGB565: + case DRM_FORMAT_XRGB8888: + for (i = 0; i < AST_LUT_SIZE; i++) + ast_load_palette_index(ast, i, lut[i].red >> 8, + lut[i].green >> 8, + lut[i].blue >> 8); + break; + default: + drm_warn_once(&ast->base, + "Unsupported format %p4cc for gamma correction\n", + &format->format); + break; + } +} + +static bool ast_get_vbios_mode_info(const struct drm_format_info *format, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + struct ast_vbios_mode_info *vbios_mode) +{ + u32 refresh_rate_index = 0, refresh_rate; + const struct ast_vbios_enhtable *best = NULL; + u32 hborder, vborder; + bool check_sync; + + switch (format->cpp[0] * 8) { + case 8: + vbios_mode->std_table = &vbios_stdtable[VGAModeIndex]; + break; + case 16: + vbios_mode->std_table = &vbios_stdtable[HiCModeIndex]; + break; + case 24: + case 32: + vbios_mode->std_table = &vbios_stdtable[TrueCModeIndex]; + break; + default: + return false; + } + + switch (mode->crtc_hdisplay) { + case 640: + vbios_mode->enh_table = &res_640x480[refresh_rate_index]; + break; + case 800: + vbios_mode->enh_table = &res_800x600[refresh_rate_index]; + break; + case 1024: + vbios_mode->enh_table = &res_1024x768[refresh_rate_index]; + break; + case 1152: + vbios_mode->enh_table = &res_1152x864[refresh_rate_index]; + break; + case 1280: + if (mode->crtc_vdisplay == 800) + vbios_mode->enh_table = + &res_1280x800[refresh_rate_index]; + else + vbios_mode->enh_table = + &res_1280x1024[refresh_rate_index]; + break; + case 1360: + vbios_mode->enh_table = &res_1360x768[refresh_rate_index]; + break; + case 1440: + vbios_mode->enh_table = &res_1440x900[refresh_rate_index]; + break; + case 1600: + if (mode->crtc_vdisplay == 900) + vbios_mode->enh_table = + &res_1600x900[refresh_rate_index]; + else + vbios_mode->enh_table = + &res_1600x1200[refresh_rate_index]; + break; + case 1680: + vbios_mode->enh_table = &res_1680x1050[refresh_rate_index]; + break; + case 1920: + if (mode->crtc_vdisplay == 1080) + vbios_mode->enh_table = + &res_1920x1080[refresh_rate_index]; + else + vbios_mode->enh_table = + &res_1920x1200[refresh_rate_index]; + break; + default: + return false; + } + + refresh_rate = drm_mode_vrefresh(mode); + check_sync = vbios_mode->enh_table->flags & WideScreenMode; + + while (1) { + const struct ast_vbios_enhtable *loop = vbios_mode->enh_table; + + while (loop->refresh_rate != 0xff) { + if ((check_sync) && + (((mode->flags & DRM_MODE_FLAG_NVSYNC) && + (loop->flags & PVSync)) || + ((mode->flags & DRM_MODE_FLAG_PVSYNC) && + (loop->flags & NVSync)) || + ((mode->flags & DRM_MODE_FLAG_NHSYNC) && + (loop->flags & PHSync)) || + ((mode->flags & DRM_MODE_FLAG_PHSYNC) && + (loop->flags & NHSync)))) { + loop++; + continue; + } + if (loop->refresh_rate <= refresh_rate && + (!best || loop->refresh_rate > best->refresh_rate)) + best = loop; + loop++; + } + if (best || !check_sync) + break; + check_sync = 0; + } + + if (best) + vbios_mode->enh_table = best; + + hborder = (vbios_mode->enh_table->flags & HBorder) ? 8 : 0; + vborder = (vbios_mode->enh_table->flags & VBorder) ? 8 : 0; + + adjusted_mode->crtc_htotal = vbios_mode->enh_table->ht; + adjusted_mode->crtc_hblank_start = vbios_mode->enh_table->hde + hborder; + adjusted_mode->crtc_hblank_end = vbios_mode->enh_table->ht - hborder; + adjusted_mode->crtc_hsync_start = vbios_mode->enh_table->hde + hborder + + vbios_mode->enh_table->hfp; + adjusted_mode->crtc_hsync_end = + (vbios_mode->enh_table->hde + hborder + + vbios_mode->enh_table->hfp + vbios_mode->enh_table->hsync); + + adjusted_mode->crtc_vtotal = vbios_mode->enh_table->vt; + adjusted_mode->crtc_vblank_start = vbios_mode->enh_table->vde + vborder; + adjusted_mode->crtc_vblank_end = vbios_mode->enh_table->vt - vborder; + adjusted_mode->crtc_vsync_start = vbios_mode->enh_table->vde + vborder + + vbios_mode->enh_table->vfp; + adjusted_mode->crtc_vsync_end = + (vbios_mode->enh_table->vde + vborder + + vbios_mode->enh_table->vfp + vbios_mode->enh_table->vsync); + + return true; +} + +static void +ast_set_vbios_color_reg(struct ast_private *ast, + const struct drm_format_info *format, + const struct ast_vbios_mode_info *vbios_mode) +{ + u32 color_index; + + switch (format->cpp[0]) { + case 1: + color_index = VGAModeIndex - 1; + break; + case 2: + color_index = HiCModeIndex; + break; + case 3: + case 4: + color_index = TrueCModeIndex; + break; + default: + return; + } + + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x8c, + (u8)((color_index & 0x0f) << 4)); + + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x91, 0x00); + + if (vbios_mode->enh_table->flags & NewModeInfo) { + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x91, 0xa8); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x92, + format->cpp[0] * 8); + } +} + +static void ast_set_vbios_mode_reg(struct ast_private *ast, + const struct drm_display_mode *adjusted_mode, + const struct ast_vbios_mode_info *vbios_mode) +{ + u32 refresh_rate_index, mode_id; + + refresh_rate_index = vbios_mode->enh_table->refresh_rate_index; + mode_id = vbios_mode->enh_table->mode_id; + + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x8d, + refresh_rate_index & 0xff); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x8e, mode_id & 0xff); + + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x91, 0x00); + + if (vbios_mode->enh_table->flags & NewModeInfo) { + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x91, 0xa8); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x93, + adjusted_mode->clock / 1000); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x94, + adjusted_mode->crtc_hdisplay); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x95, + adjusted_mode->crtc_hdisplay >> 8); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x96, + adjusted_mode->crtc_vdisplay); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x97, + adjusted_mode->crtc_vdisplay >> 8); + } +} + +static void ast_set_std_reg(struct ast_private *ast, + struct drm_display_mode *mode, + struct ast_vbios_mode_info *vbios_mode) +{ + const struct ast_vbios_stdtable *stdtable; + u32 i; + u8 jreg; + + stdtable = vbios_mode->std_table; + + jreg = stdtable->misc; + ast_io_write8(ast, AST_IO_MISC_PORT_WRITE, jreg); + + /* Set SEQ; except Screen Disable field */ + ast_set_index_reg(ast, AST_IO_SEQ_PORT, 0x00, 0x03); + ast_set_index_reg_mask(ast, AST_IO_SEQ_PORT, 0x01, 0xdf, + stdtable->seq[0]); + for (i = 1; i < 4; i++) { + jreg = stdtable->seq[i]; + ast_set_index_reg(ast, AST_IO_SEQ_PORT, (i + 1), jreg); + } + + /* Set CRTC; except base address and offset */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x11, 0x7f, 0x00); + for (i = 0; i < 12; i++) + ast_set_index_reg(ast, AST_IO_CRTC_PORT, i, stdtable->crtc[i]); + for (i = 14; i < 19; i++) + ast_set_index_reg(ast, AST_IO_CRTC_PORT, i, stdtable->crtc[i]); + for (i = 20; i < 25; i++) + ast_set_index_reg(ast, AST_IO_CRTC_PORT, i, stdtable->crtc[i]); + + /* set AR */ + jreg = ast_io_read8(ast, AST_IO_INPUT_STATUS1_READ); + for (i = 0; i < 20; i++) { + jreg = stdtable->ar[i]; + ast_io_write8(ast, AST_IO_AR_PORT_WRITE, (u8)i); + ast_io_write8(ast, AST_IO_AR_PORT_WRITE, jreg); + } + ast_io_write8(ast, AST_IO_AR_PORT_WRITE, 0x14); + ast_io_write8(ast, AST_IO_AR_PORT_WRITE, 0x00); + + jreg = ast_io_read8(ast, AST_IO_INPUT_STATUS1_READ); + ast_io_write8(ast, AST_IO_AR_PORT_WRITE, 0x20); + + /* Set GR */ + for (i = 0; i < 9; i++) + ast_set_index_reg(ast, AST_IO_GR_PORT, i, stdtable->gr[i]); +} + +static void ast_set_crtc_reg(struct ast_private *ast, + struct drm_display_mode *mode, + struct ast_vbios_mode_info *vbios_mode) +{ + u8 jreg05 = 0, jreg07 = 0, jreg09 = 0, jregAC = 0, jregAD = 0, + jregAE = 0; + u16 temp, precache = 0; + + if ((ast->chip == AST2500 || ast->chip == AST2600) && + (vbios_mode->enh_table->flags & AST2500PreCatchCRT)) + precache = 40; + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x11, 0x7f, 0x00); + + temp = (mode->crtc_htotal >> 3) - 5; + if (temp & 0x100) + jregAC |= 0x01; /* HT D[8] */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x00, 0x00, temp); + + temp = (mode->crtc_hdisplay >> 3) - 1; + if (temp & 0x100) + jregAC |= 0x04; /* HDE D[8] */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x01, 0x00, temp); + + temp = (mode->crtc_hblank_start >> 3) - 1; + if (temp & 0x100) + jregAC |= 0x10; /* HBS D[8] */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x02, 0x00, temp); + + temp = ((mode->crtc_hblank_end >> 3) - 1) & 0x7f; + if (temp & 0x20) + jreg05 |= 0x80; /* HBE D[5] */ + if (temp & 0x40) + jregAD |= 0x01; /* HBE D[5] */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x03, 0xE0, + (temp & 0x1f)); + + temp = ((mode->crtc_hsync_start - precache) >> 3) - 1; + if (temp & 0x100) + jregAC |= 0x40; /* HRS D[5] */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x04, 0x00, temp); + + temp = (((mode->crtc_hsync_end - precache) >> 3) - 1) & 0x3f; + if (temp & 0x20) + jregAD |= 0x04; /* HRE D[5] */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x05, 0x60, + (u8)((temp & 0x1f) | jreg05)); + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xAC, 0x00, jregAC); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xAD, 0x00, jregAD); + + // Workaround for HSync Time non octave pixels (1920x1080@60Hz HSync 44 pixels); + if ((ast->chip == AST2600) && (mode->crtc_vdisplay == 1080)) + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xFC, 0xFD, 0x02); + else + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xFC, 0xFD, 0x00); + + /* vert timings */ + temp = (mode->crtc_vtotal) - 2; + if (temp & 0x100) + jreg07 |= 0x01; + if (temp & 0x200) + jreg07 |= 0x20; + if (temp & 0x400) + jregAE |= 0x01; + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x06, 0x00, temp); + + temp = (mode->crtc_vsync_start) - 1; + if (temp & 0x100) + jreg07 |= 0x04; + if (temp & 0x200) + jreg07 |= 0x80; + if (temp & 0x400) + jregAE |= 0x08; + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x10, 0x00, temp); + + temp = (mode->crtc_vsync_end - 1) & 0x3f; + if (temp & 0x10) + jregAE |= 0x20; + if (temp & 0x20) + jregAE |= 0x40; + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x11, 0x70, temp & 0xf); + + temp = mode->crtc_vdisplay - 1; + if (temp & 0x100) + jreg07 |= 0x02; + if (temp & 0x200) + jreg07 |= 0x40; + if (temp & 0x400) + jregAE |= 0x02; + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x12, 0x00, temp); + + temp = mode->crtc_vblank_start - 1; + if (temp & 0x100) + jreg07 |= 0x08; + if (temp & 0x200) + jreg09 |= 0x20; + if (temp & 0x400) + jregAE |= 0x04; + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x15, 0x00, temp); + + temp = mode->crtc_vblank_end - 1; + if (temp & 0x100) + jregAE |= 0x10; + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x16, 0x00, temp); + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x07, 0x00, jreg07); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x09, 0xdf, jreg09); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xAE, 0x00, + (jregAE | 0x80)); + + if (precache) + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb6, 0x3f, 0x80); + else + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb6, 0x3f, 0x00); + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x11, 0x7f, 0x80); +} + +static void ast_set_offset_reg(struct ast_private *ast, + struct drm_framebuffer *fb) +{ + u16 offset; + + offset = fb->pitches[0] >> 3; + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x13, (offset & 0xff)); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xb0, (offset >> 8) & 0x3f); +} + +static void ast_set_dclk_reg(struct ast_private *ast, + struct drm_display_mode *mode, + struct ast_vbios_mode_info *vbios_mode) +{ + const struct ast_vbios_dclk_info *clk_info; + + if ((ast->chip == AST2500) || (ast->chip == AST2600)) + clk_info = + &dclk_table_ast2500[vbios_mode->enh_table->dclk_index]; + else + clk_info = &dclk_table[vbios_mode->enh_table->dclk_index]; + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xc0, 0x00, + clk_info->param1); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xc1, 0x00, + clk_info->param2); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xbb, 0x0f, + (clk_info->param3 & 0xc0) | + ((clk_info->param3 & 0x3) << 4)); +} + +static void ast_set_color_reg(struct ast_private *ast, + const struct drm_format_info *format) +{ + u8 jregA0 = 0, jregA3 = 0, jregA8 = 0; + + switch (format->cpp[0] * 8) { + case 8: + jregA0 = 0x70; + jregA3 = 0x01; + jregA8 = 0x00; + break; + case 15: + case 16: + jregA0 = 0x70; + jregA3 = 0x04; + jregA8 = 0x02; + break; + case 32: + jregA0 = 0x70; + jregA3 = 0x08; + jregA8 = 0x02; + break; + } + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa0, 0x8f, jregA0); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa3, 0xf0, jregA3); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa8, 0xfd, jregA8); +} + +static void ast_set_crtthd_reg(struct ast_private *ast) +{ + /* Set Threshold */ + if (ast->chip == AST2600) { + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa7, 0xe0); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa6, 0xa0); + } else if (ast->chip == AST2300 || ast->chip == AST2400 || + ast->chip == AST2500) { + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa7, 0x78); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa6, 0x60); + } else if (ast->chip == AST2100 || ast->chip == AST1100 || + ast->chip == AST2200 || ast->chip == AST2150) { + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa7, 0x3f); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa6, 0x2f); + } else { + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa7, 0x2f); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa6, 0x1f); + } +} + +static void ast_set_sync_reg(struct ast_private *ast, + struct drm_display_mode *mode, + struct ast_vbios_mode_info *vbios_mode) +{ + u8 jreg; + + jreg = ast_io_read8(ast, AST_IO_MISC_PORT_READ); + jreg &= ~0xC0; + if (vbios_mode->enh_table->flags & NVSync) + jreg |= 0x80; + if (vbios_mode->enh_table->flags & NHSync) + jreg |= 0x40; + ast_io_write8(ast, AST_IO_MISC_PORT_WRITE, jreg); +} + +static void ast_set_start_address_crt1(struct ast_private *ast, + unsigned int offset) +{ + u32 addr; + + addr = offset >> 2; + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x0d, (u8)(addr & 0xff)); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x0c, + (u8)((addr >> 8) & 0xff)); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xaf, + (u8)((addr >> 16) & 0xff)); +} + +static void ast_wait_for_vretrace(struct ast_private *ast) +{ + unsigned long timeout = jiffies + HZ; + u8 vgair1; + + do { + vgair1 = ast_io_read8(ast, AST_IO_INPUT_STATUS1_READ); + } while (!(vgair1 & AST_IO_VGAIR1_VREFRESH) && + time_before(jiffies, timeout)); +} + +/* + * Primary plane + */ + +static const uint32_t ast_primary_plane_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB565, + DRM_FORMAT_C8, +}; + +static int ast_primary_plane_helper_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_device *dev = plane->dev; + struct drm_plane_state *new_plane_state = + drm_atomic_get_new_plane_state(state, plane); + struct drm_crtc_state *new_crtc_state = NULL; + struct ast_crtc_state *new_ast_crtc_state; + int ret; + + if (new_plane_state->crtc) + new_crtc_state = drm_atomic_get_new_crtc_state( + state, new_plane_state->crtc); + + ret = drm_atomic_helper_check_plane_state( + new_plane_state, new_crtc_state, DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, false, true); + if (ret) { + return ret; + } else if (!new_plane_state->visible) { + if (drm_WARN_ON( + dev, + new_plane_state->crtc)) /* cannot legally happen */ + return -EINVAL; + else + return 0; + } + + new_ast_crtc_state = to_ast_crtc_state(new_crtc_state); + + new_ast_crtc_state->format = new_plane_state->fb->format; + + return 0; +} + +static void +ast_primary_plane_helper_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_device *dev = plane->dev; + struct ast_private *ast = to_ast_private(dev); + struct drm_plane_state *plane_state = + drm_atomic_get_new_plane_state(state, plane); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_plane_state *old_plane_state = + drm_atomic_get_old_plane_state(state, plane); + struct drm_framebuffer *old_fb = old_plane_state->fb; + struct drm_gem_vram_object *gbo; + s64 gpu_addr; + + if (!old_fb || (fb->format != old_fb->format)) { + struct drm_crtc *crtc = plane_state->crtc; + struct drm_crtc_state *crtc_state = + drm_atomic_get_new_crtc_state(state, crtc); + struct ast_crtc_state *ast_crtc_state = + to_ast_crtc_state(crtc_state); + struct ast_vbios_mode_info *vbios_mode_info = + &ast_crtc_state->vbios_mode_info; + + ast_set_color_reg(ast, fb->format); + ast_set_vbios_color_reg(ast, fb->format, vbios_mode_info); + } + + gbo = drm_gem_vram_of_gem(fb->obj[0]); + gpu_addr = drm_gem_vram_offset(gbo); + if (drm_WARN_ON_ONCE(dev, gpu_addr < 0)) + return; /* Bug: we didn't pin the BO to VRAM in prepare_fb. */ + + ast_set_offset_reg(ast, fb); + ast_set_start_address_crt1(ast, (u32)gpu_addr); + + ast_set_index_reg_mask(ast, AST_IO_SEQ_PORT, 0x1, 0xdf, 0x00); +} + +static void +ast_primary_plane_helper_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct ast_private *ast = to_ast_private(plane->dev); + + ast_set_index_reg_mask(ast, AST_IO_SEQ_PORT, 0x1, 0xdf, 0x20); +} + +static const struct drm_plane_helper_funcs ast_primary_plane_helper_funcs = { + DRM_GEM_VRAM_PLANE_HELPER_FUNCS, + .atomic_check = ast_primary_plane_helper_atomic_check, + .atomic_update = ast_primary_plane_helper_atomic_update, + .atomic_disable = ast_primary_plane_helper_atomic_disable, +}; + +static const struct drm_plane_funcs ast_primary_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static int ast_primary_plane_init(struct ast_private *ast) +{ + struct drm_device *dev = &ast->base; + struct drm_plane *primary_plane = &ast->primary_plane; + int ret; + + ret = drm_universal_plane_init(dev, primary_plane, 0x01, + &ast_primary_plane_funcs, + ast_primary_plane_formats, + ARRAY_SIZE(ast_primary_plane_formats), + NULL, DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) { + drm_err(dev, "drm_universal_plane_init() failed: %d\n", ret); + return ret; + } + drm_plane_helper_add(primary_plane, &ast_primary_plane_helper_funcs); + + return 0; +} + +/* + * Cursor plane + */ + +static void ast_update_cursor_image(u8 __iomem *dst, const u8 *src, int width, + int height) +{ + union { + u32 ul; + u8 b[4]; + } srcdata32[2], data32; + union { + u16 us; + u8 b[2]; + } data16; + u32 csum = 0; + s32 alpha_dst_delta, last_alpha_dst_delta; + u8 __iomem *dstxor; + const u8 *srcxor; + int i, j; + u32 per_pixel_copy, two_pixel_copy; + + alpha_dst_delta = AST_MAX_HWC_WIDTH << 1; + last_alpha_dst_delta = alpha_dst_delta - (width << 1); + + srcxor = src; + dstxor = (u8 *)dst + last_alpha_dst_delta + + (AST_MAX_HWC_HEIGHT - height) * alpha_dst_delta; + per_pixel_copy = width & 1; + two_pixel_copy = width >> 1; + + for (j = 0; j < height; j++) { + for (i = 0; i < two_pixel_copy; i++) { + srcdata32[0].ul = *((u32 *)srcxor) & 0xf0f0f0f0; + srcdata32[1].ul = *((u32 *)(srcxor + 4)) & 0xf0f0f0f0; + data32.b[0] = srcdata32[0].b[1] | + (srcdata32[0].b[0] >> 4); + data32.b[1] = srcdata32[0].b[3] | + (srcdata32[0].b[2] >> 4); + data32.b[2] = srcdata32[1].b[1] | + (srcdata32[1].b[0] >> 4); + data32.b[3] = srcdata32[1].b[3] | + (srcdata32[1].b[2] >> 4); + + writel(data32.ul, dstxor); + csum += data32.ul; + + dstxor += 4; + srcxor += 8; + } + + for (i = 0; i < per_pixel_copy; i++) { + srcdata32[0].ul = *((u32 *)srcxor) & 0xf0f0f0f0; + data16.b[0] = srcdata32[0].b[1] | + (srcdata32[0].b[0] >> 4); + data16.b[1] = srcdata32[0].b[3] | + (srcdata32[0].b[2] >> 4); + writew(data16.us, dstxor); + csum += (u32)data16.us; + + dstxor += 2; + srcxor += 4; + } + dstxor += last_alpha_dst_delta; + } + + /* write checksum + signature */ + dst += AST_HWC_SIZE; + writel(csum, dst); + writel(width, dst + AST_HWC_SIGNATURE_SizeX); + writel(height, dst + AST_HWC_SIGNATURE_SizeY); + writel(0, dst + AST_HWC_SIGNATURE_HOTSPOTX); + writel(0, dst + AST_HWC_SIGNATURE_HOTSPOTY); +} + +static void ast_set_cursor_base(struct ast_private *ast, u64 address) +{ + u8 addr0 = (address >> 3) & 0xff; + u8 addr1 = (address >> 11) & 0xff; + u8 addr2 = (address >> 19) & 0xff; + + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc8, addr0); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc9, addr1); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xca, addr2); +} + +static void ast_set_cursor_location(struct ast_private *ast, u16 x, u16 y, + u8 x_offset, u8 y_offset) +{ + u8 x0 = (x & 0x00ff); + u8 x1 = (x & 0x0f00) >> 8; + u8 y0 = (y & 0x00ff); + u8 y1 = (y & 0x0700) >> 8; + + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc2, x_offset); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc3, y_offset); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc4, x0); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc5, x1); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc6, y0); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc7, y1); +} + +static void ast_set_cursor_enabled(struct ast_private *ast, bool enabled) +{ + static const u8 mask = + (u8) ~(AST_IO_VGACRCB_HWC_16BPP | AST_IO_VGACRCB_HWC_ENABLED); + + u8 vgacrcb = AST_IO_VGACRCB_HWC_16BPP; + + if (enabled) + vgacrcb |= AST_IO_VGACRCB_HWC_ENABLED; + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xcb, mask, vgacrcb); +} + +static const uint32_t ast_cursor_plane_formats[] = { + DRM_FORMAT_ARGB8888, +}; + +static int ast_cursor_plane_helper_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_framebuffer *new_fb = new_plane_state->fb; + struct drm_crtc_state *new_crtc_state = NULL; + int ret; + + if (new_plane_state->crtc) + new_crtc_state = drm_atomic_get_new_crtc_state( + state, new_plane_state->crtc); + + ret = drm_atomic_helper_check_plane_state( + new_plane_state, new_crtc_state, DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, true, true); + if (ret || !new_plane_state->visible) + return ret; + + if (new_fb->width > AST_MAX_HWC_WIDTH || + new_fb->height > AST_MAX_HWC_HEIGHT) + return -EINVAL; + + return 0; +} + +static void +ast_cursor_plane_helper_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct ast_plane *ast_plane = to_ast_plane(plane); + struct drm_plane_state *plane_state = + drm_atomic_get_new_plane_state(state, plane); + struct drm_shadow_plane_state *shadow_plane_state = + to_drm_shadow_plane_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_plane_state *old_plane_state = + drm_atomic_get_old_plane_state(state, plane); + struct drm_framebuffer *old_fb = old_plane_state->fb; + struct ast_private *ast = to_ast_private(plane->dev); + struct iosys_map dst_map = ast_plane->map; + u64 dst_off = ast_plane->off; + struct iosys_map src_map = shadow_plane_state->data[0]; + unsigned int offset_x, offset_y; + u16 x, y; + u8 x_offset, y_offset; + u8 __iomem *dst; + u8 __iomem *sig; + const u8 *src; + + src = src_map.vaddr; /* TODO: Use mapping abstraction properly */ + dst = dst_map.vaddr_iomem; /* TODO: Use mapping abstraction properly */ + sig = dst + AST_HWC_SIZE; /* TODO: Use mapping abstraction properly */ + + /* + * Do data transfer to HW cursor BO. If a new cursor image was installed, + * point the scanout engine to dst_gbo's offset and page-flip the HWC buffers. + */ + + ast_update_cursor_image(dst, src, fb->width, fb->height); + + if (fb != old_fb) + ast_set_cursor_base(ast, dst_off); + + /* + * Update location in HWC signature and registers. + */ + + writel(plane_state->crtc_x, sig + AST_HWC_SIGNATURE_X); + writel(plane_state->crtc_y, sig + AST_HWC_SIGNATURE_Y); + + offset_x = AST_MAX_HWC_WIDTH - fb->width; + offset_y = AST_MAX_HWC_HEIGHT - fb->height; + + if (plane_state->crtc_x < 0) { + x_offset = (-plane_state->crtc_x) + offset_x; + x = 0; + } else { + x_offset = offset_x; + x = plane_state->crtc_x; + } + if (plane_state->crtc_y < 0) { + y_offset = (-plane_state->crtc_y) + offset_y; + y = 0; + } else { + y_offset = offset_y; + y = plane_state->crtc_y; + } + + ast_set_cursor_location(ast, x, y, x_offset, y_offset); + + /* Dummy write to enable HWC and make the HW pick-up the changes. */ + ast_set_cursor_enabled(ast, true); +} + +static void +ast_cursor_plane_helper_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct ast_private *ast = to_ast_private(plane->dev); + + ast_set_cursor_enabled(ast, false); +} + +static const struct drm_plane_helper_funcs ast_cursor_plane_helper_funcs = { + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, + .atomic_check = ast_cursor_plane_helper_atomic_check, + .atomic_update = ast_cursor_plane_helper_atomic_update, + .atomic_disable = ast_cursor_plane_helper_atomic_disable, +}; + +static void ast_cursor_plane_destroy(struct drm_plane *plane) +{ + struct ast_plane *ast_plane = to_ast_plane(plane); + struct drm_gem_vram_object *gbo = ast_plane->gbo; + struct iosys_map map = ast_plane->map; + + drm_gem_vram_vunmap(gbo, &map); + drm_gem_vram_unpin(gbo); + drm_gem_vram_put(gbo); + + drm_plane_cleanup(plane); +} + +static const struct drm_plane_funcs ast_cursor_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = ast_cursor_plane_destroy, + DRM_GEM_SHADOW_PLANE_FUNCS, +}; + +static int ast_cursor_plane_init(struct ast_private *ast) +{ + struct drm_device *dev = &ast->base; + struct ast_plane *ast_plane = &ast->cursor_plane; + struct drm_plane *cursor_plane = &ast_plane->base; + size_t size; + struct drm_gem_vram_object *gbo; + struct iosys_map map; + int ret; + s64 off; + + /* + * Allocate backing storage for cursors. The BOs are permanently + * pinned to the top end of the VRAM. + */ + + size = roundup(AST_HWC_SIZE + AST_HWC_SIGNATURE_SIZE, PAGE_SIZE); + + gbo = drm_gem_vram_create(dev, size, 0); + if (IS_ERR(gbo)) + return PTR_ERR(gbo); + + ret = drm_gem_vram_pin(gbo, DRM_GEM_VRAM_PL_FLAG_VRAM | + DRM_GEM_VRAM_PL_FLAG_TOPDOWN); + if (ret) + goto err_drm_gem_vram_put; + ret = drm_gem_vram_vmap(gbo, &map); + if (ret) + goto err_drm_gem_vram_unpin; + off = drm_gem_vram_offset(gbo); + if (off < 0) { + ret = off; + goto err_drm_gem_vram_vunmap; + } + + ast_plane->gbo = gbo; + ast_plane->map = map; + ast_plane->off = off; + + /* + * Create the cursor plane. The plane's destroy callback will release + * the backing storages' BO memory. + */ + + ret = drm_universal_plane_init(dev, cursor_plane, 0x01, + &ast_cursor_plane_funcs, + ast_cursor_plane_formats, + ARRAY_SIZE(ast_cursor_plane_formats), + NULL, DRM_PLANE_TYPE_CURSOR, NULL); + if (ret) { + drm_err(dev, "drm_universal_plane failed(): %d\n", ret); + goto err_drm_gem_vram_vunmap; + } + drm_plane_helper_add(cursor_plane, &ast_cursor_plane_helper_funcs); + + return 0; + +err_drm_gem_vram_vunmap: + drm_gem_vram_vunmap(gbo, &map); +err_drm_gem_vram_unpin: + drm_gem_vram_unpin(gbo); +err_drm_gem_vram_put: + drm_gem_vram_put(gbo); + return ret; +} + +/* + * CRTC + */ + +static void ast_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct ast_private *ast = to_ast_private(crtc->dev); + u8 ch = AST_DPMS_VSYNC_OFF | AST_DPMS_HSYNC_OFF; + struct ast_crtc_state *ast_state; + const struct drm_format_info *format; + struct ast_vbios_mode_info *vbios_mode_info; + + /* TODO: Maybe control display signal generation with + * Sync Enable (bit CR17.7). + */ + switch (mode) { + case DRM_MODE_DPMS_ON: + ast_set_index_reg_mask(ast, AST_IO_SEQ_PORT, 0x01, 0xdf, 0); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb6, 0xfc, 0); + if (ast->tx_chip_types & AST_TX_DP501_BIT) + ast_set_dp501_video_output(crtc->dev, 1); + + if (ast->tx_chip_types & AST_TX_ASTDP_BIT) { + ast_dp_power_on_off(crtc->dev, AST_DP_POWER_ON); + ast_wait_for_vretrace(ast); + ast_dp_set_on_off(crtc->dev, 1); + } + + ast_state = to_ast_crtc_state(crtc->state); + format = ast_state->format; + + if (format) { + vbios_mode_info = &ast_state->vbios_mode_info; + + ast_set_color_reg(ast, format); + ast_set_vbios_color_reg(ast, format, vbios_mode_info); + if (crtc->state->gamma_lut) + ast_crtc_set_gamma( + ast, format, + crtc->state->gamma_lut->data); + else + ast_crtc_set_gamma_linear(ast, format); + } + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + ch = mode; + if (ast->tx_chip_types & AST_TX_DP501_BIT) + ast_set_dp501_video_output(crtc->dev, 0); + + if (ast->tx_chip_types & AST_TX_ASTDP_BIT) { + ast_dp_set_on_off(crtc->dev, 0); + ast_dp_power_on_off(crtc->dev, AST_DP_POWER_OFF); + } + + ast_set_index_reg_mask(ast, AST_IO_SEQ_PORT, 0x01, 0xdf, 0x20); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb6, 0xfc, ch); + break; + } +} + +static enum drm_mode_status +ast_crtc_helper_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct ast_private *ast = to_ast_private(crtc->dev); + enum drm_mode_status status; + uint32_t jtemp; + + if (ast->support_wide_screen) { + if ((mode->hdisplay == 1680) && (mode->vdisplay == 1050)) + return MODE_OK; + if ((mode->hdisplay == 1280) && (mode->vdisplay == 800)) + return MODE_OK; + if ((mode->hdisplay == 1440) && (mode->vdisplay == 900)) + return MODE_OK; + if ((mode->hdisplay == 1360) && (mode->vdisplay == 768)) + return MODE_OK; + if ((mode->hdisplay == 1600) && (mode->vdisplay == 900)) + return MODE_OK; + if ((mode->hdisplay == 1152) && (mode->vdisplay == 864)) + return MODE_OK; + + if ((ast->chip == AST2100) || (ast->chip == AST2200) || + (ast->chip == AST2300) || (ast->chip == AST2400) || + (ast->chip == AST2500) || (ast->chip == AST2600)) { + if ((mode->hdisplay == 1920) && + (mode->vdisplay == 1080)) + return MODE_OK; + + if ((mode->hdisplay == 1920) && + (mode->vdisplay == 1200)) { + jtemp = ast_get_index_reg_mask( + ast, AST_IO_CRTC_PORT, 0xd1, 0xff); + if (jtemp & 0x01) + return MODE_NOMODE; + else + return MODE_OK; + } + } + } + + status = MODE_NOMODE; + + switch (mode->hdisplay) { + case 640: + if (mode->vdisplay == 480) + status = MODE_OK; + break; + case 800: + if (mode->vdisplay == 600) + status = MODE_OK; + break; + case 1024: + if (mode->vdisplay == 768) + status = MODE_OK; + break; + case 1152: + if (mode->vdisplay == 864) + status = MODE_OK; + break; + case 1280: + if (mode->vdisplay == 1024) + status = MODE_OK; + break; + case 1600: + if (mode->vdisplay == 1200) + status = MODE_OK; + break; + default: + break; + } + + return status; +} + +static int ast_crtc_helper_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = + drm_atomic_get_new_crtc_state(state, crtc); + struct drm_crtc_state *old_crtc_state = + drm_atomic_get_old_crtc_state(state, crtc); + struct ast_crtc_state *old_ast_crtc_state = + to_ast_crtc_state(old_crtc_state); + struct drm_device *dev = crtc->dev; + struct ast_crtc_state *ast_state; + const struct drm_format_info *format; + bool succ; + int ret; + + if (!crtc_state->enable) + return 0; + + ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state); + if (ret) + return ret; + + ast_state = to_ast_crtc_state(crtc_state); + + format = ast_state->format; + if (drm_WARN_ON_ONCE(dev, !format)) + return -EINVAL; /* BUG: We didn't set format in primary check(). */ + + /* + * The gamma LUT has to be reloaded after changing the primary + * plane's color format. + */ + if (old_ast_crtc_state->format != format) + crtc_state->color_mgmt_changed = true; + + if (crtc_state->color_mgmt_changed && crtc_state->gamma_lut) { + if (crtc_state->gamma_lut->length != + AST_LUT_SIZE * sizeof(struct drm_color_lut)) { + drm_err(dev, "Wrong size for gamma_lut %zu\n", + crtc_state->gamma_lut->length); + return -EINVAL; + } + } + + succ = ast_get_vbios_mode_info(format, &crtc_state->mode, + &crtc_state->adjusted_mode, + &ast_state->vbios_mode_info); + if (!succ) + return -EINVAL; + + return 0; +} + +static void ast_crtc_helper_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = + drm_atomic_get_new_crtc_state(state, crtc); + struct drm_device *dev = crtc->dev; + struct ast_private *ast = to_ast_private(dev); + struct ast_crtc_state *ast_crtc_state = to_ast_crtc_state(crtc_state); + struct ast_vbios_mode_info *vbios_mode_info = + &ast_crtc_state->vbios_mode_info; + + /* + * The gamma LUT has to be reloaded after changing the primary + * plane's color format. + */ + if (crtc_state->enable && crtc_state->color_mgmt_changed) { + if (crtc_state->gamma_lut) + ast_crtc_set_gamma(ast, ast_crtc_state->format, + crtc_state->gamma_lut->data); + else + ast_crtc_set_gamma_linear(ast, ast_crtc_state->format); + } + + //Set Aspeed Display-Port + if (ast->tx_chip_types & AST_TX_ASTDP_BIT) + ast_dp_set_mode(crtc, vbios_mode_info); +} + +static void ast_crtc_helper_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *dev = crtc->dev; + struct ast_private *ast = to_ast_private(dev); + struct drm_crtc_state *crtc_state = + drm_atomic_get_new_crtc_state(state, crtc); + struct ast_crtc_state *ast_crtc_state = to_ast_crtc_state(crtc_state); + struct ast_vbios_mode_info *vbios_mode_info = + &ast_crtc_state->vbios_mode_info; + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + + ast_set_vbios_mode_reg(ast, adjusted_mode, vbios_mode_info); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa1, 0x06); + ast_set_std_reg(ast, adjusted_mode, vbios_mode_info); + ast_set_crtc_reg(ast, adjusted_mode, vbios_mode_info); + ast_set_dclk_reg(ast, adjusted_mode, vbios_mode_info); + ast_set_crtthd_reg(ast); + ast_set_sync_reg(ast, adjusted_mode, vbios_mode_info); + + ast_crtc_dpms(crtc, DRM_MODE_DPMS_ON); +} + +static void ast_crtc_helper_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *old_crtc_state = + drm_atomic_get_old_crtc_state(state, crtc); + struct drm_device *dev = crtc->dev; + struct ast_private *ast = to_ast_private(dev); + + ast_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); + + /* + * HW cursors require the underlying primary plane and CRTC to + * display a valid mode and image. This is not the case during + * full modeset operations. So we temporarily disable any active + * plane, including the HW cursor. Each plane's atomic_update() + * helper will re-enable it if necessary. + * + * We only do this during *full* modesets. It does not affect + * simple pageflips on the planes. + */ + drm_atomic_helper_disable_planes_on_crtc(old_crtc_state, false); + + /* + * Ensure that no scanout takes place before reprogramming mode + * and format registers. + */ + ast_wait_for_vretrace(ast); +} + +static const struct drm_crtc_helper_funcs ast_crtc_helper_funcs = { + .mode_valid = ast_crtc_helper_mode_valid, + .atomic_check = ast_crtc_helper_atomic_check, + .atomic_flush = ast_crtc_helper_atomic_flush, + .atomic_enable = ast_crtc_helper_atomic_enable, + .atomic_disable = ast_crtc_helper_atomic_disable, +}; + +static void ast_crtc_reset(struct drm_crtc *crtc) +{ + struct ast_crtc_state *ast_state = + kzalloc(sizeof(*ast_state), GFP_KERNEL); + + if (crtc->state) + crtc->funcs->atomic_destroy_state(crtc, crtc->state); + + if (ast_state) + __drm_atomic_helper_crtc_reset(crtc, &ast_state->base); + else + __drm_atomic_helper_crtc_reset(crtc, NULL); +} + +static struct drm_crtc_state * +ast_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct ast_crtc_state *new_ast_state, *ast_state; + struct drm_device *dev = crtc->dev; + + if (drm_WARN_ON(dev, !crtc->state)) + return NULL; + + new_ast_state = kmalloc(sizeof(*new_ast_state), GFP_KERNEL); + if (!new_ast_state) + return NULL; + __drm_atomic_helper_crtc_duplicate_state(crtc, &new_ast_state->base); + + ast_state = to_ast_crtc_state(crtc->state); + + new_ast_state->format = ast_state->format; + memcpy(&new_ast_state->vbios_mode_info, &ast_state->vbios_mode_info, + sizeof(new_ast_state->vbios_mode_info)); + + return &new_ast_state->base; +} + +static void ast_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct ast_crtc_state *ast_state = to_ast_crtc_state(state); + + __drm_atomic_helper_crtc_destroy_state(&ast_state->base); + kfree(ast_state); +} + +static const struct drm_crtc_funcs ast_crtc_funcs = { + .reset = ast_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = ast_crtc_atomic_duplicate_state, + .atomic_destroy_state = ast_crtc_atomic_destroy_state, +}; + +static int ast_crtc_init(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + struct drm_crtc *crtc = &ast->crtc; + int ret; + + ret = drm_crtc_init_with_planes(dev, crtc, &ast->primary_plane, + &ast->cursor_plane.base, + &ast_crtc_funcs, NULL); + if (ret) + return ret; + + drm_mode_crtc_set_gamma_size(crtc, AST_LUT_SIZE); + drm_crtc_enable_color_mgmt(crtc, 0, false, AST_LUT_SIZE); + + drm_crtc_helper_add(crtc, &ast_crtc_helper_funcs); + + return 0; +} + +/* + * VGA Connector + */ + +static int ast_vga_connector_helper_get_modes(struct drm_connector *connector) +{ + struct ast_vga_connector *ast_vga_connector = + to_ast_vga_connector(connector); + struct drm_device *dev = connector->dev; + struct ast_private *ast = to_ast_private(dev); + struct edid *edid; + int count; + + if (!ast_vga_connector->i2c) + goto err_drm_connector_update_edid_property; + + /* + * Protect access to I/O registers from concurrent modesetting + * by acquiring the I/O-register lock. + */ + mutex_lock(&ast->ioregs_lock); + + edid = drm_get_edid(connector, &ast_vga_connector->i2c->adapter); + if (!edid) + goto err_mutex_unlock; + + mutex_unlock(&ast->ioregs_lock); + + count = drm_add_edid_modes(connector, edid); + kfree(edid); + + return count; + +err_mutex_unlock: + mutex_unlock(&ast->ioregs_lock); +err_drm_connector_update_edid_property: + drm_connector_update_edid_property(connector, NULL); + return 0; +} + +static const struct drm_connector_helper_funcs ast_vga_connector_helper_funcs = { + .get_modes = ast_vga_connector_helper_get_modes, +}; + +static const struct drm_connector_funcs ast_vga_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int ast_vga_connector_init(struct drm_device *dev, + struct ast_vga_connector *ast_vga_connector) +{ + struct drm_connector *connector = &ast_vga_connector->base; + int ret; + + ast_vga_connector->i2c = ast_i2c_create(dev); + if (!ast_vga_connector->i2c) + drm_err(dev, "failed to add ddc bus for connector\n"); + + if (ast_vga_connector->i2c) + ret = drm_connector_init_with_ddc( + dev, connector, &ast_vga_connector_funcs, + DRM_MODE_CONNECTOR_VGA, + &ast_vga_connector->i2c->adapter); + else + ret = drm_connector_init(dev, connector, + &ast_vga_connector_funcs, + DRM_MODE_CONNECTOR_VGA); + if (ret) + return ret; + + drm_connector_helper_add(connector, &ast_vga_connector_helper_funcs); + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + connector->polled = DRM_CONNECTOR_POLL_CONNECT; + + return 0; +} + +static int ast_vga_output_init(struct ast_private *ast) +{ + struct drm_device *dev = &ast->base; + struct drm_crtc *crtc = &ast->crtc; + struct drm_encoder *encoder = &ast->output.vga.encoder; + struct ast_vga_connector *ast_vga_connector = + &ast->output.vga.vga_connector; + struct drm_connector *connector = &ast_vga_connector->base; + int ret; + + ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_DAC); + if (ret) + return ret; + encoder->possible_crtcs = drm_crtc_mask(crtc); + + ret = ast_vga_connector_init(dev, ast_vga_connector); + if (ret) + return ret; + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ret; + + return 0; +} + +/* + * SIL164 Connector + */ + +static int +ast_sil164_connector_helper_get_modes(struct drm_connector *connector) +{ + struct ast_sil164_connector *ast_sil164_connector = + to_ast_sil164_connector(connector); + struct drm_device *dev = connector->dev; + struct ast_private *ast = to_ast_private(dev); + struct edid *edid; + int count; + + if (!ast_sil164_connector->i2c) + goto err_drm_connector_update_edid_property; + + /* + * Protect access to I/O registers from concurrent modesetting + * by acquiring the I/O-register lock. + */ + mutex_lock(&ast->ioregs_lock); + + edid = drm_get_edid(connector, &ast_sil164_connector->i2c->adapter); + if (!edid) + goto err_mutex_unlock; + + mutex_unlock(&ast->ioregs_lock); + + count = drm_add_edid_modes(connector, edid); + kfree(edid); + + return count; + +err_mutex_unlock: + mutex_unlock(&ast->ioregs_lock); +err_drm_connector_update_edid_property: + drm_connector_update_edid_property(connector, NULL); + return 0; +} + +static const struct drm_connector_helper_funcs + ast_sil164_connector_helper_funcs = { + .get_modes = ast_sil164_connector_helper_get_modes, + }; + +static const struct drm_connector_funcs ast_sil164_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int +ast_sil164_connector_init(struct drm_device *dev, + struct ast_sil164_connector *ast_sil164_connector) +{ + struct drm_connector *connector = &ast_sil164_connector->base; + int ret; + + ast_sil164_connector->i2c = ast_i2c_create(dev); + if (!ast_sil164_connector->i2c) + drm_err(dev, "failed to add ddc bus for connector\n"); + + if (ast_sil164_connector->i2c) + ret = drm_connector_init_with_ddc( + dev, connector, &ast_sil164_connector_funcs, + DRM_MODE_CONNECTOR_DVII, + &ast_sil164_connector->i2c->adapter); + else + ret = drm_connector_init(dev, connector, + &ast_sil164_connector_funcs, + DRM_MODE_CONNECTOR_DVII); + if (ret) + return ret; + + drm_connector_helper_add(connector, &ast_sil164_connector_helper_funcs); + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + connector->polled = DRM_CONNECTOR_POLL_CONNECT; + + return 0; +} + +static int ast_sil164_output_init(struct ast_private *ast) +{ + struct drm_device *dev = &ast->base; + struct drm_crtc *crtc = &ast->crtc; + struct drm_encoder *encoder = &ast->output.sil164.encoder; + struct ast_sil164_connector *ast_sil164_connector = + &ast->output.sil164.sil164_connector; + struct drm_connector *connector = &ast_sil164_connector->base; + int ret; + + ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS); + if (ret) + return ret; + encoder->possible_crtcs = drm_crtc_mask(crtc); + + ret = ast_sil164_connector_init(dev, ast_sil164_connector); + if (ret) + return ret; + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ret; + + return 0; +} + +/* + * DP501 Connector + */ + +static int ast_dp501_connector_helper_get_modes(struct drm_connector *connector) +{ + void *edid; + bool succ; + int count; + + edid = kmalloc(EDID_LENGTH, GFP_KERNEL); + if (!edid) + goto err_drm_connector_update_edid_property; + + succ = ast_dp501_read_edid(connector->dev, edid); + if (!succ) + goto err_kfree; + + drm_connector_update_edid_property(connector, edid); + count = drm_add_edid_modes(connector, edid); + kfree(edid); + + return count; + +err_kfree: + kfree(edid); +err_drm_connector_update_edid_property: + drm_connector_update_edid_property(connector, NULL); + return 0; +} + +static const struct drm_connector_helper_funcs + ast_dp501_connector_helper_funcs = { + .get_modes = ast_dp501_connector_helper_get_modes, + }; + +static const struct drm_connector_funcs ast_dp501_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int ast_dp501_connector_init(struct drm_device *dev, + struct drm_connector *connector) +{ + int ret; + + ret = drm_connector_init(dev, connector, &ast_dp501_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) + return ret; + + drm_connector_helper_add(connector, &ast_dp501_connector_helper_funcs); + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + connector->polled = DRM_CONNECTOR_POLL_CONNECT; + + return 0; +} + +static int ast_dp501_output_init(struct ast_private *ast) +{ + struct drm_device *dev = &ast->base; + struct drm_crtc *crtc = &ast->crtc; + struct drm_encoder *encoder = &ast->output.dp501.encoder; + struct drm_connector *connector = &ast->output.dp501.connector; + int ret; + + ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS); + if (ret) + return ret; + encoder->possible_crtcs = drm_crtc_mask(crtc); + + ret = ast_dp501_connector_init(dev, connector); + if (ret) + return ret; + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ret; + + return 0; +} + +/* + * ASPEED Display-Port Connector + */ + +static int ast_astdp_connector_helper_get_modes(struct drm_connector *connector) +{ + void *edid; + + int succ; + int count; + + edid = kmalloc(EDID_LENGTH, GFP_KERNEL); + if (!edid) + goto err_drm_connector_update_edid_property; + + succ = ast_astdp_read_edid(connector->dev, edid); + if (succ < 0) + goto err_kfree; + + drm_connector_update_edid_property(connector, edid); + count = drm_add_edid_modes(connector, edid); + kfree(edid); + + return count; + +err_kfree: + kfree(edid); +err_drm_connector_update_edid_property: + drm_connector_update_edid_property(connector, NULL); + return 0; +} + +static const struct drm_connector_helper_funcs + ast_astdp_connector_helper_funcs = { + .get_modes = ast_astdp_connector_helper_get_modes, + }; + +static const struct drm_connector_funcs ast_astdp_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int ast_astdp_connector_init(struct drm_device *dev, + struct drm_connector *connector) +{ + int ret; + + ret = drm_connector_init(dev, connector, &ast_astdp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) + return ret; + + drm_connector_helper_add(connector, &ast_astdp_connector_helper_funcs); + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + connector->polled = DRM_CONNECTOR_POLL_CONNECT; + + return 0; +} + +static int ast_astdp_output_init(struct ast_private *ast) +{ + struct drm_device *dev = &ast->base; + struct drm_crtc *crtc = &ast->crtc; + struct drm_encoder *encoder = &ast->output.astdp.encoder; + struct drm_connector *connector = &ast->output.astdp.connector; + int ret; + + ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS); + if (ret) + return ret; + encoder->possible_crtcs = drm_crtc_mask(crtc); + + ret = ast_astdp_connector_init(dev, connector); + if (ret) + return ret; + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ret; + + return 0; +} + +/* + * Mode config + */ + +static void +ast_mode_config_helper_atomic_commit_tail(struct drm_atomic_state *state) +{ + struct ast_private *ast = to_ast_private(state->dev); + + /* + * Concurrent operations could possibly trigger a call to + * drm_connector_helper_funcs.get_modes by trying to read the + * display modes. Protect access to I/O registers by acquiring + * the I/O-register lock. Released in atomic_flush(). + */ + mutex_lock(&ast->ioregs_lock); + drm_atomic_helper_commit_tail_rpm(state); + mutex_unlock(&ast->ioregs_lock); +} + +static const struct drm_mode_config_helper_funcs ast_mode_config_helper_funcs = { + .atomic_commit_tail = ast_mode_config_helper_atomic_commit_tail, +}; + +static const struct drm_mode_config_funcs ast_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .mode_valid = drm_vram_helper_mode_valid, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +int ast_mode_config_init(struct ast_private *ast) +{ + struct drm_device *dev = &ast->base; + int ret; + + ret = drmm_mode_config_init(dev); + if (ret) + return ret; + + dev->mode_config.funcs = &ast_mode_config_funcs; + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.preferred_depth = 24; + dev->mode_config.prefer_shadow = 1; + + if (ast->chip == AST2100 || ast->chip == AST2200 || + ast->chip == AST2300 || ast->chip == AST2400 || + ast->chip == AST2500 || ast->chip == AST2600) { + dev->mode_config.max_width = 1920; + dev->mode_config.max_height = 2048; + } else { + dev->mode_config.max_width = 1600; + dev->mode_config.max_height = 1200; + } + + dev->mode_config.helper_private = &ast_mode_config_helper_funcs; + + ret = ast_primary_plane_init(ast); + if (ret) + return ret; + + ret = ast_cursor_plane_init(ast); + if (ret) + return ret; + + ast_crtc_init(dev); + + if (ast->tx_chip_types & AST_TX_NONE_BIT) { + ret = ast_vga_output_init(ast); + if (ret) + return ret; + } + if (ast->tx_chip_types & AST_TX_SIL164_BIT) { + ret = ast_sil164_output_init(ast); + if (ret) + return ret; + } + if (ast->tx_chip_types & AST_TX_DP501_BIT) { + ret = ast_dp501_output_init(ast); + if (ret) + return ret; + } + if (ast->tx_chip_types & AST_TX_ASTDP_BIT) { + ret = ast_astdp_output_init(ast); + if (ret) + return ret; + } + + drm_mode_config_reset(dev); + + return 0; +} diff --git a/drivers/gpu/drm/loongson/ast_old/ast_post.c b/drivers/gpu/drm/loongson/ast_old/ast_post.c new file mode 100644 index 0000000000000000000000000000000000000000..a7a9c37dfeeed021fd4538fead7e88f036af41e4 --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/ast_post.c @@ -0,0 +1,2090 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: Dave Airlie + */ + +#include +#include + +#include + +#include "ast_dram_tables.h" +#include "ast_drv.h" + +static void ast_post_chip_2300(struct drm_device *dev); +static void ast_post_chip_2500(struct drm_device *dev); + +void ast_enable_vga(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + + ast_io_write8(ast, AST_IO_VGA_ENABLE_PORT, 0x01); + ast_io_write8(ast, AST_IO_MISC_PORT_WRITE, 0x01); +} + +void ast_enable_mmio(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa1, 0x06); +} + +bool ast_is_vga_enabled(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + u8 ch; + + ch = ast_io_read8(ast, AST_IO_VGA_ENABLE_PORT); + + return !!(ch & 0x01); +} + +static const u8 extreginfo[] = { 0x0f, 0x04, 0x1c, 0xff }; +static const u8 extreginfo_ast2300a0[] = { 0x0f, 0x04, 0x1c, 0xff }; +static const u8 extreginfo_ast2300[] = { 0x0f, 0x04, 0x1f, 0xff }; + +static void ast_set_def_ext_reg(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + struct pci_dev *pdev = to_pci_dev(dev->dev); + u8 i, index, reg; + const u8 *ext_reg_info; + + /* reset scratch */ + for (i = 0x81; i <= 0x9f; i++) + ast_set_index_reg(ast, AST_IO_CRTC_PORT, i, 0x00); + + if (ast->chip == AST2300 || ast->chip == AST2400 || + ast->chip == AST2500) { + if (pdev->revision >= 0x20) + ext_reg_info = extreginfo_ast2300; + else + ext_reg_info = extreginfo_ast2300a0; + } else + ext_reg_info = extreginfo; + + index = 0xa0; + while (*ext_reg_info != 0xff) { + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, index, 0x00, + *ext_reg_info); + index++; + ext_reg_info++; + } + + /* disable standard IO/MEM decode if secondary */ + /* ast_set_index_reg-mask(ast, AST_IO_CRTC_PORT, 0xa1, 0xff, 0x3); */ + + /* Set Ext. Default */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x8c, 0x00, 0x01); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb7, 0x00, 0x00); + + /* Enable RAMDAC for A1 */ + reg = 0x04; + if (ast->chip == AST2300 || ast->chip == AST2400 || + ast->chip == AST2500) + reg |= 0x20; + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb6, 0xff, reg); +} + +u32 ast_mindwm(struct ast_private *ast, u32 r) +{ + uint32_t data; + + ast_write32(ast, 0xf004, r & 0xffff0000); + ast_write32(ast, 0xf000, 0x1); + + do { + data = ast_read32(ast, 0xf004) & 0xffff0000; + } while (data != (r & 0xffff0000)); + return ast_read32(ast, 0x10000 + (r & 0x0000ffff)); +} + +void ast_moutdwm(struct ast_private *ast, u32 r, u32 v) +{ + uint32_t data; + + ast_write32(ast, 0xf004, r & 0xffff0000); + ast_write32(ast, 0xf000, 0x1); + do { + data = ast_read32(ast, 0xf004) & 0xffff0000; + } while (data != (r & 0xffff0000)); + ast_write32(ast, 0x10000 + (r & 0x0000ffff), v); +} + +/* + * AST2100/2150 DLL CBR Setting + */ +#define CBR_SIZE_AST2150 ((16 << 10) - 1) +#define CBR_PASSNUM_AST2150 5 +#define CBR_THRESHOLD_AST2150 10 +#define CBR_THRESHOLD2_AST2150 10 +#define TIMEOUT_AST2150 5000000 + +#define CBR_PATNUM_AST2150 8 + +static const u32 pattern_AST2150[14] = { 0xFF00FF00, 0xCC33CC33, 0xAA55AA55, + 0xFFFE0001, 0x683501FE, 0x0F1929B0, + 0x2D0B4346, 0x60767F02, 0x6FBE36A6, + 0x3A253035, 0x3019686D, 0x41C6167E, + 0x620152BF, 0x20F050E0 }; + +static u32 mmctestburst2_ast2150(struct ast_private *ast, u32 datagen) +{ + u32 data, timeout; + + ast_moutdwm(ast, 0x1e6e0070, 0x00000000); + ast_moutdwm(ast, 0x1e6e0070, 0x00000001 | (datagen << 3)); + timeout = 0; + do { + data = ast_mindwm(ast, 0x1e6e0070) & 0x40; + if (++timeout > TIMEOUT_AST2150) { + ast_moutdwm(ast, 0x1e6e0070, 0x00000000); + return 0xffffffff; + } + } while (!data); + ast_moutdwm(ast, 0x1e6e0070, 0x00000000); + ast_moutdwm(ast, 0x1e6e0070, 0x00000003 | (datagen << 3)); + timeout = 0; + do { + data = ast_mindwm(ast, 0x1e6e0070) & 0x40; + if (++timeout > TIMEOUT_AST2150) { + ast_moutdwm(ast, 0x1e6e0070, 0x00000000); + return 0xffffffff; + } + } while (!data); + data = (ast_mindwm(ast, 0x1e6e0070) & 0x80) >> 7; + ast_moutdwm(ast, 0x1e6e0070, 0x00000000); + return data; +} + +static int cbrtest_ast2150(struct ast_private *ast) +{ + int i; + + for (i = 0; i < 8; i++) + if (mmctestburst2_ast2150(ast, i)) + return 0; + return 1; +} + +static int cbrscan_ast2150(struct ast_private *ast, int busw) +{ + u32 patcnt, loop; + + for (patcnt = 0; patcnt < CBR_PATNUM_AST2150; patcnt++) { + ast_moutdwm(ast, 0x1e6e007c, pattern_AST2150[patcnt]); + for (loop = 0; loop < CBR_PASSNUM_AST2150; loop++) { + if (cbrtest_ast2150(ast)) + break; + } + if (loop == CBR_PASSNUM_AST2150) + return 0; + } + return 1; +} + +static void cbrdlli_ast2150(struct ast_private *ast, int busw) +{ + u32 dll_min[4], dll_max[4], dlli, data, passcnt; + +cbr_start: + dll_min[0] = dll_min[1] = dll_min[2] = dll_min[3] = 0xff; + dll_max[0] = dll_max[1] = dll_max[2] = dll_max[3] = 0x0; + passcnt = 0; + + for (dlli = 0; dlli < 100; dlli++) { + ast_moutdwm(ast, 0x1e6e0068, + dlli | (dlli << 8) | (dlli << 16) | (dlli << 24)); + data = cbrscan_ast2150(ast, busw); + if (data != 0) { + if (data & 0x1) { + if (dll_min[0] > dlli) + dll_min[0] = dlli; + if (dll_max[0] < dlli) + dll_max[0] = dlli; + } + passcnt++; + } else if (passcnt >= CBR_THRESHOLD_AST2150) + goto cbr_start; + } + if (dll_max[0] == 0 || + (dll_max[0] - dll_min[0]) < CBR_THRESHOLD_AST2150) + goto cbr_start; + + dlli = dll_min[0] + (((dll_max[0] - dll_min[0]) * 7) >> 4); + ast_moutdwm(ast, 0x1e6e0068, + dlli | (dlli << 8) | (dlli << 16) | (dlli << 24)); +} + +static void ast_init_dram_reg(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + u8 j; + u32 data, temp, i; + const struct ast_dramstruct *dram_reg_info; + + j = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd0, 0xff); + + if ((j & 0x80) == 0) { /* VGA only */ + if (ast->chip == AST2000) { + dram_reg_info = ast2000_dram_table_data; + ast_write32(ast, 0xf004, 0x1e6e0000); + ast_write32(ast, 0xf000, 0x1); + ast_write32(ast, 0x10100, 0xa8); + + do { + ; + } while (ast_read32(ast, 0x10100) != 0xa8); + } else { /* AST2100/1100 */ + if (ast->chip == AST2100 || ast->chip == 2200) + dram_reg_info = ast2100_dram_table_data; + else + dram_reg_info = ast1100_dram_table_data; + + ast_write32(ast, 0xf004, 0x1e6e0000); + ast_write32(ast, 0xf000, 0x1); + ast_write32(ast, 0x12000, 0x1688A8A8); + do { + ; + } while (ast_read32(ast, 0x12000) != 0x01); + + ast_write32(ast, 0x10000, 0xfc600309); + do { + ; + } while (ast_read32(ast, 0x10000) != 0x01); + } + + while (dram_reg_info->index != 0xffff) { + if (dram_reg_info->index == 0xff00) { /* delay fn */ + for (i = 0; i < 15; i++) + udelay(dram_reg_info->data); + } else if (dram_reg_info->index == 0x4 && + ast->chip != AST2000) { + data = dram_reg_info->data; + if (ast->dram_type == AST_DRAM_1Gx16) + data = 0x00000d89; + else if (ast->dram_type == AST_DRAM_1Gx32) + data = 0x00000c8d; + + temp = ast_read32(ast, 0x12070); + temp &= 0xc; + temp <<= 2; + ast_write32(ast, 0x10000 + dram_reg_info->index, + data | temp); + } else + ast_write32(ast, 0x10000 + dram_reg_info->index, + dram_reg_info->data); + dram_reg_info++; + } + + /* AST 2100/2150 DRAM calibration */ + data = ast_read32(ast, 0x10120); + if (data == 0x5061) { /* 266Mhz */ + data = ast_read32(ast, 0x10004); + if (data & 0x40) + cbrdlli_ast2150(ast, 16); /* 16 bits */ + else + cbrdlli_ast2150(ast, 32); /* 32 bits */ + } + + switch (ast->chip) { + case AST2000: + temp = ast_read32(ast, 0x10140); + ast_write32(ast, 0x10140, temp | 0x40); + break; + case AST1100: + case AST2100: + case AST2200: + case AST2150: + temp = ast_read32(ast, 0x1200c); + ast_write32(ast, 0x1200c, temp & 0xfffffffd); + temp = ast_read32(ast, 0x12040); + ast_write32(ast, 0x12040, temp | 0x40); + break; + default: + break; + } + } + + /* wait ready */ + do { + j = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd0, 0xff); + } while ((j & 0x40) == 0); +} + +void ast_post_gpu(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + struct pci_dev *pdev = to_pci_dev(dev->dev); + u32 reg; + + pci_read_config_dword(pdev, 0x04, ®); + reg |= 0x3; + pci_write_config_dword(pdev, 0x04, reg); + + ast_enable_vga(dev); + ast_open_key(ast); + ast_enable_mmio(dev); + ast_set_def_ext_reg(dev); + + if (ast->chip == AST2600) { + ast_dp_launch(dev, 1); + } else if (ast->config_mode == ast_use_p2a) { + if (ast->chip == AST2500) + ast_post_chip_2500(dev); + else if (ast->chip == AST2300 || ast->chip == AST2400) + ast_post_chip_2300(dev); + else + ast_init_dram_reg(dev); + + ast_init_3rdtx(dev); + } else { + if (ast->tx_chip_types & AST_TX_SIL164_BIT) + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa3, + 0xcf, 0x80); /* Enable DVO */ + } +} + +/* AST 2300 DRAM settings */ +#define AST_DDR3 0 +#define AST_DDR2 1 + +struct ast2300_dram_param { + u32 dram_type; + u32 dram_chipid; + u32 dram_freq; + u32 vram_size; + u32 odt; + u32 wodt; + u32 rodt; + u32 dram_config; + u32 reg_PERIOD; + u32 reg_MADJ; + u32 reg_SADJ; + u32 reg_MRS; + u32 reg_EMRS; + u32 reg_AC1; + u32 reg_AC2; + u32 reg_DQSIC; + u32 reg_DRV; + u32 reg_IOZ; + u32 reg_DQIDLY; + u32 reg_FREQ; + u32 madj_max; + u32 dll2_finetune_step; +}; + +/* + * DQSI DLL CBR Setting + */ +#define CBR_SIZE0 ((1 << 10) - 1) +#define CBR_SIZE1 ((4 << 10) - 1) +#define CBR_SIZE2 ((64 << 10) - 1) +#define CBR_PASSNUM 5 +#define CBR_PASSNUM2 5 +#define CBR_THRESHOLD 10 +#define CBR_THRESHOLD2 10 +#define TIMEOUT 5000000 +#define CBR_PATNUM 8 + +static const u32 pattern[8] = { 0xFF00FF00, 0xCC33CC33, 0xAA55AA55, 0x88778877, + 0x92CC4D6E, 0x543D3CDE, 0xF1E843C7, 0x7C61D253 }; + +static bool mmc_test(struct ast_private *ast, u32 datagen, u8 test_ctl) +{ + u32 data, timeout; + + ast_moutdwm(ast, 0x1e6e0070, 0x00000000); + ast_moutdwm(ast, 0x1e6e0070, (datagen << 3) | test_ctl); + timeout = 0; + do { + data = ast_mindwm(ast, 0x1e6e0070) & 0x3000; + if (data & 0x2000) + return false; + if (++timeout > TIMEOUT) { + ast_moutdwm(ast, 0x1e6e0070, 0x00000000); + return false; + } + } while (!data); + ast_moutdwm(ast, 0x1e6e0070, 0x0); + return true; +} + +static u32 mmc_test2(struct ast_private *ast, u32 datagen, u8 test_ctl) +{ + u32 data, timeout; + + ast_moutdwm(ast, 0x1e6e0070, 0x00000000); + ast_moutdwm(ast, 0x1e6e0070, (datagen << 3) | test_ctl); + timeout = 0; + do { + data = ast_mindwm(ast, 0x1e6e0070) & 0x1000; + if (++timeout > TIMEOUT) { + ast_moutdwm(ast, 0x1e6e0070, 0x0); + return 0xffffffff; + } + } while (!data); + data = ast_mindwm(ast, 0x1e6e0078); + data = (data | (data >> 16)) & 0xffff; + ast_moutdwm(ast, 0x1e6e0070, 0x00000000); + return data; +} + +static bool mmc_test_burst(struct ast_private *ast, u32 datagen) +{ + return mmc_test(ast, datagen, 0xc1); +} + +static u32 mmc_test_burst2(struct ast_private *ast, u32 datagen) +{ + return mmc_test2(ast, datagen, 0x41); +} + +static bool mmc_test_single(struct ast_private *ast, u32 datagen) +{ + return mmc_test(ast, datagen, 0xc5); +} + +static u32 mmc_test_single2(struct ast_private *ast, u32 datagen) +{ + return mmc_test2(ast, datagen, 0x05); +} + +static bool mmc_test_single_2500(struct ast_private *ast, u32 datagen) +{ + return mmc_test(ast, datagen, 0x85); +} + +static int cbr_test(struct ast_private *ast) +{ + u32 data; + int i; + + data = mmc_test_single2(ast, 0); + if ((data & 0xff) && (data & 0xff00)) + return 0; + for (i = 0; i < 8; i++) { + data = mmc_test_burst2(ast, i); + if ((data & 0xff) && (data & 0xff00)) + return 0; + } + if (!data) + return 3; + else if (data & 0xff) + return 2; + return 1; +} + +static int cbr_scan(struct ast_private *ast) +{ + u32 data, data2, patcnt, loop; + + data2 = 3; + for (patcnt = 0; patcnt < CBR_PATNUM; patcnt++) { + ast_moutdwm(ast, 0x1e6e007c, pattern[patcnt]); + for (loop = 0; loop < CBR_PASSNUM2; loop++) { + data = cbr_test(ast); + if (data != 0) { + data2 &= data; + if (!data2) + return 0; + break; + } + } + if (loop == CBR_PASSNUM2) + return 0; + } + return data2; +} + +static u32 cbr_test2(struct ast_private *ast) +{ + u32 data; + + data = mmc_test_burst2(ast, 0); + if (data == 0xffff) + return 0; + data |= mmc_test_single2(ast, 0); + if (data == 0xffff) + return 0; + + return ~data & 0xffff; +} + +static u32 cbr_scan2(struct ast_private *ast) +{ + u32 data, data2, patcnt, loop; + + data2 = 0xffff; + for (patcnt = 0; patcnt < CBR_PATNUM; patcnt++) { + ast_moutdwm(ast, 0x1e6e007c, pattern[patcnt]); + for (loop = 0; loop < CBR_PASSNUM2; loop++) { + data = cbr_test2(ast); + if (data != 0) { + data2 &= data; + if (!data2) + return 0; + break; + } + } + if (loop == CBR_PASSNUM2) + return 0; + } + return data2; +} + +static bool cbr_test3(struct ast_private *ast) +{ + if (!mmc_test_burst(ast, 0)) + return false; + if (!mmc_test_single(ast, 0)) + return false; + return true; +} + +static bool cbr_scan3(struct ast_private *ast) +{ + u32 patcnt, loop; + + for (patcnt = 0; patcnt < CBR_PATNUM; patcnt++) { + ast_moutdwm(ast, 0x1e6e007c, pattern[patcnt]); + for (loop = 0; loop < 2; loop++) { + if (cbr_test3(ast)) + break; + } + if (loop == 2) + return false; + } + return true; +} + +static bool finetuneDQI_L(struct ast_private *ast, + struct ast2300_dram_param *param) +{ + u32 gold_sadj[2], dllmin[16], dllmax[16], dlli, data, cnt, mask, + passcnt, retry = 0; + bool status = false; +FINETUNE_START: + for (cnt = 0; cnt < 16; cnt++) { + dllmin[cnt] = 0xff; + dllmax[cnt] = 0x0; + } + passcnt = 0; + for (dlli = 0; dlli < 76; dlli++) { + ast_moutdwm(ast, 0x1E6E0068, + 0x00001400 | (dlli << 16) | (dlli << 24)); + ast_moutdwm(ast, 0x1E6E0074, CBR_SIZE1); + data = cbr_scan2(ast); + if (data != 0) { + mask = 0x00010001; + for (cnt = 0; cnt < 16; cnt++) { + if (data & mask) { + if (dllmin[cnt] > dlli) + dllmin[cnt] = dlli; + if (dllmax[cnt] < dlli) + dllmax[cnt] = dlli; + } + mask <<= 1; + } + passcnt++; + } else if (passcnt >= CBR_THRESHOLD2) { + break; + } + } + gold_sadj[0] = 0x0; + passcnt = 0; + for (cnt = 0; cnt < 16; cnt++) { + if ((dllmax[cnt] > dllmin[cnt]) && + ((dllmax[cnt] - dllmin[cnt]) >= CBR_THRESHOLD2)) { + gold_sadj[0] += dllmin[cnt]; + passcnt++; + } + } + if (retry++ > 10) + goto FINETUNE_DONE; + if (passcnt != 16) + goto FINETUNE_START; + status = true; +FINETUNE_DONE: + gold_sadj[0] = gold_sadj[0] >> 4; + gold_sadj[1] = gold_sadj[0]; + + data = 0; + for (cnt = 0; cnt < 8; cnt++) { + data >>= 3; + if ((dllmax[cnt] > dllmin[cnt]) && + ((dllmax[cnt] - dllmin[cnt]) >= CBR_THRESHOLD2)) { + dlli = dllmin[cnt]; + if (gold_sadj[0] >= dlli) { + dlli = ((gold_sadj[0] - dlli) * 19) >> 5; + if (dlli > 3) + dlli = 3; + } else { + dlli = ((dlli - gold_sadj[0]) * 19) >> 5; + if (dlli > 4) + dlli = 4; + dlli = (8 - dlli) & 0x7; + } + data |= dlli << 21; + } + } + ast_moutdwm(ast, 0x1E6E0080, data); + + data = 0; + for (cnt = 8; cnt < 16; cnt++) { + data >>= 3; + if ((dllmax[cnt] > dllmin[cnt]) && + ((dllmax[cnt] - dllmin[cnt]) >= CBR_THRESHOLD2)) { + dlli = dllmin[cnt]; + if (gold_sadj[1] >= dlli) { + dlli = ((gold_sadj[1] - dlli) * 19) >> 5; + if (dlli > 3) + dlli = 3; + else + dlli = (dlli - 1) & 0x7; + } else { + dlli = ((dlli - gold_sadj[1]) * 19) >> 5; + dlli += 1; + if (dlli > 4) + dlli = 4; + dlli = (8 - dlli) & 0x7; + } + data |= dlli << 21; + } + } + ast_moutdwm(ast, 0x1E6E0084, data); + return status; +} /* finetuneDQI_L */ + +static void finetuneDQSI(struct ast_private *ast) +{ + u32 dlli, dqsip, dqidly; + u32 reg_mcr18, reg_mcr0c, passcnt[2], diff; + u32 g_dqidly, g_dqsip, g_margin, g_side; + u16 pass[32][2][2]; + char tag[2][76]; + + /* Disable DQI CBR */ + reg_mcr0c = ast_mindwm(ast, 0x1E6E000C); + reg_mcr18 = ast_mindwm(ast, 0x1E6E0018); + reg_mcr18 &= 0x0000ffff; + ast_moutdwm(ast, 0x1E6E0018, reg_mcr18); + + for (dlli = 0; dlli < 76; dlli++) { + tag[0][dlli] = 0x0; + tag[1][dlli] = 0x0; + } + for (dqidly = 0; dqidly < 32; dqidly++) { + pass[dqidly][0][0] = 0xff; + pass[dqidly][0][1] = 0x0; + pass[dqidly][1][0] = 0xff; + pass[dqidly][1][1] = 0x0; + } + for (dqidly = 0; dqidly < 32; dqidly++) { + passcnt[0] = passcnt[1] = 0; + for (dqsip = 0; dqsip < 2; dqsip++) { + ast_moutdwm(ast, 0x1E6E000C, 0); + ast_moutdwm(ast, 0x1E6E0018, + reg_mcr18 | (dqidly << 16) | (dqsip << 23)); + ast_moutdwm(ast, 0x1E6E000C, reg_mcr0c); + for (dlli = 0; dlli < 76; dlli++) { + ast_moutdwm(ast, 0x1E6E0068, + 0x00001300 | (dlli << 16) | + (dlli << 24)); + ast_moutdwm(ast, 0x1E6E0070, 0); + ast_moutdwm(ast, 0x1E6E0074, CBR_SIZE0); + if (cbr_scan3(ast)) { + if (dlli == 0) + break; + passcnt[dqsip]++; + tag[dqsip][dlli] = 'P'; + if (dlli < pass[dqidly][dqsip][0]) + pass[dqidly][dqsip][0] = + (u16)dlli; + if (dlli > pass[dqidly][dqsip][1]) + pass[dqidly][dqsip][1] = + (u16)dlli; + } + if (passcnt[dqsip] >= 5) + break; + if (!cbr_scan3(ast)) { + pass[dqidly][dqsip][0] = 0xff; + pass[dqidly][dqsip][1] = 0x0; + } + } + } + if (passcnt[0] == 0 && passcnt[1] == 0) + dqidly++; + } + /* Search margin */ + g_dqidly = g_dqsip = g_margin = g_side = 0; + + for (dqidly = 0; dqidly < 32; dqidly++) { + for (dqsip = 0; dqsip < 2; dqsip++) { + if (pass[dqidly][dqsip][0] > pass[dqidly][dqsip][1]) + continue; + diff = pass[dqidly][dqsip][1] - pass[dqidly][dqsip][0]; + if ((diff + 2) < g_margin) + continue; + passcnt[0] = passcnt[1] = 0; + for (dlli = pass[dqidly][dqsip][0]; + dlli > 0 && tag[dqsip][dlli] != 0; + dlli--, passcnt[0]++) + ; + for (dlli = pass[dqidly][dqsip][1]; + dlli < 76 && tag[dqsip][dlli] != 0; + dlli++, passcnt[1]++) + ; + if (passcnt[0] > passcnt[1]) + passcnt[0] = passcnt[1]; + passcnt[1] = 0; + if (passcnt[0] > g_side) + passcnt[1] = passcnt[0] - g_side; + if (diff > (g_margin + 1) && + (passcnt[1] > 0 || passcnt[0] > 8)) { + g_margin = diff; + g_dqidly = dqidly; + g_dqsip = dqsip; + g_side = passcnt[0]; + } else if (passcnt[1] > 1 && g_side < 8) { + if (diff > g_margin) + g_margin = diff; + g_dqidly = dqidly; + g_dqsip = dqsip; + g_side = passcnt[0]; + } + } + } + reg_mcr18 = reg_mcr18 | (g_dqidly << 16) | (g_dqsip << 23); + ast_moutdwm(ast, 0x1E6E0018, reg_mcr18); +} +static bool cbr_dll2(struct ast_private *ast, struct ast2300_dram_param *param) +{ + u32 dllmin[2], dllmax[2], dlli, data, passcnt, retry = 0; + bool status = false; + + finetuneDQSI(ast); + if (finetuneDQI_L(ast, param) == false) + return status; + +CBR_START2: + dllmin[0] = dllmin[1] = 0xff; + dllmax[0] = dllmax[1] = 0x0; + passcnt = 0; + for (dlli = 0; dlli < 76; dlli++) { + ast_moutdwm(ast, 0x1E6E0068, + 0x00001300 | (dlli << 16) | (dlli << 24)); + ast_moutdwm(ast, 0x1E6E0074, CBR_SIZE2); + data = cbr_scan(ast); + if (data != 0) { + if (data & 0x1) { + if (dllmin[0] > dlli) + dllmin[0] = dlli; + if (dllmax[0] < dlli) + dllmax[0] = dlli; + } + if (data & 0x2) { + if (dllmin[1] > dlli) + dllmin[1] = dlli; + if (dllmax[1] < dlli) + dllmax[1] = dlli; + } + passcnt++; + } else if (passcnt >= CBR_THRESHOLD) { + break; + } + } + if (retry++ > 10) + goto CBR_DONE2; + if (dllmax[0] == 0 || (dllmax[0] - dllmin[0]) < CBR_THRESHOLD) + goto CBR_START2; + if (dllmax[1] == 0 || (dllmax[1] - dllmin[1]) < CBR_THRESHOLD) + goto CBR_START2; + status = true; +CBR_DONE2: + dlli = (dllmin[1] + dllmax[1]) >> 1; + dlli <<= 8; + dlli += (dllmin[0] + dllmax[0]) >> 1; + ast_moutdwm(ast, 0x1E6E0068, + ast_mindwm(ast, 0x1E720058) | (dlli << 16)); + return status; +} /* CBRDLL2 */ + +static void get_ddr3_info(struct ast_private *ast, + struct ast2300_dram_param *param) +{ + u32 trap, trap_AC2, trap_MRS; + + ast_moutdwm(ast, 0x1E6E2000, 0x1688A8A8); + + /* Ger trap info */ + trap = (ast_mindwm(ast, 0x1E6E2070) >> 25) & 0x3; + trap_AC2 = 0x00020000 + (trap << 16); + trap_AC2 |= 0x00300000 + ((trap & 0x2) << 19); + trap_MRS = 0x00000010 + (trap << 4); + trap_MRS |= ((trap & 0x2) << 18); + + param->reg_MADJ = 0x00034C4C; + param->reg_SADJ = 0x00001800; + param->reg_DRV = 0x000000F0; + param->reg_PERIOD = param->dram_freq; + param->rodt = 0; + + switch (param->dram_freq) { + case 336: + ast_moutdwm(ast, 0x1E6E2020, 0x0190); + param->wodt = 0; + param->reg_AC1 = 0x22202725; + param->reg_AC2 = 0xAA007613 | trap_AC2; + param->reg_DQSIC = 0x000000BA; + param->reg_MRS = 0x04001400 | trap_MRS; + param->reg_EMRS = 0x00000000; + param->reg_IOZ = 0x00000023; + param->reg_DQIDLY = 0x00000074; + param->reg_FREQ = 0x00004DC0; + param->madj_max = 96; + param->dll2_finetune_step = 3; + switch (param->dram_chipid) { + default: + case AST_DRAM_512Mx16: + case AST_DRAM_1Gx16: + param->reg_AC2 = 0xAA007613 | trap_AC2; + break; + case AST_DRAM_2Gx16: + param->reg_AC2 = 0xAA00761C | trap_AC2; + break; + case AST_DRAM_4Gx16: + param->reg_AC2 = 0xAA007636 | trap_AC2; + break; + } + break; + default: + case 396: + ast_moutdwm(ast, 0x1E6E2020, 0x03F1); + param->wodt = 1; + param->reg_AC1 = 0x33302825; + param->reg_AC2 = 0xCC009617 | trap_AC2; + param->reg_DQSIC = 0x000000E2; + param->reg_MRS = 0x04001600 | trap_MRS; + param->reg_EMRS = 0x00000000; + param->reg_IOZ = 0x00000034; + param->reg_DRV = 0x000000FA; + param->reg_DQIDLY = 0x00000089; + param->reg_FREQ = 0x00005040; + param->madj_max = 96; + param->dll2_finetune_step = 4; + + switch (param->dram_chipid) { + default: + case AST_DRAM_512Mx16: + case AST_DRAM_1Gx16: + param->reg_AC2 = 0xCC009617 | trap_AC2; + break; + case AST_DRAM_2Gx16: + param->reg_AC2 = 0xCC009622 | trap_AC2; + break; + case AST_DRAM_4Gx16: + param->reg_AC2 = 0xCC00963F | trap_AC2; + break; + } + break; + + case 408: + ast_moutdwm(ast, 0x1E6E2020, 0x01F0); + param->wodt = 1; + param->reg_AC1 = 0x33302825; + param->reg_AC2 = 0xCC009617 | trap_AC2; + param->reg_DQSIC = 0x000000E2; + param->reg_MRS = 0x04001600 | trap_MRS; + param->reg_EMRS = 0x00000000; + param->reg_IOZ = 0x00000023; + param->reg_DRV = 0x000000FA; + param->reg_DQIDLY = 0x00000089; + param->reg_FREQ = 0x000050C0; + param->madj_max = 96; + param->dll2_finetune_step = 4; + + switch (param->dram_chipid) { + default: + case AST_DRAM_512Mx16: + case AST_DRAM_1Gx16: + param->reg_AC2 = 0xCC009617 | trap_AC2; + break; + case AST_DRAM_2Gx16: + param->reg_AC2 = 0xCC009622 | trap_AC2; + break; + case AST_DRAM_4Gx16: + param->reg_AC2 = 0xCC00963F | trap_AC2; + break; + } + + break; + case 456: + ast_moutdwm(ast, 0x1E6E2020, 0x0230); + param->wodt = 0; + param->reg_AC1 = 0x33302926; + param->reg_AC2 = 0xCD44961A; + param->reg_DQSIC = 0x000000FC; + param->reg_MRS = 0x00081830; + param->reg_EMRS = 0x00000000; + param->reg_IOZ = 0x00000045; + param->reg_DQIDLY = 0x00000097; + param->reg_FREQ = 0x000052C0; + param->madj_max = 88; + param->dll2_finetune_step = 4; + break; + case 504: + ast_moutdwm(ast, 0x1E6E2020, 0x0270); + param->wodt = 1; + param->reg_AC1 = 0x33302926; + param->reg_AC2 = 0xDE44A61D; + param->reg_DQSIC = 0x00000117; + param->reg_MRS = 0x00081A30; + param->reg_EMRS = 0x00000000; + param->reg_IOZ = 0x070000BB; + param->reg_DQIDLY = 0x000000A0; + param->reg_FREQ = 0x000054C0; + param->madj_max = 79; + param->dll2_finetune_step = 4; + break; + case 528: + ast_moutdwm(ast, 0x1E6E2020, 0x0290); + param->wodt = 1; + param->rodt = 1; + param->reg_AC1 = 0x33302926; + param->reg_AC2 = 0xEF44B61E; + param->reg_DQSIC = 0x00000125; + param->reg_MRS = 0x00081A30; + param->reg_EMRS = 0x00000040; + param->reg_DRV = 0x000000F5; + param->reg_IOZ = 0x00000023; + param->reg_DQIDLY = 0x00000088; + param->reg_FREQ = 0x000055C0; + param->madj_max = 76; + param->dll2_finetune_step = 3; + break; + case 576: + ast_moutdwm(ast, 0x1E6E2020, 0x0140); + param->reg_MADJ = 0x00136868; + param->reg_SADJ = 0x00004534; + param->wodt = 1; + param->rodt = 1; + param->reg_AC1 = 0x33302A37; + param->reg_AC2 = 0xEF56B61E; + param->reg_DQSIC = 0x0000013F; + param->reg_MRS = 0x00101A50; + param->reg_EMRS = 0x00000040; + param->reg_DRV = 0x000000FA; + param->reg_IOZ = 0x00000023; + param->reg_DQIDLY = 0x00000078; + param->reg_FREQ = 0x000057C0; + param->madj_max = 136; + param->dll2_finetune_step = 3; + break; + case 600: + ast_moutdwm(ast, 0x1E6E2020, 0x02E1); + param->reg_MADJ = 0x00136868; + param->reg_SADJ = 0x00004534; + param->wodt = 1; + param->rodt = 1; + param->reg_AC1 = 0x32302A37; + param->reg_AC2 = 0xDF56B61F; + param->reg_DQSIC = 0x0000014D; + param->reg_MRS = 0x00101A50; + param->reg_EMRS = 0x00000004; + param->reg_DRV = 0x000000F5; + param->reg_IOZ = 0x00000023; + param->reg_DQIDLY = 0x00000078; + param->reg_FREQ = 0x000058C0; + param->madj_max = 132; + param->dll2_finetune_step = 3; + break; + case 624: + ast_moutdwm(ast, 0x1E6E2020, 0x0160); + param->reg_MADJ = 0x00136868; + param->reg_SADJ = 0x00004534; + param->wodt = 1; + param->rodt = 1; + param->reg_AC1 = 0x32302A37; + param->reg_AC2 = 0xEF56B621; + param->reg_DQSIC = 0x0000015A; + param->reg_MRS = 0x02101A50; + param->reg_EMRS = 0x00000004; + param->reg_DRV = 0x000000F5; + param->reg_IOZ = 0x00000034; + param->reg_DQIDLY = 0x00000078; + param->reg_FREQ = 0x000059C0; + param->madj_max = 128; + param->dll2_finetune_step = 3; + break; + } /* switch freq */ + + switch (param->dram_chipid) { + case AST_DRAM_512Mx16: + param->dram_config = 0x130; + break; + default: + case AST_DRAM_1Gx16: + param->dram_config = 0x131; + break; + case AST_DRAM_2Gx16: + param->dram_config = 0x132; + break; + case AST_DRAM_4Gx16: + param->dram_config = 0x133; + break; + } /* switch size */ + + switch (param->vram_size) { + default: + case AST_VIDMEM_SIZE_8M: + param->dram_config |= 0x00; + break; + case AST_VIDMEM_SIZE_16M: + param->dram_config |= 0x04; + break; + case AST_VIDMEM_SIZE_32M: + param->dram_config |= 0x08; + break; + case AST_VIDMEM_SIZE_64M: + param->dram_config |= 0x0c; + break; + } +} + +static void ddr3_init(struct ast_private *ast, struct ast2300_dram_param *param) +{ + u32 data, data2, retry = 0; + +ddr3_init_start: + ast_moutdwm(ast, 0x1E6E0000, 0xFC600309); + ast_moutdwm(ast, 0x1E6E0018, 0x00000100); + ast_moutdwm(ast, 0x1E6E0024, 0x00000000); + ast_moutdwm(ast, 0x1E6E0034, 0x00000000); + udelay(10); + ast_moutdwm(ast, 0x1E6E0064, param->reg_MADJ); + ast_moutdwm(ast, 0x1E6E0068, param->reg_SADJ); + udelay(10); + ast_moutdwm(ast, 0x1E6E0064, param->reg_MADJ | 0xC0000); + udelay(10); + + ast_moutdwm(ast, 0x1E6E0004, param->dram_config); + ast_moutdwm(ast, 0x1E6E0008, 0x90040f); + ast_moutdwm(ast, 0x1E6E0010, param->reg_AC1); + ast_moutdwm(ast, 0x1E6E0014, param->reg_AC2); + ast_moutdwm(ast, 0x1E6E0020, param->reg_DQSIC); + ast_moutdwm(ast, 0x1E6E0080, 0x00000000); + ast_moutdwm(ast, 0x1E6E0084, 0x00000000); + ast_moutdwm(ast, 0x1E6E0088, param->reg_DQIDLY); + ast_moutdwm(ast, 0x1E6E0018, 0x4000A170); + ast_moutdwm(ast, 0x1E6E0018, 0x00002370); + ast_moutdwm(ast, 0x1E6E0038, 0x00000000); + ast_moutdwm(ast, 0x1E6E0040, 0xFF444444); + ast_moutdwm(ast, 0x1E6E0044, 0x22222222); + ast_moutdwm(ast, 0x1E6E0048, 0x22222222); + ast_moutdwm(ast, 0x1E6E004C, 0x00000002); + ast_moutdwm(ast, 0x1E6E0050, 0x80000000); + ast_moutdwm(ast, 0x1E6E0050, 0x00000000); + ast_moutdwm(ast, 0x1E6E0054, 0); + ast_moutdwm(ast, 0x1E6E0060, param->reg_DRV); + ast_moutdwm(ast, 0x1E6E006C, param->reg_IOZ); + ast_moutdwm(ast, 0x1E6E0070, 0x00000000); + ast_moutdwm(ast, 0x1E6E0074, 0x00000000); + ast_moutdwm(ast, 0x1E6E0078, 0x00000000); + ast_moutdwm(ast, 0x1E6E007C, 0x00000000); + /* Wait MCLK2X lock to MCLK */ + do { + data = ast_mindwm(ast, 0x1E6E001C); + } while (!(data & 0x08000000)); + data = ast_mindwm(ast, 0x1E6E001C); + data = (data >> 8) & 0xff; + while ((data & 0x08) || ((data & 0x7) < 2) || (data < 4)) { + data2 = (ast_mindwm(ast, 0x1E6E0064) & 0xfff3ffff) + 4; + if ((data2 & 0xff) > param->madj_max) + break; + ast_moutdwm(ast, 0x1E6E0064, data2); + if (data2 & 0x00100000) + data2 = ((data2 & 0xff) >> 3) + 3; + else + data2 = ((data2 & 0xff) >> 2) + 5; + data = ast_mindwm(ast, 0x1E6E0068) & 0xffff00ff; + data2 += data & 0xff; + data = data | (data2 << 8); + ast_moutdwm(ast, 0x1E6E0068, data); + udelay(10); + ast_moutdwm(ast, 0x1E6E0064, + ast_mindwm(ast, 0x1E6E0064) | 0xC0000); + udelay(10); + data = ast_mindwm(ast, 0x1E6E0018) & 0xfffff1ff; + ast_moutdwm(ast, 0x1E6E0018, data); + data = data | 0x200; + ast_moutdwm(ast, 0x1E6E0018, data); + do { + data = ast_mindwm(ast, 0x1E6E001C); + } while (!(data & 0x08000000)); + + data = ast_mindwm(ast, 0x1E6E001C); + data = (data >> 8) & 0xff; + } + ast_moutdwm(ast, 0x1E720058, ast_mindwm(ast, 0x1E6E0068) & 0xffff); + data = ast_mindwm(ast, 0x1E6E0018) | 0xC00; + ast_moutdwm(ast, 0x1E6E0018, data); + + ast_moutdwm(ast, 0x1E6E0034, 0x00000001); + ast_moutdwm(ast, 0x1E6E000C, 0x00000040); + udelay(50); + /* Mode Register Setting */ + ast_moutdwm(ast, 0x1E6E002C, param->reg_MRS | 0x100); + ast_moutdwm(ast, 0x1E6E0030, param->reg_EMRS); + ast_moutdwm(ast, 0x1E6E0028, 0x00000005); + ast_moutdwm(ast, 0x1E6E0028, 0x00000007); + ast_moutdwm(ast, 0x1E6E0028, 0x00000003); + ast_moutdwm(ast, 0x1E6E0028, 0x00000001); + ast_moutdwm(ast, 0x1E6E002C, param->reg_MRS); + ast_moutdwm(ast, 0x1E6E000C, 0x00005C08); + ast_moutdwm(ast, 0x1E6E0028, 0x00000001); + + ast_moutdwm(ast, 0x1E6E000C, 0x00005C01); + data = 0; + if (param->wodt) + data = 0x300; + if (param->rodt) + data = data | 0x3000 | ((param->reg_AC2 & 0x60000) >> 3); + ast_moutdwm(ast, 0x1E6E0034, data | 0x3); + + /* Calibrate the DQSI delay */ + if ((cbr_dll2(ast, param) == false) && (retry++ < 10)) + goto ddr3_init_start; + + ast_moutdwm(ast, 0x1E6E0120, param->reg_FREQ); + /* ECC Memory Initialization */ +#ifdef ECC + ast_moutdwm(ast, 0x1E6E007C, 0x00000000); + ast_moutdwm(ast, 0x1E6E0070, 0x221); + do { + data = ast_mindwm(ast, 0x1E6E0070); + } while (!(data & 0x00001000)); + ast_moutdwm(ast, 0x1E6E0070, 0x00000000); + ast_moutdwm(ast, 0x1E6E0050, 0x80000000); + ast_moutdwm(ast, 0x1E6E0050, 0x00000000); +#endif +} + +static void get_ddr2_info(struct ast_private *ast, + struct ast2300_dram_param *param) +{ + u32 trap, trap_AC2, trap_MRS; + + ast_moutdwm(ast, 0x1E6E2000, 0x1688A8A8); + + /* Ger trap info */ + trap = (ast_mindwm(ast, 0x1E6E2070) >> 25) & 0x3; + trap_AC2 = (trap << 20) | (trap << 16); + trap_AC2 += 0x00110000; + trap_MRS = 0x00000040 | (trap << 4); + + param->reg_MADJ = 0x00034C4C; + param->reg_SADJ = 0x00001800; + param->reg_DRV = 0x000000F0; + param->reg_PERIOD = param->dram_freq; + param->rodt = 0; + + switch (param->dram_freq) { + case 264: + ast_moutdwm(ast, 0x1E6E2020, 0x0130); + param->wodt = 0; + param->reg_AC1 = 0x11101513; + param->reg_AC2 = 0x78117011; + param->reg_DQSIC = 0x00000092; + param->reg_MRS = 0x00000842; + param->reg_EMRS = 0x00000000; + param->reg_DRV = 0x000000F0; + param->reg_IOZ = 0x00000034; + param->reg_DQIDLY = 0x0000005A; + param->reg_FREQ = 0x00004AC0; + param->madj_max = 138; + param->dll2_finetune_step = 3; + break; + case 336: + ast_moutdwm(ast, 0x1E6E2020, 0x0190); + param->wodt = 1; + param->reg_AC1 = 0x22202613; + param->reg_AC2 = 0xAA009016 | trap_AC2; + param->reg_DQSIC = 0x000000BA; + param->reg_MRS = 0x00000A02 | trap_MRS; + param->reg_EMRS = 0x00000040; + param->reg_DRV = 0x000000FA; + param->reg_IOZ = 0x00000034; + param->reg_DQIDLY = 0x00000074; + param->reg_FREQ = 0x00004DC0; + param->madj_max = 96; + param->dll2_finetune_step = 3; + switch (param->dram_chipid) { + default: + case AST_DRAM_512Mx16: + param->reg_AC2 = 0xAA009012 | trap_AC2; + break; + case AST_DRAM_1Gx16: + param->reg_AC2 = 0xAA009016 | trap_AC2; + break; + case AST_DRAM_2Gx16: + param->reg_AC2 = 0xAA009023 | trap_AC2; + break; + case AST_DRAM_4Gx16: + param->reg_AC2 = 0xAA00903B | trap_AC2; + break; + } + break; + default: + case 396: + ast_moutdwm(ast, 0x1E6E2020, 0x03F1); + param->wodt = 1; + param->rodt = 0; + param->reg_AC1 = 0x33302714; + param->reg_AC2 = 0xCC00B01B | trap_AC2; + param->reg_DQSIC = 0x000000E2; + param->reg_MRS = 0x00000C02 | trap_MRS; + param->reg_EMRS = 0x00000040; + param->reg_DRV = 0x000000FA; + param->reg_IOZ = 0x00000034; + param->reg_DQIDLY = 0x00000089; + param->reg_FREQ = 0x00005040; + param->madj_max = 96; + param->dll2_finetune_step = 4; + + switch (param->dram_chipid) { + case AST_DRAM_512Mx16: + param->reg_AC2 = 0xCC00B016 | trap_AC2; + break; + default: + case AST_DRAM_1Gx16: + param->reg_AC2 = 0xCC00B01B | trap_AC2; + break; + case AST_DRAM_2Gx16: + param->reg_AC2 = 0xCC00B02B | trap_AC2; + break; + case AST_DRAM_4Gx16: + param->reg_AC2 = 0xCC00B03F | trap_AC2; + break; + } + + break; + + case 408: + ast_moutdwm(ast, 0x1E6E2020, 0x01F0); + param->wodt = 1; + param->rodt = 0; + param->reg_AC1 = 0x33302714; + param->reg_AC2 = 0xCC00B01B | trap_AC2; + param->reg_DQSIC = 0x000000E2; + param->reg_MRS = 0x00000C02 | trap_MRS; + param->reg_EMRS = 0x00000040; + param->reg_DRV = 0x000000FA; + param->reg_IOZ = 0x00000034; + param->reg_DQIDLY = 0x00000089; + param->reg_FREQ = 0x000050C0; + param->madj_max = 96; + param->dll2_finetune_step = 4; + + switch (param->dram_chipid) { + case AST_DRAM_512Mx16: + param->reg_AC2 = 0xCC00B016 | trap_AC2; + break; + default: + case AST_DRAM_1Gx16: + param->reg_AC2 = 0xCC00B01B | trap_AC2; + break; + case AST_DRAM_2Gx16: + param->reg_AC2 = 0xCC00B02B | trap_AC2; + break; + case AST_DRAM_4Gx16: + param->reg_AC2 = 0xCC00B03F | trap_AC2; + break; + } + + break; + case 456: + ast_moutdwm(ast, 0x1E6E2020, 0x0230); + param->wodt = 0; + param->reg_AC1 = 0x33302815; + param->reg_AC2 = 0xCD44B01E; + param->reg_DQSIC = 0x000000FC; + param->reg_MRS = 0x00000E72; + param->reg_EMRS = 0x00000000; + param->reg_DRV = 0x00000000; + param->reg_IOZ = 0x00000034; + param->reg_DQIDLY = 0x00000097; + param->reg_FREQ = 0x000052C0; + param->madj_max = 88; + param->dll2_finetune_step = 3; + break; + case 504: + ast_moutdwm(ast, 0x1E6E2020, 0x0261); + param->wodt = 1; + param->rodt = 1; + param->reg_AC1 = 0x33302815; + param->reg_AC2 = 0xDE44C022; + param->reg_DQSIC = 0x00000117; + param->reg_MRS = 0x00000E72; + param->reg_EMRS = 0x00000040; + param->reg_DRV = 0x0000000A; + param->reg_IOZ = 0x00000045; + param->reg_DQIDLY = 0x000000A0; + param->reg_FREQ = 0x000054C0; + param->madj_max = 79; + param->dll2_finetune_step = 3; + break; + case 528: + ast_moutdwm(ast, 0x1E6E2020, 0x0120); + param->wodt = 1; + param->rodt = 1; + param->reg_AC1 = 0x33302815; + param->reg_AC2 = 0xEF44D024; + param->reg_DQSIC = 0x00000125; + param->reg_MRS = 0x00000E72; + param->reg_EMRS = 0x00000004; + param->reg_DRV = 0x000000F9; + param->reg_IOZ = 0x00000045; + param->reg_DQIDLY = 0x000000A7; + param->reg_FREQ = 0x000055C0; + param->madj_max = 76; + param->dll2_finetune_step = 3; + break; + case 552: + ast_moutdwm(ast, 0x1E6E2020, 0x02A1); + param->wodt = 1; + param->rodt = 1; + param->reg_AC1 = 0x43402915; + param->reg_AC2 = 0xFF44E025; + param->reg_DQSIC = 0x00000132; + param->reg_MRS = 0x00000E72; + param->reg_EMRS = 0x00000040; + param->reg_DRV = 0x0000000A; + param->reg_IOZ = 0x00000045; + param->reg_DQIDLY = 0x000000AD; + param->reg_FREQ = 0x000056C0; + param->madj_max = 76; + param->dll2_finetune_step = 3; + break; + case 576: + ast_moutdwm(ast, 0x1E6E2020, 0x0140); + param->wodt = 1; + param->rodt = 1; + param->reg_AC1 = 0x43402915; + param->reg_AC2 = 0xFF44E027; + param->reg_DQSIC = 0x0000013F; + param->reg_MRS = 0x00000E72; + param->reg_EMRS = 0x00000004; + param->reg_DRV = 0x000000F5; + param->reg_IOZ = 0x00000045; + param->reg_DQIDLY = 0x000000B3; + param->reg_FREQ = 0x000057C0; + param->madj_max = 76; + param->dll2_finetune_step = 3; + break; + } + + switch (param->dram_chipid) { + case AST_DRAM_512Mx16: + param->dram_config = 0x100; + break; + default: + case AST_DRAM_1Gx16: + param->dram_config = 0x121; + break; + case AST_DRAM_2Gx16: + param->dram_config = 0x122; + break; + case AST_DRAM_4Gx16: + param->dram_config = 0x123; + break; + } /* switch size */ + + switch (param->vram_size) { + default: + case AST_VIDMEM_SIZE_8M: + param->dram_config |= 0x00; + break; + case AST_VIDMEM_SIZE_16M: + param->dram_config |= 0x04; + break; + case AST_VIDMEM_SIZE_32M: + param->dram_config |= 0x08; + break; + case AST_VIDMEM_SIZE_64M: + param->dram_config |= 0x0c; + break; + } +} + +static void ddr2_init(struct ast_private *ast, struct ast2300_dram_param *param) +{ + u32 data, data2, retry = 0; + +ddr2_init_start: + ast_moutdwm(ast, 0x1E6E0000, 0xFC600309); + ast_moutdwm(ast, 0x1E6E0018, 0x00000100); + ast_moutdwm(ast, 0x1E6E0024, 0x00000000); + ast_moutdwm(ast, 0x1E6E0064, param->reg_MADJ); + ast_moutdwm(ast, 0x1E6E0068, param->reg_SADJ); + udelay(10); + ast_moutdwm(ast, 0x1E6E0064, param->reg_MADJ | 0xC0000); + udelay(10); + + ast_moutdwm(ast, 0x1E6E0004, param->dram_config); + ast_moutdwm(ast, 0x1E6E0008, 0x90040f); + ast_moutdwm(ast, 0x1E6E0010, param->reg_AC1); + ast_moutdwm(ast, 0x1E6E0014, param->reg_AC2); + ast_moutdwm(ast, 0x1E6E0020, param->reg_DQSIC); + ast_moutdwm(ast, 0x1E6E0080, 0x00000000); + ast_moutdwm(ast, 0x1E6E0084, 0x00000000); + ast_moutdwm(ast, 0x1E6E0088, param->reg_DQIDLY); + ast_moutdwm(ast, 0x1E6E0018, 0x4000A130); + ast_moutdwm(ast, 0x1E6E0018, 0x00002330); + ast_moutdwm(ast, 0x1E6E0038, 0x00000000); + ast_moutdwm(ast, 0x1E6E0040, 0xFF808000); + ast_moutdwm(ast, 0x1E6E0044, 0x88848466); + ast_moutdwm(ast, 0x1E6E0048, 0x44440008); + ast_moutdwm(ast, 0x1E6E004C, 0x00000000); + ast_moutdwm(ast, 0x1E6E0050, 0x80000000); + ast_moutdwm(ast, 0x1E6E0050, 0x00000000); + ast_moutdwm(ast, 0x1E6E0054, 0); + ast_moutdwm(ast, 0x1E6E0060, param->reg_DRV); + ast_moutdwm(ast, 0x1E6E006C, param->reg_IOZ); + ast_moutdwm(ast, 0x1E6E0070, 0x00000000); + ast_moutdwm(ast, 0x1E6E0074, 0x00000000); + ast_moutdwm(ast, 0x1E6E0078, 0x00000000); + ast_moutdwm(ast, 0x1E6E007C, 0x00000000); + + /* Wait MCLK2X lock to MCLK */ + do { + data = ast_mindwm(ast, 0x1E6E001C); + } while (!(data & 0x08000000)); + data = ast_mindwm(ast, 0x1E6E001C); + data = (data >> 8) & 0xff; + while ((data & 0x08) || ((data & 0x7) < 2) || (data < 4)) { + data2 = (ast_mindwm(ast, 0x1E6E0064) & 0xfff3ffff) + 4; + if ((data2 & 0xff) > param->madj_max) + break; + ast_moutdwm(ast, 0x1E6E0064, data2); + if (data2 & 0x00100000) + data2 = ((data2 & 0xff) >> 3) + 3; + else + data2 = ((data2 & 0xff) >> 2) + 5; + data = ast_mindwm(ast, 0x1E6E0068) & 0xffff00ff; + data2 += data & 0xff; + data = data | (data2 << 8); + ast_moutdwm(ast, 0x1E6E0068, data); + udelay(10); + ast_moutdwm(ast, 0x1E6E0064, + ast_mindwm(ast, 0x1E6E0064) | 0xC0000); + udelay(10); + data = ast_mindwm(ast, 0x1E6E0018) & 0xfffff1ff; + ast_moutdwm(ast, 0x1E6E0018, data); + data = data | 0x200; + ast_moutdwm(ast, 0x1E6E0018, data); + do { + data = ast_mindwm(ast, 0x1E6E001C); + } while (!(data & 0x08000000)); + + data = ast_mindwm(ast, 0x1E6E001C); + data = (data >> 8) & 0xff; + } + ast_moutdwm(ast, 0x1E720058, ast_mindwm(ast, 0x1E6E0008) & 0xffff); + data = ast_mindwm(ast, 0x1E6E0018) | 0xC00; + ast_moutdwm(ast, 0x1E6E0018, data); + + ast_moutdwm(ast, 0x1E6E0034, 0x00000001); + ast_moutdwm(ast, 0x1E6E000C, 0x00000000); + udelay(50); + /* Mode Register Setting */ + ast_moutdwm(ast, 0x1E6E002C, param->reg_MRS | 0x100); + ast_moutdwm(ast, 0x1E6E0030, param->reg_EMRS); + ast_moutdwm(ast, 0x1E6E0028, 0x00000005); + ast_moutdwm(ast, 0x1E6E0028, 0x00000007); + ast_moutdwm(ast, 0x1E6E0028, 0x00000003); + ast_moutdwm(ast, 0x1E6E0028, 0x00000001); + + ast_moutdwm(ast, 0x1E6E000C, 0x00005C08); + ast_moutdwm(ast, 0x1E6E002C, param->reg_MRS); + ast_moutdwm(ast, 0x1E6E0028, 0x00000001); + ast_moutdwm(ast, 0x1E6E0030, param->reg_EMRS | 0x380); + ast_moutdwm(ast, 0x1E6E0028, 0x00000003); + ast_moutdwm(ast, 0x1E6E0030, param->reg_EMRS); + ast_moutdwm(ast, 0x1E6E0028, 0x00000003); + + ast_moutdwm(ast, 0x1E6E000C, 0x7FFF5C01); + data = 0; + if (param->wodt) + data = 0x500; + if (param->rodt) + data = data | 0x3000 | ((param->reg_AC2 & 0x60000) >> 3); + ast_moutdwm(ast, 0x1E6E0034, data | 0x3); + ast_moutdwm(ast, 0x1E6E0120, param->reg_FREQ); + + /* Calibrate the DQSI delay */ + if ((cbr_dll2(ast, param) == false) && (retry++ < 10)) + goto ddr2_init_start; + + /* ECC Memory Initialization */ +#ifdef ECC + ast_moutdwm(ast, 0x1E6E007C, 0x00000000); + ast_moutdwm(ast, 0x1E6E0070, 0x221); + do { + data = ast_mindwm(ast, 0x1E6E0070); + } while (!(data & 0x00001000)); + ast_moutdwm(ast, 0x1E6E0070, 0x00000000); + ast_moutdwm(ast, 0x1E6E0050, 0x80000000); + ast_moutdwm(ast, 0x1E6E0050, 0x00000000); +#endif +} + +static void ast_post_chip_2300(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + struct ast2300_dram_param param; + u32 temp; + u8 reg; + + reg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd0, 0xff); + if ((reg & 0x80) == 0) { /* vga only */ + ast_write32(ast, 0xf004, 0x1e6e0000); + ast_write32(ast, 0xf000, 0x1); + ast_write32(ast, 0x12000, 0x1688a8a8); + do { + ; + } while (ast_read32(ast, 0x12000) != 0x1); + + ast_write32(ast, 0x10000, 0xfc600309); + do { + ; + } while (ast_read32(ast, 0x10000) != 0x1); + + /* Slow down CPU/AHB CLK in VGA only mode */ + temp = ast_read32(ast, 0x12008); + temp |= 0x73; + ast_write32(ast, 0x12008, temp); + + param.dram_freq = 396; + param.dram_type = AST_DDR3; + temp = ast_mindwm(ast, 0x1e6e2070); + if (temp & 0x01000000) + param.dram_type = AST_DDR2; + switch (temp & 0x18000000) { + case 0: + param.dram_chipid = AST_DRAM_512Mx16; + break; + default: + case 0x08000000: + param.dram_chipid = AST_DRAM_1Gx16; + break; + case 0x10000000: + param.dram_chipid = AST_DRAM_2Gx16; + break; + case 0x18000000: + param.dram_chipid = AST_DRAM_4Gx16; + break; + } + switch (temp & 0x0c) { + default: + case 0x00: + param.vram_size = AST_VIDMEM_SIZE_8M; + break; + + case 0x04: + param.vram_size = AST_VIDMEM_SIZE_16M; + break; + + case 0x08: + param.vram_size = AST_VIDMEM_SIZE_32M; + break; + + case 0x0c: + param.vram_size = AST_VIDMEM_SIZE_64M; + break; + } + + if (param.dram_type == AST_DDR3) { + get_ddr3_info(ast, ¶m); + ddr3_init(ast, ¶m); + } else { + get_ddr2_info(ast, ¶m); + ddr2_init(ast, ¶m); + } + + temp = ast_mindwm(ast, 0x1e6e2040); + ast_moutdwm(ast, 0x1e6e2040, temp | 0x40); + } + + /* wait ready */ + do { + reg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd0, 0xff); + } while ((reg & 0x40) == 0); +} + +static bool cbr_test_2500(struct ast_private *ast) +{ + ast_moutdwm(ast, 0x1E6E0074, 0x0000FFFF); + ast_moutdwm(ast, 0x1E6E007C, 0xFF00FF00); + if (!mmc_test_burst(ast, 0)) + return false; + if (!mmc_test_single_2500(ast, 0)) + return false; + return true; +} + +static bool ddr_test_2500(struct ast_private *ast) +{ + ast_moutdwm(ast, 0x1E6E0074, 0x0000FFFF); + ast_moutdwm(ast, 0x1E6E007C, 0xFF00FF00); + if (!mmc_test_burst(ast, 0)) + return false; + if (!mmc_test_burst(ast, 1)) + return false; + if (!mmc_test_burst(ast, 2)) + return false; + if (!mmc_test_burst(ast, 3)) + return false; + if (!mmc_test_single_2500(ast, 0)) + return false; + return true; +} + +static void ddr_init_common_2500(struct ast_private *ast) +{ + ast_moutdwm(ast, 0x1E6E0034, 0x00020080); + ast_moutdwm(ast, 0x1E6E0008, 0x2003000F); + ast_moutdwm(ast, 0x1E6E0038, 0x00000FFF); + ast_moutdwm(ast, 0x1E6E0040, 0x88448844); + ast_moutdwm(ast, 0x1E6E0044, 0x24422288); + ast_moutdwm(ast, 0x1E6E0048, 0x22222222); + ast_moutdwm(ast, 0x1E6E004C, 0x22222222); + ast_moutdwm(ast, 0x1E6E0050, 0x80000000); + ast_moutdwm(ast, 0x1E6E0208, 0x00000000); + ast_moutdwm(ast, 0x1E6E0218, 0x00000000); + ast_moutdwm(ast, 0x1E6E0220, 0x00000000); + ast_moutdwm(ast, 0x1E6E0228, 0x00000000); + ast_moutdwm(ast, 0x1E6E0230, 0x00000000); + ast_moutdwm(ast, 0x1E6E02A8, 0x00000000); + ast_moutdwm(ast, 0x1E6E02B0, 0x00000000); + ast_moutdwm(ast, 0x1E6E0240, 0x86000000); + ast_moutdwm(ast, 0x1E6E0244, 0x00008600); + ast_moutdwm(ast, 0x1E6E0248, 0x80000000); + ast_moutdwm(ast, 0x1E6E024C, 0x80808080); +} + +static void ddr_phy_init_2500(struct ast_private *ast) +{ + u32 data, pass, timecnt; + + pass = 0; + ast_moutdwm(ast, 0x1E6E0060, 0x00000005); + while (!pass) { + for (timecnt = 0; timecnt < TIMEOUT; timecnt++) { + data = ast_mindwm(ast, 0x1E6E0060) & 0x1; + if (!data) + break; + } + if (timecnt != TIMEOUT) { + data = ast_mindwm(ast, 0x1E6E0300) & 0x000A0000; + if (!data) + pass = 1; + } + if (!pass) { + ast_moutdwm(ast, 0x1E6E0060, 0x00000000); + udelay(10); /* delay 10 us */ + ast_moutdwm(ast, 0x1E6E0060, 0x00000005); + } + } + + ast_moutdwm(ast, 0x1E6E0060, 0x00000006); +} + +/* + * Check DRAM Size + * 1Gb : 0x80000000 ~ 0x87FFFFFF + * 2Gb : 0x80000000 ~ 0x8FFFFFFF + * 4Gb : 0x80000000 ~ 0x9FFFFFFF + * 8Gb : 0x80000000 ~ 0xBFFFFFFF + */ +static void check_dram_size_2500(struct ast_private *ast, u32 tRFC) +{ + u32 reg_04, reg_14; + + reg_04 = ast_mindwm(ast, 0x1E6E0004) & 0xfffffffc; + reg_14 = ast_mindwm(ast, 0x1E6E0014) & 0xffffff00; + + ast_moutdwm(ast, 0xA0100000, 0x41424344); + ast_moutdwm(ast, 0x90100000, 0x35363738); + ast_moutdwm(ast, 0x88100000, 0x292A2B2C); + ast_moutdwm(ast, 0x80100000, 0x1D1E1F10); + + /* Check 8Gbit */ + if (ast_mindwm(ast, 0xA0100000) == 0x41424344) { + reg_04 |= 0x03; + reg_14 |= (tRFC >> 24) & 0xFF; + /* Check 4Gbit */ + } else if (ast_mindwm(ast, 0x90100000) == 0x35363738) { + reg_04 |= 0x02; + reg_14 |= (tRFC >> 16) & 0xFF; + /* Check 2Gbit */ + } else if (ast_mindwm(ast, 0x88100000) == 0x292A2B2C) { + reg_04 |= 0x01; + reg_14 |= (tRFC >> 8) & 0xFF; + } else { + reg_14 |= tRFC & 0xFF; + } + ast_moutdwm(ast, 0x1E6E0004, reg_04); + ast_moutdwm(ast, 0x1E6E0014, reg_14); +} + +static void enable_cache_2500(struct ast_private *ast) +{ + u32 reg_04, data; + + reg_04 = ast_mindwm(ast, 0x1E6E0004); + ast_moutdwm(ast, 0x1E6E0004, reg_04 | 0x1000); + + do + data = ast_mindwm(ast, 0x1E6E0004); + while (!(data & 0x80000)); + ast_moutdwm(ast, 0x1E6E0004, reg_04 | 0x400); +} + +static void set_mpll_2500(struct ast_private *ast) +{ + u32 addr, data, param; + + /* Reset MMC */ + ast_moutdwm(ast, 0x1E6E0000, 0xFC600309); + ast_moutdwm(ast, 0x1E6E0034, 0x00020080); + for (addr = 0x1e6e0004; addr < 0x1e6e0090;) { + ast_moutdwm(ast, addr, 0x0); + addr += 4; + } + ast_moutdwm(ast, 0x1E6E0034, 0x00020000); + + ast_moutdwm(ast, 0x1E6E2000, 0x1688A8A8); + data = ast_mindwm(ast, 0x1E6E2070) & 0x00800000; + if (data) { + /* CLKIN = 25MHz */ + param = 0x930023E0; + ast_moutdwm(ast, 0x1E6E2160, 0x00011320); + } else { + /* CLKIN = 24MHz */ + param = 0x93002400; + } + ast_moutdwm(ast, 0x1E6E2020, param); + udelay(100); +} + +static void reset_mmc_2500(struct ast_private *ast) +{ + ast_moutdwm(ast, 0x1E78505C, 0x00000004); + ast_moutdwm(ast, 0x1E785044, 0x00000001); + ast_moutdwm(ast, 0x1E785048, 0x00004755); + ast_moutdwm(ast, 0x1E78504C, 0x00000013); + mdelay(100); + ast_moutdwm(ast, 0x1E785054, 0x00000077); + ast_moutdwm(ast, 0x1E6E0000, 0xFC600309); +} + +static void ddr3_init_2500(struct ast_private *ast, const u32 *ddr_table) +{ + ast_moutdwm(ast, 0x1E6E0004, 0x00000303); + ast_moutdwm(ast, 0x1E6E0010, ddr_table[REGIDX_010]); + ast_moutdwm(ast, 0x1E6E0014, ddr_table[REGIDX_014]); + ast_moutdwm(ast, 0x1E6E0018, ddr_table[REGIDX_018]); + ast_moutdwm(ast, 0x1E6E0020, ddr_table[REGIDX_020]); /* MODEREG4/6 */ + ast_moutdwm(ast, 0x1E6E0024, ddr_table[REGIDX_024]); /* MODEREG5 */ + ast_moutdwm(ast, 0x1E6E002C, + ddr_table[REGIDX_02C] | 0x100); /* MODEREG0/2 */ + ast_moutdwm(ast, 0x1E6E0030, ddr_table[REGIDX_030]); /* MODEREG1/3 */ + + /* DDR PHY Setting */ + ast_moutdwm(ast, 0x1E6E0200, 0x02492AAE); + ast_moutdwm(ast, 0x1E6E0204, 0x00001001); + ast_moutdwm(ast, 0x1E6E020C, 0x55E00B0B); + ast_moutdwm(ast, 0x1E6E0210, 0x20000000); + ast_moutdwm(ast, 0x1E6E0214, ddr_table[REGIDX_214]); + ast_moutdwm(ast, 0x1E6E02E0, ddr_table[REGIDX_2E0]); + ast_moutdwm(ast, 0x1E6E02E4, ddr_table[REGIDX_2E4]); + ast_moutdwm(ast, 0x1E6E02E8, ddr_table[REGIDX_2E8]); + ast_moutdwm(ast, 0x1E6E02EC, ddr_table[REGIDX_2EC]); + ast_moutdwm(ast, 0x1E6E02F0, ddr_table[REGIDX_2F0]); + ast_moutdwm(ast, 0x1E6E02F4, ddr_table[REGIDX_2F4]); + ast_moutdwm(ast, 0x1E6E02F8, ddr_table[REGIDX_2F8]); + ast_moutdwm(ast, 0x1E6E0290, 0x00100008); + ast_moutdwm(ast, 0x1E6E02C0, 0x00000006); + + /* Controller Setting */ + ast_moutdwm(ast, 0x1E6E0034, 0x00020091); + + /* Wait DDR PHY init done */ + ddr_phy_init_2500(ast); + + ast_moutdwm(ast, 0x1E6E0120, ddr_table[REGIDX_PLL]); + ast_moutdwm(ast, 0x1E6E000C, 0x42AA5C81); + ast_moutdwm(ast, 0x1E6E0034, 0x0001AF93); + + check_dram_size_2500(ast, ddr_table[REGIDX_RFC]); + enable_cache_2500(ast); + ast_moutdwm(ast, 0x1E6E001C, 0x00000008); + ast_moutdwm(ast, 0x1E6E0038, 0xFFFFFF00); +} + +static void ddr4_init_2500(struct ast_private *ast, const u32 *ddr_table) +{ + u32 data, data2, pass, retrycnt; + u32 ddr_vref, phy_vref; + u32 min_ddr_vref = 0, min_phy_vref = 0; + u32 max_ddr_vref = 0, max_phy_vref = 0; + + ast_moutdwm(ast, 0x1E6E0004, 0x00000313); + ast_moutdwm(ast, 0x1E6E0010, ddr_table[REGIDX_010]); + ast_moutdwm(ast, 0x1E6E0014, ddr_table[REGIDX_014]); + ast_moutdwm(ast, 0x1E6E0018, ddr_table[REGIDX_018]); + ast_moutdwm(ast, 0x1E6E0020, ddr_table[REGIDX_020]); /* MODEREG4/6 */ + ast_moutdwm(ast, 0x1E6E0024, ddr_table[REGIDX_024]); /* MODEREG5 */ + ast_moutdwm(ast, 0x1E6E002C, + ddr_table[REGIDX_02C] | 0x100); /* MODEREG0/2 */ + ast_moutdwm(ast, 0x1E6E0030, ddr_table[REGIDX_030]); /* MODEREG1/3 */ + + /* DDR PHY Setting */ + ast_moutdwm(ast, 0x1E6E0200, 0x42492AAE); + ast_moutdwm(ast, 0x1E6E0204, 0x09002000); + ast_moutdwm(ast, 0x1E6E020C, 0x55E00B0B); + ast_moutdwm(ast, 0x1E6E0210, 0x20000000); + ast_moutdwm(ast, 0x1E6E0214, ddr_table[REGIDX_214]); + ast_moutdwm(ast, 0x1E6E02E0, ddr_table[REGIDX_2E0]); + ast_moutdwm(ast, 0x1E6E02E4, ddr_table[REGIDX_2E4]); + ast_moutdwm(ast, 0x1E6E02E8, ddr_table[REGIDX_2E8]); + ast_moutdwm(ast, 0x1E6E02EC, ddr_table[REGIDX_2EC]); + ast_moutdwm(ast, 0x1E6E02F0, ddr_table[REGIDX_2F0]); + ast_moutdwm(ast, 0x1E6E02F4, ddr_table[REGIDX_2F4]); + ast_moutdwm(ast, 0x1E6E02F8, ddr_table[REGIDX_2F8]); + ast_moutdwm(ast, 0x1E6E0290, 0x00100008); + ast_moutdwm(ast, 0x1E6E02C4, 0x3C183C3C); + ast_moutdwm(ast, 0x1E6E02C8, 0x00631E0E); + + /* Controller Setting */ + ast_moutdwm(ast, 0x1E6E0034, 0x0001A991); + + /* Train PHY Vref first */ + pass = 0; + + for (retrycnt = 0; retrycnt < 4 && pass == 0; retrycnt++) { + max_phy_vref = 0x0; + pass = 0; + ast_moutdwm(ast, 0x1E6E02C0, 0x00001C06); + for (phy_vref = 0x40; phy_vref < 0x80; phy_vref++) { + ast_moutdwm(ast, 0x1E6E000C, 0x00000000); + ast_moutdwm(ast, 0x1E6E0060, 0x00000000); + ast_moutdwm(ast, 0x1E6E02CC, + phy_vref | (phy_vref << 8)); + /* Fire DFI Init */ + ddr_phy_init_2500(ast); + ast_moutdwm(ast, 0x1E6E000C, 0x00005C01); + if (cbr_test_2500(ast)) { + pass++; + data = ast_mindwm(ast, 0x1E6E03D0); + data2 = data >> 8; + data = data & 0xff; + if (data > data2) + data = data2; + if (max_phy_vref < data) { + max_phy_vref = data; + min_phy_vref = phy_vref; + } + } else if (pass > 0) + break; + } + } + ast_moutdwm(ast, 0x1E6E02CC, min_phy_vref | (min_phy_vref << 8)); + + /* Train DDR Vref next */ + pass = 0; + + for (retrycnt = 0; retrycnt < 4 && pass == 0; retrycnt++) { + min_ddr_vref = 0xFF; + max_ddr_vref = 0x0; + pass = 0; + for (ddr_vref = 0x00; ddr_vref < 0x40; ddr_vref++) { + ast_moutdwm(ast, 0x1E6E000C, 0x00000000); + ast_moutdwm(ast, 0x1E6E0060, 0x00000000); + ast_moutdwm(ast, 0x1E6E02C0, + 0x00000006 | (ddr_vref << 8)); + /* Fire DFI Init */ + ddr_phy_init_2500(ast); + ast_moutdwm(ast, 0x1E6E000C, 0x00005C01); + if (cbr_test_2500(ast)) { + pass++; + if (min_ddr_vref > ddr_vref) + min_ddr_vref = ddr_vref; + if (max_ddr_vref < ddr_vref) + max_ddr_vref = ddr_vref; + } else if (pass != 0) + break; + } + } + + ast_moutdwm(ast, 0x1E6E000C, 0x00000000); + ast_moutdwm(ast, 0x1E6E0060, 0x00000000); + ddr_vref = (min_ddr_vref + max_ddr_vref + 1) >> 1; + ast_moutdwm(ast, 0x1E6E02C0, 0x00000006 | (ddr_vref << 8)); + + /* Wait DDR PHY init done */ + ddr_phy_init_2500(ast); + + ast_moutdwm(ast, 0x1E6E0120, ddr_table[REGIDX_PLL]); + ast_moutdwm(ast, 0x1E6E000C, 0x42AA5C81); + ast_moutdwm(ast, 0x1E6E0034, 0x0001AF93); + + check_dram_size_2500(ast, ddr_table[REGIDX_RFC]); + enable_cache_2500(ast); + ast_moutdwm(ast, 0x1E6E001C, 0x00000008); + ast_moutdwm(ast, 0x1E6E0038, 0xFFFFFF00); +} + +static bool ast_dram_init_2500(struct ast_private *ast) +{ + u32 data; + u32 max_tries = 5; + + do { + if (max_tries-- == 0) + return false; + set_mpll_2500(ast); + reset_mmc_2500(ast); + ddr_init_common_2500(ast); + + data = ast_mindwm(ast, 0x1E6E2070); + if (data & 0x01000000) + ddr4_init_2500(ast, ast2500_ddr4_1600_timing_table); + else + ddr3_init_2500(ast, ast2500_ddr3_1600_timing_table); + } while (!ddr_test_2500(ast)); + + ast_moutdwm(ast, 0x1E6E2040, ast_mindwm(ast, 0x1E6E2040) | 0x41); + + /* Patch code */ + data = ast_mindwm(ast, 0x1E6E200C) & 0xF9FFFFFF; + ast_moutdwm(ast, 0x1E6E200C, data | 0x10000000); + + return true; +} + +void ast_patch_ahb_2500(struct ast_private *ast) +{ + u32 data; + + /* Clear bus lock condition */ + ast_moutdwm(ast, 0x1e600000, 0xAEED1A03); + ast_moutdwm(ast, 0x1e600084, 0x00010000); + ast_moutdwm(ast, 0x1e600088, 0x00000000); + ast_moutdwm(ast, 0x1e6e2000, 0x1688A8A8); + data = ast_mindwm(ast, 0x1e6e2070); + if (data & 0x08000000) { /* check fast reset */ + /* + * If "Fast restet" is enabled for ARM-ICE debugger, + * then WDT needs to enable, that + * WDT04 is WDT#1 Reload reg. + * WDT08 is WDT#1 counter restart reg to avoid system deadlock + * WDT0C is WDT#1 control reg + * [6:5]:= 01:Full chip + * [4]:= 1:1MHz clock source + * [1]:= 1:WDT will be cleeared and disabled after timeout occurs + * [0]:= 1:WDT enable + */ + ast_moutdwm(ast, 0x1E785004, 0x00000010); + ast_moutdwm(ast, 0x1E785008, 0x00004755); + ast_moutdwm(ast, 0x1E78500c, 0x00000033); + udelay(1000); + } + do { + ast_moutdwm(ast, 0x1e6e2000, 0x1688A8A8); + data = ast_mindwm(ast, 0x1e6e2000); + } while (data != 1); + ast_moutdwm(ast, 0x1e6e207c, 0x08000000); /* clear fast reset */ +} + +void ast_post_chip_2500(struct drm_device *dev) +{ + struct ast_private *ast = to_ast_private(dev); + u32 temp; + u8 reg; + + reg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd0, 0xff); + if ((reg & AST_VRAM_INIT_STATUS_MASK) == 0) { /* vga only */ + /* Clear bus lock condition */ + ast_patch_ahb_2500(ast); + + /* Disable watchdog */ + ast_moutdwm(ast, 0x1E78502C, 0x00000000); + ast_moutdwm(ast, 0x1E78504C, 0x00000000); + + /* + * Reset USB port to patch USB unknown device issue + * SCU90 is Multi-function Pin Control #5 + * [29]:= 1:Enable USB2.0 Host port#1 (that the mutually shared USB2.0 Hub + * port). + * SCU94 is Multi-function Pin Control #6 + * [14:13]:= 1x:USB2.0 Host2 controller + * SCU70 is Hardware Strap reg + * [23]:= 1:CLKIN is 25MHz and USBCK1 = 24/48 MHz (determined by + * [18]: 0(24)/1(48) MHz) + * SCU7C is Write clear reg to SCU70 + * [23]:= write 1 and then SCU70[23] will be clear as 0b. + */ + ast_moutdwm(ast, 0x1E6E2090, 0x20000000); + ast_moutdwm(ast, 0x1E6E2094, 0x00004000); + if (ast_mindwm(ast, 0x1E6E2070) & 0x00800000) { + ast_moutdwm(ast, 0x1E6E207C, 0x00800000); + mdelay(100); + ast_moutdwm(ast, 0x1E6E2070, 0x00800000); + } + /* Modify eSPI reset pin */ + temp = ast_mindwm(ast, 0x1E6E2070); + if (temp & 0x02000000) + ast_moutdwm(ast, 0x1E6E207C, 0x00004000); + + /* Slow down CPU/AHB CLK in VGA only mode */ + temp = ast_read32(ast, 0x12008); + temp |= 0x73; + ast_write32(ast, 0x12008, temp); + + if (!ast_dram_init_2500(ast)) + drm_err(dev, "DRAM init failed !\n"); + + temp = ast_mindwm(ast, 0x1e6e2040); + ast_moutdwm(ast, 0x1e6e2040, temp | 0x40); + } + + /* wait ready */ + do { + reg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd0, 0xff); + } while ((reg & 0x40) == 0); +} diff --git a/drivers/gpu/drm/loongson/ast_old/ast_tables.h b/drivers/gpu/drm/loongson/ast_old/ast_tables.h new file mode 100644 index 0000000000000000000000000000000000000000..e92a17a5cf27f933f707256d9e772c227cf7a0ed --- /dev/null +++ b/drivers/gpu/drm/loongson/ast_old/ast_tables.h @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2005 ASPEED Technology Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the authors not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. The authors makes no representations + * about the suitability of this software for any purpose. It is provided + * "as is" without express or implied warranty. + * + * THE AUTHORS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ +/* Ported from xf86-video-ast driver */ + +#ifndef AST_TABLES_H +#define AST_TABLES_H + +/* Std. Table Index Definition */ +#define TextModeIndex 0 +#define EGAModeIndex 1 +#define VGAModeIndex 2 +#define HiCModeIndex 3 +#define TrueCModeIndex 4 + +#define Charx8Dot 0x00000001 +#define HalfDCLK 0x00000002 +#define DoubleScanMode 0x00000004 +#define LineCompareOff 0x00000008 +#define HBorder 0x00000020 +#define VBorder 0x00000010 +#define WideScreenMode 0x00000100 +#define NewModeInfo 0x00000200 +#define NHSync 0x00000400 +#define PHSync 0x00000800 +#define NVSync 0x00001000 +#define PVSync 0x00002000 +#define SyncPP (PVSync | PHSync) +#define SyncPN (PVSync | NHSync) +#define SyncNP (NVSync | PHSync) +#define SyncNN (NVSync | NHSync) +#define AST2500PreCatchCRT 0x00004000 + +/* DCLK Index */ +#define VCLK25_175 0x00 +#define VCLK28_322 0x01 +#define VCLK31_5 0x02 +#define VCLK36 0x03 +#define VCLK40 0x04 +#define VCLK49_5 0x05 +#define VCLK50 0x06 +#define VCLK56_25 0x07 +#define VCLK65 0x08 +#define VCLK75 0x09 +#define VCLK78_75 0x0A +#define VCLK94_5 0x0B +#define VCLK108 0x0C +#define VCLK135 0x0D +#define VCLK157_5 0x0E +#define VCLK162 0x0F +#define VCLK154 0x10 +#define VCLK83_5 0x11 +#define VCLK106_5 0x12 +#define VCLK146_25 0x13 +#define VCLK148_5 0x14 +#define VCLK71 0x15 +#define VCLK88_75 0x16 +#define VCLK119 0x17 +#define VCLK85_5 0x18 +#define VCLK97_75 0x19 +#define VCLK118_25 0x1A + +static const struct ast_vbios_dclk_info dclk_table[] = { + { 0x2C, 0xE7, 0x03 }, /* 00: VCLK25_175 */ + { 0x95, 0x62, 0x03 }, /* 01: VCLK28_322 */ + { 0x67, 0x63, 0x01 }, /* 02: VCLK31_5 */ + { 0x76, 0x63, 0x01 }, /* 03: VCLK36 */ + { 0xEE, 0x67, 0x01 }, /* 04: VCLK40 */ + { 0x82, 0x62, 0x01 }, /* 05: VCLK49_5 */ + { 0xC6, 0x64, 0x01 }, /* 06: VCLK50 */ + { 0x94, 0x62, 0x01 }, /* 07: VCLK56_25 */ + { 0x80, 0x64, 0x00 }, /* 08: VCLK65 */ + { 0x7B, 0x63, 0x00 }, /* 09: VCLK75 */ + { 0x67, 0x62, 0x00 }, /* 0A: VCLK78_75 */ + { 0x7C, 0x62, 0x00 }, /* 0B: VCLK94_5 */ + { 0x8E, 0x62, 0x00 }, /* 0C: VCLK108 */ + { 0x85, 0x24, 0x00 }, /* 0D: VCLK135 */ + { 0x67, 0x22, 0x00 }, /* 0E: VCLK157_5 */ + { 0x6A, 0x22, 0x00 }, /* 0F: VCLK162 */ + { 0x4d, 0x4c, 0x80 }, /* 10: VCLK154 */ + { 0x68, 0x6f, 0x80 }, /* 11: VCLK83.5 */ + { 0x28, 0x49, 0x80 }, /* 12: VCLK106.5 */ + { 0x37, 0x49, 0x80 }, /* 13: VCLK146.25 */ + { 0x1f, 0x45, 0x80 }, /* 14: VCLK148.5 */ + { 0x47, 0x6c, 0x80 }, /* 15: VCLK71 */ + { 0x25, 0x65, 0x80 }, /* 16: VCLK88.75 */ + { 0x77, 0x58, 0x80 }, /* 17: VCLK119 */ + { 0x32, 0x67, 0x80 }, /* 18: VCLK85_5 */ + { 0x6a, 0x6d, 0x80 }, /* 19: VCLK97_75 */ + { 0x3b, 0x2c, 0x81 }, /* 1A: VCLK118_25 */ +}; + +static const struct ast_vbios_dclk_info dclk_table_ast2500[] = { + { 0x2C, 0xE7, 0x03 }, /* 00: VCLK25_175 */ + { 0x95, 0x62, 0x03 }, /* 01: VCLK28_322 */ + { 0x67, 0x63, 0x01 }, /* 02: VCLK31_5 */ + { 0x76, 0x63, 0x01 }, /* 03: VCLK36 */ + { 0xEE, 0x67, 0x01 }, /* 04: VCLK40 */ + { 0x82, 0x62, 0x01 }, /* 05: VCLK49_5 */ + { 0xC6, 0x64, 0x01 }, /* 06: VCLK50 */ + { 0x94, 0x62, 0x01 }, /* 07: VCLK56_25 */ + { 0x80, 0x64, 0x00 }, /* 08: VCLK65 */ + { 0x7B, 0x63, 0x00 }, /* 09: VCLK75 */ + { 0x67, 0x62, 0x00 }, /* 0A: VCLK78_75 */ + { 0x7C, 0x62, 0x00 }, /* 0B: VCLK94_5 */ + { 0x8E, 0x62, 0x00 }, /* 0C: VCLK108 */ + { 0x85, 0x24, 0x00 }, /* 0D: VCLK135 */ + { 0x67, 0x22, 0x00 }, /* 0E: VCLK157_5 */ + { 0x6A, 0x22, 0x00 }, /* 0F: VCLK162 */ + { 0x4d, 0x4c, 0x80 }, /* 10: VCLK154 */ + { 0x68, 0x6f, 0x80 }, /* 11: VCLK83.5 */ + { 0x28, 0x49, 0x80 }, /* 12: VCLK106.5 */ + { 0x37, 0x49, 0x80 }, /* 13: VCLK146.25 */ + { 0x1f, 0x45, 0x80 }, /* 14: VCLK148.5 */ + { 0x47, 0x6c, 0x80 }, /* 15: VCLK71 */ + { 0x25, 0x65, 0x80 }, /* 16: VCLK88.75 */ + { 0x58, 0x01, 0x42 }, /* 17: VCLK119 */ + { 0x32, 0x67, 0x80 }, /* 18: VCLK85_5 */ + { 0x6a, 0x6d, 0x80 }, /* 19: VCLK97_75 */ + { 0x44, 0x20, 0x43 }, /* 1A: VCLK118_25 */ +}; + +static const struct ast_vbios_stdtable vbios_stdtable[] = { + /* MD_2_3_400 */ + { 0x67, + { 0x00, 0x03, 0x00, 0x02 }, + { 0x5f, 0x4f, 0x50, 0x82, 0x55, 0x81, 0xbf, 0x1f, 0x00, + 0x4f, 0x0d, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x8e, + 0x8f, 0x28, 0x1f, 0x96, 0xb9, 0xa3, 0xff }, + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, 0x38, 0x39, + 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x0c, 0x00, 0x0f, 0x08 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0e, 0x00, 0xff } }, + /* Mode12/ExtEGATable */ + { 0xe3, + { 0x01, 0x0f, 0x00, 0x06 }, + { 0x5f, 0x4f, 0x50, 0x82, 0x55, 0x81, 0x0b, 0x3e, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x8b, + 0xdf, 0x28, 0x00, 0xe7, 0x04, 0xe3, 0xff }, + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, 0x38, 0x39, + 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x01, 0x00, 0x0f, 0x00 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0f, 0xff } }, + /* ExtVGATable */ + { 0x2f, + { 0x01, 0x0f, 0x00, 0x0e }, + { 0x5f, 0x4f, 0x50, 0x82, 0x54, 0x80, 0x0b, 0x3e, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x8c, + 0xdf, 0x28, 0x40, 0xe7, 0x04, 0xa3, 0xff }, + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x01, 0x00, 0x00, 0x00 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0f, 0xff } }, + /* ExtHiCTable */ + { 0x2f, + { 0x01, 0x0f, 0x00, 0x0e }, + { 0x5f, 0x4f, 0x50, 0x82, 0x54, 0x80, 0x0b, 0x3e, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x8c, + 0xdf, 0x28, 0x40, 0xe7, 0x04, 0xa3, 0xff }, + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x01, 0x00, 0x00, 0x00 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0f, 0xff } }, + /* ExtTrueCTable */ + { 0x2f, + { 0x01, 0x0f, 0x00, 0x0e }, + { 0x5f, 0x4f, 0x50, 0x82, 0x54, 0x80, 0x0b, 0x3e, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x8c, + 0xdf, 0x28, 0x40, 0xe7, 0x04, 0xa3, 0xff }, + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x01, 0x00, 0x00, 0x00 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0f, 0xff } }, +}; + +static const struct ast_vbios_enhtable res_640x480[] = { + { 800, 640, 8, 96, 525, 480, 2, 2, VCLK25_175, /* 60Hz */ + (SyncNN | HBorder | VBorder | Charx8Dot), 60, 1, 0x2E }, + { 832, 640, 16, 40, 520, 480, 1, 3, VCLK31_5, /* 72Hz */ + (SyncNN | HBorder | VBorder | Charx8Dot), 72, 2, 0x2E }, + { 840, 640, 16, 64, 500, 480, 1, 3, VCLK31_5, /* 75Hz */ + (SyncNN | Charx8Dot), 75, 3, 0x2E }, + { 832, 640, 56, 56, 509, 480, 1, 3, VCLK36, /* 85Hz */ + (SyncNN | Charx8Dot), 85, 4, 0x2E }, + { 832, 640, 56, 56, 509, 480, 1, 3, VCLK36, /* end */ + (SyncNN | Charx8Dot), 0xFF, 4, 0x2E }, +}; + +static const struct ast_vbios_enhtable res_800x600[] = { + { 1024, 800, 24, 72, 625, 600, 1, 2, VCLK36, /* 56Hz */ + (SyncPP | Charx8Dot), 56, 1, 0x30 }, + { 1056, 800, 40, 128, 628, 600, 1, 4, VCLK40, /* 60Hz */ + (SyncPP | Charx8Dot), 60, 2, 0x30 }, + { 1040, 800, 56, 120, 666, 600, 37, 6, VCLK50, /* 72Hz */ + (SyncPP | Charx8Dot), 72, 3, 0x30 }, + { 1056, 800, 16, 80, 625, 600, 1, 3, VCLK49_5, /* 75Hz */ + (SyncPP | Charx8Dot), 75, 4, 0x30 }, + { 1048, 800, 32, 64, 631, 600, 1, 3, VCLK56_25, /* 85Hz */ + (SyncPP | Charx8Dot), 84, 5, 0x30 }, + { 1048, 800, 32, 64, 631, 600, 1, 3, VCLK56_25, /* end */ + (SyncPP | Charx8Dot), 0xFF, 5, 0x30 }, +}; + +static const struct ast_vbios_enhtable res_1024x768[] = { + { 1344, 1024, 24, 136, 806, 768, 3, 6, VCLK65, /* 60Hz */ + (SyncNN | Charx8Dot), 60, 1, 0x31 }, + { 1328, 1024, 24, 136, 806, 768, 3, 6, VCLK75, /* 70Hz */ + (SyncNN | Charx8Dot), 70, 2, 0x31 }, + { 1312, 1024, 16, 96, 800, 768, 1, 3, VCLK78_75, /* 75Hz */ + (SyncPP | Charx8Dot), 75, 3, 0x31 }, + { 1376, 1024, 48, 96, 808, 768, 1, 3, VCLK94_5, /* 85Hz */ + (SyncPP | Charx8Dot), 84, 4, 0x31 }, + { 1376, 1024, 48, 96, 808, 768, 1, 3, VCLK94_5, /* end */ + (SyncPP | Charx8Dot), 0xFF, 4, 0x31 }, +}; + +static const struct ast_vbios_enhtable res_1280x1024[] = { + { 1688, 1280, 48, 112, 1066, 1024, 1, 3, VCLK108, /* 60Hz */ + (SyncPP | Charx8Dot), 60, 1, 0x32 }, + { 1688, 1280, 16, 144, 1066, 1024, 1, 3, VCLK135, /* 75Hz */ + (SyncPP | Charx8Dot), 75, 2, 0x32 }, + { 1728, 1280, 64, 160, 1072, 1024, 1, 3, VCLK157_5, /* 85Hz */ + (SyncPP | Charx8Dot), 85, 3, 0x32 }, + { 1728, 1280, 64, 160, 1072, 1024, 1, 3, VCLK157_5, /* end */ + (SyncPP | Charx8Dot), 0xFF, 3, 0x32 }, +}; + +static const struct ast_vbios_enhtable res_1600x1200[] = { + { 2160, 1600, 64, 192, 1250, 1200, 1, 3, VCLK162, /* 60Hz */ + (SyncPP | Charx8Dot), 60, 1, 0x33 }, + { 2160, 1600, 64, 192, 1250, 1200, 1, 3, VCLK162, /* end */ + (SyncPP | Charx8Dot), 0xFF, 1, 0x33 }, +}; + +static const struct ast_vbios_enhtable res_1152x864[] = { + { 1600, 1152, 64, 128, 900, 864, 1, 3, VCLK108, /* 75Hz */ + (SyncPP | Charx8Dot | NewModeInfo), 75, 1, 0x3B }, + { 1600, 1152, 64, 128, 900, 864, 1, 3, VCLK108, /* end */ + (SyncPP | Charx8Dot | NewModeInfo), 0xFF, 1, 0x3B }, +}; + +/* 16:9 */ +static const struct ast_vbios_enhtable res_1360x768[] = { + { 1792, 1360, 64, 112, 795, 768, 3, 6, VCLK85_5, /* 60Hz */ + (SyncPP | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo), + 60, 1, 0x39 }, + { 1792, 1360, 64, 112, 795, 768, 3, 6, VCLK85_5, /* end */ + (SyncPP | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo | + AST2500PreCatchCRT), + 0xFF, 1, 0x39 }, +}; + +static const struct ast_vbios_enhtable res_1600x900[] = { + { 1760, 1600, 48, 32, 926, 900, 3, 5, VCLK97_75, /* 60Hz CVT RB */ + (SyncNP | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo | + AST2500PreCatchCRT), + 60, 1, 0x3A }, + { 2112, 1600, 88, 168, 934, 900, 3, 5, VCLK118_25, /* 60Hz CVT */ + (SyncPN | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo), + 60, 2, 0x3A }, + { 2112, 1600, 88, 168, 934, 900, 3, 5, VCLK118_25, /* 60Hz CVT */ + (SyncPN | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo), + 0xFF, 2, 0x3A }, +}; + +static const struct ast_vbios_enhtable res_1920x1080[] = { + { 2200, 1920, 88, 44, 1125, 1080, 4, 5, VCLK148_5, /* 60Hz */ + (SyncPP | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo | + AST2500PreCatchCRT), + 60, 1, 0x38 }, + { 2200, 1920, 88, 44, 1125, 1080, 4, 5, VCLK148_5, /* 60Hz */ + (SyncPP | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo | + AST2500PreCatchCRT), + 0xFF, 1, 0x38 }, +}; + +/* 16:10 */ +static const struct ast_vbios_enhtable res_1280x800[] = { + { 1440, 1280, 48, 32, 823, 800, 3, 6, VCLK71, /* 60Hz RB */ + (SyncNP | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo | + AST2500PreCatchCRT), + 60, 1, 0x35 }, + { 1680, 1280, 72, 128, 831, 800, 3, 6, VCLK83_5, /* 60Hz */ + (SyncPN | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo), + 60, 2, 0x35 }, + { 1680, 1280, 72, 128, 831, 800, 3, 6, VCLK83_5, /* 60Hz */ + (SyncPN | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo), + 0xFF, 2, 0x35 }, + +}; + +static const struct ast_vbios_enhtable res_1440x900[] = { + { 1600, 1440, 48, 32, 926, 900, 3, 6, VCLK88_75, /* 60Hz RB */ + (SyncNP | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo | + AST2500PreCatchCRT), + 60, 1, 0x36 }, + { 1904, 1440, 80, 152, 934, 900, 3, 6, VCLK106_5, /* 60Hz */ + (SyncPN | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo), + 60, 2, 0x36 }, + { 1904, 1440, 80, 152, 934, 900, 3, 6, VCLK106_5, /* 60Hz */ + (SyncPN | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo), + 0xFF, 2, 0x36 }, +}; + +static const struct ast_vbios_enhtable res_1680x1050[] = { + { 1840, 1680, 48, 32, 1080, 1050, 3, 6, VCLK119, /* 60Hz RB */ + (SyncNP | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo | + AST2500PreCatchCRT), + 60, 1, 0x37 }, + { 2240, 1680, 104, 176, 1089, 1050, 3, 6, VCLK146_25, /* 60Hz */ + (SyncPN | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo), + 60, 2, 0x37 }, + { 2240, 1680, 104, 176, 1089, 1050, 3, 6, VCLK146_25, /* 60Hz */ + (SyncPN | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo), + 0xFF, 2, 0x37 }, +}; + +static const struct ast_vbios_enhtable res_1920x1200[] = { + { 2080, 1920, 48, 32, 1235, 1200, 3, 6, VCLK154, /* 60Hz RB*/ + (SyncNP | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo | + AST2500PreCatchCRT), + 60, 1, 0x34 }, + { 2080, 1920, 48, 32, 1235, 1200, 3, 6, VCLK154, /* 60Hz RB */ + (SyncNP | Charx8Dot | LineCompareOff | WideScreenMode | NewModeInfo | + AST2500PreCatchCRT), + 0xFF, 1, 0x34 }, +}; + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_drv.c b/drivers/gpu/drm/loongson/lsdc_drv.c index 188ec82afcfbcbc711e351d43afdd473a054ec19..75329eb31e792d9e96dc5a4cca28f72053da34ab 100644 --- a/drivers/gpu/drm/loongson/lsdc_drv.c +++ b/drivers/gpu/drm/loongson/lsdc_drv.c @@ -30,6 +30,10 @@ #define DRIVER_MINOR 0 #define DRIVER_PATCHLEVEL 0 +int loongson_lg100_support; +MODULE_PARM_DESC(LG100_support, "LG100 support (1 = enabled, 0 = disabled)"); +module_param_named(LG100_support, loongson_lg100_support, int, 0444); + DEFINE_DRM_GEM_FOPS(lsdc_gem_fops); static const struct drm_driver lsdc_drm_driver = { @@ -264,6 +268,11 @@ static int lsdc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) struct lsdc_device *ldev; int ret; + /* loongson drm driver will not be used on 7A2000 */ + if ((enum loongson_chip_id)ent->driver_data == CHIP_LS7A2000 && + !loongson_lg100_support) + return -ENODEV; + descp = lsdc_device_probe(pdev, ent->driver_data); if (IS_ERR_OR_NULL(descp)) return -ENODEV; diff --git a/drivers/gpu/drm/phytium/Kconfig b/drivers/gpu/drm/phytium/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..5f540962129a5a140ed576c617f406468f46339b --- /dev/null +++ b/drivers/gpu/drm/phytium/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config DRM_PHYTIUM + tristate "DRM Support for Phytium Graphics Card" + depends on DRM && ARCH_PHYTIUM + select DRM_KMS_HELPER + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_HDCP_HELPER + help + Choose this option if you have a phytium graphics card. + This driver provides kernel mode setting and buffer management to userspace. diff --git a/drivers/gpu/drm/phytium/Makefile b/drivers/gpu/drm/phytium/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..1f68cdcd80dac4071c5d3f1578ea7e0d85fbccfd --- /dev/null +++ b/drivers/gpu/drm/phytium/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only + +phytium-dc-drm-y := phytium_display_drv.o \ + phytium_plane.o \ + phytium_crtc.o \ + phytium_dp.o \ + phytium_fb.o \ + phytium_gem.o \ + phytium_fbdev.o \ + phytium_debugfs.o \ + px210_dp.o \ + phytium_panel.o \ + px210_dc.o \ + phytium_pci.o \ + pe220x_dp.o \ + pe220x_dc.o \ + phytium_platform.o + +obj-$(CONFIG_DRM_PHYTIUM) += phytium-dc-drm.o +CFLAGS_REMOVE_phytium_crtc.o += -mgeneral-regs-only diff --git a/drivers/gpu/drm/phytium/pe220x_dc.c b/drivers/gpu/drm/phytium/pe220x_dc.c new file mode 100644 index 0000000000000000000000000000000000000000..8f74199f9a477422246896a885c20b4da5f3c6ec --- /dev/null +++ b/drivers/gpu/drm/phytium/pe220x_dc.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium Pe220x display controller DRM driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include "phytium_display_drv.h" +#include "pe220x_reg.h" +#include "phytium_crtc.h" +#include "phytium_plane.h" +#include "phytium_fb.h" +#include "phytium_gem.h" + +void pe220x_dc_hw_disable(struct drm_crtc *crtc); + +static const unsigned int pe220x_primary_formats[] = { + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_BGRA1010102, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_BGRA4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_RGBX4444, + DRM_FORMAT_BGRX4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_YUYV, + DRM_FORMAT_UYVY, + DRM_FORMAT_NV16, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, +}; + +static uint64_t pe220x_primary_formats_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static uint64_t pe220x_cursor_formats_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static const unsigned int pe220x_cursor_formats[] = { + DRM_FORMAT_ARGB8888, +}; + +void pe220x_dc_hw_vram_init(struct phytium_display_private *priv, resource_size_t vram_addr, + resource_size_t vram_size) +{ + uint32_t config; + uint32_t group_offset = priv->address_transform_base; + + phytium_writel_reg(priv, (vram_addr & SRC_ADDR_MASK) >> SRC_ADDR_OFFSET, + group_offset, PE220X_DC_ADDRESS_TRANSFORM_SRC_ADDR); + phytium_writel_reg(priv, (vram_size >> SIZE_OFFSET) | ADDRESS_TRANSFORM_ENABLE, + group_offset, PE220X_DC_ADDRESS_TRANSFORM_SIZE); + config = phytium_readl_reg(priv, group_offset, PE220X_DC_ADDRESS_TRANSFORM_DST_ADDR); + phytium_writel_reg(priv, config, group_offset, PE220X_DC_ADDRESS_TRANSFORM_DST_ADDR); +} + +void pe220x_dc_hw_config_pix_clock(struct drm_crtc *crtc, int clock) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + int phys_pipe = phytium_crtc->phys_pipe; + int ret = 0; + + /* config pix clock */ + phytium_writel_reg(priv, FLAG_REQUEST | CMD_PIXEL_CLOCK | (clock & PIXEL_CLOCK_MASK), + 0, PE220X_DC_CMD_REGISTER(phys_pipe)); + ret = phytium_wait_cmd_done(priv, PE220X_DC_CMD_REGISTER(phys_pipe), + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to set pixel clock\n", __func__); +} + +void pe220x_dc_hw_reset(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + int config = 0; + int phys_pipe = phytium_crtc->phys_pipe; + + /* disable pixel clock for bmc mode */ + if (phys_pipe == 0) + pe220x_dc_hw_disable(crtc); + + config = phytium_readl_reg(priv, 0, PE220X_DC_CLOCK_CONTROL); + config &= (~(DC0_CORE_RESET | DC1_CORE_RESET | AXI_RESET | AHB_RESET)); + + if (phys_pipe == 0) { + phytium_writel_reg(priv, config | DC0_CORE_RESET, + 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config | DC0_CORE_RESET | AXI_RESET, + 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config | DC0_CORE_RESET | AXI_RESET | AHB_RESET, + 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config | DC0_CORE_RESET | AXI_RESET, + 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config | DC0_CORE_RESET, + 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config, 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + } else { + phytium_writel_reg(priv, config | DC1_CORE_RESET, + 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config | DC1_CORE_RESET | AXI_RESET, + 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config | DC1_CORE_RESET | AXI_RESET | AHB_RESET, + 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config | DC1_CORE_RESET | AXI_RESET, + 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config | DC1_CORE_RESET, + 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config, 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + } +} + +void pe220x_dc_hw_disable(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + int config = 0; + int phys_pipe = phytium_crtc->phys_pipe; + + /* clear framebuffer */ + phytium_writel_reg(priv, CLEAR_VALUE_BLACK, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_FRAMEBUFFER_CLEARVALUE); + config = phytium_readl_reg(priv, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_FRAMEBUFFER_CONFIG); + config |= FRAMEBUFFER_CLEAR; + phytium_writel_reg(priv, config, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_FRAMEBUFFER_CONFIG); + + /* disable cursor */ + config = phytium_readl_reg(priv, priv->dc_reg_base[phys_pipe], PHYTIUM_DC_CURSOR_CONFIG); + config = ((config & (~CURSOR_FORMAT_MASK)) | CURSOR_FORMAT_DISABLED); + phytium_writel_reg(priv, config, priv->dc_reg_base[phys_pipe], PHYTIUM_DC_CURSOR_CONFIG); + mdelay(20); + + /* reset pix clock */ + pe220x_dc_hw_config_pix_clock(crtc, 0); + + if (phys_pipe == 0) { + config = phytium_readl_reg(priv, 0, PE220X_DC_CLOCK_CONTROL); + phytium_writel_reg(priv, config | DC0_CORE_RESET, 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config & (~DC0_CORE_RESET), 0, PE220X_DC_CLOCK_CONTROL); + } else { + config = phytium_readl_reg(priv, 0, PE220X_DC_CLOCK_CONTROL); + phytium_writel_reg(priv, config | DC1_CORE_RESET, 0, PE220X_DC_CLOCK_CONTROL); + udelay(20); + phytium_writel_reg(priv, config & (~DC1_CORE_RESET), 0, PE220X_DC_CLOCK_CONTROL); + } + udelay(20); +} + +int pe220x_dc_hw_fb_format_check(const struct drm_mode_fb_cmd2 *mode_cmd, int count) +{ + int ret = 0; + + if (mode_cmd->modifier[count] != DRM_FORMAT_MOD_LINEAR) { + DRM_ERROR("unsupported fb modifier 0x%llx\n", mode_cmd->modifier[count]); + ret = -EINVAL; + } + + return ret; +} + +void pe220x_dc_hw_plane_get_primary_format(const uint64_t **format_modifiers, + const uint32_t **formats, + uint32_t *format_count) +{ + *format_modifiers = pe220x_primary_formats_modifiers; + *formats = pe220x_primary_formats; + *format_count = ARRAY_SIZE(pe220x_primary_formats); +} + +void pe220x_dc_hw_plane_get_cursor_format(const uint64_t **format_modifiers, + const uint32_t **formats, + uint32_t *format_count) +{ + *format_modifiers = pe220x_cursor_formats_modifiers; + *formats = pe220x_cursor_formats; + *format_count = ARRAY_SIZE(pe220x_cursor_formats); +} + +void pe220x_dc_hw_update_primary_hi_addr(struct drm_plane *plane) +{ + struct drm_device *dev = plane->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_plane *phytium_plane = to_phytium_plane(plane); + int phys_pipe = phytium_plane->phys_pipe; + + phytium_writel_reg(priv, (phytium_plane->iova[0] >> PREFIX_SHIFT) & PREFIX_MASK, + priv->dc_reg_base[phys_pipe], PE220X_DC_FRAMEBUFFER_Y_HI_ADDRESS); + + phytium_writel_reg(priv, (phytium_plane->iova[1] >> U_PREFIX_SHIFT) & U_PREFIX_MASK, + priv->dc_reg_base[phys_pipe], PE220X_DC_FRAMEBUFFER_U_HI_ADDRESS); + + phytium_writel_reg(priv, (phytium_plane->iova[2] >> V_PREFIX_SHIFT) & V_PREFIX_MASK, + priv->dc_reg_base[phys_pipe], PE220X_DC_FRAMEBUFFER_V_HI_ADDRESS); +} + +void pe220x_dc_hw_update_cursor_hi_addr(struct drm_plane *plane, uint64_t iova) +{ + struct drm_device *dev = plane->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_plane *phytium_plane = to_phytium_plane(plane); + int phys_pipe = phytium_plane->phys_pipe; + int config; + + config = ((iova >> CURSOR_PREFIX_SHIFT) & CURSOR_PREFIX_MASK); + phytium_writel_reg(priv, config, priv->dc_reg_base[phys_pipe], PE220X_DC_CURSOR_HI_ADDRESS); +} diff --git a/drivers/gpu/drm/phytium/pe220x_dc.h b/drivers/gpu/drm/phytium/pe220x_dc.h new file mode 100644 index 0000000000000000000000000000000000000000..f88a054cf0d0e82343132e1cc0f60f45c4347097 --- /dev/null +++ b/drivers/gpu/drm/phytium/pe220x_dc.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium Pe220x display controller DRM driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PE220X_DC_H__ +#define __PE220X_DC_H__ + +#define PE220X_DC_PIX_CLOCK_MAX (594000) +#define PE220X_DC_HDISPLAY_MAX 3840 +#define PE220X_DC_VDISPLAY_MAX 2160 +#define PE220X_DC_ADDRESS_MASK 0x7f + +extern void pe220x_dc_hw_vram_init(struct phytium_display_private *priv, + resource_size_t vram_addr, + resource_size_t vram_size); +extern void pe220x_dc_hw_config_pix_clock(struct drm_crtc *crtc, int clock); +extern void pe220x_dc_hw_disable(struct drm_crtc *crtc); +extern int pe220x_dc_hw_fb_format_check(const struct drm_mode_fb_cmd2 *mode_cmd, int count); +extern void pe220x_dc_hw_plane_get_primary_format(const uint64_t **format_modifiers, + const uint32_t **formats, + uint32_t *format_count); +extern void pe220x_dc_hw_plane_get_cursor_format(const uint64_t **format_modifiers, + const uint32_t **formats, + uint32_t *format_count); +extern void pe220x_dc_hw_update_primary_hi_addr(struct drm_plane *plane); +extern void pe220x_dc_hw_update_cursor_hi_addr(struct drm_plane *plane, uint64_t iova); +void pe220x_dc_hw_reset(struct drm_crtc *crtc); +#endif /* __PE220X_DC_H__ */ diff --git a/drivers/gpu/drm/phytium/pe220x_dp.c b/drivers/gpu/drm/phytium/pe220x_dp.c new file mode 100644 index 0000000000000000000000000000000000000000..54a6e8ac454b22e0cad76699a4ae59a6202f6de2 --- /dev/null +++ b/drivers/gpu/drm/phytium/pe220x_dp.c @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium display port DRM driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include "phytium_display_drv.h" +#include "pe220x_reg.h" +#include "phytium_dp.h" +#include "pe220x_dp.h" + +static uint8_t pe220x_dp_source_lane_count[2] = {1, 1}; + +/* [reg][ling_rate 1.62->8.1] */ +static int vco_val[12][4] = { + {0x0509, 0x0509, 0x0509, 0x0509}, /* CP_PADJ */ + {0x0f00, 0x0f00, 0x0f00, 0x0f00}, /* CP_IADJ */ + {0x0F08, 0x0F08, 0x0F08, 0x0F08}, /* FILT_PADJ */ + {0x0061, 0x006C, 0x006C, 0x0051}, /* INTDIV */ + {0x3333, 0x0000, 0x0000, 0x0000}, /* FRACDIVL */ + {0x0000, 0x0000, 0x0000, 0x0000}, /* FRACDIVH */ + {0x0042, 0x0048, 0x0048, 0x0036}, /* HIGH_THR */ + {0x0002, 0x0002, 0x0002, 0x0002}, /* PDIAG_CTRL */ + {0x0c5e, 0x0c5e, 0x0c5e, 0x0c5e}, /* VCOCAL_PLLCNT_START */ + {0x00c7, 0x00c7, 0x00c7, 0x00c7}, /* LOCK_PEFCNT */ + {0x00c7, 0x00c7, 0x00c7, 0x00c7}, /* LOCK_PLLCNT_START */ + {0x0005, 0x0005, 0x0005, 0x0005}, /* LOCK_PLLCNT_THR */ +}; + +/* [link_rate][swing][emphasis] */ +static int mgnfs_val[4][4][4] = { + /* 1.62Gbps */ + { + {0x0026, 0x001f, 0x0012, 0x0000}, + {0x0013, 0x0013, 0x0000, 0x0000}, + {0x0006, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + /* 2.7Gbps */ + { + {0x0026, 0x001f, 0x0012, 0x0000}, + {0x0013, 0x0013, 0x0000, 0x0000}, + {0x0006, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + /* 5.4Gbps */ + { + {0x001f, 0x0013, 0x005, 0x0000}, + {0x0018, 0x006, 0x0000, 0x0000}, + {0x000c, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + /* 8.1Gbps */ + { + {0x0026, 0x0013, 0x005, 0x0000}, + {0x0013, 0x006, 0x0000, 0x0000}, + {0x0006, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, +}; + +/* [link_rate][swing][emphasis] */ +static int cpost_val[4][4][4] = { + /* 1.62Gbps */ + { + {0x0000, 0x0014, 0x0020, 0x002a}, + {0x0000, 0x0010, 0x001f, 0x0000}, + {0x0000, 0x0013, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + /* 2.7Gbps */ + { + {0x0000, 0x0014, 0x0020, 0x002a}, + {0x0000, 0x0010, 0x001f, 0x0000}, + {0x0000, 0x0013, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + /* 5.4Gbps */ + { + {0x0005, 0x0014, 0x0022, 0x002e}, + {0x0000, 0x0013, 0x0020, 0x0000}, + {0x0000, 0x0013, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + /* 8.1Gbps */ + { + {0x0000, 0x0014, 0x0022, 0x002e}, + {0x0000, 0x0013, 0x0020, 0x0000}, + {0x0000, 0x0013, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, +}; + +static int pe220x_dp_hw_set_phy_lane_and_rate(struct phytium_dp_device *phytium_dp, + uint8_t link_lane_count, uint32_t link_rate) +{ + int port = phytium_dp->port%2; + int i = 0, data, tmp, tmp1, index = 0, mask = 0; + int timeout = 500, ret = 0; + + /* set pma powerdown */ + data = 0; + for (i = 0; i < phytium_dp->source_max_lane_count; i++) + data |= (A3_POWERDOWN3 << (i * A3_POWERDOWN3_SHIFT)); + phytium_phy_writel(phytium_dp, PE220X_PHY_PMA0_POWER(port), data); + + /* lane pll disable */ + data = 0; + for (i = 0; i < phytium_dp->source_max_lane_count; i++) { + data |= (PLL_EN << (i * PLL_EN_SHIFT)); + mask |= (((1<source_max_lane_count; i++) + data |= (PLL_EN << (i * PLL_EN_SHIFT)); + phytium_phy_writel(phytium_dp, PE220X_PHY_PLL_EN(port), data); + + /* set pma power active */ + data = 0; + for (i = 0; i < phytium_dp->source_max_lane_count; i++) + data |= (A0_ACTIVE << (i * A0_ACTIVE_SHIFT)); + phytium_phy_writel(phytium_dp, PE220X_PHY_PMA0_POWER(port), data); + + mask = PLL0_LOCK_DONE; + do { + mdelay(1); + timeout--; + tmp = phytium_phy_readl(phytium_dp, PE220X_PHY_PMA_CONTROL2(port)); + } while ((!(tmp & mask)) && timeout); + + if (timeout == 0) { + DRM_ERROR("dp(%d) phy pll lock failed\n", port); + ret = -1; + } + udelay(1); + + return ret; +} + +static void pe220x_dp_hw_set_phy_lane_setting(struct phytium_dp_device *phytium_dp, + uint32_t link_rate, uint8_t train_set) +{ + int port = phytium_dp->port % 3; + int voltage_swing = 0; + int pre_emphasis = 0, link_rate_index = 0; + + switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: + voltage_swing = 1; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: + voltage_swing = 2; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: + voltage_swing = 3; + break; + default: + voltage_swing = 0; + break; + } + + switch (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) { + case DP_TRAIN_PRE_EMPH_LEVEL_1: + pre_emphasis = 1; + break; + case DP_TRAIN_PRE_EMPH_LEVEL_2: + pre_emphasis = 2; + break; + case DP_TRAIN_PRE_EMPH_LEVEL_3: + pre_emphasis = 3; + break; + default: + pre_emphasis = 0; + break; + } + + switch (link_rate) { + case 810000: + link_rate_index = 3; + break; + case 540000: + link_rate_index = 2; + break; + case 270000: + link_rate_index = 1; + break; + case 162000: + link_rate_index = 0; + break; + default: + DRM_ERROR("phytium dp rate(%d) not support\n", link_rate); + link_rate_index = 2; + break; + } + + phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_DIAG_ACYA(port), LOCK); + phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_TXCC_CTRL(port), TX_TXCC_CTRL); + phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_DRV(port), TX_DRV); + phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_MGNFS(port), + mgnfs_val[link_rate_index][voltage_swing][pre_emphasis]); + phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_CPOST(port), + cpost_val[link_rate_index][voltage_swing][pre_emphasis]); + phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_DIAG_ACYA(port), UNLOCK); +} + +static int pe220x_dp_hw_init_phy(struct phytium_dp_device *phytium_dp) +{ + int port = phytium_dp->port; + int i = 0, data, tmp, mask; + int timeout = 500, ret = 0; + + phytium_phy_writel(phytium_dp, PE220X_PHY_APB_RESET(port), APB_RESET); + phytium_phy_writel(phytium_dp, PE220X_PHY_PIPE_RESET(port), RESET); + + /* config lane to dp mode */ + data = 0; + for (i = 0; i < phytium_dp->source_max_lane_count; i++) + data |= (LANE_BIT << (i * LANE_BIT_SHIFT)); + phytium_phy_writel(phytium_dp, PE220X_PHY_MODE(port), data); + + /* pll clock enable */ + data = 0; + for (i = 0; i < phytium_dp->source_max_lane_count; i++) + data |= (PLL_EN << (i * PLL_EN_SHIFT)); + phytium_phy_writel(phytium_dp, PE220X_PHY_PLL_EN(port), data); + + /* config input 20 bit */ + data = 0; + for (i = 0; i < phytium_dp->source_max_lane_count; i++) + data |= (BIT_20 << (i * BIT_20_SHIFT)); + phytium_phy_writel(phytium_dp, PE220X_PHY_PMA_WIDTH(port), data); + + /* config lane active power state */ + data = 0; + for (i = 0; i < phytium_dp->source_max_lane_count; i++) + data |= (A0_ACTIVE << (i * A0_ACTIVE_SHIFT)); + phytium_phy_writel(phytium_dp, PE220X_PHY_PMA0_POWER(port), data); + + /* link reset */ + phytium_phy_writel(phytium_dp, PE220X_PHY_LINK_RESET(port), LINK_RESET); + + phytium_phy_writel(phytium_dp, PE220X_PHY_SGMII_DPSEL_INIT(port), DP_SEL); + + /* config single link */ + phytium_phy_writel(phytium_dp, PE220X_PHY_PLL_CFG(port), SINGLE_LINK); + + /* pipe reset */ + phytium_phy_writel(phytium_dp, PE220X_PHY_PIPE_RESET(port), RESET_DEASSERT); + + mask = PLL0_LOCK_DONE; + do { + mdelay(1); + timeout--; + tmp = phytium_phy_readl(phytium_dp, PE220X_PHY_PMA_CONTROL2(port)); + } while ((!(tmp & mask)) && timeout); + + if (timeout == 0) { + DRM_ERROR("reset dp(%d) phy failed\n", port); + ret = -1; + } + udelay(1); + + return ret; +} + +static void pe220x_dp_hw_poweron_panel(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + int ret = 0; + + phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | PANEL_POWER_ENABLE, + 0, PE220X_DC_CMD_REGISTER(port)); + ret = phytium_wait_cmd_done(priv, PE220X_DC_CMD_REGISTER(port), + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to poweron panel\n", __func__); +} + +static void pe220x_dp_hw_poweroff_panel(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + int ret = 0; + + phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | PANEL_POWER_DISABLE, + 0, PE220X_DC_CMD_REGISTER(port)); + ret = phytium_wait_cmd_done(priv, PE220X_DC_CMD_REGISTER(port), + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to poweroff panel\n", __func__); +} + +static void pe220x_dp_hw_enable_backlight(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port, ret = 0; + + phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | BACKLIGHT_ENABLE, + 0, PE220X_DC_CMD_REGISTER(port)); + ret = phytium_wait_cmd_done(priv, PE220X_DC_CMD_REGISTER(port), + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to enable backlight\n", __func__); +} + +static void pe220x_dp_hw_disable_backlight(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + int ret = 0; + + phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | BACKLIGHT_DISABLE, + 0, PE220X_DC_CMD_REGISTER(port)); + ret = phytium_wait_cmd_done(priv, PE220X_DC_CMD_REGISTER(port), + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to disable backlight\n", __func__); +} + +static uint32_t pe220x_dp_hw_get_backlight(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int config; + uint32_t group_offset = priv->address_transform_base; + + config = phytium_readl_reg(priv, group_offset, PE220X_DC_ADDRESS_TRANSFORM_BACKLIGHT_VALUE); + return ((config >> BACKLIGHT_VALUE_SHIFT) & BACKLIGHT_VALUE_MASK); +} + +static int pe220x_dp_hw_set_backlight(struct phytium_dp_device *phytium_dp, uint32_t level) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + int config = 0; + int ret = 0; + + if (level > PE220X_DP_BACKLIGHT_MAX) { + ret = -EINVAL; + goto out; + } + + config = FLAG_REQUEST | CMD_BACKLIGHT | ((level & BACKLIGHT_MASK) << BACKLIGHT_SHIFT); + phytium_writel_reg(priv, config, 0, PE220X_DC_CMD_REGISTER(port)); + ret = phytium_wait_cmd_done(priv, PE220X_DC_CMD_REGISTER(port), + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to set backlight\n", __func__); +out: + return ret; +} + +bool pe220x_dp_hw_spread_is_enable(struct phytium_dp_device *phytium_dp) +{ + return false; +} + +int pe220x_dp_hw_reset(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + + phytium_writel_reg(priv, DP_RESET, group_offset, PE220X_DP_CONTROLLER_RESET); + udelay(500); + phytium_writel_reg(priv, AUX_CLK_DIVIDER_100, group_offset, PHYTIUM_DP_AUX_CLK_DIVIDER); + phytium_writel_reg(priv, SUPPORT_EDP_1_4, group_offset, PHYTIUM_EDP_CRC_ENABLE); + + return 0; +} + +uint8_t pe220x_dp_hw_get_source_lane_count(struct phytium_dp_device *phytium_dp) +{ + return pe220x_dp_source_lane_count[phytium_dp->port]; +} + +static struct phytium_dp_func pe220x_dp_funcs = { + .dp_hw_get_source_lane_count = pe220x_dp_hw_get_source_lane_count, + .dp_hw_reset = pe220x_dp_hw_reset, + .dp_hw_spread_is_enable = pe220x_dp_hw_spread_is_enable, + .dp_hw_set_backlight = pe220x_dp_hw_set_backlight, + .dp_hw_get_backlight = pe220x_dp_hw_get_backlight, + .dp_hw_disable_backlight = pe220x_dp_hw_disable_backlight, + .dp_hw_enable_backlight = pe220x_dp_hw_enable_backlight, + .dp_hw_poweroff_panel = pe220x_dp_hw_poweroff_panel, + .dp_hw_poweron_panel = pe220x_dp_hw_poweron_panel, + .dp_hw_init_phy = pe220x_dp_hw_init_phy, + .dp_hw_set_phy_lane_setting = pe220x_dp_hw_set_phy_lane_setting, + .dp_hw_set_phy_lane_and_rate = pe220x_dp_hw_set_phy_lane_and_rate, +}; + +void pe220x_dp_func_register(struct phytium_dp_device *phytium_dp) +{ + phytium_dp->funcs = &pe220x_dp_funcs; +} diff --git a/drivers/gpu/drm/phytium/pe220x_dp.h b/drivers/gpu/drm/phytium/pe220x_dp.h new file mode 100644 index 0000000000000000000000000000000000000000..6b763d9966310115dd8fdeed8895c0b9b64e9bd7 --- /dev/null +++ b/drivers/gpu/drm/phytium/pe220x_dp.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium display port DRM driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PE220X_DP_H__ +#define __PE220X_DP_H__ + +#define PE220X_DP_BACKLIGHT_MAX 100 + +void pe220x_dp_func_register(struct phytium_dp_device *phytium_dp); +#endif /* __PE220X_DP_H__ */ diff --git a/drivers/gpu/drm/phytium/pe220x_reg.h b/drivers/gpu/drm/phytium/pe220x_reg.h new file mode 100644 index 0000000000000000000000000000000000000000..88fc9c7383a58da2d059187bc7b0067a3f4ef378 --- /dev/null +++ b/drivers/gpu/drm/phytium/pe220x_reg.h @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium Pe220x display engine register + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PE220X_REG_H__ +#define __PE220X_REG_H__ + +#include "phytium_reg.h" + +/* dc register */ +#define PE220X_DC_CLOCK_CONTROL 0x0000 +#define DC1_CORE_RESET (1<<18) +#define DC0_CORE_RESET (1<<17) +#define AXI_RESET (1<<16) +#define AHB_RESET (1<<12) + +#define PE220X_DC_CMD_REGISTER(pipe) (PE220X_DC_BASE(0) + 0x00F0 + 0x4*(pipe)) +#define FLAG_REPLY (1<<31) +#define FLAG_REQUEST (1<<30) +#define CMD_PIXEL_CLOCK (0x0 << 28) +#define CMD_BACKLIGHT (0x1 << 28) +#define CMD_DC_DP_RESET (0x3 << 28) +#define BACKLIGHT_SHIFT 21 +#define BACKLIGHT_MASK 0x7f +#define BACKLIGHT_MAX 100 +#define BACKLIGHT_ENABLE (101 << BACKLIGHT_SHIFT) +#define BACKLIGHT_DISABLE (102 << BACKLIGHT_SHIFT) +#define PANEL_POWER_ENABLE (103 << BACKLIGHT_SHIFT) +#define PANEL_POWER_DISABLE (104 << BACKLIGHT_SHIFT) +#define PIXEL_CLOCK_MASK (0x1fffff) + +#define PE220X_DC_FRAMEBUFFER_Y_HI_ADDRESS 0x1404 +#define PREFIX_MASK 0xff +#define PREFIX_SHIFT 32 + +#define PE220X_DC_CURSOR_HI_ADDRESS 0x1490 +#define CURSOR_PREFIX_MASK 0xff +#define CURSOR_PREFIX_SHIFT 32 + +#define PE220X_DC_FRAMEBUFFER_U_HI_ADDRESS 0x1534 +#define U_PREFIX_MASK 0xff +#define U_PREFIX_SHIFT 32 + +#define PE220X_DC_FRAMEBUFFER_V_HI_ADDRESS 0x153c +#define V_PREFIX_MASK 0xff +#define V_PREFIX_SHIFT 32 + +/* dp register */ +#define PE220X_DP_CONTROLLER_RESET 0x0850 +#define DP_RESET 0x1 + +/* address transform register */ +#define PE220X_DC_ADDRESS_TRANSFORM_SRC_ADDR 0x0 +#define SRC_ADDR_OFFSET 22 +#define SRC_ADDR_MASK 0xffffffffff + +#define PE220X_DC_ADDRESS_TRANSFORM_SIZE 0x4 +#define ADDRESS_TRANSFORM_ENABLE (0x1 << 31) +#define SIZE_OFFSET 22 + +#define PE220X_DC_ADDRESS_TRANSFORM_DST_ADDR 0x8 +#define DST_ADDR_OFFSET 22 + +#define PE220X_DC_ADDRESS_TRANSFORM_DP_RESET_STATUS 0x48 +#define DC_DP_RESET_STATUS(pipe) (1 << pipe) +#define DP_SPREAD_ENABLE(pipe) (0x8 << pipe) + +#define PE220X_DC_ADDRESS_TRANSFORM_BACKLIGHT_VALUE 0x4c +#define BACKLIGHT_VALUE_MASK (0x7f) +#define BACKLIGHT_VALUE_SHIFT 16 + +/* phy register start */ +#define PE220X_PHY_BASE(pipe) (0x100000*pipe) + +#define PE220X_PHY_PIPE_RESET(pipe) (PE220X_PHY_BASE(pipe) + 0x40254) +#define RESET 0x0 +#define RESET_DEASSERT 0x1 + +#define PE220X_PHY_MODE(pipe) (PE220X_PHY_BASE(pipe) + 0x40034) +#define LANE_BIT (0x3) +#define LANE_BIT_SHIFT 0x2 + +#define PE220X_PHY_LINK_CFG(pipe) (PE220X_PHY_BASE(pipe) + 0x40044) +#define LANE_MASTER 0x1 +#define LANE_MASTER_SHIFT 1 + +#define PE220X_PHY_PLL_EN(pipe) (PE220X_PHY_BASE(pipe) + 0x40214) +#define PLL_EN 0x1 +#define PLL_EN_SHIFT 1 + +#define PE220X_PHY_PMA_WIDTH(pipe) (PE220X_PHY_BASE(pipe) + 0x4021c) +#define BIT_20 0x5 +#define BIT_20_SHIFT 4 + +#define PE220X_PHY_PLL_SOURCE_SEL(pipe) (PE220X_PHY_BASE(pipe) + 0x4004C) + +#define PE220X_PHY_PMA0_POWER(pipe) (PE220X_PHY_BASE(pipe) + 0x402bc) +#define A0_ACTIVE 0x1 +#define A0_ACTIVE_SHIFT 8 +#define A3_POWERDOWN3 0x8 +#define A3_POWERDOWN3_SHIFT 8 + +#define PE220X_PHY_LINK_RESET(pipe) (PE220X_PHY_BASE(pipe) + 0x40258) +#define LINK_RESET 0x1 +#define LINK_RESET_MASK 0x1 +#define LINTK_RESET_SHIFT 0x1 + +#define PE220X_PHY_SGMII_DPSEL_INIT(pipe) (PE220X_PHY_BASE(pipe) + 0x40260) +#define DP_SEL 0x1 + +#define PE220X_PHY_APB_RESET(pipe) (PE220X_PHY_BASE(pipe) + 0x40250) +#define APB_RESET 0x1 + +/* phy origin register */ +#define PE220X_PHY_PLL_CFG(pipe) (PE220X_PHY_BASE(pipe) + 0x30038) +#define SINGLE_LINK 0x0 + +#define PE220X_PHY_PMA_CONTROL(pipe) (PE220X_PHY_BASE(pipe) + 0x3800c) +#define CONTROL_ENABLE 0x1 +#define CONTROL_ENABLE_MASK 0x1 +#define CONTROL_ENABLE_SHIFT 0x1 + +#define PE220X_PHY_PMA_CONTROL2(pipe) (PE220X_PHY_BASE(pipe) + 0x38004) +#define PLL0_LOCK_DONE (0x1 << 6) + +#define PE220X_PHY_PLL0_CLK_SEL(pipe) (PE220X_PHY_BASE(pipe) + 0X684) +#define PLL_LINK_RATE_162000 0xf01 +#define PLL_LINK_RATE_270000 0x701 +#define PLL_LINK_RATE_540000 0x301 +#define PLL_LINK_RATE_810000 0x200 + +#define PE220X_PHY_HSCLK0_SEL(pipe) (PE220X_PHY_BASE(pipe) + 0x18398) +#define HSCLK_LINK_0 0x0 +#define HSCLK_LINK_1 0x1 + +#define PE220X_PHY_HSCLK0_DIV(pipe) (PE220X_PHY_BASE(pipe) + 0x1839c) +#define HSCLK_LINK_RATE_162000 0x2 +#define HSCLK_LINK_RATE_270000 0x1 +#define HSCLK_LINK_RATE_540000 0x0 +#define HSCLK_LINK_RATE_810000 0x0 + +#define PE220X_PHY_PLLDRC0_CTRL(pipe) (PE220X_PHY_BASE(pipe) + 0x18394) +#define PLLDRC_LINK0 0x1 +#define PLLDRC_LINK1 0x9 + +#define PE220X_PHY_PLL0_DSM_M0(pipe) (PE220X_PHY_BASE(pipe) + 0x250) +#define PLL0_DSM_M0 0x4 +#define PE220X_PHY_PLL0_VCOCAL_START(pipe) (PE220X_PHY_BASE(pipe) + 0x218) +#define PLL0_VCOCAL_START 0xc5e +#define PE220X_PHY_PLL0_VCOCAL_CTRL(pipe) (PE220X_PHY_BASE(pipe) + 0x208) +#define PLL0_VCOCAL_CTRL 0x3 + +#define PE220X_PHY_PLL0_CP_PADJ(pipe) (PE220X_PHY_BASE(pipe) + 0x690) +#define PE220X_PHY_PLL0_CP_IADJ(pipe) (PE220X_PHY_BASE(pipe) + 0x694) +#define PE220X_PHY_PLL0_CP_FILT_PADJ(pipe) (PE220X_PHY_BASE(pipe) + 0x698) +#define PE220X_PHY_PLL0_INTDIV(pipe) (PE220X_PHY_BASE(pipe) + 0x240) +#define PE220X_PHY_PLL0_FRACDIVL(pipe) (PE220X_PHY_BASE(pipe) + 0x244) +#define PE220X_PHY_PLL0_FRACDIVH(pipe) (PE220X_PHY_BASE(pipe) + 0x248) +#define PE220X_PHY_PLL0_HIGH_THR(pipe) (PE220X_PHY_BASE(pipe) + 0x24c) +#define PE220X_PHY_PLL0_PDIAG_CTRL(pipe) (PE220X_PHY_BASE(pipe) + 0x680) +#define PE220X_PHY_PLL0_VCOCAL_PLLCNT_START(pipe) (PE220X_PHY_BASE(pipe) + 0x220) +#define PE220X_PHY_PLL0_LOCK_PEFCNT(pipe) (PE220X_PHY_BASE(pipe) + 0x270) +#define PE220X_PHY_PLL0_LOCK_PLLCNT_START(pipe) (PE220X_PHY_BASE(pipe) + 0x278) +#define PE220X_PHY_PLL0_LOCK_PLLCNT_THR(pipe) (PE220X_PHY_BASE(pipe) + 0x27c) + +#define PE220X_PHY_PLL0_TX_PSC_A0(pipe) (PE220X_PHY_BASE(pipe) + 0x18400) +#define PLL0_TX_PSC_A0 0xfb +#define PE220X_PHY_PLL0_TX_PSC_A2(pipe) (PE220X_PHY_BASE(pipe) + 0x18408) +#define PLL0_TX_PSC_A2 0x4aa +#define PE220X_PHY_PLL0_TX_PSC_A3(pipe) (PE220X_PHY_BASE(pipe) + 0x1840c) +#define PLL0_TX_PSC_A3 0x4aa +#define PE220X_PHY_PLL0_RX_PSC_A0(pipe) (PE220X_PHY_BASE(pipe) + 0x28000) +#define PLL0_RX_PSC_A0 0x0 +#define PE220X_PHY_PLL0_RX_PSC_A2(pipe) (PE220X_PHY_BASE(pipe) + 0x28008) +#define PLL0_RX_PSC_A2 0x0 +#define PE220X_PHY_PLL0_RX_PSC_A3(pipe) (PE220X_PHY_BASE(pipe) + 0x2800C) +#define PLL0_RX_PSC_A3 0x0 +#define PE220X_PHY_PLL0_RX_PSC_CAL(pipe) (PE220X_PHY_BASE(pipe) + 0x28018) +#define PLL0_RX_PSC_CAL 0x0 + +#define PE220X_PHY_PLL0_XCVR_CTRL(pipe) (PE220X_PHY_BASE(pipe) + 0x183a8) +#define PLL0_XCVR_CTRL 0xf + +#define PE220X_PHY_PLL0_RX_GCSM1_CTRL(pipe) (PE220X_PHY_BASE(pipe) + 0x28420) +#define PLL0_RX_GCSM1_CTRL 0x0 +#define PE220X_PHY_PLL0_RX_GCSM2_CTRL(pipe) (PE220X_PHY_BASE(pipe) + 0x28440) +#define PLL0_RX_GCSM2_CTRL 0x0 +#define PE220X_PHY_PLL0_RX_PERGCSM_CTRL(pipe) (PE220X_PHY_BASE(pipe) + 0x28460) +#define PLL0_RX_PERGCSM_CTRL 0x0 + +/* swing and emphasis */ +#define PE220X_PHY_PLL0_TX_DIAG_ACYA(pipe) (PE220X_PHY_BASE(pipe) + 0x1879c) +#define LOCK 1 +#define UNLOCK 0 + +#define PE220X_PHY_PLL0_TX_TXCC_CTRL(pipe) (PE220X_PHY_BASE(pipe) + 0x18100) +#define TX_TXCC_CTRL 0x8a4 + +#define PE220X_PHY_PLL0_TX_DRV(pipe) (PE220X_PHY_BASE(pipe) + 0x18318) +#define TX_DRV 0x3 + +#define PE220X_PHY_PLL0_TX_MGNFS(pipe) (PE220X_PHY_BASE(pipe) + 0x18140) + +#define PE220X_PHY_PLL0_TX_CPOST(pipe) (PE220X_PHY_BASE(pipe) + 0x18130) + +#endif /* __PE220X_REG_H__ */ diff --git a/drivers/gpu/drm/phytium/phytium_crtc.c b/drivers/gpu/drm/phytium/phytium_crtc.c new file mode 100644 index 0000000000000000000000000000000000000000..628357837da6e34ca839e859eb85b75a7f705397 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_crtc.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include "phytium_display_drv.h" +#include "phytium_crtc.h" +#include "phytium_plane.h" +#include "phytium_dp.h" +#include "px210_dc.h" +#include "pe220x_dc.h" +#include "phytium_reg.h" + +#define MAXKERNELSIZE 9 +#define SUBPIXELINDEXBITS 5 +#define SUBPIXELCOUNT (1 << SUBPIXELINDEXBITS) +#define SUBPIXELLOADCOUNT (SUBPIXELCOUNT / 2 + 1) +#define WEIGHTSTATECOUNT (((SUBPIXELLOADCOUNT * MAXKERNELSIZE + 1) & ~1) / 2) +#define KERNELTABLESIZE (SUBPIXELLOADCOUNT * MAXKERNELSIZE * sizeof(uint16_t)) +#define PHYALIGN(n, align) (((n) + ((align) - 1)) & ~((align) - 1)) +#define KERNELSTATES (PHYALIGN(KERNELTABLESIZE + 4, 8)) +#define PHYPI 3.14159265358979323846f + +#define MATH_Add(X, Y) ((float)((X) + (Y))) +#define MATH_Multiply(X, Y) ((float)((X) * (Y))) +#define MATH_Divide(X, Y) ((float)((X) / (Y))) +#define MATH_DivideFromUInteger(X, Y) ((float)(X) / (float)(Y)) +#define MATH_I2Float(X) ((float)(X)) + +struct filter_blit_array { + uint8_t kernelSize; + uint32_t scaleFactor; + uint32_t *kernelStates; +}; + +static void phytium_crtc_gamma_set(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + int phys_pipe = phytium_crtc->phys_pipe; + uint32_t group_offset = priv->dc_reg_base[phys_pipe]; + uint32_t config = 0; + struct drm_crtc_state *state = crtc->state; + struct drm_color_lut *lut; + int i; + + if (state->gamma_lut) { + if (WARN((state->gamma_lut->length/sizeof(struct drm_color_lut) != GAMMA_INDEX_MAX), + "gamma size is not match\n")) + return; + lut = (struct drm_color_lut *)state->gamma_lut->data; + for (i = 0; i < GAMMA_INDEX_MAX; i++) { + phytium_writel_reg(priv, i, group_offset, PHYTIUM_DC_GAMMA_INDEX); + config = ((lut[i].red >> 6) & GAMMA_RED_MASK) << GAMMA_RED_SHIFT; + config |= (((lut[i].green >> 6) & GAMMA_GREEN_MASK) << GAMMA_GREEN_SHIFT); + config |= (((lut[i].blue >> 6) & GAMMA_BLUE_MASK) << GAMMA_BLUE_SHIFT); + phytium_writel_reg(priv, config, group_offset, PHYTIUM_DC_GAMMA_DATA); + } + } +} + +static void phytium_crtc_gamma_init(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + int phys_pipe = phytium_crtc->phys_pipe; + uint32_t group_offset = priv->dc_reg_base[phys_pipe]; + uint32_t config = 0; + uint16_t *red, *green, *blue; + int i; + + if (WARN((crtc->gamma_size != GAMMA_INDEX_MAX), "gamma size is not match\n")) + return; + + red = crtc->gamma_store; + green = red + crtc->gamma_size; + blue = green + crtc->gamma_size; + + for (i = 0; i < GAMMA_INDEX_MAX; i++) { + phytium_writel_reg(priv, i, group_offset, PHYTIUM_DC_GAMMA_INDEX); + config = ((*red++ >> 6) & GAMMA_RED_MASK) << GAMMA_RED_SHIFT; + config |= (((*green++ >> 6) & GAMMA_GREEN_MASK) << GAMMA_GREEN_SHIFT); + config |= (((*blue++ >> 6) & GAMMA_BLUE_MASK) << GAMMA_BLUE_SHIFT); + phytium_writel_reg(priv, config, group_offset, PHYTIUM_DC_GAMMA_DATA); + } +} + +static void phytium_crtc_destroy(struct drm_crtc *crtc) +{ + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + + drm_crtc_cleanup(crtc); + kfree(phytium_crtc); +} + +struct drm_crtc_state * +phytium_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct phytium_crtc_state *phytium_crtc_state = NULL; + + phytium_crtc_state = kmemdup(crtc->state, sizeof(*phytium_crtc_state), + GFP_KERNEL); + if (!phytium_crtc_state) + return NULL; + __drm_atomic_helper_crtc_duplicate_state(crtc, + &phytium_crtc_state->base); + + return &phytium_crtc_state->base; +} + +void +phytium_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct phytium_crtc_state *phytium_crtc_state = + to_phytium_crtc_state(state); + + phytium_crtc_state = to_phytium_crtc_state(state); + __drm_atomic_helper_crtc_destroy_state(state); + kfree(phytium_crtc_state); +} + +static int phytium_enable_vblank(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + int phys_pipe = phytium_crtc->phys_pipe; + + phytium_writel_reg(priv, INT_ENABLE, priv->dc_reg_base[phys_pipe], PHYTIUM_DC_INT_ENABLE); + + return 0; +} + +static void phytium_disable_vblank(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + int phys_pipe = phytium_crtc->phys_pipe; + + phytium_writel_reg(priv, INT_DISABLE, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_INT_ENABLE); +} + +static const struct drm_crtc_funcs phytium_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .destroy = phytium_crtc_destroy, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = phytium_crtc_atomic_duplicate_state, + .atomic_destroy_state = phytium_crtc_atomic_destroy_state, + .enable_vblank = phytium_enable_vblank, + .disable_vblank = phytium_disable_vblank, +}; + +static void +phytium_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct drm_connector_state *new_conn_state; + struct drm_connector *conn; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + int phys_pipe = phytium_crtc->phys_pipe; + uint32_t group_offset = priv->dc_reg_base[phys_pipe]; + int config = 0, i = 0; + + for_each_new_connector_in_state(state, conn, new_conn_state, i) { + if (new_conn_state->crtc != crtc) + continue; + + switch (conn->display_info.bpc) { + case 10: + phytium_crtc->bpc = DP_RGB101010; + break; + case 6: + phytium_crtc->bpc = DP_RGB666; + break; + default: + phytium_crtc->bpc = DP_RGB888; + break; + } + } + + /* config pix clock */ + phytium_crtc->dc_hw_config_pix_clock(crtc, mode->clock); + + config = ((mode->crtc_hdisplay & HDISPLAY_END_MASK) << HDISPLAY_END_SHIFT) + | ((mode->crtc_htotal&HDISPLAY_TOTAL_MASK) << HDISPLAY_TOTAL_SHIFT); + phytium_writel_reg(priv, config, group_offset, PHYTIUM_DC_HDISPLAY); + config = ((mode->crtc_hsync_start & HSYNC_START_MASK) << HSYNC_START_SHIFT) + | ((mode->crtc_hsync_end & HSYNC_END_MASK) << HSYNC_END_SHIFT) + | HSYNC_PULSE_ENABLED; + config |= (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : HSYNC_NEGATIVE; + phytium_writel_reg(priv, config, group_offset, PHYTIUM_DC_HSYNC); + config = ((mode->crtc_vdisplay & VDISPLAY_END_MASK) << VDISPLAY_END_SHIFT) + | ((mode->crtc_vtotal & VDISPLAY_TOTAL_MASK) << VDISPLAY_TOTAL_SHIFT); + phytium_writel_reg(priv, config, group_offset, PHYTIUM_DC_VDISPLAY); + config = ((mode->crtc_vsync_start & VSYNC_START_MASK) << VSYNC_START_SHIFT) + | ((mode->crtc_vsync_end & VSYNC_END_MASK) << VSYNC_END_SHIFT) + | VSYNC_PULSE_ENABLED; + config |= (mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : VSYNC_NEGATIVE; + phytium_writel_reg(priv, config, group_offset, PHYTIUM_DC_VSYNC); + config = PANEL_DATAENABLE_ENABLE | PANEL_DATA_ENABLE | PANEL_CLOCK_ENABLE; + phytium_writel_reg(priv, config, group_offset, PHYTIUM_DC_PANEL_CONFIG); + config = phytium_crtc->bpc | OUTPUT_DP; + phytium_writel_reg(priv, config, group_offset, PHYTIUM_DC_DP_CONFIG); + + config = phytium_readl_reg(priv, group_offset, PHYTIUM_DC_FRAMEBUFFER_CONFIG); + + if (crtc->state->active) + config |= FRAMEBUFFER_OUTPUT | FRAMEBUFFER_RESET; + else + config &= (~(FRAMEBUFFER_OUTPUT | FRAMEBUFFER_RESET)); + + if (phytium_crtc->scale_enable) + config |= FRAMEBUFFER_SCALE_ENABLE; + else + config &= (~FRAMEBUFFER_SCALE_ENABLE); + + if (crtc->state->gamma_lut) + phytium_crtc_gamma_set(crtc); + else + phytium_crtc_gamma_init(crtc); + + phytium_writel_reg(priv, config, group_offset, PHYTIUM_DC_FRAMEBUFFER_CONFIG); + drm_crtc_vblank_on(crtc); +} + +static void +phytium_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + + drm_crtc_vblank_off(crtc); + phytium_crtc->dc_hw_disable(crtc); +} + +static void phytium_crtc_update_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, + const struct drm_display_mode *native_mode) +{ + if (native_mode->clock == drm_mode->clock && + native_mode->htotal == drm_mode->htotal && + native_mode->vtotal == drm_mode->vtotal) { + drm_mode->crtc_hdisplay = native_mode->crtc_hdisplay; + drm_mode->crtc_vdisplay = native_mode->crtc_vdisplay; + drm_mode->crtc_clock = native_mode->crtc_clock; + drm_mode->crtc_hblank_start = native_mode->crtc_hblank_start; + drm_mode->crtc_hblank_end = native_mode->crtc_hblank_end; + drm_mode->crtc_hsync_start = native_mode->crtc_hsync_start; + drm_mode->crtc_hsync_end = native_mode->crtc_hsync_end; + drm_mode->crtc_htotal = native_mode->crtc_htotal; + drm_mode->crtc_hskew = native_mode->crtc_hskew; + drm_mode->crtc_vblank_start = native_mode->crtc_vblank_start; + drm_mode->crtc_vblank_end = native_mode->crtc_vblank_end; + drm_mode->crtc_vsync_start = native_mode->crtc_vsync_start; + drm_mode->crtc_vsync_end = native_mode->crtc_vsync_end; + drm_mode->crtc_vtotal = native_mode->crtc_vtotal; + } +} + +static int +phytium_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + struct drm_plane_state *new_plane_state = NULL; + int ret = 0; + struct drm_connector *connector; + struct drm_connector_state *new_con_state; + uint32_t i; + struct phytium_dp_device *phytium_dp = NULL; + + for_each_new_connector_in_state(state, connector, new_con_state, i) { + if (new_con_state->crtc == crtc) { + phytium_dp = connector_to_dp_device(connector); + break; + } + } + if (phytium_dp) + phytium_crtc_update_timing_for_drm_display_mode(&crtc_state->adjusted_mode, + &phytium_dp->native_mode); + + new_plane_state = drm_atomic_get_new_plane_state(crtc_state->state, + crtc->primary); + if (crtc_state->enable && new_plane_state && !new_plane_state->crtc) { + ret = -EINVAL; + goto fail; + } + + return 0; +fail: + return ret; +} + +static void +phytium_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + int phys_pipe = phytium_crtc->phys_pipe, config; + uint32_t group_offset = priv->dc_reg_base[phys_pipe]; + + config = phytium_readl_reg(priv, group_offset, PHYTIUM_DC_FRAMEBUFFER_CONFIG); + if (config & FRAMEBUFFER_RESET) { + phytium_writel_reg(priv, config | FRAMEBUFFER_VALID_PENDING, + group_offset, PHYTIUM_DC_FRAMEBUFFER_CONFIG); + } +} + +static void phytium_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + struct phytium_crtc_state *phytium_crtc_state = NULL; + int phys_pipe = phytium_crtc->phys_pipe, config; + uint32_t group_offset = priv->dc_reg_base[phys_pipe]; + + DRM_DEBUG_KMS("crtc->state active:%d enable:%d\n", + crtc->state->active, crtc->state->enable); + phytium_crtc_state = to_phytium_crtc_state(crtc->state); + + if (crtc->state->color_mgmt_changed) + phytium_crtc_gamma_set(crtc); + + config = phytium_readl_reg(priv, group_offset, PHYTIUM_DC_FRAMEBUFFER_CONFIG); + phytium_writel_reg(priv, config&(~FRAMEBUFFER_VALID_PENDING), + group_offset, PHYTIUM_DC_FRAMEBUFFER_CONFIG); + + if (crtc->state->event) { + DRM_DEBUG_KMS("vblank->refcount:%d\n", + atomic_read(&dev->vblank[0].refcount)); + spin_lock_irq(&dev->event_lock); + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, crtc->state->event); + else + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irq(&dev->event_lock); + } +} + +static enum drm_mode_status +phytium_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + + if (mode->crtc_clock > priv->info.crtc_clock_max) + return MODE_CLOCK_HIGH; + + if (mode->hdisplay > priv->info.hdisplay_max) + return MODE_BAD_HVALUE; + + if (mode->vdisplay > priv->info.vdisplay_max) + return MODE_BAD_VVALUE; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + + return MODE_OK; +} + +static const struct drm_crtc_helper_funcs phytium_crtc_helper_funcs = { + .mode_valid = phytium_crtc_mode_valid, + .atomic_check = phytium_crtc_atomic_check, + .atomic_begin = phytium_crtc_atomic_begin, + .atomic_flush = phytium_crtc_atomic_flush, + .atomic_enable = phytium_crtc_atomic_enable, + .atomic_disable = phytium_crtc_atomic_disable, +}; + +void phytium_crtc_resume(struct drm_device *drm_dev) +{ + struct drm_crtc *crtc; + struct phytium_crtc *phytium_crtc = NULL; + + drm_for_each_crtc(crtc, drm_dev) { + phytium_crtc = to_phytium_crtc(crtc); + if (phytium_crtc->dc_hw_reset) + phytium_crtc->dc_hw_reset(crtc); + phytium_crtc_gamma_init(crtc); + } +} + +int phytium_crtc_init(struct drm_device *dev, int phys_pipe) +{ + struct phytium_crtc *phytium_crtc; + struct phytium_crtc_state *phytium_crtc_state; + struct phytium_plane *phytium_primary_plane = NULL; + struct phytium_plane *phytium_cursor_plane = NULL; + struct phytium_display_private *priv = dev->dev_private; + int ret; + + phytium_crtc = kzalloc(sizeof(*phytium_crtc), GFP_KERNEL); + if (!phytium_crtc) { + ret = -ENOMEM; + goto failed_malloc_crtc; + } + + phytium_crtc_state = kzalloc(sizeof(*phytium_crtc_state), GFP_KERNEL); + if (!phytium_crtc_state) { + ret = -ENOMEM; + goto failed_malloc_crtc_state; + } + + phytium_crtc_state->base.crtc = &phytium_crtc->base; + phytium_crtc->base.state = &phytium_crtc_state->base; + phytium_crtc->phys_pipe = phys_pipe; + + if (IS_PX210(priv)) { + phytium_crtc->dc_hw_config_pix_clock = px210_dc_hw_config_pix_clock; + phytium_crtc->dc_hw_disable = px210_dc_hw_disable; + phytium_crtc->dc_hw_reset = NULL; + priv->dc_reg_base[phys_pipe] = PX210_DC_BASE(phys_pipe); + priv->dcreq_reg_base[phys_pipe] = PX210_DCREQ_BASE(phys_pipe); + priv->address_transform_base = PX210_ADDRESS_TRANSFORM_BASE; + } else if (IS_PE220X(priv)) { + phytium_crtc->dc_hw_config_pix_clock = pe220x_dc_hw_config_pix_clock; + phytium_crtc->dc_hw_disable = pe220x_dc_hw_disable; + phytium_crtc->dc_hw_reset = pe220x_dc_hw_reset; + priv->dc_reg_base[phys_pipe] = PE220X_DC_BASE(phys_pipe); + priv->dcreq_reg_base[phys_pipe] = 0x0; + priv->address_transform_base = PE220X_ADDRESS_TRANSFORM_BASE; + } + + phytium_primary_plane = phytium_primary_plane_create(dev, phys_pipe); + if (IS_ERR(phytium_primary_plane)) { + ret = PTR_ERR(phytium_primary_plane); + DRM_ERROR("create primary plane failed, phys_pipe(%d)\n", phys_pipe); + goto failed_create_primary; + } + + phytium_cursor_plane = phytium_cursor_plane_create(dev, phys_pipe); + if (IS_ERR(phytium_cursor_plane)) { + ret = PTR_ERR(phytium_cursor_plane); + DRM_ERROR("create cursor plane failed, phys_pipe(%d)\n", phys_pipe); + goto failed_create_cursor; + } + + ret = drm_crtc_init_with_planes(dev, &phytium_crtc->base, + &phytium_primary_plane->base, + &phytium_cursor_plane->base, + &phytium_crtc_funcs, + "phys_pipe %d", phys_pipe); + + if (ret) { + DRM_ERROR("init crtc with plane failed, phys_pipe(%d)\n", phys_pipe); + goto failed_crtc_init; + } + drm_crtc_helper_add(&phytium_crtc->base, &phytium_crtc_helper_funcs); + drm_crtc_vblank_reset(&phytium_crtc->base); + drm_mode_crtc_set_gamma_size(&phytium_crtc->base, GAMMA_INDEX_MAX); + drm_crtc_enable_color_mgmt(&phytium_crtc->base, 0, false, GAMMA_INDEX_MAX); + if (phytium_crtc->dc_hw_reset) + phytium_crtc->dc_hw_reset(&phytium_crtc->base); + phytium_crtc_gamma_init(&phytium_crtc->base); + + return 0; + +failed_crtc_init: +failed_create_cursor: + /* drm_mode_config_cleanup() will free any crtcs/planes already initialized */ +failed_create_primary: + kfree(phytium_crtc_state); +failed_malloc_crtc_state: + kfree(phytium_crtc); +failed_malloc_crtc: + return ret; +} diff --git a/drivers/gpu/drm/phytium/phytium_crtc.h b/drivers/gpu/drm/phytium/phytium_crtc.h new file mode 100644 index 0000000000000000000000000000000000000000..78a841c1c68412b939ba4f1dc7cc40b5a9e6bedd --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_crtc.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_CRTC_H__ +#define __PHYTIUM_CRTC_H__ + +struct phytium_crtc { + struct drm_crtc base; + int phys_pipe; + unsigned int bpc; + + /* scale */ + uint32_t src_width; + uint32_t src_height; + uint32_t dst_width; + uint32_t dst_height; + uint32_t dst_x; + uint32_t dst_y; + bool scale_enable; + bool reserve[3]; + + void (*dc_hw_config_pix_clock)(struct drm_crtc *crtc, int clock); + void (*dc_hw_disable)(struct drm_crtc *crtc); + void (*dc_hw_reset)(struct drm_crtc *crtc); +}; + +struct phytium_crtc_state { + struct drm_crtc_state base; +}; + +#define to_phytium_crtc(x) container_of(x, struct phytium_crtc, base) +#define to_phytium_crtc_state(x) container_of(x, struct phytium_crtc_state, base) + +void phytium_crtc_resume(struct drm_device *drm_dev); +int phytium_crtc_init(struct drm_device *dev, int pipe); +#endif /* __PHYTIUM_CRTC_H__ */ diff --git a/drivers/gpu/drm/phytium/phytium_debugfs.c b/drivers/gpu/drm/phytium/phytium_debugfs.c new file mode 100644 index 0000000000000000000000000000000000000000..eedad22c153653c1dde2052db8b49c3cb6388f34 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_debugfs.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include "phytium_display_drv.h" +#include "phytium_dp.h" +#include "phytium_reg.h" + +const char *const mem_state[PHYTIUM_MEM_STATE_TYPE_COUNT] = { + "Memory_Vram_Total", + "Memory_Vram_Alloc", + "Memory_System_Carveout_Total", + "Memory_System_Carveout_Alloc", + "Memory_System_Alloc", +}; + +static ssize_t +phytium_dp_register_write(struct file *filp, + const char __user *ubuf, + size_t len, + loff_t *ppos) +{ + char tmp[16]; + + if (len >= sizeof(tmp)) + return -EINVAL; + + memset(tmp, 0, sizeof(tmp)); + if (copy_from_user(tmp, ubuf, len)) + return -EFAULT; + tmp[len] = '\0'; + + return len; +} + +static int phytium_dp_register_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + + seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_M_VID, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_M_VID)); + seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_N_VID, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_N_VID)); + seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_TRANSFER_UNIT_SIZE, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_TRANSFER_UNIT_SIZE)); + seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_DATA_COUNT, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_DATA_COUNT)); + seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_MAIN_LINK_HTOTAL, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_HTOTAL)); + seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_MAIN_LINK_HRES, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_HRES)); + seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_MAIN_LINK_HSWIDTH, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_HSWIDTH)); + seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_MAIN_LINK_HSTART, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_HSTART)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_VTOTAL, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_VTOTAL)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_VRES, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_VRES)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_VSWIDTH, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_VSWIDTH)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_VSTART, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_VSTART)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_POLARITY, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_POLARITY)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_MISC0, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_MISC0)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_MISC1, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_MISC1)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_USER_SYNC_POLARITY, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_USER_SYNC_POLARITY)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_VIDEO_STREAM_ENABLE, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_VIDEO_STREAM_ENABLE)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SECONDARY_STREAM_ENABLE, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE)); + seq_puts(m, "audio:\n"); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_INPUT_SELECT, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_INPUT_SELECT)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_DIRECT_CLKDIV, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_DIRECT_CLKDIV)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CHANNEL_COUNT, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CHANNEL_COUNT)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CHANNEL_MAP, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CHANNEL_MAP)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_DATA_WINDOW, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_DATA_WINDOW)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CS_CATEGORY_CODE, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CS_CATEGORY_CODE)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_MAUD, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_MAUD)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_NAUD, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_NAUD)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CLOCK_MODE, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CLOCK_MODE)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CS_SOURCE_FORMAT, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CS_SOURCE_FORMAT)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CS_LENGTH_ORIG_FREQ, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CS_LENGTH_ORIG_FREQ)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CS_FREQ_CLOCK_ACCURACY, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CS_FREQ_CLOCK_ACCURACY)); + seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_AUDIO_ENABLE, + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE)); + + return 0; +} + +static int phytium_dp_register_open(struct inode *inode, struct file *file) +{ + return single_open(file, phytium_dp_register_show, inode->i_private); +} + +static const struct file_operations phytium_dp_register_fops = { + .owner = THIS_MODULE, + .open = phytium_dp_register_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = phytium_dp_register_write, +}; + +static ssize_t +phytium_dp_trigger_train_fail_write(struct file *filp, + const char __user *ubuf, + size_t len, + loff_t *ppos) +{ + struct seq_file *m = filp->private_data; + struct drm_connector *connector = m->private; + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + char tmp[16]; + + if (len >= sizeof(tmp)) + return -EINVAL; + + memset(tmp, 0, sizeof(tmp)); + if (copy_from_user(tmp, ubuf, len)) + return -EFAULT; + tmp[len] = '\0'; + + if (kstrtouint(tmp, 10, &phytium_dp->trigger_train_fail) != 0) + return -EINVAL; + + return len; +} + +static int phytium_dp_trigger_train_fail_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + + seq_printf(m, "trigger_train_fail: %d\n", phytium_dp->trigger_train_fail); + seq_printf(m, "train_retry_count: %d\n", phytium_dp->train_retry_count); + + return 0; +} + +static int phytium_dp_trigger_train_fail_open(struct inode *inode, struct file *file) +{ + return single_open(file, phytium_dp_trigger_train_fail_show, inode->i_private); +} + +static const struct file_operations phytium_dp_trigger_train_fail_fops = { + .owner = THIS_MODULE, + .open = phytium_dp_trigger_train_fail_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = phytium_dp_trigger_train_fail_write, +}; + +static int phytium_edp_backlight_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + + if (!phytium_dp->is_edp) + return -ENODEV; + + mutex_lock(&phytium_dp->panel.panel_lock); + seq_printf(m, "backlight: %s\n", phytium_dp->panel.backlight_enabled?"enabled":"disabled"); + mutex_unlock(&phytium_dp->panel.panel_lock); + + return 0; +} + +static int phytium_edp_backlight_open(struct inode *inode, struct file *file) +{ + return single_open(file, phytium_edp_backlight_show, inode->i_private); +} + +static const struct file_operations phytium_edp_backlight_fops = { + .owner = THIS_MODULE, + .open = phytium_edp_backlight_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int phytium_edp_power_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + + if (!phytium_dp->is_edp) + return -ENODEV; + + mutex_lock(&phytium_dp->panel.panel_lock); + seq_printf(m, "power: %s\n", phytium_dp->panel.power_enabled?"enabled":"disabled"); + mutex_unlock(&phytium_dp->panel.panel_lock); + + return 0; +} + +static int phytium_edp_power_open(struct inode *inode, struct file *file) +{ + return single_open(file, phytium_edp_power_show, inode->i_private); +} + +static const struct file_operations phytium_edp_power_fops = { + .owner = THIS_MODULE, + .open = phytium_edp_power_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +struct dpcd_block { + /* DPCD dump start address. */ + unsigned int offset; + /* DPCD dump end address, inclusive. If unset, .size will be used. */ + unsigned int end; + /* DPCD dump size. Used if .end is unset. If unset, defaults to 1. */ + size_t size; + /* Only valid for eDP. */ + bool edp; +}; + +static const struct dpcd_block phytium_dpcd_debug[] = { + { .offset = DP_DPCD_REV, .size = DP_RECEIVER_CAP_SIZE }, + { .offset = DP_PSR_SUPPORT, .end = DP_PSR_CAPS }, + { .offset = DP_DOWNSTREAM_PORT_0, .size = 16 }, + { .offset = DP_LINK_BW_SET, .end = DP_EDP_CONFIGURATION_SET }, + { .offset = DP_SINK_COUNT, .end = DP_ADJUST_REQUEST_LANE2_3 }, + { .offset = DP_SET_POWER }, + { .offset = DP_EDP_DPCD_REV }, + { .offset = DP_EDP_GENERAL_CAP_1, .end = DP_EDP_GENERAL_CAP_3 }, + { .offset = DP_EDP_DISPLAY_CONTROL_REGISTER, .end = DP_EDP_BACKLIGHT_FREQ_CAP_MAX_LSB }, + { .offset = DP_EDP_DBC_MINIMUM_BRIGHTNESS_SET, .end = DP_EDP_DBC_MAXIMUM_BRIGHTNESS_SET }, + { .offset = DP_DEVICE_SERVICE_IRQ_VECTOR, .size = 1 }, + { .offset = DP_TEST_REQUEST, .end = DP_TEST_PATTERN }, +}; + +static int phytium_dpcd_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + uint8_t buf[16], i; + ssize_t err; + + if (connector->status != connector_status_connected) + return -ENODEV; + + for (i = 0; i < ARRAY_SIZE(phytium_dpcd_debug); i++) { + const struct dpcd_block *b = &phytium_dpcd_debug[i]; + size_t size = b->end ? b->end - b->offset + 1 : (b->size ?: 1); + + if (WARN_ON(size > sizeof(buf))) + continue; + + err = drm_dp_dpcd_read(&phytium_dp->aux, b->offset, buf, size); + if (err <= 0) { + DRM_ERROR("dpcd read (%zu bytes at %u) failed (%zd)\n", + size, b->offset, err); + continue; + } + + seq_printf(m, "%04x: %*ph\n", b->offset, (int) size, buf); + } + + return 0; +} + +static int phytium_dpcd_open(struct inode *inode, struct file *file) +{ + return single_open(file, phytium_dpcd_show, inode->i_private); +} + +static const struct file_operations phytium_dpcd_fops = { + .owner = THIS_MODULE, + .open = phytium_dpcd_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static ssize_t +phytium_dp_state_write(struct file *filp, + const char __user *ubuf, + size_t len, + loff_t *ppos) +{ + char tmp[16]; + + if (len >= sizeof(tmp)) + return -EINVAL; + + memset(tmp, 0, sizeof(tmp)); + if (copy_from_user(tmp, ubuf, len)) + return -EFAULT; + tmp[len] = '\0'; + + return len; +} + +static int phytium_dp_state_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + + seq_printf(m, "port number: %d\n", phytium_dp->port); + seq_printf(m, "source_max_lane_count: %d\n", phytium_dp->source_max_lane_count); + seq_printf(m, "max_source_rates: %d\n", + phytium_dp->source_rates[phytium_dp->num_source_rates-1]); + if (connector->status == connector_status_connected) { + seq_printf(m, "sink_max_lane_count: %d\n", phytium_dp->sink_max_lane_count); + seq_printf(m, "max_sink_rates: %d\n", + phytium_dp->sink_rates[phytium_dp->num_sink_rates-1]); + seq_printf(m, "link_rate: %d\n", phytium_dp->link_rate); + seq_printf(m, "link_lane_count: %d\n", phytium_dp->link_lane_count); + seq_printf(m, "train_set[0]: %d\n", phytium_dp->train_set[0]); + seq_printf(m, "has_audio: %s\n", phytium_dp->has_audio?"yes":"no"); + } + + return 0; +} + +static int phytium_dp_state_open(struct inode *inode, struct file *file) +{ + return single_open(file, phytium_dp_state_show, inode->i_private); +} + +static const struct file_operations phytium_dp_state_fops = { + .owner = THIS_MODULE, + .open = phytium_dp_state_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = phytium_dp_state_write, +}; + +static const struct phytium_debugfs_files { + const char *name; + const struct file_operations *fops; +} phytium_debugfs_connector_files[] = { + {"dp_state", &phytium_dp_state_fops}, + {"dpcd", &phytium_dpcd_fops}, + {"dp_register", &phytium_dp_register_fops}, + {"dp_trigger_train_fail", &phytium_dp_trigger_train_fail_fops}, +}; + +static const struct phytium_debugfs_files phytium_edp_debugfs_connector_files[] = { + {"edp_power", &phytium_edp_power_fops}, + {"edp_backlight", &phytium_edp_backlight_fops}, +}; + +int phytium_debugfs_connector_add(struct drm_connector *connector) +{ + struct dentry *root = connector->debugfs_entry; + struct dentry *ent; + int i; + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + + if (!root) + return -ENODEV; + + for (i = 0; i < ARRAY_SIZE(phytium_debugfs_connector_files); i++) { + ent = debugfs_create_file(phytium_debugfs_connector_files[i].name, + 0644, + root, + connector, + phytium_debugfs_connector_files[i].fops); + if (!ent) + return -ENOMEM; + } + + if (phytium_dp->is_edp) + for (i = 0; i < ARRAY_SIZE(phytium_edp_debugfs_connector_files); i++) { + ent = debugfs_create_file(phytium_edp_debugfs_connector_files[i].name, + 0644, + root, + connector, + phytium_edp_debugfs_connector_files[i].fops); + if (!ent) + return -ENOMEM; + } + + return 0; +} + +static int phytium_mem_state_show(struct seq_file *m, void *data) +{ + struct phytium_display_private *priv = m->private; + uint8_t i; + + for (i = 0; i < ARRAY_SIZE(mem_state); i++) + seq_printf(m, "%-34s %10lld\n", mem_state[i], priv->mem_state[i]); + + return 0; +} + +static int phytium_mem_state_open(struct inode *inode, struct file *file) +{ + return single_open(file, phytium_mem_state_show, inode->i_private); +} + +static const struct file_operations phytium_mem_state_fops = { + .owner = THIS_MODULE, + .open = phytium_mem_state_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct phytium_debugfs_files phytium_debugfs_display_files[] = { + {"mem_state", &phytium_mem_state_fops}, +}; + +int phytium_debugfs_display_register(struct phytium_display_private *priv) +{ + struct drm_minor *minor = priv->dev->primary; + struct dentry *root = minor->debugfs_root; + struct dentry *ent; + + if (!root) + return -ENODEV; + + ent = debugfs_create_file(phytium_debugfs_display_files[0].name, + 0644, + root, + priv, + phytium_debugfs_display_files[0].fops); + if (!ent) + return -ENOMEM; + + return 0; +} diff --git a/drivers/gpu/drm/phytium/phytium_debugfs.h b/drivers/gpu/drm/phytium/phytium_debugfs.h new file mode 100644 index 0000000000000000000000000000000000000000..dc784bc557a7355db3f311e6b864b2e54d7185cc --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_debugfs.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_DEBUGFS_H__ +#define __PHYTIUM_DEBUGFS_H__ + +int phytium_debugfs_connector_add(struct drm_connector *connector); +int phytium_debugfs_display_register(struct phytium_display_private *priv); + +#endif /* __PHYTIUM_DEBUGFS_H__ */ diff --git a/drivers/gpu/drm/phytium/phytium_display_drv.c b/drivers/gpu/drm/phytium/phytium_display_drv.c new file mode 100644 index 0000000000000000000000000000000000000000..60c7a20e7ca2a52d070ff2134b2802ee0eacb993 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_display_drv.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phytium_display_drv.h" +#include "phytium_plane.h" +#include "phytium_crtc.h" +#include "phytium_dp.h" +#include "phytium_gem.h" +#include "phytium_fb.h" +#include "phytium_fbdev.h" +#include "phytium_reg.h" +#include "phytium_pci.h" +#include "phytium_platform.h" +#include "phytium_debugfs.h" + +int dc_fake_mode_enable; +module_param(dc_fake_mode_enable, int, 0644); +MODULE_PARM_DESC(dc_fake_mode_enable, "Enable DC fake mode (0-disabled; 1-enabled; default-0)"); + +int dc_fast_training_check = 1; +module_param(dc_fast_training_check, int, 0644); +MODULE_PARM_DESC(dc_fast_training_check, "Check dp fast training (0-disabled; 1-enabled; default-1)"); + +int num_source_rates = 4; +module_param(num_source_rates, int, 0644); +MODULE_PARM_DESC(num_source_rates, "set the source max rates (1-1.62Gbps; 2-2.7Gbps; 3-5.4Gbps; 4-8.1Gbps; default-4)"); + +int source_max_lane_count = 4; +module_param(source_max_lane_count, int, 0644); +MODULE_PARM_DESC(source_max_lane_count, "set the source lane count (1-1lane; 2-2lane; 4-4lane; default-4)"); + +int link_dynamic_adjust; +module_param(link_dynamic_adjust, int, 0644); +MODULE_PARM_DESC(link_dynamic_adjust, "dynamic select the train pamameter according to the display mode (0-disabled; 1-enabled; default-1)"); + +int phytium_wait_cmd_done(struct phytium_display_private *priv, + uint32_t register_offset, + uint32_t request_bit, + uint32_t reply_bit) +{ + int timeout = 500, config = 0, ret = 0; + + do { + mdelay(1); + timeout--; + config = phytium_readl_reg(priv, 0, register_offset); + } while ((!(config & reply_bit)) && timeout); + + phytium_writel_reg(priv, config & (~request_bit), 0, register_offset); + + if (timeout == 0) { + DRM_ERROR("wait cmd reply timeout\n"); + ret = -EBUSY; + } else { + timeout = 500; + do { + mdelay(1); + timeout--; + config = phytium_readl_reg(priv, 0, register_offset); + } while ((config & reply_bit) && timeout); + if (timeout == 0) { + DRM_ERROR("clear cmd timeout\n"); + ret = -EBUSY; + } + } + mdelay(5); + + return ret; +} + +static void phytium_irq_preinstall(struct drm_device *dev) +{ + struct phytium_display_private *priv = dev->dev_private; + int i, status; + + for_each_pipe_masked(priv, i) { + status = phytium_readl_reg(priv, priv->dc_reg_base[i], PHYTIUM_DC_INT_STATUS); + phytium_writel_reg(priv, INT_DISABLE, priv->dc_reg_base[i], PHYTIUM_DC_INT_ENABLE); + } +} + +static void phytium_irq_uninstall(struct drm_device *dev) +{ + struct phytium_display_private *priv = dev->dev_private; + int i, status; + + for_each_pipe_masked(priv, i) { + status = phytium_readl_reg(priv, priv->dc_reg_base[i], PHYTIUM_DC_INT_STATUS); + phytium_writel_reg(priv, INT_DISABLE, priv->dc_reg_base[i], PHYTIUM_DC_INT_ENABLE); + } +} + +static irqreturn_t phytium_display_irq_handler(int irq, void *data) +{ + struct drm_device *dev = data; + struct phytium_display_private *priv = dev->dev_private; + bool enabled = 0; + int i = 0, virt_pipe = 0; + irqreturn_t ret = IRQ_NONE, ret1 = IRQ_NONE; + + for_each_pipe_masked(priv, i) { + enabled = phytium_readl_reg(priv, priv->dc_reg_base[i], PHYTIUM_DC_INT_STATUS); + if (enabled & INT_STATUS) { + virt_pipe = phytium_get_virt_pipe(priv, i); + if (virt_pipe < 0) + return IRQ_NONE; + drm_handle_vblank(dev, virt_pipe); + ret = IRQ_HANDLED; + if (priv->dc_hw_clear_msi_irq) + priv->dc_hw_clear_msi_irq(priv, i); + } + } + + ret1 = phytium_dp_hpd_irq_handler(priv); + if (ret == IRQ_HANDLED || ret1 == IRQ_HANDLED) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static const struct drm_mode_config_funcs phytium_mode_funcs = { + .fb_create = phytium_fb_create, + .output_poll_changed = drm_fb_helper_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void phytium_atomic_commit_tail(struct drm_atomic_state *state) +{ + struct drm_device *dev = state->dev; + + drm_atomic_helper_commit_modeset_disables(dev, state); + drm_atomic_helper_commit_planes(dev, state, false); + drm_atomic_helper_commit_modeset_enables(dev, state); + drm_atomic_helper_commit_hw_done(state); + drm_atomic_helper_wait_for_flip_done(dev, state); + drm_atomic_helper_cleanup_planes(dev, state); +} + +static struct drm_mode_config_helper_funcs phytium_mode_config_helpers = { + .atomic_commit_tail = phytium_atomic_commit_tail, +}; + +static int phytium_modeset_init(struct drm_device *dev) +{ + struct phytium_display_private *priv = dev->dev_private; + int i = 0, ret; + + drm_mode_config_init(dev); + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = 16384; + dev->mode_config.max_height = 16384; + dev->mode_config.cursor_width = 32; + dev->mode_config.cursor_height = 32; + + dev->mode_config.preferred_depth = 24; + dev->mode_config.prefer_shadow = 1; + dev->mode_config.fb_modifiers_not_supported = false; + + dev->mode_config.funcs = &phytium_mode_funcs; + dev->mode_config.helper_private = &phytium_mode_config_helpers; + + for_each_pipe_masked(priv, i) { + ret = phytium_crtc_init(dev, i); + if (ret) { + DRM_ERROR("phytium_crtc_init(pipe %d) return failed\n", i); + goto failed_crtc_init; + } + } + + for_each_pipe_masked(priv, i) { + ret = phytium_dp_init(dev, i); + if (ret) { + DRM_ERROR("phytium_dp_init(pipe %d) return failed\n", i); + goto failed_dp_init; + } + } + + drm_mode_config_reset(dev); + + return 0; +failed_dp_init: +failed_crtc_init: + drm_mode_config_cleanup(dev); + return ret; +} + +int phytium_get_virt_pipe(struct phytium_display_private *priv, int phys_pipe) +{ + int i = 0; + int virt_pipe = 0; + + for_each_pipe_masked(priv, i) { + if (i != phys_pipe) + virt_pipe++; + else + return virt_pipe; + } + + DRM_ERROR("%s %d failed\n", __func__, phys_pipe); + return -EINVAL; +} + +int phytium_get_phys_pipe(struct phytium_display_private *priv, int virt_pipe) +{ + int i = 0; + int tmp = 0; + + for_each_pipe_masked(priv, i) { + if (tmp != virt_pipe) + tmp++; + else + return i; + } + + DRM_ERROR("%s %d failed\n", __func__, virt_pipe); + return -EINVAL; +} + +static int phytium_display_load(struct drm_device *dev, unsigned long flags) +{ + struct phytium_display_private *priv = dev->dev_private; + int ret = 0; + + ret = drm_vblank_init(dev, priv->info.num_pipes); + if (ret) { + DRM_ERROR("vblank init failed\n"); + goto failed_vblank_init; + } + + ret = phytium_modeset_init(dev); + if (ret) { + DRM_ERROR("phytium_modeset_init failed\n"); + goto failed_modeset_init; + } + + if (priv->support_memory_type & MEMORY_TYPE_VRAM) + priv->vram_hw_init(priv); + + phytium_irq_preinstall(dev); + ret = request_irq(priv->irq, phytium_display_irq_handler, + IRQF_SHARED, dev->driver->name, dev); + if (ret) { + DRM_ERROR("install irq failed\n"); + goto failed_irq_install; + } + + ret = phytium_drm_fbdev_init(dev); + if (ret) + DRM_ERROR("failed to init dev\n"); + + phytium_debugfs_display_register(priv); + + return ret; + +failed_irq_install: + drm_mode_config_cleanup(dev); +failed_modeset_init: +failed_vblank_init: + return ret; +} + +static void phytium_display_unload(struct drm_device *dev) +{ + struct phytium_display_private *priv = dev->dev_private; + + phytium_drm_fbdev_fini(dev); + phytium_irq_uninstall(dev); + free_irq(priv->irq, dev); + drm_mode_config_cleanup(dev); +} + +static const struct drm_ioctl_desc phytium_ioctls[] = { + /* for test, none so far */ +}; + +static const struct file_operations phytium_drm_driver_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, + .compat_ioctl = drm_compat_ioctl, + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, + .mmap = phytium_gem_mmap, +}; + +struct drm_driver phytium_display_drm_driver = { + .driver_features = DRIVER_HAVE_IRQ | + DRIVER_MODESET | + DRIVER_ATOMIC | + DRIVER_GEM, + .load = phytium_display_load, + .unload = phytium_display_unload, + .lastclose = drm_fb_helper_lastclose, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_import_sg_table = phytium_gem_prime_import_sg_table, + .dumb_create = phytium_gem_dumb_create, + .ioctls = phytium_ioctls, + .num_ioctls = ARRAY_SIZE(phytium_ioctls), + .fops = &phytium_drm_driver_fops, + .name = DRV_NAME, + .desc = DRV_DESC, + .date = DRV_DATE, + .major = DRV_MAJOR, + .minor = DRV_MINOR, +}; + +static void phytium_display_shutdown(struct drm_device *dev) +{ + drm_atomic_helper_shutdown(dev); +} + +static int phytium_display_pm_suspend(struct drm_device *dev) +{ + struct drm_atomic_state *state; + struct phytium_display_private *priv = dev->dev_private; + int ret, ret1; + + phytium_dp_hpd_irq_setup(dev, false); + cancel_work_sync(&priv->hotplug_work); + drm_fb_helper_set_suspend_unlocked(dev->fb_helper, 1); + state = drm_atomic_helper_suspend(dev); + if (IS_ERR(state)) { + DRM_ERROR("drm_atomic_helper_suspend failed: %ld\n", PTR_ERR(state)); + ret = PTR_ERR(state); + goto suspend_failed; + } + dev->mode_config.suspend_state = state; + ret = phytium_gem_suspend(dev); + if (ret) { + DRM_ERROR("phytium_gem_suspend failed: %d\n", ret); + goto gem_suspend_failed; + } + + return 0; + +gem_suspend_failed: + ret1 = drm_atomic_helper_resume(dev, dev->mode_config.suspend_state); + if (ret1) + DRM_ERROR("Failed to resume (%d)\n", ret1); + dev->mode_config.suspend_state = NULL; +suspend_failed: + drm_fb_helper_set_suspend_unlocked(dev->fb_helper, 0); + phytium_dp_hpd_irq_setup(dev, true); + + return ret; +} + +static int phytium_display_pm_resume(struct drm_device *dev) +{ + struct phytium_display_private *priv = dev->dev_private; + int ret = 0; + + if (WARN_ON(!dev->mode_config.suspend_state)) + return -EINVAL; + + ret = phytium_dp_resume(dev); + if (ret) + return -EIO; + + phytium_crtc_resume(dev); + phytium_gem_resume(dev); + + if (priv->support_memory_type & MEMORY_TYPE_VRAM) + priv->vram_hw_init(priv); + + ret = drm_atomic_helper_resume(dev, dev->mode_config.suspend_state); + if (ret) { + DRM_ERROR("Failed to resume (%d)\n", ret); + return ret; + } + + dev->mode_config.suspend_state = NULL; + drm_fb_helper_set_suspend_unlocked(dev->fb_helper, 0); + phytium_dp_hpd_irq_setup(dev, true); + + return 0; +} + +void phytium_display_private_init(struct phytium_display_private *priv, struct drm_device *dev) +{ + INIT_LIST_HEAD(&priv->gem_list_head); + spin_lock_init(&priv->hotplug_irq_lock); + INIT_WORK(&priv->hotplug_work, phytium_dp_hpd_work_func); + memset(priv->mem_state, 0, sizeof(priv->mem_state)); + priv->dev = dev; + priv->display_shutdown = phytium_display_shutdown; + priv->display_pm_suspend = phytium_display_pm_suspend; + priv->display_pm_resume = phytium_display_pm_resume; +} + +static int __init phytium_display_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&phytium_platform_driver); + if (ret) + return ret; + + ret = pci_register_driver(&phytium_pci_driver); + + return ret; +} + +static void __exit phytium_display_exit(void) +{ + pci_unregister_driver(&phytium_pci_driver); + + platform_driver_unregister(&phytium_platform_driver); +} + +module_init(phytium_display_init); +module_exit(phytium_display_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yang Xun "); +MODULE_DESCRIPTION("Phytium Display Controller"); diff --git a/drivers/gpu/drm/phytium/phytium_display_drv.h b/drivers/gpu/drm/phytium/phytium_display_drv.h new file mode 100644 index 0000000000000000000000000000000000000000..70080dad86219a05329a9abc0f43f24677a0384f --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_display_drv.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_DISPLAY_DRV_H__ +#define __PHYTIUM_DISPLAY_DRV_H__ + +#include +#include + +#define DEBUG_LOG 0 + +#define PHYTIUM_FORMAT_MAX_PLANE 3 +#define DP_MAX_DOWNSTREAM_PORTS 0x10 + +#define DRV_NAME "dc" +#define DRV_DESC "phytium dc" +#define DRV_DATE "20201220" +#define DRV_MAJOR 1 +#define DRV_MINOR 1 + +/* come from GPU */ +#define DRM_FORMAT_MOD_VENDOR_PHYTIUM 0x92 + +/* dc:mode0 8x8 16bpp gpu: FBCDC_8X8_V10 */ +#define DRM_FORMAT_MOD_PHYTIUM_TILE_MODE0_FBCDC fourcc_mod_code(PHYTIUM, 21) +/* dc:mode3 8x4 32bpp gpu: FBCDC_16X4_v10 */ +#define DRM_FORMAT_MOD_PHYTIUM_TILE_MODE3_FBCDC fourcc_mod_code(PHYTIUM, 22) + +#define PIPE_MASK_SHIFT 0x0 +#define PIPE_MASK_MASK 0x7 +#define EDP_MASK_SHIFT 0x3 +#define EDP_MASK_MASK 0x7 + +enum phytium_platform { + PHYTIUM_PLATFORM_UNINITIALIZED = 0, + PHYTIUM_PLATFORM_PX210, + PHYTIUM_PLATFORM_PE220X, +}; + +enum phytium_mem_state_type { + PHYTIUM_MEM_VRAM_TOTAL = 0, + PHYTIUM_MEM_VRAM_ALLOC, + PHYTIUM_MEM_SYSTEM_CARVEOUT_TOTAL, + PHYTIUM_MEM_SYSTEM_CARVEOUT_ALLOC, + PHYTIUM_MEM_SYSTEM_UNIFIED_ALLOC, + PHYTIUM_MEM_STATE_TYPE_COUNT, +}; + +#define MEMORY_TYPE_VRAM 0x1 +#define MEMORY_TYPE_SYSTEM_CARVEOUT 0x2 +#define MEMORY_TYPE_SYSTEM_UNIFIED 0x4 + +#define IS_PLATFORM(priv, p) ((priv)->info.platform_mask & BIT(p)) + +#define IS_PX210(priv) IS_PLATFORM(priv, PHYTIUM_PLATFORM_PX210) +#define IS_PE220X(priv) IS_PLATFORM(priv, PHYTIUM_PLATFORM_PE220X) + +struct phytium_device_info { + unsigned char platform_mask; + unsigned char pipe_mask; + unsigned char num_pipes; + unsigned char total_pipes; + unsigned char edp_mask; + unsigned int crtc_clock_max; + unsigned int hdisplay_max; + unsigned int vdisplay_max; + unsigned int backlight_max; + unsigned long address_mask; +}; + +struct phytium_display_private { + /* hw */ + void __iomem *regs; + void __iomem *vram_addr; + struct phytium_device_info info; + char support_memory_type; + char reserve[3]; + uint32_t dc_reg_base[3]; + uint32_t dcreq_reg_base[3]; + uint32_t dp_reg_base[3]; + uint32_t address_transform_base; + uint32_t phy_access_base[3]; + + /* drm */ + struct drm_device *dev; + int irq; + + /* fb_dev */ + struct drm_fb_helper fbdev_helper; + struct phytium_gem_object *fbdev_phytium_gem; + + int save_reg[3]; + struct list_head gem_list_head; + + struct work_struct hotplug_work; + spinlock_t hotplug_irq_lock; + + void (*vram_hw_init)(struct phytium_display_private *priv); + void (*display_shutdown)(struct drm_device *dev); + int (*display_pm_suspend)(struct drm_device *dev); + int (*display_pm_resume)(struct drm_device *dev); + void (*dc_hw_clear_msi_irq)(struct phytium_display_private *priv, uint32_t phys_pipe); + int (*dc_hw_fb_format_check)(const struct drm_mode_fb_cmd2 *mode_cmd, int count); + + struct gen_pool *memory_pool; + resource_size_t pool_phys_addr; + resource_size_t pool_size; + void *pool_virt_addr; + uint64_t mem_state[PHYTIUM_MEM_STATE_TYPE_COUNT]; + + /* DMA info */ + int dma_inited; + struct dma_chan *dma_chan; +}; + +static inline unsigned int +phytium_readl_reg(struct phytium_display_private *priv, uint32_t group_offset, uint32_t reg_offset) +{ + unsigned int data; + + data = readl(priv->regs + group_offset + reg_offset); +#if DEBUG_LOG + pr_info("Read 32'h%08x 32'h%08x\n", group_offset + reg_offset, data); +#endif + return data; +} + +static inline void +phytium_writel_reg(struct phytium_display_private *priv, uint32_t data, + uint32_t group_offset, uint32_t reg_offset) +{ + + writel(data, priv->regs + group_offset + reg_offset); +#if DEBUG_LOG + pr_info("Write 32'h%08x 32'h%08x\n", group_offset + reg_offset, data); +#endif +} + +static inline void +phytium_writeb_reg(struct phytium_display_private *priv, uint8_t data, + uint32_t group_offset, uint32_t reg_offset) +{ + writeb(data, priv->regs + group_offset + reg_offset); +#if DEBUG_LOG + pr_info("Write 32'h%08x 8'h%08x\n", group_offset + reg_offset, data); +#endif +} + +#define for_each_pipe(__dev_priv, __p) \ + for ((__p) = 0; (__p) < __dev_priv->info.total_pipes; (__p)++) + +#define for_each_pipe_masked(__dev_priv, __p) \ + for ((__p) = 0; (__p) < __dev_priv->info.total_pipes; (__p)++) \ + for_each_if((__dev_priv->info.pipe_mask) & BIT(__p)) + +int phytium_get_virt_pipe(struct phytium_display_private *priv, int phys_pipe); +int phytium_get_phys_pipe(struct phytium_display_private *priv, int virt_pipe); +int phytium_wait_cmd_done(struct phytium_display_private *priv, + uint32_t register_offset, + uint32_t request_bit, + uint32_t reply_bit); +void phytium_display_private_init(struct phytium_display_private *priv, struct drm_device *dev); + +extern struct drm_driver phytium_display_drm_driver; +extern int dc_fake_mode_enable; +extern int dc_fast_training_check; +extern int num_source_rates; +extern int source_max_lane_count; +extern int link_dynamic_adjust; + +#endif /* __PHYTIUM_DISPLAY_DRV_H__ */ diff --git a/drivers/gpu/drm/phytium/phytium_dp.c b/drivers/gpu/drm/phytium/phytium_dp.c new file mode 100644 index 0000000000000000000000000000000000000000..98a06ccbc48d0ca5e49cc1f999ff91f28e5e2628 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_dp.c @@ -0,0 +1,2639 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phytium_display_drv.h" +#include "phytium_dp.h" +#include "phytium_debugfs.h" +#include "px210_dp.h" +#include "pe220x_dp.h" +#include "phytium_panel.h" +#include "phytium_reg.h" + +static void phytium_dp_aux_init(struct phytium_dp_device *phytium_dp); +static void handle_plugged_change(struct phytium_dp_device *phytium_dp, bool plugged); +static bool phytium_edp_init_connector(struct phytium_dp_device *phytium_dp); +static void phytium_edp_fini_connector(struct phytium_dp_device *phytium_dp); +static void phytium_edp_panel_poweroff(struct phytium_dp_device *phytium_dp); +static void phytium_dp_audio_codec_fini(struct phytium_dp_device *phytium_dp); + +static int phytium_rate[] = {162000, 270000, 540000, 810000}; +static int codec_id = PHYTIUM_DP_AUDIO_ID; + +void phytium_phy_writel(struct phytium_dp_device *phytium_dp, uint32_t address, uint32_t data) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->phy_access_base[port]; + +#if DEBUG_LOG + pr_info("phy address write: 0x%x data:0x%x\n", address, data); +#endif + phytium_writel_reg(priv, address, group_offset, PHYTIUM_PHY_ACCESS_ADDRESS); + phytium_writel_reg(priv, data, group_offset, PHYTIUM_PHY_WRITE_DATA); + phytium_writel_reg(priv, ACCESS_WRITE, group_offset, PHYTIUM_PHY_ACCESS_CTRL); + udelay(10); +} + +uint32_t phytium_phy_readl(struct phytium_dp_device *phytium_dp, uint32_t address) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->phy_access_base[port]; + uint32_t data; + + phytium_writel_reg(priv, address, group_offset, PHYTIUM_PHY_ACCESS_ADDRESS); + phytium_writel_reg(priv, ACCESS_READ, group_offset, PHYTIUM_PHY_ACCESS_CTRL); + udelay(10); + data = phytium_readl_reg(priv, group_offset, PHYTIUM_PHY_READ_DATA); +#if DEBUG_LOG + pr_info("phy address read: 0x%x data:0x%x\n", address, data); +#endif + + return data; +} + +static int +phytium_dp_hw_aux_transfer_write(struct phytium_dp_device *phytium_dp, struct drm_dp_aux_msg *msg) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + unsigned int i = 0, j = 0; + unsigned int cmd = 0; + unsigned int aux_status = 0, interrupt_status = 0; + unsigned char *data = msg->buffer; + int count_timeout = 0; + long ret = 0; + + for (i = 0; i < 3; i++) { + /* clear PX210_DP_INTERRUPT_RAW_STATUS */ + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_STATUS); + phytium_writel_reg(priv, msg->address, group_offset, PHYTIUM_DP_AUX_ADDRESS); + for (j = 0; j < msg->size; j++) + phytium_writeb_reg(priv, data[j], group_offset, PHYTIUM_DP_AUX_WRITE_FIFO); + + cmd = ((msg->request & COMMAND_MASK) << COMMAND_SHIFT); + if (msg->size == 0) + cmd |= ADDRESS_ONLY; + else + cmd |= (msg->size-1) & BYTE_COUNT_MASK; + phytium_writel_reg(priv, cmd, group_offset, PHYTIUM_DP_AUX_COMMAND); + + count_timeout = 0; + do { + mdelay(5); + interrupt_status = phytium_readl_reg(priv, group_offset, + PHYTIUM_DP_INTERRUPT_RAW_STATUS); + aux_status = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_STATUS); + if ((aux_status & REPLY_RECEIVED) || (aux_status & REPLY_ERROR) + || (interrupt_status & REPLY_TIMEOUT)) { + DRM_DEBUG_KMS("aux wait exit\n"); + break; + } + count_timeout++; + } while (count_timeout < 6); + + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_STATUS); + if (interrupt_status & REPLY_TIMEOUT) { + DRM_DEBUG_KMS("aux write reply timeout\n"); + continue; + } else if (aux_status & REPLY_ERROR) { + DRM_DEBUG_KMS("aux write reply error\n"); + continue; + } else if (aux_status & REPLY_RECEIVED) { + DRM_DEBUG_KMS("aux write reply received succussful\n"); + break; + } + } + + if (interrupt_status & REPLY_TIMEOUT) { + DRM_NOTE("aux(%d) write reply timeout\n", phytium_dp->port); + ret = -EIO; + goto out; + } else if (aux_status & REPLY_ERROR) { + DRM_ERROR("aux(%d) write reply error\n", phytium_dp->port); + ret = -EIO; + goto out; + } else if ((aux_status & REPLY_RECEIVED) != REPLY_RECEIVED) { + DRM_ERROR("aux(%d) write reply no response\n", phytium_dp->port); + ret = -EIO; + goto out; + } + + msg->reply = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_REPLY_CODE); + ret = msg->size; +out: + return ret; +} + +static int +phytium_dp_hw_aux_transfer_read(struct phytium_dp_device *phytium_dp, struct drm_dp_aux_msg *msg) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + unsigned int i = 0; + unsigned int cmd = 0; + unsigned int aux_status = 0, interrupt_status = 0; + unsigned char *data = msg->buffer; + int count_timeout = 0; + long ret = 0; + + for (i = 0; i < 3; i++) { + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_STATUS); + phytium_writel_reg(priv, msg->address, group_offset, PHYTIUM_DP_AUX_ADDRESS); + cmd = ((msg->request & COMMAND_MASK) << COMMAND_SHIFT); + if (msg->size == 0) + cmd |= ADDRESS_ONLY; + else + cmd |= ((msg->size-1) & BYTE_COUNT_MASK); + phytium_writel_reg(priv, cmd, group_offset, PHYTIUM_DP_AUX_COMMAND); + + count_timeout = 0; + do { + mdelay(5); + interrupt_status = phytium_readl_reg(priv, group_offset, + PHYTIUM_DP_INTERRUPT_RAW_STATUS); + aux_status = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_STATUS); + if ((aux_status & REPLY_RECEIVED) || (aux_status & REPLY_ERROR) + || (interrupt_status & REPLY_TIMEOUT)) { + DRM_DEBUG_KMS("aux wait exit\n"); + break; + } + count_timeout++; + } while (count_timeout < 6); + + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_STATUS); + if (interrupt_status & REPLY_TIMEOUT) { + DRM_DEBUG_KMS("aux read reply timeout\n"); + continue; + } else if (aux_status & REPLY_ERROR) { + DRM_DEBUG_KMS("aux read reply error\n"); + continue; + } else if (aux_status & REPLY_RECEIVED) { + DRM_DEBUG_KMS("aux read reply received succussful\n"); + break; + } + } + + if (interrupt_status & REPLY_TIMEOUT) { + DRM_NOTE("aux(%d) read reply timeout\n", phytium_dp->port); + ret = -EIO; + goto out; + } else if (aux_status & REPLY_ERROR) { + DRM_ERROR("aux(%d) read reply error\n", phytium_dp->port); + ret = -EIO; + goto out; + } else if ((aux_status & REPLY_RECEIVED) != REPLY_RECEIVED) { + DRM_ERROR("aux(%d) read reply no response\n", phytium_dp->port); + ret = -EIO; + goto out; + } + + msg->reply = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_REPLY_CODE); + ret = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_REPLY_DATA_COUNT); + + if (ret > msg->size) { + ret = msg->size; + } else if (ret != msg->size) { + DRM_DEBUG_KMS("aux read count error(ret:0x%lx != 0x%lx)\n", ret, msg->size); + ret = -EBUSY; + goto out; + } + + for (i = 0; i < ret; i++) + data[i] = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_REPLY_DATA); + +out: + return ret; +} + +static void phytium_get_native_mode(struct phytium_dp_device *phytium_dp) +{ + struct drm_display_mode *t, *mode; + struct drm_connector *connector = &phytium_dp->connector; + struct drm_display_mode *native_mode = &phytium_dp->native_mode; + + list_for_each_entry_safe(mode, t, &connector->probed_modes, head) { + if (mode->type & DRM_MODE_TYPE_PREFERRED) { + if (mode->hdisplay != native_mode->hdisplay || + mode->vdisplay != native_mode->vdisplay) { + memcpy(native_mode, mode, sizeof(*mode)); + drm_mode_set_crtcinfo(native_mode, 0); + } + break; + } + } + + if (&mode->head == &connector->probed_modes) + native_mode->clock = 0; +} + +static int phytium_connector_add_common_modes(struct phytium_dp_device *phytium_dp) +{ + int i = 0, ret = 0; + struct drm_device *dev = phytium_dp->dev; + struct drm_display_mode *mode = NULL, *current_mode = NULL; + struct drm_display_mode *native_mode = &phytium_dp->native_mode; + bool mode_existed = false; + struct mode_size { + char name[DRM_DISPLAY_MODE_LEN]; + int w; + int h; + } common_mode[] = { + { "640x480", 640, 480}, + { "800x600", 800, 600}, + { "1024x768", 1024, 768}, + { "1280x720", 1280, 720}, + { "1280x800", 1280, 800}, + {"1280x1024", 1280, 1024}, + { "1440x900", 1440, 900}, + {"1680x1050", 1680, 1050}, + {"1600x1200", 1600, 1200}, + {"1920x1080", 1920, 1080}, + {"1920x1200", 1920, 1200} + }; + + if (native_mode->clock == 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(common_mode); i++) { + mode_existed = false; + + if (common_mode[i].w > native_mode->hdisplay || + common_mode[i].h > native_mode->vdisplay || + (common_mode[i].w == native_mode->hdisplay && + common_mode[i].h == native_mode->vdisplay)) + continue; + + list_for_each_entry(current_mode, &phytium_dp->connector.probed_modes, head) { + if (common_mode[i].w == current_mode->hdisplay && + common_mode[i].h == current_mode->vdisplay) { + mode_existed = true; + break; + } + } + + if (mode_existed) + continue; + + mode = drm_mode_duplicate(dev, native_mode); + if (mode == NULL) + continue; + + mode->hdisplay = common_mode[i].w; + mode->vdisplay = common_mode[i].h; + mode->type &= ~DRM_MODE_TYPE_PREFERRED; + strscpy(mode->name, common_mode[i].name, DRM_DISPLAY_MODE_LEN); + drm_mode_probed_add(&phytium_dp->connector, mode); + ret++; + } + + return ret; +} + +static int phytium_connector_get_modes(struct drm_connector *connector) +{ + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + struct edid *edid; + int ret = 0; + + if (phytium_dp->is_edp) + edid = phytium_dp->edp_edid; + else + edid = drm_get_edid(connector, &phytium_dp->aux.ddc); + + if (edid && drm_edid_is_valid(edid)) { + drm_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + phytium_dp->has_audio = drm_detect_monitor_audio(edid); + phytium_get_native_mode(phytium_dp); + if (dc_fake_mode_enable) + ret += phytium_connector_add_common_modes(phytium_dp); + } else { + drm_connector_update_edid_property(connector, NULL); + phytium_dp->has_audio = false; + } + + if (!phytium_dp->is_edp) + kfree(edid); + + return ret; +} + +static struct drm_encoder *phytium_dp_best_encoder(struct drm_connector *connector) +{ + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + + return &phytium_dp->encoder; +} + +static const +struct drm_connector_helper_funcs phytium_connector_helper_funcs = { + .get_modes = phytium_connector_get_modes, + .best_encoder = phytium_dp_best_encoder, +}; + +static void phytium_dp_set_sink_rates(struct phytium_dp_device *phytium_dp) +{ + static const int dp_rates[] = {162000, 270000, 540000, 810000}; + int i, max_rate; + + max_rate = drm_dp_bw_code_to_link_rate(phytium_dp->dpcd[DP_MAX_LINK_RATE]); + for (i = 0; i < ARRAY_SIZE(dp_rates); i++) { + if (dp_rates[i] > max_rate) + break; + phytium_dp->sink_rates[i] = dp_rates[i]; + } + phytium_dp->num_sink_rates = i; +} + +static int get_common_rates(const int *source_rates, int source_len, const int *sink_rates, + int sink_len, int *common_rates) +{ + int i = 0, j = 0, k = 0; + + while (i < source_len && j < sink_len) { + if (source_rates[i] == sink_rates[j]) { + if (WARN_ON(k >= DP_MAX_SUPPORTED_RATES)) + return k; + common_rates[k] = source_rates[i]; + ++k; + ++i; + ++j; + } else if (source_rates[i] < sink_rates[j]) { + ++i; + } else { + ++j; + } + } + return k; +} + +static void phytium_dp_set_common_rates(struct phytium_dp_device *phytium_dp) +{ + WARN_ON(!phytium_dp->num_source_rates || !phytium_dp->num_sink_rates); + + phytium_dp->num_common_rates = get_common_rates(phytium_dp->source_rates, + phytium_dp->num_source_rates, + phytium_dp->sink_rates, + phytium_dp->num_sink_rates, + phytium_dp->common_rates); + + if (WARN_ON(phytium_dp->num_common_rates == 0)) { + phytium_dp->common_rates[0] = 162000; + phytium_dp->num_common_rates = 1; + } +} + +static bool phytium_dp_get_dpcd(struct phytium_dp_device *phytium_dp) +{ + int ret; + unsigned char sink_count = 0; + + /* get dpcd capability,but don't check data error; so check revision */ + ret = drm_dp_dpcd_read(&phytium_dp->aux, 0x00, phytium_dp->dpcd, + sizeof(phytium_dp->dpcd)); + if (ret < 0) { + DRM_ERROR("port %d get DPCD capability fail\n", phytium_dp->port); + return false; + } + + if (phytium_dp->dpcd[DP_DPCD_REV] == 0) { + DRM_ERROR("DPCD data error: 0x%x\n", phytium_dp->dpcd[DP_DPCD_REV]); + return false; + } + + /* parse sink support link */ + phytium_dp_set_sink_rates(phytium_dp); + phytium_dp_set_common_rates(phytium_dp); + phytium_dp->sink_max_lane_count = drm_dp_max_lane_count(phytium_dp->dpcd); + phytium_dp->common_max_lane_count = min(phytium_dp->source_max_lane_count, + phytium_dp->sink_max_lane_count); + + /* get dpcd sink count */ + if (drm_dp_dpcd_readb(&phytium_dp->aux, DP_SINK_COUNT, &sink_count) <= 0) { + DRM_ERROR("get DPCD sink_count fail\n"); + return false; + } + + phytium_dp->sink_count = DP_GET_SINK_COUNT(sink_count); + if (!phytium_dp->sink_count) { + DRM_ERROR("DPCD sink_count should not be zero\n"); + return false; + } + + if (!drm_dp_is_branch(phytium_dp->dpcd)) + return true; + + if (phytium_dp->dpcd[DP_DPCD_REV] == 0x10) + return true; + + /* get downstream port for branch device */ + ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_DOWNSTREAM_PORT_0, + phytium_dp->downstream_ports, DP_MAX_DOWNSTREAM_PORTS); + if (ret < 0) { + DRM_ERROR("get DPCD DFP fail\n"); + return false; + } + + return true; +} + +static enum drm_connector_status +phytium_dp_detect_dpcd(struct phytium_dp_device *phytium_dp) +{ + if (!phytium_dp_get_dpcd(phytium_dp)) + return connector_status_disconnected; + + if (!drm_dp_is_branch(phytium_dp->dpcd)) + return connector_status_connected; + + if (phytium_dp->downstream_ports[0] & DP_DS_PORT_HPD) { + return phytium_dp->sink_count ? connector_status_connected + : connector_status_disconnected; + } + return connector_status_connected; +} + +static void phytium_get_adjust_train(struct phytium_dp_device *phytium_dp, + const uint8_t link_status[DP_LINK_STATUS_SIZE], uint8_t lane_count) +{ + unsigned char v = 0; + unsigned char p = 0; + int lane; + unsigned char voltage_max; + unsigned char preemph_max; + + /* find max value */ + for (lane = 0; lane < lane_count; lane++) { + uint8_t this_v = drm_dp_get_adjust_request_voltage(link_status, lane); + uint8_t this_p = drm_dp_get_adjust_request_pre_emphasis(link_status, lane); + + if (this_v > v) + v = this_v; + if (this_p > p) + p = this_p; + } + voltage_max = DP_TRAIN_VOLTAGE_SWING_LEVEL_3; + if (v >= voltage_max) + v = voltage_max | DP_TRAIN_MAX_SWING_REACHED; + + preemph_max = DP_TRAIN_PRE_EMPH_LEVEL_3; + if (p >= preemph_max) + p = preemph_max | DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + for (lane = 0; lane < 4; lane++) + phytium_dp->train_set[lane] = v | p; +} + +bool phytium_dp_coding_8b10b_need_enable(unsigned char test_pattern) +{ + switch (test_pattern) { + case PHYTIUM_PHY_TP_D10_2: + case PHYTIUM_PHY_TP_SYMBOL_ERROR: + case PHYTIUM_PHY_TP_CP2520_1: + case PHYTIUM_PHY_TP_CP2520_2: + case PHYTIUM_PHY_TP_CP2520_3: + return true; + case PHYTIUM_PHY_TP_PRBS7: + case PHYTIUM_PHY_TP_80BIT_CUSTOM: + return false; + default: + return false; + } +} + +bool phytium_dp_scrambled_need_enable(unsigned char test_pattern) +{ + switch (test_pattern) { + case PHYTIUM_PHY_TP_SYMBOL_ERROR: + case PHYTIUM_PHY_TP_CP2520_1: + case PHYTIUM_PHY_TP_CP2520_2: + case PHYTIUM_PHY_TP_CP2520_3: + return true; + case PHYTIUM_PHY_TP_D10_2: + case PHYTIUM_PHY_TP_PRBS7: + case PHYTIUM_PHY_TP_80BIT_CUSTOM: + return false; + default: + return false; + } +} + +static void phytium_dp_hw_set_lane_setting(struct phytium_dp_device *phytium_dp, + uint32_t link_rate, + uint8_t train_set) +{ + phytium_dp->funcs->dp_hw_set_phy_lane_setting(phytium_dp, link_rate, train_set); +} + +static void phytium_dp_hw_set_link(struct phytium_dp_device *phytium_dp, + uint8_t lane_count, + uint32_t link_rate) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port, ret = 0, retry = 3; + uint32_t group_offset = priv->dp_reg_base[port]; + + phytium_writel_reg(priv, lane_count, + group_offset, PHYTIUM_DP_LANE_COUNT_SET); + phytium_writel_reg(priv, + drm_dp_link_rate_to_bw_code(link_rate), + group_offset, PHYTIUM_DP_LINK_BW_SET); + + if (drm_dp_enhanced_frame_cap(phytium_dp->dpcd)) + phytium_writel_reg(priv, ENHANCED_FRAME_ENABLE, + group_offset, PHYTIUM_DP_ENHANCED_FRAME_EN); + else + phytium_writel_reg(priv, ENHANCED_FRAME_DISABLE, + group_offset, PHYTIUM_DP_ENHANCED_FRAME_EN); + +try_again: + ret = phytium_dp->funcs->dp_hw_set_phy_lane_and_rate(phytium_dp, lane_count, link_rate); + if ((ret < 0) && retry) { + retry--; + goto try_again; + } +} + +static void phytium_dp_hw_set_test_pattern(struct phytium_dp_device *phytium_dp, + uint8_t lane_count, + uint8_t test_pattern, + uint8_t *custom_pattern, + uint32_t custom_pattern_size) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port, val = 0, tmp = 0, i; + uint32_t group_offset = priv->dp_reg_base[port]; + + if ((test_pattern == PHYTIUM_PHY_TP_80BIT_CUSTOM) + && custom_pattern && (custom_pattern_size > 0)) { + val = *(int *)custom_pattern; + phytium_writel_reg(priv, val, group_offset, PHYTIUM_DP_CUSTOM_80BIT_PATTERN_0); + val = *(int *)(custom_pattern + 4); + phytium_writel_reg(priv, val, group_offset, PHYTIUM_DP_CUSTOM_80BIT_PATTERN_1); + val = *(short int *)(custom_pattern + 8); + phytium_writel_reg(priv, val, group_offset, PHYTIUM_DP_CUSTOM_80BIT_PATTERN_2); + } + + if (test_pattern == PHYTIUM_PHY_TP_D10_2 || test_pattern == PHYTIUM_PHY_TP_PRBS7 + || test_pattern == PHYTIUM_PHY_TP_80BIT_CUSTOM) + phytium_writel_reg(priv, SCRAMBLING_DISABLE, group_offset, + PHYTIUM_DP_SCRAMBLING_DISABLE); + else + phytium_writel_reg(priv, SCRAMBLING_ENABLE, group_offset, + PHYTIUM_DP_SCRAMBLING_DISABLE); + + tmp = test_pattern - PHYTIUM_PHY_TP_NONE + TEST_PATTERN_NONE; + val = 0; + for (i = 0; i < lane_count; i++) + val |= (tmp << (TEST_PATTERN_LANE_SHIFT * i)); + phytium_writel_reg(priv, val, group_offset, PHYTIUM_DP_LINK_QUAL_PATTERN_SET); +} + +static void phytium_dp_hw_set_train_pattern(struct phytium_dp_device *phytium_dp, + uint8_t train_pattern) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port, tmp = 0; + uint32_t group_offset = priv->dp_reg_base[port]; + + /* Scrambling is disabled for TPS1/TPS2/3 and enabled for TPS4 */ + if (train_pattern == DP_TRAINING_PATTERN_4 + || train_pattern == DP_TRAINING_PATTERN_DISABLE) { + phytium_writel_reg(priv, SCRAMBLING_ENABLE, group_offset, + PHYTIUM_DP_SCRAMBLING_DISABLE); + phytium_writel_reg(priv, SCRAMBLER_RESET, group_offset, + PHYTIUM_DP_FORCE_SCRAMBLER_RESET); + } else { + phytium_writel_reg(priv, SCRAMBLING_DISABLE, group_offset, + PHYTIUM_DP_SCRAMBLING_DISABLE); + } + switch (train_pattern) { + case DP_TRAINING_PATTERN_DISABLE: + tmp = TRAINING_OFF; + break; + case DP_TRAINING_PATTERN_1: + tmp = TRAINING_PATTERN_1; + break; + case DP_TRAINING_PATTERN_2: + tmp = TRAINING_PATTERN_2; + break; + case DP_TRAINING_PATTERN_3: + tmp = TRAINING_PATTERN_3; + break; + case DP_TRAINING_PATTERN_4: + tmp = TRAINING_PATTERN_4; + break; + default: + tmp = TRAINING_OFF; + break; + } + + phytium_writel_reg(priv, tmp, group_offset, PHYTIUM_DP_TRAINING_PATTERN_SET); +} + +void phytium_dp_hw_enable_audio(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + int config = 0, config1, data_window = 0; + const struct dp_audio_n_m *n_m = NULL; + uint32_t group_offset = priv->dp_reg_base[port]; + + config = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE); + phytium_writel_reg(priv, CHANNEL_MUTE_ENABLE, group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE); + + data_window = 90*(phytium_dp->link_rate)/100 + *(phytium_dp->mode.htotal - phytium_dp->mode.hdisplay) + /phytium_dp->mode.clock/4; + + phytium_writel_reg(priv, data_window, group_offset, PHYTIUM_DP_SEC_DATA_WINDOW); + + n_m = phytium_dp_audio_get_n_m(phytium_dp->link_rate, phytium_dp->audio_info.sample_rate); + if (n_m == NULL) { + DRM_NOTE("can not get n_m for link_rate(%d) and sample_rate(%d)\n", + phytium_dp->link_rate, phytium_dp->audio_info.sample_rate); + phytium_writel_reg(priv, 0, group_offset, PHYTIUM_DP_SEC_MAUD); + phytium_writel_reg(priv, 0, group_offset, PHYTIUM_DP_SEC_NAUD); + } else { + phytium_writel_reg(priv, n_m->m, group_offset, PHYTIUM_DP_SEC_MAUD); + phytium_writel_reg(priv, n_m->n, group_offset, PHYTIUM_DP_SEC_NAUD); + } + + config1 = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE); + phytium_writel_reg(priv, SECONDARY_STREAM_DISABLE, + group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE); + phytium_writel_reg(priv, config1, group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE); + phytium_writel_reg(priv, config, group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE); +} + +static void phytium_dp_hw_audio_shutdown(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + + phytium_writel_reg(priv, SECONDARY_STREAM_DISABLE, + group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE); +} + +static void phytium_dp_hw_audio_digital_mute(struct phytium_dp_device *phytium_dp, bool enable) +{ + struct phytium_display_private *priv = phytium_dp->dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + + if (enable) + phytium_writel_reg(priv, CHANNEL_MUTE_ENABLE, + group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE); + else + phytium_writel_reg(priv, SEC_AUDIO_ENABLE, + group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE); +} + +static int +phytium_dp_hw_audio_hw_params(struct phytium_dp_device *phytium_dp, struct audio_info audio_info) +{ + struct phytium_display_private *priv = phytium_dp->dev->dev_private; + int port = phytium_dp->port; + int ret = 0, data_window = 0; + const struct dp_audio_n_m *n_m = NULL; + uint32_t fs, ws, fs_accurac; + uint32_t group_offset = priv->dp_reg_base[port]; + + DRM_DEBUG_KMS("%s:set port%d sample_rate(%d) channels(%d) sample_width(%d)\n", + __func__, phytium_dp->port, audio_info.sample_rate, + audio_info.channels, audio_info.sample_width); + + phytium_writel_reg(priv, INPUT_SELECT_I2S, group_offset, PHYTIUM_DP_SEC_INPUT_SELECT); + phytium_writel_reg(priv, APB_CLOCK/audio_info.sample_rate, + group_offset, PHYTIUM_DP_SEC_DIRECT_CLKDIV); + phytium_writel_reg(priv, audio_info.channels & CHANNEL_MASK, + group_offset, PHYTIUM_DP_SEC_CHANNEL_COUNT); + phytium_writel_reg(priv, CHANNEL_MAP_DEFAULT, group_offset, PHYTIUM_DP_SEC_CHANNEL_MAP); + data_window = 90*(phytium_dp->link_rate)/100 + *(phytium_dp->mode.htotal - phytium_dp->mode.hdisplay) + /phytium_dp->mode.clock/4; + phytium_writel_reg(priv, data_window, group_offset, PHYTIUM_DP_SEC_DATA_WINDOW); + phytium_writel_reg(priv, 0xb5, group_offset, PHYTIUM_DP_SEC_CS_CATEGORY_CODE); + + phytium_writel_reg(priv, CLOCK_MODE_SYNC, group_offset, PHYTIUM_DP_SEC_CLOCK_MODE); + phytium_writel_reg(priv, CS_SOURCE_FORMAT_DEFAULT, + group_offset, PHYTIUM_DP_SEC_CS_SOURCE_FORMAT); + + switch (audio_info.sample_rate) { + case 32000: + fs = ORIG_FREQ_32000; + fs_accurac = SAMPLING_FREQ_32000; + break; + case 44100: + fs = ORIG_FREQ_44100; + fs_accurac = SAMPLING_FREQ_44100; + break; + case 48000: + fs = ORIG_FREQ_48000; + fs_accurac = SAMPLING_FREQ_48000; + break; + case 96000: + fs = ORIG_FREQ_96000; + fs_accurac = SAMPLING_FREQ_96000; + break; + case 176400: + fs = ORIG_FREQ_176400; + fs_accurac = SAMPLING_FREQ_176400; + break; + case 192000: + fs = ORIG_FREQ_192000; + fs_accurac = SAMPLING_FREQ_192000; + break; + default: + DRM_ERROR("dp not support sample_rate %d\n", audio_info.sample_rate); + goto out; + } + + switch (audio_info.sample_width) { + case 16: + ws = WORD_LENGTH_16; + break; + case 18: + ws = WORD_LENGTH_18; + break; + case 20: + ws = WORD_LENGTH_20; + break; + case 24: + ws = WORD_LENGTH_24; + break; + default: + DRM_ERROR("dp not support sample_width %d\n", audio_info.sample_width); + goto out; + } + + phytium_writel_reg(priv, ((fs&ORIG_FREQ_MASK)<link_rate, audio_info.sample_rate); + if (n_m == NULL) { + DRM_NOTE("can not get n_m for link_rate(%d) and sample_rate(%d)\n", + phytium_dp->link_rate, audio_info.sample_rate); + phytium_writel_reg(priv, 0, group_offset, PHYTIUM_DP_SEC_MAUD); + phytium_writel_reg(priv, 0, group_offset, PHYTIUM_DP_SEC_NAUD); + + } else { + phytium_writel_reg(priv, n_m->m, group_offset, PHYTIUM_DP_SEC_MAUD); + phytium_writel_reg(priv, n_m->n, group_offset, PHYTIUM_DP_SEC_NAUD); + } + phytium_writel_reg(priv, SECONDARY_STREAM_ENABLE, + group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE); + phytium_dp->audio_info = audio_info; + + return 0; + +out: + phytium_writel_reg(priv, SECONDARY_STREAM_DISABLE, + group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE); + + return ret; +} + +void phytium_dp_hw_disable_video(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + + phytium_writel_reg(priv, SST_MST_SOURCE_0_DISABLE, + group_offset, PHYTIUM_DP_VIDEO_STREAM_ENABLE); +} + +bool phytium_dp_hw_video_is_enable(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port, config; + uint32_t group_offset = priv->dp_reg_base[port]; + + config = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_VIDEO_STREAM_ENABLE); + return config ? true : false; +} + +void phytium_dp_hw_enable_video(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + + phytium_writel_reg(priv, SST_MST_SOURCE_0_ENABLE, + group_offset, PHYTIUM_DP_VIDEO_STREAM_ENABLE); + phytium_writel_reg(priv, LINK_SOFT_RESET, group_offset, PHYTIUM_DP_SOFT_RESET); +} + +void phytium_dp_hw_config_video(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + unsigned long link_bw, date_rate = 0; + struct drm_display_info *display_info = &phytium_dp->connector.display_info; + unsigned char tu_size = 64; + unsigned long data_per_tu = 0; + int symbols_per_tu, frac_symbols_per_tu, symbol_count, udc, value; + + /* cal M/N and tu_size */ + phytium_writel_reg(priv, phytium_dp->mode.crtc_clock/10, group_offset, PHYTIUM_DP_M_VID); + phytium_writel_reg(priv, phytium_dp->link_rate/10, group_offset, PHYTIUM_DP_N_VID); + link_bw = phytium_dp->link_rate * phytium_dp->link_lane_count; + date_rate = (phytium_dp->mode.crtc_clock * display_info->bpc * 3)/8; + + /* mul 10 for register setting */ + data_per_tu = 10*tu_size * date_rate/link_bw; + symbols_per_tu = (data_per_tu/10)&0xff; + frac_symbols_per_tu = (data_per_tu%10*16/10) & 0xf; + phytium_writel_reg(priv, frac_symbols_per_tu<<24 | symbols_per_tu<<16 | tu_size, + group_offset, PHYTIUM_DP_TRANSFER_UNIT_SIZE); + + symbol_count = (phytium_dp->mode.crtc_hdisplay*display_info->bpc*3 + 7)/8; + udc = (symbol_count + phytium_dp->link_lane_count - 1)/phytium_dp->link_lane_count; + phytium_writel_reg(priv, udc, group_offset, PHYTIUM_DP_DATA_COUNT); + + /* config main stream attributes */ + phytium_writel_reg(priv, phytium_dp->mode.crtc_htotal, + group_offset, PHYTIUM_DP_MAIN_LINK_HTOTAL); + phytium_writel_reg(priv, phytium_dp->mode.crtc_hdisplay, + group_offset, PHYTIUM_DP_MAIN_LINK_HRES); + phytium_writel_reg(priv, + phytium_dp->mode.crtc_hsync_end - phytium_dp->mode.crtc_hsync_start, + group_offset, PHYTIUM_DP_MAIN_LINK_HSWIDTH); + phytium_writel_reg(priv, phytium_dp->mode.crtc_htotal - phytium_dp->mode.crtc_hsync_start, + group_offset, PHYTIUM_DP_MAIN_LINK_HSTART); + phytium_writel_reg(priv, phytium_dp->mode.crtc_vtotal, + group_offset, PHYTIUM_DP_MAIN_LINK_VTOTAL); + phytium_writel_reg(priv, phytium_dp->mode.crtc_vdisplay, + group_offset, PHYTIUM_DP_MAIN_LINK_VRES); + phytium_writel_reg(priv, + phytium_dp->mode.crtc_vsync_end - phytium_dp->mode.crtc_vsync_start, + group_offset, PHYTIUM_DP_MAIN_LINK_VSWIDTH); + phytium_writel_reg(priv, phytium_dp->mode.crtc_vtotal - phytium_dp->mode.crtc_vsync_start, + group_offset, PHYTIUM_DP_MAIN_LINK_VSTART); + + value = 0; + if (phytium_dp->mode.flags & DRM_MODE_FLAG_PHSYNC) + value = value & (~HSYNC_POLARITY_LOW); + else + value = value | HSYNC_POLARITY_LOW; + + if (phytium_dp->mode.flags & DRM_MODE_FLAG_PVSYNC) + value = value & (~VSYNC_POLARITY_LOW); + else + value = value | VSYNC_POLARITY_LOW; + phytium_writel_reg(priv, value, group_offset, PHYTIUM_DP_MAIN_LINK_POLARITY); + + switch (display_info->bpc) { + case 10: + value = (MISC0_BIT_DEPTH_10BIT << MISC0_BIT_DEPTH_OFFSET); + break; + case 6: + value = (MISC0_BIT_DEPTH_6BIT << MISC0_BIT_DEPTH_OFFSET); + break; + default: + value = (MISC0_BIT_DEPTH_8BIT << MISC0_BIT_DEPTH_OFFSET); + break; + } + value |= (MISC0_COMPONENT_FORMAT_RGB << MISC0_COMPONENT_FORMAT_SHIFT) + | MISC0_SYNCHRONOUS_CLOCK; + phytium_writel_reg(priv, value, group_offset, PHYTIUM_DP_MAIN_LINK_MISC0); + phytium_writel_reg(priv, 0, group_offset, PHYTIUM_DP_MAIN_LINK_MISC1); + + value = USER_ODDEVEN_POLARITY_HIGH | USER_DATA_ENABLE_POLARITY_HIGH; + if (phytium_dp->mode.flags & DRM_MODE_FLAG_PHSYNC) + value = value | USER_HSYNC_POLARITY_HIGH; + else + value = value & (~USER_HSYNC_POLARITY_HIGH); + if (phytium_dp->mode.flags & DRM_MODE_FLAG_PVSYNC) + value = value | USER_VSYNC_POLARITY_HIGH; + else + value = value & (~USER_VSYNC_POLARITY_HIGH); + phytium_writel_reg(priv, value, group_offset, PHYTIUM_DP_USER_SYNC_POLARITY); +} + +void phytium_dp_hw_disable_output(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + + phytium_writel_reg(priv, TRANSMITTER_OUTPUT_DISABLE, + group_offset, PHYTIUM_DP_TRANSMITTER_OUTPUT_ENABLE); + phytium_writel_reg(priv, LINK_SOFT_RESET, group_offset, PHYTIUM_DP_SOFT_RESET); +} + +void phytium_dp_hw_enable_output(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + + phytium_writel_reg(priv, LINK_SOFT_RESET, group_offset, PHYTIUM_DP_SOFT_RESET); + phytium_writel_reg(priv, TRANSMITTER_OUTPUT_ENABLE, + group_offset, PHYTIUM_DP_TRANSMITTER_OUTPUT_ENABLE); +} + +void phytium_dp_hw_enable_input_source(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + + phytium_writel_reg(priv, VIRTUAL_SOURCE_0_ENABLE, + group_offset, PHYTIUM_INPUT_SOURCE_ENABLE); +} + +void phytium_dp_hw_disable_input_source(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + + phytium_writel_reg(priv, (~VIRTUAL_SOURCE_0_ENABLE)&VIRTUAL_SOURCE_0_ENABLE_MASK, + priv->dp_reg_base[port], PHYTIUM_INPUT_SOURCE_ENABLE); +} + +bool phytium_dp_hw_output_is_enable(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + int config = 0; + + config = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_TRANSMITTER_OUTPUT_ENABLE); + return config ? true : false; +} + +static void phytium_dp_hw_get_hpd_state(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t val = 0, raw_state = 0; + uint32_t group_offset = priv->dp_reg_base[port]; + + val = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_RAW_STATUS); + + /* maybe miss hpd, so used for clear PHYTIUM_DP_INTERRUPT_RAW_STATUS */ + phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_STATUS); + raw_state = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SINK_HPD_STATE); + if (val & HPD_EVENT) + phytium_dp->dp_hpd_state.hpd_event_state = true; + + if (val & HPD_IRQ) + phytium_dp->dp_hpd_state.hpd_irq_state = true; + + if (raw_state & HPD_CONNECT) + phytium_dp->dp_hpd_state.hpd_raw_state = true; + else + phytium_dp->dp_hpd_state.hpd_raw_state = false; +} + +void phytium_dp_hw_hpd_irq_setup(struct phytium_dp_device *phytium_dp, bool enable) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dp_reg_base[port]; + + phytium_dp->dp_hpd_state.hpd_irq_enable = enable; + if (enable) + phytium_writel_reg(priv, HPD_OTHER_MASK, group_offset, PHYTIUM_DP_INTERRUPT_MASK); + else + phytium_writel_reg(priv, HPD_IRQ_MASK|HPD_EVENT_MASK|HPD_OTHER_MASK, + group_offset, PHYTIUM_DP_INTERRUPT_MASK); +} + +int phytium_dp_hw_init(struct phytium_dp_device *phytium_dp) +{ + int ret = 0; + uint8_t count = 0; + + phytium_dp->source_rates = phytium_rate; + phytium_dp->num_source_rates = num_source_rates; + count = phytium_dp->funcs->dp_hw_get_source_lane_count(phytium_dp); + phytium_dp->source_max_lane_count = count; + + ret = phytium_dp->funcs->dp_hw_reset(phytium_dp); + if (ret) + goto out; + ret = phytium_dp->funcs->dp_hw_init_phy(phytium_dp); + if (ret) + goto out; + + phytium_dp->fast_train_support = false; + phytium_dp->hw_spread_enable = phytium_dp->funcs->dp_hw_spread_is_enable(phytium_dp); + +out: + return ret; +} + +static int phytium_dp_dpcd_get_tp_link(struct phytium_dp_device *phytium_dp, + uint8_t *test_lane_count, + uint32_t *test_link_rate) +{ + uint8_t test_link_bw; + int ret; + + ret = drm_dp_dpcd_readb(&phytium_dp->aux, DP_TEST_LANE_COUNT, + test_lane_count); + if (ret <= 0) { + DRM_DEBUG_KMS("test pattern Lane count read failed(%d)\n", ret); + goto failed; + } + + ret = drm_dp_dpcd_readb(&phytium_dp->aux, DP_TEST_LINK_RATE, + &test_link_bw); + if (ret <= 0) { + DRM_DEBUG_KMS("test pattern link rate read failed(%d)\n", ret); + goto failed; + } + *test_link_rate = drm_dp_bw_code_to_link_rate(test_link_bw); + + return 0; +failed: + return ret; +} + +static int phytium_dp_dpcd_set_link(struct phytium_dp_device *phytium_dp, + uint8_t lane_count, uint32_t link_rate) +{ + uint8_t link_config[2]; + int ret = 0; + + link_config[0] = drm_dp_link_rate_to_bw_code(link_rate); + link_config[1] = lane_count; + if (drm_dp_enhanced_frame_cap(phytium_dp->dpcd)) + link_config[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + + ret = drm_dp_dpcd_write(&phytium_dp->aux, DP_LINK_BW_SET, link_config, 2); + if (ret < 0) { + DRM_NOTE("write dpcd DP_LINK_BW_SET fail: ret:%d\n", ret); + goto failed; + } + + if (phytium_dp->hw_spread_enable) + link_config[0] = DP_SPREAD_AMP_0_5; + else + link_config[0] = 0; + link_config[1] = DP_SET_ANSI_8B10B; + ret = drm_dp_dpcd_write(&phytium_dp->aux, DP_DOWNSPREAD_CTRL, link_config, 2); + if (ret < 0) { + DRM_ERROR("write DP_DOWNSPREAD_CTRL fail: ret:%d\n", ret); + goto failed; + } + + return 0; +failed: + return ret; +} + +static int phytium_dp_dpcd_set_test_pattern(struct phytium_dp_device *phytium_dp, + uint8_t test_pattern) +{ + unsigned char value; + int ret; + + if (phytium_dp_coding_8b10b_need_enable(test_pattern)) + value = DP_SET_ANSI_8B10B; + else + value = 0; + ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_MAIN_LINK_CHANNEL_CODING_SET, value); + if (ret < 0) { + DRM_ERROR("write DP_MAIN_LINK_CHANNEL_CODING_SET fail: ret:%d\n", ret); + goto failed; + } + + if (phytium_dp_scrambled_need_enable(test_pattern)) + value = DP_TRAINING_PATTERN_DISABLE; + else + value = (DP_TRAINING_PATTERN_DISABLE | DP_LINK_SCRAMBLING_DISABLE); + + ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_TRAINING_PATTERN_SET, value); + if (ret < 0) { + DRM_ERROR("write DP_TRAINING_PATTERN_SET fail: ret:%d\n", ret); + goto failed; + } + + ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_LINK_QUAL_LANE0_SET, test_pattern); + if (ret < 0) { + DRM_ERROR("write DP_TRAINING_PATTERN_SET fail: ret:%d\n", ret); + goto failed; + } + + return 0; +failed: + return ret; +} + +static int phytium_dp_dpcd_set_train_pattern(struct phytium_dp_device *phytium_dp, + uint8_t train_pattern) +{ + uint8_t value; + int ret; + + /* Scrambling is disabled for TPS1/2/3 and enabled for TPS4 */ + if (train_pattern == DP_TRAINING_PATTERN_4 || train_pattern == DP_TRAINING_PATTERN_DISABLE) + value = train_pattern; + else + value = (train_pattern | DP_LINK_SCRAMBLING_DISABLE); + + ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_TRAINING_PATTERN_SET, value); + if (ret < 0) { + DRM_NOTE("write DP_TRAINING_PATTERN_SET fail: ret:%d\n", ret); + goto failed; + } + + return 0; +failed: + return ret; +} + +static int +phytium_dp_dpcd_set_lane_setting(struct phytium_dp_device *phytium_dp, uint8_t *train_set) +{ + int ret = 0; + + ret = drm_dp_dpcd_write(&phytium_dp->aux, DP_TRAINING_LANE0_SET, + phytium_dp->train_set, 4); + if (ret < 0) { + DRM_ERROR("write DP_TRAINING_LANE0_SET fail: ret:%d\n", ret); + return ret; + } + + return 0; +} + +static int +phytium_dp_dpcd_get_adjust_request(struct phytium_dp_device *phytium_dp, uint8_t lane_count) +{ + int ret = 0; + uint8_t link_status[DP_LINK_STATUS_SIZE]; + + ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_LANE0_1_STATUS, + link_status, DP_LINK_STATUS_SIZE); + if (ret < 0) { + DRM_ERROR("failed to get link status(DP_LANE0_1_STATUS)\n"); + goto failed; + } + phytium_get_adjust_train(phytium_dp, link_status, lane_count); + + return 0; +failed: + return ret; +} + +void phytium_dp_dpcd_sink_dpms(struct phytium_dp_device *phytium_dp, int mode) +{ + int ret, i; + + if (phytium_dp->dpcd[DP_DPCD_REV] < 0x11) + return; + if (mode != DRM_MODE_DPMS_ON) { + ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_SET_POWER, DP_SET_POWER_D3); + } else { + for (i = 0; i < 3; i++) { + ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_SET_POWER, DP_SET_POWER_D0); + if (ret == 1) + break; + msleep(20); + } + } + + if (ret != 1) + DRM_DEBUG_KMS("failed to %s sink power state\n", + mode == DRM_MODE_DPMS_ON ? "enable" : "disable"); +} + +static bool phytium_dp_link_training_clock_recovery(struct phytium_dp_device *phytium_dp) +{ + int ret; + unsigned char voltage, max_vswing_tries; + int voltage_tries; + + /* clear the test pattern */ + phytium_dp_hw_set_test_pattern(phytium_dp, phytium_dp->link_lane_count, + PHYTIUM_PHY_TP_NONE, NULL, 0); + + /* config source and sink's link rate and lane count */ + phytium_dp_hw_set_link(phytium_dp, phytium_dp->link_lane_count, phytium_dp->link_rate); + ret = phytium_dp_dpcd_set_link(phytium_dp, phytium_dp->link_lane_count, + phytium_dp->link_rate); + if (ret < 0) { + DRM_NOTE("phytium_dp_dpcd_set_link failed(ret=%d)\n", ret); + return false; + } + + /* config source's voltage swing and pre-emphasis(103-106) */ + memset(phytium_dp->train_set, 0, sizeof(phytium_dp->train_set)); + phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->link_rate, + phytium_dp->train_set[0]); + + /* config train pattern */ + phytium_dp_hw_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_1); + ret = phytium_dp_dpcd_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_1); + if (ret < 0) { + DRM_ERROR("phytium_dp_dpcd_set_train_pattern fail: ret:%d\n", ret); + return false; + } + + /* config sink's voltage swing and pre-emphasis(103-106) */ + ret = phytium_dp_dpcd_set_lane_setting(phytium_dp, phytium_dp->train_set); + if (ret < 0) { + DRM_ERROR("phytium_dp_dpcd_set_lane_setting fail: ret:%d\n", ret); + return false; + } + + voltage_tries = 1; + max_vswing_tries = 0; + for (;;) { + unsigned char link_status[DP_LINK_STATUS_SIZE]; + + drm_dp_link_train_clock_recovery_delay(&phytium_dp->aux, phytium_dp->dpcd); + /* get link status 0x202-0x207 */ + ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_LANE0_1_STATUS, + link_status, DP_LINK_STATUS_SIZE); + if (ret < 0) { + DRM_ERROR("failed to get link status(DP_LANE0_1_STATUS)\n"); + return false; + } + + if (drm_dp_clock_recovery_ok(link_status, phytium_dp->link_lane_count)) { + DRM_DEBUG_KMS("clock revorery ok\n"); + return true; + } + + if (voltage_tries == 5) { + DRM_DEBUG_KMS("Same voltage tried 5 times\n"); + return false; + } + + if (max_vswing_tries == 1) { + DRM_DEBUG_KMS("Max Voltage Swing reached\n"); + return false; + } + + voltage = phytium_dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK; + + /* config source and sink's voltage swing and pre-emphasis(103-106) */ + phytium_get_adjust_train(phytium_dp, link_status, phytium_dp->link_lane_count); + phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->link_rate, + phytium_dp->train_set[0]); + ret = phytium_dp_dpcd_set_lane_setting(phytium_dp, phytium_dp->train_set); + if (ret < 0) { + DRM_ERROR("phytium_dp_dpcd_set_lane_setting fail: ret:%d\n", ret); + return false; + } + + if ((phytium_dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == voltage) + ++voltage_tries; + else + voltage_tries = 1; + + if (phytium_dp->train_set[0] & DP_TRAIN_MAX_SWING_REACHED) + ++max_vswing_tries; + + DRM_DEBUG_KMS("try train_set:0x%x voltage_tries:%d max_vswing_tries:%d\n", + phytium_dp->train_set[0], voltage_tries, max_vswing_tries); + } +} + +static unsigned int phytium_dp_get_training_pattern(struct phytium_dp_device *phytium_dp) +{ + bool sink_tps3, sink_tps4; + + sink_tps4 = drm_dp_tps4_supported(phytium_dp->dpcd); + if (sink_tps4) + return DP_TRAINING_PATTERN_4; + else if (phytium_dp->link_rate == 810000) + DRM_DEBUG_KMS("8.1 Gbps link rate without sink TPS4 support\n"); + + sink_tps3 = drm_dp_tps3_supported(phytium_dp->dpcd); + if (sink_tps3) + return DP_TRAINING_PATTERN_3; + else if (phytium_dp->link_rate >= 540000) + DRM_DEBUG_KMS(">=5.4/6.48 Gbps link rate without sink TPS3 support\n"); + + return DP_TRAINING_PATTERN_2; +} + +static bool phytium_dp_link_training_channel_equalization(struct phytium_dp_device *phytium_dp) +{ + unsigned int training_pattern; + int tries, ret; + unsigned char link_status[DP_LINK_STATUS_SIZE]; + bool channel_eq = false; + + /* config source and sink's voltage swing and pre-emphasis(103-106), from clock recovery */ + phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->link_rate, + phytium_dp->train_set[0]); + ret = phytium_dp_dpcd_set_lane_setting(phytium_dp, phytium_dp->train_set); + if (ret < 0) { + DRM_ERROR("phytium_dp_dpcd_set_lane_setting fail: ret:%d\n", ret); + return channel_eq; + } + + /* config source and sink's train_pattern x */ + training_pattern = phytium_dp_get_training_pattern(phytium_dp); + phytium_dp_hw_set_train_pattern(phytium_dp, training_pattern); + ret = phytium_dp_dpcd_set_train_pattern(phytium_dp, training_pattern); + if (ret < 0) { + DRM_ERROR("phytium_dp_dpcd_set_train_pattern fail: ret:%d\n", ret); + return channel_eq; + } + + for (tries = 0; tries < 5; tries++) { + drm_dp_link_train_channel_eq_delay(&phytium_dp->aux, phytium_dp->dpcd); + + /* get link status 0x202-0x207 */ + ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_LANE0_1_STATUS, + link_status, DP_LINK_STATUS_SIZE); + if (ret < 0) { + DRM_ERROR("failed to get link status(DP_LANE0_1_STATUS)\n"); + break; + } + + /* Make sure clock is still ok */ + if (!drm_dp_clock_recovery_ok(link_status, phytium_dp->link_lane_count)) { + DRM_DEBUG_KMS("CR check failed, cannot continue channel equalization\n"); + break; + } + + if (drm_dp_channel_eq_ok(link_status, phytium_dp->link_lane_count)) { + channel_eq = true; + DRM_DEBUG_KMS("Channel EQ done. DP Training successful\n"); + break; + } + + /* config source and sink's voltage swing and pre-emphasis(103-106) */ + phytium_get_adjust_train(phytium_dp, link_status, phytium_dp->link_lane_count); + phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->link_rate, + phytium_dp->train_set[0]); + ret = phytium_dp_dpcd_set_lane_setting(phytium_dp, phytium_dp->train_set); + if (ret < 0) { + DRM_ERROR("phytium_dp_dpcd_set_lane_setting fail: ret:%d\n", ret); + break; + } + } + + /* Try 5 times, else fail and try at lower BW */ + if (tries == 5) + DRM_DEBUG_KMS("Channel equalization failed 5 times\n"); + + return channel_eq; +} + +static void phytium_dp_train_retry_work_fn(struct work_struct *work) +{ + struct phytium_dp_device *phytium_dp = train_retry_to_dp_device(work); + struct drm_connector *connector; + + connector = &phytium_dp->connector; + DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, connector->name); + mutex_lock(&connector->dev->mode_config.mutex); + drm_connector_set_link_status_property(connector, DRM_MODE_LINK_STATUS_BAD); + mutex_unlock(&connector->dev->mode_config.mutex); + drm_kms_helper_hotplug_event(connector->dev); +} + +/* return index of rate in rates array, or -1 if not found */ +static int phytium_dp_rate_index(const int *rates, int len, int rate) +{ + int i; + + for (i = 0; i < len; i++) + if (rate == rates[i]) + return i; + + return -1; +} + +int phytium_dp_get_link_train_fallback_values(struct phytium_dp_device *phytium_dp) +{ + int index, ret = 0; + + if (phytium_dp->is_edp) { + phytium_dp->train_retry_count++; + DRM_INFO("Retrying Link training for eDP(%d) with same parameters\n", + phytium_dp->port); + goto out; + } else { + index = phytium_dp_rate_index(phytium_dp->common_rates, + phytium_dp->num_common_rates, + phytium_dp->link_rate); + if (index > 0) { + phytium_dp->link_rate = phytium_dp->common_rates[index - 1]; + } else if (phytium_dp->link_lane_count > 1) { + phytium_dp->link_rate = phytium_dp->max_link_rate; + phytium_dp->link_lane_count = phytium_dp->link_lane_count >> 1; + } else { + phytium_dp->train_retry_count++; + phytium_dp->link_rate = phytium_dp->max_link_rate; + phytium_dp->link_lane_count = phytium_dp->max_link_lane_count; + DRM_INFO("Retrying Link training for DP(%d) with maximal parameters\n", + phytium_dp->port); + ret = -1; + } + } + +out: + return ret; +} + +static int +phytium_dp_stop_link_train(struct phytium_dp_device *phytium_dp) +{ + int ret; + + /* config source and sink's train_pattern x: DP_TRAINING_PATTERN_DISABLE */ + phytium_dp_hw_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_DISABLE); + + ret = phytium_dp_dpcd_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_DISABLE); + if (ret < 0) { + DRM_NOTE("phytium_dp_dpcd_set_train_pattern fail: ret:%d\n", ret); + return ret; + } + + return 0; +} + +int phytium_dp_start_link_train(struct phytium_dp_device *phytium_dp) +{ + int ret = 0; + + phytium_dp_hw_disable_output(phytium_dp); + phytium_dp_hw_disable_input_source(phytium_dp); + phytium_dp_hw_disable_video(phytium_dp); + phytium_dp_hw_enable_input_source(phytium_dp); + phytium_dp_hw_enable_output(phytium_dp); + phytium_dp_dpcd_sink_dpms(phytium_dp, DRM_MODE_DPMS_OFF); + phytium_dp_dpcd_sink_dpms(phytium_dp, DRM_MODE_DPMS_ON); + + if (!phytium_dp_link_training_clock_recovery(phytium_dp)) + goto failure_handling; + + if (!phytium_dp_link_training_channel_equalization(phytium_dp)) + goto failure_handling; + + ret = phytium_dp_stop_link_train(phytium_dp); + if (ret < 0) { + DRM_NOTE("phytium_dp_stop_link_train failed: ret = %d\n", ret); + goto out; + } + + if (phytium_dp->trigger_train_fail) { + phytium_dp->trigger_train_fail--; + goto failure_handling; + } + phytium_dp->train_retry_count = 0; + + DRM_DEBUG_KMS("[CONNECTOR:%d:%s] Link Training Pass at Link Rate = %d, Lane count = %d\n", + phytium_dp->connector.base.id, + phytium_dp->connector.name, phytium_dp->link_rate, + phytium_dp->link_lane_count); + + return 0; + +failure_handling: + DRM_INFO("[CONNECTOR:%d:%s] Link Training failed at Link Rate = %d, Lane count = %d", + phytium_dp->connector.base.id, + phytium_dp->connector.name, + phytium_dp->link_rate, phytium_dp->link_lane_count); + + ret = phytium_dp_stop_link_train(phytium_dp); + if (ret < 0) { + DRM_NOTE("phytium_dp_stop_link_train failed: ret = %d\n", ret); + goto out; + } + + phytium_dp_get_link_train_fallback_values(phytium_dp); + if (phytium_dp->train_retry_count < 5) + schedule_work(&phytium_dp->train_retry_work); + else + DRM_ERROR("DP(%d) Link Training Unsuccessful, and stop Training\n", + phytium_dp->port); + +out: + return -1; +} + +static bool phytium_dp_needs_link_retrain(struct phytium_dp_device *phytium_dp) +{ + unsigned char link_status[DP_LINK_STATUS_SIZE]; + int ret = 0; + + /* get link status 0x202-0x207 */ + ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_LANE0_1_STATUS, + link_status, DP_LINK_STATUS_SIZE); + if (ret < 0) { + DRM_ERROR("failed to get link status(DP_LANE0_1_STATUS)\n"); + return true; + } + + if ((phytium_dp->link_rate == 0) || (phytium_dp->link_lane_count == 0)) { + DRM_DEBUG_KMS("link_rate(%d) or lane_count(%d) is invalid\n", + phytium_dp->link_rate, phytium_dp->link_lane_count); + return true; + } + + /* Make sure clock is still ok */ + if (!drm_dp_clock_recovery_ok(link_status, phytium_dp->link_lane_count)) { + DRM_DEBUG_KMS("Clock recovery check failed\n"); + return true; + } + + if (!drm_dp_channel_eq_ok(link_status, phytium_dp->link_lane_count)) { + DRM_DEBUG_KMS("Channel EQ check failed\n"); + return true; + } + + if (!phytium_dp_hw_output_is_enable(phytium_dp)) { + DRM_DEBUG_KMS("check DP output enable failed\n"); + return true; + } + return false; +} + +static bool +phytium_dp_get_sink_irq(struct phytium_dp_device *phytium_dp, u8 *sink_irq_vector) +{ + return drm_dp_dpcd_readb(&phytium_dp->aux, DP_DEVICE_SERVICE_IRQ_VECTOR, + sink_irq_vector) == 1; +} + +static uint8_t phytium_dp_autotest_phy_pattern(struct phytium_dp_device *phytium_dp) +{ + union phytium_phy_tp phytium_phy_tp; + int ret; + unsigned char test_80_bit_pattern[ + (DP_TEST_80BIT_CUSTOM_PATTERN_79_72 - + DP_TEST_80BIT_CUSTOM_PATTERN_7_0)+1] = {0}; + unsigned char test_pattern; + unsigned int offset; + + offset = DP_PHY_TEST_PATTERN; + + ret = drm_dp_dpcd_read(&phytium_dp->aux, offset, + &phytium_phy_tp.raw, + sizeof(phytium_phy_tp)); + if (ret <= 0) { + DRM_DEBUG_KMS("Could not read DP_TEST_PHY_PATTERN\n"); + goto failed; + } + + test_pattern = phytium_phy_tp.bits.PATTERN; + + if (test_pattern == PHYTIUM_PHY_TP_80BIT_CUSTOM) { + ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_TEST_80BIT_CUSTOM_PATTERN_7_0, + test_80_bit_pattern, + sizeof(test_80_bit_pattern)); + if (ret <= 0) { + DRM_DEBUG_KMS("Could not read DP_TEST_PHY_PATTERN\n"); + goto failed; + } + } + + /* config source and sink's link rate and link count */ + ret = phytium_dp_dpcd_get_tp_link(phytium_dp, &phytium_dp->compliance.test_lane_count, + &phytium_dp->compliance.test_link_rate); + if (ret < 0) { + DRM_ERROR("phytium_dp_dpcd_get_tp_link fail: ret:%d\n", ret); + goto failed; + } + + phytium_dp_hw_set_link(phytium_dp, phytium_dp->compliance.test_lane_count, + phytium_dp->compliance.test_link_rate); + ret = phytium_dp_dpcd_set_link(phytium_dp, phytium_dp->compliance.test_lane_count, + phytium_dp->compliance.test_link_rate); + if (ret < 0) { + DRM_ERROR("phytium_dp_dpcd_set_link fail: ret:%d\n", ret); + goto failed_dpcd_set_link; + } + + /* config source and sink's lane setting: voltage swing and pre-emphasis */ + ret = phytium_dp_dpcd_get_adjust_request(phytium_dp, + phytium_dp->compliance.test_lane_count); + if (ret < 0) { + DRM_ERROR("phytium_dp_dpcd_get_adjust_request fail: ret:%d\n", ret); + goto failed_dpcd_get_adjust_request; + } + phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->compliance.test_link_rate, + phytium_dp->train_set[0]); + ret = phytium_dp_dpcd_set_lane_setting(phytium_dp, phytium_dp->train_set); + if (ret < 0) { + DRM_ERROR("phytium_dp_dpcd_set_lane_setting fail: ret:%d\n", ret); + goto failed_dpcd_set_lane_setting; + } + + /* config test pattern */ + phytium_dp_hw_set_test_pattern(phytium_dp, phytium_dp->compliance.test_lane_count, + test_pattern, test_80_bit_pattern, + sizeof(test_80_bit_pattern)); + ret = phytium_dp_dpcd_set_test_pattern(phytium_dp, test_pattern); + if (ret < 0) { + DRM_ERROR("phytium_dp_dpcd_set_test_pattern fail: ret:%d\n", ret); + goto failed_dpcd_set_tp; + } + + return DP_TEST_ACK; + +failed_dpcd_set_tp: + phytium_dp_hw_set_test_pattern(phytium_dp, phytium_dp->compliance.test_lane_count, + PHYTIUM_PHY_TP_NONE, test_80_bit_pattern, + sizeof(test_80_bit_pattern)); +failed_dpcd_set_link: +failed_dpcd_set_lane_setting: +failed_dpcd_get_adjust_request: +failed: + return DP_TEST_NAK; +} + +static void phytium_dp_handle_test_request(struct phytium_dp_device *phytium_dp) +{ + uint8_t response = DP_TEST_NAK; + uint8_t request = 0; + int status; + + status = drm_dp_dpcd_readb(&phytium_dp->aux, DP_TEST_REQUEST, &request); + if (status <= 0) { + DRM_DEBUG_KMS("Could not read test request from sink\n"); + goto update_status; + } + + switch (request) { + case DP_TEST_LINK_TRAINING: + case DP_TEST_LINK_VIDEO_PATTERN: + case DP_TEST_LINK_EDID_READ: + DRM_DEBUG_KMS("Not support test request '%02x'\n", request); + response = DP_TEST_NAK; + break; + case DP_TEST_LINK_PHY_TEST_PATTERN: + DRM_DEBUG_KMS("PHY_PATTERN test requested\n"); + response = phytium_dp_autotest_phy_pattern(phytium_dp); + break; + default: + DRM_DEBUG_KMS("Invalid test request '%02x'\n", request); + break; + } + +update_status: + status = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_TEST_RESPONSE, response); + if (status <= 0) + DRM_DEBUG_KMS("Could not write test response to sink\n"); + +} + +static int phytium_dp_long_pulse(struct drm_connector *connector, bool hpd_raw_state) +{ + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + enum drm_connector_status status = connector->status; + bool video_enable = false; + uint32_t index = 0; + + if (phytium_dp->is_edp) + status = connector_status_connected; + else if (hpd_raw_state) { + if (!phytium_dp_needs_link_retrain(phytium_dp)) { + status = connector_status_connected; + goto out; + } + } else { + status = connector_status_disconnected; + goto out; + } + + if (!phytium_dp->is_edp) { + status = phytium_dp_detect_dpcd(phytium_dp); + if (status == connector_status_disconnected) + goto out; + + index = phytium_dp->num_common_rates-1; + phytium_dp->max_link_rate = phytium_dp->common_rates[index]; + phytium_dp->max_link_lane_count = phytium_dp->common_max_lane_count; + phytium_dp->link_rate = phytium_dp->max_link_rate; + phytium_dp->link_lane_count = phytium_dp->max_link_lane_count; + DRM_DEBUG_KMS("common_max_lane_count: %d, common_max_rate:%d\n", + phytium_dp->max_link_lane_count, phytium_dp->max_link_rate); + + video_enable = phytium_dp_hw_video_is_enable(phytium_dp); + phytium_dp_start_link_train(phytium_dp); + + if (video_enable) { + mdelay(2); + phytium_dp_hw_enable_video(phytium_dp); + } + } + +out: + return status; +} + +static int phytium_dp_short_pulse(struct drm_connector *connector) +{ + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + enum drm_connector_status status = connector->status; + u8 sink_irq_vector = 0; + bool video_enable = false; + + /* handle the test pattern */ + if (phytium_dp_get_sink_irq(phytium_dp, &sink_irq_vector) && + sink_irq_vector != 0) { + drm_dp_dpcd_writeb(&phytium_dp->aux, + DP_DEVICE_SERVICE_IRQ_VECTOR, + sink_irq_vector); + if (sink_irq_vector & DP_AUTOMATED_TEST_REQUEST) + phytium_dp_handle_test_request(phytium_dp); + if (sink_irq_vector & (DP_CP_IRQ | DP_SINK_SPECIFIC_IRQ)) + DRM_DEBUG_DRIVER("CP or sink specific irq unhandled\n"); + } + if (!phytium_dp_needs_link_retrain(phytium_dp)) { + status = connector_status_connected; + goto out; + } + + video_enable = phytium_dp_hw_video_is_enable(phytium_dp); + phytium_dp_start_link_train(phytium_dp); + if (video_enable) { + mdelay(2); + phytium_dp_hw_enable_video(phytium_dp); + } + +out: + return status; +} + +void phytium_dp_hpd_poll_handler(struct phytium_display_private *priv) +{ + struct drm_device *dev = priv->dev; + struct drm_connector_list_iter conn_iter; + struct drm_connector *connector; + enum drm_connector_status old_status; + bool changed = false; + + mutex_lock(&dev->mode_config.mutex); + DRM_DEBUG_KMS("running encoder hotplug poll functions\n"); + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + if (connector->force) + continue; + old_status = connector->status; + connector->status = drm_helper_probe_detect(connector, NULL, false); + if (old_status != connector->status) { + const char *old, *new; + + old = drm_get_connector_status_name(old_status); + new = drm_get_connector_status_name(connector->status); + DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n", + connector->base.id, + connector->name, + old, new); + changed = true; + } + } + drm_connector_list_iter_end(&conn_iter); + mutex_unlock(&dev->mode_config.mutex); + + if (changed) + drm_kms_helper_hotplug_event(dev); +} + +void phytium_dp_hpd_irq_setup(struct drm_device *dev, bool enable) +{ + struct phytium_dp_device *phytium_dp; + struct drm_encoder *encoder; + struct phytium_display_private *priv = dev->dev_private; + bool handler = false; + bool hpd_raw_state_old = false; + + /* We might have missed any hotplugs that happened, so polling and handler */ + if (enable) { + spin_lock_irq(&priv->hotplug_irq_lock); + + drm_for_each_encoder(encoder, dev) { + phytium_dp = encoder_to_dp_device(encoder); + if (!phytium_dp->dp_hpd_state.hpd_irq_enable) { + hpd_raw_state_old = phytium_dp->dp_hpd_state.hpd_raw_state; + phytium_dp_hw_get_hpd_state(phytium_dp); + if (phytium_dp->dp_hpd_state.hpd_event_state + || phytium_dp->dp_hpd_state.hpd_irq_state + || (hpd_raw_state_old != phytium_dp->dp_hpd_state.hpd_raw_state)) { + handler = true; + } + } + } + spin_unlock_irq(&priv->hotplug_irq_lock); + if (handler) + phytium_dp_hpd_poll_handler(priv); + } + + drm_for_each_encoder(encoder, dev) { + phytium_dp = encoder_to_dp_device(encoder); + phytium_dp_hw_hpd_irq_setup(phytium_dp, enable); + } +} + +void phytium_dp_hpd_work_func(struct work_struct *work) +{ + struct phytium_display_private *priv = + container_of(work, struct phytium_display_private, hotplug_work); + struct drm_device *dev = priv->dev; + struct drm_connector_list_iter conn_iter; + struct drm_connector *connector; + enum drm_connector_status old_status; + bool changed = false; + + mutex_lock(&dev->mode_config.mutex); + DRM_DEBUG_KMS("running encoder hotplug work functions\n"); + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + if (connector->force) + continue; + old_status = connector->status; + connector->status = drm_helper_probe_detect(connector, NULL, false); + if (old_status != connector->status) { + const char *old, *new; + + old = drm_get_connector_status_name(old_status); + new = drm_get_connector_status_name(connector->status); + DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n", + connector->base.id, + connector->name, + old, new); + changed = true; + } + } + drm_connector_list_iter_end(&conn_iter); + mutex_unlock(&dev->mode_config.mutex); + + if (changed) + drm_kms_helper_hotplug_event(dev); + + phytium_dp_hpd_irq_setup(dev, true); +} + +irqreturn_t phytium_dp_hpd_irq_handler(struct phytium_display_private *priv) +{ + struct drm_encoder *encoder = NULL; + struct phytium_dp_device *phytium_dp = NULL; + struct drm_device *dev = priv->dev; + bool handler = false; + + spin_lock(&priv->hotplug_irq_lock); + + drm_for_each_encoder(encoder, dev) { + phytium_dp = encoder_to_dp_device(encoder); + if (phytium_dp->dp_hpd_state.hpd_irq_enable) { + phytium_dp_hw_get_hpd_state(phytium_dp); + if (phytium_dp->dp_hpd_state.hpd_event_state + || phytium_dp->dp_hpd_state.hpd_irq_state) { + handler = true; + } + } + } + spin_unlock(&priv->hotplug_irq_lock); + + if (handler) { + phytium_dp_hpd_irq_setup(dev, false); + schedule_work(&priv->hotplug_work); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + + +static void phytium_dp_fast_link_train_detect(struct phytium_dp_device *phytium_dp) +{ + phytium_dp->fast_train_support = !!(phytium_dp->dpcd[DP_MAX_DOWNSPREAD] + & DP_NO_AUX_HANDSHAKE_LINK_TRAINING); + DRM_DEBUG_KMS("fast link training %s\n", + phytium_dp->fast_train_support ? "supported" : "unsupported"); +} + +bool phytium_dp_fast_link_train(struct phytium_dp_device *phytium_dp) +{ + int ret = 0; + unsigned int training_pattern; + + /* clear the test pattern */ + phytium_dp_hw_set_test_pattern(phytium_dp, phytium_dp->link_lane_count, + PHYTIUM_PHY_TP_NONE, NULL, 0); + + /* config source and sink's link rate and lane count */ + phytium_dp_hw_set_link(phytium_dp, phytium_dp->link_lane_count, phytium_dp->link_rate); + + /* config source and sink's voltage swing and pre-emphasis(103-106) */ + phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->link_rate, + phytium_dp->train_set[0]); + + /* config train pattern */ + phytium_dp_hw_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_1); + usleep_range(500, 600); + + training_pattern = phytium_dp_get_training_pattern(phytium_dp); + phytium_dp_hw_set_train_pattern(phytium_dp, training_pattern); + usleep_range(500, 600); + + phytium_dp_hw_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_DISABLE); + + if (dc_fast_training_check) { + unsigned char link_status[DP_LINK_STATUS_SIZE]; + + ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_LANE0_1_STATUS, + link_status, DP_LINK_STATUS_SIZE); + if (ret < 0) { + DRM_ERROR("failed to get link status(DP_LANE0_1_STATUS)\n"); + return false; + } + + if (!drm_dp_clock_recovery_ok(link_status, phytium_dp->link_lane_count)) { + DRM_DEBUG_KMS("check clock recovery failed\n"); + return false; + } + + if (!drm_dp_channel_eq_ok(link_status, phytium_dp->link_lane_count)) { + DRM_DEBUG_KMS("check channel equalization failed\n"); + return false; + } + } + + return true; +} + +static enum drm_connector_status +phytium_connector_detect(struct drm_connector *connector, bool force) +{ + enum drm_connector_status status = connector->status; + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + bool hpd_event_state, hpd_irq_state, hpd_raw_state; + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + bool plugged = true; + + spin_lock_irq(&priv->hotplug_irq_lock); + hpd_event_state = phytium_dp->dp_hpd_state.hpd_event_state; + hpd_irq_state = phytium_dp->dp_hpd_state.hpd_irq_state; + hpd_raw_state = phytium_dp->dp_hpd_state.hpd_raw_state; + phytium_dp->dp_hpd_state.hpd_event_state = false; + phytium_dp->dp_hpd_state.hpd_irq_state = false; + spin_unlock_irq(&priv->hotplug_irq_lock); + + if (hpd_event_state) + status = phytium_dp_long_pulse(connector, hpd_raw_state); + + if (hpd_irq_state) + status = phytium_dp_short_pulse(connector); + + if (status == connector_status_unknown) + status = connector_status_disconnected; + + if ((!phytium_dp->is_edp) && (!hpd_raw_state)) + status = connector_status_disconnected; + + if (connector->status != status) { + if ((status == connector_status_connected) && phytium_dp->has_audio) + plugged = true; + else + plugged = false; + + handle_plugged_change(phytium_dp, plugged); + } + + return status; +} + +static void +phytium_connector_destroy(struct drm_connector *connector) +{ + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + + drm_connector_cleanup(connector); + kfree(phytium_dp); +} + +static int +phytium_dp_connector_register(struct drm_connector *connector) +{ + int ret; + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + + phytium_dp_aux_init(phytium_dp); + if (phytium_dp->is_edp) { + phytium_edp_init_connector(phytium_dp); + ret = phytium_edp_backlight_device_register(phytium_dp); + if (ret) + DRM_ERROR("failed to register port(%d) backlight device(ret=%d)\n", + phytium_dp->port, ret); + } + + ret = phytium_debugfs_connector_add(connector); + if (ret) + DRM_ERROR("failed to register phytium connector debugfs(ret=%d)\n", ret); + + return 0; +} + +static void +phytium_dp_connector_unregister(struct drm_connector *connector) +{ + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + + if (phytium_dp->is_edp) { + phytium_edp_backlight_device_unregister(phytium_dp); + phytium_edp_fini_connector(phytium_dp); + } + drm_dp_aux_unregister(&phytium_dp->aux); +} + +static const struct drm_connector_funcs phytium_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = phytium_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = phytium_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .late_register = phytium_dp_connector_register, + .early_unregister = phytium_dp_connector_unregister, +}; + +static void phytium_dp_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + struct phytium_dp_device *dp = encoder_to_dp_device(encoder); + + drm_mode_copy(&dp->mode, adjusted); +} + +static void phytium_edp_panel_poweron(struct phytium_dp_device *phytium_dp) +{ + phytium_panel_poweron(&phytium_dp->panel); +} + +static void phytium_edp_panel_poweroff(struct phytium_dp_device *phytium_dp) +{ + phytium_panel_poweroff(&phytium_dp->panel); +} + +static void phytium_edp_backlight_on(struct phytium_dp_device *phytium_dp) +{ + phytium_panel_enable_backlight(&phytium_dp->panel); +} + +static void phytium_edp_backlight_off(struct phytium_dp_device *phytium_dp) +{ + phytium_panel_disable_backlight(&phytium_dp->panel); +} + +static void phytium_encoder_disable(struct drm_encoder *encoder) +{ + struct phytium_dp_device *phytium_dp = encoder_to_dp_device(encoder); + + if (phytium_dp->is_edp) + phytium_edp_backlight_off(phytium_dp); + + phytium_dp_hw_disable_video(phytium_dp); + + mdelay(50); + + if (phytium_dp->is_edp) + phytium_edp_panel_poweroff(phytium_dp); +} + +void phytium_dp_adjust_link_train_parameter(struct phytium_dp_device *phytium_dp) +{ + struct drm_display_info *display_info = &phytium_dp->connector.display_info; + unsigned long link_bw, date_rate = 0, bs_limit, bs_request; + int rate = 0; + + bs_request = phytium_dp->mode.crtc_htotal/(phytium_dp->mode.crtc_clock/1000); + date_rate = (phytium_dp->mode.crtc_clock * display_info->bpc * 3)/8; + + for (;;) { + bs_limit = 8192 / (phytium_dp->link_rate/1000); + link_bw = phytium_dp->link_rate * phytium_dp->link_lane_count; + rate = 10 * date_rate / link_bw; + DRM_DEBUG_KMS("adjust link rate(%d), lane count(%d)\n", + phytium_dp->link_rate, phytium_dp->link_lane_count); + DRM_DEBUG_KMS("for crtc_clock(%d) bs_request(%ld) bs_limit(%ld) rate(%d)\n", + phytium_dp->mode.crtc_clock, bs_request, bs_limit, rate); + if ((link_dynamic_adjust && (bs_request < bs_limit) && rate < 10) || + ((!link_dynamic_adjust) && (rate < 10))) + break; + phytium_dp_get_link_train_fallback_values(phytium_dp); + } + + DRM_DEBUG_KMS("Try link training at Link Rate = %d, Lane count = %d\n", + phytium_dp->link_rate, phytium_dp->link_lane_count); +} + +static void phytium_encoder_enable(struct drm_encoder *encoder) +{ + struct phytium_dp_device *phytium_dp = encoder_to_dp_device(encoder); + int ret = 0; + + phytium_dp_hw_disable_video(phytium_dp); + + if (phytium_dp->is_edp) { + phytium_edp_panel_poweron(phytium_dp); + if (phytium_dp->fast_train_support) + phytium_dp_fast_link_train(phytium_dp); + else + ret = phytium_dp_start_link_train(phytium_dp); + mdelay(2); + phytium_dp_fast_link_train_detect(phytium_dp); + } else { + phytium_dp_adjust_link_train_parameter(phytium_dp); + ret = phytium_dp_start_link_train(phytium_dp); + mdelay(2); + } + + phytium_dp_hw_config_video(phytium_dp); + if (ret == 0) { + phytium_dp_hw_enable_video(phytium_dp); + if (phytium_dp->has_audio) + phytium_dp_hw_enable_audio(phytium_dp); + } + + if (phytium_dp->is_edp) + phytium_edp_backlight_on(phytium_dp); + +} + +enum drm_mode_status +phytium_encoder_mode_valid(struct drm_encoder *encoder, const struct drm_display_mode *mode) +{ + struct phytium_dp_device *phytium_dp = encoder_to_dp_device(encoder); + struct drm_display_info *display_info = &phytium_dp->connector.display_info; + unsigned int requested, actual; + + switch (display_info->bpc) { + case 10: + case 6: + case 8: + break; + default: + DRM_INFO("not support bpc(%d)\n", display_info->bpc); + display_info->bpc = 8; + break; + } + + if ((display_info->color_formats & DRM_COLOR_FORMAT_RGB444) == 0) { + DRM_INFO("not support color_format(%d)\n", display_info->color_formats); + display_info->color_formats = DRM_COLOR_FORMAT_RGB444; + } + + requested = mode->clock * display_info->bpc * 3 / 1000; + actual = phytium_dp->max_link_rate * phytium_dp->max_link_lane_count / 100; + actual = actual * 8 / 10; + if (requested >= actual) { + DRM_DEBUG_KMS("requested=%d, actual=%d, clock=%d\n", requested, actual, + mode->clock); + return MODE_CLOCK_HIGH; + } + + if (dc_fake_mode_enable && + (phytium_dp->native_mode.clock == mode->clock) && + (phytium_dp->native_mode.htotal == mode->htotal) && + (phytium_dp->native_mode.vtotal == mode->vtotal)) + return MODE_OK; + + if ((mode->hdisplay == 1600) && (mode->vdisplay == 900)) + return MODE_BAD_HVALUE; + + if ((mode->hdisplay == 1024) && (mode->clock > 78000)) + return MODE_BAD_HVALUE; + + if ((mode->hdisplay < 640) || (mode->vdisplay < 480)) + return MODE_BAD_HVALUE; + + return MODE_OK; +} + +static const struct drm_encoder_helper_funcs phytium_encoder_helper_funcs = { + .mode_set = phytium_dp_encoder_mode_set, + .disable = phytium_encoder_disable, + .enable = phytium_encoder_enable, + .mode_valid = phytium_encoder_mode_valid, +}; + +void phytium_dp_encoder_destroy(struct drm_encoder *encoder) +{ + struct phytium_dp_device *phytium_dp = encoder_to_dp_device(encoder); + + phytium_dp_audio_codec_fini(phytium_dp); + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs phytium_encoder_funcs = { + .destroy = phytium_dp_encoder_destroy, +}; + +static const struct dp_audio_n_m phytium_dp_audio_n_m[] = { + { 32000, 162000, 1024, 10125 }, + { 44100, 162000, 784, 5625 }, + { 48000, 162000, 512, 3375 }, + { 64000, 162000, 2048, 10125 }, + { 88200, 162000, 1568, 5625 }, + { 96000, 162000, 1024, 3375 }, + { 128000, 162000, 4096, 10125 }, + { 176400, 162000, 3136, 5625 }, + { 192000, 162000, 2048, 3375 }, + { 32000, 270000, 1024, 16875 }, + { 44100, 270000, 784, 9375 }, + { 48000, 270000, 512, 5625 }, + { 64000, 270000, 2048, 16875 }, + { 88200, 270000, 1568, 9375 }, + { 96000, 270000, 1024, 5625 }, + { 128000, 270000, 4096, 16875 }, + { 176400, 270000, 3136, 9375 }, + { 192000, 270000, 2048, 5625 }, + { 32000, 540000, 1024, 33750 }, + { 44100, 540000, 784, 18750 }, + { 48000, 540000, 512, 11250 }, + { 64000, 540000, 2048, 33750 }, + { 88200, 540000, 1568, 18750 }, + { 96000, 540000, 1024, 11250 }, + { 128000, 540000, 4096, 33750 }, + { 176400, 540000, 3136, 18750 }, + { 192000, 540000, 2048, 11250 }, + { 32000, 810000, 1024, 50625 }, + { 44100, 810000, 784, 28125 }, + { 48000, 810000, 512, 16875 }, + { 64000, 810000, 2048, 50625 }, + { 88200, 810000, 1568, 28125 }, + { 96000, 810000, 1024, 16875 }, + { 128000, 810000, 4096, 50625 }, + { 176400, 810000, 3136, 28125 }, + { 192000, 810000, 2048, 16875 }, +}; + +static int phytium_dp_audio_get_eld(struct device *dev, void *data, u8 *buf, size_t len) +{ + struct phytium_dp_device *phytium_dp = data; + + memcpy(buf, phytium_dp->connector.eld, min(sizeof(phytium_dp->connector.eld), len)); + + return 0; +} + +static int phytium_dp_audio_mute_stream(struct device *dev, void *data, bool enable, int direction) +{ + struct phytium_dp_device *phytium_dp = data; + + phytium_dp_hw_audio_digital_mute(phytium_dp, enable); + + return 0; +} + +const struct dp_audio_n_m *phytium_dp_audio_get_n_m(int link_rate, int sample_rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(phytium_dp_audio_n_m); i++) { + if (sample_rate == phytium_dp_audio_n_m[i].sample_rate + && link_rate == phytium_dp_audio_n_m[i].link_rate) + return &phytium_dp_audio_n_m[i]; + } + + return NULL; +} + +static int phytium_dp_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct phytium_dp_device *phytium_dp = data; + int ret = 0; + struct audio_info audio_info = { + .sample_width = params->sample_width, + .sample_rate = params->sample_rate, + .channels = params->channels, + }; + + if (daifmt->fmt != HDMI_I2S) { + DRM_ERROR("invalid audio format %d\n", daifmt->fmt); + ret = -EINVAL; + goto failed; + } + + ret = phytium_dp_hw_audio_hw_params(phytium_dp, audio_info); + +failed: + return ret; +} + +static void phytium_dp_audio_shutdown(struct device *dev, void *data) +{ + struct phytium_dp_device *phytium_dp = data; + + phytium_dp_hw_audio_shutdown(phytium_dp); +} + +static void handle_plugged_change(struct phytium_dp_device *phytium_dp, bool plugged) +{ + if (phytium_dp->plugged_cb && phytium_dp->codec_dev) + phytium_dp->plugged_cb(phytium_dp->codec_dev, plugged); +} + +static int phytium_dp_audio_hook_plugged_cb(struct device *dev, void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + struct phytium_dp_device *phytium_dp = data; + bool plugged; + + phytium_dp->plugged_cb = fn; + phytium_dp->codec_dev = codec_dev; + + if ((phytium_dp->connector.status == connector_status_connected) && phytium_dp->has_audio) + plugged = true; + else + plugged = false; + + handle_plugged_change(phytium_dp, plugged); + return 0; +} + + +static const struct hdmi_codec_ops phytium_audio_codec_ops = { + .hw_params = phytium_dp_audio_hw_params, + .audio_shutdown = phytium_dp_audio_shutdown, + .mute_stream = phytium_dp_audio_mute_stream, + .get_eld = phytium_dp_audio_get_eld, + .hook_plugged_cb = phytium_dp_audio_hook_plugged_cb, +}; + +static int phytium_dp_audio_codec_init(struct phytium_dp_device *phytium_dp) +{ + struct device *dev = phytium_dp->dev->dev; + struct hdmi_codec_pdata codec_data = { + .i2s = 1, + .spdif = 0, + .ops = &phytium_audio_codec_ops, + .max_i2s_channels = 2, + .data = phytium_dp, + }; + + phytium_dp->audio_pdev = platform_device_register_data(dev, HDMI_CODEC_DRV_NAME, + codec_id, + &codec_data, sizeof(codec_data)); + if (!PTR_ERR_OR_ZERO(phytium_dp->audio_pdev)) + codec_id += 1; + + return PTR_ERR_OR_ZERO(phytium_dp->audio_pdev); +} + +static void phytium_dp_audio_codec_fini(struct phytium_dp_device *phytium_dp) +{ + + if (!PTR_ERR_OR_ZERO(phytium_dp->audio_pdev)) + platform_device_unregister(phytium_dp->audio_pdev); + phytium_dp->audio_pdev = NULL; + codec_id -= 1; +} + +static long phytium_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) +{ + struct phytium_dp_device *phytium_dp = container_of(aux, struct phytium_dp_device, aux); + long ret = 0; + + DRM_DEBUG_KMS("msg->size: 0x%lx\n", msg->size); + + if (WARN_ON(msg->size > 16)) + return -E2BIG; + + switch (msg->request & ~DP_AUX_I2C_MOT) { + case DP_AUX_NATIVE_WRITE: + case DP_AUX_I2C_WRITE: + case DP_AUX_I2C_WRITE_STATUS_UPDATE: + ret = phytium_dp_hw_aux_transfer_write(phytium_dp, msg); + DRM_DEBUG_KMS("aux write reply:0x%x ret:0x%lx\n", msg->reply, ret); + break; + case DP_AUX_NATIVE_READ: + case DP_AUX_I2C_READ: + ret = phytium_dp_hw_aux_transfer_read(phytium_dp, msg); + DRM_DEBUG_KMS("aux read ret:0x%lx\n", ret); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static void phytium_dp_aux_init(struct phytium_dp_device *phytium_dp) +{ + drm_dp_aux_init(&phytium_dp->aux); + phytium_dp->aux.name = kasprintf(GFP_KERNEL, "dp-%d", phytium_dp->port); + phytium_dp->aux.transfer = phytium_dp_aux_transfer; +} + +int phytium_get_encoder_crtc_mask(struct phytium_dp_device *phytium_dp, int port) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int i, mask = 0; + + for_each_pipe_masked(priv, i) { + if (i != port) + mask++; + else + break; + } + + return BIT(mask); +} + +static bool phytium_dp_is_edp(struct phytium_dp_device *phytium_dp, int port) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + + if (priv->info.edp_mask & BIT(port)) + return true; + else + return false; +} + +static bool phytium_edp_init_connector(struct phytium_dp_device *phytium_dp) +{ + enum drm_connector_status status; + struct drm_connector *connector = &phytium_dp->connector; + + phytium_edp_panel_poweron(phytium_dp); + + status = phytium_dp_detect_dpcd(phytium_dp); + if (status == connector_status_disconnected) { + DRM_ERROR("detect edp dpcd failed\n"); + return false; + } + + phytium_dp->edp_edid = drm_get_edid(connector, &phytium_dp->aux.ddc); + if (!phytium_dp->edp_edid) { + DRM_ERROR("get edp edid failed\n"); + return false; + } + + connector->status = status; + phytium_dp->max_link_rate = phytium_dp->common_rates[phytium_dp->num_common_rates-1]; + phytium_dp->max_link_lane_count = phytium_dp->common_max_lane_count; + phytium_dp->link_rate = phytium_dp->max_link_rate; + phytium_dp->link_lane_count = phytium_dp->max_link_lane_count; + DRM_DEBUG_KMS("common_max_lane_count: %d, common_max_rate:%d\n", + phytium_dp->max_link_lane_count, phytium_dp->max_link_rate); + + return true; +} + +static void phytium_edp_fini_connector(struct phytium_dp_device *phytium_dp) +{ + kfree(phytium_dp->edp_edid); + + phytium_dp->edp_edid = NULL; + phytium_edp_panel_poweroff(phytium_dp); +} + +int phytium_dp_resume(struct drm_device *drm_dev) +{ + struct phytium_dp_device *phytium_dp; + struct drm_encoder *encoder; + int ret = 0; + + drm_for_each_encoder(encoder, drm_dev) { + phytium_dp = encoder_to_dp_device(encoder); + if (phytium_dp->is_edp) { + phytium_edp_backlight_off(phytium_dp); + phytium_edp_panel_poweroff(phytium_dp); + } + ret = phytium_dp_hw_init(phytium_dp); + if (ret) { + DRM_ERROR("failed to initialize dp %d\n", phytium_dp->port); + return -EIO; + } + } + + return 0; +} + +int phytium_dp_init(struct drm_device *dev, int port) +{ + struct phytium_display_private *priv = dev->dev_private; + struct phytium_dp_device *phytium_dp = NULL; + int ret, type; + + DRM_DEBUG_KMS("%s: port %d\n", __func__, port); + phytium_dp = kzalloc(sizeof(*phytium_dp), GFP_KERNEL); + if (!phytium_dp) { + ret = -ENOMEM; + goto failed_malloc_dp; + } + + phytium_dp->dev = dev; + phytium_dp->port = port; + + if (IS_PX210(priv)) { + px210_dp_func_register(phytium_dp); + priv->dp_reg_base[port] = PX210_DP_BASE(port); + priv->phy_access_base[port] = PX210_PHY_ACCESS_BASE(port); + } else if (IS_PE220X(priv)) { + pe220x_dp_func_register(phytium_dp); + priv->dp_reg_base[port] = PE220X_DP_BASE(port); + priv->phy_access_base[port] = PE220X_PHY_ACCESS_BASE(port); + } + + if (phytium_dp_is_edp(phytium_dp, port)) { + phytium_dp->is_edp = true; + type = DRM_MODE_CONNECTOR_eDP; + phytium_dp_panel_init_backlight_funcs(phytium_dp); + phytium_edp_backlight_off(phytium_dp); + phytium_edp_panel_poweroff(phytium_dp); + } else { + phytium_dp->is_edp = false; + type = DRM_MODE_CONNECTOR_DisplayPort; + } + + ret = phytium_dp_hw_init(phytium_dp); + if (ret) { + DRM_ERROR("failed to initialize dp %d\n", phytium_dp->port); + goto failed_init_dp; + } + + ret = drm_encoder_init(dev, &phytium_dp->encoder, + &phytium_encoder_funcs, + DRM_MODE_ENCODER_TMDS, "DP %d", port); + if (ret) { + DRM_ERROR("failed to initialize encoder with drm\n"); + goto failed_encoder_init; + } + drm_encoder_helper_add(&phytium_dp->encoder, &phytium_encoder_helper_funcs); + phytium_dp->encoder.possible_crtcs = phytium_get_encoder_crtc_mask(phytium_dp, port); + + phytium_dp->connector.dpms = DRM_MODE_DPMS_OFF; + phytium_dp->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + ret = drm_connector_init(dev, &phytium_dp->connector, &phytium_connector_funcs, + type); + if (ret) { + DRM_ERROR("failed to initialize connector with drm\n"); + goto failed_connector_init; + } + drm_connector_helper_add(&phytium_dp->connector, &phytium_connector_helper_funcs); + drm_connector_attach_encoder(&phytium_dp->connector, &phytium_dp->encoder); + + ret = phytium_dp_audio_codec_init(phytium_dp); + if (ret) { + DRM_ERROR("failed to initialize audio codec\n"); + goto failed_connector_init; + } + + phytium_dp->train_retry_count = 0; + INIT_WORK(&phytium_dp->train_retry_work, phytium_dp_train_retry_work_fn); + drm_connector_register(&phytium_dp->connector); + + return 0; +failed_connector_init: +failed_encoder_init: +failed_init_dp: + kfree(phytium_dp); +failed_malloc_dp: + return ret; +} diff --git a/drivers/gpu/drm/phytium/phytium_dp.h b/drivers/gpu/drm/phytium/phytium_dp.h new file mode 100644 index 0000000000000000000000000000000000000000..ada3f42a68684243bcd45577e068815f9b540aca --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_dp.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_DP_H__ +#define __PHYTIUM_DP_H__ + +#include +#include +#include +#include + +struct phytium_dp_device; + +#include "phytium_panel.h" + +struct audio_info { + int sample_rate; + int channels; + int sample_width; +}; + +struct dp_audio_n_m { + int sample_rate; + int link_rate; + u16 m; + u16 n; +}; + +struct phytium_dp_compliance { + unsigned long test_type; + uint32_t test_link_rate; + u8 test_lane_count; + bool test_active; + u8 reserve[2]; +}; + +struct phytium_dp_func { + uint8_t (*dp_hw_get_source_lane_count)(struct phytium_dp_device *phytium_dp); + int (*dp_hw_reset)(struct phytium_dp_device *phytium_dp); + bool (*dp_hw_spread_is_enable)(struct phytium_dp_device *phytium_dp); + int (*dp_hw_set_backlight)(struct phytium_dp_device *phytium_dp, uint32_t level); + uint32_t (*dp_hw_get_backlight)(struct phytium_dp_device *phytium_dp); + void (*dp_hw_disable_backlight)(struct phytium_dp_device *phytium_dp); + void (*dp_hw_enable_backlight)(struct phytium_dp_device *phytium_dp); + void (*dp_hw_poweroff_panel)(struct phytium_dp_device *phytium_dp); + void (*dp_hw_poweron_panel)(struct phytium_dp_device *phytium_dp); + int (*dp_hw_init_phy)(struct phytium_dp_device *phytium_dp); + void (*dp_hw_set_phy_lane_setting)(struct phytium_dp_device *phytium_dp, + uint32_t link_rate, uint8_t train_set); + int (*dp_hw_set_phy_lane_and_rate)(struct phytium_dp_device *phytium_dp, + uint8_t link_lane_count, + uint32_t link_rate); +}; + +struct phytium_dp_hpd_state { + bool hpd_event_state; + bool hpd_irq_state; + bool hpd_raw_state; + bool hpd_irq_enable; +}; + +struct phytium_dp_device { + struct drm_device *dev; + struct drm_encoder encoder; + struct drm_connector connector; + int port; + struct drm_display_mode mode; + bool link_trained; + bool detect_done; + bool is_edp; + bool reserve0; + struct drm_dp_aux aux; + unsigned char dpcd[DP_RECEIVER_CAP_SIZE]; + uint8_t edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]; + unsigned char downstream_ports[DP_MAX_DOWNSTREAM_PORTS]; + unsigned char sink_count; + + int *source_rates; + int num_source_rates; + int sink_rates[DP_MAX_SUPPORTED_RATES]; + int num_sink_rates; + int common_rates[DP_MAX_SUPPORTED_RATES]; + int num_common_rates; + + int source_max_lane_count; + int sink_max_lane_count; + int common_max_lane_count; + + int max_link_rate; + int max_link_lane_count; + int link_rate; + int link_lane_count; + struct work_struct train_retry_work; + int train_retry_count; + uint32_t trigger_train_fail; + + unsigned char train_set[4]; + struct edid *edp_edid; + bool has_audio; + bool fast_train_support; + bool hw_spread_enable; + bool reserve[1]; + struct platform_device *audio_pdev; + struct audio_info audio_info; + hdmi_codec_plugged_cb plugged_cb; + struct device *codec_dev; + struct phytium_dp_compliance compliance; + struct phytium_dp_func *funcs; + struct phytium_dp_hpd_state dp_hpd_state; + + struct phytium_panel panel; + struct drm_display_mode native_mode; +}; + +union phytium_phy_tp { + struct { + /* DpcdPhyTestPatterns. This field is 2 bits for DP1.1 + * and 3 bits for DP1.2. + */ + uint8_t PATTERN :3; + uint8_t RESERVED :5; + } bits; + uint8_t raw; +}; + +/* PHY test patterns + * The order of test patterns follows DPCD register PHY_TEST_PATTERN (0x248) + */ +enum phytium_dpcd_phy_tp { + PHYTIUM_PHY_TP_NONE = 0, + PHYTIUM_PHY_TP_D10_2, + PHYTIUM_PHY_TP_SYMBOL_ERROR, + PHYTIUM_PHY_TP_PRBS7, + PHYTIUM_PHY_TP_80BIT_CUSTOM, + PHYTIUM_PHY_TP_CP2520_1, + PHYTIUM_PHY_TP_CP2520_2, + PHYTIUM_PHY_TP_CP2520_3, +}; +#define PHYTIUM_DP_AUDIO_ID (('P' << 24) + ('H' << 16) + ('Y' << 8)) +#define encoder_to_dp_device(x) container_of(x, struct phytium_dp_device, encoder) +#define connector_to_dp_device(x) container_of(x, struct phytium_dp_device, connector) +#define panel_to_dp_device(x) container_of(x, struct phytium_dp_device, panel) +#define train_retry_to_dp_device(x) container_of(x, struct phytium_dp_device, train_retry_work) +void phytium_phy_writel(struct phytium_dp_device *phytium_dp, uint32_t address, uint32_t data); +uint32_t phytium_phy_readl(struct phytium_dp_device *phytium_dp, uint32_t address); + +int phytium_dp_init(struct drm_device *dev, int pipe); +int phytium_dp_resume(struct drm_device *drm_dev); +void phytium_dp_hpd_irq_setup(struct drm_device *dev, bool enable); +irqreturn_t phytium_dp_hpd_irq_handler(struct phytium_display_private *priv); +void phytium_dp_hpd_work_func(struct work_struct *work); +const struct dp_audio_n_m *phytium_dp_audio_get_n_m(int link_rate, int sample_rate); +#endif /* __PHYTIUM_DP_H__ */ diff --git a/drivers/gpu/drm/phytium/phytium_fb.c b/drivers/gpu/drm/phytium/phytium_fb.c new file mode 100644 index 0000000000000000000000000000000000000000..879065964729e79326c2ecf4db6196ed70ea1f0b --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_fb.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include "phytium_display_drv.h" +#include "phytium_fb.h" +#include "phytium_gem.h" + +static int +phytium_fb_create_handle(struct drm_framebuffer *fb, struct drm_file *file_priv, + unsigned int *handle) +{ + struct phytium_framebuffer *phytium_fb = to_phytium_framebuffer(fb); + + return drm_gem_handle_create(file_priv, &phytium_fb->phytium_gem_obj[0]->base, handle); +} + +static void phytium_fb_destroy(struct drm_framebuffer *fb) +{ + struct phytium_framebuffer *phytium_fb = to_phytium_framebuffer(fb); + int i, num_planes; + struct drm_gem_object *obj = NULL; + const struct drm_format_info *info; + + info = drm_format_info(fb->format->format); + num_planes = info ? info->num_planes : 1; + + for (i = 0; i < num_planes; i++) { + obj = &phytium_fb->phytium_gem_obj[i]->base; + if (obj) + drm_gem_object_put(obj); + } + + drm_framebuffer_cleanup(fb); + kfree(phytium_fb); +} + +static struct drm_framebuffer_funcs viv_fb_funcs = { + .create_handle = phytium_fb_create_handle, + .destroy = phytium_fb_destroy, +}; + +struct phytium_framebuffer * +phytium_fb_alloc(struct drm_device *dev, const struct drm_mode_fb_cmd2 *mode_cmd, + struct phytium_gem_object **phytium_gem_obj, unsigned int num_planes) +{ + struct phytium_framebuffer *phytium_fb; + int ret = 0, i; + + phytium_fb = kzalloc(sizeof(*phytium_fb), GFP_KERNEL); + if (!phytium_fb) + return ERR_PTR(-ENOMEM); + + drm_helper_mode_fill_fb_struct(dev, &phytium_fb->base, mode_cmd); + + ret = drm_framebuffer_init(dev, &phytium_fb->base, &viv_fb_funcs); + + if (ret) { + DRM_ERROR("Failed to initialize framebuffer: %d\n", ret); + kfree(phytium_fb); + return ERR_PTR(ret); + } + + for (i = 0; i < num_planes; i++) { + phytium_fb->phytium_gem_obj[i] = phytium_gem_obj[i]; + phytium_fb->base.obj[i] = &phytium_gem_obj[i]->base; + } + return phytium_fb; +} + +struct drm_framebuffer * +phytium_fb_create(struct drm_device *dev, struct drm_file *file_priv, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + int ret = 0, i, num_planes; + struct drm_gem_object *obj; + unsigned int hsub, vsub, size; + struct phytium_gem_object *phytium_gem_obj[PHYTIUM_FORMAT_MAX_PLANE] = {0}; + struct phytium_framebuffer *phytium_fb; + struct phytium_display_private *priv = dev->dev_private; + const struct drm_format_info *info; + + info = drm_format_info(mode_cmd->pixel_format); + hsub = info ? info->hsub : 1; + vsub = info ? info->vsub : 1; + num_planes = info ? info->num_planes : 1; + num_planes = min(num_planes, PHYTIUM_FORMAT_MAX_PLANE); + + for (i = 0; i < num_planes; i++) { + unsigned int height = mode_cmd->height / (i ? vsub : 1); + + size = height * mode_cmd->pitches[i] + mode_cmd->offsets[i]; + obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[i]); + if (!obj) { + DRM_ERROR("Failed to lookup GEM object\n"); + ret = -ENXIO; + goto error; + } + + if (obj->size < size) { + drm_gem_object_put(obj); + ret = -EINVAL; + goto error; + } + + phytium_gem_obj[i] = to_phytium_gem_obj(obj); + + ret = priv->dc_hw_fb_format_check(mode_cmd, i); + if (ret < 0) + goto error; + } + + phytium_fb = phytium_fb_alloc(dev, mode_cmd, phytium_gem_obj, i); + if (IS_ERR(phytium_fb)) { + DRM_DEBUG_KMS("phytium_fb_alloc failed\n"); + ret = PTR_ERR(phytium_fb); + goto error; + } + + return &phytium_fb->base; +error: + for (i--; i >= 0; i--) + drm_gem_object_put(&phytium_gem_obj[i]->base); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/phytium/phytium_fb.h b/drivers/gpu/drm/phytium/phytium_fb.h new file mode 100644 index 0000000000000000000000000000000000000000..e096aa30ccb508143ae8940a483eaf64aded4da1 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_fb.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_FB_H__ +#define __PHYTIUM_FB_H__ + +#include + +struct phytium_framebuffer { + struct drm_framebuffer base; + struct phytium_gem_object *phytium_gem_obj[PHYTIUM_FORMAT_MAX_PLANE]; +}; + +#define to_phytium_framebuffer(fb) container_of(fb, struct phytium_framebuffer, base) + +struct phytium_framebuffer *phytium_fb_alloc(struct drm_device *dev, + const struct drm_mode_fb_cmd2 *mode_cmd, + struct phytium_gem_object **phytium_gem_obj, + unsigned int num_planes); + +struct drm_framebuffer *phytium_fb_create(struct drm_device *dev, struct drm_file *file_priv, + const struct drm_mode_fb_cmd2 *mode_cmd); +#endif /* __PHYTIUM_FB_H__ */ diff --git a/drivers/gpu/drm/phytium/phytium_fbdev.c b/drivers/gpu/drm/phytium/phytium_fbdev.c new file mode 100644 index 0000000000000000000000000000000000000000..e929ad281724ade0b47f826bb537b594d7a7af45 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_fbdev.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include "phytium_display_drv.h" +#include "phytium_gem.h" +#include "phytium_fb.h" + + +#define PHYTIUM_MAX_CONNECTOR 1 +#define helper_to_drm_private(x) container_of(x, struct phytium_display_private, fbdev_helper) + +static void phytium_fbdev_destroy(struct fb_info *info) +{ + struct drm_fb_helper *helper = info->par; + struct phytium_display_private *priv = helper_to_drm_private(helper); + + phytium_gem_free_object(&priv->fbdev_phytium_gem->base); +} + +static int phytium_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct drm_fb_helper *helper = info->par; + struct phytium_display_private *priv = helper_to_drm_private(helper); + + return phytium_gem_mmap_obj(&priv->fbdev_phytium_gem->base, vma); +} + +static const struct fb_ops phytium_fbdev_ops = { + .owner = THIS_MODULE, + DRM_FB_HELPER_DEFAULT_OPS, + .fb_mmap = phytium_fbdev_mmap, + FB_DEFAULT_IOMEM_OPS, + .fb_destroy = phytium_fbdev_destroy, +}; + +static int +phytium_drm_fbdev_create(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) +{ + struct phytium_display_private *priv = helper_to_drm_private(helper); + struct drm_device *dev = helper->dev; + unsigned int bytes_per_pixel; + struct drm_mode_fb_cmd2 mode_cmd = {0}; + struct phytium_framebuffer *phytium_fb = NULL; + struct fb_info *fbi = NULL; + struct drm_framebuffer *fb = NULL; + size_t size = 0; + int ret = 0; + unsigned long offset; + + bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + mode_cmd.pitches[0] = ALIGN(sizes->surface_width * bytes_per_pixel, 128); + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); + size = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height); + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret < 0) { + DRM_ERROR("failed to get mutex lock\n"); + return ret; + } + + priv->fbdev_phytium_gem = phytium_gem_create_object(dev, size); + if (!priv->fbdev_phytium_gem) { + DRM_ERROR("failed to create gem object\n"); + return -ENOMEM; + } + mutex_unlock(&dev->struct_mutex); + + fbi = drm_fb_helper_alloc_info(helper); + if (IS_ERR(fbi)) { + DRM_DEV_ERROR(dev->dev, "Failed to create framebuffer info."); + ret = PTR_ERR(fbi); + goto out; + } + + phytium_fb = phytium_fb_alloc(dev, &mode_cmd, &priv->fbdev_phytium_gem, 1); + if (IS_ERR(phytium_fb)) { + DRM_DEV_ERROR(dev->dev, "Failed to alloc DRM framebuffer.\n"); + ret = PTR_ERR(phytium_fb); + goto out; + } + + helper->fb = &(phytium_fb->base); + fbi->par = helper; + fbi->fbops = &phytium_fbdev_ops; + + fb = helper->fb; + drm_fb_helper_fill_info(fbi, helper, sizes); + + offset = fbi->var.xoffset * bytes_per_pixel; + offset += fbi->var.yoffset * fb->pitches[0]; + fbi->screen_base = priv->fbdev_phytium_gem->vaddr + offset; + fbi->screen_size = priv->fbdev_phytium_gem->base.size; + fbi->fix.smem_len = priv->fbdev_phytium_gem->base.size; + DRM_DEBUG_KMS("FB [%dx%d]-%d kvaddr=%pa offset=%ld size=%zu\n", fb->width, fb->height, + fb->format->depth, &priv->fbdev_phytium_gem->iova, offset, size); + fbi->skip_vt_switch = true; + + return 0; +out: + phytium_gem_free_object(&priv->fbdev_phytium_gem->base); + return ret; +} + +static const struct drm_fb_helper_funcs phytium_drm_fb_helper_funcs = { + .fb_probe = phytium_drm_fbdev_create, +}; + +int phytium_drm_fbdev_init(struct drm_device *dev) +{ + struct phytium_display_private *priv = dev->dev_private; + struct drm_fb_helper *helper; + int ret; + + if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector) + return -EINVAL; + + helper = &priv->fbdev_helper; + drm_fb_helper_prepare(dev, helper, 32, &phytium_drm_fb_helper_funcs); + + ret = drm_fb_helper_init(dev, helper); + if (ret < 0) { + DRM_DEV_ERROR(dev->dev, "Failed to initialize drm fb helper -ret %d\n", ret); + return ret; + } + + ret = drm_fb_helper_initial_config(helper); + return 0; +} + +void phytium_drm_fbdev_fini(struct drm_device *dev) +{ + struct phytium_display_private *priv = dev->dev_private; + struct drm_fb_helper *helper; + + helper = &priv->fbdev_helper; + drm_fb_helper_unregister_info(helper); + + if (helper->fb) + drm_framebuffer_put(helper->fb); + + drm_fb_helper_fini(helper); +} diff --git a/drivers/gpu/drm/phytium/phytium_fbdev.h b/drivers/gpu/drm/phytium/phytium_fbdev.h new file mode 100644 index 0000000000000000000000000000000000000000..fe352557a4f9d14ef037dc6170215b49c5e760e3 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_fbdev.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef _PHYTIUM_FBDEV_H +#define _PHYTIUM_FBDEV_H + +int phytium_drm_fbdev_init(struct drm_device *dev); +void phytium_drm_fbdev_fini(struct drm_device *dev); + +#endif /* _PHYTIUM_FBDEV_H */ diff --git a/drivers/gpu/drm/phytium/phytium_gem.c b/drivers/gpu/drm/phytium/phytium_gem.c new file mode 100644 index 0000000000000000000000000000000000000000..f470f769dce6a0789b7070b6982d05f8d420dd64 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_gem.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phytium_display_drv.h" +#include "phytium_gem.h" + +#define VRAM_POOL_ALLOC_ORDER 12 + +int phytium_memory_pool_alloc(struct phytium_display_private *priv, void **pvaddr, + phys_addr_t *phys_addr, uint64_t size) +{ + unsigned long vaddr; + + vaddr = gen_pool_alloc(priv->memory_pool, size); + if (!vaddr) + return -ENOMEM; + + *phys_addr = gen_pool_virt_to_phys(priv->memory_pool, vaddr); + + *pvaddr = (void *)vaddr; + return 0; +} + +void phytium_memory_pool_free(struct phytium_display_private *priv, void *vaddr, uint64_t size) +{ + gen_pool_free(priv->memory_pool, (unsigned long)vaddr, size); +} + +int phytium_memory_pool_init(struct device *dev, struct phytium_display_private *priv) +{ + int ret = 0; + + priv->memory_pool = gen_pool_create(VRAM_POOL_ALLOC_ORDER, -1); + if (priv->memory_pool == NULL) { + DRM_ERROR("fail to create memory pool\n"); + ret = -1; + goto failed_create_pool; + } + + ret = gen_pool_add_virt(priv->memory_pool, (unsigned long)priv->pool_virt_addr, + priv->pool_phys_addr, priv->pool_size, -1); + if (ret) { + DRM_ERROR("fail to add vram pool\n"); + ret = -1; + goto failed_add_pool_virt; + } + + return 0; + +failed_add_pool_virt: + gen_pool_destroy(priv->memory_pool); + +failed_create_pool: + return ret; +} + +void phytium_memory_pool_fini(struct device *dev, struct phytium_display_private *priv) +{ + gen_pool_destroy(priv->memory_pool); +} + +struct sg_table * +phytium_gem_prime_get_sg_table(struct drm_gem_object *obj) +{ + struct phytium_gem_object *phytium_gem_obj = to_phytium_gem_obj(obj); + struct sg_table *sgt; + struct drm_device *dev = obj->dev; + int ret; + struct page *page = NULL; + + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) { + DRM_DEBUG_KMS("malloc sgt fail\n"); + return ERR_PTR(-ENOMEM); + } + + if ((phytium_gem_obj->memory_type == MEMORY_TYPE_VRAM) || + (phytium_gem_obj->memory_type == MEMORY_TYPE_SYSTEM_CARVEOUT)) { + ret = sg_alloc_table(sgt, 1, GFP_KERNEL); + if (ret) { + DRM_ERROR("failed to allocate sg\n"); + goto sgt_free; + } + page = phys_to_page(phytium_gem_obj->phys_addr); + sg_set_page(sgt->sgl, page, PAGE_ALIGN(phytium_gem_obj->size), 0); + } else if (phytium_gem_obj->memory_type == MEMORY_TYPE_SYSTEM_UNIFIED) { + ret = dma_get_sgtable_attrs(dev->dev, sgt, phytium_gem_obj->vaddr, + phytium_gem_obj->iova, phytium_gem_obj->size, + DMA_ATTR_WRITE_COMBINE); + if (ret) { + DRM_ERROR("failed to allocate sgt, %d\n", ret); + goto sgt_free; + } + } + + return sgt; +sgt_free: + kfree(sgt); + return ERR_PTR(ret); +} + +struct drm_gem_object * +phytium_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sgt) +{ + struct phytium_gem_object *phytium_gem_obj = NULL; + struct scatterlist *s; + dma_addr_t expected; + int ret, i; + + phytium_gem_obj = kzalloc(sizeof(*phytium_gem_obj), GFP_KERNEL); + if (!phytium_gem_obj) { + DRM_ERROR("failed to allocate phytium_gem_obj\n"); + ret = -ENOMEM; + goto failed_malloc; + } + + ret = drm_gem_object_init(dev, &phytium_gem_obj->base, attach->dmabuf->size); + if (ret) { + DRM_ERROR("failed to initialize drm gem object: %d\n", ret); + goto failed_object_init; + } + + expected = sg_dma_address(sgt->sgl); + for_each_sg(sgt->sgl, s, sgt->nents, i) { + if (sg_dma_address(s) != expected) { + DRM_ERROR("sg_table is not contiguous"); + ret = -EINVAL; + goto failed_check_continue; + } + expected = sg_dma_address(s) + sg_dma_len(s); + } + + phytium_gem_obj->iova = sg_dma_address(sgt->sgl); + phytium_gem_obj->sgt = sgt; + + return &phytium_gem_obj->base; +failed_check_continue: + drm_gem_object_release(&phytium_gem_obj->base); +failed_object_init: + kfree(phytium_gem_obj); +failed_malloc: + return ERR_PTR(ret); +} + +int phytium_gem_prime_vmap(struct drm_gem_object *obj, struct iosys_map *map) +{ + struct phytium_gem_object *phytium_obj = to_phytium_gem_obj(obj); + + iosys_map_set_vaddr(map, phytium_obj->vaddr); + + return 0; +} + +void phytium_gem_prime_vunmap(struct drm_gem_object *obj, struct iosys_map *map) +{ + +} + +static void phytium_dma_callback(void *callback_param) +{ + struct completion *comp = callback_param; + + complete(comp); +} + +int phytium_dma_transfer(struct drm_device *drm_dev, int dev_to_mem, void *addr, + dma_addr_t iova, uint64_t size) +{ + struct phytium_display_private *priv = drm_dev->dev_private; + struct dma_chan *dma_chan = priv->dma_chan; + struct sg_table st; + struct scatterlist *sgl; + int ret = 0, timeout; + uint32_t nents, i; + struct dma_slave_config cfg = {0}; + struct dma_async_tx_descriptor *desc; + struct completion comp; + enum dma_data_direction dir; + size_t min = 0; + + nents = DIV_ROUND_UP(size, PAGE_SIZE); + ret = sg_alloc_table(&st, nents, GFP_KERNEL); + if (ret) { + DRM_ERROR("failed to allocate sg_table\n"); + ret = -ENOMEM; + goto failed_sg_alloc_table; + } + + for_each_sg(st.sgl, sgl, st.nents, i) { + min = min_t(size_t, size, PAGE_SIZE - offset_in_page(addr)); + sg_set_page(sgl, vmalloc_to_page(addr), min, offset_in_page(addr)); + addr += min; + size -= min; + } + + memset(&cfg, 0, sizeof(cfg)); + if (dev_to_mem) { + cfg.direction = DMA_DEV_TO_MEM; + cfg.src_addr = iova; + cfg.dst_addr = 0; + dir = DMA_FROM_DEVICE; + } else { + cfg.direction = DMA_MEM_TO_DEV; + cfg.src_addr = 0; + cfg.dst_addr = iova; + dir = DMA_TO_DEVICE; + } + + dmaengine_slave_config(dma_chan, &cfg); + + nents = dma_map_sg(dma_chan->device->dev, st.sgl, st.nents, dir); + if (!nents) { + DRM_DEV_ERROR(drm_dev->dev, "failed to dma_map_sg for dmaengine\n"); + ret = -EINVAL; + goto failed_dma_map_sg; + } + st.nents = nents; + dma_sync_sg_for_device(dma_chan->device->dev, st.sgl, st.nents, dir); + + sgl = st.sgl; + desc = dmaengine_prep_slave_sg(dma_chan, + st.sgl, + st.nents, + cfg.direction, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + DRM_DEV_ERROR(drm_dev->dev, "failed to dmaengine_prep_slave_sg\n"); + ret = -EINVAL; + goto failed_prep_slave_sg; + } + init_completion(&comp); + desc->callback = phytium_dma_callback; + desc->callback_param = ∁ + + dmaengine_submit(desc); + dma_async_issue_pending(dma_chan); + + timeout = wait_for_completion_timeout(&comp, 2 * HZ); + if (timeout == 0) { + DRM_DEV_ERROR(drm_dev->dev, "wait for dma callback timeout\n"); + ret = -EIO; + } + dma_sync_sg_for_cpu(dma_chan->device->dev, st.sgl, st.nents, dir); + +failed_prep_slave_sg: + dma_unmap_sg(dma_chan->device->dev, st.sgl, st.nents, dir); +failed_dma_map_sg: + sg_free_table(&st); +failed_sg_alloc_table: + return ret; +} + +int phytium_gem_suspend(struct drm_device *drm_dev) +{ + struct phytium_display_private *priv = drm_dev->dev_private; + struct phytium_gem_object *phytium_gem_obj = NULL; + int ret = 0; + + list_for_each_entry(phytium_gem_obj, &priv->gem_list_head, list) { + if (phytium_gem_obj->memory_type != MEMORY_TYPE_VRAM) + continue; + + phytium_gem_obj->vaddr_save = vmalloc(phytium_gem_obj->size); + if (!phytium_gem_obj->vaddr_save) + goto malloc_failed; + + if (priv->dma_inited) + ret = phytium_dma_transfer(drm_dev, 1, phytium_gem_obj->vaddr_save, + phytium_gem_obj->iova, phytium_gem_obj->size); + + if ((!priv->dma_inited) || ret) + memcpy(phytium_gem_obj->vaddr_save, phytium_gem_obj->vaddr, + phytium_gem_obj->size); + } + + return 0; +malloc_failed: + list_for_each_entry(phytium_gem_obj, &priv->gem_list_head, list) { + if (phytium_gem_obj->memory_type != MEMORY_TYPE_VRAM) + continue; + + if (phytium_gem_obj->vaddr_save) { + vfree(phytium_gem_obj->vaddr_save); + phytium_gem_obj->vaddr_save = NULL; + } + } + return -ENOMEM; +} + +void phytium_gem_resume(struct drm_device *drm_dev) +{ + struct phytium_display_private *priv = drm_dev->dev_private; + struct phytium_gem_object *phytium_gem_obj = NULL; + + list_for_each_entry(phytium_gem_obj, &priv->gem_list_head, list) { + if (phytium_gem_obj->memory_type != MEMORY_TYPE_VRAM) + continue; + + memcpy(phytium_gem_obj->vaddr, phytium_gem_obj->vaddr_save, phytium_gem_obj->size); + vfree(phytium_gem_obj->vaddr_save); + phytium_gem_obj->vaddr_save = NULL; + } +} + +void phytium_gem_free_object(struct drm_gem_object *obj) +{ + struct phytium_gem_object *phytium_gem_obj = to_phytium_gem_obj(obj); + struct drm_device *dev = obj->dev; + struct phytium_display_private *priv = dev->dev_private; + uint64_t size = phytium_gem_obj->size; + + DRM_DEBUG_KMS("free phytium_gem_obj iova:0x%pa size:0x%lx\n", + &phytium_gem_obj->iova, phytium_gem_obj->size); + if (phytium_gem_obj->vaddr) { + if (phytium_gem_obj->memory_type == MEMORY_TYPE_VRAM) { + phytium_memory_pool_free(priv, phytium_gem_obj->vaddr, size); + priv->mem_state[PHYTIUM_MEM_VRAM_ALLOC] -= size; + } else if (phytium_gem_obj->memory_type == MEMORY_TYPE_SYSTEM_CARVEOUT) { + dma_unmap_page(dev->dev, phytium_gem_obj->iova, size, DMA_TO_DEVICE); + phytium_memory_pool_free(priv, phytium_gem_obj->vaddr, size); + priv->mem_state[PHYTIUM_MEM_SYSTEM_CARVEOUT_ALLOC] -= size; + } else if (phytium_gem_obj->memory_type == MEMORY_TYPE_SYSTEM_UNIFIED) { + dma_free_attrs(dev->dev, size, phytium_gem_obj->vaddr, + phytium_gem_obj->iova, 0); + priv->mem_state[PHYTIUM_MEM_SYSTEM_UNIFIED_ALLOC] -= size; + } + list_del(&phytium_gem_obj->list); + } else if (obj->import_attach) + drm_prime_gem_destroy(obj, phytium_gem_obj->sgt); + drm_gem_object_release(obj); + kfree(phytium_gem_obj); +} + +int phytium_gem_mmap_obj(struct drm_gem_object *obj, struct vm_area_struct *vma) +{ + int ret = 0; + struct phytium_gem_object *phytium_gem_obj = to_phytium_gem_obj(obj); + unsigned long pfn = PHYS_PFN(phytium_gem_obj->phys_addr); + /* + * Clear the VM_PFNMAP flag that was set by drm_gem_mmap(), and set the + * vm_pgoff (used as a fake buffer offset by DRM) to 0 as we want to map + * the whole buffer. + */ + vm_flags_clear(vma, VM_PFNMAP); + vma->vm_pgoff = 0; + vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); + + if (phytium_gem_obj->memory_type == MEMORY_TYPE_VRAM) { + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + ret = remap_pfn_range(vma, vma->vm_start, pfn, + vma->vm_end - vma->vm_start, vma->vm_page_prot); + } else if (phytium_gem_obj->memory_type == MEMORY_TYPE_SYSTEM_CARVEOUT) { + ret = remap_pfn_range(vma, vma->vm_start, pfn, + vma->vm_end - vma->vm_start, vma->vm_page_prot); + } else if (phytium_gem_obj->memory_type == MEMORY_TYPE_SYSTEM_UNIFIED) { + ret = dma_mmap_attrs(obj->dev->dev, vma, phytium_gem_obj->vaddr, + phytium_gem_obj->iova, vma->vm_end - vma->vm_start, 0); + } + if (ret) + drm_gem_vm_close(vma); + + return ret; +} + +int phytium_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int ret = 0; + + ret = drm_gem_mmap(filp, vma); + if (ret < 0) + return ret; + + return phytium_gem_mmap_obj(vma->vm_private_data, vma); +} + +static const struct vm_operations_struct phytium_vm_ops = { + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static const struct drm_gem_object_funcs phytium_drm_gem_object_funcs = { + .free = phytium_gem_free_object, + .get_sg_table = phytium_gem_prime_get_sg_table, + .vmap = phytium_gem_prime_vmap, + .vunmap = phytium_gem_prime_vunmap, + .vm_ops = &phytium_vm_ops, +}; + +struct phytium_gem_object *phytium_gem_create_object(struct drm_device *dev, unsigned long size) +{ + struct phytium_gem_object *phytium_gem_obj = NULL; + struct phytium_display_private *priv = dev->dev_private; + struct page *page = NULL; + int ret = 0; + + phytium_gem_obj = kzalloc(sizeof(*phytium_gem_obj), GFP_KERNEL); + if (!phytium_gem_obj) { + DRM_ERROR("failed to allocate phytium_gem_obj\n"); + ret = -ENOMEM; + goto error; + } + + ret = drm_gem_object_init(dev, &phytium_gem_obj->base, size); + if (ret) { + DRM_ERROR("failed to initialize drm gem object: %d\n", ret); + goto failed_object_init; + } + + if (priv->support_memory_type & MEMORY_TYPE_VRAM) { + ret = phytium_memory_pool_alloc(priv, &phytium_gem_obj->vaddr, + &phytium_gem_obj->phys_addr, size); + if (ret) { + DRM_ERROR("fail to allocate vram buffer with size %lx\n", size); + goto failed_dma_alloc; + } + phytium_gem_obj->iova = phytium_gem_obj->phys_addr; + phytium_gem_obj->memory_type = MEMORY_TYPE_VRAM; + priv->mem_state[PHYTIUM_MEM_VRAM_ALLOC] += size; + } else if (priv->support_memory_type & MEMORY_TYPE_SYSTEM_CARVEOUT) { + ret = phytium_memory_pool_alloc(priv, &phytium_gem_obj->vaddr, + &phytium_gem_obj->phys_addr, size); + if (ret) { + DRM_ERROR("fail to allocate carveout memory with size %lx\n", size); + goto failed_dma_alloc; + } + page = phys_to_page(phytium_gem_obj->phys_addr); + phytium_gem_obj->iova = dma_map_page(dev->dev, page, 0, size, DMA_TO_DEVICE); + if (dma_mapping_error(dev->dev, phytium_gem_obj->iova)) { + DRM_ERROR("fail to dma map carveout memory with size %lx\n", size); + phytium_memory_pool_free(priv, phytium_gem_obj->vaddr, size); + ret = -ENOMEM; + goto failed_dma_alloc; + } + phytium_gem_obj->memory_type = MEMORY_TYPE_SYSTEM_CARVEOUT; + priv->mem_state[PHYTIUM_MEM_SYSTEM_CARVEOUT_ALLOC] += size; + } else if (priv->support_memory_type & MEMORY_TYPE_SYSTEM_UNIFIED) { + phytium_gem_obj->vaddr = dma_alloc_attrs(dev->dev, size, &phytium_gem_obj->iova, + GFP_KERNEL, 0); + if (!phytium_gem_obj->vaddr) { + DRM_ERROR("fail to allocate unified buffer with size %lx\n", size); + ret = -ENOMEM; + goto failed_dma_alloc; + } + phytium_gem_obj->memory_type = MEMORY_TYPE_SYSTEM_UNIFIED; + priv->mem_state[PHYTIUM_MEM_SYSTEM_UNIFIED_ALLOC] += size; + } else { + DRM_ERROR("fail to allocate buffer with size %lx\n", size); + ret = -ENOMEM; + goto failed_dma_alloc; + } + + phytium_gem_obj->base.funcs = &phytium_drm_gem_object_funcs; + + phytium_gem_obj->size = size; + list_add_tail(&phytium_gem_obj->list, &priv->gem_list_head); + DRM_DEBUG_KMS("phytium_gem_obj iova:0x%pa size:0x%lx\n", + &phytium_gem_obj->iova, phytium_gem_obj->size); + return phytium_gem_obj; + +failed_dma_alloc: + drm_gem_object_put(&phytium_gem_obj->base); + + return ERR_PTR(ret); +failed_object_init: + kfree(phytium_gem_obj); +error: + return ERR_PTR(ret); +} + +int phytium_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + int size = 0; + struct phytium_gem_object *phytium_gem_obj = NULL; + int ret = 0; + + args->pitch = ALIGN(args->width*DIV_ROUND_UP(args->bpp, 8), 128); + args->size = args->pitch * args->height; + size = PAGE_ALIGN(args->size); + phytium_gem_obj = phytium_gem_create_object(dev, size); + if (IS_ERR(phytium_gem_obj)) + return PTR_ERR(phytium_gem_obj); + ret = drm_gem_handle_create(file, &phytium_gem_obj->base, &args->handle); + if (ret) { + DRM_ERROR("failed to drm_gem_handle_create\n"); + goto failed_gem_handle; + } + drm_gem_object_put(&phytium_gem_obj->base); + + return 0; +failed_gem_handle: + phytium_gem_free_object(&phytium_gem_obj->base); + return ret; +} diff --git a/drivers/gpu/drm/phytium/phytium_gem.h b/drivers/gpu/drm/phytium/phytium_gem.h new file mode 100644 index 0000000000000000000000000000000000000000..17c438e6e63c9f37cee1948e2547956d2da07afe --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_gem.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_GEM_H__ +#define __PHYTIUM_GEM_H__ + +#include + +struct phytium_gem_object { + struct drm_gem_object base; + phys_addr_t phys_addr; + dma_addr_t iova; + void *vaddr; + unsigned long size; + struct sg_table *sgt; + char memory_type; + char reserve[3]; + struct list_head list; + void *vaddr_save; +}; + +#define to_phytium_gem_obj(obj) container_of(obj, struct phytium_gem_object, base) + +int phytium_memory_pool_init(struct device *dev, struct phytium_display_private *priv); +void phytium_memory_pool_fini(struct device *dev, struct phytium_display_private *priv); +int phytium_gem_mmap_obj(struct drm_gem_object *obj, struct vm_area_struct *vma); +int phytium_gem_mmap(struct file *filp, struct vm_area_struct *vma); +void phytium_gem_free_object(struct drm_gem_object *obj); +struct sg_table *phytium_gem_prime_get_sg_table(struct drm_gem_object *obj); +struct drm_gem_object *phytium_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, struct sg_table *sgt); +void phytium_gem_free_object(struct drm_gem_object *obj); +int phytium_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, unsigned int handle); +struct phytium_gem_object *phytium_gem_create_object(struct drm_device *dev, unsigned long size); +int phytium_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args); +int phytium_gem_suspend(struct drm_device *drm_dev); +void phytium_gem_resume(struct drm_device *drm_dev); +#endif /* __PHYTIUM_GEM_H__ */ diff --git a/drivers/gpu/drm/phytium/phytium_panel.c b/drivers/gpu/drm/phytium/phytium_panel.c new file mode 100644 index 0000000000000000000000000000000000000000..1cd266e868b37a9e376885776da5223ed75a75e0 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_panel.c @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include "phytium_display_drv.h" +#include "phytium_dp.h" +#include "phytium_panel.h" + +static int +phytium_dp_aux_set_backlight(struct phytium_panel *panel, unsigned int level) +{ + struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel); + unsigned char vals[2] = { 0x0 }; + + vals[0] = level; + if (phytium_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) { + vals[0] = (level & 0xFF00) >> 8; + vals[1] = (level & 0xFF); + } + + if (drm_dp_dpcd_write(&phytium_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, + vals, sizeof(vals)) < 0) { + DRM_DEBUG_KMS("Failed to write aux backlight level\n"); + return -EIO; + } + + return 0; +} + +static unsigned int phytium_dp_aux_get_backlight(struct phytium_panel *panel) +{ + unsigned char read_val[2] = { 0x0 }; + unsigned char level = 0; + struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel); + + if (drm_dp_dpcd_read(&phytium_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, + &read_val, sizeof(read_val)) < 0) { + DRM_DEBUG_KMS("Failed to read DPCD register 0x%x\n", + DP_EDP_BACKLIGHT_BRIGHTNESS_MSB); + return 0; + } + + level = read_val[0]; + if (phytium_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) + level = (read_val[0] << 8 | read_val[1]); + + return level; +} + +static void set_aux_backlight_enable(struct phytium_panel *panel, bool enable) +{ + u8 reg_val = 0; + struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel); + + if (!(phytium_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP)) + return; + + if (drm_dp_dpcd_readb(&phytium_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER, + ®_val) < 0) { + DRM_DEBUG_KMS("Failed to read DPCD register 0x%x\n", + DP_EDP_DISPLAY_CONTROL_REGISTER); + return; + } + + if (enable) + reg_val |= DP_EDP_BACKLIGHT_ENABLE; + else + reg_val &= ~(DP_EDP_BACKLIGHT_ENABLE); + + if (drm_dp_dpcd_writeb(&phytium_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER, + reg_val) != 1) { + DRM_DEBUG_KMS("Failed to %s aux backlight\n", + enable ? "enable" : "disable"); + } +} + +static void phytium_dp_aux_enable_backlight(struct phytium_panel *panel) +{ + unsigned char dpcd_buf, new_dpcd_buf, edp_backlight_mode; + struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel); + + if (drm_dp_dpcd_readb(&phytium_dp->aux, + DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &dpcd_buf) != 1) { + DRM_DEBUG_KMS("Failed to read DPCD register 0x%x\n", + DP_EDP_BACKLIGHT_MODE_SET_REGISTER); + return; + } + + new_dpcd_buf = dpcd_buf; + edp_backlight_mode = dpcd_buf & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK; + + switch (edp_backlight_mode) { + case DP_EDP_BACKLIGHT_CONTROL_MODE_PWM: + case DP_EDP_BACKLIGHT_CONTROL_MODE_PRESET: + case DP_EDP_BACKLIGHT_CONTROL_MODE_PRODUCT: + new_dpcd_buf &= ~DP_EDP_BACKLIGHT_CONTROL_MODE_MASK; + new_dpcd_buf |= DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; + break; + + /* Do nothing when it is already DPCD mode */ + case DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD: + default: + break; + } + + if (new_dpcd_buf != dpcd_buf) { + if (drm_dp_dpcd_writeb(&phytium_dp->aux, + DP_EDP_BACKLIGHT_MODE_SET_REGISTER, new_dpcd_buf) < 0) { + DRM_DEBUG_KMS("Failed to write aux backlight mode\n"); + } + } + + set_aux_backlight_enable(panel, true); + phytium_dp_aux_set_backlight(panel, panel->level); +} + +static void phytium_dp_aux_disable_backlight(struct phytium_panel *panel) +{ + set_aux_backlight_enable(panel, false); +} + +static void phytium_dp_aux_setup_backlight(struct phytium_panel *panel) +{ + struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel); + + if (phytium_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) + phytium_dp->panel.max = 0xFFFF; + else + phytium_dp->panel.max = 0xFF; + + phytium_dp->panel.min = 0; + phytium_dp->panel.level = phytium_dp_aux_get_backlight(panel); + phytium_dp->panel.backlight_enabled = (phytium_dp->panel.level != 0); +} + +static void phytium_dp_hw_poweron_panel(struct phytium_panel *panel) +{ + struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel); + + phytium_dp->funcs->dp_hw_poweron_panel(phytium_dp); +} + +static void phytium_dp_hw_poweroff_panel(struct phytium_panel *panel) +{ + struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel); + + phytium_dp->funcs->dp_hw_poweroff_panel(phytium_dp); +} + +static int +phytium_dp_hw_set_backlight(struct phytium_panel *panel, uint32_t level) +{ + int ret; + struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel); + + ret = phytium_dp->funcs->dp_hw_set_backlight(phytium_dp, level); + + return ret; +} + +static uint32_t phytium_dp_hw_get_backlight(struct phytium_panel *panel) +{ + uint32_t ret; + struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel); + + ret = phytium_dp->funcs->dp_hw_get_backlight(phytium_dp); + + return ret; +} + +static void phytium_dp_hw_enable_backlight(struct phytium_panel *panel) +{ + struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel); + + phytium_dp->funcs->dp_hw_set_backlight(phytium_dp, phytium_dp->panel.level); + phytium_dp->funcs->dp_hw_enable_backlight(phytium_dp); +} + +static void phytium_dp_hw_disable_backlight(struct phytium_panel *panel) +{ + struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel); + + phytium_dp->funcs->dp_hw_disable_backlight(phytium_dp); +} + +static void phytium_dp_hw_setup_backlight(struct phytium_panel *panel) +{ + struct drm_device *dev = panel->dev; + struct phytium_display_private *priv = dev->dev_private; + + panel->max = priv->info.backlight_max; + panel->min = 0; + panel->level = phytium_dp_hw_get_backlight(panel); +} + +void phytium_dp_panel_init_backlight_funcs(struct phytium_dp_device *phytium_dp) +{ + if (phytium_dp->edp_dpcd[1] & DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP && + (phytium_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP) && + !(phytium_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_PWM_PIN_CAP)) { + DRM_DEBUG_KMS("AUX Backlight Control Supported!\n"); + phytium_dp->panel.setup_backlight = phytium_dp_aux_setup_backlight; + phytium_dp->panel.enable_backlight = phytium_dp_aux_enable_backlight; + phytium_dp->panel.disable_backlight = phytium_dp_aux_disable_backlight; + phytium_dp->panel.set_backlight = phytium_dp_aux_set_backlight; + phytium_dp->panel.get_backlight = phytium_dp_aux_get_backlight; + } else { + DRM_DEBUG_KMS("SE Backlight Control Supported!\n"); + phytium_dp->panel.setup_backlight = phytium_dp_hw_setup_backlight; + phytium_dp->panel.enable_backlight = phytium_dp_hw_enable_backlight; + phytium_dp->panel.disable_backlight = phytium_dp_hw_disable_backlight; + phytium_dp->panel.set_backlight = phytium_dp_hw_set_backlight; + phytium_dp->panel.get_backlight = phytium_dp_hw_get_backlight; + } + phytium_dp->panel.poweron = phytium_dp_hw_poweron_panel; + phytium_dp->panel.poweroff = phytium_dp_hw_poweroff_panel; + mutex_init(&phytium_dp->panel.panel_lock); + phytium_dp->panel.dev = phytium_dp->dev; + + /* Upper limits from eDP 1.3 spec */ + phytium_dp->panel.panel_power_up_delay = 210; /* t1_t3 */ + phytium_dp->panel.backlight_on_delay = 50; /* t7 */ + phytium_dp->panel.backlight_off_delay = 50; + phytium_dp->panel.panel_power_down_delay = 0; /* t10 */ + phytium_dp->panel.panel_power_cycle_delay = 510; /* t11 + t12 */ +} + +void phytium_dp_panel_release_backlight_funcs(struct phytium_dp_device *phytium_dp) +{ + phytium_dp->panel.setup_backlight = NULL; + phytium_dp->panel.enable_backlight = NULL; + phytium_dp->panel.disable_backlight = NULL; + phytium_dp->panel.set_backlight = NULL; + phytium_dp->panel.get_backlight = NULL; + phytium_dp->panel.poweron = NULL; + phytium_dp->panel.poweroff = NULL; +} + +void phytium_panel_enable_backlight(struct phytium_panel *panel) +{ + + if (panel->enable_backlight) { + mutex_lock(&panel->panel_lock); + msleep(panel->backlight_on_delay); + panel->enable_backlight(panel); + panel->backlight_enabled = true; + mutex_unlock(&panel->panel_lock); + } +} + +void phytium_panel_disable_backlight(struct phytium_panel *panel) +{ + if (panel->disable_backlight) { + mutex_lock(&panel->panel_lock); + panel->disable_backlight(panel); + panel->backlight_enabled = false; + msleep(panel->backlight_off_delay); + mutex_unlock(&panel->panel_lock); + } +} + +void phytium_panel_poweron(struct phytium_panel *panel) +{ + if (panel->poweron) { + mutex_lock(&panel->panel_lock); + panel->poweron(panel); + panel->power_enabled = true; + msleep(panel->panel_power_up_delay); + mutex_unlock(&panel->panel_lock); + } +} + +void phytium_panel_poweroff(struct phytium_panel *panel) +{ + if (panel->poweroff) { + mutex_lock(&panel->panel_lock); + msleep(panel->panel_power_down_delay); + panel->poweroff(panel); + panel->power_enabled = false; + mutex_unlock(&panel->panel_lock); + } +} + +static uint32_t phytium_scale(uint32_t source_val, + uint32_t source_min, uint32_t source_max, + uint32_t target_min, uint32_t target_max) +{ + uint64_t target_val; + + WARN_ON(source_min > source_max); + WARN_ON(target_min > target_max); + + /* defensive */ + source_val = clamp(source_val, source_min, source_max); + + /* avoid overflows */ + target_val = mul_u32_u32(source_val - source_min, target_max - target_min); + target_val = DIV_ROUND_CLOSEST_ULL(target_val, source_max - source_min); + target_val += target_min; + + return target_val; +} + +static inline uint32_t +phytium_scale_hw_to_user(struct phytium_panel *panel, uint32_t hw_level, uint32_t user_max) +{ + return phytium_scale(hw_level, panel->min, panel->max, + 0, user_max); +} + +static inline uint32_t +phytium_scale_user_to_hw(struct phytium_panel *panel, u32 user_level, u32 user_max) +{ + return phytium_scale(user_level, 0, user_max, + panel->min, panel->max); +} + +static int phytium_backlight_device_update_status(struct backlight_device *bd) +{ + struct phytium_panel *panel = bl_get_data(bd); + struct drm_device *dev = panel->dev; + uint32_t hw_level = 0; + int ret = 0; + + DRM_DEBUG_KMS("updating phytium_backlight, brightness=%d/%d\n", + bd->props.brightness, bd->props.max_brightness); + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + hw_level = phytium_scale_user_to_hw(panel, bd->props.brightness, bd->props.max_brightness); + + if ((panel->set_backlight) && (panel->backlight_enabled)) { + mutex_lock(&panel->panel_lock); + ret = panel->set_backlight(panel, hw_level); + panel->level = hw_level; + mutex_unlock(&panel->panel_lock); + } + drm_modeset_unlock(&dev->mode_config.connection_mutex); + + return ret; +} + +static int phytium_backlight_device_get_brightness(struct backlight_device *bd) +{ + struct phytium_panel *panel = bl_get_data(bd); + struct drm_device *dev = panel->dev; + uint32_t hw_level = 0; + int ret; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + if (panel->get_backlight && panel->backlight_enabled) { + mutex_lock(&panel->panel_lock); + hw_level = panel->get_backlight(panel); + panel->level = hw_level; + mutex_unlock(&panel->panel_lock); + } + drm_modeset_unlock(&dev->mode_config.connection_mutex); + ret = phytium_scale_hw_to_user(panel, hw_level, bd->props.max_brightness); + DRM_DEBUG_KMS("get phytium_backlight, brightness=%d/%d\n", + ret, bd->props.max_brightness); + + return ret; +} + +static const struct backlight_ops phytium_backlight_device_ops = { + .update_status = phytium_backlight_device_update_status, + .get_brightness = phytium_backlight_device_get_brightness, +}; + +int phytium_edp_backlight_device_register(struct phytium_dp_device *phytium_dp) +{ + struct backlight_properties props; + char bl_name[16]; + + if (phytium_dp->panel.setup_backlight) { + mutex_lock(&phytium_dp->panel.panel_lock); + phytium_dp->panel.setup_backlight(&phytium_dp->panel); + mutex_unlock(&phytium_dp->panel.panel_lock); + } else { + return -EINVAL; + } + + memset(&props, 0, sizeof(props)); + props.max_brightness = PHYTIUM_MAX_BL_LEVEL; + props.type = BACKLIGHT_RAW; + props.brightness = phytium_scale_hw_to_user(&phytium_dp->panel, phytium_dp->panel.level, + props.max_brightness); + snprintf(bl_name, sizeof(bl_name), "phytium_bl%d", phytium_dp->port); + + phytium_dp->panel.bl_device = + backlight_device_register(bl_name, + phytium_dp->connector.kdev, + &phytium_dp->panel, + &phytium_backlight_device_ops, + &props); + + if (IS_ERR(phytium_dp->panel.bl_device)) { + DRM_ERROR("Failed to register backlight: %ld\n", + PTR_ERR(phytium_dp->panel.bl_device)); + phytium_dp->panel.bl_device = NULL; + return -ENODEV; + } + + DRM_DEBUG_KMS("Connector %s backlight sysfs interface registered\n", + phytium_dp->connector.name); + + return 0; +} + +void phytium_edp_backlight_device_unregister(struct phytium_dp_device *phytium_dp) +{ + if (phytium_dp->panel.bl_device) { + backlight_device_unregister(phytium_dp->panel.bl_device); + phytium_dp->panel.bl_device = NULL; + } +} diff --git a/drivers/gpu/drm/phytium/phytium_panel.h b/drivers/gpu/drm/phytium/phytium_panel.h new file mode 100644 index 0000000000000000000000000000000000000000..f9e2c7e65896f2c129c5d5274f64beb091a73b23 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_panel.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_PANEL_H__ +#define __PHYTIUM_PANEL_H__ +#include "phytium_dp.h" + +#define PHYTIUM_MAX_BL_LEVEL 0xFF + +struct phytium_panel { + struct drm_device *dev; + bool backlight_enabled; + bool power_enabled; + bool reserve1[2]; + unsigned int min; + unsigned int level; + unsigned int max; + struct backlight_device *bl_device; + void (*setup_backlight)(struct phytium_panel *panel); + uint32_t (*get_backlight)(struct phytium_panel *panel); + int (*set_backlight)(struct phytium_panel *panel, uint32_t level); + void (*disable_backlight)(struct phytium_panel *panel); + void (*enable_backlight)(struct phytium_panel *panel); + void (*poweron)(struct phytium_panel *panel); + void (*poweroff)(struct phytium_panel *panel); + struct mutex panel_lock; + uint32_t panel_power_up_delay; + uint32_t backlight_on_delay; + uint32_t backlight_off_delay; + uint32_t panel_power_down_delay; + uint32_t panel_power_cycle_delay; +}; + +void phytium_dp_panel_init_backlight_funcs(struct phytium_dp_device *phytium_dp); +void phytium_panel_release_backlight_funcs(struct phytium_dp_device *phytium_dp); +int phytium_edp_backlight_device_register(struct phytium_dp_device *phytium_dp); +void phytium_edp_backlight_device_unregister(struct phytium_dp_device *phytium_dp); +void phytium_panel_enable_backlight(struct phytium_panel *panel); +void phytium_panel_disable_backlight(struct phytium_panel *panel); +void phytium_panel_poweron(struct phytium_panel *panel); +void phytium_panel_poweroff(struct phytium_panel *panel); + +#endif /* __PHYTIUM_PANEL_H__ */ diff --git a/drivers/gpu/drm/phytium/phytium_pci.c b/drivers/gpu/drm/phytium/phytium_pci.c new file mode 100644 index 0000000000000000000000000000000000000000..f93ab85395c5dc436f9788e124d4f42682c4a90c --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_pci.c @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include "phytium_display_drv.h" +#include "phytium_pci.h" +#include "phytium_dp.h" +#include "phytium_gem.h" +#include "px210_dc.h" +#include "px210_dp.h" +#include "pe220x_dc.h" +#include "pe220x_dp.h" + +int dc_msi_enable; +module_param(dc_msi_enable, int, 0644); +MODULE_PARM_DESC(dc_msi_enable, "Enable DC msi interrupt (0-disabled; 1-enabled; default-0)"); + +void phytium_pci_vram_hw_init(struct phytium_display_private *priv) +{ + struct phytium_pci_private *pci_priv = to_pci_priv(priv); + + pci_priv->dc_hw_vram_init(priv, priv->pool_phys_addr, priv->pool_size); +} + +int phytium_pci_vram_init(struct pci_dev *pdev, struct phytium_display_private *priv) +{ + int ret = 0; + + priv->pool_phys_addr = pci_resource_start(pdev, 2); + priv->pool_size = pci_resource_len(pdev, 2); + if ((priv->pool_phys_addr != 0) && (priv->pool_size != 0)) { + priv->pool_virt_addr = devm_ioremap_wc(&pdev->dev, priv->pool_phys_addr, + priv->pool_size); + if (priv->pool_virt_addr == NULL) { + DRM_ERROR("pci vram ioremap fail, addr:0x%llx, size:0x%llx\n", + priv->pool_phys_addr, priv->pool_size); + ret = -EINVAL; + goto failed_ioremap; + } + ret = phytium_memory_pool_init(&pdev->dev, priv); + if (ret) + goto failed_init_memory_pool; + + priv->mem_state[PHYTIUM_MEM_VRAM_TOTAL] = priv->pool_size; + priv->support_memory_type = MEMORY_TYPE_VRAM; + priv->vram_hw_init = phytium_pci_vram_hw_init; + } else { + DRM_DEBUG_KMS("not support vram\n"); + priv->pool_virt_addr = NULL; + priv->mem_state[PHYTIUM_MEM_VRAM_TOTAL] = 0; + priv->support_memory_type = MEMORY_TYPE_SYSTEM_UNIFIED; + priv->vram_hw_init = NULL; + } + + return 0; + +failed_init_memory_pool: + devm_iounmap(&pdev->dev, priv->pool_virt_addr); +failed_ioremap: + return ret; +} + +void phytium_pci_vram_fini(struct pci_dev *pdev, struct phytium_display_private *priv) +{ + if (priv->support_memory_type == MEMORY_TYPE_VRAM) { + phytium_memory_pool_fini(&pdev->dev, priv); + devm_iounmap(&pdev->dev, priv->pool_virt_addr); + } +} + +static bool phytium_pci_dma_chan_filter(struct dma_chan *chan, void *param) +{ + struct phytium_dma_slave *s = param; + + if (s->dma_dev != chan->device->dev) + return false; + + if (s->chan_id == chan->chan_id) + return true; + else + return false; +} + +int phytium_pci_dma_init(struct phytium_display_private *priv) +{ + struct pci_dev *dma_dev, *gpu_dev; + struct drm_device *drm_dev = priv->dev; + dma_cap_mask_t mask; + struct phytium_dma_slave s; + int ret = 0; + u16 cmd; + + /* check px210 gpu enable */ + gpu_dev = pci_get_device(PCI_VENDOR_ID_PHYTIUM, 0xdc20, NULL); + if (!gpu_dev) { + DRM_INFO("failed to get gpu_dev\n"); + ret = -ENODEV; + goto failed; + } + + pci_read_config_word(gpu_dev, PCI_COMMAND, &cmd); + if (!(cmd & PCI_COMMAND_MASTER)) { + DRM_INFO("gpu_dev master is disabled\n"); + ret = -ENODEV; + goto failed; + } + + dma_dev = pci_get_device(PCI_VENDOR_ID_PHYTIUM, 0xdc3c, NULL); + if (!dma_dev) { + DRM_INFO("failed to get dma_dev\n"); + ret = -ENODEV; + goto failed; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + s.dma_dev = &dma_dev->dev; + s.chan_id = 2; + priv->dma_chan = dma_request_channel(mask, phytium_pci_dma_chan_filter, &s); + if (!priv->dma_chan) { + DRM_DEV_ERROR(drm_dev->dev, "failed to request dma chan\n"); + ret = -EBUSY; + goto failed; + } + priv->dma_inited = 1; + +failed: + return ret; +} + +void phytium_pci_dma_fini(struct phytium_display_private *priv) +{ + if (priv->dma_inited) + dma_release_channel(priv->dma_chan); + priv->dma_inited = 0; + priv->dma_chan = NULL; +} + +static struct phytium_display_private* +phytium_pci_private_init(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + struct phytium_display_private *priv = NULL; + struct phytium_pci_private *pci_priv = NULL; + struct phytium_device_info *phytium_info = (struct phytium_device_info *)ent->driver_data; + int i = 0; + resource_size_t io_addr, io_size; + + pci_priv = devm_kzalloc(&pdev->dev, sizeof(*pci_priv), GFP_KERNEL); + if (!pci_priv) { + DRM_ERROR("no memory to allocate for drm_display_private\n"); + goto failed_malloc_priv; + } + + memset(pci_priv, 0, sizeof(*pci_priv)); + priv = &pci_priv->base; + phytium_display_private_init(priv, dev); + + memcpy(&(priv->info), phytium_info, sizeof(struct phytium_device_info)); + DRM_DEBUG_KMS("priv->info.num_pipes :%d\n", priv->info.num_pipes); + priv->info.pipe_mask = ((pdev->subsystem_device >> PIPE_MASK_SHIFT) & PIPE_MASK_MASK); + priv->info.edp_mask = ((pdev->subsystem_device >> EDP_MASK_SHIFT) & EDP_MASK_MASK); + priv->info.num_pipes = 0; + for_each_pipe_masked(priv, i) + priv->info.num_pipes++; + if (priv->info.num_pipes == 0) { + DRM_ERROR("num_pipes is zero, so exit init\n"); + goto failed_init_numpipe; + } + + io_addr = pci_resource_start(pdev, 0); + io_size = pci_resource_len(pdev, 0); + priv->regs = ioremap(io_addr, io_size); + if (priv->regs == NULL) { + DRM_ERROR("pci bar0 ioremap fail, addr:0x%llx, size:0x%llx\n", io_addr, io_size); + goto failed_ioremap; + } + + priv->irq = pdev->irq; + if (IS_PX210(priv)) { + pci_priv->dc_hw_vram_init = px210_dc_hw_vram_init; + priv->dc_hw_clear_msi_irq = px210_dc_hw_clear_msi_irq; + priv->dc_hw_fb_format_check = px210_dc_hw_fb_format_check; + } else if (IS_PE220X(priv)) { + pci_priv->dc_hw_vram_init = pe220x_dc_hw_vram_init; + priv->dc_hw_clear_msi_irq = NULL; + priv->dc_hw_fb_format_check = pe220x_dc_hw_fb_format_check; + } + + return priv; + +failed_ioremap: +failed_init_numpipe: + devm_kfree(&pdev->dev, pci_priv); +failed_malloc_priv: + return NULL; +} + +static void +phytium_pci_private_fini(struct pci_dev *pdev, struct phytium_display_private *priv) +{ + struct phytium_pci_private *pci_priv = to_pci_priv(priv); + + if (priv->regs) + iounmap(priv->regs); + + devm_kfree(&pdev->dev, pci_priv); +} + +static int phytium_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct phytium_display_private *priv = NULL; + struct drm_device *dev = NULL; + int ret = 0; + + dev = drm_dev_alloc(&phytium_display_drm_driver, &pdev->dev); + if (IS_ERR(dev)) { + DRM_ERROR("failed to allocate drm_device\n"); + return PTR_ERR(dev); + } + pci_set_drvdata(pdev, dev); + pci_set_master(pdev); + ret = pci_enable_device(pdev); + if (ret) { + DRM_ERROR("pci enable device fail\n"); + goto failed_enable_device; + } + + if (dc_msi_enable) { + ret = pci_enable_msi(pdev); + if (ret) + DRM_ERROR("pci enable msi fail\n"); + } + + dma_set_mask(&pdev->dev, DMA_BIT_MASK(40)); + + priv = phytium_pci_private_init(pdev, ent); + if (priv) + dev->dev_private = priv; + else + goto failed_pci_private_init; + + ret = phytium_pci_vram_init(pdev, priv); + if (ret) { + DRM_ERROR("failed to init pci vram\n"); + goto failed_pci_vram_init; + } + + ret = drm_dev_register(dev, 0); + if (ret) { + DRM_ERROR("failed to register drm dev\n"); + goto failed_register_drm; + } + + phytium_dp_hpd_irq_setup(dev, true); + + return 0; + +failed_register_drm: + phytium_pci_vram_fini(pdev, priv); +failed_pci_vram_init: + phytium_pci_private_fini(pdev, priv); +failed_pci_private_init: + if (pdev->msi_enabled) + pci_disable_msi(pdev); + pci_disable_device(pdev); +failed_enable_device: + pci_set_drvdata(pdev, NULL); + drm_dev_put(dev); + + return -1; +} + +static void phytium_pci_remove(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + struct phytium_display_private *priv = dev->dev_private; + + phytium_dp_hpd_irq_setup(dev, false); + cancel_work_sync(&priv->hotplug_work); + drm_dev_unregister(dev); + phytium_pci_vram_fini(pdev, priv); + phytium_pci_private_fini(pdev, priv); + if (pdev->msi_enabled) + pci_disable_msi(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + drm_dev_put(dev); +} + +static void phytium_pci_shutdown(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + struct phytium_display_private *priv = dev->dev_private; + + priv->display_shutdown(dev); +} + +static int phytium_pci_pm_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + struct phytium_display_private *priv = drm_dev->dev_private; + int ret = 0; + + if (IS_PX210(priv)) + phytium_pci_dma_init(priv); + + ret = priv->display_pm_suspend(drm_dev); + if (ret < 0) + goto out; + + pci_save_state(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, PCI_D3hot); + udelay(200); + +out: + return ret; +} + +static int phytium_pci_pm_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + struct phytium_display_private *priv = drm_dev->dev_private; + int ret = 0; + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + ret = pci_enable_device(pdev); + if (ret) + return ret; + pci_set_master(pdev); + + ret = priv->display_pm_resume(drm_dev); + if (IS_PX210(priv)) + phytium_pci_dma_fini(priv); + + return ret; +} + +static const struct dev_pm_ops phytium_pci_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(phytium_pci_pm_suspend, phytium_pci_pm_resume) +}; + +static const struct phytium_device_info px210_info = { + .platform_mask = BIT(PHYTIUM_PLATFORM_PX210), + .total_pipes = 3, + .crtc_clock_max = PX210_DC_PIX_CLOCK_MAX, + .hdisplay_max = PX210_DC_HDISPLAY_MAX, + .vdisplay_max = PX210_DC_VDISPLAY_MAX, + .address_mask = PX210_DC_ADDRESS_MASK, + .backlight_max = PX210_DP_BACKLIGHT_MAX, +}; + +static const struct phytium_device_info pe220x_info = { + .platform_mask = BIT(PHYTIUM_PLATFORM_PE220X), + .total_pipes = 2, + .crtc_clock_max = PE220X_DC_PIX_CLOCK_MAX, + .hdisplay_max = PE220X_DC_HDISPLAY_MAX, + .vdisplay_max = PE220X_DC_VDISPLAY_MAX, + .address_mask = PE220X_DC_ADDRESS_MASK, + .backlight_max = PE220X_DP_BACKLIGHT_MAX, +}; + +static const struct pci_device_id phytium_display_pci_ids[] = { + { PCI_VDEVICE(PHYTIUM, 0xdc22), (kernel_ulong_t)&px210_info }, + { PCI_VDEVICE(PHYTIUM, 0xdc3e), (kernel_ulong_t)&pe220x_info }, + { /* End: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, phytium_display_pci_ids); + +struct pci_driver phytium_pci_driver = { + .name = "phytium_display_pci", + .id_table = phytium_display_pci_ids, + .probe = phytium_pci_probe, + .remove = phytium_pci_remove, + .shutdown = phytium_pci_shutdown, + .driver.pm = &phytium_pci_pm_ops, +}; diff --git a/drivers/gpu/drm/phytium/phytium_pci.h b/drivers/gpu/drm/phytium/phytium_pci.h new file mode 100644 index 0000000000000000000000000000000000000000..92b08fcb045296bc8507960ecd73366994254ec4 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_pci.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_PCI_H__ +#define __PHYTIUM_PCI_H__ + +#include "phytium_display_drv.h" + +struct phytium_pci_private { + struct phytium_display_private base; + void (*dc_hw_vram_init)(struct phytium_display_private *priv, resource_size_t vram_addr, + resource_size_t vram_size); +}; + +struct phytium_dma_slave { + struct device *dma_dev; + u32 chan_id; +}; + +#define to_pci_priv(priv) container_of(priv, struct phytium_pci_private, base) + +extern struct pci_driver phytium_pci_driver; +#endif /* __PHYTIUM_PCI_H__ */ diff --git a/drivers/gpu/drm/phytium/phytium_plane.c b/drivers/gpu/drm/phytium/phytium_plane.c new file mode 100644 index 0000000000000000000000000000000000000000..c7bf90d65dd402546c0fa8bfcea637be7f26e7ba --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_plane.c @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "phytium_display_drv.h" +#include "phytium_plane.h" +#include "phytium_fb.h" +#include "phytium_gem.h" +#include "phytium_crtc.h" +#include "px210_dc.h" +#include "pe220x_dc.h" +#include "phytium_reg.h" + +#define PHYTIUM_CURS_W_SIZE 32 +#define PHYTIUM_CURS_H_SIZE 32 + +void phytium_plane_destroy(struct drm_plane *plane) +{ + struct phytium_plane *phytium_plane = to_phytium_plane(plane); + + drm_plane_cleanup(plane); + kfree(phytium_plane); +} + +/** + * phytium_plane_atomic_get_property - fetch plane property value + * @plane: plane to fetch property for + * @state: state containing the property value + * @property: property to look up + * @val: pointer to write property value into + * + * The DRM core does not store shadow copies of properties for + * atomic-capable drivers. This entrypoint is used to fetch + * the current value of a driver-specific plane property. + */ +static int +phytium_plane_atomic_get_property(struct drm_plane *plane, + const struct drm_plane_state *state, + struct drm_property *property, + uint64_t *val) +{ + DRM_DEBUG_KMS("Unknown plane property [PROP:%d:%s]\n", property->base.id, property->name); + return -EINVAL; +} + +/** + * phytium_plane_atomic_set_property - set plane property value + * @plane: plane to set property for + * @state: state to update property value in + * @property: property to set + * @val: value to set property to + * + * Writes the specified property value for a plane into the provided atomic + * state object. + * + * Returns 0 on success, -EINVAL on unrecognized properties + */ +int +phytium_plane_atomic_set_property(struct drm_plane *plane, + struct drm_plane_state *state, + struct drm_property *property, + uint64_t val) +{ + DRM_DEBUG_KMS("Unknown plane property [PROP:%d:%s]\n", property->base.id, property->name); + return -EINVAL; +} + +struct drm_plane_state * +phytium_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct drm_plane_state *state = NULL; + struct phytium_plane_state *phytium_state = NULL; + + phytium_state = kmemdup(plane->state, sizeof(*phytium_state), GFP_KERNEL); + + if (!phytium_state) + return NULL; + + state = &phytium_state->base; + if (state->fb) + drm_framebuffer_get(state->fb); + + state->fence = NULL; + state->commit = NULL; + + return state; +} + +void +phytium_plane_atomic_destroy_state(struct drm_plane *plane, struct drm_plane_state *state) +{ + struct phytium_plane_state *phytium_state = to_phytium_plane_state(state); + + __drm_atomic_helper_plane_destroy_state(state); + kfree(phytium_state); +} + +const struct drm_plane_funcs phytium_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = phytium_plane_destroy, + .reset = drm_atomic_helper_plane_reset, + .atomic_get_property = phytium_plane_atomic_get_property, + .atomic_set_property = phytium_plane_atomic_set_property, + .atomic_duplicate_state = phytium_plane_atomic_duplicate_state, + .atomic_destroy_state = phytium_plane_atomic_destroy_state, +}; + +static int +phytium_plane_atomic_check(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_device *dev = plane->dev; + struct phytium_display_private *priv = dev->dev_private; + struct drm_framebuffer *fb = state->fb; + struct drm_crtc *crtc = state->crtc; + struct drm_crtc_state *crtc_state; + int src_x, src_y, src_w, src_h; + unsigned long base_offset; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + + if ((!fb) || (!crtc)) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state->state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + if (plane->type == DRM_PLANE_TYPE_CURSOR) { + src_w = state->src_w >> 16; + src_h = state->src_h >> 16; + if (phytium_crtc->scale_enable) + return -EINVAL; + if ((src_w != PHYTIUM_CURS_W_SIZE) || (src_h != PHYTIUM_CURS_W_SIZE)) { + DRM_INFO("Invalid cursor size(%d, %d)\n", src_w, src_h); + return -EINVAL; + } + } else if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + src_x = state->src_x >> 16; + src_y = state->src_y >> 16; + src_w = state->src_w >> 16; + src_h = state->src_h >> 16; + + base_offset = src_x * fb->format->cpp[0] + src_y*fb->pitches[0]; + if (base_offset & (priv->info.address_mask)) { + DRM_ERROR("fb base address is not aligned by 0x%lx byte\n", + priv->info.address_mask); + return -EINVAL; + } + + if (src_w != state->crtc_w || src_h != state->crtc_h) { + DRM_ERROR("scale not support: crtc_w(0x%x)/h(0x%x) src_w(0x%x)/h(0x%x)\n", + state->crtc_w, state->crtc_h, src_w, src_h); + return -EINVAL; + } + + if ((state->crtc_x < 0) || (state->crtc_y < 0)) { + DRM_ERROR("crtc_x(0x%x)/y(0x%x) of drm plane state is invalid\n", + state->crtc_x, state->crtc_y); + return -EINVAL; + } + + if ((state->crtc_x + state->crtc_w > crtc_state->adjusted_mode.hdisplay) + || (state->crtc_y + state->crtc_h > crtc_state->adjusted_mode.vdisplay)) { + DRM_ERROR("plane out of crtc region\n"); + return -EINVAL; + } + } + + return 0; +} + +static void phytium_dc_get_plane_parameter(struct drm_plane *plane) +{ + struct phytium_plane *phytium_plane = to_phytium_plane(plane); + struct drm_framebuffer *fb = plane->state->fb; + struct phytium_framebuffer *phytium_fb = to_phytium_framebuffer(fb); + struct phytium_gem_object *phytium_gem_obj = NULL; + int i, num_planes = 0; + const struct drm_format_info *info; + + info = drm_format_info(fb->format->format); + num_planes = info ? info->num_planes : 1; + + for (i = 0; i < num_planes; i++) { + phytium_gem_obj = phytium_fb->phytium_gem_obj[i]; + phytium_plane->iova[i] = phytium_gem_obj->iova + fb->offsets[i]; + phytium_plane->size[i] = phytium_gem_obj->size - fb->offsets[i]; + + if (fb->modifier == DRM_FORMAT_MOD_PHYTIUM_TILE_MODE0_FBCDC) + phytium_plane->tiling[i] = FRAMEBUFFER_TILE_MODE0; + else if (fb->modifier == DRM_FORMAT_MOD_PHYTIUM_TILE_MODE3_FBCDC) + phytium_plane->tiling[i] = FRAMEBUFFER_TILE_MODE3; + else if (fb->modifier == DRM_FORMAT_MOD_LINEAR) + phytium_plane->tiling[i] = FRAMEBUFFER_LINEAR; + else + phytium_plane->tiling[i] = FRAMEBUFFER_LINEAR; + + if (i == 0) { + switch (fb->format->format) { + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: + phytium_plane->format = FRAMEBUFFER_FORMAT_ARGB2101010; + break; + + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + phytium_plane->format = FRAMEBUFFER_FORMAT_ARGB8888; + break; + + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + phytium_plane->format = FRAMEBUFFER_FORMAT_XRGB8888; + break; + + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_BGRA4444: + phytium_plane->format = FRAMEBUFFER_FORMAT_ARGB4444; + break; + + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_BGRX4444: + phytium_plane->format = FRAMEBUFFER_FORMAT_XRGB4444; + break; + + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRA5551: + phytium_plane->format = FRAMEBUFFER_FORMAT_ARGB1555; + break; + + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_BGRX5551: + phytium_plane->format = FRAMEBUFFER_FORMAT_XRGB1555; + break; + + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + phytium_plane->format = FRAMEBUFFER_FORMAT_RGB565; + break; + + case DRM_FORMAT_YUYV: + phytium_plane->format = FRAMEBUFFER_FORMAT_YUYV; + break; + + case DRM_FORMAT_UYVY: + phytium_plane->format = FRAMEBUFFER_FORMAT_UYVY; + break; + case DRM_FORMAT_NV16: + phytium_plane->format = FRAMEBUFFER_FORMAT_NV16; + break; + case DRM_FORMAT_NV12: + phytium_plane->format = FRAMEBUFFER_FORMAT_NV12; + break; + case DRM_FORMAT_NV21: + phytium_plane->format = FRAMEBUFFER_FORMAT_NV12; + break; + default: + DRM_ERROR("unsupported pixel format (format = %d)\n", + fb->format->format); + return; + } + + switch (fb->format->format) { + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_RGB565: + phytium_plane->swizzle = FRAMEBUFFER_SWIZZLE_ARGB; + phytium_plane->uv_swizzle = FRAMEBUFFER_UVSWIZZLE_DISABLE; + break; + + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_BGR565: + phytium_plane->swizzle = FRAMEBUFFER_SWIZZLE_ABGR; + phytium_plane->uv_swizzle = FRAMEBUFFER_UVSWIZZLE_DISABLE; + break; + + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_RGBX5551: + phytium_plane->swizzle = FRAMEBUFFER_SWIZZLE_RGBA; + phytium_plane->uv_swizzle = FRAMEBUFFER_UVSWIZZLE_DISABLE; + break; + + case DRM_FORMAT_BGRA1010102: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA4444: + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_BGRX5551: + phytium_plane->swizzle = FRAMEBUFFER_SWIZZLE_BGRA; + phytium_plane->uv_swizzle = FRAMEBUFFER_UVSWIZZLE_DISABLE; + break; + + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV12: + phytium_plane->swizzle = FRAMEBUFFER_SWIZZLE_ARGB; + phytium_plane->uv_swizzle = FRAMEBUFFER_UVSWIZZLE_DISABLE; + break; + + default: + DRM_ERROR("unsupported pixel format (format = %d)\n", + fb->format->format); + return; + } + } + } +} + +static void phytium_dc_primary_plane_update(struct drm_plane *plane) +{ + struct drm_device *dev = plane->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_plane *phytium_plane = to_phytium_plane(plane); + struct drm_framebuffer *fb = plane->state->fb; + int phys_pipe = phytium_plane->phys_pipe; + int src_x, src_y, crtc_x, crtc_y, crtc_w, crtc_h; + unsigned long base_offset; + int config; + + src_x = plane->state->src_x >> 16; + src_y = plane->state->src_y >> 16; + crtc_x = plane->state->crtc_x; + crtc_y = plane->state->crtc_y; + crtc_w = plane->state->crtc_w; + crtc_h = plane->state->crtc_h; + + if (phytium_plane->dc_hw_update_dcreq) + phytium_plane->dc_hw_update_dcreq(plane); + phytium_plane->dc_hw_update_primary_hi_addr(plane); + + /* config dc */ + /* Y */ + base_offset = src_x * fb->format->cpp[0] + src_y*fb->pitches[0]; + phytium_writel_reg(priv, (phytium_plane->iova[0] + base_offset) & ADDRESS_MASK, + priv->dc_reg_base[phys_pipe], PHYTIUM_DC_FRAMEBUFFER_Y_ADDRESS); + phytium_writel_reg(priv, ALIGN(fb->pitches[0], 128), + priv->dc_reg_base[phys_pipe], PHYTIUM_DC_FRAMEBUFFER_Y_STRIDE); + + /* U */ + phytium_writel_reg(priv, phytium_plane->iova[1] & 0xffffffff, + priv->dc_reg_base[phys_pipe], PHYTIUM_DC_FRAMEBUFFER_U_ADDRESS); + phytium_writel_reg(priv, ALIGN(fb->pitches[1], 128), + priv->dc_reg_base[phys_pipe], PHYTIUM_DC_FRAMEBUFFER_U_STRIDE); + + /* V */ + phytium_writel_reg(priv, phytium_plane->iova[2] & 0xffffffff, + priv->dc_reg_base[phys_pipe], PHYTIUM_DC_FRAMEBUFFER_V_ADDRESS); + phytium_writel_reg(priv, ALIGN(fb->pitches[2], 128), + priv->dc_reg_base[phys_pipe], PHYTIUM_DC_FRAMEBUFFER_V_STRIDE); + + /* size */ + phytium_writel_reg(priv, (crtc_w & WIDTH_MASK) | ((crtc_h&HEIGHT_MASK) << HEIGHT_SHIFT), + priv->dc_reg_base[phys_pipe], PHYTIUM_DC_FRAMEBUFFER_SIZE); + /* config */ + config = phytium_readl_reg(priv, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_FRAMEBUFFER_CONFIG); + config &= ~(FRAMEBUFFER_FORMAT_MASK << FRAMEBUFFER_FORMAT_SHIFT); + config |= (phytium_plane->format << FRAMEBUFFER_FORMAT_SHIFT); + config &= ~(1 << FRAMEBUFFER_UVSWIZZLE_SHIFT); + config |= (phytium_plane->uv_swizzle << FRAMEBUFFER_UVSWIZZLE_SHIFT); + config &= ~(FRAMEBUFFER_SWIZZLE_MASK << FRAMEBUFFER_SWIZZLE_SHIFT); + config |= (phytium_plane->swizzle << FRAMEBUFFER_SWIZZLE_SHIFT); + config &= ~(FRAMEBUFFER_TILE_MODE_MASK << FRAMEBUFFER_TILE_MODE_SHIFT); + config |= (phytium_plane->tiling[0] << FRAMEBUFFER_TILE_MODE_SHIFT); + config &= (~FRAMEBUFFER_CLEAR); + phytium_writel_reg(priv, config, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_FRAMEBUFFER_CONFIG); +} + +static void phytium_dc_cursor_plane_update(struct drm_plane *plane) +{ + struct drm_device *dev = plane->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_plane *phytium_plane = to_phytium_plane(plane); + int phys_pipe = phytium_plane->phys_pipe; + int config; + unsigned long iova; + + phytium_plane->enable = 1; + phytium_plane->cursor_hot_x = plane->state->hotspot_x; + phytium_plane->cursor_hot_y = plane->state->hotspot_y; + phytium_plane->cursor_x = plane->state->crtc_x + plane->state->hotspot_x; + phytium_plane->cursor_y = plane->state->crtc_y + plane->state->hotspot_y; + + config = CURSOR_FORMAT_ARGB8888 | + ((phytium_plane->cursor_hot_y & CURSOR_HOT_Y_MASK) << CURSOR_HOT_Y_SHIFT) | + ((phytium_plane->cursor_hot_x & CURSOR_HOT_X_MASK) << CURSOR_HOT_X_SHIFT); + phytium_writel_reg(priv, config, priv->dc_reg_base[phys_pipe], PHYTIUM_DC_CURSOR_CONFIG); + + config = ((phytium_plane->cursor_x & CURSOR_X_MASK) << CURSOR_X_SHIFT) | + ((phytium_plane->cursor_y & CURSOR_Y_MASK) << CURSOR_Y_SHIFT); + phytium_writel_reg(priv, config, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_CURSOR_LOCATION); + iova = phytium_plane->iova[0]; + phytium_writel_reg(priv, iova & 0xffffffff, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_CURSOR_ADDRESS); + if (phytium_plane->dc_hw_update_cursor_hi_addr) + phytium_plane->dc_hw_update_cursor_hi_addr(plane, iova); +} + +static void phytium_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_framebuffer *fb, *old_fb; + + DRM_DEBUG_KMS("update plane: type=%d\n", plane->type); + if (!plane->state->crtc || !plane->state->fb) + return; + + fb = plane->state->fb; + old_fb = old_state->fb; + + if (fb) + drm_framebuffer_get(fb); + if (old_fb) + drm_framebuffer_put(old_fb); + + phytium_dc_get_plane_parameter(plane); + + if (plane->type == DRM_PLANE_TYPE_PRIMARY) + phytium_dc_primary_plane_update(plane); + else if (plane->type == DRM_PLANE_TYPE_CURSOR) + phytium_dc_cursor_plane_update(plane); +} + +static void phytium_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_device *dev = plane->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_plane *phytium_plane = to_phytium_plane(plane); + int phys_pipe = phytium_plane->phys_pipe; + int config; + struct drm_framebuffer *old_fb; + + old_fb = old_state->fb; + if (old_fb) + drm_framebuffer_put(old_fb); + + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + phytium_writel_reg(priv, CLEAR_VALUE_RED, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_FRAMEBUFFER_CLEARVALUE); + config = phytium_readl_reg(priv, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_FRAMEBUFFER_CONFIG); + config |= FRAMEBUFFER_CLEAR; + phytium_writel_reg(priv, config, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_FRAMEBUFFER_CONFIG); + } else if (plane->type == DRM_PLANE_TYPE_CURSOR) { + phytium_writel_reg(priv, CURSOR_FORMAT_DISABLED, + priv->dc_reg_base[phys_pipe], PHYTIUM_DC_CURSOR_CONFIG); + } +} + +const struct drm_plane_helper_funcs phytium_plane_helper_funcs = { + .prepare_fb = drm_gem_plane_helper_prepare_fb, + .atomic_check = phytium_plane_atomic_check, + .atomic_update = phytium_plane_atomic_update, + .atomic_disable = phytium_plane_atomic_disable, +}; + +struct phytium_plane *phytium_primary_plane_create(struct drm_device *dev, int phys_pipe) +{ + struct phytium_display_private *priv = dev->dev_private; + struct phytium_plane *phytium_plane = NULL; + struct phytium_plane_state *phytium_plane_state = NULL; + int ret = 0; + unsigned int flags = 0; + const uint32_t *formats = NULL; + uint32_t format_count; + const uint64_t *format_modifiers; + + phytium_plane = kzalloc(sizeof(*phytium_plane), GFP_KERNEL); + if (!phytium_plane) { + ret = -ENOMEM; + goto failed_malloc_plane; + } + + phytium_plane_state = kzalloc(sizeof(*phytium_plane_state), GFP_KERNEL); + if (!phytium_plane_state) { + ret = -ENOMEM; + goto failed_malloc_plane_state; + } + phytium_plane_state->base.plane = &phytium_plane->base; + phytium_plane_state->base.rotation = DRM_MODE_ROTATE_0; + phytium_plane->base.state = &phytium_plane_state->base; + phytium_plane->phys_pipe = phys_pipe; + + if (IS_PX210(priv)) { + phytium_plane->dc_hw_plane_get_format = px210_dc_hw_plane_get_primary_format; + phytium_plane->dc_hw_update_dcreq = px210_dc_hw_update_dcreq; + phytium_plane->dc_hw_update_primary_hi_addr = px210_dc_hw_update_primary_hi_addr; + phytium_plane->dc_hw_update_cursor_hi_addr = NULL; + } else if (IS_PE220X(priv)) { + phytium_plane->dc_hw_plane_get_format = pe220x_dc_hw_plane_get_primary_format; + phytium_plane->dc_hw_update_dcreq = NULL; + phytium_plane->dc_hw_update_primary_hi_addr = pe220x_dc_hw_update_primary_hi_addr; + phytium_plane->dc_hw_update_cursor_hi_addr = NULL; + } + + phytium_plane->dc_hw_plane_get_format(&format_modifiers, &formats, &format_count); + ret = drm_universal_plane_init(dev, &phytium_plane->base, 0x0, + &phytium_plane_funcs, formats, + format_count, + format_modifiers, + DRM_PLANE_TYPE_PRIMARY, "primary %d", phys_pipe); + + if (ret) + goto failed_plane_init; + + flags = DRM_MODE_ROTATE_0; + drm_plane_create_rotation_property(&phytium_plane->base, DRM_MODE_ROTATE_0, flags); + drm_plane_helper_add(&phytium_plane->base, &phytium_plane_helper_funcs); + + return phytium_plane; +failed_plane_init: + kfree(phytium_plane_state); +failed_malloc_plane_state: + kfree(phytium_plane); +failed_malloc_plane: + return ERR_PTR(ret); +} + +struct phytium_plane *phytium_cursor_plane_create(struct drm_device *dev, int phys_pipe) +{ + struct phytium_display_private *priv = dev->dev_private; + struct phytium_plane *phytium_plane = NULL; + struct phytium_plane_state *phytium_plane_state = NULL; + int ret = 0; + unsigned int flags = 0; + const uint32_t *formats = NULL; + uint32_t format_count; + const uint64_t *format_modifiers; + + phytium_plane = kzalloc(sizeof(*phytium_plane), GFP_KERNEL); + if (!phytium_plane) { + ret = -ENOMEM; + goto failed_malloc_plane; + } + + phytium_plane_state = kzalloc(sizeof(*phytium_plane_state), GFP_KERNEL); + if (!phytium_plane_state) { + ret = -ENOMEM; + goto failed_malloc_plane_state; + } + phytium_plane_state->base.plane = &phytium_plane->base; + phytium_plane_state->base.rotation = DRM_MODE_ROTATE_0; + phytium_plane->base.state = &phytium_plane_state->base; + phytium_plane->phys_pipe = phys_pipe; + + if (IS_PX210(priv)) { + phytium_plane->dc_hw_plane_get_format = px210_dc_hw_plane_get_cursor_format; + phytium_plane->dc_hw_update_dcreq = NULL; + phytium_plane->dc_hw_update_primary_hi_addr = NULL; + phytium_plane->dc_hw_update_cursor_hi_addr = NULL; + } else if (IS_PE220X(priv)) { + phytium_plane->dc_hw_plane_get_format = pe220x_dc_hw_plane_get_cursor_format; + phytium_plane->dc_hw_update_dcreq = NULL; + phytium_plane->dc_hw_update_primary_hi_addr = NULL; + phytium_plane->dc_hw_update_cursor_hi_addr = pe220x_dc_hw_update_cursor_hi_addr; + } + + phytium_plane->dc_hw_plane_get_format(&format_modifiers, &formats, &format_count); + ret = drm_universal_plane_init(dev, &phytium_plane->base, 0x0, + &phytium_plane_funcs, + formats, format_count, + format_modifiers, + DRM_PLANE_TYPE_CURSOR, "cursor %d", phys_pipe); + + if (ret) + goto failed_plane_init; + + flags = DRM_MODE_ROTATE_0; + drm_plane_create_rotation_property(&phytium_plane->base, DRM_MODE_ROTATE_0, flags); + drm_plane_helper_add(&phytium_plane->base, &phytium_plane_helper_funcs); + + return phytium_plane; +failed_plane_init: + kfree(phytium_plane_state); +failed_malloc_plane_state: + kfree(phytium_plane); +failed_malloc_plane: + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/phytium/phytium_plane.h b/drivers/gpu/drm/phytium/phytium_plane.h new file mode 100644 index 0000000000000000000000000000000000000000..5527579b03489313f9ef8223e76cfcc2a3bf3def --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_plane.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_PLANE_H__ +#define __PHYTIUM_PLANE_H__ + +struct phytium_plane { + struct drm_plane base; + int phys_pipe; + unsigned long iova[PHYTIUM_FORMAT_MAX_PLANE]; + unsigned long size[PHYTIUM_FORMAT_MAX_PLANE]; + unsigned int format; + unsigned int tiling[PHYTIUM_FORMAT_MAX_PLANE]; + unsigned int swizzle; + unsigned int uv_swizzle; + unsigned int rot_angle; + + /* only for cursor */ + bool enable; + bool reserve[3]; + unsigned int cursor_x; + unsigned int cursor_y; + unsigned int cursor_hot_x; + unsigned int cursor_hot_y; + + void (*dc_hw_plane_get_format)(const uint64_t **format_modifiers, + const uint32_t **formats, + uint32_t *format_count); + void (*dc_hw_update_dcreq)(struct drm_plane *plane); + void (*dc_hw_update_primary_hi_addr)(struct drm_plane *plane); + void (*dc_hw_update_cursor_hi_addr)(struct drm_plane *plane, uint64_t iova); +}; + +struct phytium_plane_state { + struct drm_plane_state base; +}; + +#define to_phytium_plane(x) container_of(x, struct phytium_plane, base) +#define to_phytium_plane_state(x) container_of(x, struct phytium_plane_state, base) + +struct phytium_plane *phytium_primary_plane_create(struct drm_device *dev, int pipe); +struct phytium_plane *phytium_cursor_plane_create(struct drm_device *dev, int pipe); +#endif /* __PHYTIUM_PLANE_H__ */ diff --git a/drivers/gpu/drm/phytium/phytium_platform.c b/drivers/gpu/drm/phytium/phytium_platform.c new file mode 100644 index 0000000000000000000000000000000000000000..d28aadba7c30ebe41007a7c02654c52360ffcb76 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_platform.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium display engine DRM driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include "phytium_display_drv.h" +#include "phytium_platform.h" +#include "phytium_dp.h" +#include "phytium_gem.h" +#include "pe220x_dc.h" +#include "pe220x_dp.h" + +int phytium_platform_carveout_mem_init(struct platform_device *pdev, + struct phytium_display_private *priv) +{ + struct resource *res; + int ret = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + priv->pool_size = resource_size(res); + priv->pool_phys_addr = res->start; + } + + if ((priv->pool_phys_addr != 0) && (priv->pool_size != 0)) { + priv->pool_virt_addr = ioremap_cache(priv->pool_phys_addr, priv->pool_size); + if (priv->pool_virt_addr == NULL) { + DRM_ERROR("failed to remap carveout mem(0x%llx)\n", priv->pool_phys_addr); + ret = -EINVAL; + goto failed_ioremap; + } + ret = phytium_memory_pool_init(&pdev->dev, priv); + if (ret) + goto failed_init_memory_pool; + + priv->mem_state[PHYTIUM_MEM_SYSTEM_CARVEOUT_TOTAL] = priv->pool_size; + priv->support_memory_type = MEMORY_TYPE_SYSTEM_CARVEOUT; + priv->vram_hw_init = NULL; + } else { + DRM_DEBUG_KMS("not support carveout memory\n"); + priv->mem_state[PHYTIUM_MEM_SYSTEM_CARVEOUT_TOTAL] = 0; + priv->support_memory_type = MEMORY_TYPE_SYSTEM_UNIFIED; + priv->vram_hw_init = NULL; + } + + return 0; + +failed_init_memory_pool: + iounmap(priv->pool_virt_addr); +failed_ioremap: + return ret; +} + +void phytium_platform_carveout_mem_fini(struct platform_device *pdev, + struct phytium_display_private *priv) +{ + if (priv->support_memory_type == MEMORY_TYPE_SYSTEM_CARVEOUT) { + phytium_memory_pool_fini(&pdev->dev, priv); + iounmap(priv->pool_virt_addr); + } +} + +static struct phytium_display_private * +phytium_platform_private_init(struct platform_device *pdev) +{ + struct drm_device *dev = dev_get_drvdata(&pdev->dev); + struct device_node *node; + struct fwnode_handle *np; + struct phytium_display_private *priv = NULL; + struct phytium_platform_private *platform_priv = NULL; + struct phytium_device_info *phytium_info = NULL; + int i = 0, ret = 0; + struct resource *res; + + platform_priv = devm_kzalloc(&pdev->dev, sizeof(*platform_priv), GFP_KERNEL); + if (!platform_priv) { + DRM_ERROR("no memory to allocate for phytium_platform_private\n"); + goto exit; + } + + memset(platform_priv, 0, sizeof(*platform_priv)); + priv = &platform_priv->base; + phytium_display_private_init(priv, dev); + + if (pdev->dev.of_node) { + phytium_info = (struct phytium_device_info *)of_device_get_match_data(&pdev->dev); + if (!phytium_info) { + DRM_ERROR("failed to get dts id data(phytium_info)\n"); + goto failed; + } + + memcpy(&(priv->info), phytium_info, sizeof(struct phytium_device_info)); + node = pdev->dev.of_node; + ret = of_property_read_u8(node, "pipe_mask", &priv->info.pipe_mask); + if (ret < 0) { + dev_err(&pdev->dev, "missing pipe_mask property from dts\n"); + goto failed; + } + + ret = of_property_read_u8(node, "edp_mask", &priv->info.edp_mask); + if (ret < 0) { + dev_err(&pdev->dev, "missing edp_mask property from dts\n"); + goto failed; + } + } else if (has_acpi_companion(&pdev->dev)) { + phytium_info = (struct phytium_device_info *)acpi_device_get_match_data(&pdev->dev); + if (!phytium_info) { + DRM_ERROR("failed to get acpi id data(phytium_info)\n"); + goto failed; + } + + memcpy(&(priv->info), phytium_info, sizeof(struct phytium_device_info)); + np = dev_fwnode(&(pdev->dev)); + ret = fwnode_property_read_u8(np, "pipe_mask", &priv->info.pipe_mask); + if (ret < 0) { + dev_err(&pdev->dev, "missing pipe_mask property from acpi\n"); + goto failed; + } + ret = fwnode_property_read_u8(np, "edp_mask", &priv->info.edp_mask); + if (ret < 0) { + dev_err(&pdev->dev, "missing edp_mask property from acpi\n"); + goto failed; + } + } + + priv->info.num_pipes = 0; + for_each_pipe_masked(priv, i) + priv->info.num_pipes++; + if (priv->info.num_pipes == 0) { + DRM_ERROR("num_pipes is zero, so exit init\n"); + goto failed; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(&pdev->dev, res); + if (priv->regs == NULL) { + DRM_ERROR("ioremap fail, addr:0x%llx, size:0x%llx\n", res->start, res->end); + goto failed; + } + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) { + dev_err(&pdev->dev, "failed to get irq\n"); + goto failed; + } + + if (IS_PE220X(priv)) { + priv->dc_hw_clear_msi_irq = NULL; + priv->dc_hw_fb_format_check = pe220x_dc_hw_fb_format_check; + } + + return priv; + +failed: + devm_kfree(&pdev->dev, platform_priv); +exit: + return NULL; +} + +static void phytium_platform_private_fini(struct platform_device *pdev) +{ + struct drm_device *dev = dev_get_drvdata(&pdev->dev); + struct phytium_display_private *priv = dev->dev_private; + struct phytium_platform_private *platform_priv = to_platform_priv(priv); + + devm_kfree(&pdev->dev, platform_priv); +} + +static int phytium_platform_probe(struct platform_device *pdev) +{ + struct phytium_display_private *priv = NULL; + struct drm_device *dev = NULL; + int ret = 0; + + dev = drm_dev_alloc(&phytium_display_drm_driver, &pdev->dev); + if (IS_ERR(dev)) { + DRM_ERROR("failed to allocate drm_device\n"); + return PTR_ERR(dev); + } + + dev_set_drvdata(&pdev->dev, dev); + dma_set_mask(&pdev->dev, DMA_BIT_MASK(40)); + + priv = phytium_platform_private_init(pdev); + if (priv) + dev->dev_private = priv; + else + goto failed_platform_private_init; + + ret = phytium_platform_carveout_mem_init(pdev, priv); + if (ret) { + DRM_ERROR("failed to init system carveout memory\n"); + goto failed_carveout_mem_init; + } + + ret = drm_dev_register(dev, 0); + if (ret) { + DRM_ERROR("failed to register drm dev\n"); + goto failed_register_drm; + } + + phytium_dp_hpd_irq_setup(dev, true); + + return 0; + +failed_register_drm: + phytium_platform_carveout_mem_fini(pdev, priv); +failed_carveout_mem_init: + phytium_platform_private_fini(pdev); +failed_platform_private_init: + dev_set_drvdata(&pdev->dev, NULL); + drm_dev_put(dev); + return -1; +} + +static int phytium_platform_remove(struct platform_device *pdev) +{ + struct drm_device *dev = dev_get_drvdata(&pdev->dev); + struct phytium_display_private *priv = dev->dev_private; + + phytium_dp_hpd_irq_setup(dev, false); + cancel_work_sync(&priv->hotplug_work); + drm_dev_unregister(dev); + phytium_platform_private_fini(pdev); + dev_set_drvdata(&pdev->dev, NULL); + drm_dev_put(dev); + + return 0; +} + +static void phytium_platform_shutdown(struct platform_device *pdev) +{ + struct drm_device *dev = dev_get_drvdata(&pdev->dev); + struct phytium_display_private *priv = dev->dev_private; + + priv->display_shutdown(dev); +} + +static int phytium_platform_pm_suspend(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + struct phytium_display_private *priv = drm_dev->dev_private; + + return priv->display_pm_suspend(drm_dev); +} + +static int phytium_platform_pm_resume(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + struct phytium_display_private *priv = drm_dev->dev_private; + + return priv->display_pm_resume(drm_dev); +} + +static const struct dev_pm_ops phytium_platform_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(phytium_platform_pm_suspend, phytium_platform_pm_resume) +}; + +static const struct phytium_device_info pe220x_info = { + .platform_mask = BIT(PHYTIUM_PLATFORM_PE220X), + .total_pipes = 2, + .crtc_clock_max = PE220X_DC_PIX_CLOCK_MAX, + .hdisplay_max = PE220X_DC_HDISPLAY_MAX, + .vdisplay_max = PE220X_DC_VDISPLAY_MAX, + .address_mask = PE220X_DC_ADDRESS_MASK, + .backlight_max = PE220X_DP_BACKLIGHT_MAX, +}; + +static const struct of_device_id display_of_match[] = { + { + .compatible = "phytium,dc", + .data = &pe220x_info, + }, + { } +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id display_acpi_ids[] = { + { + .id = "PHYT0015", + .driver_data = (kernel_ulong_t)&pe220x_info, + }, + {}, +}; + +MODULE_DEVICE_TABLE(acpi, display_acpi_ids); +#else +#define display_acpi_ids NULL +#endif + +struct platform_driver phytium_platform_driver = { + .driver = { + .name = "phytium_display_platform", + .of_match_table = of_match_ptr(display_of_match), + .acpi_match_table = ACPI_PTR(display_acpi_ids), + }, + .probe = phytium_platform_probe, + .remove = phytium_platform_remove, + .shutdown = phytium_platform_shutdown, + .driver.pm = &phytium_platform_pm_ops, +}; diff --git a/drivers/gpu/drm/phytium/phytium_platform.h b/drivers/gpu/drm/phytium/phytium_platform.h new file mode 100644 index 0000000000000000000000000000000000000000..42f6570b476fbc252b5702f7571c3c45c836d1d0 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_platform.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_PLATFORM_H__ +#define __PHYTIUM_PLATFORM_H__ + +struct phytium_platform_private { + struct phytium_display_private base; +}; + +#define to_platform_priv(priv) container_of(priv, struct phytium_platform_private, base) + +extern struct platform_driver phytium_platform_driver; + +#endif /* __PHYTIUM_PLATFORM_H__ */ diff --git a/drivers/gpu/drm/phytium/phytium_reg.h b/drivers/gpu/drm/phytium/phytium_reg.h new file mode 100644 index 0000000000000000000000000000000000000000..99ac9d4cb4d969831443d92db500eeda6af7fc62 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_reg.h @@ -0,0 +1,365 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_REG_H__ +#define __PHYTIUM_REG_H__ + +/******************************register base******************************************/ +#define PX210_PIPE_BASE(pipe) (0x8000*pipe) +#define PX210_DC_BASE(pipe) (PX210_PIPE_BASE(pipe) + 0x0000) +#define PX210_DCREQ_BASE(pipe) (PX210_PIPE_BASE(pipe) + 0x2000) +#define PX210_DP_BASE(pipe) (PX210_PIPE_BASE(pipe) + 0x3000) +#define PX210_ADDRESS_TRANSFORM_BASE 0x4000 +#define PX210_PHY_ACCESS_BASE(pipe) (PX210_PIPE_BASE(pipe) + 0x5000) + +#define PE220X_DC_BASE(pipe) (0x1000*pipe) +#define PE220X_DP_BASE(pipe) (0x4000 + 0x1000*pipe) +#define PE220X_ADDRESS_TRANSFORM_BASE 0x8000 +#define PE220X_PHY_ACCESS_BASE(pipe) (0x6000 + 0x1000*pipe) +/******************************register base end******************************************/ + +/******************************dc register start******************************************/ +#define PHYTIUM_DC_FRAMEBUFFER_Y_ADDRESS 0x1400 + #define ADDRESS_MASK 0xffffff80 +#define PHYTIUM_DC_FRAMEBUFFER_Y_STRIDE 0x1408 +#define PHYTIUM_DC_PANEL_CONFIG 0x1418 + #define PANEL_DATAENABLE_ENABLE (1<<0) + #define PANEL_DATA_ENABLE (1<<4) + #define PANEL_CLOCK_ENABLE (1<<8) +#define PHYTIUM_DC_HDISPLAY 0x1430 + #define HDISPLAY_END_SHIFT 0 + #define HDISPLAY_END_MASK 0x7fff + #define HDISPLAY_TOTAL_SHIFT 16 + #define HDISPLAY_TOTAL_MASK 0x7fff +#define PHYTIUM_DC_HSYNC 0x1438 + #define HSYNC_START_SHIFT 0 + #define HSYNC_START_MASK 0x7fff + #define HSYNC_END_SHIFT 15 + #define HSYNC_END_MASK 0x7fff + #define HSYNC_PULSE_ENABLED (1<<30) + #define HSYNC_NEGATIVE (1<<31) +#define PHYTIUM_DC_VDISPLAY 0x1440 + #define VDISPLAY_END_SHIFT 0 + #define VDISPLAY_END_MASK 0x7fff + #define VDISPLAY_TOTAL_SHIFT 16 + #define VDISPLAY_TOTAL_MASK 0x7fff +#define PHYTIUM_DC_VSYNC 0x1448 + #define VSYNC_START_SHIFT 0 + #define VSYNC_START_MASK 0x7fff + #define VSYNC_END_SHIFT 15 + #define VSYNC_END_MASK 0x7fff + #define VSYNC_PULSE_ENABLED (1<<30) + #define VSYNC_NEGATIVE (1<<31) +#define PHYTIUM_DC_DISPLAY_CURRENT_LOCATION 0x1450 +#define PHYTIUM_DC_GAMMA_INDEX 0x1458 + #define GAMMA_INDEX_MAX 256 +#define PHYTIUM_DC_GAMMA_DATA 0x1460 + #define GAMMA_BLUE_SHIFT 0 + #define GAMMA_BLUE_MASK 0x3ff + #define GAMMA_GREEN_SHIFT 10 + #define GAMMA_GREEN_MASK 0x3ff + #define GAMMA_RED_SHIFT 20 + #define GAMMA_RED_MASK 0x3ff +#define PHYTIUM_DC_CURSOR_CONFIG 0x1468 + #define CURSOR_FORMAT_DISABLED 0x0 + #define CURSOR_FORMAT_MASKMODE 0x3 + #define CURSOR_FORMAT_ARGB8888 0x2 + #define CURSOR_FORMAT_MASK 0x3 + #define CURSOR_HOT_Y_SHIFT 8 + #define CURSOR_HOT_Y_MASK 0x1f + #define CURSOR_HOT_X_SHIFT 16 + #define CURSOR_HOT_X_MASK 0x1f +#define PHYTIUM_DC_CURSOR_ADDRESS 0x146c +#define PHYTIUM_DC_CURSOR_LOCATION 0x1470 + #define CURSOR_X_SHIFT 0 + #define CURSOR_X_MASK 0x7fff + #define CURSOR_Y_SHIFT 16 + #define CURSOR_Y_MASK 0x7fff +#define PHYTIUM_DC_CURSOR_BACKGROUND 0x1474 +#define PHYTIUM_DC_CURSOR_FOREGROUND 0x1478 +#define PHYTIUM_DC_INT_STATUS 0x147c + #define INT_STATUS 0x1 +#define PHYTIUM_DC_INT_ENABLE 0x1480 + #define INT_ENABLE 0x1 + #define INT_DISABLE 0x0 + +#define PHYTIUM_DC_FRAMEBUFFER_CONFIG 0x1518 + #define FRAMEBUFFER_OUTPUT BIT(0) + #define FRAMEBUFFER_GAMMA_ENABLE BIT(2) + #define FRAMEBUFFER_VALID_PENDING BIT(3) + #define FRAMEBUFFER_RESET BIT(4) + #define FRAMEBUFFER_PROGRESS BIT(6) + #define FRAMEBUFFER_ROT_ANGLE_SHIFT (11) + #define FRAMEBUFFER_ROT_ANGLE_MASK (0x7) + #define FRAMEBUFFER_ROT_ANGLE_ROT0 (0) + #define FRAMEBUFFER_ROT_ANGLE_FLIP_X (1) + #define FRAMEBUFFER_ROT_ANGLE_FLIP_Y (2) + #define FRAMEBUFFER_TILE_MODE_SHIFT (17) + #define FRAMEBUFFER_TILE_MODE_MASK (0x1f) + #define FRAMEBUFFER_LINEAR 0 + #define FRAMEBUFFER_TILE_MODE0 4 + #define FRAMEBUFFER_TILE_MODE3 7 + #define FRAMEBUFFER_FORMAT_SHIFT 26 + #define FRAMEBUFFER_FORMAT_MASK 0x3f + #define FRAMEBUFFER_FORMAT_XRGB4444 0x0 + #define FRAMEBUFFER_FORMAT_ARGB4444 0x1 + #define FRAMEBUFFER_FORMAT_XRGB1555 0x2 + #define FRAMEBUFFER_FORMAT_ARGB1555 0x3 + #define FRAMEBUFFER_FORMAT_RGB565 0x4 + #define FRAMEBUFFER_FORMAT_XRGB8888 0x5 + #define FRAMEBUFFER_FORMAT_ARGB8888 0x6 + #define FRAMEBUFFER_FORMAT_YUYV 0x7 + #define FRAMEBUFFER_FORMAT_UYVY 0x8 + #define FRAMEBUFFER_FORMAT_NV12 0x11 + #define FRAMEBUFFER_FORMAT_NV16 0x12 + #define FRAMEBUFFER_FORMAT_ARGB2101010 0x16 + #define FRAMEBUFFER_SWIZZLE_SHIFT 23 + #define FRAMEBUFFER_SWIZZLE_MASK 0x3 + #define FRAMEBUFFER_SWIZZLE_ARGB 0 + #define FRAMEBUFFER_SWIZZLE_RGBA 1 + #define FRAMEBUFFER_SWIZZLE_ABGR 2 + #define FRAMEBUFFER_SWIZZLE_BGRA 3 + #define FRAMEBUFFER_UVSWIZZLE_SHIFT 25 + #define FRAMEBUFFER_UVSWIZZLE_DISABLE 0 + #define FRAMEBUFFER_UVSWIZZLE_ENABLE 1 + #define FRAMEBUFFER_CLEAR BIT(8) + #define FRAMEBUFFER_SCALE_ENABLE BIT(22) +#define PHYTIUM_DC_FRAMEBUFFER_SCALECONFIG 0x1520 + #define FRAMEBUFFER_FILTER_TAP 3 + #define FRAMEBUFFER_HORIZONTAL_FILTER_TAP 3 + #define FRAMEBUFFER_TAP 0x33 +#define PHYTIUM_DC_FRAMEBUFFER_U_ADDRESS 0x1530 +#define PHYTIUM_DC_FRAMEBUFFER_V_ADDRESS 0x1538 +#define PHYTIUM_DC_OVERLAY_CONFIG 0x1540 + #define PX210_DC_OVERLAY_ENABLE BIT(24) + +#define PHYTIUM_DC_FRAMEBUFFER_U_STRIDE 0x1800 +#define PHYTIUM_DC_FRAMEBUFFER_V_STRIDE 0x1808 +#define PHYTIUM_DC_FRAMEBUFFER_SIZE 0x1810 + #define WIDTH_SHIFT 0 + #define WIDTH_MASK 0x7fff + #define HEIGHT_SHIFT 15 + #define HEIGHT_MASK 0x7fff + +#define PHYTIUM_DC_FRAMEBUFFER_SCALE_FACTOR_X 0x1828 + #define SCALE_FACTOR_X_MASK 0x7fffffff +#define PHYTIUM_DC_FRAMEBUFFER_SCALE_FACTOR_Y 0x1830 + #define SCALE_FACTOR_Y_MASK 0x7fffffff + #define SCALE_FACTOR_Y_MAX 0x3 + #define SCALE_FACTOR_SRC_OFFSET 16 + +#define PHYTIUM_DC_FRAMEBUFFER_HORI_FILTER_INDEX 0x1838 + #define HORI_FILTER_INDEX 0x0 +#define PHYTIUM_DC_FRAMEBUFFER_HORI_FILTER 0x1a00 +#define PHYTIUM_DC_FRAMEBUFFER_VERT_FILTER_INDEX 0x1a08 + #define VERT_FILTER_INDEX 0x0 +#define PHYTIUM_DC_FRAMEBUFFER_VERT_FILTER 0x1a10 +#define PHYTIUM_DC_FRAMEBUFFER_CLEARVALUE 0x1a18 + #define CLEAR_VALUE_RED 0x00ff0000 + #define CLEAR_VALUE_GREEN 0x0000ff00 + #define CLEAR_VALUE_BLACK 0x00000000 +#define PHYTIUM_DC_FRAMEBUFFER_INITIALOFFSET 0x1a20 + #define INITIALOFFSET (0x8000 | (0X8000 << 16)) +#define PHYTIUM_DC_DP_CONFIG 0x1cd0 + #define OUTPUT_DP (1<<3) + #define DP_RGB666 (0x1) + #define DP_RGB888 (0x2) + #define DP_RGB101010 (0x3) +/******************************dc register end********************************************/ + +/******************************phy access register****************************************/ +#define PHYTIUM_PHY_ACCESS_ADDRESS 0x0000 +#define PHYTIUM_PHY_WRITE_DATA 0x0004 +#define PHYTIUM_PHY_READ_DATA 0x0008 +#define PHYTIUM_PHY_ACCESS_CTRL 0x000c + #define ACCESS_WRITE (1<<0) + #define ACCESS_READ (1<<1) +/******************************phy access register end*************************************/ + +/******************************dp register start******************************************/ +#define PHYTIUM_DP_LINK_BW_SET 0x0000 +#define PHYTIUM_DP_LANE_COUNT_SET 0x0004 +#define PHYTIUM_DP_ENHANCED_FRAME_EN 0x0008 + #define ENHANCED_FRAME_ENABLE 0x1 + #define ENHANCED_FRAME_DISABLE 0x0 +#define PHYTIUM_DP_TRAINING_PATTERN_SET 0x000c + #define TRAINING_OFF 0x0 + #define TRAINING_PATTERN_1 0x1 + #define TRAINING_PATTERN_2 0x2 + #define TRAINING_PATTERN_3 0x3 + #define TRAINING_PATTERN_4 0x4 +#define PHYTIUM_DP_LINK_QUAL_PATTERN_SET 0x0010 + #define TEST_PATTERN_NONE 0x0 + #define TEST_PATTERN_D10_2 0x1 + #define TEST_PATTERN_SYMBOL_ERROR 0x2 + #define TEST_PATTERN_PRBS7 0x3 + #define TEST_PATTERN_80BIT_CUSTOM 0x4 + #define TEST_PATTERN_CP2520_1 0x5 + #define TEST_PATTERN_CP2520_2 0x6 + #define TEST_PATTERN_CP2520_3 0x7 + #define TEST_PATTERN_LANE_SHIFT 8 +#define PHYTIUM_DP_SCRAMBLING_DISABLE 0x0014 + #define SCRAMBLING_ENABLE 0x0 + #define SCRAMBLING_DISABLE 0x1 +#define PHYTIUM_DP_DOWNSPREAD_CTRL 0x0018 +#define PHYTIUM_DP_ALT_SCRAMBLER_RESET 0x001c +#define PHYTIUM_DP_HBR2_SCRAMBLER_RESET 0x0020 +#define PHYTIUM_DP_DISPLAYPORT_VERSION 0x0024 +#define PHYTIUM_DP_CUSTOM_80BIT_PATTERN_0 0x0030 +#define PHYTIUM_DP_CUSTOM_80BIT_PATTERN_1 0x0034 +#define PHYTIUM_DP_CUSTOM_80BIT_PATTERN_2 0x0038 +#define PHYTIUM_DP_TRANSMITTER_OUTPUT_ENABLE 0x0080 + #define TRANSMITTER_OUTPUT_ENABLE BIT(0) + #define TRANSMITTER_OUTPUT_DISABLE 0 +#define PHYTIUM_DP_VIDEO_STREAM_ENABLE 0x0084 + #define SST_MST_SOURCE_0_ENABLE BIT(0) + #define SST_MST_SOURCE_0_ENABLE_MASK 0x1 + #define SST_MST_SOURCE_0_DISABLE 0 +#define PHYTIUM_DP_SECONDARY_STREAM_ENABLE 0x0088 + #define SECONDARY_STREAM_ENABLE 0x1 + #define SECONDARY_STREAM_DISABLE 0x0 +#define PHYTIUM_DP_SEC_DATA_WINDOW 0x008C +#define PHYTIUM_DP_SOFT_RESET 0x0090 + #define LINK_SOFT_RESET (0x1 << 0) + #define VIDEO_SOFT_RESET (0x1 << 1) +#define PHYTIUM_INPUT_SOURCE_ENABLE 0x0094 + #define VIRTUAL_SOURCE_0_ENABLE BIT(0) + #define VIRTUAL_SOURCE_0_ENABLE_MASK 0x1 +#define PHYTIUM_DP_FORCE_SCRAMBLER_RESET 0x00C0 + #define SCRAMBLER_RESET BIT(0) +#define PHYTIUM_DP_SOURCE_CONTROL_STATUS 0x00C4 +#define PHYTIUM_DP_DATA_CONTROL 0x00C8 +#define PHYTIUM_DP_CORE_CAPABILITY 0x00F8 +#define PHYTIUM_DP_CORE_ID 0x00FC +#define PHYTIUM_DP_AUX_COMMAND 0x0100 + #define BYTE_COUNT_MASK 0xf + #define COMMAND_SHIFT 8 + #define COMMAND_MASK 0xf + #define ADDRESS_ONLY (1<<12) +#define PHYTIUM_DP_AUX_WRITE_FIFO 0x0104 +#define PHYTIUM_DP_AUX_ADDRESS 0x0108 +#define PHYTIUM_DP_AUX_CLK_DIVIDER 0x010C + #define AUX_CLK_DIVIDER 48 + #define AUX_CLK_DIVIDER_100 100 +#define PHYTIUM_DP_SINK_HPD_STATE 0x0128 + #define HPD_CONNECT 0x1 + #define HPD_DISCONNECT 0x0 +#define PHYTIUM_DP_INTERRUPT_RAW_STATUS 0x0130 + #define REPLY_TIMEOUT (1<<3) + #define DP_STATUS_REQUEST_IN_PROGRESS (1<<1) + #define HPD_STATE (0<<1) +#define PHYTIUM_DP_AUX_REPLY_DATA 0x0134 +#define PHYTIUM_DP_AUX_REPLY_CODE 0x0138 + #define AUX_NATIVE_ACK (0x0<<0) + #define AUX_NATIVE_NACK (0x1<<0) + #define AUX_NATIVE_DEFER (0x2<<0) + #define AUX_NATIVE_MASK (0x3 << 0) + #define AUX_I2C_ACK (0x0<<2) + #define AUX_I2C_NACK (0x1<<2) + #define AUX_I2C_DEFER (0x2<<2) + #define AUX_I2C_MASK (0x3 << 2) +#define PHYTIUM_DP_INTERRUPT_STATUS 0x0140 + #define HPD_IRQ (1<<1) + #define HPD_EVENT (1<<0) +#define PHYTIUM_DP_INTERRUPT_MASK 0x0144 + #define HPD_IRQ_MASK (1<<1) + #define HPD_EVENT_MASK (1<<0) + #define HPD_OTHER_MASK 0x3c +#define PHYTIUM_DP_AUX_REPLY_DATA_COUNT 0x0148 +#define PHYTIUM_DP_AUX_STATUS 0x014C + #define REPLY_RECEIVED 0x1 + #define REPLY_IN_PROGRESS 0x2 + #define REQUEST_IN_PROGRESS 0x4 + #define REPLY_ERROR 0x8 +#define PHYTIUM_DP_AUX_TIMER 0x0158 +#define PHYTIUM_DP_MAIN_LINK_HTOTAL 0x0180 +#define PHYTIUM_DP_MAIN_LINK_VTOTAL 0x0184 +#define PHYTIUM_DP_MAIN_LINK_POLARITY 0x0188 + #define VSYNC_POLARITY_LOW BIT(1) + #define HSYNC_POLARITY_LOW BIT(0) +#define PHYTIUM_DP_MAIN_LINK_HSWIDTH 0x018C +#define PHYTIUM_DP_MAIN_LINK_VSWIDTH 0x0190 +#define PHYTIUM_DP_MAIN_LINK_HRES 0x0194 +#define PHYTIUM_DP_MAIN_LINK_VRES 0x0198 +#define PHYTIUM_DP_MAIN_LINK_HSTART 0x019C +#define PHYTIUM_DP_MAIN_LINK_VSTART 0x01A0 +#define PHYTIUM_DP_MAIN_LINK_MISC0 0x01A4 + #define MISC0_SYNCHRONOUS_CLOCK BIT(0) + #define MISC0_BIT_DEPTH_OFFSET 5 + #define MISC0_BIT_DEPTH_6BIT 0x0 + #define MISC0_BIT_DEPTH_8BIT 0x1 + #define MISC0_BIT_DEPTH_10BIT 0x2 + #define MISC0_COMPONENT_FORMAT_SHIFT 1 + #define MISC0_COMPONENT_FORMAT_RGB 0x0 +#define PHYTIUM_DP_MAIN_LINK_MISC1 0x01A8 +#define PHYTIUM_DP_M_VID 0x01AC +#define PHYTIUM_DP_TRANSFER_UNIT_SIZE 0x01B0 +#define PHYTIUM_DP_N_VID 0x01B4 +#define PHYTIUM_DP_USER_PIXEL_WIDTH 0x01B8 +#define PHYTIUM_DP_DATA_COUNT 0x01BC +#define PHYTIUM_DP_INTERLACED 0x01C0 +#define PHYTIUM_DP_USER_SYNC_POLARITY 0x01C4 + #define USER_ODDEVEN_POLARITY_HIGH BIT(3) + #define USER_DATA_ENABLE_POLARITY_HIGH BIT(2) + #define USER_VSYNC_POLARITY_HIGH BIT(1) + #define USER_HSYNC_POLARITY_HIGH BIT(0) +#define PHYTIUM_DP_USER_CONTROL 0x01C8 +#define PHYTIUM_EDP_CRC_ENABLE 0x01D0 + #define SUPPORT_EDP_1_4 BIT(1) +#define PHYTIUM_EDP_CRC_RED 0x01D4 +#define PHYTIUM_EDP_CRC_GREEN 0x01D8 +#define PHYTIUM_EDP_CRC_BLUE 0x01DC +#define PHYTIUM_DP_SEC_AUDIO_ENABLE 0x0300 + #define SEC_AUDIO_ENABLE BIT(0) + #define CHANNEL_MUTE_ENABLE BIT(1) +#define PHYTIUM_DP_SEC_INPUT_SELECT 0x0304 + #define INPUT_SELECT_I2S 0x0 +#define PHYTIUM_DP_SEC_CHANNEL_COUNT 0x0308 + #define CHANNEL_2 0x2 + #define CHANNEL_2_LFE 0x3 + #define CHANNEL_5_1 0x6 + #define CHANNEL_7_1 0x7 + #define CHANNEL_MASK 0xf +#define PHYTIUM_DP_SEC_DIRECT_CLKDIV 0x030c + #define APB_CLOCK 48000000 +#define PHYTIUM_DP_SEC_MAUD 0x0318 +#define PHYTIUM_DP_SEC_NAUD 0x031c +#define PHYTIUM_DP_SEC_CLOCK_MODE 0x0320 + #define CLOCK_MODE_SYNC 0x1 +#define PHYTIUM_DP_SEC_CS_SOURCE_FORMAT 0x0340 + #define CS_SOURCE_FORMAT_DEFAULT 0x0 +#define PHYTIUM_DP_SEC_CS_CATEGORY_CODE 0x0344 +#define PHYTIUM_DP_SEC_CS_LENGTH_ORIG_FREQ 0x0348 + #define ORIG_FREQ_32000 0xc + #define ORIG_FREQ_44100 0xf + #define ORIG_FREQ_48000 0xd + #define ORIG_FREQ_88200 0x7 + #define ORIG_FREQ_96000 0x5 + #define ORIG_FREQ_176400 0x3 + #define ORIG_FREQ_192000 0x1 + #define ORIG_FREQ_MASK 0xf + #define ORIG_FREQ_SHIFT 0 + #define WORD_LENGTH_16 0x4 + #define WORD_LENGTH_18 0x2 + #define WORD_LENGTH_20 0xc + #define WORD_LENGTH_24 0xd + #define WORD_LENGTH_MASK 0xf + #define WORD_LENGTH_SHIFT 4 +#define PHYTIUM_DP_SEC_CS_FREQ_CLOCK_ACCURACY 0x034c // not used + #define SAMPLING_FREQ_32000 0xc + #define SAMPLING_FREQ_44100 0x0 + #define SAMPLING_FREQ_48000 0x4 + #define SAMPLING_FREQ_88200 0x1 + #define SAMPLING_FREQ_96000 0x5 + #define SAMPLING_FREQ_176400 0x3 + #define SAMPLING_FREQ_192000 0x7 + #define SAMPLING_FREQ_MASK 0xf + #define SAMPLING_FREQ_SHIFT 4 +#define PHYTIUM_DP_SEC_CHANNEL_MAP 0x035C + #define CHANNEL_MAP_DEFAULT 0x87654321 +/******************************dp register end********************************************/ + +#endif /* __PHYTIUM_REG_H__ */ diff --git a/drivers/gpu/drm/phytium/px210_dc.c b/drivers/gpu/drm/phytium/px210_dc.c new file mode 100644 index 0000000000000000000000000000000000000000..ae022f9fe3fb3418cad9cacf747ae5953f83750b --- /dev/null +++ b/drivers/gpu/drm/phytium/px210_dc.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include "phytium_display_drv.h" +#include "px210_reg.h" +#include "phytium_crtc.h" +#include "phytium_plane.h" +#include "phytium_fb.h" +#include "phytium_gem.h" + +static const unsigned int px210_primary_formats[] = { + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_BGRA1010102, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_BGRA4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_RGBX4444, + DRM_FORMAT_BGRX4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_YUYV, + DRM_FORMAT_UYVY, +}; + +static uint64_t px210_primary_formats_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_PHYTIUM_TILE_MODE0_FBCDC, + DRM_FORMAT_MOD_PHYTIUM_TILE_MODE3_FBCDC, + DRM_FORMAT_MOD_INVALID +}; + +static uint64_t px210_cursor_formats_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static const unsigned int px210_cursor_formats[] = { + DRM_FORMAT_ARGB8888, +}; + +void px210_dc_hw_vram_init(struct phytium_display_private *priv, resource_size_t vram_addr, + resource_size_t vram_size) +{ + uint32_t config; + uint32_t group_offset = priv->address_transform_base; + + config = phytium_readl_reg(priv, group_offset, + PX210_GPU_ADDRESS_TRANSFORM_SRC_ADDR); + if (config) + phytium_writel_reg(priv, config, group_offset, + PX210_GPU_ADDRESS_TRANSFORM_SRC_ADDR); + + config = phytium_readl_reg(priv, group_offset, + PX210_GPU_ADDRESS_TRANSFORM_SIZE); + if (config) + phytium_writel_reg(priv, config, group_offset, + PX210_GPU_ADDRESS_TRANSFORM_SIZE); + + config = phytium_readl_reg(priv, group_offset, + PX210_GPU_ADDRESS_TRANSFORM_DST_ADDR); + if (config) + phytium_writel_reg(priv, config, group_offset, + PX210_GPU_ADDRESS_TRANSFORM_DST_ADDR); + + phytium_writel_reg(priv, (vram_addr & SRC_ADDR_MASK) >> SRC_ADDR_OFFSET, + group_offset, PX210_DC_ADDRESS_TRANSFORM_SRC_ADDR); + phytium_writel_reg(priv, (vram_size >> SIZE_OFFSET) | ADDRESS_TRANSFORM_ENABLE, + group_offset, PX210_DC_ADDRESS_TRANSFORM_SIZE); + config = phytium_readl_reg(priv, group_offset, PX210_DC_ADDRESS_TRANSFORM_DST_ADDR); + phytium_writel_reg(priv, config, group_offset, PX210_DC_ADDRESS_TRANSFORM_DST_ADDR); +} + +void px210_dc_hw_clear_msi_irq(struct phytium_display_private *priv, uint32_t phys_pipe) +{ + phytium_writel_reg(priv, MSI_CLEAR, priv->dcreq_reg_base[phys_pipe], PX210_DCREQ_MSI_CLEAR); +} + +void px210_dc_hw_config_pix_clock(struct drm_crtc *crtc, int clock) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + int phys_pipe = phytium_crtc->phys_pipe; + uint32_t group_offset = priv->dcreq_reg_base[phys_pipe]; + int ret = 0; + + /* config pix clock */ + phytium_writel_reg(priv, FLAG_REQUEST | CMD_PIXEL_CLOCK | (clock & PIXEL_CLOCK_MASK), + group_offset, PX210_DCREQ_CMD_REGISTER); + ret = phytium_wait_cmd_done(priv, group_offset + PX210_DCREQ_CMD_REGISTER, + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to set pixel clock\n", __func__); +} + +void px210_dc_hw_disable(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_crtc *phytium_crtc = to_phytium_crtc(crtc); + int reset_timeout = 100; + int config = 0; + int phys_pipe = phytium_crtc->phys_pipe; + + // reset dc + config = phytium_readl_reg(priv, priv->dc_reg_base[phys_pipe], PX210_DC_CLOCK_CONTROL); + phytium_writel_reg(priv, config | SOFT_RESET, priv->dc_reg_base[phys_pipe], + PX210_DC_CLOCK_CONTROL); + phytium_writel_reg(priv, 0, priv->dc_reg_base[phys_pipe], PX210_DC_CLOCK_CONTROL); + do { + config = phytium_readl_reg(priv, priv->dc_reg_base[phys_pipe], PX210_DC_CLOCK_IDLE); + if (config | IS_IDLE) + break; + mdelay(1); + reset_timeout--; + } while (reset_timeout); + + /* reset pix clock */ + px210_dc_hw_config_pix_clock(crtc, 0); + + // reset dc + reset_timeout = 100; + config = phytium_readl_reg(priv, priv->dc_reg_base[phys_pipe], PX210_DC_CLOCK_CONTROL); + phytium_writel_reg(priv, config | SOFT_RESET, priv->dc_reg_base[phys_pipe], + PX210_DC_CLOCK_CONTROL); + phytium_writel_reg(priv, 0, priv->dc_reg_base[phys_pipe], PX210_DC_CLOCK_CONTROL); + do { + config = phytium_readl_reg(priv, priv->dc_reg_base[phys_pipe], PX210_DC_CLOCK_IDLE); + if (config | IS_IDLE) + break; + mdelay(1); + reset_timeout--; + } while (reset_timeout); + + /* reset dcreq */ + phytium_writel_reg(priv, DCREQ_PLAN_A, priv->dcreq_reg_base[phys_pipe], PX210_DCREQ_PLAN); + phytium_writel_reg(priv, 0, priv->dcreq_reg_base[phys_pipe], PX210_DCREQ_CONTROL); + phytium_writel_reg(priv, DCREQ_RESET, priv->dcreq_reg_base[phys_pipe], PX210_DCREQ_RESET); + msleep(20); + phytium_writel_reg(priv, (~DCREQ_RESET)&DCREQ_RESET_MASK, + priv->dcreq_reg_base[phys_pipe], PX210_DCREQ_RESET); +} + +int px210_dc_hw_fb_format_check(const struct drm_mode_fb_cmd2 *mode_cmd, int count) +{ + int ret = 0; + + switch (mode_cmd->modifier[count]) { + case DRM_FORMAT_MOD_PHYTIUM_TILE_MODE0_FBCDC: + switch (mode_cmd->pixel_format) { + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_BGRA4444: + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + break; + default: + DRM_ERROR("TILE_MODE0_FBCDC not support DRM_FORMAT %d", + mode_cmd->pixel_format); + ret = -EINVAL; + goto error; + } + break; + case DRM_FORMAT_MOD_PHYTIUM_TILE_MODE3_FBCDC: + switch (mode_cmd->pixel_format) { + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + break; + default: + DRM_ERROR("TILE_MODE3_FBCDC not support DRM_FORMAT %d", + mode_cmd->pixel_format); + ret = -EINVAL; + goto error; + } + break; + case DRM_FORMAT_MOD_LINEAR: + break; + default: + DRM_ERROR("unsupported fb modifier 0x%llx\n", mode_cmd->modifier[0]); + ret = -EINVAL; + goto error; + } + + return 0; +error: + return ret; +} + +void px210_dc_hw_plane_get_primary_format(const uint64_t **format_modifiers, + const uint32_t **formats, + uint32_t *format_count) +{ + *format_modifiers = px210_primary_formats_modifiers; + *formats = px210_primary_formats; + *format_count = ARRAY_SIZE(px210_primary_formats); +} + +void px210_dc_hw_plane_get_cursor_format(const uint64_t **format_modifiers, + const uint32_t **formats, + uint32_t *format_count) +{ + *format_modifiers = px210_cursor_formats_modifiers; + *formats = px210_cursor_formats; + *format_count = ARRAY_SIZE(px210_cursor_formats); +} + +void px210_dc_hw_update_dcreq(struct drm_plane *plane) +{ + struct drm_device *dev = plane->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_plane *phytium_plane = to_phytium_plane(plane); + int phys_pipe = phytium_plane->phys_pipe; + uint32_t group_offset = priv->dcreq_reg_base[phys_pipe]; + int config; + + if (phytium_plane->tiling[0] == FRAMEBUFFER_LINEAR) { + phytium_writel_reg(priv, DCREQ_MODE_LINEAR, + group_offset, PX210_DCREQ_PLANE0_CONFIG); + } else { + config = DCREQ_NO_LOSSY; + if (phytium_plane->tiling[0] == FRAMEBUFFER_TILE_MODE0) + config |= DCREQ_TILE_TYPE_MODE0; + else if (phytium_plane->tiling[0] == FRAMEBUFFER_TILE_MODE3) + config |= DCREQ_TILE_TYPE_MODE3; + else + config |= DCREQ_TILE_TYPE_MODE0; + + switch (phytium_plane->format) { + case FRAMEBUFFER_FORMAT_ARGB8888: + case FRAMEBUFFER_FORMAT_XRGB8888: + config |= DCREQ_COLOURFORMAT_BGRA8888; + break; + case FRAMEBUFFER_FORMAT_ARGB2101010: + config |= DCREQ_COLOURFORMAT_ARGB2101010; + break; + case FRAMEBUFFER_FORMAT_XRGB4444: + case FRAMEBUFFER_FORMAT_ARGB4444: + config |= DCREQ_COLOURFORMAT_ARGB4444; + break; + case FRAMEBUFFER_FORMAT_XRGB1555: + case FRAMEBUFFER_FORMAT_ARGB1555: + config |= DCREQ_COLOURFORMAT_ARGB1555; + break; + case FRAMEBUFFER_FORMAT_RGB565: + config |= DCREQ_COLOURFORMAT_RGB565; + break; + case FRAMEBUFFER_FORMAT_YUYV: + config |= DCREQ_COLOURFORMAT_YUYV; + break; + case FRAMEBUFFER_FORMAT_UYVY: + config |= DCREQ_COLOURFORMAT_UYVY; + break; + } + config |= DCREQ_ARGBSWIZZLE_ARGB; + config |= DCREQ_MODE_TILE; + phytium_writel_reg(priv, phytium_plane->iova[0] & 0xffffffff, + group_offset, PX210_DCREQ_PLANE0_ADDR_START); + phytium_writel_reg(priv, (phytium_plane->iova[0] + phytium_plane->size[0]) & + 0xffffffff, group_offset, PX210_DCREQ_PLANE0_ADDR_END); + phytium_writel_reg(priv, config, group_offset, PX210_DCREQ_PLANE0_CONFIG); + } +} + +void px210_dc_hw_update_primary_hi_addr(struct drm_plane *plane) +{ + struct drm_device *dev = plane->dev; + struct phytium_display_private *priv = dev->dev_private; + struct phytium_plane *phytium_plane = to_phytium_plane(plane); + int phys_pipe = phytium_plane->phys_pipe; + + phytium_writel_reg(priv, (phytium_plane->iova[0] >> PREFIX_SHIFT) & PREFIX_MASK, + priv->dcreq_reg_base[phys_pipe], PX210_DCREQ_PIX_DMA_PREFIX); +} diff --git a/drivers/gpu/drm/phytium/px210_dc.h b/drivers/gpu/drm/phytium/px210_dc.h new file mode 100644 index 0000000000000000000000000000000000000000..1d8220faadc7399f9fad696db53787cff1a0968e --- /dev/null +++ b/drivers/gpu/drm/phytium/px210_dc.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PX210_DC_H__ +#define __PX210_DC_H__ + +#define PX210_DC_PIX_CLOCK_MAX (594000) +#define PX210_DC_HDISPLAY_MAX 3840 +#define PX210_DC_VDISPLAY_MAX 2160 +#define PX210_DC_ADDRESS_MASK 0x7f + +extern void px210_dc_hw_vram_init(struct phytium_display_private *priv, + resource_size_t vram_addr, + resource_size_t vram_size); +extern void px210_dc_hw_clear_msi_irq(struct phytium_display_private *priv, uint32_t phys_pipe); +extern void px210_dc_hw_config_pix_clock(struct drm_crtc *crtc, int clock); +extern void px210_dc_hw_disable(struct drm_crtc *crtc); +extern int px210_dc_hw_fb_format_check(const struct drm_mode_fb_cmd2 *mode_cmd, int count); +extern void px210_dc_hw_plane_get_primary_format(const uint64_t **format_modifiers, + const uint32_t **formats, + uint32_t *format_count); +extern void px210_dc_hw_plane_get_cursor_format(const uint64_t **format_modifiers, + const uint32_t **formats, + uint32_t *format_count); +void px210_dc_hw_update_dcreq(struct drm_plane *plane); +void px210_dc_hw_update_primary_hi_addr(struct drm_plane *plane); +#endif /* __PX210_DC_H__ */ diff --git a/drivers/gpu/drm/phytium/px210_dp.c b/drivers/gpu/drm/phytium/px210_dp.c new file mode 100644 index 0000000000000000000000000000000000000000..be3c520a3c09d4ecb4e063cc1a031382c700f5cc --- /dev/null +++ b/drivers/gpu/drm/phytium/px210_dp.c @@ -0,0 +1,920 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include "phytium_display_drv.h" +#include "px210_reg.h" +#include "phytium_dp.h" +#include "px210_dp.h" + +static uint8_t px210_dp_source_lane_count[3] = {4, 4, 1}; + +/* [reg][ling_rate 1.62->8.1] */ +static int vco_val[12][4] = { + {0x0509, 0x0509, 0x0509, 0x0509}, // CP_PADJ + {0x0f00, 0x0f00, 0x0f00, 0x0f00}, // CP_IADJ + {0x0F08, 0x0F08, 0x0F08, 0x0F08}, // FILT_PADJ + {0x0061, 0x006C, 0x006C, 0x0051}, // INTDIV + {0x3333, 0x0000, 0x0000, 0x0000}, // FRACDIVL + {0x0000, 0x0000, 0x0000, 0x0000}, // FRACDIVH + {0x0042, 0x0048, 0x0048, 0x0036}, // HIGH_THR + {0x0002, 0x0002, 0x0002, 0x0002}, // PDIAG_CTRL + {0x0c5e, 0x0c5e, 0x0c5e, 0x0c5e}, // VCOCAL_PLLCNT_START + {0x00c7, 0x00c7, 0x00c7, 0x00c7}, // LOCK_PEFCNT + {0x00c7, 0x00c7, 0x00c7, 0x00c7}, // LOCK_PLLCNT_START + {0x0005, 0x0005, 0x0005, 0x0005}, // LOCK_PLLCNT_THR +}; + +static int mgnfs_val[4][4][4] = // [link_rate][swing][emphasis] +{ + /* 1.62Gbps */ + { + {0x0026, 0x001f, 0x0012, 0x0000}, + {0x0013, 0x0013, 0x0000, 0x0000}, + {0x0006, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + + /* 2.7Gbps */ + { + {0x0026, 0x001f, 0x0012, 0x0000}, + {0x0013, 0x0013, 0x0000, 0x0000}, + {0x0006, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + + /* 5.4Gbps */ + { + {0x0026, 0x0013, 0x005, 0x0000}, + {0x0018, 0x006, 0x0000, 0x0000}, + {0x000c, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + + /* 8.1Gbps */ + { + {0x0026, 0x0013, 0x005, 0x0000}, + {0x0013, 0x006, 0x0000, 0x0000}, + {0x0006, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, +}; + +static int cpost_val[4][4][4] = // [link_rate][swing][emphasis] +{ + /* 1.62Gbps */ + { + {0x0000, 0x0014, 0x0020, 0x002a}, + {0x0000, 0x0010, 0x001f, 0x0000}, + {0x0000, 0x0013, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + + /* 2.7Gbps */ + { + {0x0000, 0x0014, 0x0020, 0x002a}, + {0x0000, 0x0010, 0x001f, 0x0000}, + {0x0000, 0x0013, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + + /* 5.4Gbps */ + { + {0x0000, 0x0014, 0x0022, 0x002e}, + {0x0000, 0x0013, 0x0020, 0x0000}, + {0x0000, 0x0013, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, + + /* 8.1Gbps */ + { + {0x0000, 0x0014, 0x0022, 0x002e}, + {0x0000, 0x0013, 0x0020, 0x0000}, + {0x0000, 0x0013, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000}, + }, +}; + +static int px210_dp_hw_set_phy_lane_and_rate(struct phytium_dp_device *phytium_dp, + uint8_t link_lane_count, + uint32_t link_rate) +{ + int port = phytium_dp->port%3; + int i = 0, data, tmp, tmp1, index = 0, mask; + int timeout = 500, ret = 0; + + if (port == 0 || port == 1) { + /* set pma powerdown */ + data = 0; + mask = 0; + for (i = 0; i < phytium_dp->source_max_lane_count; i++) { + data |= (A3_POWERDOWN3 << i*A3_POWERDOWN3_SHIFT); + mask |= (((1<source_max_lane_count; i++) { + data |= (PLL_EN << i*PLL_EN_SHIFT); + mask |= (((1<source_max_lane_count; i++) { + data |= (PLL_EN << i*PLL_EN_SHIFT); + mask |= (((1<source_max_lane_count; i++) { + data |= (A0_ACTIVE << i*A0_ACTIVE_SHIFT); + mask |= (((1<port%3; + int voltage_swing = 0; + int pre_emphasis = 0, link_rate_index = 0; + + switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: + default: + voltage_swing = 0; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: + voltage_swing = 1; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: + voltage_swing = 2; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: + voltage_swing = 3; + break; + } + switch (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) { + case DP_TRAIN_PRE_EMPH_LEVEL_0: + default: + pre_emphasis = 0; + break; + case DP_TRAIN_PRE_EMPH_LEVEL_1: + pre_emphasis = 1; + break; + case DP_TRAIN_PRE_EMPH_LEVEL_2: + pre_emphasis = 2; + break; + case DP_TRAIN_PRE_EMPH_LEVEL_3: + pre_emphasis = 3; + break; + } + + switch (link_rate) { + case 810000: + link_rate_index = 3; + break; + case 540000: + link_rate_index = 2; + break; + case 270000: + link_rate_index = 1; + break; + case 162000: + link_rate_index = 0; + break; + default: + DRM_ERROR("phytium dp rate(%d) not support\n", link_rate); + link_rate_index = 2; + break; + } + + if (port == 0) { + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_DIAG_ACYA, LOCK); + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_TXCC_CTRL, TX_TXCC_CTRL); + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_DRV, TX_DRV); + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_MGNFS, + mgnfs_val[link_rate_index][voltage_swing][pre_emphasis]); + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_CPOST, + cpost_val[link_rate_index][voltage_swing][pre_emphasis]); + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_DIAG_ACYA, UNLOCK); + + } else if (port == 1) { + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_DIAG_ACYA, LOCK); + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_TXCC_CTRL, TX_TXCC_CTRL); + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_DRV, TX_DRV); + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_MGNFS, + mgnfs_val[link_rate_index][voltage_swing][pre_emphasis]); + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_CPOST, + cpost_val[link_rate_index][voltage_swing][pre_emphasis]); + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_CPOST1, + cpost_val[link_rate_index][voltage_swing][pre_emphasis]); + phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_DIAG_ACYA, UNLOCK); + } else { + phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_DIAG_ACYA, LOCK); + phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_TXCC_CTRL, TX_TXCC_CTRL); + phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_DRV, TX_DRV); + phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_MGNFS, + mgnfs_val[link_rate_index][voltage_swing][pre_emphasis]); + phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_CPOST, + cpost_val[link_rate_index][voltage_swing][pre_emphasis]); + phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_DIAG_ACYA, UNLOCK); + } +} + +static int px210_dp_hw_init_phy(struct phytium_dp_device *phytium_dp) +{ + int port = phytium_dp->port; + int i = 0, data, tmp, mask; + int timeout = 500, ret = 0; + + if (port == 0 || port == 1) { + phytium_phy_writel(phytium_dp, PX210_PHY0_APB_RESET, APB_RESET); + + phytium_phy_writel(phytium_dp, PX210_PHY0_PIPE_RESET, RESET); + + /* config lane to dp mode */ + data = 0; + mask = 0; + for (i = 0; i < phytium_dp->source_max_lane_count; i++) { + data |= (LANE_BIT << i*LANE_BIT_SHIFT); + mask |= (((1<source_max_lane_count; i++) { + data |= (LANE_MASTER << i*LANE_MASTER_SHIFT); + mask |= (((1<source_max_lane_count; i++) { + data |= (PLL_EN << i*PLL_EN_SHIFT); + mask |= (((1<source_max_lane_count; i++) { + data |= (BIT_20 << i*BIT_20_SHIFT); + mask |= (((1<source_max_lane_count; i++) { + data |= (A0_ACTIVE << i*A0_ACTIVE_SHIFT); + mask |= (((1<dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dcreq_reg_base[port]; + int ret = 0; + + phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | PANEL_POWER_ENABLE, + group_offset, PX210_DCREQ_CMD_REGISTER); + ret = phytium_wait_cmd_done(priv, group_offset + PX210_DCREQ_CMD_REGISTER, + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to poweron panel\n", __func__); +} + +static void px210_dp_hw_poweroff_panel(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dcreq_reg_base[port]; + int ret = 0; + + phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | PANEL_POWER_DISABLE, + group_offset, PX210_DCREQ_CMD_REGISTER); + ret = phytium_wait_cmd_done(priv, group_offset + PX210_DCREQ_CMD_REGISTER, + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to poweroff panel\n", __func__); +} + +static void px210_dp_hw_enable_backlight(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port, ret = 0; + uint32_t group_offset = priv->dcreq_reg_base[port]; + + phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | BACKLIGHT_ENABLE, + group_offset, PX210_DCREQ_CMD_REGISTER); + ret = phytium_wait_cmd_done(priv, group_offset + PX210_DCREQ_CMD_REGISTER, + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to enable backlight\n", __func__); +} + +static void px210_dp_hw_disable_backlight(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dcreq_reg_base[port]; + int ret = 0; + + phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | BACKLIGHT_DISABLE, + group_offset, PX210_DCREQ_CMD_REGISTER); + ret = phytium_wait_cmd_done(priv, group_offset + PX210_DCREQ_CMD_REGISTER, + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to disable backlight\n", __func__); +} + +static uint32_t px210_dp_hw_get_backlight(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int config; + uint32_t group_offset = priv->address_transform_base; + + config = phytium_readl_reg(priv, group_offset, PX210_DC_ADDRESS_TRANSFORM_BACKLIGHT_VALUE); + return ((config >> BACKLIGHT_VALUE_SHIFT) & BACKLIGHT_VALUE_MASK); +} + +static int px210_dp_hw_set_backlight(struct phytium_dp_device *phytium_dp, uint32_t level) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + uint32_t group_offset = priv->dcreq_reg_base[port]; + int config = 0; + int ret = 0; + + if (level > PX210_DP_BACKLIGHT_MAX) { + ret = -EINVAL; + goto out; + } + + config = FLAG_REQUEST | CMD_BACKLIGHT | ((level & BACKLIGHT_MASK) << BACKLIGHT_SHIFT); + phytium_writel_reg(priv, config, group_offset, PX210_DCREQ_CMD_REGISTER); + ret = phytium_wait_cmd_done(priv, group_offset + PX210_DCREQ_CMD_REGISTER, + FLAG_REQUEST, FLAG_REPLY); + if (ret < 0) + DRM_ERROR("%s: failed to set backlight\n", __func__); + +out: + return ret; +} + +bool px210_dp_hw_spread_is_enable(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port, config; + uint32_t group_offset = priv->address_transform_base; + + config = phytium_readl_reg(priv, group_offset, PX210_DC_ADDRESS_TRANSFORM_DP_RESET_STATUS); + + return ((config & DP_SPREAD_ENABLE(port)) ? true:false); +} + +int px210_dp_hw_reset(struct phytium_dp_device *phytium_dp) +{ + struct drm_device *dev = phytium_dp->dev; + struct phytium_display_private *priv = dev->dev_private; + int port = phytium_dp->port; + int timeout = 100, config, ret = 0; + uint32_t group_offset = priv->address_transform_base; + uint32_t group_offset_dp = priv->dp_reg_base[port]; + + config = phytium_readl_reg(priv, group_offset, PX210_DC_ADDRESS_TRANSFORM_DP_RESET_STATUS); + config &= (~DC_DP_RESET_STATUS(port)); + + phytium_writel_reg(priv, config, group_offset, PX210_DC_ADDRESS_TRANSFORM_DP_RESET_STATUS); + phytium_writel_reg(priv, FLAG_REQUEST | CMD_DC_DP_RESET, + priv->dcreq_reg_base[port], PX210_DCREQ_CMD_REGISTER); + do { + mdelay(10); + timeout--; + config = phytium_readl_reg(priv, group_offset, + PX210_DC_ADDRESS_TRANSFORM_DP_RESET_STATUS); + if (config & DC_DP_RESET_STATUS(port)) + break; + } while (timeout); + if (timeout == 0) { + DRM_ERROR("reset dc/dp pipe(%d) failed\n", port); + ret = -1; + } + + phytium_writel_reg(priv, AUX_CLK_DIVIDER, group_offset_dp, PHYTIUM_DP_AUX_CLK_DIVIDER); + + return ret; +} + +uint8_t px210_dp_hw_get_source_lane_count(struct phytium_dp_device *phytium_dp) +{ + return px210_dp_source_lane_count[phytium_dp->port]; +} + +static struct phytium_dp_func px210_dp_funcs = { + .dp_hw_get_source_lane_count = px210_dp_hw_get_source_lane_count, + .dp_hw_reset = px210_dp_hw_reset, + .dp_hw_spread_is_enable = px210_dp_hw_spread_is_enable, + .dp_hw_set_backlight = px210_dp_hw_set_backlight, + .dp_hw_get_backlight = px210_dp_hw_get_backlight, + .dp_hw_disable_backlight = px210_dp_hw_disable_backlight, + .dp_hw_enable_backlight = px210_dp_hw_enable_backlight, + .dp_hw_poweroff_panel = px210_dp_hw_poweroff_panel, + .dp_hw_poweron_panel = px210_dp_hw_poweron_panel, + .dp_hw_init_phy = px210_dp_hw_init_phy, + .dp_hw_set_phy_lane_setting = px210_dp_hw_set_phy_lane_setting, + .dp_hw_set_phy_lane_and_rate = px210_dp_hw_set_phy_lane_and_rate, +}; + +void px210_dp_func_register(struct phytium_dp_device *phytium_dp) +{ + phytium_dp->funcs = &px210_dp_funcs; +} diff --git a/drivers/gpu/drm/phytium/px210_dp.h b/drivers/gpu/drm/phytium/px210_dp.h new file mode 100644 index 0000000000000000000000000000000000000000..f2436ace18453f5409ac034199a39299b7a4e6fe --- /dev/null +++ b/drivers/gpu/drm/phytium/px210_dp.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PX210_DP_H__ +#define __PX210_DP_H__ + +#define PX210_DP_BACKLIGHT_MAX 100 + +void px210_dp_func_register(struct phytium_dp_device *phytium_dp); +#endif /* __PX210_DP_H__ */ diff --git a/drivers/gpu/drm/phytium/px210_reg.h b/drivers/gpu/drm/phytium/px210_reg.h new file mode 100644 index 0000000000000000000000000000000000000000..e594fbc8d96f34a0566d710ce832be14d9a79346 --- /dev/null +++ b/drivers/gpu/drm/phytium/px210_reg.h @@ -0,0 +1,349 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium display drm driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PX210_REG_H__ +#define __PX210_REG_H__ + +#include "phytium_reg.h" + +/******************************dc register start******************************************/ +#define PX210_DC_CLOCK_CONTROL 0x0000 + #define SOFT_RESET (1<<12) +#define PX210_DC_CLOCK_IDLE 0x0004 + #define IS_IDLE (1<<16) +/******************************dc register end********************************************/ + +/******************************dcreq register start**************************************/ +#define PX210_DCREQ_PLANE0_ADDR_START 0x00 +#define PX210_DCREQ_PLANE0_ADDR_END 0x04 +#define PX210_DCREQ_PLANE1_ADDR_START 0x08 +#define PX210_DCREQ_PLANE1_ADDR_END 0x0c +#define PX210_DCREQ_PLANE0_CONFIG 0x10 + #define DCREQ_NO_LOSSY (0 << 0) + #define DCREQ_LOSSY (1 << 0) + #define DCREQ_TILE_TYPE_MASK (0x3 << 1) + #define DCREQ_TILE_TYPE_MODE0 (0x1 << 1) + #define DCREQ_TILE_TYPE_MODE3 (0x2 << 1) + #define DCREQ_COLOURFORMAT_MASK (0x7f << 8) + #define DCREQ_COLOURFORMAT_RGB565 (0x5 << 8) + #define DCREQ_COLOURFORMAT_ARGB1555 (0x4 << 8) + #define DCREQ_COLOURFORMAT_ARGB4444 (0x02 << 8) + #define DCREQ_COLOURFORMAT_BGRA8888 (0x29 << 8) + #define DCREQ_COLOURFORMAT_ARGB2101010 (0xe << 8) + #define DCREQ_COLOURFORMAT_YUYV (0x59 << 8) + #define DCREQ_COLOURFORMAT_UYVY (0x5b << 8) + #define DCREQ_ARGBSWIZZLE_MASK (0xf << 4) + #define DCREQ_ARGBSWIZZLE_ARGB (0X0 << 4) + #define DCREQ_ARGBSWIZZLE_BGRA (0XC << 4) + #define DCREQ_MODE_MASK (1 << 16) + #define DCREQ_MODE_LINEAR (0 << 16) + #define DCREQ_MODE_TILE (1 << 16) +#define PX210_DCREQ_PLANE1_CONFIG(pipe) 0x14 +#define PX210_DCREQ_PLANE0_CLEAR_COLOR_L 0x18 +#define PX210_DCREQ_PLANE0_CLEAR_COLOR_H 0x1C +#define PX210_DCREQ_PLANE1_CLEAR_COLOR_L 0x20 +#define PX210_DCREQ_PLANE1_CLEAR_COLOR_H 0x24 +#define PX210_DCREQ_CMD_REGISTER 0x38 + #define FLAG_REPLY (1<<31) + #define FLAG_REQUEST (1<<30) + #define CMD_PIXEL_CLOCK (0x0 << 28) + #define CMD_BACKLIGHT (0x1 << 28) + #define CMD_DC_DP_RESET (0x3 << 28) + #define BACKLIGHT_SHIFT 21 + #define BACKLIGHT_MASK 0x7f + #define BACKLIGHT_MAX 100 + #define BACKLIGHT_ENABLE (101 << BACKLIGHT_SHIFT) + #define BACKLIGHT_DISABLE (102 << BACKLIGHT_SHIFT) + #define PANEL_POWER_ENABLE (103 << BACKLIGHT_SHIFT) + #define PANEL_POWER_DISABLE (104 << BACKLIGHT_SHIFT) + #define PIXEL_CLOCK_MASK (0x1fffff) +#define PX210_DCREQ_FBCD_CLOCK_CONFIG 0x3c +#define PX210_DCREQ_PIX_DMA_PREFIX 0x50 + #define PREFIX_MASK 0xff + #define PREFIX_SHIFT 32 +#define PX210_DCREQ_FRAME_START 0x54 +#define PX210_DCREQ_FILTER_CONFIG 0x58 +#define PX210_DCREQ_CONTROL 0x5C + #define DC_REQ_ENABLE (1<<0) +#define PX210_DCREQ_MSI_CLEAR 0x60 + #define MSI_CLEAR 0x0 +#define PX210_DCREQ_RESET 0x68 + #define DCREQ_RESET (0x3 << 0) + #define DCREQ_RESET_MASK 0x3 +#define PX210_DCREQ_PLAN 0x94 + #define DCREQ_PLAN_A 0x0 + #define DCREQ_PLAN_B 0X5 +/******************************dcreq register end**************************************/ + +/******************************address transform register start**************************/ +#define PX210_GPU_ADDRESS_TRANSFORM_SRC_ADDR 0x0 +#define PX210_GPU_ADDRESS_TRANSFORM_SIZE 0x4 +#define PX210_GPU_ADDRESS_TRANSFORM_DST_ADDR 0x8 + +#define PX210_DC_ADDRESS_TRANSFORM_SRC_ADDR 0x24 + #define SRC_ADDR_OFFSET 22 + #define SRC_ADDR_MASK 0xffffffffff +#define PX210_DC_ADDRESS_TRANSFORM_SIZE 0x28 + #define ADDRESS_TRANSFORM_ENABLE (0x1 << 31) + #define SIZE_OFFSET 22 +#define PX210_DC_ADDRESS_TRANSFORM_DST_ADDR 0x2c + #define DST_ADDR_OFFSET 22 +#define PX210_DC_ADDRESS_TRANSFORM_DP_RESET_STATUS 0x48 + #define DC_DP_RESET_STATUS(pipe) (1 << pipe) + #define DP_SPREAD_ENABLE(pipe) (0x8 << pipe) +#define PX210_DC_ADDRESS_TRANSFORM_BACKLIGHT_VALUE 0x4c + #define BACKLIGHT_VALUE_MASK (0x7f) + #define BACKLIGHT_VALUE_SHIFT 16 +/******************************address transform register end**************************/ + +/******************************phy register start******************************************/ +/* self define */ +#define PX210_PHY0_PIPE_RESET 0x40104 + #define RESET 0x0 + #define RESET_DEASSERT 0x1 +#define PX210_PHY1_PIPE_RESET 0x100100 + #define PHY1_PIPE_RESET 0x0 + #define PHY1_PIPE_RESET_DEASSERT 0x4 + +#define PX210_PHY1_EN_REFCLK 0x100070 + +#define PX210_PHY0_MODE 0x40088 + #define LANE_BIT (0x3) + #define LANE_BIT_SHIFT 0x2 +#define PX210_PHY1_SEL 0x100004 + #define PHY1_DP_LANE_BIT 0x1 + #define PHY1_DP_LANE_BIT_SHIFT 2 + +#define PX210_PHY0_LINK_CFG 0x40044 + #define LANE_MASTER 0x1 + #define LANE_MASTER_SHIFT 1 + +#define PX210_PHY0_PLL_EN 0x40010 + #define PLL_EN 0x1 + #define PLL_EN_SHIFT 1 +#define PX210_PHY0_PMA_WIDTH 0x40020 + #define BIT_20 0x5 + #define BIT_20_SHIFT 4 + +#define PX210_PHY0_PMA0_POWER 0x40014 +#define PX210_PHY0_PMA1_POWER 0x40018 + #define A0_ACTIVE 0x1 + #define A0_ACTIVE_SHIFT 8 + #define A3_POWERDOWN3 0x8 + #define A3_POWERDOWN3_SHIFT 8 + +#define PX210_PHY1_PMA_MISC 0x1000a0 + #define PHY1_PLL_EN 0x1 + #define PHY1_PLL_EN_MASK 1 + #define PHY1_PLL_EN_SHIFT 8 + #define PHY1_BIT_20 0x5 + #define PHY1_BIT_20_SHIFT 9 + #define PHY1_A0_ACTIVE 0x1 + #define PHY1_A0_ACTIVE_SHIFT 2 + #define PHY1_A0_ACTIVE_MASK 0x3f + #define PHY1_A3_POWERDOWN3 0x8 + #define PHY1_A3_POWERDOWN3_MASK 0x3f + #define PHY1_A3_POWERDOWN3_SHIFT 2 + +#define PX210_PHY0_LINK_RESET 0x40108 + #define LINK_RESET 0x1 + #define LINK_RESET_MASK 0x1 + #define LINTK_RESET_SHIFT 0x1 + +#define PX210_PHY0_APB_RESET 0x40100 + #define APB_RESET 0x1 +#define PX210_PHY1_APB_RESET 0x100104 + #define PHY1_APB_RESET 0x4 + +/* phy origin register */ +#define PX210_PHY0_PLL_CFG 0x30038 +#define PX210_PHY1_PLL_CFG 0xb0038 + #define SINGLE_LINK 0x0 + #define DOUBLE_LINK 0x2 + +#define PX210_PHY0_PMA_CONTROL 0x3800c +#define PX210_PHY1_PMA_CONTROL 0xb800c + #define CONTROL_ENABLE 0x1 + #define CONTROL_ENABLE_MASK 0x1 + #define CONTROL_ENABLE_SHIFT 0x1 + +#define PX210_PHY0_PMA_CONTROL2 0x38004 +#define PX210_PHY1_PMA_CONTROL2 0xb8004 + #define PLL0_LOCK_DONE (0x1 << 6) + #define PLL1_LOCK_DONE (0x1 << 7) + +#define PX210_PHY0_PLL0_CLK_SEL 0X684 +#define PX210_PHY0_PLL1_CLK_SEL 0x704 +#define PX210_PHY1_PLL_CLK_SEL 0X80684 + #define PLL_LINK_RATE_162000 0xf01 + #define PLL_LINK_RATE_270000 0x701 + #define PLL_LINK_RATE_540000 0x301 + #define PLL_LINK_RATE_810000 0x200 + +#define PX210_PHY0_HSCLK0_SEL 0x18398 +#define PX210_PHY0_HSCLK1_SEL 0x1a398 +#define PX210_PHY1_HSCLK_SEL 0x90398 + #define HSCLK_LINK_0 0x0 + #define HSCLK_LINK_1 0x1 + +#define PX210_PHY0_HSCLK0_DIV 0x1839c +#define PX210_PHY0_HSCLK1_DIV 0x1a39c +#define PX210_PHY1_HSCLK_DIV 0x9039c + #define HSCLK_LINK_RATE_162000 0x2 + #define HSCLK_LINK_RATE_270000 0x1 + #define HSCLK_LINK_RATE_540000 0x0 + #define HSCLK_LINK_RATE_810000 0x0 + +#define PX210_PHY0_PLLDRC0_CTRL 0x18394 +#define PX210_PHY0_PLLDRC1_CTRL 0x1a394 +#define PX210_PHY1_PLLDRC_CTRL 0x90394 + #define PLLDRC_LINK0 0x1 + #define PLLDRC_LINK1 0x9 + +#define PX210_PHY0_PLL0_DSM_M0 0x250 +#define PX210_PHY1_PLL0_DSM_M0 0x80250 + #define PLL0_DSM_M0 0x4 +#define PX210_PHY0_PLL0_VCOCAL_START 0x218 +#define PX210_PHY1_PLL0_VCOCAL_START 0x80218 + #define PLL0_VCOCAL_START 0xc5e +#define PX210_PHY0_PLL0_VCOCAL_CTRL 0x208 +#define PX210_PHY1_PLL0_VCOCAL_CTRL 0x80208 + #define PLL0_VCOCAL_CTRL 0x3 + +#define PX210_PHY0_PLL1_DSM_M0 0x350 + #define PLL1_DSM_M0 0x4 +#define PX210_PHY0_PLL1_VCOCAL_START 0x318 + #define PLL1_VCOCAL_START 0xc5e +#define PX210_PHY0_PLL1_VCOCAL_CTRL 0x308 + #define PLL1_VCOCAL_CTRL 0x3 + +#define PX210_PHY0_PLL0_CP_PADJ 0x690 +#define PX210_PHY0_PLL0_CP_IADJ 0x694 +#define PX210_PHY0_PLL0_CP_FILT_PADJ 0x698 +#define PX210_PHY0_PLL0_INTDIV 0x240 +#define PX210_PHY0_PLL0_FRACDIVL 0x244 +#define PX210_PHY0_PLL0_FRACDIVH 0x248 +#define PX210_PHY0_PLL0_HIGH_THR 0x24c +#define PX210_PHY0_PLL0_PDIAG_CTRL 0x680 +#define PX210_PHY0_PLL0_VCOCAL_PLLCNT_START 0x220 +#define PX210_PHY0_PLL0_LOCK_PEFCNT 0x270 +#define PX210_PHY0_PLL0_LOCK_PLLCNT_START 0x278 +#define PX210_PHY0_PLL0_LOCK_PLLCNT_THR 0x27c + +#define PX210_PHY0_PLL1_CP_PADJ 0x710 +#define PX210_PHY0_PLL1_CP_IADJ 0x714 +#define PX210_PHY0_PLL1_CP_FILT_PADJ 0x718 +#define PX210_PHY0_PLL1_INTDIV 0x340 +#define PX210_PHY0_PLL1_FRACDIVL 0x344 +#define PX210_PHY0_PLL1_FRACDIVH 0x348 +#define PX210_PHY0_PLL1_HIGH_THR 0x34c +#define PX210_PHY0_PLL1_PDIAG_CTRL 0x700 +#define PX210_PHY0_PLL1_VCOCAL_PLLCNT_START 0x320 +#define PX210_PHY0_PLL1_LOCK_PEFCNT 0x370 +#define PX210_PHY0_PLL1_LOCK_PLLCNT_START 0x378 +#define PX210_PHY0_PLL1_LOCK_PLLCNT_THR 0x37c + +#define PX210_PHY1_PLL0_CP_PADJ 0x80690 +#define PX210_PHY1_PLL0_CP_IADJ 0x80694 +#define PX210_PHY1_PLL0_CP_FILT_PADJ 0x80698 +#define PX210_PHY1_PLL0_INTDIV 0x80240 +#define PX210_PHY1_PLL0_FRACDIVL 0x80244 +#define PX210_PHY1_PLL0_FRACDIVH 0x80248 +#define PX210_PHY1_PLL0_HIGH_THR 0x8024c +#define PX210_PHY1_PLL0_PDIAG_CTRL 0x80680 +#define PX210_PHY1_PLL0_VCOCAL_PLLCNT_START 0x80220 +#define PX210_PHY1_PLL0_LOCK_PEFCNT 0x80270 +#define PX210_PHY1_PLL0_LOCK_PLLCNT_START 0x80278 +#define PX210_PHY1_PLL0_LOCK_PLLCNT_THR 0x8027c + +#define PX210_PHY0_PLL0_TX_PSC_A0 0x18400 +#define PX210_PHY1_PLL0_TX_PSC_A0 0x90400 + #define PLL0_TX_PSC_A0 0xfb +#define PX210_PHY0_PLL0_TX_PSC_A2 0x18408 +#define PX210_PHY1_PLL0_TX_PSC_A2 0x90408 + #define PLL0_TX_PSC_A2 0x4aa +#define PX210_PHY0_PLL0_TX_PSC_A3 0x1840c +#define PX210_PHY1_PLL0_TX_PSC_A3 0x9040c + #define PLL0_TX_PSC_A3 0x4aa +#define PX210_PHY0_PLL0_RX_PSC_A0 0x28000 +#define PX210_PHY1_PLL0_RX_PSC_A0 0xa0000 + #define PLL0_RX_PSC_A0 0x0 +#define PX210_PHY0_PLL0_RX_PSC_A2 0x28008 +#define PX210_PHY1_PLL0_RX_PSC_A2 0xa0008 + #define PLL0_RX_PSC_A2 0x0 +#define PX210_PHY0_PLL0_RX_PSC_A3 0x2800C +#define PX210_PHY1_PLL0_RX_PSC_A3 0xa000C + #define PLL0_RX_PSC_A3 0x0 +#define PX210_PHY0_PLL0_RX_PSC_CAL 0x28018 +#define PX210_PHY1_PLL0_RX_PSC_CAL 0xa0018 + #define PLL0_RX_PSC_CAL 0x0 + +#define PX210_PHY0_PLL1_TX_PSC_A0 0x1a400 + #define PLL1_TX_PSC_A0 0xfb +#define PX210_PHY0_PLL1_TX_PSC_A2 0x1a408 + #define PLL1_TX_PSC_A2 0x4aa +#define PX210_PHY0_PLL1_TX_PSC_A3 0x1a40c + #define PLL1_TX_PSC_A3 0x4aa +#define PX210_PHY0_PLL1_RX_PSC_A0 0x2a000 + #define PLL1_RX_PSC_A0 0x0 +#define PX210_PHY0_PLL1_RX_PSC_A2 0x2a008 + #define PLL1_RX_PSC_A2 0x0 +#define PX210_PHY0_PLL1_RX_PSC_A3 0x2a00C + #define PLL1_RX_PSC_A3 0x0 +#define PX210_PHY0_PLL1_RX_PSC_CAL 0x2a018 + #define PLL1_RX_PSC_CAL 0x0 + +#define PX210_PHY0_PLL0_XCVR_CTRL 0x183a8 +#define PX210_PHY1_PLL0_XCVR_CTRL 0x903a8 + #define PLL0_XCVR_CTRL 0xf +#define PX210_PHY0_PLL1_XCVR_CTRL 0x1a3a8 + #define PLL1_XCVR_CTRL 0xf + +#define PX210_PHY0_PLL0_RX_GCSM1_CTRL 0x28420 +#define PX210_PHY1_PLL0_RX_GCSM1_CTRL 0xa0420 + #define PLL0_RX_GCSM1_CTRL 0x0 +#define PX210_PHY0_PLL0_RX_GCSM2_CTRL 0x28440 +#define PX210_PHY1_PLL0_RX_GCSM2_CTRL 0xa0440 + #define PLL0_RX_GCSM2_CTRL 0x0 +#define PX210_PHY0_PLL0_RX_PERGCSM_CTRL 0x28460 +#define PX210_PHY1_PLL0_RX_PERGCSM_CTRL 0xa0460 + #define PLL0_RX_PERGCSM_CTRL 0x0 + +#define PX210_PHY0_PLL1_RX_GCSM1_CTRL 0x2a420 + #define PLL1_RX_GCSM1_CTRL 0x0 +#define PX210_PHY0_PLL1_RX_GCSM2_CTRL 0x2a440 + #define PLL1_RX_GCSM2_CTRL 0x0 +#define PX210_PHY0_PLL1_RX_PERGCSM_CTRL 0x2a460 + #define PLL1_RX_PERGCSM_CTRL 0x0 + +/* swing and emphasis */ +#define PX210_PHY0_PLL0_TX_DIAG_ACYA 0x1879c +#define PX210_PHY0_PLL1_TX_DIAG_ACYA 0x1a79c +#define PX210_PHY1_PLL0_TX_DIAG_ACYA 0x9079c + #define LOCK 1 + #define UNLOCK 0 + +#define PX210_PHY0_PLL0_TX_TXCC_CTRL 0x18100 +#define PX210_PHY0_PLL1_TX_TXCC_CTRL 0x1a100 +#define PX210_PHY1_PLL0_TX_TXCC_CTRL 0x90100 + #define TX_TXCC_CTRL 0x8a4 + +#define PX210_PHY0_PLL0_TX_DRV 0x18318 +#define PX210_PHY0_PLL1_TX_DRV 0x1a318 +#define PX210_PHY1_PLL0_TX_DRV 0x90318 + #define TX_DRV 0x3 + +#define PX210_PHY0_PLL0_TX_MGNFS 0x18140 +#define PX210_PHY0_PLL1_TX_MGNFS 0x1a140 +#define PX210_PHY1_PLL0_TX_MGNFS 0x90140 + +#define PX210_PHY0_PLL0_TX_CPOST 0x18130 +#define PX210_PHY0_PLL1_TX_CPOST 0x1a130 +#define PX210_PHY0_PLL1_TX_CPOST1 0x1a13c +#define PX210_PHY1_PLL0_TX_CPOST 0x90130 + +/******************************phy register end********************************************/ +#endif /* __PX210_REG_H__ */ diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 87b8b59ef71c86a8b8ddd2a8a3c9e18944d646ec..4404af719fd9dccf98239acfa96d9710c5b52887 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -412,6 +412,15 @@ config SENSORS_ASPEED This driver can also be built as a module. If so, the module will be called aspeed_pwm_tacho. +config SENSORS_PHYTIUM + tristate "Phytium Fan tach and capture counter driver" + help + This driver provides support for Phytium Fan Tacho and capture + counter controllers. + + This driver can also be built as a module. If so, the module + will be called tacho-phytium. + config SENSORS_ATXP1 tristate "Attansic ATXP1 VID controller" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 025cfdbf6ba078db2286263a808c3b07e8368206..521b61869d722832bf3ab5f5efab777e5b9be780 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -155,6 +155,7 @@ obj-$(CONFIG_MAX31827) += max31827.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_MC34VR500) += mc34vr500.o obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o +obj-$(CONFIG_SENSORS_PHYTIUM) += tacho-phytium.o obj-$(CONFIG_SENSORS_TC654) += tc654.o obj-$(CONFIG_SENSORS_TPS23861) += tps23861.o obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o diff --git a/drivers/hwmon/tacho-phytium.c b/drivers/hwmon/tacho-phytium.c new file mode 100644 index 0000000000000000000000000000000000000000..aa6b508b2050e8eb92b3c5080b4d0cbc6861730e --- /dev/null +++ b/drivers/hwmon/tacho-phytium.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Hwmon driver for Phytium tachometer. + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TIMER_CTRL_REG 0x00 +#define TIMER_CTRL_MODE_SHIFT 0//0:1 +#define TIMER_CTRL_RESET_SHIFT BIT(2) +#define TIMER_CTRL_FORCE_SHIFT BIT(3) +#define TIMER_CTRL_CAPTURE_EN_SHIFT BIT(4) +#define TIMER_CTRL_CAPTURE_CNT_SHIFT 5//5:11 +#define TIMER_CTRL_ANTI_JITTER_SHIFT 18//18:19 +#define TIMER_CTRL_TACHO_MODE_SHIFT 20//20:21 +#define TIMER_CTRL_TIMER_CNT_MODE_SHIFT BIT(22) +#define TIMER_CTRL_BIT_SET_SHIFT 24 +#define TIMER_CTRL_CNT_EN_SHIFT BIT(25) +#define TIMER_CTRL_CNT_CLR_SHIFT BIT(26) +#define TIMER_CTRL_TIMER_MODE_SHIFT BIT(27) +#define TIMER_CTRL_PULSE_NUM_SHIFT 28//28:30 +#define TIMER_CTRL_TACHO_EN_SHIFT BIT(31) +#define TIMER_TACHO_RES_REG 0x04 +#define TIMER_TACHO_RES_VALID_SHIFT BIT(31) +#define TIMER_TACHO_RES_MASK GENMASK(30, 0) +#define TIMER_CMP_VALUE_UP_REG 0x08 +#define TIMER_CMP_VALUE_LOW_REG 0x1C +#define TIMER_CNT_VALUE_UP_REG 0x20 +#define TIMER_CNT_VALUE_LOW_REG 0x24 +#define TIMER_INT_MASK_REG 0x28 +#define TIMER_INT_STAT_REG 0x2C +#define TIMER_INT_CAPTURE_SHIFT BIT(5) +#define TIMER_INT_CYC_COMP_SHIFT BIT(4) +#define TIMER_INT_ONE_COMP_SHIFT BIT(3) +#define TIMER_INT_ROLLOVER_SHIFT BIT(2) +#define TIMER_INT_TACHO_UNDER_SHIFT BIT(1) +#define TIMER_INT_TACHO_OVER_SHIFT BIT(0) +#define TIMER_TACHO_OVER_REG 0x30 +#define TIMER_TACHO_UNDER_REG 0x34 +#define TIMER_START_VALUE_REG 0x38 + +#define TIMER_INT_CLR_MASK GENMASK(5, 0) + +enum tacho_modes { + tacho_mode = 1, + capture_mode, +}; + +enum edge_modes { + rising_edge, + falling_edge, + double_edge, +}; + +struct phytium_tacho { + struct device *dev; + struct device *hwmon; + void __iomem *base; + struct clk *clk; + u32 freq; + int irq; + u8 work_mode; + u8 edge_mode; + u32 debounce; +}; + +static u16 capture_count; + +static void phytium_tacho_init(struct phytium_tacho *tacho) +{ + u32 val; + + if (tacho->work_mode == tacho_mode) { + val = (TIMER_CTRL_TACHO_EN_SHIFT | + TIMER_CTRL_CNT_EN_SHIFT | + (tacho->edge_mode << TIMER_CTRL_TACHO_MODE_SHIFT) | + (tacho->debounce << TIMER_CTRL_ANTI_JITTER_SHIFT) | + (tacho->work_mode << TIMER_CTRL_MODE_SHIFT)); + writel_relaxed(val, tacho->base + TIMER_CTRL_REG); + writel_relaxed(0x2faf07f, tacho->base + TIMER_CMP_VALUE_LOW_REG); + } else { + val = (TIMER_CTRL_TACHO_EN_SHIFT | + TIMER_CTRL_CNT_EN_SHIFT | + (tacho->edge_mode << TIMER_CTRL_TACHO_MODE_SHIFT) | + (tacho->debounce << TIMER_CTRL_ANTI_JITTER_SHIFT) | + TIMER_CTRL_CAPTURE_EN_SHIFT | + (0x7f << TIMER_CTRL_CAPTURE_CNT_SHIFT) | + (tacho->work_mode << TIMER_CTRL_MODE_SHIFT)), + writel_relaxed(val, tacho->base + TIMER_CTRL_REG); + writel_relaxed(0x20, tacho->base + TIMER_INT_MASK_REG); + } +} + +static int phytium_get_fan_tach_rpm(struct phytium_tacho *priv) +{ + u64 raw_data, tach_div, clk_source; + u8 mode, both; + unsigned long timeout; + unsigned long loopcounter; + + timeout = jiffies + msecs_to_jiffies(500); + + for (loopcounter = 0;; loopcounter++) { + raw_data = readl_relaxed(priv->base + TIMER_TACHO_RES_REG); + + if (raw_data & TIMER_TACHO_RES_VALID_SHIFT) + break; + + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + + if (loopcounter > 3000) + msleep(20); + else { + udelay(100); + cond_resched(); + } + } + + raw_data = raw_data & TIMER_TACHO_RES_MASK; + clk_source = priv->freq; + mode = priv->edge_mode; + both = (mode == double_edge) ? 1 : 0; + tach_div = 1 << both; + + if (raw_data == 0) + return 0; + + return (clk_source * 60 * raw_data) / 0x2faf080 / tach_div; +} + +static ssize_t show_rpm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int rpm; + struct phytium_tacho *priv = dev_get_drvdata(dev); + + rpm = phytium_get_fan_tach_rpm(priv); + if (rpm < 0) + return rpm; + + return sprintf(buf, "%d\n", rpm); +} + +static SENSOR_DEVICE_ATTR(fan_input, 0444, + show_rpm, NULL, 0); + +static struct attribute *tacho_dev_attrs[] = { + &sensor_dev_attr_fan_input.dev_attr.attr, + NULL +}; + +static umode_t tacho_dev_is_visible(struct kobject *kobj, + struct attribute *a, int index) +{ + return a->mode; +} + +static const struct attribute_group tacho_group = { + .attrs = tacho_dev_attrs, + .is_visible = tacho_dev_is_visible, +}; + +static const struct attribute_group *tacho_groups[] = { + &tacho_group, + NULL +}; + +static irqreturn_t capture_irq_handler(int irq, void *dev_id) +{ + struct phytium_tacho *priv = dev_id; + u32 status = readl_relaxed(priv->base + TIMER_INT_STAT_REG); + + if (status & TIMER_INT_CAPTURE_SHIFT) { + capture_count++; + + if (capture_count == 0) + dev_err(priv->dev, "Capture counter is overflowed"); + + writel_relaxed(status, priv->base + TIMER_INT_STAT_REG); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static ssize_t show_capture(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int cnt; + struct phytium_tacho *priv = dev_get_drvdata(dev); + + cnt = capture_count * 0x7f + readl_relaxed(priv->base + TIMER_CNT_VALUE_LOW_REG); + + return sprintf(buf, "%d\n", cnt); +} + +static SENSOR_DEVICE_ATTR(capture_input, 0444, + show_capture, NULL, 0); + +static struct attribute *capture_dev_attrs[] = { + &sensor_dev_attr_capture_input.dev_attr.attr, + NULL +}; + +static umode_t capture_dev_is_visible(struct kobject *kobj, + struct attribute *a, int index) +{ + return a->mode; +} + +static const struct attribute_group capture_group = { + .attrs = capture_dev_attrs, + .is_visible = capture_dev_is_visible, +}; + +static const struct attribute_group *capture_groups[] = { + &capture_group, + NULL +}; + +static int phytium_tacho_get_work_mode(struct phytium_tacho *tacho) +{ + struct device_node *nc = tacho->dev->of_node; + struct fwnode_handle *fwn = tacho->dev->fwnode; + + if (of_property_read_bool(nc, "tacho")) + return tacho_mode; + else if (has_acpi_companion(tacho->dev) && fwnode_property_read_bool( + fwn, "tacho")) + return tacho_mode; + if (of_property_read_bool(nc, "capture")) + return capture_mode; + else if (has_acpi_companion(tacho->dev) && fwnode_property_read_bool( + fwn, "capture")) + return capture_mode; + return tacho_mode; +} + +static int phytium_tacho_get_edge_mode(struct phytium_tacho *tacho) +{ + struct device_node *nc = tacho->dev->of_node; + struct fwnode_handle *fwn = tacho->dev->fwnode; + + if (of_property_read_bool(nc, "up")) + return rising_edge; + else if (has_acpi_companion(tacho->dev) && fwnode_property_read_bool( + fwn, "up")) + return rising_edge; + if (of_property_read_bool(nc, "down")) + return falling_edge; + else if (has_acpi_companion(tacho->dev) && fwnode_property_read_bool( + fwn, "down")) + return falling_edge; + if (of_property_read_bool(nc, "double")) + return double_edge; + else if (has_acpi_companion(tacho->dev) && fwnode_property_read_bool( + fwn, "double")) + return double_edge; + return rising_edge; +} + +static int phytium_tacho_get_debounce(struct phytium_tacho *tacho) +{ + u32 value; + struct device_node *nc = tacho->dev->of_node; + struct fwnode_handle *fwn = tacho->dev->fwnode; + + if (!of_property_read_u32(nc, "debounce-level", &value)) + return value; + if (has_acpi_companion(tacho->dev)) { + if (!fwnode_property_read_u32(fwn, "debounce-level", &value)) + return value; + } + return 0; +} + +static void phytium_tacho_get_of_data(struct phytium_tacho *tacho) +{ + tacho->work_mode = phytium_tacho_get_work_mode(tacho); + tacho->edge_mode = phytium_tacho_get_edge_mode(tacho); + tacho->debounce = phytium_tacho_get_debounce(tacho); +} + +static int phytium_tacho_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct phytium_tacho *tacho; + int ret; + + tacho = devm_kzalloc(dev, sizeof(*tacho), GFP_KERNEL); + if (!tacho) + return -ENOMEM; + + tacho->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + tacho->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tacho->base)) { + dev_err(&pdev->dev, "region map failed\n"); + return PTR_ERR(tacho->base); + } + + if (!has_acpi_companion(tacho->dev)) { + tacho->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(tacho->clk)) + return PTR_ERR(tacho->clk); + ret = clk_prepare_enable(tacho->clk); + if (ret) + return ret; + tacho->freq = clk_get_rate(tacho->clk); + } else { + u32 fre; + + ret = clk_prepare_enable(tacho->clk); + if (ret) + return ret; + fwnode_property_read_u32(tacho->dev->fwnode, "clock-frequency", &fre); + tacho->freq = fre; + } + + tacho->irq = platform_get_irq(pdev, 0); + if (tacho->irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return tacho->irq; + } + + ret = devm_request_irq(dev, tacho->irq, capture_irq_handler, + 0, "phytium_tacho", tacho); + if (ret) { + dev_err(&pdev->dev, "Cannot request IRQ\n"); + return ret; + } + + phytium_tacho_get_of_data(tacho); + + phytium_tacho_init(tacho); + + if (tacho->work_mode == tacho_mode) + tacho->hwmon = devm_hwmon_device_register_with_groups(dev, + "phytium_tacho", + tacho, tacho_groups); + else + tacho->hwmon = devm_hwmon_device_register_with_groups(dev, + "phytium_capture", + tacho, capture_groups); + + platform_set_drvdata(pdev, tacho); + + return PTR_ERR_OR_ZERO(tacho->hwmon); +} + +#ifdef CONFIG_PM_SLEEP +static int phytium_tacho_suspend(struct device *dev) +{ + return 0; +} + +static int phytium_tacho_resume(struct device *dev) +{ + struct phytium_tacho *tacho = dev_get_drvdata(dev); + + phytium_tacho_init(tacho); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_tacho_pm, phytium_tacho_suspend, phytium_tacho_resume); + +static const struct of_device_id tacho_of_match[] = { + { .compatible = "phytium,tacho", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tacho_of_match); + +static const struct acpi_device_id tacho_acpi_match[] = { + { "PHYT0033", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, tacho_acpi_match); + +static struct platform_driver phytium_tacho_driver = { + .probe = phytium_tacho_probe, + .driver = { + .name = "phytium_tacho", + .pm = &phytium_tacho_pm, + .of_match_table = of_match_ptr(tacho_of_match), + .acpi_match_table = ACPI_PTR(tacho_acpi_match), + }, +}; + +module_platform_driver(phytium_tacho_driver); + +MODULE_AUTHOR("Zhang Yiqun "); +MODULE_DESCRIPTION("Phytium tachometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwspinlock/Kconfig b/drivers/hwspinlock/Kconfig index 3874d15b0e9b71bb42bbea2eb2dac213e513e1e5..f4690adb512a6d603f5afcd7f757ffe99ac6a55e 100644 --- a/drivers/hwspinlock/Kconfig +++ b/drivers/hwspinlock/Kconfig @@ -17,6 +17,15 @@ config HWSPINLOCK_OMAP If unsure, say N. +config HWSPINLOCK_PHYTIUM + tristate "Phytium Hardware Spinlock device" + depends on HWSPINLOCK + depends on ARCH_PHYTIUM + help + Say y here to support the Phytium Hardware Spinlock device. + + If unsure, say N. + config HWSPINLOCK_QCOM tristate "Qualcomm Hardware Spinlock device" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/hwspinlock/Makefile b/drivers/hwspinlock/Makefile index a0f16c9aaa82124e86cc5387e1308d0680248013..d3ef14bc57306ea8e10618bcbed017839dfb6095 100644 --- a/drivers/hwspinlock/Makefile +++ b/drivers/hwspinlock/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_HWSPINLOCK) += hwspinlock_core.o obj-$(CONFIG_HWSPINLOCK_OMAP) += omap_hwspinlock.o +obj-$(CONFIG_HWSPINLOCK_PHYTIUM) += phytium_hwspinlock.o obj-$(CONFIG_HWSPINLOCK_QCOM) += qcom_hwspinlock.o obj-$(CONFIG_HWSPINLOCK_SPRD) += sprd_hwspinlock.o obj-$(CONFIG_HWSPINLOCK_STM32) += stm32_hwspinlock.o diff --git a/drivers/hwspinlock/phytium_hwspinlock.c b/drivers/hwspinlock/phytium_hwspinlock.c new file mode 100644 index 0000000000000000000000000000000000000000..c253df68f2937dd2610169fbde4867757115e280 --- /dev/null +++ b/drivers/hwspinlock/phytium_hwspinlock.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium hardware spinlock driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + * + * Derived from drivers/hwspinlock/omap_hwspinlock.c + * Copyright (C) 2010-2015 Texas Instruments Incorporated - http://www.ti.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hwspinlock_internal.h" + +/* Spinlock register offsets */ +#define LOCK_BASE 0x10 + +#define SEMA_NOTTAKEN (0) /* free */ +#define SEMA_TAKEN (1) /* locked */ + +static int phytium_hwspinlock_trylock(struct hwspinlock *lock) +{ + void __iomem *lock_addr = lock->priv; + + /* attempt to acquire the lock by reading its value */ + return (readl(lock_addr) == SEMA_NOTTAKEN); +} + +static void phytium_hwspinlock_unlock(struct hwspinlock *lock) +{ + void __iomem *lock_addr = lock->priv; + + /* release the lock by writing 0 to it */ + writel(SEMA_NOTTAKEN, lock_addr); +} + +static void phytium_hwspinlock_relax(struct hwspinlock *lock) +{ + ndelay(50); +} + +static const struct hwspinlock_ops phytium_hwspinlock_ops = { + .trylock = phytium_hwspinlock_trylock, + .unlock = phytium_hwspinlock_unlock, + .relax = phytium_hwspinlock_relax, +}; + +static int phytium_hwspinlock_probe(struct platform_device *pdev) +{ + struct fwnode_handle *np = dev_fwnode(&(pdev->dev)); + struct hwspinlock_device *bank; + struct hwspinlock *hwlock; + struct resource *res; + void __iomem *io_base; + int num_locks, i, ret; + + if (!np) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + io_base = ioremap(res->start, resource_size(res)); + if (!io_base) + return -ENOMEM; + + /* + * make sure the module is enabled and clocked before reading + * the module SYSSTATUS register + */ + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) { + pm_runtime_put_noidle(&pdev->dev); + goto iounmap_base; + } + + /* Determine number of locks */ + if (fwnode_property_read_u32(np, "nr-locks", &num_locks)) { + dev_err(&pdev->dev, "missing/invalid number of locks\n"); + ret = -EINVAL; + goto iounmap_base; + } + + /* + * runtime PM will make sure the clock of this module is + * enabled again iff at least one lock is requested + */ + ret = pm_runtime_put(&pdev->dev); + if (ret < 0) + goto iounmap_base; + + bank = kzalloc(struct_size(bank, lock, num_locks), GFP_KERNEL); + if (!bank) { + ret = -ENOMEM; + goto iounmap_base; + } + + platform_set_drvdata(pdev, bank); + + for (i = 0, hwlock = &bank->lock[0]; i < num_locks; i++, hwlock++) { + /* Set register address of each lock */ + hwlock->priv = io_base + LOCK_BASE + sizeof(u32) * i; + } + + ret = hwspin_lock_register(bank, &pdev->dev, &phytium_hwspinlock_ops, + 0, num_locks); + if (ret) + goto reg_fail; + + return 0; + +reg_fail: + kfree(bank); +iounmap_base: + iounmap(io_base); + return ret; +} + +static int phytium_hwspinlock_remove(struct platform_device *pdev) +{ + struct hwspinlock_device *bank = platform_get_drvdata(pdev); + void __iomem *io_base = bank->lock[0].priv - LOCK_BASE; + int ret; + + ret = hwspin_lock_unregister(bank); + if (ret) { + dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret); + return ret; + } + + iounmap(io_base); + kfree(bank); + + return 0; +} + +static const struct of_device_id phytium_hwspinlock_of_match[] = { + { .compatible = "phytium,hwspinlock", }, + { /* end */ }, +}; +MODULE_DEVICE_TABLE(of, phytium_hwspinlock_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_hwspinlock_acpi_match[] = { + { "PHYT0053", 0 }, + { } +}; +#endif + +static struct platform_driver phytium_hwspinlock_driver = { + .probe = phytium_hwspinlock_probe, + .remove = phytium_hwspinlock_remove, + .driver = { + .name = "phytium_hwspinlock", + .of_match_table = of_match_ptr(phytium_hwspinlock_of_match), + .acpi_match_table = ACPI_PTR(phytium_hwspinlock_acpi_match), + }, +}; + +static int __init phytium_hwspinlock_init(void) +{ + return platform_driver_register(&phytium_hwspinlock_driver); +} +postcore_initcall(phytium_hwspinlock_init); + +static void __exit phytium_hwspinlock_exit(void) +{ + platform_driver_unregister(&phytium_hwspinlock_driver); +} +module_exit(phytium_hwspinlock_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Hardware spinlock driver for Phytium"); +MODULE_AUTHOR("Chen Baozi "); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 2f23413d1b011357d6253c181c30d5e0fafe0279..856feb1d06c1f82520999e97aefa09dfa76d3fe5 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -961,6 +961,34 @@ config I2C_PCA_PLATFORM This driver can also be built as a module. If so, the module will be called i2c-pca-platform. +config I2C_PHYTIUM_CORE + tristate + +config I2C_PHYTIUM_PCI + tristate "Phytium I2C PCI" + depends on PCI && ARCH_PHYTIUM + select I2C_PHYTIUM_CORE + select I2C_SMBUS + help + If you say yes to this option, support will be included for the + Phytium I2C adapter. Only master mode is supported. + + This driver can also be built as a module. If so, the module + will be called i2c-phytium-pci. + +config I2C_PHYTIUM_PLATFORM + tristate "Phytium I2C Platform" + depends on (ACPI && COMMON_CLK) || !ACPI + select I2C_SLAVE + select I2C_PHYTIUM_CORE + select I2C_SMBUS + help + If you say yes to this option, support will be included for the + Phytium I2C adapter. Only master mode is supported. + + This driver can also be built as a module. If so, the module + will be called i2c-phytium-platform. + config I2C_PNX tristate "I2C bus support for Philips PNX and NXP LPC targets" depends on ARCH_LPC32XX || COMPILE_TEST diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 1b1b0ddbf2b3a2ecdcce01e5659605ea3e570e41..04fd2fe9925548859d099c23016c76e5f7928650 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -94,6 +94,10 @@ obj-$(CONFIG_I2C_OWL) += i2c-owl.o obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi-core.o i2c-pasemi-pci.o obj-$(CONFIG_I2C_APPLE) += i2c-pasemi-core.o i2c-pasemi-platform.o obj-$(CONFIG_I2C_PCA_PLATFORM) += i2c-pca-platform.o +obj-$(CONFIG_I2C_PHYTIUM_CORE) += i2c-phytium-core.o +i2c-phytium-core-objs := i2c-phytium-common.o i2c-phytium-master.o i2c-phytium-slave.o +obj-$(CONFIG_I2C_PHYTIUM_PCI) += i2c-phytium-pci.o +obj-$(CONFIG_I2C_PHYTIUM_PLATFORM) += i2c-phytium-platform.o obj-$(CONFIG_I2C_PNX) += i2c-pnx.o obj-$(CONFIG_I2C_PXA) += i2c-pxa.o obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 855b698e99c08004df29a5da485722974de63335..6ab38151cfed3be452bb2df67ac92a119d3d8b9c 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -57,6 +57,7 @@ static const struct acpi_device_id dw_i2c_acpi_match[] = { { "HISI02A2", 0 }, { "HISI02A3", 0 }, { "HYGO0010", ACCESS_INTR_MASK }, + { "PHYT0003", 0 }, { } }; MODULE_DEVICE_TABLE(acpi, dw_i2c_acpi_match); diff --git a/drivers/i2c/busses/i2c-phytium-common.c b/drivers/i2c/busses/i2c-phytium-common.c new file mode 100644 index 0000000000000000000000000000000000000000..a1d6dd055498774d568ac328209069a9abe217f3 --- /dev/null +++ b/drivers/i2c/busses/i2c-phytium-common.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Phytium I2C adapter driver. + * + * Derived from Synopysys I2C driver. + * Copyright (C) 2006 Texas Instruments. + * Copyright (C) 2007 MontaVista Software Inc. + * Copyright (C) 2009 Provigent Ltd. + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i2c-phytium-core.h" + +static char *abort_sources[] = { + [ABRT_7B_ADDR_NOACK] = + "slave address not acknowledged (7bit mode)", + [ABRT_10ADDR1_NOACK] = + "first address byte not acknowledged (10bit mode)", + [ABRT_10ADDR2_NOACK] = + "second address byte not acknowledged (10bit mode)", + [ABRT_TXDATA_NOACK] = + "data not acknowledged", + [ABRT_GCALL_NOACK] = + "no acknowledgment for a general call", + [ABRT_GCALL_READ] = + "read after general call", + [ABRT_SBYTE_ACKDET] = + "start byte acknowledged", + [ABRT_SBYTE_NORSTRT] = + "trying to send start byte when restart is disabled", + [ABRT_10B_RD_NORSTRT] = + "trying to read when restart is disabled (10bit mode)", + [ABRT_MASTER_DIS] = + "trying to use disabled adapter", + [ARB_LOST] = + "lost arbitration", + [ABRT_SLAVE_FLUSH_TXFIFO] = + "read command so flush old data in the TX FIFO", + [ABRT_SLAVE_ARBLOST] = + "slave lost the bus while transmitting data to a remote master", + [ABRT_SLAVE_RD_INTX] = + "incorrect slave-transmitter mode configuration", +}; + +u32 phytium_readl(struct phytium_i2c_dev *dev, int offset) +{ + return readl_relaxed(dev->base + offset); +} + +void phytium_writel(struct phytium_i2c_dev *dev, u32 b, int offset) +{ + writel_relaxed(b, dev->base + offset); +} + +u32 i2c_phytium_scl_hcnt(u32 ic_clk, u32 tSYMBOL, u32 tf, int cond, int offset) +{ + if (cond) + return (ic_clk * tSYMBOL + 500000) / 1000000 - 8 + offset; + else + return (ic_clk * (tSYMBOL + tf) + 500000) / 1000000 - 3 + offset; +} + +u32 i2c_phytium_scl_lcnt(u32 ic_clk, u32 tLOW, u32 tf, int offset) +{ + return ((ic_clk * (tLOW + tf) + 500000) / 1000000) - 1 + offset; +} + +int i2c_phytium_set_sda_hold(struct phytium_i2c_dev *dev) +{ + if (!dev->sda_hold_time) { + /* Keep previous hold time setting if no one set it */ + dev->sda_hold_time = phytium_readl(dev, IC_SDA_HOLD); + } + + if (!(dev->sda_hold_time & IC_SDA_HOLD_RX_MASK)) + dev->sda_hold_time |= 1 << IC_SDA_HOLD_RX_SHIFT; + + dev_dbg(dev->dev, "SDA Hold Time TX:RX = %d:%d\n", + dev->sda_hold_time & ~(u32)IC_SDA_HOLD_RX_MASK, + dev->sda_hold_time >> IC_SDA_HOLD_RX_SHIFT); + + return 0; +} + +void __i2c_phytium_disable(struct phytium_i2c_dev *dev) +{ + int timeout = 100; + + do { + __i2c_phytium_disable_nowait(dev); + if ((phytium_readl(dev, IC_ENABLE_STATUS) & 1) == 0) + return; + + /* + * Wait 10 times the signaling period of the highest I2C + * transfer supported by the driver (for 400KHz this is + * 25us). + */ + usleep_range(25, 250); + } while (timeout--); + + dev_warn(dev->dev, "timeout in disabling adapter\n"); +} + +unsigned long i2c_phytium_clk_rate(struct phytium_i2c_dev *dev) +{ + if (WARN_ON_ONCE(!dev->get_clk_rate_khz)) + return 0; + return dev->get_clk_rate_khz(dev); +} + +int i2c_phytium_prepare_clk(struct phytium_i2c_dev *dev, bool prepare) +{ + if (IS_ERR(dev->clk)) + return PTR_ERR(dev->clk); + + if (prepare) + return clk_prepare_enable(dev->clk); + + clk_disable_unprepare(dev->clk); + return 0; +} +EXPORT_SYMBOL_GPL(i2c_phytium_prepare_clk); + +int i2c_phytium_wait_bus_not_busy(struct phytium_i2c_dev *dev) +{ + int timeout = 20; /* 20 ms */ + + while (phytium_readl(dev, IC_STATUS) & IC_STATUS_ACTIVITY) { + if (timeout <= 0) { + dev_warn(dev->dev, "timeout waiting for bus ready\n"); + i2c_recover_bus(&dev->adapter); + + if (phytium_readl(dev, IC_STATUS) & IC_STATUS_ACTIVITY) + return -ETIMEDOUT; + return 0; + } + timeout--; + usleep_range(1000, 1100); + } + + return 0; +} + +int i2c_phytium_handle_tx_abort(struct phytium_i2c_dev *dev) +{ + unsigned long abort_source = dev->abort_source; + int i; + + if (abort_source & IC_TX_ABRT_NOACK) { + for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources)) + dev_dbg(dev->dev, + "%s: %s\n", __func__, abort_sources[i]); + return -EREMOTEIO; + } + + for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources)) + dev_err(dev->dev, "%s: %s\n", __func__, abort_sources[i]); + + if (abort_source & IC_TX_ARB_LOST) + return -EAGAIN; + else if (abort_source & IC_TX_ABRT_GCALL_READ) + return -EINVAL; + else + return -EIO; + + return 0; +} + +u32 i2c_phytium_func(struct i2c_adapter *adapter) +{ + struct phytium_i2c_dev *dev = i2c_get_adapdata(adapter); + + return dev->functionality; +} + +void i2c_phytium_disable(struct phytium_i2c_dev *dev) +{ + /* Disable controller */ + __i2c_phytium_disable(dev); + + /* Disable all interrupts */ + phytium_writel(dev, 0, IC_INTR_MASK); + phytium_readl(dev, IC_CLR_INTR); +} + +void i2c_phytium_disable_int(struct phytium_i2c_dev *dev) +{ + phytium_writel(dev, 0, IC_INTR_MASK); +} + +MODULE_AUTHOR("Cheng Quan "); +MODULE_DESCRIPTION("Phytium I2C bus adapter core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-phytium-core.h b/drivers/i2c/busses/i2c-phytium-core.h new file mode 100644 index 0000000000000000000000000000000000000000..2081a252724a466f6dc3b60dd02379f5f1bf5837 --- /dev/null +++ b/drivers/i2c/busses/i2c-phytium-core.h @@ -0,0 +1,259 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium I2C adapter driver. + * + * Derived from Synopysys I2C driver. + * Copyright (C) 2006 Texas Instruments. + * Copyright (C) 2007 MontaVista Software Inc. + * Copyright (C) 2009 Provigent Ltd. + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include + +#define IC_DEFAULT_FUNCTIONALITY (I2C_FUNC_I2C | \ + I2C_FUNC_SMBUS_BYTE | \ + I2C_FUNC_SMBUS_BYTE_DATA | \ + I2C_FUNC_SMBUS_WORD_DATA | \ + I2C_FUNC_SMBUS_BLOCK_DATA | \ + I2C_FUNC_SMBUS_I2C_BLOCK) + +#define IC_CON_MASTER 0x1 +#define IC_CON_SPEED_STD 0x2 +#define IC_CON_SPEED_FAST 0x4 +#define IC_CON_SPEED_HIGH 0x6 +#define IC_CON_SPEED_MASK 0x6 +#define IC_CON_10BITADDR_SLAVE 0x8 +#define IC_CON_10BITADDR_MASTER 0x10 +#define IC_CON_RESTART_EN 0x20 +#define IC_CON_SLAVE_DISABLE 0x40 +#define IC_CON_STOP_DET_IFADDRESSED 0x80 +#define IC_CON_TX_EMPTY_CTRL 0x100 +#define IC_CON_RX_FIFO_FULL_HLD_CTRL 0x200 + +#define IC_CON 0x0 +#define IC_TAR 0x4 +#define IC_SAR 0x8 +#define IC_DATA_CMD 0x10 +#define IC_SS_SCL_HCNT 0x14 +#define IC_SS_SCL_LCNT 0x18 +#define IC_FS_SCL_HCNT 0x1c +#define IC_FS_SCL_LCNT 0x20 +#define IC_HS_SCL_HCNT 0x24 +#define IC_HS_SCL_LCNT 0x28 +#define IC_INTR_STAT 0x2c +#define IC_INTR_MASK 0x30 +#define IC_RAW_INTR_STAT 0x34 +#define IC_RX_TL 0x38 +#define IC_TX_TL 0x3c +#define IC_CLR_INTR 0x40 +#define IC_CLR_RX_UNDER 0x44 +#define IC_CLR_RX_OVER 0x48 +#define IC_CLR_TX_OVER 0x4c +#define IC_CLR_RD_REQ 0x50 +#define IC_CLR_TX_ABRT 0x54 +#define IC_CLR_RX_DONE 0x58 +#define IC_CLR_ACTIVITY 0x5c +#define IC_CLR_STOP_DET 0x60 +#define IC_CLR_START_DET 0x64 +#define IC_CLR_GEN_CALL 0x68 +#define IC_ENABLE 0x6c +#define IC_STATUS 0x70 +#define IC_TXFLR 0x74 +#define IC_RXFLR 0x78 +#define IC_SDA_HOLD 0x7c +#define IC_TX_ABRT_SOURCE 0x80 +#define IC_ENABLE_STATUS 0x9c +#define IC_SMBCLK_LOW_MEXT 0xa8 +#define IC_SMBCLK_LOW_TIMEOUT 0xac +#define IC_SMBDAT_STUCK_TIMEOUT 0xb4 +#define IC_CLR_SMBCLK_EXT_LOW_TIMEOUT 0xbc +#define IC_CLR_SMBCLK_TMO_LOW_TIMEOUT 0xc0 +#define IC_CLR_SMBDAT_LOW_TIMEOUT 0xc4 +#define IC_CLR_SMBALERT_IN_N 0xd0 + +#define IC_INTR_RX_UNDER 0x001 +#define IC_INTR_RX_OVER 0x002 +#define IC_INTR_RX_FULL 0x004 +#define IC_INTR_TX_OVER 0x008 +#define IC_INTR_TX_EMPTY 0x010 +#define IC_INTR_RD_REQ 0x020 +#define IC_INTR_TX_ABRT 0x040 +#define IC_INTR_RX_DONE 0x080 +#define IC_INTR_ACTIVITY 0x100 +#define IC_INTR_STOP_DET 0x200 +#define IC_INTR_START_DET 0x400 +#define IC_INTR_GEN_CALL 0x800 +#define IC_INTR_SMBCLK_EXT_LOW_TIMEOUT 0x1000 +#define IC_INTR_SMBCLK_TMO_LOW_TIMEOUT 0x2000 +#define IC_INTR_SMBSDA_LOW_TIMEOUT 0x4000 +#define IC_INTR_SMBALERT_IN_N 0x20000 + +#define IC_INTR_DEFAULT_MASK (IC_INTR_RX_FULL | \ + IC_INTR_TX_ABRT | \ + IC_INTR_STOP_DET) +#define IC_INTR_MASTER_MASK (IC_INTR_DEFAULT_MASK | \ + IC_INTR_TX_EMPTY) +#define IC_INTR_SLAVE_MASK (IC_INTR_DEFAULT_MASK | \ + IC_INTR_RX_DONE | \ + IC_INTR_RX_UNDER | \ + IC_INTR_RD_REQ) +#define IC_INTR_SMBUS_MASK (IC_INTR_MASTER_MASK | \ + IC_INTR_SMBCLK_EXT_LOW_TIMEOUT | \ + IC_INTR_SMBCLK_TMO_LOW_TIMEOUT | \ + IC_INTR_SMBSDA_LOW_TIMEOUT) + +#define IC_STATUS_ACTIVITY 0x1 +#define IC_STATUS_TFE BIT(2) +#define IC_STATUS_MASTER_ACTIVITY BIT(5) +#define IC_STATUS_SLAVE_ACTIVITY BIT(6) + +#define IC_SDA_HOLD_RX_SHIFT 16 +#define IC_SDA_HOLD_RX_MASK GENMASK(23, IC_SDA_HOLD_RX_SHIFT) + +#define IC_ERR_TX_ABRT 0x1 + +#define IC_TAR_10BITADDR_MASTER BIT(12) + +#define IC_COMP_PARAM_1_SPEED_MODE_HIGH (BIT(2) | BIT(3)) +#define IC_COMP_PARAM_1_SPEED_MODE_MASK GENMASK(3, 2) + +#define STATUS_IDLE 0x0 +#define STATUS_WRITE_IN_PROGRESS 0x1 +#define STATUS_READ_IN_PROGRESS 0x2 + +/* + * operation modes + */ +#define PHYTIUM_IC_MASTER 0 +#define PHYTIUM_IC_SLAVE 1 + +#define ABRT_7B_ADDR_NOACK 0 +#define ABRT_10ADDR1_NOACK 1 +#define ABRT_10ADDR2_NOACK 2 +#define ABRT_TXDATA_NOACK 3 +#define ABRT_GCALL_NOACK 4 +#define ABRT_GCALL_READ 5 +#define ABRT_SBYTE_ACKDET 7 +#define ABRT_SBYTE_NORSTRT 9 +#define ABRT_10B_RD_NORSTRT 10 +#define ABRT_MASTER_DIS 11 +#define ARB_LOST 12 +#define ABRT_SLAVE_FLUSH_TXFIFO 13 +#define ABRT_SLAVE_ARBLOST 14 +#define ABRT_SLAVE_RD_INTX 15 + +#define IC_TX_ABRT_7B_ADDR_NOACK (1UL << ABRT_7B_ADDR_NOACK) +#define IC_TX_ABRT_10ADDR1_NOACK (1UL << ABRT_10ADDR1_NOACK) +#define IC_TX_ABRT_10ADDR2_NOACK (1UL << ABRT_10ADDR2_NOACK) +#define IC_TX_ABRT_TXDATA_NOACK (1UL << ABRT_TXDATA_NOACK) +#define IC_TX_ABRT_GCALL_NOACK (1UL << ABRT_GCALL_NOACK) +#define IC_TX_ABRT_GCALL_READ (1UL << ABRT_GCALL_READ) +#define IC_TX_ABRT_SBYTE_ACKDET (1UL << ABRT_SBYTE_ACKDET) +#define IC_TX_ABRT_SBYTE_NORSTRT (1UL << ABRT_SBYTE_NORSTRT) +#define IC_TX_ABRT_10B_RD_NORSTRT (1UL << ABRT_10B_RD_NORSTRT) +#define IC_TX_ABRT_MASTER_DIS (1UL << ABRT_MASTER_DIS) +#define IC_TX_ARB_LOST (1UL << ARB_LOST) +#define IC_RX_ABRT_SLAVE_RD_INTX (1UL << ABRT_SLAVE_RD_INTX) +#define IC_RX_ABRT_SLAVE_ARBLOST (1UL << ABRT_SLAVE_ARBLOST) +#define IC_RX_ABRT_SLAVE_FLUSH_TXFIFO (1UL << ABRT_SLAVE_FLUSH_TXFIFO) + +#define IC_TX_ABRT_NOACK (IC_TX_ABRT_7B_ADDR_NOACK | \ + IC_TX_ABRT_10ADDR1_NOACK | \ + IC_TX_ABRT_10ADDR2_NOACK | \ + IC_TX_ABRT_TXDATA_NOACK | \ + IC_TX_ABRT_GCALL_NOACK) +#define CONTROLLER_TYPE_IIC 0 +#define CONTROLLER_TYPE_SMBUS 1 + +struct phytium_i2c_dev { + struct device *dev; + void __iomem *base; + int irq; + u32 flags; + struct completion cmd_complete; + struct clk *clk; + struct reset_control *rst; + int mode; + struct i2c_client *slave; + u32 (*get_clk_rate_khz)(struct phytium_i2c_dev *dev); + + struct i2c_adapter adapter; + struct i2c_client *ara; + struct i2c_smbus_alert_setup alert_data; + + struct phytium_pci_i2c *controller; + + unsigned int status; + int cmd_err; + u32 abort_source; + + struct i2c_msg *msgs; + int msgs_num; + int msg_write_idx; + int msg_read_idx; + int msg_err; + u32 tx_buf_len; + u8 *tx_buf; + u32 rx_buf_len; + u8 *rx_buf; + + u32 master_cfg; + u32 slave_cfg; + u32 functionality; + unsigned int tx_fifo_depth; + unsigned int rx_fifo_depth; + int rx_outstanding; + + struct i2c_timings timings; + u32 sda_hold_time; + u16 ss_hcnt; + u16 ss_lcnt; + u16 fs_hcnt; + u16 fs_lcnt; + u16 fp_hcnt; + u16 fp_lcnt; + u16 hs_hcnt; + u16 hs_lcnt; + + bool pm_disabled; + void (*disable)(struct phytium_i2c_dev *dev); + void (*disable_int)(struct phytium_i2c_dev *dev); + int (*init)(struct phytium_i2c_dev *dev); +}; + +#define ACCESS_INTR_MASK 0x00000004 + +#define DEFAULT_CLOCK_FREQUENCY 48000000 + +u32 phytium_readl(struct phytium_i2c_dev *dev, int offset); +void phytium_writel(struct phytium_i2c_dev *dev, u32 b, int offset); +unsigned long i2c_phytium_clk_rate(struct phytium_i2c_dev *dev); +int i2c_phytium_prepare_clk(struct phytium_i2c_dev *dev, bool prepare); +int i2c_phytium_wait_bus_not_busy(struct phytium_i2c_dev *dev); +int i2c_phytium_handle_tx_abort(struct phytium_i2c_dev *dev); +u32 i2c_phytium_func(struct i2c_adapter *adap); +void i2c_phytium_disable(struct phytium_i2c_dev *dev); +void i2c_phytium_disable_int(struct phytium_i2c_dev *dev); +int i2c_phytium_set_sda_hold(struct phytium_i2c_dev *dev); +u32 i2c_phytium_scl_hcnt(u32 ic_clk, u32 tSYMBOL, u32 tf, int cond, int offset); +u32 i2c_phytium_scl_lcnt(u32 ic_clk, u32 tLOW, u32 tf, int offset); + +static inline void __i2c_phytium_enable(struct phytium_i2c_dev *dev) +{ + phytium_writel(dev, 1, IC_ENABLE); +} + +static inline void __i2c_phytium_disable_nowait(struct phytium_i2c_dev *dev) +{ + phytium_writel(dev, 0, IC_ENABLE); +} + +void __i2c_phytium_disable(struct phytium_i2c_dev *dev); + +extern int i2c_phytium_probe(struct phytium_i2c_dev *dev); + +extern int i2c_phytium_probe_slave(struct phytium_i2c_dev *dev); diff --git a/drivers/i2c/busses/i2c-phytium-master.c b/drivers/i2c/busses/i2c-phytium-master.c new file mode 100644 index 0000000000000000000000000000000000000000..796450084a27ca39e5f3795b1dfdadca5b5a1c4b --- /dev/null +++ b/drivers/i2c/busses/i2c-phytium-master.c @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium I2C adapter driver. + * + * Derived from Synopysys I2C driver. + * Copyright (C) 2006 Texas Instruments. + * Copyright (C) 2007 MontaVista Software Inc. + * Copyright (C) 2009 Provigent Ltd. + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i2c-phytium-core.h" + +static int i2c_phytium_init_master(struct phytium_i2c_dev *dev) +{ + /* Disable the adapter */ + __i2c_phytium_disable(dev); + + /* Write standard speed timing parameters */ + phytium_writel(dev, dev->ss_hcnt, IC_SS_SCL_HCNT); + phytium_writel(dev, dev->ss_lcnt, IC_SS_SCL_LCNT); + + /* Write fast mode/fast mode plus timing parameters */ + phytium_writel(dev, dev->fs_hcnt, IC_FS_SCL_HCNT); + phytium_writel(dev, dev->fs_lcnt, IC_FS_SCL_LCNT); + + /* Write high speed timing parameters if supported */ + if (dev->hs_hcnt && dev->hs_hcnt) { + phytium_writel(dev, dev->hs_hcnt, IC_HS_SCL_HCNT); + phytium_writel(dev, dev->hs_lcnt, IC_HS_SCL_LCNT); + } + + /* Write SDA hold time if supported */ + if (dev->sda_hold_time) + phytium_writel(dev, dev->sda_hold_time, IC_SDA_HOLD); + + /* Configure Tx/Rx FIFO threshold levels */ + phytium_writel(dev, dev->tx_fifo_depth >> 1, IC_TX_TL); + phytium_writel(dev, 0, IC_RX_TL); + + /* Configure the I2C master */ + phytium_writel(dev, dev->master_cfg, IC_CON); + + return 0; +} + +static void i2c_phytium_xfer_init(struct phytium_i2c_dev *dev) +{ + struct i2c_msg *msgs = dev->msgs; + u32 ic_con, ic_tar = 0; + + /* Disable the adapter */ + __i2c_phytium_disable(dev); + + /* If the slave address is 10-bit address, enable 10BITADDR */ + ic_con = phytium_readl(dev, IC_CON); + if (msgs[dev->msg_write_idx].flags & I2C_M_TEN) { + ic_con |= IC_CON_10BITADDR_MASTER; + ic_tar = IC_TAR_10BITADDR_MASTER; + } else { + ic_con &= ~IC_CON_10BITADDR_MASTER; + } + + phytium_writel(dev, ic_con, IC_CON); + + /* + * Set the slave (target) address and enable 10-bit addressing mode + * if applicable. + */ + phytium_writel(dev, msgs[dev->msg_write_idx].addr | ic_tar, IC_TAR); + + /* Enforce disabled interrupts */ + i2c_phytium_disable_int(dev); + + /* Enable the adapter */ + __i2c_phytium_enable(dev); + + /* Dummy read */ + phytium_readl(dev, IC_ENABLE_STATUS); + + /* Clear and enable interrupts */ + phytium_readl(dev, IC_CLR_INTR); + phytium_writel(dev, IC_INTR_SMBUS_MASK, IC_INTR_MASK); +} + +static void i2c_phytium_xfer_msg(struct phytium_i2c_dev *dev) +{ + struct i2c_msg *msgs = dev->msgs; + u32 intr_mask; + int tx_limit, rx_limit; + u32 addr = msgs[dev->msg_write_idx].addr; + u32 buf_len = dev->tx_buf_len; + u8 *buf = dev->tx_buf; + bool need_restart = false; + + intr_mask = IC_INTR_MASTER_MASK; + + for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) { + u32 flags = msgs[dev->msg_write_idx].flags; + + if (msgs[dev->msg_write_idx].addr != addr) { + dev_err(dev->dev, + "%s: invalid target address\n", __func__); + dev->msg_err = -EINVAL; + break; + } + + if (!(dev->status & STATUS_WRITE_IN_PROGRESS)) { + /* new i2c_msg */ + buf = msgs[dev->msg_write_idx].buf; + buf_len = msgs[dev->msg_write_idx].len; + + if ((dev->master_cfg & IC_CON_RESTART_EN) && + (dev->msg_write_idx > 0)) + need_restart = true; + } + + tx_limit = dev->tx_fifo_depth - phytium_readl(dev, IC_TXFLR); + rx_limit = dev->tx_fifo_depth - phytium_readl(dev, IC_RXFLR); + + while (buf_len > 0 && tx_limit > 0 && rx_limit > 0) { + u32 cmd = 0; + + if (dev->msg_write_idx == dev->msgs_num - 1 && + buf_len == 1 && !(flags & I2C_M_RECV_LEN)) + cmd |= BIT(9); + + if (need_restart) { + cmd |= BIT(10); + need_restart = false; + } + + if (msgs[dev->msg_write_idx].flags & I2C_M_RD) { + /* avoid rx buffer overrun */ + if (dev->rx_outstanding >= dev->rx_fifo_depth) + break; + + phytium_writel(dev, cmd | 0x100, IC_DATA_CMD); + rx_limit--; + dev->rx_outstanding++; + } else { + phytium_writel(dev, cmd | *buf++, IC_DATA_CMD); + } + tx_limit--; + buf_len--; + } + + dev->tx_buf = buf; + dev->tx_buf_len = buf_len; + + /* + * Because we don't know the buffer length in the + * I2C_FUNC_SMBUS_BLOCK_DATA case, we can't stop + * the transaction here. + */ + if (buf_len > 0 || flags & I2C_M_RECV_LEN) { + /* more bytes to be written */ + dev->status |= STATUS_WRITE_IN_PROGRESS; + break; + } + + dev->status &= ~STATUS_WRITE_IN_PROGRESS; + } + + if (dev->msg_write_idx == dev->msgs_num) + intr_mask &= ~IC_INTR_TX_EMPTY; + + if (dev->msg_err) + intr_mask = 0; + + phytium_writel(dev, intr_mask, IC_INTR_MASK); +} + +static u8 i2c_phytium_recv_len(struct phytium_i2c_dev *dev, u8 len) +{ + struct i2c_msg *msgs = dev->msgs; + u32 flags = msgs[dev->msg_read_idx].flags; + + /* + * Adjust the buffer length and mask the flag + * after receiving the first byte. + */ + len += (flags & I2C_CLIENT_PEC) ? 2 : 1; + dev->tx_buf_len = len - min_t(u8, len, dev->rx_outstanding); + msgs[dev->msg_read_idx].len = len; + msgs[dev->msg_read_idx].flags &= ~I2C_M_RECV_LEN; + + return len; +} + +static void i2c_phytium_read(struct phytium_i2c_dev *dev) +{ + struct i2c_msg *msgs = dev->msgs; + int rx_valid; + + for (; dev->msg_read_idx < dev->msgs_num; dev->msg_read_idx++) { + u32 len; + u8 *buf; + + if (!(msgs[dev->msg_read_idx].flags & I2C_M_RD)) + continue; + + if (!(dev->status & STATUS_READ_IN_PROGRESS)) { + len = msgs[dev->msg_read_idx].len; + buf = msgs[dev->msg_read_idx].buf; + } else { + len = dev->rx_buf_len; + buf = dev->rx_buf; + } + + rx_valid = phytium_readl(dev, IC_RXFLR); + + for (; len > 0 && rx_valid > 0; len--, rx_valid--) { + u32 flags = msgs[dev->msg_read_idx].flags; + + *buf = phytium_readl(dev, IC_DATA_CMD); + /* Ensure length byte is a valid value */ + if (flags & I2C_M_RECV_LEN && + *buf <= I2C_SMBUS_BLOCK_MAX && *buf > 0) { + len = i2c_phytium_recv_len(dev, *buf); + } + buf++; + dev->rx_outstanding--; + } + + if (len > 0) { + dev->status |= STATUS_READ_IN_PROGRESS; + dev->rx_buf_len = len; + dev->rx_buf = buf; + return; + } + + dev->status &= ~STATUS_READ_IN_PROGRESS; + } +} + +static int i2c_phytium_xfer(struct i2c_adapter *adapter, struct i2c_msg msgs[], int num) +{ + struct phytium_i2c_dev *dev = i2c_get_adapdata(adapter); + int ret; + + dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num); + + pm_runtime_get_sync(dev->dev); + + reinit_completion(&dev->cmd_complete); + dev->msgs = msgs; + dev->msgs_num = num; + dev->cmd_err = 0; + dev->msg_write_idx = 0; + dev->msg_read_idx = 0; + dev->msg_err = 0; + dev->status = STATUS_IDLE; + dev->abort_source = 0; + dev->rx_outstanding = 0; + + ret = i2c_phytium_wait_bus_not_busy(dev); + if (ret < 0) + goto done; + + /* Start the transfers */ + i2c_phytium_xfer_init(dev); + + /* Wait for tx to complete */ + if (!wait_for_completion_timeout(&dev->cmd_complete, adapter->timeout)) { + dev_err(dev->dev, "controller timed out\n"); + i2c_recover_bus(&dev->adapter); + i2c_phytium_init_master(dev); + ret = -ETIMEDOUT; + goto done; + } + + __i2c_phytium_disable_nowait(dev); + + if (dev->msg_err) { + ret = dev->msg_err; + goto done; + } + + if (likely(!dev->cmd_err && !dev->status)) { + ret = num; + goto done; + } + + /* We have got an error */ + if (dev->cmd_err == IC_ERR_TX_ABRT) { + ret = i2c_phytium_handle_tx_abort(dev); + goto done; + } + + if (dev->status) + dev_err(dev->dev, "transfer terminated early.\n"); + + ret = -EIO; + +done: + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); + + return ret; +} + +static const struct i2c_algorithm i2c_phytium_algo = { + .master_xfer = i2c_phytium_xfer, + .functionality = i2c_phytium_func, +}; + +static const struct i2c_adapter_quirks i2c_phytium_quirks = { + .flags = I2C_AQ_NO_ZERO_LEN, +}; + +static u32 i2c_phytium_read_clear_intrbits(struct phytium_i2c_dev *dev) +{ + u32 stat; + + stat = phytium_readl(dev, IC_INTR_STAT); + + if (stat & IC_INTR_RX_UNDER) + phytium_readl(dev, IC_CLR_RX_UNDER); + if (stat & IC_INTR_RX_OVER) + phytium_readl(dev, IC_CLR_RX_OVER); + if (stat & IC_INTR_TX_OVER) + phytium_readl(dev, IC_CLR_TX_OVER); + if (stat & IC_INTR_RD_REQ) + phytium_readl(dev, IC_CLR_RD_REQ); + if (stat & IC_INTR_TX_ABRT) { + dev->abort_source = phytium_readl(dev, IC_TX_ABRT_SOURCE); + phytium_readl(dev, IC_CLR_TX_ABRT); + } + if (stat & IC_INTR_RX_DONE) + phytium_readl(dev, IC_CLR_RX_DONE); + if (stat & IC_INTR_ACTIVITY) + phytium_readl(dev, IC_CLR_ACTIVITY); + if (stat & IC_INTR_STOP_DET) + phytium_readl(dev, IC_CLR_STOP_DET); + if (stat & IC_INTR_START_DET) + phytium_readl(dev, IC_CLR_START_DET); + if (stat & IC_INTR_GEN_CALL) + phytium_readl(dev, IC_CLR_GEN_CALL); + if (stat & IC_INTR_SMBCLK_EXT_LOW_TIMEOUT) + phytium_readl(dev, IC_CLR_SMBCLK_EXT_LOW_TIMEOUT); + if (stat & IC_INTR_SMBCLK_TMO_LOW_TIMEOUT) + phytium_readl(dev, IC_CLR_SMBCLK_TMO_LOW_TIMEOUT); + if (stat & IC_INTR_SMBSDA_LOW_TIMEOUT) + phytium_readl(dev, IC_CLR_SMBDAT_LOW_TIMEOUT); + if (stat & IC_INTR_SMBALERT_IN_N) + phytium_readl(dev, IC_CLR_SMBALERT_IN_N); + + return stat; +} + +static int i2c_phytium_irq_handler_master(struct phytium_i2c_dev *dev) +{ + u32 stat; + + stat = i2c_phytium_read_clear_intrbits(dev); + + /* SMBus interrupt */ + if (stat & (IC_INTR_SMBCLK_EXT_LOW_TIMEOUT | IC_INTR_SMBCLK_TMO_LOW_TIMEOUT)) { + phytium_writel(dev, phytium_readl(dev, IC_ENABLE) & (~BIT(6)), + IC_ENABLE); + phytium_writel(dev, phytium_readl(dev, IC_ENABLE) | BIT(4), + IC_ENABLE); + goto abort; + } + + if (stat & IC_INTR_SMBSDA_LOW_TIMEOUT) { + phytium_writel(dev, phytium_readl(dev, IC_ENABLE) | BIT(6), + IC_ENABLE); + goto abort; + } + + if (stat & IC_INTR_SMBALERT_IN_N && dev->ara) + i2c_handle_smbus_alert(dev->ara); + + if (stat & IC_INTR_TX_ABRT) { + dev->cmd_err |= IC_ERR_TX_ABRT; + dev->status = STATUS_IDLE; + + /* Anytime TX_ABRT is set, the contents of the tx/rx + * buffers are flushed. Make sure to skip them. + */ + phytium_writel(dev, 0, IC_INTR_MASK); + goto abort; + } + + if (stat & IC_INTR_RX_FULL) + i2c_phytium_read(dev); + + if (stat & IC_INTR_TX_EMPTY) + i2c_phytium_xfer_msg(dev); + +abort: + if ((stat & (IC_INTR_TX_ABRT | IC_INTR_STOP_DET)) || + dev->msg_err) + complete(&dev->cmd_complete); + else if (unlikely(dev->flags & ACCESS_INTR_MASK)) { + /* Workaround to trigger pending interrupt */ + stat = phytium_readl(dev, IC_INTR_MASK); + i2c_phytium_disable_int(dev); + phytium_writel(dev, stat, IC_INTR_MASK); + } + + return 0; +} + +static int i2c_phytium_set_timings_master(struct phytium_i2c_dev *dev) +{ + const char *mode_str, *fp_str = ""; + u32 sda_falling_time, scl_falling_time; + struct i2c_timings *t = &dev->timings; + u32 ic_clk; + int ret; + + /* Set standard and fast speed dividers for high/low periods */ + sda_falling_time = t->sda_fall_ns ?: 300; /* ns */ + scl_falling_time = t->scl_fall_ns ?: 300; /* ns */ + + /* Calculate SCL timing parameters for standard mode if not set */ + if (!dev->ss_hcnt || !dev->ss_lcnt) { + ic_clk = i2c_phytium_clk_rate(dev); + dev->ss_hcnt = + i2c_phytium_scl_hcnt(ic_clk, + 4000, /* tHD;STA = tHIGH = 4.0 us */ + sda_falling_time, + 0, /* 0: DW default, 1: Ideal */ + 0); /* No offset */ + dev->ss_lcnt = + i2c_phytium_scl_lcnt(ic_clk, + 4700, /* tLOW = 4.7 us */ + scl_falling_time, + 0); /* No offset */ + } + dev_dbg(dev->dev, "Standard Mode HCNT:LCNT = %d:%d\n", + dev->ss_hcnt, dev->ss_lcnt); + /* + * Set SCL timing parameters for fast mode or fast mode plus. Only + * difference is the timing parameter values since the registers are + * the same. + */ + if (t->bus_freq_hz == 1000000) { + /* + * Check are fast mode plus parameters available and use + * fast mode if not. + */ + if (dev->fp_hcnt && dev->fp_lcnt) { + dev->fs_hcnt = dev->fp_hcnt; + dev->fs_lcnt = dev->fp_lcnt; + fp_str = " Plus"; + } + } + /* + * Calculate SCL timing parameters for fast mode if not set. They are + * needed also in high speed mode. + */ + if (!dev->fs_hcnt || !dev->fs_lcnt) { + ic_clk = i2c_phytium_clk_rate(dev); + dev->fs_hcnt = + i2c_phytium_scl_hcnt(ic_clk, + 600, /* tHD;STA = tHIGH = 0.6 us */ + sda_falling_time, + 0, /* 0: DW default, 1: Ideal */ + 0); /* No offset */ + dev->fs_lcnt = + i2c_phytium_scl_lcnt(ic_clk, + 1300, /* tLOW = 1.3 us */ + scl_falling_time, + 0); /* No offset */ + } + dev_dbg(dev->dev, "Fast Mode%s HCNT:LCNT = %d:%d\n", + fp_str, dev->fs_hcnt, dev->fs_lcnt); + + if (dev->hs_hcnt && dev->hs_lcnt) + dev_dbg(dev->dev, "High Speed Mode HCNT:LCNT = %d:%d\n", + dev->hs_hcnt, dev->hs_lcnt); + + ret = i2c_phytium_set_sda_hold(dev); + if (ret) + goto out; + + switch (dev->master_cfg & IC_CON_SPEED_MASK) { + case IC_CON_SPEED_STD: + mode_str = "Standard Mode"; + break; + case IC_CON_SPEED_HIGH: + mode_str = "High Speed Mode"; + break; + default: + mode_str = "Fast Mode"; + } + dev_dbg(dev->dev, "Bus speed: %s%s\n", mode_str, fp_str); + +out: + return ret; +} + +static irqreturn_t i2c_phytium_isr(int this_irq, void *dev_id) +{ + struct phytium_i2c_dev *dev = dev_id; + u32 stat, enabled; + + enabled = phytium_readl(dev, IC_ENABLE); + stat = phytium_readl(dev, IC_RAW_INTR_STAT); + if (!enabled || !(stat & ~IC_INTR_ACTIVITY)) + return IRQ_NONE; + + i2c_phytium_irq_handler_master(dev); + + return IRQ_HANDLED; +} + +int i2c_phytium_probe(struct phytium_i2c_dev *dev) +{ + struct i2c_adapter *adapter = &dev->adapter; + unsigned long irq_flags; + int ret; + + init_completion(&dev->cmd_complete); + + dev->init = i2c_phytium_init_master; + dev->disable = i2c_phytium_disable; + dev->disable_int = i2c_phytium_disable_int; + + ret = i2c_phytium_set_timings_master(dev); + if (ret) + return ret; + + ret = dev->init(dev); + if (ret) + return ret; + + /* XXX: should be initialized in firmware, remove it in future */ +#define DEFAULT_TIMEOUT (DEFAULT_CLOCK_FREQUENCY / 1000 * 35) + phytium_writel(dev, DEFAULT_TIMEOUT, IC_SMBCLK_LOW_MEXT); + phytium_writel(dev, DEFAULT_TIMEOUT, IC_SMBCLK_LOW_TIMEOUT); + phytium_writel(dev, DEFAULT_TIMEOUT, IC_SMBDAT_STUCK_TIMEOUT); + + snprintf(adapter->name, sizeof(adapter->name), "Phytium I2C adapter"); + adapter->retries = 3; + adapter->algo = &i2c_phytium_algo; + adapter->quirks = &i2c_phytium_quirks; + adapter->dev.parent = dev->dev; + i2c_set_adapdata(adapter, dev); + + irq_flags = IRQF_SHARED | IRQF_COND_SUSPEND; + + i2c_phytium_disable_int(dev); + ret = devm_request_irq(dev->dev, dev->irq, i2c_phytium_isr, irq_flags, + dev_name(dev->dev), dev); + if (ret) { + dev_err(dev->dev, "failed to request irq %i: %d\n", dev->irq, ret); + return ret; + } + + /* + * Increment PM usage count during adapter registration in order to + * avoid possible spurious runtime suspend when adapter device is + * registered to the device core and immediate resume in case bus has + * registered I2C slaves that do I2C transfers in their probe. + */ + pm_runtime_get_noresume(dev->dev); + ret = i2c_add_numbered_adapter(adapter); + if (ret) + dev_err(dev->dev, "fail to add adapter: %d\n", ret); + pm_runtime_put_noidle(dev->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(i2c_phytium_probe); + +MODULE_DESCRIPTION("Phytium I2C bus master adapter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-phytium-pci.c b/drivers/i2c/busses/i2c-phytium-pci.c new file mode 100644 index 0000000000000000000000000000000000000000..deba421664b8eb7b712cbd5e69e5839c51c56b42 --- /dev/null +++ b/drivers/i2c/busses/i2c-phytium-pci.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PCI driver for Phytium I2C adapter. + * + * Derived from Synopysys I2C driver. + * Copyright (C) 2006 Texas Instruments. + * Copyright (C) 2007 MontaVista Software Inc. + * Copyright (C) 2009 Provigent Ltd. + * Copyright (C) 2011, 2015, 2016 Intel Corporation. + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i2c-phytium-core.h" + +#define DRV_NAME "i2c-phytium-pci" + +enum phytium_pci_ctl_id_t { + octopus_i2c, +}; + +struct scl_sda_cfg { + u32 ss_hcnt; + u32 fs_hcnt; + u32 ss_lcnt; + u32 fs_lcnt; + u32 sda_hold; +}; + +struct phytium_pci_i2c { + u32 bus_num; + u32 bus_cfg; + u32 tx_fifo_depth; + u32 rx_fifo_depth; + u32 clk_khz; + u32 functionality; + u32 flags; + struct scl_sda_cfg *scl_sda_cfg; + int (*setup)(struct pci_dev *pdev, struct phytium_pci_i2c *c); +}; + +/* Octopus HCNT/LCNT/SDA hold time */ +static struct scl_sda_cfg octopus_config = { + .ss_hcnt = 0x190, + .ss_lcnt = 0x1d6, + .fs_hcnt = 0x3c, + .fs_lcnt = 0x82, + .sda_hold = 0x0, // XXX +}; + +static int octopus_setup(struct pci_dev *pdev, struct phytium_pci_i2c *c) +{ + struct phytium_i2c_dev *i2c = pci_get_drvdata(pdev); + struct i2c_client *ara; + + if (pdev->device == 0xdc32) { + /* + * Since we have already register the adapter, the dev->irq + * must be valid. + */ + i2c->alert_data.irq = i2c->irq; + + ara = i2c_new_smbus_alert_device(&i2c->adapter, &i2c->alert_data); + if (IS_ERR(ara)) + return PTR_ERR(ara); + + i2c->ara = ara; + } + + return 0; +} + +static struct phytium_pci_i2c pci_ctrl_info[] = { + [octopus_i2c] = { + .bus_num = -1, + .bus_cfg = IC_CON_MASTER | IC_CON_SLAVE_DISABLE | + IC_CON_RESTART_EN | IC_CON_SPEED_FAST, + .tx_fifo_depth = 7, + .rx_fifo_depth = 7, + .functionality = I2C_FUNC_10BIT_ADDR, + .clk_khz = 48000000, + .scl_sda_cfg = &octopus_config, + .setup = octopus_setup, + }, +}; + +#ifdef CONFIG_PM +static int i2c_phytium_pci_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct phytium_i2c_dev *i_dev = pci_get_drvdata(pdev); + + i_dev->disable(i_dev); + + return 0; +} + +static int i2c_phytium_pci_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct phytium_i2c_dev *i_dev = pci_get_drvdata(pdev); + + return i_dev->init(i_dev); +} +#endif + +static UNIVERSAL_DEV_PM_OPS(i2c_phytium_pm_ops, i2c_phytium_pci_suspend, + i2c_phytium_pci_resume, NULL); + +static u32 i2c_phytium_get_clk_rate_khz(struct phytium_i2c_dev *dev) +{ + return dev->controller->clk_khz; +} + +static int i2c_phytium_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct phytium_i2c_dev *dev; + struct i2c_adapter *adapter; + struct phytium_pci_i2c *controller; + struct scl_sda_cfg *cfg; + int ret; + + if (id->driver_data >= ARRAY_SIZE(pci_ctrl_info)) { + dev_err(&pdev->dev, "%s: invalid driver data %ld\n", __func__, + id->driver_data); + ret = -EINVAL; + goto out; + } + + controller = &pci_ctrl_info[id->driver_data]; + + ret = pcim_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "Failed to enable I2C PCI device (%d)\n", ret); + goto out; + } + + ret = pcim_iomap_regions(pdev, 0x1, pci_name(pdev)); + if (ret) { + dev_err(&pdev->dev, "I/O memory remapping failed\n"); + goto out; + } + + dev = devm_kzalloc(&pdev->dev, sizeof(struct phytium_i2c_dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto out; + } + + dev->controller = controller; + dev->get_clk_rate_khz = i2c_phytium_get_clk_rate_khz; + dev->base = pcim_iomap_table(pdev)[0]; + dev->dev = &pdev->dev; + dev->irq = pdev->irq; + dev->flags |= controller->flags; + + dev->functionality = controller->functionality | IC_DEFAULT_FUNCTIONALITY; + dev->master_cfg = controller->bus_cfg; + if (controller->scl_sda_cfg) { + cfg = controller->scl_sda_cfg; + dev->ss_hcnt = cfg->ss_hcnt; + dev->fs_hcnt = cfg->fs_hcnt; + dev->ss_lcnt = cfg->ss_lcnt; + dev->fs_lcnt = cfg->fs_lcnt; + dev->sda_hold_time = cfg->sda_hold; + } + + pci_set_drvdata(pdev, dev); + + dev->tx_fifo_depth = controller->tx_fifo_depth; + dev->rx_fifo_depth = controller->rx_fifo_depth; + + adapter = &dev->adapter; + adapter->owner = THIS_MODULE; + adapter->class = 0; + ACPI_COMPANION_SET(&adapter->dev, ACPI_COMPANION(&pdev->dev)); + adapter->nr = controller->bus_num; + + ret = i2c_phytium_probe(dev); + if (ret) + goto out; + + if (controller->setup) { + ret = controller->setup(pdev, controller); + if (ret) + goto out; + } + + pm_runtime_set_autosuspend_delay(&pdev->dev, 1000); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_put_autosuspend(&pdev->dev); + pm_runtime_allow(&pdev->dev); + +out: + return ret; +} + +static void i2c_phytium_pci_remove(struct pci_dev *pdev) +{ + struct phytium_i2c_dev *dev = pci_get_drvdata(pdev); + + dev->disable(dev); + pm_runtime_forbid(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + + i2c_del_adapter(&dev->adapter); +} + +static const struct pci_device_id i2_phytium_pci_ids[] = { + { PCI_VDEVICE(PHYTIUM, 0xdc32), octopus_i2c }, + { PCI_VDEVICE(PHYTIUM, 0xdc30), octopus_i2c }, + { } +}; +MODULE_DEVICE_TABLE(pci, i2_phytium_pci_ids); + +static struct pci_driver phytium_i2c_driver = { + .name = DRV_NAME, + .id_table = i2_phytium_pci_ids, + .probe = i2c_phytium_pci_probe, + .remove = i2c_phytium_pci_remove, + .driver = { + .pm = &i2c_phytium_pm_ops, + }, +}; + +module_pci_driver(phytium_i2c_driver); + +MODULE_ALIAS("i2c-phytium-pci"); +MODULE_AUTHOR("Cheng Quan "); +MODULE_DESCRIPTION("Phytium PCI I2C bus adapter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-phytium-platform.c b/drivers/i2c/busses/i2c-phytium-platform.c new file mode 100644 index 0000000000000000000000000000000000000000..7077d851d1f26196690a3d674780e4c57abc5081 --- /dev/null +++ b/drivers/i2c/busses/i2c-phytium-platform.c @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Phytium I2C adapter driver. + * + * Derived from Synopysys I2C driver. + * Copyright (C) 2006 Texas Instruments. + * Copyright (C) 2007 MontaVista Software Inc. + * Copyright (C) 2009 Provigent Ltd. + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i2c-phytium-core.h" + +#define DRV_NAME "i2c-phytium-platform" + +static u32 i2c_phytium_get_clk_rate_khz(struct phytium_i2c_dev *dev) +{ + return clk_get_rate(dev->clk)/1000; +} + +#ifdef CONFIG_ACPI +static void phytium_i2c_acpi_params(struct platform_device *pdev, char method[], + u16 *hcnt, u16 *lcnt, u32 *sda_hold) +{ + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; + acpi_handle handle = ACPI_HANDLE(&pdev->dev); + union acpi_object *obj; + + if (ACPI_FAILURE(acpi_evaluate_object(handle, method, NULL, &buf))) + return; + + obj = (union acpi_object *)buf.pointer; + if (obj->type == ACPI_TYPE_PACKAGE && obj->package.count == 3) { + const union acpi_object *objs = obj->package.elements; + + *hcnt = (u16)objs[0].integer.value; + *lcnt = (u16)objs[1].integer.value; + *sda_hold = (u32)objs[2].integer.value; + } + + kfree(buf.pointer); +} + +static int phytium_i2c_acpi_configure(struct platform_device *pdev) +{ + struct phytium_i2c_dev *dev = platform_get_drvdata(pdev); + struct i2c_timings *t = &dev->timings; + u32 ss_ht = 0, fp_ht = 0, hs_ht = 0, fs_ht = 0; + const struct acpi_device_id *id; + + dev->adapter.nr = -1; + dev->tx_fifo_depth = 32; + dev->rx_fifo_depth = 32; + + /* + * Try to get SDA hold time and *CNT values from an ACPI method for + * selected speed modes. + */ + phytium_i2c_acpi_params(pdev, "SSCN", &dev->ss_hcnt, &dev->ss_lcnt, &ss_ht); + phytium_i2c_acpi_params(pdev, "FPCN", &dev->fp_hcnt, &dev->fp_lcnt, &fp_ht); + phytium_i2c_acpi_params(pdev, "HSCN", &dev->hs_hcnt, &dev->hs_lcnt, &hs_ht); + phytium_i2c_acpi_params(pdev, "FMCN", &dev->fs_hcnt, &dev->fs_lcnt, &fs_ht); + + switch (t->bus_freq_hz) { + case 100000: + dev->sda_hold_time = ss_ht; + break; + case 1000000: + dev->sda_hold_time = fp_ht; + break; + case 3400000: + dev->sda_hold_time = hs_ht; + break; + case 400000: + default: + dev->sda_hold_time = fs_ht; + break; + } + + id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev); + if (id && id->driver_data) + dev->flags |= (u32)id->driver_data; + + return 0; +} + +static const struct acpi_device_id phytium_i2c_acpi_match[] = { + { "PHYT0038", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, phytium_i2c_acpi_match); +#else +static inline int phytium_i2c_acpi_configure(struct platform_device *pdev) +{ + return -ENODEV; +} +#endif + +static void i2c_phytium_configure_master(struct phytium_i2c_dev *dev) +{ + struct i2c_timings *t = &dev->timings; + + dev->functionality = I2C_FUNC_10BIT_ADDR | IC_DEFAULT_FUNCTIONALITY; + + dev->master_cfg = IC_CON_MASTER | IC_CON_SLAVE_DISABLE | + IC_CON_RESTART_EN; + + dev->mode = PHYTIUM_IC_MASTER; + + switch (t->bus_freq_hz) { + case 100000: + dev->master_cfg |= IC_CON_SPEED_STD; + break; + case 3400000: + dev->master_cfg |= IC_CON_SPEED_HIGH; + break; + default: + dev->master_cfg |= IC_CON_SPEED_FAST; + } +} + +static void i2c_phytium_configure_slave(struct phytium_i2c_dev *dev) +{ + dev->functionality = I2C_FUNC_SLAVE | IC_DEFAULT_FUNCTIONALITY; + + dev->slave_cfg = IC_CON_RX_FIFO_FULL_HLD_CTRL | + IC_CON_RESTART_EN | IC_CON_STOP_DET_IFADDRESSED; + + dev->mode = PHYTIUM_IC_SLAVE; +} + +static int phytium_i2c_plat_probe(struct platform_device *pdev) +{ + struct i2c_adapter *adap; + struct phytium_i2c_dev *dev; + struct i2c_timings *t; + u32 acpi_speed; + struct resource *mem; + int irq, ret, i; + static const int supported_speeds[] = { + 0, 100000, 400000, 1000000, 3400000 + }; + + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + dev = devm_kzalloc(&pdev->dev, sizeof(struct phytium_i2c_dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(dev->base)) + return PTR_ERR(dev->base); + + dev->dev = &pdev->dev; + dev->irq = irq; + platform_set_drvdata(pdev, dev); + + dev->rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); + if (IS_ERR(dev->rst)) { + if (PTR_ERR(dev->rst) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } else { + reset_control_deassert(dev->rst); + } + + t = &dev->timings; + i2c_parse_fw_timings(&pdev->dev, t, false); + + acpi_speed = i2c_acpi_find_bus_speed(&pdev->dev); + /* + * Some DSTDs use a non standard speed, round down to the lowest + * standard speed. + */ + for (i = 1; i < ARRAY_SIZE(supported_speeds); i++) { + if (acpi_speed < supported_speeds[i]) + break; + } + acpi_speed = supported_speeds[i - 1]; + + /* + * Find bus speed from the "clock-frequency" device property, ACPI + * or by using fast mode if neither is set. + */ + if (acpi_speed && t->bus_freq_hz) + t->bus_freq_hz = min(t->bus_freq_hz, acpi_speed); + else if (acpi_speed || t->bus_freq_hz) + t->bus_freq_hz = max(t->bus_freq_hz, acpi_speed); + else + t->bus_freq_hz = 400000; + + if (has_acpi_companion(&pdev->dev)) + phytium_i2c_acpi_configure(pdev); + + /* + * Only standard mode at 100kHz, fast mode at 400kHz, + * fast mode plus at 1MHz and high speed mode at 3.4MHz are supported. + */ + if (t->bus_freq_hz != 100000 && t->bus_freq_hz != 400000 && + t->bus_freq_hz != 1000000 && t->bus_freq_hz != 3400000) { + dev_err(&pdev->dev, + "%d Hz is unsupported, only 100kHz, 400kHz, 1MHz and 3.4MHz are supported\n", + t->bus_freq_hz); + ret = -EINVAL; + goto exit_reset; + } + + if (i2c_detect_slave_mode(&pdev->dev)) + i2c_phytium_configure_slave(dev); + else + i2c_phytium_configure_master(dev); + + dev->clk = devm_clk_get(&pdev->dev, NULL); + if (!i2c_phytium_prepare_clk(dev, true)) { + u64 clk_khz; + + dev->get_clk_rate_khz = i2c_phytium_get_clk_rate_khz; + clk_khz = dev->get_clk_rate_khz(dev); + + if (!dev->sda_hold_time && t->sda_hold_ns) + dev->sda_hold_time = + div_u64(clk_khz * t->sda_hold_ns + 500000, 1000000); + } + + dev->tx_fifo_depth = 7; + dev->rx_fifo_depth = 7; + dev->adapter.nr = pdev->id; + + adap = &dev->adapter; + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_DEPRECATED; + ACPI_COMPANION_SET(&adap->dev, ACPI_COMPANION(&pdev->dev)); + adap->dev.of_node = pdev->dev.of_node; + + dev_pm_set_driver_flags(&pdev->dev, + DPM_FLAG_SMART_PREPARE | + DPM_FLAG_SMART_SUSPEND | + DPM_FLAG_MAY_SKIP_RESUME); + + /* The code below assumes runtime PM to be disabled. */ + WARN_ON(pm_runtime_enabled(&pdev->dev)); + + pm_runtime_set_autosuspend_delay(&pdev->dev, 1000); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + + pm_runtime_enable(&pdev->dev); + + if (dev->mode == PHYTIUM_IC_SLAVE) + ret = i2c_phytium_probe_slave(dev); + else + ret = i2c_phytium_probe(dev); + + if (ret) + goto exit_probe; + + return ret; + +exit_probe: + pm_runtime_disable(dev->dev); +exit_reset: + if (!IS_ERR_OR_NULL(dev->rst)) + reset_control_assert(dev->rst); + return ret; +} + +static int phytium_i2c_plat_remove(struct platform_device *pdev) +{ + struct phytium_i2c_dev *dev = platform_get_drvdata(pdev); + + pm_runtime_get_sync(&pdev->dev); + + i2c_del_adapter(&dev->adapter); + + dev->disable(dev); + + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(dev->dev); + + if (!IS_ERR_OR_NULL(dev->rst)) + reset_control_assert(dev->rst); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id phytium_i2c_of_match[] = { + { .compatible = "phytium,i2c", }, + {}, +}; +MODULE_DEVICE_TABLE(of, phytium_i2c_of_match); +#endif + +static int __maybe_unused phytium_i2c_plat_suspend(struct device *dev) +{ + struct phytium_i2c_dev *idev = dev_get_drvdata(dev); + + idev->disable(idev); + i2c_phytium_prepare_clk(idev, false); + + return 0; +} + +static int __maybe_unused phytium_i2c_plat_resume(struct device *dev) +{ + struct phytium_i2c_dev *idev = dev_get_drvdata(dev); + + i2c_phytium_prepare_clk(idev, true); + + idev->init(idev); + + return 0; +} + +static const struct dev_pm_ops phytium_i2c_dev_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(phytium_i2c_plat_suspend, + phytium_i2c_plat_resume) + SET_RUNTIME_PM_OPS(phytium_i2c_plat_suspend, + phytium_i2c_plat_resume, NULL) +}; + +static struct platform_driver phytium_i2c_driver = { + .probe = phytium_i2c_plat_probe, + .remove = phytium_i2c_plat_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(phytium_i2c_of_match), + .acpi_match_table = ACPI_PTR(phytium_i2c_acpi_match), + .pm = &phytium_i2c_dev_pm_ops, + }, +}; +module_platform_driver(phytium_i2c_driver); + +MODULE_ALIAS("platform:i2c-phytium"); +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Phytium I2C bus adapter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-phytium-slave.c b/drivers/i2c/busses/i2c-phytium-slave.c new file mode 100644 index 0000000000000000000000000000000000000000..52cc2c9246cb5573bb379e9b91df8743bfcaa43b --- /dev/null +++ b/drivers/i2c/busses/i2c-phytium-slave.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium I2C adapter driver (slave only). + * + * Derived from Synopysys I2C driver. + * Copyright (C) 2016 Synopsys Inc. + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i2c-phytium-core.h" + +static void i2c_phytium_configure_fifo_slave(struct phytium_i2c_dev *dev) +{ + /* Configure Tx/Rx FIFO threshold levels. */ + phytium_writel(dev, 0, IC_TX_TL); + phytium_writel(dev, 0, IC_RX_TL); + + /* Configure the I2C slave. */ + phytium_writel(dev, dev->slave_cfg, IC_CON); + phytium_writel(dev, IC_INTR_SLAVE_MASK, IC_INTR_MASK); +} + +static int i2c_phytium_init_slave(struct phytium_i2c_dev *dev) +{ + /* Disable the adapter. */ + __i2c_phytium_disable(dev); + + /* Write SDA hold time if supported */ + if (dev->sda_hold_time) + phytium_writel(dev, dev->sda_hold_time, IC_SDA_HOLD); + + i2c_phytium_configure_fifo_slave(dev); + + return 0; +} + +static int i2c_phytium_reg_slave(struct i2c_client *slave) +{ + struct phytium_i2c_dev *dev = i2c_get_adapdata(slave->adapter); + + if (dev->slave) + return -EBUSY; + if (slave->flags & I2C_CLIENT_TEN) + return -EAFNOSUPPORT; + pm_runtime_get_sync(dev->dev); + + /* + * Set slave address in the IC_SAR register, + * the address to which the i2c responds. + */ + __i2c_phytium_disable_nowait(dev); + phytium_writel(dev, slave->addr, IC_SAR); + dev->slave = slave; + + __i2c_phytium_enable(dev); + + dev->cmd_err = 0; + dev->msg_write_idx = 0; + dev->msg_read_idx = 0; + dev->msg_err = 0; + dev->status = STATUS_IDLE; + dev->abort_source = 0; + dev->rx_outstanding = 0; + + return 0; +} + +static int i2c_phytium_unreg_slave(struct i2c_client *slave) +{ + struct phytium_i2c_dev *dev = i2c_get_adapdata(slave->adapter); + + dev->disable_int(dev); + dev->disable(dev); + dev->slave = NULL; + pm_runtime_put(dev->dev); + + return 0; +} + +static u32 i2c_phytium_read_clear_intrbits_slave(struct phytium_i2c_dev *dev) +{ + u32 stat; + + /* + * The IC_INTR_STAT register just indicates "enabled" interrupts. + * Ths unmasked raw version of interrupt status bits are available + * in the IC_RAW_INTR_STAT register. + * + * That is, + * stat = phytium_readl(IC_INTR_STAT); + * equals to, + * stat = phytium_readl(IC_RAW_INTR_STAT) & phytium_readl(IC_INTR_MASK); + * + * The raw version might be useful for debugging purposes. + */ + stat = phytium_readl(dev, IC_INTR_STAT); + + /* + * Do not use the IC_CLR_INTR register to clear interrupts, or + * you'll miss some interrupts, triggered during the period from + * phytium_readl(IC_INTR_STAT) to phytium_readl(IC_CLR_INTR). + * + * Instead, use the separately-prepared IC_CLR_* registers. + */ + if (stat & IC_INTR_TX_ABRT) + phytium_readl(dev, IC_CLR_TX_ABRT); + if (stat & IC_INTR_RX_UNDER) + phytium_readl(dev, IC_CLR_RX_UNDER); + if (stat & IC_INTR_RX_OVER) + phytium_readl(dev, IC_CLR_RX_OVER); + if (stat & IC_INTR_TX_OVER) + phytium_readl(dev, IC_CLR_TX_OVER); + if (stat & IC_INTR_RX_DONE) + phytium_readl(dev, IC_CLR_RX_DONE); + if (stat & IC_INTR_ACTIVITY) + phytium_readl(dev, IC_CLR_ACTIVITY); + if (stat & IC_INTR_STOP_DET) + phytium_readl(dev, IC_CLR_STOP_DET); + if (stat & IC_INTR_START_DET) + phytium_readl(dev, IC_CLR_START_DET); + if (stat & IC_INTR_GEN_CALL) + phytium_readl(dev, IC_CLR_GEN_CALL); + + return stat; +} + +/* + * Interrupt service routine. This gets called whenever an I2C slave interrupt + * occurs. + */ +static int i2c_phytium_irq_handler_slave(struct phytium_i2c_dev *dev) +{ + u32 raw_stat, stat, enabled; + u8 val, slave_activity; + + stat = phytium_readl(dev, IC_INTR_STAT); + enabled = phytium_readl(dev, IC_ENABLE); + raw_stat = phytium_readl(dev, IC_RAW_INTR_STAT); + slave_activity = ((phytium_readl(dev, IC_STATUS) & + IC_STATUS_SLAVE_ACTIVITY) >> 6); + + if (!enabled || !(raw_stat & ~IC_INTR_ACTIVITY) || !dev->slave) + return 0; + + dev_dbg(dev->dev, + "%#x STATUS SLAVE_ACTIVITY=%#x : RAW_INTR_STAT=%#x : INTR_STAT=%#x\n", + enabled, slave_activity, raw_stat, stat); + + if ((stat & IC_INTR_RX_FULL) && (stat & IC_INTR_STOP_DET)) + i2c_slave_event(dev->slave, I2C_SLAVE_WRITE_REQUESTED, &val); + + if (stat & IC_INTR_RD_REQ) { + if (slave_activity) { + if (stat & IC_INTR_RX_FULL) { + val = phytium_readl(dev, IC_DATA_CMD); + + if (!i2c_slave_event(dev->slave, + I2C_SLAVE_WRITE_RECEIVED, + &val)) { + dev_vdbg(dev->dev, "Byte %X acked!", + val); + } + phytium_readl(dev, IC_CLR_RD_REQ); + stat = i2c_phytium_read_clear_intrbits_slave(dev); + } else { + phytium_readl(dev, IC_CLR_RD_REQ); + phytium_readl(dev, IC_CLR_RX_UNDER); + stat = i2c_phytium_read_clear_intrbits_slave(dev); + } + if (!i2c_slave_event(dev->slave, + I2C_SLAVE_READ_REQUESTED, + &val)) + phytium_writel(dev, val, IC_DATA_CMD); + } + } + + if (stat & IC_INTR_RX_DONE) { + if (!i2c_slave_event(dev->slave, I2C_SLAVE_READ_PROCESSED, + &val)) + phytium_readl(dev, IC_CLR_RX_DONE); + + i2c_slave_event(dev->slave, I2C_SLAVE_STOP, &val); + stat = i2c_phytium_read_clear_intrbits_slave(dev); + return 1; + } + + if (stat & IC_INTR_RX_FULL) { + val = phytium_readl(dev, IC_DATA_CMD); + if (!i2c_slave_event(dev->slave, I2C_SLAVE_WRITE_RECEIVED, + &val)) + dev_vdbg(dev->dev, "Byte %X acked!", val); + } else { + i2c_slave_event(dev->slave, I2C_SLAVE_STOP, &val); + stat = i2c_phytium_read_clear_intrbits_slave(dev); + } + + return 1; +} + +static irqreturn_t i2c_phytium_isr_slave(int this_irq, void *dev_id) +{ + struct phytium_i2c_dev *dev = dev_id; + int ret; + + i2c_phytium_read_clear_intrbits_slave(dev); + ret = i2c_phytium_irq_handler_slave(dev); + if (ret > 0) + complete(&dev->cmd_complete); + + return IRQ_RETVAL(ret); +} + +static const struct i2c_algorithm i2c_phytium_algo = { + .functionality = i2c_phytium_func, + .reg_slave = i2c_phytium_reg_slave, + .unreg_slave = i2c_phytium_unreg_slave, +}; + +int i2c_phytium_probe_slave(struct phytium_i2c_dev *dev) +{ + struct i2c_adapter *adap = &dev->adapter; + int ret; + + init_completion(&dev->cmd_complete); + + dev->init = i2c_phytium_init_slave; + dev->disable = i2c_phytium_disable; + dev->disable_int = i2c_phytium_disable_int; + + ret = dev->init(dev); + if (ret) + return ret; + + snprintf(adap->name, sizeof(adap->name), + "Synopsys DesignWare I2C Slave adapter"); + adap->retries = 3; + adap->algo = &i2c_phytium_algo; + adap->dev.parent = dev->dev; + i2c_set_adapdata(adap, dev); + + ret = devm_request_irq(dev->dev, dev->irq, i2c_phytium_isr_slave, + IRQF_SHARED, dev_name(dev->dev), dev); + if (ret) { + dev_err(dev->dev, "failure requesting irq %i: %d\n", + dev->irq, ret); + return ret; + } + + ret = i2c_add_numbered_adapter(adap); + if (ret) + dev_err(dev->dev, "failure adding adapter: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(i2c_phytium_probe_slave); diff --git a/drivers/i2c/busses/i2c-zhaoxin.c b/drivers/i2c/busses/i2c-zhaoxin.c index cd00f6ce1567e0a3dd345a103af226c44a79ce8c..f9dc1c49317487e60d4d73d04352639f3d4364b7 100644 --- a/drivers/i2c/busses/i2c-zhaoxin.c +++ b/drivers/i2c/busses/i2c-zhaoxin.c @@ -4,7 +4,7 @@ * All rights reserved. */ -#define DRIVER_VERSION "1.6.0" +#define DRIVER_VERSION "1.6.1" #include #include @@ -231,14 +231,15 @@ int zxi2c_fifo_irq_xfer(struct zxi2c *i2c, bool irq) static irqreturn_t zxi2c_isr(int irq, void *data) { struct zxi2c *i2c = data; + void __iomem *base = i2c->base; u8 status; /* save the status and write-clear it */ - status = readw(i2c->base + ZXI2C_REG_ISR); + status = readw(base + ZXI2C_REG_ISR); if (!status) return IRQ_NONE; - writew(status, i2c->base + ZXI2C_REG_ISR); + writew(status, base + ZXI2C_REG_ISR); i2c->ret = 0; if (status & ZXI2C_ISR_NACK_ADDR) @@ -260,16 +261,17 @@ static irqreturn_t zxi2c_isr(int irq, void *data) static int zxi2c_write(struct zxi2c *i2c, struct i2c_msg *msg, int last) { u16 tcr_val = i2c->tcr; + void __iomem *base = i2c->base; i2c->last = last; - writew(msg->buf[0] & 0xFF, i2c->base + ZXI2C_REG_CDR); + writew(msg->buf[0] & 0xFF, base + ZXI2C_REG_CDR); reinit_completion(&i2c->complete); tcr_val |= msg->addr & 0x7f; - writew(tcr_val, i2c->base + ZXI2C_REG_TCR); + writew(tcr_val, base + ZXI2C_REG_TCR); if (!wait_for_completion_timeout(&i2c->complete, ZXI2C_TIMEOUT)) return -ETIMEDOUT; @@ -280,25 +282,26 @@ static int zxi2c_write(struct zxi2c *i2c, struct i2c_msg *msg, int last) static int zxi2c_read(struct zxi2c *i2c, struct i2c_msg *msg, bool first) { u16 val, tcr_val = i2c->tcr; + void __iomem *base = i2c->base; - val = readw(i2c->base + ZXI2C_REG_CR); + val = readw(base + ZXI2C_REG_CR); val &= ~(ZXI2C_CR_TX_END | ZXI2C_CR_RX_END); if (msg->len == 1) val |= ZXI2C_CR_RX_END; - writew(val, i2c->base + ZXI2C_REG_CR); + writew(val, base + ZXI2C_REG_CR); reinit_completion(&i2c->complete); tcr_val |= ZXI2C_TCR_READ | (msg->addr & 0x7f); - writew(tcr_val, i2c->base + ZXI2C_REG_TCR); + writew(tcr_val, base + ZXI2C_REG_TCR); if (!first) { - val = readw(i2c->base + ZXI2C_REG_CR); + val = readw(base + ZXI2C_REG_CR); val |= ZXI2C_CR_CPU_RDY; - writew(val, i2c->base + ZXI2C_REG_CR); + writew(val, base + ZXI2C_REG_CR); } if (!wait_for_completion_timeout(&i2c->complete, ZXI2C_TIMEOUT)) @@ -358,8 +361,10 @@ static int zxi2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int zxi2c_fifo_irq_xfer(i2c, 0); - if (!wait_for_completion_timeout(&i2c->complete, ZXI2C_TIMEOUT)) + if (!wait_for_completion_timeout(&i2c->complete, ZXI2C_TIMEOUT)) { + dev_dbg(i2c->dev, "fifo mode timeout\n"); return -ETIMEDOUT; + } ret = i2c->ret; } else { @@ -371,8 +376,10 @@ static int zxi2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int iowrite8(ZXI2C_ISR_NACK_ADDR | ZXI2C_IMR_BYTE, base + ZXI2C_REG_IMR); ret = zxi2c_xfer(adap, msgs, num); - if (ret == -ETIMEDOUT) + if (ret == -ETIMEDOUT) { + dev_dbg(i2c->dev, "byte mode timeout\n"); iowrite16(tmp | ZXI2C_CR_END_MASK, base + ZXI2C_REG_CR); + } } /* dis interrupt */ iowrite8(0, base + ZXI2C_REG_IMR); @@ -409,9 +416,11 @@ static const u32 zxi2c_speed_params_table[][3] = { static void zxi2c_set_bus_speed(struct zxi2c *i2c) { - iowrite16(i2c->tr, i2c->base + ZXI2C_REG_TR); - iowrite8(ZXI2C_CLK_50M, i2c->base + ZXI2C_REG_CLK); - iowrite16(i2c->mcr, i2c->base + ZXI2C_REG_MCR); + void __iomem *base = i2c->base; + + iowrite16(i2c->tr, base + ZXI2C_REG_TR); + iowrite8(ZXI2C_CLK_50M, base + ZXI2C_REG_CLK); + iowrite16(i2c->mcr, base + ZXI2C_REG_MCR); } static void zxi2c_get_bus_speed(struct zxi2c *i2c) @@ -419,6 +428,7 @@ static void zxi2c_get_bus_speed(struct zxi2c *i2c) u8 i, count; u8 fstp; const u32 *params; + void __iomem *base = i2c->base; u32 acpi_speed = i2c_acpi_find_bus_speed(i2c->dev); @@ -430,7 +440,7 @@ static void zxi2c_get_bus_speed(struct zxi2c *i2c) i = i < count ? i : 1; params = zxi2c_speed_params_table[i]; - fstp = ioread8(i2c->base + ZXI2C_REG_TR); + fstp = ioread8(base + ZXI2C_REG_TR); if (abs(fstp - params[2]) > 0x10) { /* * if BIOS setting value far from golden value, @@ -444,7 +454,7 @@ static void zxi2c_get_bus_speed(struct zxi2c *i2c) } i2c->tcr = params[1]; - i2c->mcr = ioread16(i2c->base + ZXI2C_REG_MCR); + i2c->mcr = ioread16(base + ZXI2C_REG_MCR); /* for Hs-mode, use 0000 1000 as master code */ if (params[0] == I2C_MAX_HIGH_SPEED_MODE_FREQ) i2c->mcr |= ZXI2C_HS_MASTER_CODE; @@ -516,7 +526,7 @@ static int zxi2c_probe(struct platform_device *pdev) dev_name(i2c->dev)); i2c_set_adapdata(adap, i2c); - error = i2c_add_adapter(adap); + error = devm_i2c_add_adapter(&pdev->dev, adap); if (error) return error; @@ -526,21 +536,6 @@ static int zxi2c_probe(struct platform_device *pdev) return 0; } -static int zxi2c_remove(struct platform_device *pdev) -{ - struct zxi2c *i2c = platform_get_drvdata(pdev); - - devm_free_irq(&pdev->dev, i2c->irq, i2c); - - i2c_del_adapter(&i2c->adapter); - - platform_set_drvdata(pdev, NULL); - - devm_kfree(&pdev->dev, i2c); - - return 0; -} - static int zxi2c_resume(struct device *dev) { struct zxi2c *i2c = dev_get_drvdata(dev); @@ -563,7 +558,6 @@ MODULE_DEVICE_TABLE(acpi, zxi2c_acpi_match); static struct platform_driver zxi2c_driver = { .probe = zxi2c_probe, - .remove = zxi2c_remove, .driver = { .name = ZX_I2C_NAME, .acpi_match_table = zxi2c_acpi_match, diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile index 11982efbc6d91473dd914820c12839b9a567c253..02d9b450fb8647bb01d6bab98cf986b1291cac17 100644 --- a/drivers/i3c/Makefile +++ b/drivers/i3c/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 i3c-y := device.o master.o obj-$(CONFIG_I3C) += i3c.o +i3c-$(CONFIG_ACPI) += i3c_master_acpi.o obj-$(CONFIG_I3C) += master/ diff --git a/drivers/i3c/i3c_master_acpi.c b/drivers/i3c/i3c_master_acpi.c new file mode 100644 index 0000000000000000000000000000000000000000..9346d882aa5529d644a91d3fc0928a8d1c62cccc --- /dev/null +++ b/drivers/i3c/i3c_master_acpi.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for I3C ACPI Interface + * + * Copyright (C) 2021-2023 Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include "i3c_master_acpi.h" + +static int i3c_master_acpi_parse_val(union acpi_object *store_elements, char *method, + int count, u32 *value) +{ + int i, package_count, ret = -1; + union acpi_object *acpi_elements; + + for (i = 0; i < count; i++) { + acpi_elements = store_elements; + if (acpi_elements[i].type == ACPI_TYPE_PACKAGE) { + package_count = acpi_elements[i].package.count; + + if (package_count == 2) { + acpi_elements = acpi_elements[i].package.elements; + if (acpi_elements[0].type == ACPI_TYPE_STRING) { + if (!strcmp(acpi_elements[0].string.pointer, method)) { + *value = acpi_elements[1].integer.value; + ret = 0; + break; + } + } + } + } + } + return ret; +} + +int i3c_master_acpi_get_params(acpi_handle handle, char *method, u32 *value) +{ + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; + acpi_status status = 0; + union acpi_object *obj; + int count, ret = -1; + + status = acpi_evaluate_object(handle, "_DSD", NULL, &buf); + if (ACPI_FAILURE(status)) { + kfree(buf.pointer); + return -2; + } + + obj = (union acpi_object *)buf.pointer; + + if (obj->type == ACPI_TYPE_PACKAGE) { + union acpi_object *acpi_elements; + union acpi_object *store_elements; + + acpi_elements = obj->package.elements; + if ((obj->package.count >= 2) && (acpi_elements[1].type == ACPI_TYPE_PACKAGE)) { + count = acpi_elements[1].package.count; + acpi_elements = acpi_elements[1].package.elements; + + store_elements = acpi_elements; + ret = i3c_master_acpi_parse_val(store_elements, method, count, value); + } + } + + kfree(buf.pointer); + return ret; +} +EXPORT_SYMBOL_GPL(i3c_master_acpi_get_params); + +void i3c_master_acpi_clk_params(acpi_handle handle, char *method, + u32 *pres_ctrl0, u32 *pres_ctrl1, u32 *thd_delay) +{ + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; + union acpi_object *obj; + + if (ACPI_FAILURE(acpi_evaluate_object(handle, method, NULL, &buf))) + return; + + obj = (union acpi_object *)buf.pointer; + if (obj->type == ACPI_TYPE_PACKAGE && obj->package.count == 3) { + const union acpi_object *objs = obj->package.elements; + + *pres_ctrl0 = (u32)objs[0].integer.value; + *pres_ctrl1 = (u32)objs[1].integer.value; + *thd_delay = (u32)objs[2].integer.value; + } + + kfree(buf.pointer); +} +EXPORT_SYMBOL_GPL(i3c_master_acpi_clk_params); diff --git a/drivers/i3c/i3c_master_acpi.h b/drivers/i3c/i3c_master_acpi.h new file mode 100644 index 0000000000000000000000000000000000000000..61fa62ff12fbaaf7ccd704dfec13afbaa8918a92 --- /dev/null +++ b/drivers/i3c/i3c_master_acpi.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver for I3C ACPI Interface + * + * Copyright (C) 2021-2023 Phytium Technology Co., Ltd. + */ + +#ifndef __I3C_MASTER_ACPI_H +#define __I3C_MASTER_ACPI_H + +#include +#include + +int i3c_master_acpi_get_params(acpi_handle handle, char *method, u32 *value); +void i3c_master_acpi_clk_params(acpi_handle handle, char *method, + u32 *pres_ctrl0, u32 *pres_ctrl1, u32 *thd_delay); + +#endif diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 0e9ff5500a7771bb0d873dbb2e1395939c10aa60..826f2877d554623c41152526e7c04b8146ce5430 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -5,6 +5,8 @@ * Author: Boris Brezillon */ +#include +#include #include #include #include @@ -17,8 +19,25 @@ #include #include +#ifdef CONFIG_ACPI +#include "i3c_master_acpi.h" +#endif #include "internals.h" +struct i3c_acpi_lookup { + u64 pid; + acpi_handle adapter_handle; + acpi_handle device_handle; + acpi_handle search_handle; + int n; + int index; + u32 speed; + u32 min_speed; + u32 force_speed; + u32 slave_address; + u32 lvr; +}; + static DEFINE_IDR(i3c_bus_idr); static DEFINE_MUTEX(i3c_core_lock); static int __i3c_first_dynamic_bus_num; @@ -263,6 +282,88 @@ static ssize_t modalias_show(struct device *dev, } static DEVICE_ATTR_RO(modalias); +static ssize_t reg_read_store(struct device *dev, + struct device_attribute *da, + const char *buf, size_t size) +{ + struct i3c_device *i3c = dev_to_i3cdev(dev); + struct i3c_priv_xfer xfers[2]; + u8 status = 0; + long value; + __u8 reg; + __u8 val; + + status = kstrtol(buf, 0, &value); + if (status) + return status; + + reg = (__u8)value; + + xfers[0].rnw = false; + xfers[0].len = 1; + xfers[0].data.out = ® + + xfers[1].rnw = true; + xfers[1].len = 1; + xfers[1].data.in = &val; + + i3c_bus_normaluse_lock(i3c->bus); + i3c_device_do_priv_xfers(i3c, xfers, 2); + dev_info(dev, "reg read reg =0x%x, val=%x\n", reg, val); + i3c_bus_normaluse_unlock(i3c->bus); + + return size; +} +static DEVICE_ATTR_WO(reg_read); + +static ssize_t reg_write_store(struct device *dev, + struct device_attribute *da, + const char *buf, size_t size) +{ + struct i3c_device *i3c = dev_to_i3cdev(dev); + struct i3c_priv_xfer xfers[2]; + u8 status = 0; + char *p; + char *token; + long value; + __u8 reg; + u16 val; + + p = kmalloc(size, GFP_KERNEL); + strscpy(p, buf, size); + token = strsep(&p, " "); + + if (!token) + return -EINVAL; + + status = kstrtol(token, 0, &value); + if (status) + return status; + + reg = (u8)value; + token = strsep(&p, " "); + if (!token) + return -EINVAL; + + status = kstrtol(token, 0, &value); + if (status) + return status; + + val = ((u8)value << 8) | reg; + xfers[0].rnw = false; + xfers[0].len = 2; + xfers[0].data.out = &val; + + i3c_bus_normaluse_lock(i3c->bus); + i3c_device_do_priv_xfers(i3c, xfers, 1); + dev_info(dev, "reg write reg =0x%x, val=%x\n", reg, val); + i3c_bus_normaluse_unlock(i3c->bus); + + kfree(p); + return size; +} +static DEVICE_ATTR_WO(reg_write); + static struct attribute *i3c_device_attrs[] = { &dev_attr_bcr.attr, &dev_attr_dcr.attr, @@ -270,6 +371,8 @@ static struct attribute *i3c_device_attrs[] = { &dev_attr_dynamic_address.attr, &dev_attr_hdrcap.attr, &dev_attr_modalias.attr, + &dev_attr_reg_read.attr, + &dev_attr_reg_write.attr, NULL, }; ATTRIBUTE_GROUPS(i3c_device); @@ -1521,9 +1624,10 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) dev_set_name(&desc->dev->dev, "%d-%llx", master->bus.id, desc->info.pid); - if (desc->boardinfo) + if (desc->boardinfo) { desc->dev->dev.of_node = desc->boardinfo->of_node; - + desc->dev->dev.fwnode = desc->boardinfo->fwnode; + } ret = device_register(&desc->dev->dev); if (ret) { dev_err(&master->dev, @@ -2148,6 +2252,183 @@ static int of_populate_i3c_bus(struct i3c_master_controller *master) return 0; } +#ifdef CONFIG_ACPI +static int i3c_acpi_master_add_i2c_boardinfo(struct i3c_master_controller *master, + struct acpi_device *adev, struct i3c_acpi_lookup *look_up) +{ + struct i2c_dev_boardinfo *boardinfo; + struct device *dev = &master->dev; + u32 addr = look_up->slave_address; + + boardinfo = devm_kzalloc(dev, sizeof(*boardinfo), GFP_KERNEL); + if (!boardinfo) + return -ENOMEM; + + boardinfo->base.addr = addr; + acpi_set_modalias(adev, dev_name(&adev->dev), boardinfo->base.type, + sizeof(boardinfo->base.type)); + boardinfo->base.fwnode = acpi_fwnode_handle(adev); + + /* + * The I3C Specification does not clearly say I2C devices with 10-bit + * address are supported. These devices can't be passed properly through + * DEFSLVS command. + */ + if (boardinfo->base.flags & I2C_CLIENT_TEN) { + dev_err(dev, "I2C device with 10 bit address not supported."); + return -EOPNOTSUPP; + } + + boardinfo->lvr = look_up->lvr; + + /* If the client speed is smaller than controller speed, + * then set the controller speed to the client speed + */ + if (look_up->speed && look_up->speed < master->bus.scl_rate.i2c) + master->bus.scl_rate.i2c = look_up->speed; + + list_add_tail(&boardinfo->node, &master->boardinfo.i2c); + + return 0; +} + +static int i3c_acpi_master_add_i3c_boardinfo(struct i3c_master_controller *master, + struct acpi_device *adev, acpi_handle handle, struct i3c_acpi_lookup *look_up) +{ + struct i3c_dev_boardinfo *boardinfo; + struct device *dev = &master->dev; + enum i3c_addr_slot_status addrstatus; + u32 init_dyn_addr = 0; + + boardinfo = devm_kzalloc(dev, sizeof(*boardinfo), GFP_KERNEL); + if (!boardinfo) + return -ENOMEM; + + if (look_up->slave_address) { + if (look_up->slave_address > I3C_MAX_ADDR) + return -EINVAL; + + addrstatus = i3c_bus_get_addr_slot_status(&master->bus, look_up->slave_address); + if (addrstatus != I3C_ADDR_SLOT_FREE) + return -EINVAL; + } + + boardinfo->static_addr = look_up->slave_address; + boardinfo->pid = look_up->pid; + + if ((boardinfo->pid & GENMASK_ULL(63, 48)) || + I3C_PID_RND_LOWER_32BITS(boardinfo->pid)) + return -EINVAL; + + boardinfo->init_dyn_addr = init_dyn_addr; + boardinfo->fwnode = acpi_fwnode_handle(adev); + + /* If the client speed is smaller than controller speed, + * then set the controller speed to the client speed + */ + if (look_up->speed && look_up->speed < master->bus.scl_rate.i3c) + master->bus.scl_rate.i3c = look_up->speed; + + list_add_tail(&boardinfo->node, &master->boardinfo.i3c); + + return 0; +} + +static int i3c_acpi_master_get_resource(struct acpi_resource *ares, void *data) +{ + struct i3c_acpi_lookup *look_up = (struct i3c_acpi_lookup *)data; + struct acpi_resource_i2c_serialbus *sb; + bool ret; + + ret = i2c_acpi_get_i2c_resource(ares, &sb); + if (ret == true) { + look_up->slave_address = sb->slave_address; + look_up->speed = sb->connection_speed; + } + + return 0; +} + +static int i3c_acpi_master_get_slave_info(struct i3c_master_controller *master, + struct acpi_device *adev, struct i3c_acpi_lookup *look_up) +{ + struct list_head resource_list; + int ret; + + /* Look up for I2cSerialBus resource */ + INIT_LIST_HEAD(&resource_list); + ret = acpi_dev_get_resources(adev, &resource_list, + i3c_acpi_master_get_resource, look_up); + acpi_dev_free_resource_list(&resource_list); + + return ret; +} + + +static bool i3c_master_check_cpu_workaround(void) +{ + u32 cpu_implementor; + + cpu_implementor = read_cpuid_implementor(); + + if (cpu_implementor == ARM_CPU_IMP_PHYTIUM) + return true; + + return false; +} + +static int i3c_acpi_master_add_dev(struct acpi_device *adev, void *data) +{ + int ret = -1; + acpi_handle handle = adev->handle; + acpi_status status; + struct i3c_master_controller *master = (struct i3c_master_controller *)data; + u64 pid, lvr; + struct i3c_acpi_lookup look_up; + u32 mode_val; + + i3c_acpi_master_get_slave_info(master, adev, &look_up); + + status = acpi_evaluate_integer(handle, "_ADR", NULL, &pid); + + if (ACPI_SUCCESS(status)) + look_up.pid = pid; + else + look_up.pid = 0x202a0a0a0a0; + + if (i3c_master_check_cpu_workaround() == true) { + dev_info(&master->dev, "I3C ACPI SPECIAL KEYS\n"); + status = acpi_evaluate_integer(handle, "_LVR", NULL, &lvr); + + if (ACPI_SUCCESS(status)) + look_up.lvr = (u32)lvr; + else + look_up.lvr = 0; + + ret = i3c_master_acpi_get_params(handle, "i3c-mode", &mode_val); + if (!ret) { + if (!mode_val) + i3c_acpi_master_add_i2c_boardinfo(master, adev, &look_up); + else + i3c_acpi_master_add_i3c_boardinfo(master, adev, handle, &look_up); + } + } + return 0; +} + +static void i3c_acpi_register_devices(struct i3c_master_controller *master) +{ + struct acpi_device *adev = NULL; + + if (!has_acpi_companion(master->dev.parent)) + return; + + adev = acpi_fetch_acpi_dev(ACPI_HANDLE(master->dev.parent)); + if (adev) + acpi_dev_for_each_child(adev, i3c_acpi_master_add_dev, master); +} +#endif + static int i3c_master_i2c_adapter_xfer(struct i2c_adapter *adap, struct i2c_msg *xfers, int nxfers) { @@ -2311,7 +2592,7 @@ static int i3c_master_i2c_adapter_init(struct i3c_master_controller *master) adap->dev.parent = master->dev.parent; adap->owner = master->dev.parent->driver->owner; adap->algo = &i3c_master_i2c_algo; - strncpy(adap->name, dev_name(master->dev.parent), sizeof(adap->name)); + strscpy(adap->name, dev_name(master->dev.parent), sizeof(adap->name)); /* FIXME: Should we allow i3c masters to override these values? */ adap->timeout = 1000; @@ -2616,6 +2897,7 @@ int i3c_master_register(struct i3c_master_controller *master, master->dev.parent = parent; master->dev.of_node = of_node_get(parent->of_node); + master->dev.fwnode = parent->fwnode; master->dev.bus = &i3c_bus_type; master->dev.type = &i3c_masterdev_type; master->dev.release = i3c_masterdev_release; @@ -2634,7 +2916,9 @@ int i3c_master_register(struct i3c_master_controller *master, ret = of_populate_i3c_bus(master); if (ret) goto err_put_dev; - +#ifdef CONFIG_ACPI + i3c_acpi_register_devices(master); +#endif list_for_each_entry(i2cbi, &master->boardinfo.i2c, node) { switch (i2cbi->lvr & I3C_LVR_I2C_INDEX_MASK) { case I3C_LVR_I2C_INDEX(0): diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig index 90dee3ec5520979cf66c9d73c46b60dcc9aa9c98..f39122918b187da03c8077759f7990a7586cbefb 100644 --- a/drivers/i3c/master/Kconfig +++ b/drivers/i3c/master/Kconfig @@ -57,3 +57,12 @@ config MIPI_I3C_HCI This driver can also be built as a module. If so, the module will be called mipi-i3c-hci. + +config PHYTIUM_I3C_MASTER + tristate "Phytium I3C master driver" + depends on I3C + depends on HAS_IOMEM + depends on !(ALPHA || PARISC) + help + Enable this driver if you want to support phytium I3C master block. + diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile index 3e97960160bc85e5eaf2966ec0c3fae458c2711e..3819a4dab6acf47a54d2780accbdc2449dc07744 100644 --- a/drivers/i3c/master/Makefile +++ b/drivers/i3c/master/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_DW_I3C_MASTER) += dw-i3c-master.o obj-$(CONFIG_AST2600_I3C_MASTER) += ast2600-i3c-master.o obj-$(CONFIG_SVC_I3C_MASTER) += svc-i3c-master.o obj-$(CONFIG_MIPI_I3C_HCI) += mipi-i3c-hci/ +obj-$(CONFIG_PHYTIUM_I3C_MASTER) += i3c-master-phytium.o diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c index fa5aaaf4461814d61c8765afc93040fa9e82bc23..925deb7468b83acf8610e2744cd84c85594e4bf9 100644 --- a/drivers/i3c/master/i3c-master-cdns.c +++ b/drivers/i3c/master/i3c-master-cdns.c @@ -393,6 +393,7 @@ struct cdns_i3c_xfer { struct cdns_i3c_data { u8 thd_delay_ns; + u8 halt_disable; }; struct cdns_i3c_master { @@ -724,6 +725,19 @@ static int cdns_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000))) cdns_i3c_master_unqueue_xfer(master, xfer); + /*GETMXDS format 1 need retransmission*/ + if ((xfer->ret) && (cmd->id == I3C_CCC_GETMXDS)) { + if (cmd->dests[0].payload.len == 5) { + cmd->dests[0].payload.len = 2; + ccmd->rx_len = cmd->dests[0].payload.len; + ccmd->cmd0 &= 0xfff000fff; + ccmd->cmd0 |= CMD0_FIFO_PL_LEN(cmd->dests[0].payload.len); + cdns_i3c_master_queue_xfer(master, xfer); + if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000))) + cdns_i3c_master_unqueue_xfer(master, xfer); + } + } + ret = xfer->ret; cmd->err = cdns_i3c_cmd_get_err(&xfer->cmds[0]); cdns_i3c_master_free_xfer(xfer); @@ -1285,7 +1299,10 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m) * * We will issue ENTDAA afterwards from the threaded IRQ handler. */ - ctrl |= CTRL_HJ_ACK | CTRL_HJ_DISEC | CTRL_HALT_EN | CTRL_MCS_EN; + if (master->devdata->halt_disable) + ctrl |= CTRL_HJ_DISEC | CTRL_MCS_EN; + else + ctrl |= CTRL_HJ_ACK | CTRL_HJ_DISEC | CTRL_HALT_EN | CTRL_MCS_EN; /* * Configure data hold delay based on device-specific data. @@ -1556,10 +1573,17 @@ static void cdns_i3c_master_hj(struct work_struct *work) static struct cdns_i3c_data cdns_i3c_devdata = { .thd_delay_ns = 10, + .halt_disable = 0, +}; + +static struct cdns_i3c_data phytium_i3c_devdata = { + .thd_delay_ns = 10, + .halt_disable = 1, }; static const struct of_device_id cdns_i3c_master_of_ids[] = { { .compatible = "cdns,i3c-master", .data = &cdns_i3c_devdata }, + { .compatible = "phytium,cdns-i3c-master", .data = &phytium_i3c_devdata}, { /* sentinel */ }, }; @@ -1651,6 +1675,8 @@ static int cdns_i3c_master_probe(struct platform_device *pdev) if (ret) goto err_disable_sysclk; + writel(readl(master->regs + CTRL) | CTRL_HJ_ACK, master->regs + CTRL); + return 0; err_disable_sysclk: diff --git a/drivers/i3c/master/i3c-master-phytium.c b/drivers/i3c/master/i3c-master-phytium.c new file mode 100644 index 0000000000000000000000000000000000000000..50f3f6c9899511d3ba197959ef5c347bcedd8e43 --- /dev/null +++ b/drivers/i3c/master/i3c-master-phytium.c @@ -0,0 +1,1848 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Phytium Technology Co.,Ltd. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../i3c_master_acpi.h" + +#define DEV_ID 0x0 +#define DEV_ID_I3C_MASTER 0x5034 + +#define CONF_STATUS0 0x4 +#define CONF_STATUS0_CMDR_DEPTH(x) (4 << (((x) & GENMASK(31, 29)) >> 29)) +#define CONF_STATUS0_ECC_CHK BIT(28) +#define CONF_STATUS0_INTEG_CHK BIT(27) +#define CONF_STATUS0_CSR_DAP_CHK BIT(26) +#define CONF_STATUS0_TRANS_TOUT_CHK BIT(25) +#define CONF_STATUS0_PROT_FAULTS_CHK BIT(24) +#define CONF_STATUS0_GPO_NUM(x) (((x) & GENMASK(23, 16)) >> 16) +#define CONF_STATUS0_GPI_NUM(x) (((x) & GENMASK(15, 8)) >> 8) +#define CONF_STATUS0_IBIR_DEPTH(x) (4 << (((x) & GENMASK(7, 6)) >> 7)) +#define CONF_STATUS0_SUPPORTS_DDR BIT(5) +#define CONF_STATUS0_SEC_MASTER BIT(4) +#define CONF_STATUS0_DEVS_NUM(x) ((x) & GENMASK(3, 0)) + +#define CONF_STATUS1 0x8 +#define CONF_STATUS1_IBI_HW_RES(x) ((((x) & GENMASK(31, 28)) >> 28) + 1) +#define CONF_STATUS1_CMD_DEPTH(x) (4 << (((x) & GENMASK(27, 26)) >> 26)) +#define CONF_STATUS1_SLVDDR_RX_DEPTH(x) (8 << (((x) & GENMASK(25, 21)) >> 21)) +#define CONF_STATUS1_SLVDDR_TX_DEPTH(x) (8 << (((x) & GENMASK(20, 16)) >> 16)) +#define CONF_STATUS1_IBI_DEPTH(x) (2 << (((x) & GENMASK(12, 10)) >> 10)) +#define CONF_STATUS1_RX_DEPTH(x) (8 << (((x) & GENMASK(9, 5)) >> 5)) +#define CONF_STATUS1_TX_DEPTH(x) (8 << ((x) & GENMASK(4, 0))) + +#define REV_ID 0xc +#define REV_ID_VID(id) (((id) & GENMASK(31, 20)) >> 20) +#define REV_ID_PID(id) (((id) & GENMASK(19, 8)) >> 8) +#define REV_ID_REV_MAJOR(id) (((id) & GENMASK(7, 4)) >> 4) +#define REV_ID_REV_MINOR(id) ((id) & GENMASK(3, 0)) + +#define CTRL 0x10 +#define CTRL_DEV_EN BIT(31) +#define CTRL_HALT_EN BIT(30) +#define CTRL_MCS BIT(29) +#define CTRL_MCS_EN BIT(28) +#define CTRL_THD_DELAY(x) (((x) << 24) & GENMASK(25, 24)) +#define CTRL_HJ_DISEC BIT(8) +#define CTRL_MST_ACK BIT(7) +#define CTRL_HJ_ACK BIT(6) +#define CTRL_HJ_INIT BIT(5) +#define CTRL_MST_INIT BIT(4) +#define CTRL_AHDR_OPT BIT(3) +#define CTRL_PURE_BUS_MODE 0 +#define CTRL_MIXED_FAST_BUS_MODE 2 +#define CTRL_MIXED_SLOW_BUS_MODE 3 +#define CTRL_BUS_MODE_MASK GENMASK(1, 0) +#define THD_DELAY_MAX 3 + +#define PRESCL_CTRL0 0x14 +#define PRESCL_CTRL0_I2C(x) ((x) << 16) +#define PRESCL_CTRL0_I3C(x) (x) +#define PRESCL_CTRL0_MAX GENMASK(9, 0) + +#define PRESCL_CTRL1 0x18 +#define PRESCL_CTRL1_PP_LOW_MASK GENMASK(15, 8) +#define PRESCL_CTRL1_PP_LOW(x) ((x) << 8) +#define PRESCL_CTRL1_OD_LOW_MASK GENMASK(7, 0) +#define PRESCL_CTRL1_OD_LOW(x) (x) + +#define MST_IER 0x20 +#define MST_IDR 0x24 +#define MST_IMR 0x28 +#define MST_ICR 0x2c +#define MST_ISR 0x30 +#define MST_INT_HALTED BIT(18) +#define MST_INT_MR_DONE BIT(17) +#define MST_INT_IMM_COMP BIT(16) +#define MST_INT_TX_THR BIT(15) +#define MST_INT_TX_OVF BIT(14) +#define MST_INT_IBID_THR BIT(12) +#define MST_INT_IBID_UNF BIT(11) +#define MST_INT_IBIR_THR BIT(10) +#define MST_INT_IBIR_UNF BIT(9) +#define MST_INT_IBIR_OVF BIT(8) +#define MST_INT_RX_THR BIT(7) +#define MST_INT_RX_UNF BIT(6) +#define MST_INT_CMDD_EMP BIT(5) +#define MST_INT_CMDD_THR BIT(4) +#define MST_INT_CMDD_OVF BIT(3) +#define MST_INT_CMDR_THR BIT(2) +#define MST_INT_CMDR_UNF BIT(1) +#define MST_INT_CMDR_OVF BIT(0) + +#define MST_STATUS0 0x34 +#define MST_STATUS0_IDLE BIT(18) +#define MST_STATUS0_HALTED BIT(17) +#define MST_STATUS0_MASTER_MODE BIT(16) +#define MST_STATUS0_TX_FULL BIT(13) +#define MST_STATUS0_IBID_FULL BIT(12) +#define MST_STATUS0_IBIR_FULL BIT(11) +#define MST_STATUS0_RX_FULL BIT(10) +#define MST_STATUS0_CMDD_FULL BIT(9) +#define MST_STATUS0_CMDR_FULL BIT(8) +#define MST_STATUS0_TX_EMP BIT(5) +#define MST_STATUS0_IBID_EMP BIT(4) +#define MST_STATUS0_IBIR_EMP BIT(3) +#define MST_STATUS0_RX_EMP BIT(2) +#define MST_STATUS0_CMDD_EMP BIT(1) +#define MST_STATUS0_CMDR_EMP BIT(0) + +#define CMDR 0x38 +#define CMDR_NO_ERROR 0 +#define CMDR_DDR_PREAMBLE_ERROR 1 +#define CMDR_DDR_PARITY_ERROR 2 +#define CMDR_DDR_RX_FIFO_OVF 3 +#define CMDR_DDR_TX_FIFO_UNF 4 +#define CMDR_M0_ERROR 5 +#define CMDR_M1_ERROR 6 +#define CMDR_M2_ERROR 7 +#define CMDR_MST_ABORT 8 +#define CMDR_NACK_RESP 9 +#define CMDR_INVALID_DA 10 +#define CMDR_DDR_DROPPED 11 +#define CMDR_ERROR(x) (((x) & GENMASK(27, 24)) >> 24) +#define CMDR_XFER_BYTES(x) (((x) & GENMASK(19, 8)) >> 8) +#define CMDR_CMDID_HJACK_DISEC 0xfe +#define CMDR_CMDID_HJACK_ENTDAA 0xff +#define CMDR_CMDID(x) ((x) & GENMASK(7, 0)) + +#define IBIR 0x3c +#define IBIR_ACKED BIT(12) +#define IBIR_SLVID(x) (((x) & GENMASK(11, 8)) >> 8) +#define IBIR_ERROR BIT(7) +#define IBIR_XFER_BYTES(x) (((x) & GENMASK(6, 2)) >> 2) +#define IBIR_TYPE_IBI 0 +#define IBIR_TYPE_HJ 1 +#define IBIR_TYPE_MR 2 +#define IBIR_TYPE(x) ((x) & GENMASK(1, 0)) + +#define SLV_IER 0x40 +#define SLV_IDR 0x44 +#define SLV_IMR 0x48 +#define SLV_ICR 0x4c +#define SLV_ISR 0x50 +#define SLV_INT_TM BIT(20) +#define SLV_INT_ERROR BIT(19) +#define SLV_INT_EVENT_UP BIT(18) +#define SLV_INT_HJ_DONE BIT(17) +#define SLV_INT_MR_DONE BIT(16) +#define SLV_INT_DA_UPD BIT(15) +#define SLV_INT_SDR_FAIL BIT(14) +#define SLV_INT_DDR_FAIL BIT(13) +#define SLV_INT_M_RD_ABORT BIT(12) +#define SLV_INT_DDR_RX_THR BIT(11) +#define SLV_INT_DDR_TX_THR BIT(10) +#define SLV_INT_SDR_RX_THR BIT(9) +#define SLV_INT_SDR_TX_THR BIT(8) +#define SLV_INT_DDR_RX_UNF BIT(7) +#define SLV_INT_DDR_TX_OVF BIT(6) +#define SLV_INT_SDR_RX_UNF BIT(5) +#define SLV_INT_SDR_TX_OVF BIT(4) +#define SLV_INT_DDR_RD_COMP BIT(3) +#define SLV_INT_DDR_WR_COMP BIT(2) +#define SLV_INT_SDR_RD_COMP BIT(1) +#define SLV_INT_SDR_WR_COMP BIT(0) + +#define SLV_STATUS0 0x54 +#define SLV_STATUS0_REG_ADDR(s) (((s) & GENMASK(23, 16)) >> 16) +#define SLV_STATUS0_XFRD_BYTES(s) ((s) & GENMASK(15, 0)) + +#define SLV_STATUS1 0x58 +#define SLV_STATUS1_AS(s) (((s) & GENMASK(21, 20)) >> 20) +#define SLV_STATUS1_VEN_TM BIT(19) +#define SLV_STATUS1_HJ_DIS BIT(18) +#define SLV_STATUS1_MR_DIS BIT(17) +#define SLV_STATUS1_PROT_ERR BIT(16) +#define SLV_STATUS1_DA(x) (((s) & GENMASK(15, 9)) >> 9) +#define SLV_STATUS1_HAS_DA BIT(8) +#define SLV_STATUS1_DDR_RX_FULL BIT(7) +#define SLV_STATUS1_DDR_TX_FULL BIT(6) +#define SLV_STATUS1_DDR_RX_EMPTY BIT(5) +#define SLV_STATUS1_DDR_TX_EMPTY BIT(4) +#define SLV_STATUS1_SDR_RX_FULL BIT(3) +#define SLV_STATUS1_SDR_TX_FULL BIT(2) +#define SLV_STATUS1_SDR_RX_EMPTY BIT(1) +#define SLV_STATUS1_SDR_TX_EMPTY BIT(0) + +#define CMD0_FIFO 0x60 +#define CMD0_FIFO_IS_DDR BIT(31) +#define CMD0_FIFO_IS_CCC BIT(30) +#define CMD0_FIFO_BCH BIT(29) +#define XMIT_BURST_STATIC_SUBADDR 0 +#define XMIT_SINGLE_INC_SUBADDR 1 +#define XMIT_SINGLE_STATIC_SUBADDR 2 +#define XMIT_BURST_WITHOUT_SUBADDR 3 +#define CMD0_FIFO_PRIV_XMIT_MODE(m) ((m) << 27) +#define CMD0_FIFO_SBCA BIT(26) +#define CMD0_FIFO_RSBC BIT(25) +#define CMD0_FIFO_IS_10B BIT(24) +#define CMD0_FIFO_PL_LEN(l) ((l) << 12) +#define CMD0_FIFO_PL_LEN_MAX 4095 +#define CMD0_FIFO_DEV_ADDR(a) ((a) << 1) +#define CMD0_FIFO_RNW BIT(0) + +#define CMD1_FIFO 0x64 +#define CMD1_FIFO_CMDID(id) ((id) << 24) +#define CMD1_FIFO_CSRADDR(a) (a) +#define CMD1_FIFO_CCC(id) (id) + +#define TX_FIFO 0x68 + +#define IMD_CMD0 0x70 +#define IMD_CMD0_PL_LEN(l) ((l) << 12) +#define IMD_CMD0_DEV_ADDR(a) ((a) << 1) +#define IMD_CMD0_RNW BIT(0) + +#define IMD_CMD1 0x74 +#define IMD_CMD1_CCC(id) (id) + +#define IMD_DATA 0x78 +#define RX_FIFO 0x80 +#define IBI_DATA_FIFO 0x84 +#define SLV_DDR_TX_FIFO 0x88 +#define SLV_DDR_RX_FIFO 0x8c + +#define CMD_IBI_THR_CTRL 0x90 +#define IBIR_THR(t) ((t) << 24) +#define CMDR_THR(t) ((t) << 16) +#define IBI_THR(t) ((t) << 8) +#define CMD_THR(t) (t) + +#define TX_RX_THR_CTRL 0x94 +#define RX_THR(t) ((t) << 16) +#define TX_THR(t) (t) + +#define SLV_DDR_TX_RX_THR_CTRL 0x98 +#define SLV_DDR_RX_THR(t) ((t) << 16) +#define SLV_DDR_TX_THR(t) (t) + +#define FLUSH_CTRL 0x9c +#define FLUSH_IBI_RESP BIT(23) +#define FLUSH_CMD_RESP BIT(22) +#define FLUSH_SLV_DDR_RX_FIFO BIT(22) +#define FLUSH_SLV_DDR_TX_FIFO BIT(21) +#define FLUSH_IMM_FIFO BIT(20) +#define FLUSH_IBI_FIFO BIT(19) +#define FLUSH_RX_FIFO BIT(18) +#define FLUSH_TX_FIFO BIT(17) +#define FLUSH_CMD_FIFO BIT(16) + +#define TTO_PRESCL_CTRL0 0xb0 +#define TTO_PRESCL_CTRL0_DIVB(x) ((x) << 16) +#define TTO_PRESCL_CTRL0_DIVA(x) (x) + +#define TTO_PRESCL_CTRL1 0xb4 +#define TTO_PRESCL_CTRL1_DIVB(x) ((x) << 16) +#define TTO_PRESCL_CTRL1_DIVA(x) (x) + +#define DEVS_CTRL 0xb8 +#define DEVS_CTRL_DEV_CLR_SHIFT 16 +#define DEVS_CTRL_DEV_CLR_ALL GENMASK(31, 16) +#define DEVS_CTRL_DEV_CLR(dev) BIT(16 + (dev)) +#define DEVS_CTRL_DEV_ACTIVE(dev) BIT(dev) +#define DEVS_CTRL_DEVS_ACTIVE_MASK GENMASK(15, 0) +#define MAX_DEVS 16 + +#define DEV_ID_RR0(d) (0xc0 + ((d) * 0x10)) +#define DEV_ID_RR0_LVR_EXT_ADDR BIT(11) +#define DEV_ID_RR0_HDR_CAP BIT(10) +#define DEV_ID_RR0_IS_I3C BIT(9) +#define DEV_ID_RR0_DEV_ADDR_MASK (GENMASK(6, 0) | GENMASK(15, 13)) +#define DEV_ID_RR0_SET_DEV_ADDR(a) (((a) & GENMASK(6, 0)) | \ + (((a) & GENMASK(9, 7)) << 6)) +#define DEV_ID_RR0_GET_DEV_ADDR(x) ((((x) >> 1) & GENMASK(6, 0)) | \ + (((x) >> 6) & GENMASK(9, 7))) + +#define DEV_ID_RR1(d) (0xc4 + ((d) * 0x10)) +#define DEV_ID_RR1_PID_MSB(pid) (pid) + +#define DEV_ID_RR2(d) (0xc8 + ((d) * 0x10)) +#define DEV_ID_RR2_PID_LSB(pid) ((pid) << 16) +#define DEV_ID_RR2_BCR(bcr) ((bcr) << 8) +#define DEV_ID_RR2_DCR(dcr) (dcr) +#define DEV_ID_RR2_LVR(lvr) (lvr) + +#define SIR_MAP(x) (0x180 + ((x) * 4)) +#define SIR_MAP_DEV_REG(d) SIR_MAP((d) / 2) +#define SIR_MAP_DEV_SHIFT(d, fs) ((fs) + (((d) % 2) ? 16 : 0)) +#define SIR_MAP_DEV_CONF_MASK(d) (GENMASK(15, 0) << (((d) % 2) ? 16 : 0)) +#define SIR_MAP_DEV_CONF(d, c) ((c) << (((d) % 2) ? 16 : 0)) +#define DEV_ROLE_SLAVE 0 +#define DEV_ROLE_MASTER 1 +#define SIR_MAP_DEV_ROLE(role) ((role) << 14) +#define SIR_MAP_DEV_SLOW BIT(13) +#define SIR_MAP_DEV_PL(l) ((l) << 8) +#define SIR_MAP_PL_MAX GENMASK(4, 0) +#define SIR_MAP_DEV_DA(a) ((a) << 1) +#define SIR_MAP_DEV_ACK BIT(0) + +#define GPIR_WORD(x) (0x200 + ((x) * 4)) +#define GPI_REG(val, id) \ + (((val) >> (((id) % 4) * 8)) & GENMASK(7, 0)) + +#define GPOR_WORD(x) (0x220 + ((x) * 4)) +#define GPO_REG(val, id) \ + (((val) >> (((id) % 4) * 8)) & GENMASK(7, 0)) + +#define ASF_INT_STATUS 0x300 +#define ASF_INT_RAW_STATUS 0x304 +#define ASF_INT_MASK 0x308 +#define ASF_INT_TEST 0x30c +#define ASF_INT_FATAL_SELECT 0x310 +#define ASF_INTEGRITY_ERR BIT(6) +#define ASF_PROTOCOL_ERR BIT(5) +#define ASF_TRANS_TIMEOUT_ERR BIT(4) +#define ASF_CSR_ERR BIT(3) +#define ASF_DAP_ERR BIT(2) +#define ASF_SRAM_UNCORR_ERR BIT(1) +#define ASF_SRAM_CORR_ERR BIT(0) + +#define ASF_SRAM_CORR_FAULT_STATUS 0x320 +#define ASF_SRAM_UNCORR_FAULT_STATUS 0x324 +#define ASF_SRAM_CORR_FAULT_INSTANCE(x) ((x) >> 24) +#define ASF_SRAM_CORR_FAULT_ADDR(x) ((x) & GENMASK(23, 0)) + +#define ASF_SRAM_FAULT_STATS 0x328 +#define ASF_SRAM_FAULT_UNCORR_STATS(x) ((x) >> 16) +#define ASF_SRAM_FAULT_CORR_STATS(x) ((x) & GENMASK(15, 0)) + +#define ASF_TRANS_TOUT_CTRL 0x330 +#define ASF_TRANS_TOUT_EN BIT(31) +#define ASF_TRANS_TOUT_VAL(x) (x) + +#define ASF_TRANS_TOUT_FAULT_MASK 0x334 +#define ASF_TRANS_TOUT_FAULT_STATUS 0x338 +#define ASF_TRANS_TOUT_FAULT_APB BIT(3) +#define ASF_TRANS_TOUT_FAULT_SCL_LOW BIT(2) +#define ASF_TRANS_TOUT_FAULT_SCL_HIGH BIT(1) +#define ASF_TRANS_TOUT_FAULT_FSCL_HIGH BIT(0) + +#define ASF_PROTO_FAULT_MASK 0x340 +#define ASF_PROTO_FAULT_STATUS 0x344 +#define ASF_PROTO_FAULT_SLVSDR_RD_ABORT BIT(31) +#define ASF_PROTO_FAULT_SLVDDR_FAIL BIT(30) +#define ASF_PROTO_FAULT_S(x) BIT(16 + (x)) +#define ASF_PROTO_FAULT_MSTSDR_RD_ABORT BIT(15) +#define ASF_PROTO_FAULT_MSTDDR_FAIL BIT(14) +#define ASF_PROTO_FAULT_M(x) BIT(x) + +#define I3C_CONTROL_DEFAULT_I2C_SCL (1000000) +#define I3C_CONTROL_DEFAULT_I3C_SCL (1000000) +#define PHYTIUM_I3C_DEV_MAX_NUM (12) +#define PHYTIUM_I3C_CMDR_MAX_TIMES (32) + +struct phytium_i3c_master_caps { + u32 cmdfifodepth; + u32 cmdrfifodepth; + u32 txfifodepth; + u32 rxfifodepth; + u32 ibirfifodepth; +}; + +struct phytium_i3c_cmd { + u32 cmd0; + u32 cmd1; + u32 tx_len; + const void *tx_buf; + u32 rx_len; + void *rx_buf; + u32 error; +}; + +struct phytium_i3c_xfer { + struct list_head node; + struct completion comp; + int ret; + unsigned int ncmds; + struct phytium_i3c_cmd cmds[]; +}; + +struct phytium_i3c_data { + u8 thd_delay_ns; +}; + +struct phytium_i3c_slave_dev { + u32 dev_rr0; + u32 dev_rr1; + u32 dev_rr2; +}; + +struct phytium_i3c_master { + struct work_struct hj_work; + struct i3c_master_controller base; + u32 free_rr_slots; + unsigned int maxdevs; + struct { + unsigned int num_slots; + struct i3c_dev_desc **slots; + spinlock_t lock; + } ibi; + struct { + struct list_head list; + struct phytium_i3c_xfer *cur; + spinlock_t lock; + } xferqueue; + void __iomem *regs; + struct clk *sysclk; + struct clk *pclk; + u32 sysclk_rate; + u32 prescl0; + u32 prescl1; + u32 ctrl_thd_del; + u32 ctrl_info; + u32 dev_valid; + struct device *dev; + struct phytium_i3c_master_caps caps; + unsigned long i3c_scl_lim; + const struct phytium_i3c_data *devdata; + struct phytium_i3c_slave_dev devinfo[PHYTIUM_I3C_DEV_MAX_NUM]; +}; + +static inline struct phytium_i3c_master * +to_phytium_i3c_master(struct i3c_master_controller *master) +{ + return container_of(master, struct phytium_i3c_master, base); +} + +static void phytium_i3c_master_wr_to_tx_fifo(struct phytium_i3c_master *master, + const u8 *bytes, int nbytes) +{ + writesl(master->regs + TX_FIFO, bytes, nbytes / 4); + if (nbytes & 3) { + u32 tmp = 0; + + memcpy(&tmp, bytes + (nbytes & ~3), nbytes & 3); + writesl(master->regs + TX_FIFO, &tmp, 1); + } +} + +static void phytium_i3c_master_rd_from_rx_fifo(struct phytium_i3c_master *master, + u8 *bytes, int nbytes) +{ + readsl(master->regs + RX_FIFO, bytes, nbytes / 4); + if (nbytes & 3) { + u32 tmp; + + readsl(master->regs + RX_FIFO, &tmp, 1); + memcpy(bytes + (nbytes & ~3), &tmp, nbytes & 3); + } +} + +static bool phytium_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m, + const struct i3c_ccc_cmd *cmd) +{ + if (cmd->ndests > 1) + return false; + + switch (cmd->id) { + case I3C_CCC_ENEC(true): + case I3C_CCC_ENEC(false): + case I3C_CCC_DISEC(true): + case I3C_CCC_DISEC(false): + case I3C_CCC_ENTAS(0, true): + case I3C_CCC_ENTAS(0, false): + case I3C_CCC_RSTDAA(true): + case I3C_CCC_RSTDAA(false): + case I3C_CCC_ENTDAA: + case I3C_CCC_SETMWL(true): + case I3C_CCC_SETMWL(false): + case I3C_CCC_SETMRL(true): + case I3C_CCC_SETMRL(false): + case I3C_CCC_DEFSLVS: + case I3C_CCC_ENTHDR(0): + case I3C_CCC_SETDASA: + case I3C_CCC_SETNEWDA: + case I3C_CCC_GETMWL: + case I3C_CCC_GETMRL: + case I3C_CCC_GETPID: + case I3C_CCC_GETBCR: + case I3C_CCC_GETDCR: + case I3C_CCC_GETSTATUS: + case I3C_CCC_GETACCMST: + case I3C_CCC_GETMXDS: + case I3C_CCC_GETHDRCAP: + return true; + default: + break; + } + + return false; +} + +static int phytium_i3c_master_disable(struct phytium_i3c_master *master) +{ + u32 status; + + writel(readl(master->regs + CTRL) & ~CTRL_DEV_EN, master->regs + CTRL); + + return readl_poll_timeout(master->regs + MST_STATUS0, status, + status & MST_STATUS0_IDLE, 10, 1000000); +} + +static void phytium_i3c_master_enable(struct phytium_i3c_master *master) +{ + writel(readl(master->regs + CTRL) | CTRL_DEV_EN, master->regs + CTRL); +} + +static struct phytium_i3c_xfer * +phytium_i3c_master_alloc_xfer(struct phytium_i3c_master *master, unsigned int ncmds) +{ + struct phytium_i3c_xfer *xfer; + + xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL); + if (!xfer) + return NULL; + + INIT_LIST_HEAD(&xfer->node); + xfer->ncmds = ncmds; + xfer->ret = -ETIMEDOUT; + + return xfer; +} + +static void phytium_i3c_master_free_xfer(struct phytium_i3c_xfer *xfer) +{ + kfree(xfer); +} + +static void phytium_i3c_master_start_xfer_locked(struct phytium_i3c_master *master) +{ + struct phytium_i3c_xfer *xfer = master->xferqueue.cur; + unsigned int i; + + if (!xfer) + return; + + phytium_i3c_master_enable(master); + + writel(MST_INT_CMDD_EMP, master->regs + MST_ICR); + for (i = 0; i < xfer->ncmds; i++) { + struct phytium_i3c_cmd *cmd = &xfer->cmds[i]; + + phytium_i3c_master_wr_to_tx_fifo(master, cmd->tx_buf, + cmd->tx_len); + } + for (i = 0; i < xfer->ncmds; i++) { + struct phytium_i3c_cmd *cmd = &xfer->cmds[i]; + + writel(cmd->cmd1 | CMD1_FIFO_CMDID(i), + master->regs + CMD1_FIFO); + writel(cmd->cmd0, master->regs + CMD0_FIFO); + } + + writel(readl(master->regs + CTRL) | CTRL_MCS, + master->regs + CTRL); + writel(MST_INT_CMDD_EMP, master->regs + MST_IER); +} + +static void phytium_i3c_master_end_xfer_locked(struct phytium_i3c_master *master, + u32 isr) +{ + struct phytium_i3c_xfer *xfer = master->xferqueue.cur; + int i, ret = 0; + u32 status0; + + if (!xfer) + return; + + if (!(isr & MST_INT_CMDD_EMP)) + return; + + writel(MST_INT_CMDD_EMP, master->regs + MST_IDR); + + for (status0 = readl(master->regs + MST_STATUS0); + !(status0 & MST_STATUS0_CMDR_EMP); + status0 = readl(master->regs + MST_STATUS0)) { + struct phytium_i3c_cmd *cmd; + u32 cmdr, rx_len, id; + + cmdr = readl(master->regs + CMDR); + id = CMDR_CMDID(cmdr); + if (id == CMDR_CMDID_HJACK_DISEC || + id == CMDR_CMDID_HJACK_ENTDAA || + WARN_ON(id >= xfer->ncmds)) + continue; + + cmd = &xfer->cmds[CMDR_CMDID(cmdr)]; + rx_len = min_t(u32, CMDR_XFER_BYTES(cmdr), cmd->rx_len); + phytium_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len); + cmd->error = CMDR_ERROR(cmdr); + } + + for (i = 0; i < xfer->ncmds; i++) { + switch (xfer->cmds[i].error) { + case CMDR_NO_ERROR: + break; + + case CMDR_DDR_PREAMBLE_ERROR: + case CMDR_DDR_PARITY_ERROR: + case CMDR_M0_ERROR: + case CMDR_M1_ERROR: + case CMDR_M2_ERROR: + case CMDR_MST_ABORT: + case CMDR_NACK_RESP: + case CMDR_DDR_DROPPED: + ret = -EIO; + break; + + case CMDR_DDR_RX_FIFO_OVF: + case CMDR_DDR_TX_FIFO_UNF: + ret = -ENOSPC; + break; + + case CMDR_INVALID_DA: + default: + ret = -EINVAL; + break; + } + } + + xfer->ret = ret; + complete(&xfer->comp); + + xfer = list_first_entry_or_null(&master->xferqueue.list, + struct phytium_i3c_xfer, node); + if (xfer) + list_del_init(&xfer->node); + + master->xferqueue.cur = xfer; + phytium_i3c_master_start_xfer_locked(master); +} + +static void phytium_i3c_master_queue_xfer(struct phytium_i3c_master *master, + struct phytium_i3c_xfer *xfer) +{ + unsigned long flags; + + init_completion(&xfer->comp); + spin_lock_irqsave(&master->xferqueue.lock, flags); + if (master->xferqueue.cur) { + list_add_tail(&xfer->node, &master->xferqueue.list); + } else { + master->xferqueue.cur = xfer; + phytium_i3c_master_start_xfer_locked(master); + } + spin_unlock_irqrestore(&master->xferqueue.lock, flags); +} + +static void phytium_i3c_master_unqueue_xfer(struct phytium_i3c_master *master, + struct phytium_i3c_xfer *xfer) +{ + unsigned long flags; + + spin_lock_irqsave(&master->xferqueue.lock, flags); + if (master->xferqueue.cur == xfer) { + u32 status; + + writel(readl(master->regs + CTRL) & ~CTRL_DEV_EN, + master->regs + CTRL); + readl_poll_timeout_atomic(master->regs + MST_STATUS0, status, + status & MST_STATUS0_IDLE, 10, + 1000000); + master->xferqueue.cur = NULL; + writel(FLUSH_RX_FIFO | FLUSH_TX_FIFO | FLUSH_CMD_FIFO | + FLUSH_CMD_RESP, + master->regs + FLUSH_CTRL); + writel(MST_INT_CMDD_EMP, master->regs + MST_IDR); + writel(readl(master->regs + CTRL) | CTRL_DEV_EN, + master->regs + CTRL); + } else { + list_del_init(&xfer->node); + } + spin_unlock_irqrestore(&master->xferqueue.lock, flags); +} + +static enum i3c_error_code phytium_i3c_cmd_get_err(struct phytium_i3c_cmd *cmd) +{ + switch (cmd->error) { + case CMDR_M0_ERROR: + return I3C_ERROR_M0; + + case CMDR_M1_ERROR: + return I3C_ERROR_M1; + + case CMDR_M2_ERROR: + case CMDR_NACK_RESP: + return I3C_ERROR_M2; + + default: + break; + } + + return I3C_ERROR_UNKNOWN; +} + +static int phytium_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, + struct i3c_ccc_cmd *cmd) +{ + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + struct phytium_i3c_xfer *xfer; + struct phytium_i3c_cmd *ccmd; + int ret; + + xfer = phytium_i3c_master_alloc_xfer(master, 1); + if (!xfer) + return -ENOMEM; + + ccmd = xfer->cmds; + ccmd->cmd1 = CMD1_FIFO_CCC(cmd->id); + ccmd->cmd0 = CMD0_FIFO_IS_CCC | + CMD0_FIFO_PL_LEN(cmd->dests[0].payload.len); + + if (cmd->id & I3C_CCC_DIRECT) + ccmd->cmd0 |= CMD0_FIFO_DEV_ADDR(cmd->dests[0].addr); + + if (cmd->rnw) { + ccmd->cmd0 |= CMD0_FIFO_RNW; + ccmd->rx_buf = cmd->dests[0].payload.data; + ccmd->rx_len = cmd->dests[0].payload.len; + } else { + ccmd->tx_buf = cmd->dests[0].payload.data; + ccmd->tx_len = cmd->dests[0].payload.len; + } + + phytium_i3c_master_queue_xfer(master, xfer); + if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000))) + phytium_i3c_master_unqueue_xfer(master, xfer); + + /*GETMXDS format 1 need retransmission*/ + if ((xfer->ret) && (cmd->id == I3C_CCC_GETMXDS)) { + if (cmd->dests[0].payload.len == 5) { + cmd->dests[0].payload.len = 2; + ccmd->rx_len = cmd->dests[0].payload.len; + ccmd->cmd0 &= 0xfff000fff; + ccmd->cmd0 |= CMD0_FIFO_PL_LEN(cmd->dests[0].payload.len); + phytium_i3c_master_queue_xfer(master, xfer); + if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000))) + phytium_i3c_master_unqueue_xfer(master, xfer); + } + } + ret = xfer->ret; + cmd->err = phytium_i3c_cmd_get_err(&xfer->cmds[0]); + phytium_i3c_master_free_xfer(xfer); + + return ret; +} + +static int phytium_i3c_master_priv_xfers(struct i3c_dev_desc *dev, + struct i3c_priv_xfer *xfers, + int nxfers) +{ + struct i3c_master_controller *m = i3c_dev_get_master(dev); + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + int txslots = 0, rxslots = 0, i, ret; + struct phytium_i3c_xfer *phytium_xfer; + + for (i = 0; i < nxfers; i++) { + if (xfers[i].len > CMD0_FIFO_PL_LEN_MAX) + return -EOPNOTSUPP; + } + + if (!nxfers) + return 0; + + if (nxfers > master->caps.cmdfifodepth || + nxfers > master->caps.cmdrfifodepth) + return -EOPNOTSUPP; + + /* + * First make sure that all transactions (block of transfers separated + * by a STOP marker) fit in the FIFOs. + */ + for (i = 0; i < nxfers; i++) { + if (xfers[i].rnw) + rxslots += DIV_ROUND_UP(xfers[i].len, 4); + else + txslots += DIV_ROUND_UP(xfers[i].len, 4); + } + + if (rxslots > master->caps.rxfifodepth || + txslots > master->caps.txfifodepth) + return -EOPNOTSUPP; + + phytium_xfer = phytium_i3c_master_alloc_xfer(master, nxfers); + if (!phytium_xfer) + return -ENOMEM; + + for (i = 0; i < nxfers; i++) { + struct phytium_i3c_cmd *ccmd = &phytium_xfer->cmds[i]; + u32 pl_len = xfers[i].len; + + ccmd->cmd0 = CMD0_FIFO_DEV_ADDR(dev->info.dyn_addr) | + CMD0_FIFO_PRIV_XMIT_MODE(XMIT_BURST_WITHOUT_SUBADDR); + + if (xfers[i].rnw) { + ccmd->cmd0 |= CMD0_FIFO_RNW; + ccmd->rx_buf = xfers[i].data.in; + ccmd->rx_len = xfers[i].len; + pl_len++; + } else { + ccmd->tx_buf = xfers[i].data.out; + ccmd->tx_len = xfers[i].len; + } + + ccmd->cmd0 |= CMD0_FIFO_PL_LEN(pl_len); + + if (i < nxfers - 1) + ccmd->cmd0 |= CMD0_FIFO_RSBC; + + if (!i) + ccmd->cmd0 |= CMD0_FIFO_BCH; + } + + phytium_i3c_master_queue_xfer(master, phytium_xfer); + if (!wait_for_completion_timeout(&phytium_xfer->comp, + msecs_to_jiffies(1000))) + phytium_i3c_master_unqueue_xfer(master, phytium_xfer); + + ret = phytium_xfer->ret; + + for (i = 0; i < nxfers; i++) + xfers[i].err = phytium_i3c_cmd_get_err(&phytium_xfer->cmds[i]); + + phytium_i3c_master_free_xfer(phytium_xfer); + + return ret; +} + +static int phytium_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, + const struct i2c_msg *xfers, int nxfers) +{ + struct i3c_master_controller *m = i2c_dev_get_master(dev); + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + unsigned int nrxwords = 0, ntxwords = 0; + struct phytium_i3c_xfer *xfer; + int i, ret = 0; + + if (nxfers > master->caps.cmdfifodepth) + return -EOPNOTSUPP; + + for (i = 0; i < nxfers; i++) { + if (xfers[i].len > CMD0_FIFO_PL_LEN_MAX) + return -EOPNOTSUPP; + + if (xfers[i].flags & I2C_M_RD) + nrxwords += DIV_ROUND_UP(xfers[i].len, 4); + else + ntxwords += DIV_ROUND_UP(xfers[i].len, 4); + } + + if (ntxwords > master->caps.txfifodepth || + nrxwords > master->caps.rxfifodepth) + return -EOPNOTSUPP; + + xfer = phytium_i3c_master_alloc_xfer(master, nxfers); + if (!xfer) + return -ENOMEM; + + for (i = 0; i < nxfers; i++) { + struct phytium_i3c_cmd *ccmd = &xfer->cmds[i]; + + ccmd->cmd0 = CMD0_FIFO_DEV_ADDR(xfers[i].addr) | + CMD0_FIFO_PL_LEN(xfers[i].len) | + CMD0_FIFO_PRIV_XMIT_MODE(XMIT_BURST_WITHOUT_SUBADDR); + + if (xfers[i].flags & I2C_M_TEN) + ccmd->cmd0 |= CMD0_FIFO_IS_10B; + + if (xfers[i].flags & I2C_M_RD) { + ccmd->cmd0 |= CMD0_FIFO_RNW; + ccmd->rx_buf = xfers[i].buf; + ccmd->rx_len = xfers[i].len; + } else { + ccmd->tx_buf = xfers[i].buf; + ccmd->tx_len = xfers[i].len; + } + } + + phytium_i3c_master_queue_xfer(master, xfer); + if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000))) + phytium_i3c_master_unqueue_xfer(master, xfer); + + ret = xfer->ret; + phytium_i3c_master_free_xfer(xfer); + + return ret; +} + +struct phytium_i3c_i2c_dev_data { + u16 id; + s16 ibi; + struct i3c_generic_ibi_pool *ibi_pool; +}; + +static u32 prepare_rr0_dev_address(u32 addr) +{ + u32 ret = (addr << 1) & 0xff; + + /* RR0[7:1] = addr[6:0] */ + ret |= (addr & GENMASK(6, 0)) << 1; + + /* RR0[15:13] = addr[9:7] */ + ret |= (addr & GENMASK(9, 7)) << 6; + + /* RR0[0] = ~XOR(addr[6:0]) */ + if (!(hweight8(addr & 0x7f) & 1)) + ret |= 1; + + return ret; +} + +static void phytium_i3c_master_upd_i3c_addr(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *m = i3c_dev_get_master(dev); + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + struct phytium_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); + u32 rr; + + rr = prepare_rr0_dev_address(dev->info.dyn_addr ? + dev->info.dyn_addr : + dev->info.static_addr); + writel(DEV_ID_RR0_IS_I3C | rr, master->regs + DEV_ID_RR0(data->id)); +} + +static int phytium_i3c_master_get_rr_slot(struct phytium_i3c_master *master, + u8 dyn_addr) +{ + unsigned long activedevs; + u32 rr; + int i; + + if (!dyn_addr) { + if (!master->free_rr_slots) + return -ENOSPC; + + return ffs(master->free_rr_slots) - 1; + } + + activedevs = readl(master->regs + DEVS_CTRL) & DEVS_CTRL_DEVS_ACTIVE_MASK; + activedevs &= ~BIT(0); + + for_each_set_bit(i, &activedevs, master->maxdevs + 1) { + rr = readl(master->regs + DEV_ID_RR0(i)); + if (!(rr & DEV_ID_RR0_IS_I3C) || + DEV_ID_RR0_GET_DEV_ADDR(rr) != dyn_addr) + continue; + + return i; + } + + return -EINVAL; +} + +static int phytium_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, + u8 old_dyn_addr) +{ + phytium_i3c_master_upd_i3c_addr(dev); + + return 0; +} + +static int phytium_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *m = i3c_dev_get_master(dev); + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + struct phytium_i3c_i2c_dev_data *data; + int slot; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + slot = phytium_i3c_master_get_rr_slot(master, dev->info.dyn_addr); + if (slot < 0) { + kfree(data); + return slot; + } + + data->ibi = -1; + data->id = slot; + i3c_dev_set_master_data(dev, data); + master->free_rr_slots &= ~BIT(slot); + + if (!dev->info.dyn_addr) { + phytium_i3c_master_upd_i3c_addr(dev); + writel(readl(master->regs + DEVS_CTRL) | + DEVS_CTRL_DEV_ACTIVE(data->id), + master->regs + DEVS_CTRL); + } + + return 0; +} + +static void phytium_i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *m = i3c_dev_get_master(dev); + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + struct phytium_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); + + writel(readl(master->regs + DEVS_CTRL) | + DEVS_CTRL_DEV_CLR(data->id), + master->regs + DEVS_CTRL); + + i3c_dev_set_master_data(dev, NULL); + master->free_rr_slots |= BIT(data->id); + kfree(data); +} + +static int phytium_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev) +{ + struct i3c_master_controller *m = i2c_dev_get_master(dev); + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + struct phytium_i3c_i2c_dev_data *data; + int slot; + + slot = phytium_i3c_master_get_rr_slot(master, 0); + if (slot < 0) + return slot; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->id = slot; + master->free_rr_slots &= ~BIT(slot); + i2c_dev_set_master_data(dev, data); + + writel(prepare_rr0_dev_address(dev->addr), + master->regs + DEV_ID_RR0(data->id)); + writel(dev->lvr, master->regs + DEV_ID_RR2(data->id)); + writel(readl(master->regs + DEVS_CTRL) | + DEVS_CTRL_DEV_ACTIVE(data->id), + master->regs + DEVS_CTRL); + + return 0; +} + +static void phytium_i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev) +{ + struct i3c_master_controller *m = i2c_dev_get_master(dev); + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + struct phytium_i3c_i2c_dev_data *data = i2c_dev_get_master_data(dev); + + writel(readl(master->regs + DEVS_CTRL) | + DEVS_CTRL_DEV_CLR(data->id), + master->regs + DEVS_CTRL); + master->free_rr_slots |= BIT(data->id); + + i2c_dev_set_master_data(dev, NULL); + kfree(data); +} + +static void phytium_i3c_master_bus_cleanup(struct i3c_master_controller *m) +{ + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + + phytium_i3c_master_disable(master); +} + +static void phytium_i3c_master_dev_rr_to_info(struct phytium_i3c_master *master, + unsigned int slot, + struct i3c_device_info *info) +{ + u32 rr; + + memset(info, 0, sizeof(*info)); + rr = readl(master->regs + DEV_ID_RR0(slot)); + info->dyn_addr = DEV_ID_RR0_GET_DEV_ADDR(rr); + rr = readl(master->regs + DEV_ID_RR2(slot)); + info->dcr = rr; + info->bcr = rr >> 8; + info->pid = rr >> 16; + info->pid |= (u64)readl(master->regs + DEV_ID_RR1(slot)) << 16; +} + +static void phytium_i3c_master_upd_i3c_scl_lim(struct phytium_i3c_master *master) +{ + struct i3c_master_controller *m = &master->base; + unsigned long i3c_lim_period, pres_step, ncycles; + struct i3c_bus *bus = i3c_master_get_bus(m); + unsigned long new_i3c_scl_lim = 0; + struct i3c_dev_desc *dev; + u32 prescl1, ctrl; + + i3c_bus_for_each_i3cdev(bus, dev) { + unsigned long max_fscl; + + max_fscl = max(I3C_CCC_MAX_SDR_FSCL(dev->info.max_read_ds), + I3C_CCC_MAX_SDR_FSCL(dev->info.max_write_ds)); + switch (max_fscl) { + case I3C_SDR1_FSCL_8MHZ: + max_fscl = 8000000; + break; + case I3C_SDR2_FSCL_6MHZ: + max_fscl = 6000000; + break; + case I3C_SDR3_FSCL_4MHZ: + max_fscl = 4000000; + break; + case I3C_SDR4_FSCL_2MHZ: + max_fscl = 2000000; + break; + case I3C_SDR0_FSCL_MAX: + default: + max_fscl = 0; + break; + } + + if (max_fscl && + (new_i3c_scl_lim > max_fscl || !new_i3c_scl_lim)) + new_i3c_scl_lim = max_fscl; + } + + /* Only update PRESCL_CTRL1 if the I3C SCL limitation has changed. */ + if (new_i3c_scl_lim == master->i3c_scl_lim) + return; + master->i3c_scl_lim = new_i3c_scl_lim; + if (!new_i3c_scl_lim) + return; + pres_step = 1000000000UL / (bus->scl_rate.i3c * 4); + + /* Configure PP_LOW to meet I3C slave limitations. */ + prescl1 = readl(master->regs + PRESCL_CTRL1) & + ~PRESCL_CTRL1_PP_LOW_MASK; + ctrl = readl(master->regs + CTRL); + + i3c_lim_period = DIV_ROUND_UP(1000000000, master->i3c_scl_lim); + ncycles = DIV_ROUND_UP(i3c_lim_period, pres_step); + if (ncycles < 4) + ncycles = 0; + else + ncycles -= 4; + + prescl1 |= PRESCL_CTRL1_PP_LOW(ncycles); + + /* Disable I3C master before updating PRESCL_CTRL1. */ + if (ctrl & CTRL_DEV_EN) + phytium_i3c_master_disable(master); + + writel(prescl1, master->regs + PRESCL_CTRL1); + + if (ctrl & CTRL_DEV_EN) + phytium_i3c_master_enable(master); +} + +static int phytium_i3c_master_do_daa(struct i3c_master_controller *m) +{ + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + unsigned long olddevs, newdevs; + int ret, slot; + u8 addrs[MAX_DEVS] = { }; + u8 last_addr = 0; + + olddevs = readl(master->regs + DEVS_CTRL) & DEVS_CTRL_DEVS_ACTIVE_MASK; + olddevs |= BIT(0); + + /* Prepare RR slots before launching DAA. */ + for_each_clear_bit(slot, &olddevs, master->maxdevs + 1) { + ret = i3c_master_get_free_addr(m, last_addr + 1); + if (ret < 0) + return -ENOSPC; + + last_addr = ret; + addrs[slot] = last_addr; + writel(prepare_rr0_dev_address(last_addr) | DEV_ID_RR0_IS_I3C, + master->regs + DEV_ID_RR0(slot)); + writel(0, master->regs + DEV_ID_RR1(slot)); + writel(0, master->regs + DEV_ID_RR2(slot)); + } + + ret = i3c_master_entdaa_locked(&master->base); + if (ret && ret != I3C_ERROR_M2) + return ret; + + newdevs = readl(master->regs + DEVS_CTRL) & DEVS_CTRL_DEVS_ACTIVE_MASK; + newdevs &= ~olddevs; + + /* + * Clear all retaining registers filled during DAA. We already + * have the addressed assigned to them in the addrs array. + */ + for_each_set_bit(slot, &newdevs, master->maxdevs + 1) + i3c_master_add_i3c_dev_locked(m, addrs[slot]); + + /* + * Clear slots that ended up not being used. Can be caused by I3C + * device creation failure or when the I3C device was already known + * by the system but with a different address (in this case the device + * already has a slot and does not need a new one). + */ + writel(readl(master->regs + DEVS_CTRL) | + master->free_rr_slots << DEVS_CTRL_DEV_CLR_SHIFT, + master->regs + DEVS_CTRL); + i3c_master_defslvs_locked(&master->base); + + phytium_i3c_master_upd_i3c_scl_lim(master); + + /* Unmask Hot-Join and Mastership request interrupts. */ + i3c_master_enec_locked(m, I3C_BROADCAST_ADDR, + I3C_CCC_EVENT_HJ | I3C_CCC_EVENT_MR); + + return 0; +} + +static u8 phytium_i3c_master_calculate_thd_delay(struct phytium_i3c_master *master) +{ + unsigned long sysclk_rate = clk_get_rate(master->sysclk); + u8 thd_delay = DIV_ROUND_UP(master->devdata->thd_delay_ns, + (NSEC_PER_SEC / sysclk_rate)); + + /* Every value greater than 3 is not valid. */ + if (thd_delay > THD_DELAY_MAX) + thd_delay = THD_DELAY_MAX; + + /* CTLR_THD_DEL value is encoded. */ + return (THD_DELAY_MAX - thd_delay); +} + +static int phytium_i3c_master_bus_init(struct i3c_master_controller *m) +{ + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + unsigned long pres_step, sysclk_rate, max_i2cfreq; + struct i3c_bus *bus = i3c_master_get_bus(m); + u32 ctrl, prescl0, prescl1, pres, low; + struct i3c_device_info info = { }; + int ret, ncycles; + + switch (bus->mode) { + case I3C_BUS_MODE_PURE: + ctrl = CTRL_PURE_BUS_MODE; + break; + + case I3C_BUS_MODE_MIXED_FAST: + ctrl = CTRL_MIXED_FAST_BUS_MODE; + break; + + case I3C_BUS_MODE_MIXED_SLOW: + ctrl = CTRL_MIXED_SLOW_BUS_MODE; + break; + + default: + return -EINVAL; + } + + if (has_acpi_companion(master->dev)) { + writel(master->prescl0, master->regs + PRESCL_CTRL0); + writel(master->prescl1, master->regs + PRESCL_CTRL1); + } else { + sysclk_rate = clk_get_rate(master->sysclk); + + if (!sysclk_rate) + return -EINVAL; + + pres = DIV_ROUND_UP(sysclk_rate, (bus->scl_rate.i3c * 4)) - 1; + if (pres > PRESCL_CTRL0_MAX) + return -ERANGE; + + bus->scl_rate.i3c = sysclk_rate / ((pres + 1) * 4); + + prescl0 = PRESCL_CTRL0_I3C(pres); + + low = ((I3C_BUS_TLOW_OD_MIN_NS * sysclk_rate) / (pres + 1)) - 2; + prescl1 = PRESCL_CTRL1_OD_LOW(low); + + max_i2cfreq = bus->scl_rate.i2c; + + pres = (sysclk_rate / (max_i2cfreq * 5)) - 1; + if (pres > PRESCL_CTRL0_MAX) + return -ERANGE; + + bus->scl_rate.i2c = sysclk_rate / ((pres + 1) * 5); + + prescl0 |= PRESCL_CTRL0_I2C(pres); + writel(prescl0, master->regs + PRESCL_CTRL0); + master->prescl0 = prescl0; + + /* Calculate OD and PP low. */ + pres_step = 1000000000 / (bus->scl_rate.i3c * 4); + ncycles = DIV_ROUND_UP(I3C_BUS_TLOW_OD_MIN_NS, pres_step) - 2; + if (ncycles < 0) + ncycles = 0; + prescl1 = PRESCL_CTRL1_OD_LOW(ncycles); + writel(prescl1, master->regs + PRESCL_CTRL1); + master->prescl1 = prescl1; + } + /* Get an address for the master. */ + ret = i3c_master_get_free_addr(m, 0); + if (ret < 0) + return ret; + + writel(prepare_rr0_dev_address(ret) | DEV_ID_RR0_IS_I3C, + master->regs + DEV_ID_RR0(0)); + + phytium_i3c_master_dev_rr_to_info(master, 0, &info); + if (info.bcr & I3C_BCR_HDR_CAP) + info.hdr_cap = I3C_CCC_HDR_MODE(I3C_HDR_DDR); + + ret = i3c_master_set_info(&master->base, &info); + if (ret) + return ret; + + /* + * Enable Hot-Join, and, when a Hot-Join request happens, disable all + * events coming from this device. + * + * We will issue ENTDAA afterwards from the threaded IRQ handler. + */ + + ctrl |= CTRL_HJ_DISEC | CTRL_MCS_EN; + /* + * Configure data hold delay based on device-specific data. + * + * MIPI I3C Specification 1.0 defines non-zero minimal tHD_PP timing on + * master output. This setting allows to meet this timing on master's + * SoC outputs, regardless of PCB balancing. + */ + if (has_acpi_companion(master->dev)) + ctrl |= CTRL_THD_DELAY(master->ctrl_thd_del); + else + ctrl |= CTRL_THD_DELAY(phytium_i3c_master_calculate_thd_delay(master)); + + writel(ctrl, master->regs + CTRL); + + phytium_i3c_master_enable(master); + + return 0; +} + +static void phytium_i3c_master_handle_ibi(struct phytium_i3c_master *master, + u32 ibir) +{ + struct phytium_i3c_i2c_dev_data *data; + bool data_consumed = false; + struct i3c_ibi_slot *slot; + u32 id = IBIR_SLVID(ibir); + struct i3c_dev_desc *dev; + size_t nbytes; + u8 *buf; + + /* + * FIXME: maybe we should report the FIFO OVF errors to the upper + * layer. + */ + if (id >= master->ibi.num_slots || (ibir & IBIR_ERROR)) + goto out; + + dev = master->ibi.slots[id]; + spin_lock(&master->ibi.lock); + + data = i3c_dev_get_master_data(dev); + slot = i3c_generic_ibi_get_free_slot(data->ibi_pool); + if (!slot) + goto out_unlock; + + buf = slot->data; + + nbytes = IBIR_XFER_BYTES(ibir); + readsl(master->regs + IBI_DATA_FIFO, buf, nbytes / 4); + if (nbytes % 3) { + u32 tmp = __raw_readl(master->regs + IBI_DATA_FIFO); + + memcpy(buf + (nbytes & ~3), &tmp, nbytes & 3); + } + + slot->len = min_t(unsigned int, IBIR_XFER_BYTES(ibir), + dev->ibi->max_payload_len); + i3c_master_queue_ibi(dev, slot); + data_consumed = true; + +out_unlock: + spin_unlock(&master->ibi.lock); + +out: + /* Consume data from the FIFO if it's not been done already. */ + if (!data_consumed) { + int i; + + for (i = 0; i < IBIR_XFER_BYTES(ibir); i += 4) + readl(master->regs + IBI_DATA_FIFO); + } +} + +static void phytium_i3c_master_demux_ibis(struct phytium_i3c_master *master) +{ + u32 status0; + + writel(MST_INT_IBIR_THR, master->regs + MST_ICR); + + for (status0 = readl(master->regs + MST_STATUS0); + !(status0 & MST_STATUS0_IBIR_EMP); + status0 = readl(master->regs + MST_STATUS0)) { + u32 ibir = readl(master->regs + IBIR); + + switch (IBIR_TYPE(ibir)) { + case IBIR_TYPE_IBI: + phytium_i3c_master_handle_ibi(master, ibir); + break; + + case IBIR_TYPE_HJ: + WARN_ON(IBIR_XFER_BYTES(ibir) || (ibir & IBIR_ERROR)); + queue_work(master->base.wq, &master->hj_work); + break; + + case IBIR_TYPE_MR: + WARN_ON(IBIR_XFER_BYTES(ibir) || (ibir & IBIR_ERROR)); + break; + default: + break; + } + } +} + +static irqreturn_t phytium_i3c_master_interrupt(int irq, void *data) +{ + struct phytium_i3c_master *master = data; + u32 status; + + status = readl(master->regs + MST_ISR); + if (!(status & readl(master->regs + MST_IMR))) + return IRQ_NONE; + + spin_lock(&master->xferqueue.lock); + phytium_i3c_master_end_xfer_locked(master, status); + spin_unlock(&master->xferqueue.lock); + + if (status & MST_INT_IBIR_THR) + phytium_i3c_master_demux_ibis(master); + + return IRQ_HANDLED; +} + +static int phytium_i3c_master_disable_ibi(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *m = i3c_dev_get_master(dev); + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + struct phytium_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); + unsigned long flags; + u32 sirmap; + int ret; + + ret = i3c_master_disec_locked(m, dev->info.dyn_addr, + I3C_CCC_EVENT_SIR); + if (ret) + return ret; + + spin_lock_irqsave(&master->ibi.lock, flags); + sirmap = readl(master->regs + SIR_MAP_DEV_REG(data->ibi)); + sirmap &= ~SIR_MAP_DEV_CONF_MASK(data->ibi); + sirmap |= SIR_MAP_DEV_CONF(data->ibi, + SIR_MAP_DEV_DA(I3C_BROADCAST_ADDR)); + writel(sirmap, master->regs + SIR_MAP_DEV_REG(data->ibi)); + spin_unlock_irqrestore(&master->ibi.lock, flags); + + return ret; +} + +static int phytium_i3c_master_enable_ibi(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *m = i3c_dev_get_master(dev); + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + struct phytium_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); + unsigned long flags; + u32 sircfg, sirmap; + int ret; + + spin_lock_irqsave(&master->ibi.lock, flags); + sirmap = readl(master->regs + SIR_MAP_DEV_REG(data->ibi)); + sirmap &= ~SIR_MAP_DEV_CONF_MASK(data->ibi); + sircfg = SIR_MAP_DEV_ROLE(dev->info.bcr >> 6) | + SIR_MAP_DEV_DA(dev->info.dyn_addr) | + SIR_MAP_DEV_PL(dev->info.max_ibi_len) | + SIR_MAP_DEV_ACK; + + if (dev->info.bcr & I3C_BCR_MAX_DATA_SPEED_LIM) + sircfg |= SIR_MAP_DEV_SLOW; + + sirmap |= SIR_MAP_DEV_CONF(data->ibi, sircfg); + writel(sirmap, master->regs + SIR_MAP_DEV_REG(data->ibi)); + spin_unlock_irqrestore(&master->ibi.lock, flags); + + ret = i3c_master_enec_locked(m, dev->info.dyn_addr, + I3C_CCC_EVENT_SIR); + if (ret) { + spin_lock_irqsave(&master->ibi.lock, flags); + sirmap = readl(master->regs + SIR_MAP_DEV_REG(data->ibi)); + sirmap &= ~SIR_MAP_DEV_CONF_MASK(data->ibi); + sirmap |= SIR_MAP_DEV_CONF(data->ibi, + SIR_MAP_DEV_DA(I3C_BROADCAST_ADDR)); + writel(sirmap, master->regs + SIR_MAP_DEV_REG(data->ibi)); + spin_unlock_irqrestore(&master->ibi.lock, flags); + } + + return ret; +} + +static int phytium_i3c_master_request_ibi(struct i3c_dev_desc *dev, + const struct i3c_ibi_setup *req) +{ + struct i3c_master_controller *m = i3c_dev_get_master(dev); + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + struct phytium_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); + unsigned long flags; + unsigned int i; + + data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req); + if (IS_ERR(data->ibi_pool)) + return PTR_ERR(data->ibi_pool); + + spin_lock_irqsave(&master->ibi.lock, flags); + for (i = 0; i < master->ibi.num_slots; i++) { + if (!master->ibi.slots[i]) { + data->ibi = i; + master->ibi.slots[i] = dev; + break; + } + } + spin_unlock_irqrestore(&master->ibi.lock, flags); + + if (i < master->ibi.num_slots) + return 0; + + i3c_generic_ibi_free_pool(data->ibi_pool); + data->ibi_pool = NULL; + + return -ENOSPC; +} + +static void phytium_i3c_master_free_ibi(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *m = i3c_dev_get_master(dev); + struct phytium_i3c_master *master = to_phytium_i3c_master(m); + struct phytium_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); + unsigned long flags; + + spin_lock_irqsave(&master->ibi.lock, flags); + master->ibi.slots[data->ibi] = NULL; + data->ibi = -1; + spin_unlock_irqrestore(&master->ibi.lock, flags); + + i3c_generic_ibi_free_pool(data->ibi_pool); +} + +static void phytium_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev, + struct i3c_ibi_slot *slot) +{ + struct phytium_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); + + i3c_generic_ibi_recycle_slot(data->ibi_pool, slot); +} + +static const struct i3c_master_controller_ops phytium_i3c_master_ops = { + .bus_init = phytium_i3c_master_bus_init, + .bus_cleanup = phytium_i3c_master_bus_cleanup, + .do_daa = phytium_i3c_master_do_daa, + .attach_i3c_dev = phytium_i3c_master_attach_i3c_dev, + .reattach_i3c_dev = phytium_i3c_master_reattach_i3c_dev, + .detach_i3c_dev = phytium_i3c_master_detach_i3c_dev, + .attach_i2c_dev = phytium_i3c_master_attach_i2c_dev, + .detach_i2c_dev = phytium_i3c_master_detach_i2c_dev, + .supports_ccc_cmd = phytium_i3c_master_supports_ccc_cmd, + .send_ccc_cmd = phytium_i3c_master_send_ccc_cmd, + .priv_xfers = phytium_i3c_master_priv_xfers, + .i2c_xfers = phytium_i3c_master_i2c_xfers, + .enable_ibi = phytium_i3c_master_enable_ibi, + .disable_ibi = phytium_i3c_master_disable_ibi, + .request_ibi = phytium_i3c_master_request_ibi, + .free_ibi = phytium_i3c_master_free_ibi, + .recycle_ibi_slot = phytium_i3c_master_recycle_ibi_slot, +}; + +static void phytium_i3c_master_hj(struct work_struct *work) +{ + struct phytium_i3c_master *master = container_of(work, + struct phytium_i3c_master, + hj_work); + + i3c_master_do_daa(&master->base); +} + +static struct phytium_i3c_data phytium_i3c_devdata = { + .thd_delay_ns = 10, +}; + +static const struct of_device_id phytium_i3c_master_of_ids[] = { + { .compatible = "phytium,cdns-i3c-master", .data = &phytium_i3c_devdata }, + { /* sentinel */ }, +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_i3c_master_acpi_ids[] = { + { "PHYT0035", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, phytium_i3c_master_acpi_ids); +#endif + +static void phytium_i3c_master_controller_init(struct phytium_i3c_master *master) +{ + int i; + + phytium_i3c_master_disable(master); + + writel(FLUSH_RX_FIFO | FLUSH_TX_FIFO | FLUSH_CMD_FIFO | + FLUSH_CMD_RESP, + master->regs + FLUSH_CTRL); + + writel(0xffffffff, master->regs + MST_IDR); + writel(0xffffffff, master->regs + SLV_IDR); + + writel(master->prescl0, master->regs + PRESCL_CTRL0); + writel(master->prescl1, master->regs + PRESCL_CTRL1); + + writel(IBIR_THR(1), master->regs + CMD_IBI_THR_CTRL); + writel(MST_INT_IBIR_THR, master->regs + MST_IER); + writel(master->dev_valid, master->regs + DEVS_CTRL); + + for (i = 0; i < PHYTIUM_I3C_DEV_MAX_NUM; i++) { + writel(master->devinfo[i].dev_rr0, master->regs + DEV_ID_RR0(i)); + writel(master->devinfo[i].dev_rr1, master->regs + DEV_ID_RR1(i)); + writel(master->devinfo[i].dev_rr2, master->regs + DEV_ID_RR2(i)); + } + + for (i = 0; i < PHYTIUM_I3C_CMDR_MAX_TIMES; i++) + readl(master->regs + CMDR); + + writel(master->ctrl_info, master->regs + CTRL); + + phytium_i3c_master_enable(master); +} + +static void phytium_i3c_master_store_dev(struct phytium_i3c_master *master) +{ + int i; + + master->ctrl_info = readl(master->regs + CTRL); + master->dev_valid = readl(master->regs + DEVS_CTRL); + + for (i = 0; i < PHYTIUM_I3C_DEV_MAX_NUM; i++) { + master->devinfo[i].dev_rr0 = readl(master->regs + DEV_ID_RR0(i)); + master->devinfo[i].dev_rr1 = readl(master->regs + DEV_ID_RR1(i)); + master->devinfo[i].dev_rr2 = readl(master->regs + DEV_ID_RR2(i)); + } +} + +static int phytium_i3c_master_probe(struct platform_device *pdev) +{ + struct phytium_i3c_master *master; + struct resource *res; + int ret, irq; + u32 val; + + master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; + + master->dev = &pdev->dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + master->regs = devm_ioremap_resource(&pdev->dev, res); + + if (IS_ERR(master->regs)) + return PTR_ERR(master->regs); + + if (pdev->dev.of_node) { + master->devdata = of_device_get_match_data(&pdev->dev); + + if (!master->devdata) + return -EINVAL; + + + master->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(master->pclk)) + return PTR_ERR(master->pclk); + + master->sysclk = devm_clk_get(&pdev->dev, "sysclk"); + if (IS_ERR(master->sysclk)) + return PTR_ERR(master->sysclk); + + ret = clk_prepare_enable(master->pclk); + if (ret) + return ret; + + ret = clk_prepare_enable(master->sysclk); + if (ret) + goto err_disable_pclk; +#ifdef CONFIG_ACPI + } else if (has_acpi_companion(&pdev->dev)) { + i3c_master_acpi_clk_params(ACPI_HANDLE(&pdev->dev), "SCLK", &master->prescl0, + &master->prescl1, &master->ctrl_thd_del); + + if (!master->prescl0) + goto err_disable_sysclk; + + master->base.bus.scl_rate.i2c = I3C_CONTROL_DEFAULT_I2C_SCL; + master->base.bus.scl_rate.i3c = I3C_BUS_MAX_I3C_SCL_RATE; + ACPI_COMPANION_SET(master->dev, ACPI_COMPANION(&pdev->dev)); +#endif + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + if (readl(master->regs + DEV_ID) != DEV_ID_I3C_MASTER) { + ret = -EINVAL; + goto err_disable_sysclk; + } + + spin_lock_init(&master->xferqueue.lock); + INIT_LIST_HEAD(&master->xferqueue.list); + + INIT_WORK(&master->hj_work, phytium_i3c_master_hj); + phytium_i3c_master_disable(master); + + writel(FLUSH_RX_FIFO | FLUSH_TX_FIFO | FLUSH_CMD_FIFO | + FLUSH_CMD_RESP, + master->regs + FLUSH_CTRL); + writel(0xffffffff, master->regs + MST_IDR); + writel(0xffffffff, master->regs + SLV_IDR); + ret = devm_request_irq(&pdev->dev, irq, phytium_i3c_master_interrupt, 0, + dev_name(&pdev->dev), master); + if (ret) + goto err_disable_sysclk; + + platform_set_drvdata(pdev, master); + + val = readl(master->regs + CONF_STATUS0); + + /* Device ID0 is reserved to describe this master. */ + master->maxdevs = CONF_STATUS0_DEVS_NUM(val); + master->free_rr_slots = GENMASK(master->maxdevs, 1); + + val = readl(master->regs + CONF_STATUS1); + master->caps.cmdfifodepth = CONF_STATUS1_CMD_DEPTH(val); + master->caps.rxfifodepth = CONF_STATUS1_RX_DEPTH(val); + master->caps.txfifodepth = CONF_STATUS1_TX_DEPTH(val); + master->caps.ibirfifodepth = CONF_STATUS0_IBIR_DEPTH(val); + master->caps.cmdrfifodepth = CONF_STATUS0_CMDR_DEPTH(val); + + spin_lock_init(&master->ibi.lock); + master->ibi.num_slots = CONF_STATUS1_IBI_HW_RES(val); + master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots, + sizeof(*master->ibi.slots), + GFP_KERNEL); + if (!master->ibi.slots) { + ret = -ENOMEM; + goto err_disable_sysclk; + } + + writel(IBIR_THR(1), master->regs + CMD_IBI_THR_CTRL); + writel(MST_INT_IBIR_THR, master->regs + MST_IER); + writel(DEVS_CTRL_DEV_CLR_ALL, master->regs + DEVS_CTRL); + + ret = i3c_master_register(&master->base, &pdev->dev, + &phytium_i3c_master_ops, false); + if (ret) + goto err_disable_sysclk; + writel(readl(master->regs + CTRL) | CTRL_HJ_ACK, master->regs + CTRL); + phytium_i3c_master_store_dev(master); + + return 0; + +err_disable_sysclk: + clk_disable_unprepare(master->sysclk); + +err_disable_pclk: + clk_disable_unprepare(master->pclk); + + return ret; +} + +static int phytium_i3c_master_remove(struct platform_device *pdev) +{ + struct phytium_i3c_master *master = platform_get_drvdata(pdev); + + i3c_master_unregister(&master->base); + + clk_disable_unprepare(master->sysclk); + clk_disable_unprepare(master->pclk); + + return 0; +} + +static int __maybe_unused phytium_i3c_plat_suspend(struct device *dev) +{ + struct phytium_i3c_master *master = dev_get_drvdata(dev); + + + phytium_i3c_master_disable(master); + + clk_disable_unprepare(master->sysclk); + clk_disable_unprepare(master->pclk); + + return 0; +} + +static int __maybe_unused phytium_i3c_plat_resume(struct device *dev) +{ + struct phytium_i3c_master *master = dev_get_drvdata(dev); + + phytium_i3c_master_controller_init(master); + + clk_prepare_enable(master->sysclk); + clk_prepare_enable(master->pclk); + + return 0; +} + +static const struct dev_pm_ops phytium_i3c_dev_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(phytium_i3c_plat_suspend, + phytium_i3c_plat_resume) +}; + +static struct platform_driver phytium_i3c_master = { + .probe = phytium_i3c_master_probe, + .remove = phytium_i3c_master_remove, + .driver = { + .name = "phytium-i3c-master", + .of_match_table = phytium_i3c_master_of_ids, + .acpi_match_table = ACPI_PTR(phytium_i3c_master_acpi_ids), + .pm = &phytium_i3c_dev_pm_ops, + }, +}; +module_platform_driver(phytium_i3c_master); + +MODULE_AUTHOR("Feng Jun "); +MODULE_DESCRIPTION("Phytium I3C master driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:phytium-i3c-master"); diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index dfb925cfe38ef2992b2312e01a4c129719992a37..a4183e3a585059cc97810057dc0aa757361b836e 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -886,6 +886,18 @@ config PALMAS_GPADC is used in smartphones and tablets and supports a 16 channel general purpose ADC. +config PHYTIUM_ADC + tristate "Phytium ADC driver" + depends on ARCH_PHYTIUM || COMPILE_TEST + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Phytium analog to digital + converters (ADC). + + To compile this driver as a module, choose M here: the module + will be called phytium-adc. + config QCOM_VADC_COMMON tristate diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 2facf979327d754959309c00b79d80ea353a924a..5b411b68bf3a9adcc91c97e6b8e8eb0198a8fded 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -82,6 +82,7 @@ obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_NPCM_ADC) += npcm_adc.o obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o +obj-$(CONFIG_PHYTIUM_ADC) += phytium-adc.o obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o obj-$(CONFIG_QCOM_SPMI_RRADC) += qcom-spmi-rradc.o diff --git a/drivers/iio/adc/phytium-adc.c b/drivers/iio/adc/phytium-adc.c new file mode 100644 index 0000000000000000000000000000000000000000..2f4a6939f7dbd4168bef60341b157f3cfca21a0f --- /dev/null +++ b/drivers/iio/adc/phytium-adc.c @@ -0,0 +1,688 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium ADC device driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* ADC register */ +#define ADC_CTRL_REG 0x00 +#define ADC_CTRL_REG_PD_EN BIT(31) +#define ADC_CTRL_REG_CH_ONLY_S(x) ((x & 0x7) << 16) +#define ADC_CTRL_REG_CLK_DIV(x) ((x) << 12) +#define ADC_CTRL_REG_CHANNEL_EN(x) BIT((x) + 4) +#define ADC_CTRL_REG_CH_ONLY_EN BIT(3) +#define ADC_CTRL_REG_SINGLE_EN BIT(2) +#define ADC_CTRL_REG_SINGLE_SEL BIT(1) +#define ADC_CTRL_REG_SOC_EN BIT(0) +#define ADC_INTER_REG 0x04 +#define ADC_STATE_REG 0x08 +#define ADC_STATE_REG_B_STA(x) ((x) << 8) +#define ADC_STATE_REG_EOC_STA BIT(7) +#define ADC_STATE_REG_S_STA(x) ((x) << 4) +#define ADC_STATE_REG_SOC_STA BIT(3) +#define ADC_STATE_REG_ERR_STA BIT(2) +#define ADC_STATE_REG_COV_FINISH_STA BIT(1) +#define ADC_STATE_REG_ADCCTL_BUSY_STA BIT(0) +#define ADC_ERRCLR_REG 0x0c +#define ADC_LEVEL_REG(x) (0x10 + ((x) << 2)) +#define ADC_LEVEL_REG_HIGH_LEVEL(x) ((x) << 16) +#define ADC_LEVEL_REG_LOW_LEVEL(x) (x) +#define ADC_INTRMASK_REG 0x30 +#define ADC_INTRMASK_REG_ERR_INTR_MASK BIT(24) +#define ADC_INTRMASK_REG_ULIMIT_OFF(x) BIT(9 + ((x) << 1)) +#define ADC_INTRMASK_REG_DLIMIT_MASK(x) BIT(8 + ((x) << 1)) +#define ADC_INTRMASK_REG_COVFIN_MASK(x) BIT((x)) +#define ADC_INTR_REG 0x34 +#define ADC_INTR_REG_ERR BIT(24) +#define ADC_INTR_REG_ULIMIT(x) BIT(9 + ((x) << 1)) +#define ADC_INTR_REG_DLIMIT(x) BIT(8 + ((x) << 1)) +#define ADC_INTR_REG_LIMIT_MASK GENMASK(23, 8) +#define ADC_INTR_REG_COVFIN(x) BIT((x)) +#define ADC_INTR_REG_COVFIN_MASK GENMASK(7, 0) +#define ADC_COV_RESULT_REG(x) (0x38 + ((x) << 2)) +#define ADC_COV_RESULT_REG_MASK GENMASK(9, 0) +#define ADC_FINISH_CNT_REG(x) (0x58 + ((x) << 2)) +#define ADC_HIS_LIMIT_REG(x) (0x78 + ((x) << 2)) + +#define PHYTIUM_MAX_CHANNELS 8 +#define PHYTIUM_ADC_TIMEOUT usecs_to_jiffies(1000 * 1000) + +static const struct iio_event_spec phytium_adc_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, +}; + +struct phytium_adc_data { + const struct iio_chan_spec *channels; + u8 num_channels; +}; + +struct phytium_adc { + struct device *dev; + void __iomem *regs; + struct clk *adc_clk; + + u32 interval; + u16 thresh_high[PHYTIUM_MAX_CHANNELS]; + u16 thresh_low[PHYTIUM_MAX_CHANNELS]; + u16 last_val[PHYTIUM_MAX_CHANNELS]; + const struct phytium_adc_data *data; + u16 *scan_data; + + struct completion completion; + struct mutex lock; +}; + +static ssize_t phytium_adc_show_conv_interval(struct iio_dev *indio_dev, + uintptr_t priv, + struct iio_chan_spec const *ch, + char *buf) +{ + struct phytium_adc *adc = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", adc->interval); +} + +static ssize_t phytium_adc_store_conv_interval(struct iio_dev *indio_dev, + uintptr_t priv, + struct iio_chan_spec const *ch, + const char *buf, size_t len) +{ + struct phytium_adc *adc = iio_priv(indio_dev); + u32 interval; + int ret; + + ret = kstrtou32(buf, 0, &interval); + if (ret < 0) + return ret; + + mutex_lock(&adc->lock); + adc->interval = interval; + mutex_unlock(&adc->lock); + + return len; +} + +static const struct iio_chan_spec_ext_info phytium_adc_ext_info[] = { + { + .name = "conv_interval", + .read = phytium_adc_show_conv_interval, + .write = phytium_adc_store_conv_interval, + }, + { /* sentinel */ } +}; + +static int phytium_adc_parse_properties(struct platform_device *pdev, struct phytium_adc *adc) +{ + struct iio_chan_spec *chan_array; + struct fwnode_handle *fwnode; + struct phytium_adc_data *data; + unsigned int channel; + int num_channels; + int ret, i = 0; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + num_channels = device_get_child_node_count(&pdev->dev); + if (!num_channels) { + dev_err(&pdev->dev, "no channel children\n"); + return -ENODEV; + } + + if (num_channels > PHYTIUM_MAX_CHANNELS) { + dev_err(&pdev->dev, "num of channel children out of range\n"); + return -EINVAL; + } + + chan_array = devm_kcalloc(&pdev->dev, num_channels, sizeof(*chan_array), + GFP_KERNEL); + if (!chan_array) + return -ENOMEM; + + device_for_each_child_node(&pdev->dev, fwnode) { + ret = fwnode_property_read_u32(fwnode, "reg", &channel); + if (ret) + return ret; + + if (channel >= PHYTIUM_MAX_CHANNELS) + return -EINVAL; + + chan_array[i].type = IIO_VOLTAGE; + chan_array[i].indexed = 1; + chan_array[i].channel = channel; + chan_array[i].info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + chan_array[i].event_spec = phytium_adc_event; + chan_array[i].num_event_specs = ARRAY_SIZE(phytium_adc_event); + chan_array[i].scan_index = channel; + chan_array[i].scan_type.sign = 'u'; + chan_array[i].scan_type.realbits = 10; + chan_array[i].scan_type.storagebits = 16; + chan_array[i].scan_type.endianness = IIO_LE; + chan_array[i].ext_info = phytium_adc_ext_info; + i++; + } + + data->num_channels = num_channels; + data->channels = chan_array; + adc->data = data; + + return 0; +} + +static void phytium_adc_start_stop(struct phytium_adc *adc, bool start) +{ + u32 ctrl; + + ctrl = readl(adc->regs + ADC_CTRL_REG); + if (start) + ctrl |= ADC_CTRL_REG_SOC_EN | ADC_CTRL_REG_SINGLE_EN; + else + ctrl &= ~ADC_CTRL_REG_SOC_EN; + /* Start conversion */ + writel(ctrl, adc->regs + ADC_CTRL_REG); +} + +static void phytium_adc_power_setup(struct phytium_adc *adc, bool on) +{ + u32 reg; + + reg = readl(adc->regs + ADC_CTRL_REG); + if (on) + reg &= ~ADC_CTRL_REG_PD_EN; + else + reg |= ADC_CTRL_REG_PD_EN; + writel(reg, adc->regs + ADC_CTRL_REG); +} + +static int phytium_adc_hw_init(struct phytium_adc *adc) +{ + int ret; + u32 reg; + + ret = clk_prepare_enable(adc->adc_clk); + if (ret) + return ret; + + /* + * Setup ctrl register: + * - Power up conversion module + * - Set the division by 4 as default + */ + reg = ADC_CTRL_REG_CLK_DIV(4); + writel(reg, adc->regs + ADC_CTRL_REG); + + /* Set all the interrupt mask, unmask them when necessary. */ + writel(0x1ffffff, adc->regs + ADC_INTRMASK_REG); + + /* Set default conversion interval */ + adc->interval = (clk_get_rate(adc->adc_clk) * 1000) / NSEC_PER_SEC; + + phytium_adc_power_setup(adc, true); + + return 0; +} + +static void phytium_adc_intrmask_setup(struct phytium_adc *adc, unsigned long chan_mask, bool on) +{ + u32 reg; + u16 limit_mask = 0; + int ch; + + for_each_set_bit(ch, &chan_mask, PHYTIUM_MAX_CHANNELS) + limit_mask |= BIT(ch << 1) | BIT((ch << 1) + 1); + + reg = readl(adc->regs + ADC_INTRMASK_REG); + if (on) + reg &= ~(ADC_INTRMASK_REG_ERR_INTR_MASK | + (limit_mask << 8) | chan_mask); + else + reg |= (ADC_INTRMASK_REG_ERR_INTR_MASK | + (limit_mask << 8) | chan_mask); + writel(reg, adc->regs + ADC_INTRMASK_REG); +} + +static void phytium_adc_single_conv_setup(struct phytium_adc *adc, u8 ch) +{ + u32 reg; + + /* + * Setup control register: + * - Single conversion mode selection + * - Single conversion enable + * - Fixed channel conversion + * - Target channel + */ + reg = readl(adc->regs + ADC_CTRL_REG); + + /* Clean ch_only_s bits */ + reg &= ~ADC_CTRL_REG_CH_ONLY_S(7); + + /* Clean channel_en bit */ + reg &= 0xFFF00F; + + reg |= ADC_CTRL_REG_SINGLE_SEL | ADC_CTRL_REG_SINGLE_EN | + ADC_CTRL_REG_CH_ONLY_EN | ADC_CTRL_REG_CH_ONLY_S(ch) | ADC_CTRL_REG_CHANNEL_EN(ch); + writel(reg, adc->regs + ADC_CTRL_REG); +} + +static int phytium_adc_single_conv(struct iio_dev *indio_dev, u8 ch) +{ + struct phytium_adc *adc = iio_priv(indio_dev); + int ret; + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + mutex_lock(&adc->lock); + + phytium_adc_intrmask_setup(adc, BIT(ch), true); + reinit_completion(&adc->completion); + phytium_adc_single_conv_setup(adc, ch); + phytium_adc_start_stop(adc, true); + + if (!wait_for_completion_timeout(&adc->completion, PHYTIUM_ADC_TIMEOUT)) + ret = -ETIMEDOUT; + + phytium_adc_start_stop(adc, false); + phytium_adc_intrmask_setup(adc, BIT(ch), false); + + mutex_unlock(&adc->lock); + iio_device_release_direct_mode(indio_dev); + + return ret; +} + +static int phytium_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct phytium_adc *adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_VOLTAGE) + return -EINVAL; + + ret = phytium_adc_single_conv(indio_dev, chan->channel); + if (ret) + return ret; + *val = adc->last_val[chan->channel]; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int phytium_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info iinfo, int *val, int *val2) +{ + struct phytium_adc *adc = iio_priv(indio_dev); + + if (dir == IIO_EV_DIR_FALLING) + *val = adc->thresh_low[chan->channel]; + else + *val = adc->thresh_high[chan->channel]; + + return IIO_VAL_INT; +} + +static int phytium_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info einfo, int val, int val2) +{ + struct phytium_adc *adc = iio_priv(indio_dev); + u32 thresh; + + switch (dir) { + case IIO_EV_DIR_FALLING: + adc->thresh_low[chan->channel] = val; + thresh = readl(adc->regs + ADC_LEVEL_REG(chan->channel)) & 0x3ff0000; + thresh |= ADC_LEVEL_REG_LOW_LEVEL(val); + writel(thresh, adc->regs + ADC_LEVEL_REG(chan->channel)); + break; + case IIO_EV_DIR_RISING: + adc->thresh_high[chan->channel] = val; + thresh = readl(adc->regs + ADC_LEVEL_REG(chan->channel)) & 0xffff; + thresh |= ADC_LEVEL_REG_HIGH_LEVEL(val); + writel(thresh, adc->regs + ADC_LEVEL_REG(chan->channel)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int phytium_update_scan_mode(struct iio_dev *indio_dev, const unsigned long *mask) +{ + struct phytium_adc *adc = iio_priv(indio_dev); + unsigned int n; + + n = bitmap_weight(mask, indio_dev->masklength); + + kfree(adc->scan_data); + adc->scan_data = kcalloc(n, sizeof(*adc->scan_data), GFP_KERNEL); + if (!adc->scan_data) + return -ENOMEM; + + return 0; +} + +static const u64 phytium_adc_event_codes[] = { + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 1, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 1, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 2, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 2, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 3, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 3, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 4, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 4, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 5, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 5, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 6, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 6, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 7, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 7, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), +}; + +static irqreturn_t phytium_adc_threaded_irq(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct phytium_adc *adc = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(indio_dev); + unsigned long status; + int ch; + u32 intr; + + intr = readl(adc->regs + ADC_INTR_REG); + + if (intr & ADC_INTR_REG_ERR) { + dev_err(adc->dev, "conversion error: ADC_INTR_REG(0x%x)\n", intr); + writel(ADC_INTR_REG_ERR, adc->regs + ADC_INTR_REG); + return IRQ_HANDLED; + } + + status = (intr & ADC_INTR_REG_LIMIT_MASK) >> 8; + if (status) { + for_each_set_bit(ch, &status, PHYTIUM_MAX_CHANNELS * 2) + iio_push_event(indio_dev, phytium_adc_event_codes[ch], timestamp); + } + + status = intr & ADC_INTR_REG_COVFIN_MASK; + if (status) { + for_each_set_bit(ch, &status, PHYTIUM_MAX_CHANNELS) + adc->last_val[ch] = readl(adc->regs + ADC_COV_RESULT_REG(ch)) & + ADC_COV_RESULT_REG_MASK; + + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll(indio_dev->trig); + else + complete(&adc->completion); + } + + /* Clear all the interrupts */ + writel(status, adc->regs + ADC_INTR_REG); + + return IRQ_HANDLED; +} + +static void phytium_adc_cont_conv_setup(struct phytium_adc *adc, + unsigned long chan_mask, + u32 interval) +{ + u32 reg; + + /* + * Setup control register: + * - Continuous conversion mode + * - Multi-channel rotation mode + * - Channel enablement + */ + reg = readl(adc->regs + ADC_CTRL_REG); + reg &= ~(ADC_CTRL_REG_SINGLE_SEL | ADC_CTRL_REG_SINGLE_EN | + ADC_CTRL_REG_CH_ONLY_EN); + reg |= chan_mask << 4; + writel(reg, adc->regs + ADC_CTRL_REG); + + /* Setup interval between two conversions */ + writel(interval, adc->regs + ADC_INTER_REG); +} + +static int phytium_adc_preenable(struct iio_dev *indio_dev) +{ + struct phytium_adc *adc = iio_priv(indio_dev); + unsigned long scan_mask = *indio_dev->active_scan_mask; + + phytium_adc_cont_conv_setup(adc, scan_mask & 0xff, adc->interval); + phytium_adc_intrmask_setup(adc, scan_mask & 0xff, true); + + return 0; +} + +static int phytium_adc_postenable(struct iio_dev *indio_dev) +{ + struct phytium_adc *adc = iio_priv(indio_dev); + + phytium_adc_start_stop(adc, true); + + return 0; +} + +static int phytium_adc_postdisable(struct iio_dev *indio_dev) +{ + struct phytium_adc *adc = iio_priv(indio_dev); + unsigned long scan_mask = *indio_dev->active_scan_mask; + + phytium_adc_start_stop(adc, false); + phytium_adc_intrmask_setup(adc, scan_mask & 0xff, false); + + return 0; +} + +static const struct iio_buffer_setup_ops phytium_buffer_setup_ops = { + .preenable = &phytium_adc_preenable, + .postenable = &phytium_adc_postenable, + .postdisable = &phytium_adc_postdisable, +}; + +static irqreturn_t phytium_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct phytium_adc *adc = iio_priv(indio_dev); + int i, j = 0; + + if (!adc->scan_data) + goto out; + + for_each_set_bit(i, indio_dev->active_scan_mask, indio_dev->masklength) + adc->scan_data[j++] = adc->last_val[i]; + + iio_push_to_buffers(indio_dev, adc->scan_data); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_info phytium_adc_iio_info = { + .read_raw = &phytium_adc_read_raw, + .read_event_value = &phytium_read_thresh, + .write_event_value = &phytium_write_thresh, + .update_scan_mode = &phytium_update_scan_mode, +}; + +static int phytium_adc_probe(struct platform_device *pdev) +{ + struct phytium_adc *adc; + struct iio_dev *indio_dev; + struct device *dev = &pdev->dev; + struct resource *res; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->dev = dev; + + ret = phytium_adc_parse_properties(pdev, adc); + if (ret) + return ret; + + mutex_init(&adc->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + adc->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(adc->regs)) + return PTR_ERR(adc->regs); + + adc->adc_clk = devm_clk_get(dev, NULL); + if (IS_ERR(adc->adc_clk)) + return PTR_ERR(adc->adc_clk); + + init_completion(&adc->completion); + + indio_dev->name = dev_name(dev); + indio_dev->info = &phytium_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adc->data->channels; + indio_dev->num_channels = adc->data->num_channels; + + platform_set_drvdata(pdev, indio_dev); + + ret = devm_request_threaded_irq(adc->dev, platform_get_irq(pdev, 0), + NULL, phytium_adc_threaded_irq, IRQF_ONESHOT, + dev_name(dev), indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + phytium_adc_trigger_handler, + &phytium_buffer_setup_ops); + if (ret) + return ret; + + ret = phytium_adc_hw_init(adc); + if (ret) { + dev_err(&pdev->dev, "failed to initialize Phytium ADC, %d\n", ret); + return ret; + } + + return iio_device_register(indio_dev); +} + +static int phytium_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct phytium_adc *adc = iio_priv(indio_dev); + + phytium_adc_power_setup(adc, false); + iio_device_unregister(indio_dev); + kfree(adc->scan_data); + + return 0; +} + +static const struct of_device_id phytium_of_match[] = { + { .compatible = "phytium,adc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, phytium_of_match); + +#ifdef CONFIG_PM +static int phytium_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct phytium_adc *adc = iio_priv(indio_dev); + + phytium_adc_power_setup(adc, false); + clk_disable_unprepare(adc->adc_clk); + + return 0; +} + +static int phytium_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct phytium_adc *adc = iio_priv(indio_dev); + + clk_prepare_enable(adc->adc_clk); + phytium_adc_power_setup(adc, true); + + return 0; +} +#endif + +SIMPLE_DEV_PM_OPS(phytium_adc_pm_ops, phytium_adc_suspend, phytium_adc_resume); + +static struct platform_driver phytium_adc_driver = { + .driver = { + .name = "phytium_adc", + .of_match_table = phytium_of_match, + .pm = &phytium_adc_pm_ops, + }, + .probe = phytium_adc_probe, + .remove = phytium_adc_remove, +}; +module_platform_driver(phytium_adc_driver); + +MODULE_AUTHOR("Yang Liu "); +MODULE_DESCRIPTION("Phytium ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 1d0c5f4c0f99e07711248bb2d28f9e0bd49521d7..bd943d967685468066cf12fb9daf8dd61dde7a63 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -809,4 +809,15 @@ config KEYBOARD_CYPRESS_SF To compile this driver as a module, choose M here: the module will be called cypress-sf. +config KEYBOARD_PHYTIUM + tristate "Phytium keypad support" + depends on ARCH_PHYTIUM + select INPUT_MATRIXKMAP + help + Say Y here if you want to enable support for Phytium keypad + port. + + To compile this driver as a module, choose M here: the + module will be called phytium_keypad. + endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index aecef00c5d09d3406023277ffed3c9883fbe6897..43ade5a0d620b6b8110a68e3a7343ee392ca591d 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_KEYBOARD_NSPIRE) += nspire-keypad.o obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o obj-$(CONFIG_KEYBOARD_OMAP4) += omap4-keypad.o obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o +obj-$(CONFIG_KEYBOARD_PHYTIUM) += phytium-keypad.o obj-$(CONFIG_KEYBOARD_PINEPHONE) += pinephone-keyboard.o obj-$(CONFIG_KEYBOARD_PMIC8XXX) += pmic8xxx-keypad.o obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o diff --git a/drivers/input/keyboard/phytium-keypad.c b/drivers/input/keyboard/phytium-keypad.c new file mode 100644 index 0000000000000000000000000000000000000000..10613643801d9177b775d1591c8bca03ae82ef56 --- /dev/null +++ b/drivers/input/keyboard/phytium-keypad.c @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Phytium keypad port. + * + * Copyright (C) 2020-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Keypad Controller registers + */ +#define KPCR 0x00 /* Keypad Control Register */ + +#define KPSR 0x04 /* Keypad Status Register */ +#define KBD_STAT_KPKD (0x1 << 0) /* Key Press Interrupt Status bit (w1c) */ +#define KBD_STAT_KPKR (0x1 << 1) /* Key Release Interrupt Status bit (w1c) */ +#define KBD_STAT_KDSC (0x1 << 2) /* Key Depress Synch Chain Status bit (w1c)*/ +#define KBD_STAT_KRSS (0x1 << 3) /* Key Release Synch Status bit (w1c)*/ +#define KBD_STAT_KDIE (0x1 << 8) /* Key Depress Interrupt Enable Status bit */ +#define KBD_STAT_KRIE (0x1 << 9) /* Key Release Interrupt Enable */ + +#define KDDR 0x08 /* Keypad Data Direction Register */ +#define KPDR 0x0C /* Keypad Data Register */ + +#define MAX_MATRIX_KEY_ROWS 8 +#define MAX_MATRIX_KEY_COLS 8 + +#define MAX_MATRIX_KEY_NUM (MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS) + +struct phytium_keypad { + struct input_dev *input_dev; + void __iomem *mmio_base; + + int irq; + struct timer_list check_matrix_timer; + + /* + * The matrix is stable only if no changes are detected after + * PHYTIUM_KEYPAD_SCANS_FOR_STABILITY scans + */ +#define PHYTIUM_KEYPAD_SCANS_FOR_STABILITY 3 + + int stable_count; + + bool enabled; + + unsigned int n_rows; + unsigned int n_cols; + int row_shift; + + /* Masks for enabled rows/cols */ + unsigned short rows_en_mask; + unsigned short cols_en_mask; + + unsigned short keycodes[MAX_MATRIX_KEY_NUM]; + + /* + * Matrix states: + * -stable: achieved after a complete debounce process. + * -unstable: used in the debouncing process. + */ + unsigned short matrix_stable_state[MAX_MATRIX_KEY_COLS]; + unsigned short matrix_unstable_state[MAX_MATRIX_KEY_COLS]; +}; + +static u32 phytium_read(struct phytium_keypad *keypad, int reg) +{ + return readl(keypad->mmio_base + reg); +} + +static void phytium_write(struct phytium_keypad *keypad, u32 value, int reg) +{ + writel(value, keypad->mmio_base + reg); +} + +/* Scan the matrix and return the new state in *matrix_volatile_state. */ +static void phytium_keypad_scan_matrix(struct phytium_keypad *keypad, + unsigned short *matrix_volatile_state) +{ + int col; + u32 reg_val; + + for (col = 0; col < keypad->n_cols; col++) { + if ((keypad->cols_en_mask & (1 << col)) == 0) + continue; + /* + * Discharge keypad capacitance: + * 2. write 0s on KDDR[KCD], configure columns as input. + */ + reg_val = phytium_read(keypad, KDDR); + reg_val = 0x00000000; + phytium_write(keypad, reg_val, KDDR); + + /* + * 3. Write a single column to 0, others to 1. + * 4. Sample row inputs and save data. + * 5. Repeat steps 3 - 4 for remaining columns. + */ + reg_val = 0; + reg_val |= (1 << (16 + col)); + phytium_write(keypad, reg_val, KDDR); + reg_val = phytium_read(keypad, KPDR); + reg_val = 0x00000000; + phytium_write(keypad, reg_val, KPDR); + + /* + * Delay added to avoid propagating the 0 from column to row + * when scanning. + */ + udelay(5); + + /* + * 1s in matrix_volatile_state[col] means key pressures + * throw data from non enabled rows. + */ + reg_val = phytium_read(keypad, KPDR); + matrix_volatile_state[col] = (~reg_val) & keypad->rows_en_mask; + } + + /* + * Return in standby mode: + * 6. write 0s to columns + */ + /* Configure columns as output, output 0 */ + reg_val = 0; + reg_val |= (keypad->cols_en_mask & 0xffff) << 16; + phytium_write(keypad, reg_val, KDDR); + phytium_write(keypad, 0x00000000, KPDR); +} + +/* + * Compare the new matrix state (volatile) with the stable one stored in + * keypad->matrix_stable_state and fire events if changes are detected. + */ +static void phytium_keypad_fire_events(struct phytium_keypad *keypad, + unsigned short *matrix_volatile_state) +{ + struct input_dev *input_dev = keypad->input_dev; + int row, col; + + for (col = 0; col < keypad->n_cols; col++) { + unsigned short bits_changed; + int code; + + if ((keypad->cols_en_mask & (1 << col)) == 0) + continue; /* Column is not enabled */ + + bits_changed = keypad->matrix_stable_state[col] ^ + matrix_volatile_state[col]; + + if (bits_changed == 0) + continue; /* Column does not contain changes */ + + for (row = 0; row < keypad->n_rows; row++) { + if ((keypad->rows_en_mask & (1 << row)) == 0) + continue; /* Row is not enabled */ + if ((bits_changed & (1 << row)) == 0) + continue; /* Row does not contain changes */ + + code = MATRIX_SCAN_CODE(row, col, keypad->row_shift); + input_event(input_dev, EV_MSC, MSC_SCAN, code); + input_report_key(input_dev, keypad->keycodes[code], + matrix_volatile_state[col] & (1 << row)); + dev_dbg(&input_dev->dev, "Event code: %d, val: %d", + keypad->keycodes[code], + matrix_volatile_state[col] & (1 << row)); + } + } + input_sync(input_dev); +} + +/* + * phytium_keypad_check_for_events is the timer handler. + */ +static void phytium_keypad_check_for_events(struct timer_list *t) +{ + struct phytium_keypad *keypad = from_timer(keypad, t, check_matrix_timer); + unsigned short matrix_volatile_state[MAX_MATRIX_KEY_COLS]; + u32 reg_val; + bool state_changed, is_zero_matrix; + int i; + + memset(matrix_volatile_state, 0, sizeof(matrix_volatile_state)); + + phytium_keypad_scan_matrix(keypad, matrix_volatile_state); + + state_changed = false; + for (i = 0; i < keypad->n_cols; i++) { + if ((keypad->cols_en_mask & (1 << i)) == 0) + continue; + + if (keypad->matrix_unstable_state[i] ^ matrix_volatile_state[i]) { + state_changed = true; + break; + } + } + + /* + * If the matrix state is changed from the previous scan + * (Re)Begin the debouncing process, saving the new state in + * keypad->matrix_unstable_state. + * else + * Increase the count of number of scans with a stable state. + */ + if (state_changed) { + memcpy(keypad->matrix_unstable_state, matrix_volatile_state, + sizeof(matrix_volatile_state)); + keypad->stable_count = 0; + } else { + keypad->stable_count++; + } + + /* + * If the matrix is not as stable as we want reschedule scan + * in the near future. + */ + if (keypad->stable_count < PHYTIUM_KEYPAD_SCANS_FOR_STABILITY) { + mod_timer(&keypad->check_matrix_timer, + jiffies + msecs_to_jiffies(10)); + return; + } + + /* + * If the matrix state is stable, fire the events and save the new + * stable state. Note, if the matrix is kept stable for longer + * (keypad->stable_count > PHYTIUM_KEYPAD_SCANS_FOR_STABILITY) all + * events have already been generated. + */ + if (keypad->stable_count == PHYTIUM_KEYPAD_SCANS_FOR_STABILITY) { + phytium_keypad_fire_events(keypad, matrix_volatile_state); + memcpy(keypad->matrix_stable_state, matrix_volatile_state, + sizeof(matrix_volatile_state)); + } + + is_zero_matrix = true; + for (i = 0; i < keypad->n_cols; i++) { + if (matrix_volatile_state[i] != 0) { + is_zero_matrix = false; + break; + } + } + + if (is_zero_matrix) { + /* + * All keys have been released. Enable only the KDI + * interrupt for future key presses (clear the KDI + * status bit and its sync chain before that). + */ + reg_val = phytium_read(keypad, KPSR); + reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC; + phytium_write(keypad, reg_val, KPSR); + + reg_val = phytium_read(keypad, KPSR); + reg_val |= KBD_STAT_KDIE; + reg_val &= ~KBD_STAT_KRIE; + phytium_write(keypad, reg_val, KPSR); + } else { + /* + * Some keys are still pressed. Schedule a rescan in + * attempt to detect multiple key presses and enable + * the KRI interrupt to react quickly to key release + * event. + */ + mod_timer(&keypad->check_matrix_timer, + jiffies + msecs_to_jiffies(60)); + + reg_val = phytium_read(keypad, KPSR); + reg_val |= KBD_STAT_KPKR | KBD_STAT_KRSS; + phytium_write(keypad, reg_val, KPSR); + + reg_val = phytium_read(keypad, KPSR); + reg_val |= KBD_STAT_KRIE; + reg_val &= ~KBD_STAT_KDIE; + phytium_write(keypad, reg_val, KPSR); + } +} + +static irqreturn_t phytium_keypad_irq_handler(int irq, void *dev_id) +{ + struct phytium_keypad *keypad = dev_id; + u32 reg_val; + + reg_val = phytium_read(keypad, KPSR); + /* Disable both interrupt types */ + reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE); + /* Clear interrupts status bits */ + reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD; + phytium_write(keypad, reg_val, KPSR); + + if (keypad->enabled) { + /* The matrix is supposed to be changed */ + keypad->stable_count = 0; + + /* Schedule the scanning procedure near in the future */ + mod_timer(&keypad->check_matrix_timer, + jiffies + msecs_to_jiffies(2)); + } + + return IRQ_HANDLED; +} + +static void phytium_keypad_config(struct phytium_keypad *keypad) +{ + u32 reg_val; + + /* + * Include enabled rows in interrupt generation (KPCR[15:0]) + * Configure keypad columns as open-drain (KPCR[31:16]) + */ + reg_val = phytium_read(keypad, KPCR); + reg_val |= keypad->rows_en_mask & 0xffff; /* rows */ + reg_val |= (keypad->cols_en_mask & 0xffff) << 16; /* cols */ + phytium_write(keypad, reg_val, KPCR); + + /* Configure columns as output, output 0 */ + reg_val = 0; + reg_val |= (keypad->cols_en_mask & 0xffff) << 16; + phytium_write(keypad, reg_val, KDDR); + phytium_write(keypad, 0x00000000, KPDR); + + /* + * Clear Key Depress and Key Release status bit. + * Clear both synchronizer chain. + */ + reg_val = phytium_read(keypad, KPSR); + reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD | + KBD_STAT_KDSC | KBD_STAT_KRSS; + phytium_write(keypad, reg_val, KPSR); + + /* Enable KDI and disable KRI (avoid false release events). */ + reg_val |= KBD_STAT_KDIE; + reg_val &= ~KBD_STAT_KRIE; + phytium_write(keypad, reg_val, KPSR); +} + +static void phytium_keypad_inhibit(struct phytium_keypad *keypad) +{ + unsigned short reg_val; + + /* Inhibit KDI and KRI interrupts. */ + reg_val = phytium_read(keypad, KPSR); + reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE); + reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD; + phytium_write(keypad, reg_val, KPSR); +} + +static void phytium_keypad_close(struct input_dev *dev) +{ + struct phytium_keypad *keypad = input_get_drvdata(dev); + + /* Mark keypad as being inactive */ + keypad->enabled = false; + synchronize_irq(keypad->irq); + del_timer_sync(&keypad->check_matrix_timer); + + phytium_keypad_inhibit(keypad); +} + +static int phytium_keypad_open(struct input_dev *dev) +{ + struct phytium_keypad *keypad = input_get_drvdata(dev); + + /* We became active from now */ + keypad->enabled = true; + + phytium_keypad_config(keypad); + + /* Sanity control, not all the rows must be activated now. */ + if ((phytium_read(keypad, KPDR) & keypad->rows_en_mask) == 0) { + dev_err(&dev->dev, + "too many keys pressed, control pins initialisation\n"); + goto open_err; + } + + return 0; + +open_err: + phytium_keypad_close(dev); + return -EIO; +} + +#ifdef CONFIG_OF +static const struct of_device_id phytium_keypad_of_match[] = { + { .compatible = "phytium,keypad", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, phytium_keypad_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_keypad_acpi_match[] = { + { "PHYT0028", 0}, + {} +}; +MODULE_DEVICE_TABLE(acpi, phytium_keypad_acpi_match); +#endif + +static int phytium_keypad_probe(struct platform_device *pdev) +{ + const struct matrix_keymap_data *keymap_data = dev_get_platdata(&pdev->dev); + struct phytium_keypad *keypad; + struct input_dev *input_dev; + struct resource *res; + int irq, error, i, row, col; + + if (!keymap_data && !pdev->dev.of_node && !has_acpi_companion(&pdev->dev)) { + dev_err(&pdev->dev, "no keymap defined\n"); + return -EINVAL; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq defined in platform data\n"); + return irq; + } + + input_dev = devm_input_allocate_device(&pdev->dev); + if (!input_dev) { + dev_err(&pdev->dev, "failed to allocate the input device\n"); + return -ENOMEM; + } + + keypad = devm_kzalloc(&pdev->dev, sizeof(*keypad), GFP_KERNEL); + if (!keypad) + return -ENOMEM; + + keypad->input_dev = input_dev; + keypad->irq = irq; + keypad->stable_count = 0; + + timer_setup(&keypad->check_matrix_timer, + phytium_keypad_check_for_events, 0); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + keypad->mmio_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(keypad->mmio_base)) + return PTR_ERR(keypad->mmio_base); + + /* Init the Input device */ + input_dev->name = pdev->name; + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &pdev->dev; + input_dev->open = phytium_keypad_open; + input_dev->close = phytium_keypad_close; + + error = matrix_keypad_parse_properties(&pdev->dev, &keypad->n_rows, &keypad->n_cols); + if (error) { + dev_err(&pdev->dev, "failed to parse phytium kp params\n"); + return error; + } + + error = matrix_keypad_build_keymap(keymap_data, NULL, + keypad->n_rows, + keypad->n_cols, + keypad->keycodes, input_dev); + if (error) { + dev_err(&pdev->dev, "failed to build keymap\n"); + return error; + } + + keypad->row_shift = get_count_order(keypad->n_cols); + + /* Search for rows and cols enabled */ + for (row = 0; row < keypad->n_rows; row++) { + for (col = 0; col < keypad->n_cols; col++) { + i = MATRIX_SCAN_CODE(row, col, keypad->row_shift); + if (keypad->keycodes[i] != KEY_RESERVED) { + keypad->rows_en_mask |= 1 << row; + keypad->cols_en_mask |= 1 << col; + } + } + } + + __set_bit(EV_REP, input_dev->evbit); + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + input_set_drvdata(input_dev, keypad); + + phytium_keypad_inhibit(keypad); + + error = devm_request_irq(&pdev->dev, irq, phytium_keypad_irq_handler, 0, + pdev->name, keypad); + if (error) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + return error; + } + + /* Register the input device */ + error = input_register_device(input_dev); + if (error) { + dev_err(&pdev->dev, "failed to register input device\n"); + return error; + } + + platform_set_drvdata(pdev, keypad); + device_init_wakeup(&pdev->dev, 1); + + return 0; +} + +static int phytium_keypad_remove(struct platform_device *pdev) +{ + struct phytium_keypad *keypad = platform_get_drvdata(pdev); + + input_unregister_device(keypad->input_dev); + devm_kfree(&pdev->dev, keypad); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int phytium_keypad_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct phytium_keypad *keypad = platform_get_drvdata(pdev); + struct input_dev *input_dev = keypad->input_dev; + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + phytium_keypad_inhibit(keypad); + + mutex_unlock(&input_dev->mutex); + + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(keypad->irq); + + return 0; +} + +static int phytium_keypad_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct phytium_keypad *keypad = platform_get_drvdata(pdev); + struct input_dev *input_dev = keypad->input_dev; + int ret = 0; + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(keypad->irq); + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + phytium_keypad_config(keypad); + + mutex_unlock(&input_dev->mutex); + + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_keypad_pm_ops, phytium_keypad_suspend, phytium_keypad_resume); + +static struct platform_driver phytium_keypad_driver = { + .driver = { + .name = "phytium-keypad", + .pm = &phytium_keypad_pm_ops, + .of_match_table = of_match_ptr(phytium_keypad_of_match), + .acpi_match_table = ACPI_PTR(phytium_keypad_acpi_match), + }, + .probe = phytium_keypad_probe, + .remove = phytium_keypad_remove, +}; +module_platform_driver(phytium_keypad_driver); + +MODULE_AUTHOR("Song Wenting "); +MODULE_DESCRIPTION("PHYTIUM Keypad Port Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:phytium-keypad"); diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig index 17edc1597446f2643b362d9bee123295d336dbb2..50b5da6b68a832b4ae4adff0eb7ab93568f46d81 100644 --- a/drivers/input/serio/Kconfig +++ b/drivers/input/serio/Kconfig @@ -40,6 +40,18 @@ config SERIO_I8042 To compile this driver as a module, choose M here: the module will be called i8042. +config SERIO_PHYTIUM_PS2 + depends on SERIO + tristate "PHYTIUM PS/2 (keyboard and mouse)" + default y if ARCH_PHYTIUM + depends on PCI + help + This selects support for the PS/2 Host Controller on + Phytium SoCs. + + To compile this driver as a module, choose M here: the + module will be called phytium-ps2. + config SERIO_SERPORT tristate "Serial port line discipline" default y diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile index 6d97bad7b844eccb972f1e6cc1f21776e46511f9..9585918221de5fcb8c2b4fe8b34deb99fc2698dc 100644 --- a/drivers/input/serio/Makefile +++ b/drivers/input/serio/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_SERIO) += serio.o obj-$(CONFIG_SERIO_I8042) += i8042.o +obj-$(CONFIG_SERIO_PHYTIUM_PS2) += phytium-ps2.o obj-$(CONFIG_SERIO_PARKBD) += parkbd.o obj-$(CONFIG_SERIO_SERPORT) += serport.o obj-$(CONFIG_SERIO_CT82C710) += ct82c710.o diff --git a/drivers/input/serio/i8042-phytiumio.h b/drivers/input/serio/i8042-phytiumio.h new file mode 100644 index 0000000000000000000000000000000000000000..c106ce1f6571e1fb4467820747f62d4833d1ff44 --- /dev/null +++ b/drivers/input/serio/i8042-phytiumio.h @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _I8042_PHYTIUMIO_H +#define _I8042_PHYTIUMIO_H + +#include +#include +#include +#include +#include + +/* + * 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. + */ + +/* + * Names. + */ + +#define I8042_KBD_PHYS_DESC "isa0060/serio0" +#define I8042_AUX_PHYS_DESC "isa0060/serio1" +#define I8042_MUX_PHYS_DESC "isa0060/serio%d" + +/* + * IRQs. + */ +static int kbd_irq; +static int aux_irq; + +#define I8042_KBD_IRQ kbd_irq +#define I8042_AUX_IRQ aux_irq + +static u32 i8042_data_port = 0x60UL; +static u32 i8042_command_port = 0x64UL; +static u32 i8042_status_port = 0x64UL; + +/* + * Register numbers. + */ +#define I8042_COMMAND_REG i8042_command_port +#define I8042_STATUS_REG i8042_status_port +#define I8042_DATA_REG i8042_data_port + +#define LPC_STATUS_REG 0xFFF4 +#define LPC_INTERRUPT_REG 0xFFF0 +#define LPC_IRQMODE_REG 0xFFE8 + +static inline int i8042_read_data(void) +{ + return ft_lpc_read(I8042_DATA_REG); +} + +static inline int i8042_read_status(void) +{ + return ft_lpc_read(I8042_STATUS_REG); +} + +static inline void i8042_write_data(int val) +{ + ft_lpc_write(val, I8042_DATA_REG); +} + +static inline void i8042_write_command(int val) +{ + ft_lpc_write(val, I8042_COMMAND_REG); +} + +static const struct acpi_device_id i8042_acpi_match[] = { + { "KBCI8042", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, i8042_acpi_match); + +static inline int i8042_platform_init(void) +{ + if (!acpi_dev_present(i8042_acpi_match[0].id, NULL, -1) && + !of_find_compatible_node(NULL, NULL, "phytium,i8042")) + return -ENODEV; + + kbd_irq = phytium_lpc_irq_find_mapping(PHYTIUM_LPC_SIRQ_BIT_KBD); + aux_irq = phytium_lpc_irq_find_mapping(PHYTIUM_LPC_SIRQ_BIT_AUX); + + if (!kbd_irq && !aux_irq) { + pr_err("Can't get i8042 kbd/aux irq\n"); + return -EINVAL; + } + + if (of_find_compatible_node(NULL, NULL, "czc,laptop")) { + i8042_data_port = 0x180UL; + i8042_command_port = 0x190UL; + i8042_status_port = 0x190UL; + } + + i8042_reset = 1; + + return 0; +} + +static inline void i8042_platform_exit(void) +{ + return; +} + +#endif /* _I8042_FT1500A_H */ diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c index 29340f8095bb25e838e73767cab61701adfd5b6b..57f39c8695c3dc36b83515dd3214f2f29ac926d6 100644 --- a/drivers/input/serio/i8042.c +++ b/drivers/input/serio/i8042.c @@ -758,6 +758,7 @@ static irqreturn_t i8042_aux_test_irq(int irq, void *dev_id) int ret = 0; spin_lock_irqsave(&i8042_lock, flags); + str = i8042_read_status(); if (str & I8042_STR_OBF) { data = i8042_read_data(); @@ -1517,6 +1518,7 @@ static int i8042_setup_kbd(void) free_irq(I8042_KBD_IRQ, i8042_platform_device); err_free_port: i8042_free_kbd_port(); + return error; } diff --git a/drivers/input/serio/i8042.h b/drivers/input/serio/i8042.h index adb5173372d3ee6d83d2164f0c6bd434cba9975f..6b73e4caa24871e9aaedfc4b616dd893eec5db6b 100644 --- a/drivers/input/serio/i8042.h +++ b/drivers/input/serio/i8042.h @@ -21,6 +21,8 @@ #include "i8042-sparcio.h" #elif defined(CONFIG_X86) || defined(CONFIG_IA64) || defined(CONFIG_LOONGARCH) #include "i8042-acpipnpio.h" +#elif defined(CONFIG_ARM64) +#include "i8042-phytiumio.h" #else #include "i8042-io.h" #endif diff --git a/drivers/input/serio/phytium-ps2.c b/drivers/input/serio/phytium-ps2.c new file mode 100644 index 0000000000000000000000000000000000000000..d08433d954d704ed79f8067d242de564dd47d703 --- /dev/null +++ b/drivers/input/serio/phytium-ps2.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Phytium PS/2 keyboard controller driver. + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "phytium_ps2_pci" + +#define REG_STAT 0x0 +#define REG_STAT_TX_TIMEOUT 0x1 +#define REG_STAT_RX_TIMEOUT 0x2 +#define REG_STAT_TX_FULL 0x4 +#define REG_CTRL 0x4 +#define REG_CTRL_RESET 0x1 +#define REG_CTRL_TX_TIMEOUT 0x2 +#define REG_CTRL_RX_TIMEOUT 0x4 +#define REG_CTRL_RX_INTR 0x8 +#define REG_INTR 0x8 +#define REG_INTR_TIMEOUT 0x1 +#define REG_INTR_RX 0x2 +#define REG_TX 0xc +#define REG_RX 0x10 +#define REG_TIMER_VAL 0x14 + +#define REG_CTRL_ENABLE (REG_CTRL_TX_TIMEOUT|REG_CTRL_RX_TIMEOUT|REG_CTRL_RX_INTR) +#define REG_DATA_PARITY 0x100 + +#define STAT_RX_COUNTER(stat) ((stat >> 8) & 0x1f) + +struct phytium_ps2_data { + void __iomem *base; + struct serio *io; + struct pci_dev *dev; +}; + +static irqreturn_t phytium_ps2_irq(int irq, void *devid) +{ + struct phytium_ps2_data *ps2if = devid; + u32 status, scancode, val = 0; + unsigned int flag; + int i, rxcount; + + status = readl(ps2if->base + REG_STAT); + if (!status) + return IRQ_NONE; + + /* Check if there is timeout interrupt */ + if (status & (REG_STAT_RX_TIMEOUT|REG_STAT_TX_TIMEOUT)) + val |= REG_INTR_TIMEOUT; + + rxcount = STAT_RX_COUNTER(status); + for (i = 0; i < rxcount; i++) { + scancode = readl(ps2if->base + REG_RX) & 0x1ff; + + if (rxcount <= 16 && scancode != 0x1ff) { + flag = ((scancode & REG_DATA_PARITY) ? SERIO_PARITY : 0); + serio_interrupt(ps2if->io, scancode & 0xff, flag); + } + } + + val |= REG_INTR_RX; + writel(val, ps2if->base + REG_INTR); + + return IRQ_HANDLED; +} + +int phytium_ps2_write(struct serio *serio, unsigned char val) +{ + struct phytium_ps2_data *ps2if = serio->port_data; + unsigned int stat; + + do { + stat = readl(ps2if->base + REG_STAT); + cpu_relax(); + } while (stat & REG_STAT_TX_FULL); + + writel(val, ps2if->base + REG_TX); + + return 0; +} + +int phytium_ps2_open(struct serio *io) +{ + struct phytium_ps2_data *ps2if = io->port_data; + + writel(REG_CTRL_RESET, ps2if->base + REG_CTRL); + /* Wait 4ms for the controller to be reset */ + usleep_range(4000, 6000); + writel(REG_CTRL_ENABLE, ps2if->base + REG_CTRL); + + return 0; +} + +void phytium_ps2_close(struct serio *io) +{ + struct phytium_ps2_data *ps2if = io->port_data; + + writel(0, ps2if->base + REG_CTRL); +} + +static int phytium_pci_ps2_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct phytium_ps2_data *ps2if; + struct serio *serio; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) + goto out; + + ret = pcim_iomap_regions(pdev, 0x1, DRV_NAME); + if (ret) + goto out; + + ps2if = devm_kzalloc(&pdev->dev, sizeof(struct phytium_ps2_data), GFP_KERNEL); + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!ps2if || !serio) { + ret = -ENOMEM; + goto free; + } + + serio->id.type = SERIO_8042; + serio->write = phytium_ps2_write; + serio->open = phytium_ps2_open; + serio->close = phytium_ps2_close; + strscpy(serio->name, pci_name(pdev), sizeof(serio->name)); + strscpy(serio->phys, dev_name(&pdev->dev), sizeof(serio->phys)); + serio->port_data = ps2if; + serio->dev.parent = &pdev->dev; + ps2if->io = serio; + ps2if->dev = pdev; + ps2if->base = pcim_iomap_table(pdev)[0]; + + ret = devm_request_irq(&pdev->dev, pdev->irq, phytium_ps2_irq, + IRQF_SHARED, DRV_NAME, ps2if); + if (ret) { + dev_err(&pdev->dev, "could not request IRQ %d\n", pdev->irq); + goto free; + } + + pci_set_drvdata(pdev, ps2if); + serio_register_port(ps2if->io); + + return 0; + +free: + kfree(serio); +out: + return ret; +} + +static void phytium_pci_ps2_remove(struct pci_dev *pdev) +{ + struct phytium_ps2_data *ps2if = pci_get_drvdata(pdev); + + serio_unregister_port(ps2if->io); + pcim_iounmap_regions(pdev, 0x1); +} + +static const struct pci_device_id phytium_pci_ps2_ids[] = { + { PCI_VDEVICE(PHYTIUM, 0xdc34) }, + {}, +}; +MODULE_DEVICE_TABLE(pci, phytium_pci_ps2_ids); + +static struct pci_driver phytium_pci_ps2_driver = { + .name = DRV_NAME, + .id_table = phytium_pci_ps2_ids, + .probe = phytium_pci_ps2_probe, + .remove = phytium_pci_ps2_remove, +}; +module_pci_driver(phytium_pci_ps2_driver); + +MODULE_AUTHOR("Cheng Quan "); +MODULE_DESCRIPTION("Phytium PCI PS/2 controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 6d63246cc18488cdb77cca34e507bf43a8f42611..fed04b360331b50b53d2b2a565648033a9b496d6 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -3122,6 +3122,13 @@ static void arm_smmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg) doorbell = (((u64)msg->address_hi) << 32) | msg->address_lo; doorbell &= MSI_CFG0_ADDR_MASK; +#ifdef CONFIG_PM_SLEEP + /* Saves the msg (base addr of msi irq) and restores it during resume */ + desc->msg.address_lo = msg->address_lo; + desc->msg.address_hi = msg->address_hi; + desc->msg.data = msg->data; +#endif + writeq_relaxed(doorbell, smmu->base + cfg[0]); writel_relaxed(msg->data, smmu->base + cfg[1]); writel_relaxed(ARM_SMMU_MEMATTR_DEVICE_nGnRE, smmu->base + cfg[2]); @@ -3164,11 +3171,53 @@ static void arm_smmu_setup_msis(struct arm_smmu_device *smmu) devm_add_action_or_reset(dev, arm_smmu_free_msis, dev); } -static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu) +#ifdef CONFIG_PM_SLEEP +static void arm_smmu_resume_msis(struct arm_smmu_device *smmu) +{ + struct msi_desc *desc; + struct device *dev = smmu->dev; + + if (!dev->msi.domain) + return; + + msi_for_each_desc(desc, dev, MSI_DESC_ASSOCIATED) { + switch (desc->msi_index) { + case EVTQ_MSI_INDEX: + case GERROR_MSI_INDEX: + case PRIQ_MSI_INDEX: { + phys_addr_t *cfg = arm_smmu_msi_cfg[desc->msi_index]; + struct msi_msg *msg = &desc->msg; + phys_addr_t doorbell = (((u64)msg->address_hi) << 32) | msg->address_lo; + + doorbell &= MSI_CFG0_ADDR_MASK; + writeq_relaxed(doorbell, smmu->base + cfg[0]); + writel_relaxed(msg->data, smmu->base + cfg[1]); + writel_relaxed(ARM_SMMU_MEMATTR_DEVICE_nGnRE, + smmu->base + cfg[2]); + break; + } + default: + break; + } + } +} +#else +static void arm_smmu_resume_msis(struct arm_smmu_device *smmu) +{ +} +#endif + +static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu, bool resume) { int irq, ret; - arm_smmu_setup_msis(smmu); + if (!resume) + arm_smmu_setup_msis(smmu); + else { + /* The irq doesn't need to be re-requested during resume */ + arm_smmu_resume_msis(smmu); + return; + } /* Request interrupt lines */ irq = smmu->evtq.q.irq; @@ -3210,7 +3259,7 @@ static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu) } } -static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu) +static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu, bool resume) { int ret, irq; u32 irqen_flags = IRQ_CTRL_EVTQ_IRQEN | IRQ_CTRL_GERROR_IRQEN; @@ -3237,7 +3286,7 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu) if (ret < 0) dev_warn(smmu->dev, "failed to enable combined irq\n"); } else - arm_smmu_setup_unique_irqs(smmu); + arm_smmu_setup_unique_irqs(smmu, resume); if (smmu->features & ARM_SMMU_FEAT_PRI) irqen_flags |= IRQ_CTRL_PRIQ_IRQEN; @@ -3262,7 +3311,7 @@ static int arm_smmu_device_disable(struct arm_smmu_device *smmu) return ret; } -static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool bypass) +static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool resume) { int ret; u32 reg, enables; @@ -3370,7 +3419,7 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool bypass) } } - ret = arm_smmu_setup_irqs(smmu); + ret = arm_smmu_setup_irqs(smmu, resume); if (ret) { dev_err(smmu->dev, "failed to setup irqs\n"); return ret; @@ -3380,7 +3429,7 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool bypass) enables &= ~(CR0_EVTQEN | CR0_PRIQEN); /* Enable the SMMU interface, or ensure bypass */ - if (!bypass || disable_bypass) { + if (!smmu->bypass || disable_bypass) { enables |= CR0_SMMUEN; } else { ret = arm_smmu_update_gbpa(smmu, 0, GBPA_ABORT); @@ -3772,6 +3821,24 @@ static void arm_smmu_rmr_install_bypass_ste(struct arm_smmu_device *smmu) iort_put_rmr_sids(dev_fwnode(smmu->dev), &rmr_list); } +#ifdef CONFIG_PM_SLEEP +static int arm_smmu_suspend(struct device *dev) +{ + /* + * The smmu is powered off and related registers are automatically + * cleared when suspend. No need to do anything. + */ + return 0; +} +static int arm_smmu_resume(struct device *dev) +{ + struct arm_smmu_device *smmu = dev_get_drvdata(dev); + + arm_smmu_device_reset(smmu, true); + return 0; +} +#endif + static int arm_smmu_device_probe(struct platform_device *pdev) { int irq, ret; @@ -3779,7 +3846,6 @@ static int arm_smmu_device_probe(struct platform_device *pdev) resource_size_t ioaddr; struct arm_smmu_device *smmu; struct device *dev = &pdev->dev; - bool bypass; smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL); if (!smmu) @@ -3795,7 +3861,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev) } /* Set bypass mode according to firmware probing result */ - bypass = !!ret; + smmu->bypass = !!ret; /* Base address */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -3859,7 +3925,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev) arm_smmu_rmr_install_bypass_ste(smmu); /* Reset the device */ - ret = arm_smmu_device_reset(smmu, bypass); + ret = arm_smmu_device_reset(smmu, false); if (ret) return ret; @@ -3909,10 +3975,21 @@ static void arm_smmu_driver_unregister(struct platform_driver *drv) platform_driver_unregister(drv); } +#ifdef CONFIG_PM_SLEEP +static const struct dev_pm_ops arm_smmu_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(arm_smmu_suspend, + arm_smmu_resume) +}; +#define ARM_SMMU_PM_OPS (&arm_smmu_pm_ops) +#else +#define ARM_SMMU_PM_OPS NULL +#endif + static struct platform_driver arm_smmu_driver = { .driver = { .name = "arm-smmu-v3", .of_match_table = arm_smmu_of_match, + .pm = ARM_SMMU_PM_OPS, .suppress_bind_attrs = true, }, .probe = arm_smmu_device_probe, diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index f653c3dfed477bc3ef6cab90165fe67012ae76fe..13dd0eff2e2f457d5fd34625becdd042efd8453b 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -686,6 +686,7 @@ struct arm_smmu_device { struct rb_root streams; struct mutex streams_mutex; + bool bypass; }; struct arm_smmu_stream { diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 6c9efa83f580cea5b3c4c4fdffe0efbb38fdbe82..160a670f5fdb19dd23f3e298c9adda84e6e48262 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -58,11 +58,9 @@ config ARM_GIC_V3_ITS_FSL_MC config ARM_GIC_PHYTIUM_2500 bool - select IRQ_DOMAIN select IRQ_DOMAIN_HIERARCHY select PARTITION_PERCPU select GENERIC_IRQ_EFFECTIVE_AFF_MASK - select GENERIC_MSI_IRQ_DOMAIN config ARM_NVIC bool @@ -687,4 +685,12 @@ config SUNPLUS_SP7021_INTC chained controller, routing all interrupt source in P-Chip to the primary controller on C-Chip. +config PHYTIUM_IXIC + bool "Phytium SoC PCI Legacy Interrupt Controller" + depends on ARCH_PHYTIUM + select IRQ_DOMAIN + select IRQ_DOMAIN_HIERARCHY + help + This enables support PCI Legacy Interrupt on Phytium SoC. + endmenu diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index b0828f0737d0a43b464f6853b336a93a6296e6d9..8a72dc73346dea070f9f2b8c09c30a657734b01e 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -105,7 +105,7 @@ obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o obj-$(CONFIG_TI_PRUSS_INTC) += irq-pruss-intc.o -obj-$(CONFIG_IRQ_LOONGARCH_CPU) += irq-loongarch-cpu.o +obj-$(CONFIG_IRQ_LOONGARCH_CPU) += irq-loongarch-cpu.o irq-loongarch-avec.o obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.o obj-$(CONFIG_LOONGSON_EIOINTC) += irq-loongson-eiointc.o obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o @@ -121,3 +121,4 @@ obj-$(CONFIG_IRQ_IDT3243X) += irq-idt3243x.o obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o obj-$(CONFIG_MCHP_EIC) += irq-mchp-eic.o obj-$(CONFIG_SUNPLUS_SP7021_INTC) += irq-sp7021-intc.o +obj-$(CONFIG_PHYTIUM_IXIC) += irq-phytium-ixic.o diff --git a/drivers/irqchip/irq-gic-phytium-2500-its.c b/drivers/irqchip/irq-gic-phytium-2500-its.c index 4f8ee53667419737d4c00a44c58f326ed777f1ba..8e72c401ff98d5c303b3c689aca0f2e78b8ff8d2 100644 --- a/drivers/irqchip/irq-gic-phytium-2500-its.c +++ b/drivers/irqchip/irq-gic-phytium-2500-its.c @@ -1,23 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (C) 2022 Phytium Corporation. - * Author: - * Wang Yinfeng - * Chen Baozi - * Chen Siyu - * Cui Fulong - * Li Yuting - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * Copyright (C) 2020-2023, Phytium Technology Co., Ltd */ #include @@ -27,9 +10,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -58,7 +41,9 @@ #define ITS_FLAGS_CMDQ_NEEDS_FLUSHING (1ULL << 0) #define ITS_FLAGS_WORKAROUND_CAVIUM_22375 (1ULL << 1) #define ITS_FLAGS_WORKAROUND_CAVIUM_23144 (1ULL << 2) -#define ITS_FLAGS_FORCE_NON_SHAREABLE (1ULL << 3) + +#define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING (1 << 0) +#define RDIST_FLAGS_RD_TABLES_PREALLOCATED (1 << 1) #define RD_LOCAL_LPI_ENABLED BIT(0) #define RD_LOCAL_PENDTABLE_PREALLOCATED BIT(1) @@ -196,7 +181,7 @@ struct cpu_lpi_count { atomic_t unmanaged; }; -static DEFINE_PER_CPU(struct cpu_lpi_count, cpu_lpi_count_ft2500); +static DEFINE_PER_CPU(struct cpu_lpi_count, cpu_lpi_count); static LIST_HEAD(its_nodes); static DEFINE_RAW_SPINLOCK(its_lock); @@ -286,24 +271,13 @@ static void vpe_to_cpuid_unlock(struct its_vpe *vpe, unsigned long flags) raw_spin_unlock_irqrestore(&vpe->vpe_lock, flags); } -static struct irq_chip its_vpe_irq_chip; - static int irq_to_cpuid_lock(struct irq_data *d, unsigned long *flags) { - struct its_vpe *vpe = NULL; + struct its_vlpi_map *map = get_vlpi_map(d); int cpu; - if (d->chip == &its_vpe_irq_chip) { - vpe = irq_data_get_irq_chip_data(d); - } else { - struct its_vlpi_map *map = get_vlpi_map(d); - - if (map) - vpe = map->vpe; - } - - if (vpe) { - cpu = vpe_to_cpuid_lock(vpe, flags); + if (map) { + cpu = vpe_to_cpuid_lock(map->vpe, flags); } else { /* Physical LPIs are already locked via the irq_desc lock */ struct its_device *its_dev = irq_data_get_irq_chip_data(d); @@ -318,19 +292,10 @@ static int irq_to_cpuid_lock(struct irq_data *d, unsigned long *flags) static void irq_to_cpuid_unlock(struct irq_data *d, unsigned long flags) { - struct its_vpe *vpe = NULL; - - if (d->chip == &its_vpe_irq_chip) { - vpe = irq_data_get_irq_chip_data(d); - } else { - struct its_vlpi_map *map = get_vlpi_map(d); - - if (map) - vpe = map->vpe; - } + struct its_vlpi_map *map = get_vlpi_map(d); - if (vpe) - vpe_to_cpuid_unlock(vpe, flags); + if (map) + vpe_to_cpuid_unlock(map->vpe, flags); } static struct its_collection *valid_col(struct its_collection *col) @@ -670,7 +635,10 @@ static struct its_collection *its_build_mapti_cmd(struct its_node *its, col = dev_event_to_col(desc->its_mapti_cmd.dev, desc->its_mapti_cmd.event_id); - col->col_id = col->col_id % 64; + if (is_kdump_kernel()) + col->col_id = col->col_id % 65; + else + col->col_id = col->col_id % 64; its_encode_cmd(cmd, GITS_CMD_MAPTI); its_encode_devid(cmd, desc->its_mapti_cmd.dev->device_id); @@ -1468,28 +1436,13 @@ static void wait_for_syncr(void __iomem *rdbase) cpu_relax(); } -static void __direct_lpi_inv(struct irq_data *d, u64 val) -{ - void __iomem *rdbase; - unsigned long flags; - int cpu; - - /* Target the redistributor this LPI is currently routed to */ - cpu = irq_to_cpuid_lock(d, &flags); - raw_spin_lock(&gic_data_rdist_cpu(cpu)->rd_lock); - - rdbase = per_cpu_ptr(gic_rdists->rdist, cpu)->rd_base; - gic_write_lpir(val, rdbase + GICR_INVLPIR); - wait_for_syncr(rdbase); - - raw_spin_unlock(&gic_data_rdist_cpu(cpu)->rd_lock); - irq_to_cpuid_unlock(d, flags); -} - static void direct_lpi_inv(struct irq_data *d) { struct its_vlpi_map *map = get_vlpi_map(d); + void __iomem *rdbase; + unsigned long flags; u64 val; + int cpu; if (map) { struct its_device *its_dev = irq_data_get_irq_chip_data(d); @@ -1503,7 +1456,15 @@ static void direct_lpi_inv(struct irq_data *d) val = d->hwirq; } - __direct_lpi_inv(d, val); + /* Target the redistributor this LPI is currently routed to */ + cpu = irq_to_cpuid_lock(d, &flags); + raw_spin_lock(&gic_data_rdist_cpu(cpu)->rd_lock); + rdbase = per_cpu_ptr(gic_rdists->rdist, cpu)->rd_base; + gic_write_lpir(val, rdbase + GICR_INVLPIR); + + wait_for_syncr(rdbase); + raw_spin_unlock(&gic_data_rdist_cpu(cpu)->rd_lock); + irq_to_cpuid_unlock(d, flags); } static void lpi_update_config(struct irq_data *d, u8 clr, u8 set) @@ -1572,25 +1533,25 @@ static void its_unmask_irq(struct irq_data *d) static __maybe_unused u32 its_read_lpi_count(struct irq_data *d, int cpu) { if (irqd_affinity_is_managed(d)) - return atomic_read(&per_cpu_ptr(&cpu_lpi_count_ft2500, cpu)->managed); + return atomic_read(&per_cpu_ptr(&cpu_lpi_count, cpu)->managed); - return atomic_read(&per_cpu_ptr(&cpu_lpi_count_ft2500, cpu)->unmanaged); + return atomic_read(&per_cpu_ptr(&cpu_lpi_count, cpu)->unmanaged); } static void its_inc_lpi_count(struct irq_data *d, int cpu) { if (irqd_affinity_is_managed(d)) - atomic_inc(&per_cpu_ptr(&cpu_lpi_count_ft2500, cpu)->managed); + atomic_inc(&per_cpu_ptr(&cpu_lpi_count, cpu)->managed); else - atomic_inc(&per_cpu_ptr(&cpu_lpi_count_ft2500, cpu)->unmanaged); + atomic_inc(&per_cpu_ptr(&cpu_lpi_count, cpu)->unmanaged); } static void its_dec_lpi_count(struct irq_data *d, int cpu) { if (irqd_affinity_is_managed(d)) - atomic_dec(&per_cpu_ptr(&cpu_lpi_count_ft2500, cpu)->managed); + atomic_dec(&per_cpu_ptr(&cpu_lpi_count, cpu)->managed); else - atomic_dec(&per_cpu_ptr(&cpu_lpi_count_ft2500, cpu)->unmanaged); + atomic_dec(&per_cpu_ptr(&cpu_lpi_count, cpu)->unmanaged); } static unsigned int cpumask_pick_least_loaded(struct irq_data *d, @@ -1688,11 +1649,11 @@ static int its_select_cpu(struct irq_data *d, return cpu; } -#define MAX_MARS3_SKT_COUNT 8 +#define MAX_MARS3_SKT_COUNT 8 static int its_cpumask_select(struct its_device *its_dev, - const struct cpumask *mask_val, - const struct cpumask *cpu_mask) + const struct cpumask *mask_val, + const struct cpumask *cpu_mask) { unsigned int skt, skt_id, i; phys_addr_t its_phys_base; @@ -1700,18 +1661,22 @@ static int its_cpumask_select(struct its_device *its_dev, unsigned int skt_cpu_cnt[MAX_MARS3_SKT_COUNT] = {0}; + its_phys_base = its_dev->its->phys_base; + skt_id = (its_phys_base >> 41) & 0x7; + for (i = 0; i < nr_cpu_ids; i++) { skt = (cpu_logical_map(i) >> 16) & 0xff; - if ((skt >= 0) && (skt < MAX_MARS3_SKT_COUNT)) + if ((skt >= 0) && (skt < MAX_MARS3_SKT_COUNT)) { + if ((is_kdump_kernel()) && (skt_id == skt)) + return i; + skt_cpu_cnt[skt]++; - else if (skt != 0xff) + } else if (skt != 0xff) { pr_err("socket address: %d is out of range.", skt); + } } - its_phys_base = its_dev->its->phys_base; - skt_id = (its_phys_base >> 41) & 0x7; - - if (skt_id != 0) { + if (skt_id) { for (i = 0; i < skt_id; i++) cpus += skt_cpu_cnt[i]; } @@ -1746,6 +1711,7 @@ static int its_set_affinity(struct irq_data *d, const struct cpumask *mask_val, cpu = its_select_cpu(d, mask_val); else cpu = cpumask_pick_least_loaded(d, mask_val); + skt_t2 = (cpu_logical_map(cpu) >> 16) & 0xff; if (skt_t1 != skt_t2) cpu = cpu_idx; @@ -2300,7 +2266,7 @@ static bool gic_check_reserved_range(phys_addr_t addr, unsigned long size) } /* Not found, not a good sign... */ - pr_warn("GIC-2500: Expected reserved range [%pa:%pa], not found\n", + pr_warn("GIC-S2500: Expected reserved range [%pa:%pa], not found\n", &addr, &addr_end); add_taint(TAINT_CRAP, LOCKDEP_STILL_OK); return false; @@ -2345,13 +2311,13 @@ static int __init its_setup_lpi_prop_table(void) LPI_PROPBASE_SZ)); } - pr_info("GIC-2500: using LPI property table @%pa\n", + pr_info("GICv-S2500: using LPI property table @%pa\n", &gic_rdists->prop_table_pa); return its_lpi_init(lpi_id_bits); } -static const char * const its_base_type_string[] = { +static const char *its_base_type_string[] = { [GITS_BASER_TYPE_DEVICE] = "Devices", [GITS_BASER_TYPE_VCPU] = "Virtual CPUs", [GITS_BASER_TYPE_RESERVED3] = "Reserved (3)", @@ -2445,9 +2411,6 @@ static int its_setup_baser(struct its_node *its, struct its_baser *baser, its_write_baser(its, baser, val); tmp = baser->val; - if (its->flags & ITS_FLAGS_FORCE_NON_SHAREABLE) - tmp &= ~GITS_BASER_SHAREABILITY_MASK; - if ((val ^ tmp) & GITS_BASER_SHAREABILITY_MASK) { /* * Shareability didn't stick. Just use @@ -2529,8 +2492,8 @@ static bool its_parse_indirect_baser(struct its_node *its, * feature is not supported by hardware. */ new_order = max_t(u32, get_order(esz << ids), new_order); - if (new_order > MAX_PAGE_ORDER) { - new_order = MAX_PAGE_ORDER; + if (new_order >= MAX_PAGE_ORDER) { + new_order = MAX_PAGE_ORDER - 1; ids = ilog2(PAGE_ORDER_TO_SIZE(new_order) / (int)esz); pr_warn("ITS@%pa: %s Table too large, reduce ids %llu->%u\n", &its->phys_base, its_base_type_string[type], @@ -2700,8 +2663,7 @@ static int its_alloc_tables(struct its_node *its) struct its_node *sibling; WARN_ON(i != 2); - sibling = find_sibling_its(its); - if (sibling != NULL) { + if ((sibling = find_sibling_its(its))) { *baser = sibling->tables[2]; its_write_baser(its, baser, baser->val); continue; @@ -3076,7 +3038,7 @@ static int __init allocate_lpi_tables(void) if ((val & GICR_CTLR_ENABLE_LPIS) && enabled_lpis_allowed()) { gic_rdists->flags |= (RDIST_FLAGS_RD_TABLES_PREALLOCATED | RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING); - pr_info("GIC-2500: Using preallocated redistributor tables\n"); + pr_info("GIC-S2500: Using preallocated redistributor tables\n"); } err = its_setup_lpi_prop_table(); @@ -3103,18 +3065,12 @@ static int __init allocate_lpi_tables(void) return 0; } -static u64 its_clear_vpend_valid(void __iomem *vlpi_base, u64 clr, u64 set) +static u64 read_vpend_dirty_clear(void __iomem *vlpi_base) { u32 count = 1000000; /* 1s! */ bool clean; u64 val; - val = gicr_read_vpendbaser(vlpi_base + GICR_VPENDBASER); - val &= ~GICR_VPENDBASER_Valid; - val &= ~clr; - val |= set; - gicr_write_vpendbaser(val, vlpi_base + GICR_VPENDBASER); - do { val = gicr_read_vpendbaser(vlpi_base + GICR_VPENDBASER); clean = !(val & GICR_VPENDBASER_Dirty); @@ -3125,10 +3081,26 @@ static u64 its_clear_vpend_valid(void __iomem *vlpi_base, u64 clr, u64 set) } } while (!clean && count); - if (unlikely(val & GICR_VPENDBASER_Dirty)) { + if (unlikely(!clean)) pr_err_ratelimited("ITS virtual pending table not cleaning\n"); + + return val; +} + +static u64 its_clear_vpend_valid(void __iomem *vlpi_base, u64 clr, u64 set) +{ + u64 val; + + /* Make sure we wait until the RD is done with the initial scan */ + val = read_vpend_dirty_clear(vlpi_base); + val &= ~GICR_VPENDBASER_Valid; + val &= ~clr; + val |= set; + gicr_write_vpendbaser(val, vlpi_base + GICR_VPENDBASER); + + val = read_vpend_dirty_clear(vlpi_base); + if (unlikely(val & GICR_VPENDBASER_Dirty)) val |= GICR_VPENDBASER_PendingLast; - } return val; } @@ -3176,9 +3148,6 @@ static void its_cpu_init_lpis(void) gicr_write_propbaser(val, rbase + GICR_PROPBASER); tmp = gicr_read_propbaser(rbase + GICR_PROPBASER); - if (gic_rdists->flags & RDIST_FLAGS_FORCE_NON_SHAREABLE) - tmp &= ~GICR_PROPBASER_SHAREABILITY_MASK; - if ((tmp ^ val) & GICR_PROPBASER_SHAREABILITY_MASK) { if (!(tmp & GICR_PROPBASER_SHAREABILITY_MASK)) { /* @@ -3203,9 +3172,6 @@ static void its_cpu_init_lpis(void) gicr_write_pendbaser(val, rbase + GICR_PENDBASER); tmp = gicr_read_pendbaser(rbase + GICR_PENDBASER); - if (gic_rdists->flags & RDIST_FLAGS_FORCE_NON_SHAREABLE) - tmp &= ~GICR_PENDBASER_SHAREABILITY_MASK; - if (!(tmp & GICR_PENDBASER_SHAREABILITY_MASK)) { /* * The HW reports non-shareable, we must remove the @@ -3270,9 +3236,6 @@ static void its_cpu_init_collection(struct its_node *its) { int cpu = smp_processor_id(); u64 target; - unsigned long mpid; - phys_addr_t its_phys_base; - unsigned long skt_id; /* avoid cross node collections and its mapping */ if (its->flags & ITS_FLAGS_WORKAROUND_CAVIUM_23144) { @@ -3284,10 +3247,6 @@ static void its_cpu_init_collection(struct its_node *its) return; } - mpid = cpu_logical_map(cpu); - its_phys_base = its->phys_base; - skt_id = (its_phys_base >> 41) & 0x7; - /* * We now have to bind each collection to its target * redistributor. @@ -3306,7 +3265,10 @@ static void its_cpu_init_collection(struct its_node *its) /* Perform collection mapping */ its->collections[cpu].target_address = target; - its->collections[cpu].col_id = cpu % 64; + if (is_kdump_kernel()) + its->collections[cpu].col_id = cpu % 65; + else + its->collections[cpu].col_id = cpu % 64; its_send_mapc(its, &its->collections[cpu], 1); its_send_invall(its, &its->collections[cpu]); @@ -3667,7 +3629,6 @@ static int its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, irqd = irq_get_irq_data(virq + i); irqd_set_single_target(irqd); irqd_set_affinity_on_activate(irqd); - irqd_set_resend_when_in_progress(irqd); pr_debug("ID:%d pID:%d vID:%d\n", (int)(hwirq + i - its_dev->event_map.lpi_base), (int)(hwirq + i), virq + i); @@ -3677,28 +3638,32 @@ static int its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, } static int its_cpumask_first(struct its_device *its_dev, - const struct cpumask *cpu_mask) + const struct cpumask *cpu_mask) { unsigned int skt, skt_id, i; phys_addr_t its_phys_base; unsigned int cpu, cpus = 0; - unsigned int skt_cpu_cnt[MAX_MARS3_SKT_COUNT] = {0}; + its_phys_base = its_dev->its->phys_base; + skt_id = (its_phys_base >> 41) & 0x7; + for (i = 0; i < nr_cpu_ids; i++) { skt = (cpu_logical_map(i) >> 16) & 0xff; - if ((skt >= 0) && (skt < MAX_MARS3_SKT_COUNT)) + if ((skt >= 0) && (skt < MAX_MARS3_SKT_COUNT)) { + if ((is_kdump_kernel()) && (skt_id == skt)) + return i; + skt_cpu_cnt[skt]++; - else if (skt != 0xff) + } else if (skt != 0xff) { pr_err("socket address: %d is out of range.", skt); + } } - its_phys_base = its_dev->its->phys_base; - skt_id = (its_phys_base >> 41) & 0x7; - - if (skt_id != 0) + if (skt_id) { for (i = 0; i < skt_id; i++) cpus += skt_cpu_cnt[i]; + } cpu = cpumask_first(cpu_mask); if ((cpu > cpus) && (cpu < (cpus + skt_cpu_cnt[skt_id]))) @@ -4068,10 +4033,18 @@ static void its_vpe_send_inv(struct irq_data *d) { struct its_vpe *vpe = irq_data_get_irq_chip_data(d); - if (gic_rdists->has_direct_lpi) - __direct_lpi_inv(d, d->parent_data->hwirq); - else + if (gic_rdists->has_direct_lpi) { + void __iomem *rdbase; + + /* Target the redistributor this VPE is currently known on */ + raw_spin_lock(&gic_data_rdist_cpu(vpe->col_idx)->rd_lock); + rdbase = per_cpu_ptr(gic_rdists->rdist, vpe->col_idx)->rd_base; + gic_write_lpir(d->parent_data->hwirq, rdbase + GICR_INVLPIR); + wait_for_syncr(rdbase); + raw_spin_unlock(&gic_data_rdist_cpu(vpe->col_idx)->rd_lock); + } else { its_vpe_send_cmd(vpe, its_send_inv); + } } static void its_vpe_mask_irq(struct irq_data *d) @@ -4140,7 +4113,7 @@ static struct irq_chip its_vpe_irq_chip = { static struct its_node *find_4_1_its(void) { - static struct its_node *its; + static struct its_node *its = NULL; if (!its) { list_for_each_entry(its, &its_nodes, entry) { @@ -4631,7 +4604,6 @@ static int its_vpe_irq_domain_alloc(struct irq_domain *domain, unsigned int virq irq_domain_set_hwirq_and_chip(domain, virq + i, i, irqchip, vm->vpes[i]); set_bit(i, bitmap); - irqd_set_resend_when_in_progress(irq_get_irq_data(virq + i)); } if (err) { @@ -4830,28 +4802,6 @@ static bool __maybe_unused its_enable_quirk_hip07_161600802(void *data) return true; } -static bool __maybe_unused its_enable_rk3588001(void *data) -{ - struct its_node *its = data; - - if (!of_machine_is_compatible("rockchip,rk3588") && - !of_machine_is_compatible("rockchip,rk3588s")) - return false; - - its->flags |= ITS_FLAGS_FORCE_NON_SHAREABLE; - gic_rdists->flags |= RDIST_FLAGS_FORCE_NON_SHAREABLE; - - return true; -} - -static bool its_set_non_coherent(void *data) -{ - struct its_node *its = data; - - its->flags |= ITS_FLAGS_FORCE_NON_SHAREABLE; - return true; -} - static const struct gic_quirk its_quirks[] = { #ifdef CONFIG_CAVIUM_ERRATUM_22375 { @@ -4898,19 +4848,6 @@ static const struct gic_quirk its_quirks[] = { .init = its_enable_quirk_hip07_161600802, }, #endif -#ifdef CONFIG_ROCKCHIP_ERRATUM_3588001 - { - .desc = "ITS: Rockchip erratum RK3588001", - .iidr = 0x0201743b, - .mask = 0xffffffff, - .init = its_enable_rk3588001, - }, -#endif - { - .desc = "ITS: non-coherent attribute", - .property = "dma-noncoherent", - .init = its_set_non_coherent, - }, { } }; @@ -4920,10 +4857,6 @@ static void its_enable_quirks(struct its_node *its) u32 iidr = readl_relaxed(its->base + GITS_IIDR); gic_enable_quirks(iidr, its_quirks, its); - - if (is_of_node(its->fwnode_handle)) - gic_enable_of_quirks(to_of_node(its->fwnode_handle), - its_quirks, its); } static int its_save_disable(void) @@ -5059,7 +4992,7 @@ static void __init __iomem *its_map_one(struct resource *res, int *err) return NULL; } -static int its_init_domain(struct its_node *its) +static int its_init_domain(struct fwnode_handle *handle, struct its_node *its) { struct irq_domain *inner_domain; struct msi_domain_info *info; @@ -5068,19 +5001,18 @@ static int its_init_domain(struct its_node *its) if (!info) return -ENOMEM; - info->ops = &its_msi_domain_ops; - info->data = its; - - inner_domain = irq_domain_create_hierarchy(its_parent, - its->msi_domain_flags, 0, - its->fwnode_handle, &its_domain_ops, - info); + inner_domain = irq_domain_create_tree(handle, &its_domain_ops, its); if (!inner_domain) { kfree(info); return -ENOMEM; } + inner_domain->parent = its_parent; irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS); + inner_domain->flags |= its->msi_domain_flags; + info->ops = &its_msi_domain_ops; + info->data = its; + inner_domain->host_data = info; return 0; } @@ -5124,7 +5056,8 @@ static int its_init_vpe_domain(void) return 0; } -static int __init its_compute_its_list_map(struct its_node *its) +static int __init its_compute_its_list_map(struct resource *res, + void __iomem *its_base) { int its_number; u32 ctlr; @@ -5138,15 +5071,15 @@ static int __init its_compute_its_list_map(struct its_node *its) its_number = find_first_zero_bit(&its_list_map, GICv4_ITS_LIST_MAX); if (its_number >= GICv4_ITS_LIST_MAX) { pr_err("ITS@%pa: No ITSList entry available!\n", - &its->phys_base); + &res->start); return -EINVAL; } - ctlr = readl_relaxed(its->base + GITS_CTLR); + ctlr = readl_relaxed(its_base + GITS_CTLR); ctlr &= ~GITS_CTLR_ITS_NUMBER; ctlr |= its_number << GITS_CTLR_ITS_NUMBER_SHIFT; - writel_relaxed(ctlr, its->base + GITS_CTLR); - ctlr = readl_relaxed(its->base + GITS_CTLR); + writel_relaxed(ctlr, its_base + GITS_CTLR); + ctlr = readl_relaxed(its_base + GITS_CTLR); if ((ctlr & GITS_CTLR_ITS_NUMBER) != (its_number << GITS_CTLR_ITS_NUMBER_SHIFT)) { its_number = ctlr & GITS_CTLR_ITS_NUMBER; its_number >>= GITS_CTLR_ITS_NUMBER_SHIFT; @@ -5154,50 +5087,75 @@ static int __init its_compute_its_list_map(struct its_node *its) if (test_and_set_bit(its_number, &its_list_map)) { pr_err("ITS@%pa: Duplicate ITSList entry %d\n", - &its->phys_base, its_number); + &res->start, its_number); return -EINVAL; } return its_number; } -static int __init its_probe_one(struct its_node *its) +static int __init its_probe_one(struct resource *res, + struct fwnode_handle *handle, int numa_node) { - u64 baser, tmp; + struct its_node *its; + void __iomem *its_base; + u64 baser, tmp, typer; struct page *page; u32 ctlr; int err; + its_base = its_map_one(res, &err); + if (!its_base) + return err; + + pr_info("ITS %pR\n", res); + + its = kzalloc(sizeof(*its), GFP_KERNEL); + if (!its) { + err = -ENOMEM; + goto out_unmap; + } + + raw_spin_lock_init(&its->lock); + mutex_init(&its->dev_alloc_lock); + INIT_LIST_HEAD(&its->entry); + INIT_LIST_HEAD(&its->its_device_list); + typer = gic_read_typer(its_base + GITS_TYPER); + its->typer = typer; + its->base = its_base; + its->phys_base = res->start; if (is_v4(its)) { - if (!(its->typer & GITS_TYPER_VMOVP)) { - err = its_compute_its_list_map(its); + if (!(typer & GITS_TYPER_VMOVP)) { + err = its_compute_its_list_map(res, its_base); if (err < 0) - goto out; + goto out_free_its; its->list_nr = err; pr_info("ITS@%pa: Using ITS number %d\n", - &its->phys_base, err); + &res->start, err); } else { - pr_info("ITS@%pa: Single VMOVP capable\n", &its->phys_base); + pr_info("ITS@%pa: Single VMOVP capable\n", &res->start); } if (is_v4_1(its)) { - u32 svpet = FIELD_GET(GITS_TYPER_SVPET, its->typer); + u32 svpet = FIELD_GET(GITS_TYPER_SVPET, typer); - its->sgir_base = ioremap(its->phys_base + SZ_128K, SZ_64K); + its->sgir_base = ioremap(res->start + SZ_128K, SZ_64K); if (!its->sgir_base) { err = -ENOMEM; - goto out; + goto out_free_its; } - its->mpidr = readl_relaxed(its->base + GITS_MPIDR); + its->mpidr = readl_relaxed(its_base + GITS_MPIDR); pr_info("ITS@%pa: Using GICv4.1 mode %08x %08x\n", - &its->phys_base, its->mpidr, svpet); + &res->start, its->mpidr, svpet); } } + its->numa_node = numa_node; + page = alloc_pages_node(its->numa_node, GFP_KERNEL | __GFP_ZERO, get_order(ITS_CMD_QUEUE_SZ)); if (!page) { @@ -5206,6 +5164,11 @@ static int __init its_probe_one(struct its_node *its) } its->cmd_base = (void *)page_address(page); its->cmd_write = its->cmd_base; + its->fwnode_handle = handle; + its->get_msi_base = its_irq_get_msi_base; + its->msi_domain_flags = IRQ_DOMAIN_FLAG_ISOLATED_MSI; + + its_enable_quirks(its); err = its_alloc_tables(its); if (err) @@ -5224,9 +5187,6 @@ static int __init its_probe_one(struct its_node *its) gits_write_cbaser(baser, its->base + GITS_CBASER); tmp = gits_read_cbaser(its->base + GITS_CBASER); - if (its->flags & ITS_FLAGS_FORCE_NON_SHAREABLE) - tmp &= ~GITS_CBASER_SHAREABILITY_MASK; - if ((tmp ^ baser) & GITS_CBASER_SHAREABILITY_MASK) { if (!(tmp & GITS_CBASER_SHAREABILITY_MASK)) { /* @@ -5250,7 +5210,7 @@ static int __init its_probe_one(struct its_node *its) ctlr |= GITS_CTLR_ImDe; writel_relaxed(ctlr, its->base + GITS_CTLR); - err = its_init_domain(its); + err = its_init_domain(handle, its); if (err) goto out_free_tables; @@ -5267,8 +5227,11 @@ static int __init its_probe_one(struct its_node *its) out_unmap_sgir: if (its->sgir_base) iounmap(its->sgir_base); -out: - pr_err("ITS@%pa: failed probing (%d)\n", &its->phys_base, err); +out_free_its: + kfree(its); +out_unmap: + iounmap(its_base); + pr_err("ITS@%pa: failed probing (%d)\n", &res->start, err); return err; } @@ -5370,6 +5333,44 @@ static void rdist_memreserve_cpuhp_cleanup_workfn(struct work_struct *work) static DECLARE_WORK(rdist_memreserve_cpuhp_cleanup_work, rdist_memreserve_cpuhp_cleanup_workfn); +static int its_cpu_memreserve_lpi(unsigned int cpu) +{ + struct page *pend_page; + int ret = 0; + + /* This gets to run exactly once per CPU */ + if (gic_data_rdist()->flags & RD_LOCAL_MEMRESERVE_DONE) + return 0; + + pend_page = gic_data_rdist()->pend_page; + if (WARN_ON(!pend_page)) { + ret = -ENOMEM; + goto out; + } + /* + * If the pending table was pre-programmed, free the memory we + * preemptively allocated. Otherwise, reserve that memory for + * later kexecs. + */ + if (gic_data_rdist()->flags & RD_LOCAL_PENDTABLE_PREALLOCATED) { + its_free_pending_table(pend_page); + gic_data_rdist()->pend_page = NULL; + } else { + phys_addr_t paddr = page_to_phys(pend_page); + + WARN_ON(gic_reserve_range(paddr, LPI_PENDBASE_SZ)); + } + +out: + /* Last CPU being brought up gets to issue the cleanup */ + if (!IS_ENABLED(CONFIG_SMP) || + cpumask_equal(&cpus_booted_once_mask, cpu_possible_mask)) + schedule_work(&rdist_memreserve_cpuhp_cleanup_work); + + gic_data_rdist()->flags |= RD_LOCAL_MEMRESERVE_DONE; + return ret; +} + /* Mark all the BASER registers as invalid before they get reprogrammed */ static int __init its_reset_one(struct resource *res) { @@ -5388,59 +5389,14 @@ static int __init its_reset_one(struct resource *res) } static const struct of_device_id its_device_id[] = { - { .compatible = "arm,gic-phytium-2500-its", }, + { .compatible = "arm,gic-s2500-its", }, {}, }; -static struct its_node __init *its_node_init(struct resource *res, - struct fwnode_handle *handle, int numa_node) -{ - void __iomem *its_base; - struct its_node *its; - int err; - - its_base = its_map_one(res, &err); - if (!its_base) - return NULL; - - pr_info("ITS %pR\n", res); - - its = kzalloc(sizeof(*its), GFP_KERNEL); - if (!its) - goto out_unmap; - - raw_spin_lock_init(&its->lock); - mutex_init(&its->dev_alloc_lock); - INIT_LIST_HEAD(&its->entry); - INIT_LIST_HEAD(&its->its_device_list); - - its->typer = gic_read_typer(its_base + GITS_TYPER); - its->base = its_base; - its->phys_base = res->start; - its->get_msi_base = its_irq_get_msi_base; - its->msi_domain_flags = IRQ_DOMAIN_FLAG_ISOLATED_MSI; - - its->numa_node = numa_node; - its->fwnode_handle = handle; - - return its; - -out_unmap: - iounmap(its_base); - return NULL; -} - -static void its_node_destroy(struct its_node *its) -{ - iounmap(its->base); - kfree(its); -} - static int __init its_of_probe(struct device_node *node) { struct device_node *np; struct resource res; - int err; /* * Make sure *all* the ITS are reset before we probe any, as @@ -5450,6 +5406,8 @@ static int __init its_of_probe(struct device_node *node) */ for (np = of_find_matching_node(node, its_device_id); np; np = of_find_matching_node(np, its_device_id)) { + int err; + if (!of_device_is_available(np) || !of_property_read_bool(np, "msi-controller") || of_address_to_resource(np, 0, &res)) @@ -5462,8 +5420,6 @@ static int __init its_of_probe(struct device_node *node) for (np = of_find_matching_node(node, its_device_id); np; np = of_find_matching_node(np, its_device_id)) { - struct its_node *its; - if (!of_device_is_available(np)) continue; if (!of_property_read_bool(np, "msi-controller")) { @@ -5477,17 +5433,7 @@ static int __init its_of_probe(struct device_node *node) continue; } - - its = its_node_init(&res, &np->fwnode, of_node_to_nid(np)); - if (!its) - return -ENOMEM; - - its_enable_quirks(its); - err = its_probe_one(its); - if (err) { - its_node_destroy(its); - return err; - } + its_probe_one(&res, &np->fwnode, of_node_to_nid(np)); } return 0; } @@ -5599,7 +5545,6 @@ static int __init gic_acpi_parse_madt_its(union acpi_subtable_headers *header, { struct acpi_madt_generic_translator *its_entry; struct fwnode_handle *dom_handle; - struct its_node *its; struct resource res; int err; @@ -5611,7 +5556,7 @@ static int __init gic_acpi_parse_madt_its(union acpi_subtable_headers *header, dom_handle = irq_domain_alloc_fwnode(&res.start); if (!dom_handle) { - pr_err("ITS@%pa: Unable to allocate GIC-phytium-2500 ITS domain token\n", + pr_err("ITS@%pa: Unable to allocate GIC-phytium-S2500 ITS domain token\n", &res.start); return -ENOMEM; } @@ -5619,23 +5564,16 @@ static int __init gic_acpi_parse_madt_its(union acpi_subtable_headers *header, err = iort_register_domain_token(its_entry->translation_id, res.start, dom_handle); if (err) { - pr_err("ITS@%pa: Unable to register GIC-phytium-2500 ITS domain token (ITS ID %d) to IORT\n", + pr_err("ITS@%pa: Unable to register GIC-Phytium-S2500 ITS domain token (ITS ID %d) to IORT\n", &res.start, its_entry->translation_id); goto dom_err; } - its = its_node_init(&res, dom_handle, - acpi_get_its_numa_node(its_entry->translation_id)); - if (!its) { - err = -ENOMEM; - goto node_err; - } - - err = its_probe_one(its); + err = its_probe_one(&res, dom_handle, + acpi_get_its_numa_node(its_entry->translation_id)); if (!err) return 0; -node_err: iort_deregister_domain_token(its_entry->translation_id); dom_err: irq_domain_free_fwnode(dom_handle); @@ -5677,6 +5615,28 @@ static void __init its_acpi_probe(void) static void __init its_acpi_probe(void) { } #endif +int __init phytium_its_lpi_memreserve_init(void) +{ + int state; + + if (!efi_enabled(EFI_CONFIG_TABLES)) + return 0; + + if (list_empty(&its_nodes)) + return 0; + + gic_rdists->cpuhp_memreserve_state = CPUHP_INVALID; + state = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, + "irqchip/arm/gic-2500/memreserve:online", + its_cpu_memreserve_lpi, + NULL); + if (state < 0) + return state; + + gic_rdists->cpuhp_memreserve_state = state; + + return 0; +} int __init phytium_its_init(struct fwnode_handle *handle, struct rdists *rdists, struct irq_domain *parent_domain) { diff --git a/drivers/irqchip/irq-gic-phytium-2500.c b/drivers/irqchip/irq-gic-phytium-2500.c index 7214f364636727b8fda1189f1f5cde2cd78f04cc..1488e7bae79fa47e7270f27eb496cf3c3be93e89 100644 --- a/drivers/irqchip/irq-gic-phytium-2500.c +++ b/drivers/irqchip/irq-gic-phytium-2500.c @@ -1,26 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (C) 2022 Phytium Corporation. - * Author: - * Wang Yinfeng - * Chen Baozi - * Chen Siyu - * Cui Fulong - * Li Yuting - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * Copyright (C) 2020-2023, Phytium Technology Co., Ltd */ - #define pr_fmt(fmt) "GIC-2500: " fmt #include @@ -29,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -41,9 +22,6 @@ #include #include #include -#include -#include -#include #include #include @@ -51,6 +29,7 @@ #include #include "irq-gic-common.h" +#include #define MAX_MARS3_SOC_COUNT 8 #define MARS3_ADDR_SKTID_SHIFT 41 @@ -61,18 +40,10 @@ struct gic_dist_desc { unsigned long size; }; -static struct gic_dist_desc mars3_gic_dists[MAX_MARS3_SOC_COUNT] __read_mostly; - -static unsigned int mars3_sockets_bitmap = 0x1; - -#define mars3_irq_to_skt(hwirq) (((hwirq) - 32) % 8) - #define GICD_INT_NMI_PRI (GICD_INT_DEF_PRI & ~0x80) #define FLAGS_WORKAROUND_GICR_WAKER_MSM8996 (1ULL << 0) #define FLAGS_WORKAROUND_CAVIUM_ERRATUM_38539 (1ULL << 1) -#define FLAGS_WORKAROUND_MTK_GICR_SAVE (1ULL << 2) -#define FLAGS_WORKAROUND_ASR_ERRATUM_8601001 (1ULL << 3) #define GIC_IRQ_TYPE_PARTITION (GIC_IRQ_TYPE_LPI + 1) @@ -82,9 +53,14 @@ struct redist_region { bool single_redist; }; +static struct gic_dist_desc mars3_gic_dists[MAX_MARS3_SOC_COUNT] __read_mostly; + +static unsigned int mars3_sockets_bitmap = 0x1; + +#define mars3_irq_to_skt(hwirq) (((hwirq) - 32) % 8) + struct gic_chip_data { struct fwnode_handle *fwnode; - phys_addr_t dist_phys_base; void __iomem *dist_base; struct redist_region *redist_regions; struct rdists rdists; @@ -97,12 +73,6 @@ struct gic_chip_data { struct partition_desc **ppi_descs; }; -#define T241_CHIPS_MAX 4 -static void __iomem *t241_dist_base_alias[T241_CHIPS_MAX] __read_mostly; -static DEFINE_STATIC_KEY_FALSE(gic_nvidia_t241_erratum); - -static DEFINE_STATIC_KEY_FALSE(gic_arm64_2941627_erratum); - static struct gic_chip_data gic_data __read_mostly; static DEFINE_STATIC_KEY_TRUE(supports_deactivate_key); @@ -131,9 +101,14 @@ static DEFINE_STATIC_KEY_TRUE(supports_deactivate_key); * - Figure 4-7 Secure read of the priority field for a Non-secure Group 1 * interrupt. */ -static DEFINE_STATIC_KEY_FALSE(supports_pseudo_nmis_ft2500); +static DEFINE_STATIC_KEY_FALSE(supports_pseudo_nmis); +#ifndef CONFIG_ARM_GIC_V3 +DEFINE_STATIC_KEY_FALSE(gic_nonsecure_priorities); +EXPORT_SYMBOL(gic_nonsecure_priorities); +#else extern struct static_key_false gic_nonsecure_priorities; +#endif /* * When the Non-secure world has access to group 0 interrupts (as a @@ -160,7 +135,7 @@ extern struct static_key_false gic_nonsecure_priorities; static refcount_t *ppi_nmi_refs; static struct gic_kvm_info gic_v3_kvm_info __initdata; -static DEFINE_PER_CPU(bool, has_rss_ft2500); +static DEFINE_PER_CPU(bool, has_rss); #define MPIDR_RS(mpidr) (((mpidr) & 0xF0UL) >> 4) #define gic_data_rdist() (this_cpu_ptr(gic_data.rdists.rdist)) @@ -222,39 +197,6 @@ static inline bool gic_irq_in_rdist(struct irq_data *d) } } -static inline void __iomem *gic_dist_base_alias(struct irq_data *d) -{ - if (static_branch_unlikely(&gic_nvidia_t241_erratum)) { - irq_hw_number_t hwirq = irqd_to_hwirq(d); - u32 chip; - - /* - * For the erratum T241-FABRIC-4, read accesses to GICD_In{E} - * registers are directed to the chip that owns the SPI. The - * the alias region can also be used for writes to the - * GICD_In{E} except GICD_ICENABLERn. Each chip has support - * for 320 {E}SPIs. Mappings for all 4 chips: - * Chip0 = 32-351 - * Chip1 = 352-671 - * Chip2 = 672-991 - * Chip3 = 4096-4415 - */ - switch (__get_intid_range(hwirq)) { - case SPI_RANGE: - chip = (hwirq - 32) / 320; - break; - case ESPI_RANGE: - chip = 3; - break; - default: - unreachable(); - } - return t241_dist_base_alias[chip]; - } - - return gic_data.dist_base; -} - static inline void __iomem *gic_dist_base(struct irq_data *d) { switch (get_intid_range(d)) { @@ -274,11 +216,11 @@ static inline void __iomem *gic_dist_base(struct irq_data *d) } } -static void gic_do_wait_for_rwp(void __iomem *base) +static void gic_do_wait_for_rwp(void __iomem *base, u32 bit) { u32 count = 1000000; /* 1s! */ - while (readl_relaxed(base + GICD_CTLR) & GICD_CTLR_RWP) { + while (readl_relaxed(base + GICD_CTLR) & bit) { count--; if (!count) { pr_err_ratelimited("RWP timeout, gone fishing\n"); @@ -292,13 +234,13 @@ static void gic_do_wait_for_rwp(void __iomem *base) /* Wait for completion of a distributor change */ static void gic_dist_wait_for_rwp(void) { - gic_do_wait_for_rwp(gic_data.dist_base); + gic_do_wait_for_rwp(gic_data.dist_base, GICD_CTLR_RWP); } /* Wait for completion of a redistributor change */ static void gic_redist_wait_for_rwp(void) { - gic_do_wait_for_rwp(gic_data_rdist_rd_base()); + gic_do_wait_for_rwp(gic_data_rdist_rd_base(), GICR_CTLR_RWP); } static void gic_enable_redist(bool enable) @@ -341,24 +283,25 @@ static void gic_enable_redist(bool enable) mpidr = (unsigned long)cpu_logical_map(smp_processor_id()); - if (mpidr & 0xFFFF) // either Aff1 or Aff0 is not zero + /* Either Aff0 or Aff1 is not zero */ + if (mpidr & 0xffff) return; - rbase = rbase + 64 * SZ_128K; // skip 64 Redistributors + /* Skip 64 Redistributors */ + rbase = rbase + 64 * SZ_128K; for (i = 0; i < 4; i++) { val = readl_relaxed(rbase + GICR_WAKER); if (enable) - /* Wake up this CPU redistributor */ val &= ~GICR_WAKER_ProcessorSleep; else val |= GICR_WAKER_ProcessorSleep; writel_relaxed(val, rbase + GICR_WAKER); - if (!enable) { /* Check that GICR_WAKER is writeable */ + if (!enable) { val = readl_relaxed(rbase + GICR_WAKER); if (!(val & GICR_WAKER_ProcessorSleep)) - return; /* No PM support in this redistributor */ + return; } count = 1000000; /* 1s! */ @@ -369,11 +312,12 @@ static void gic_enable_redist(bool enable) cpu_relax(); udelay(1); }; + if (!count) pr_err_ratelimited("CPU MPIDR 0x%lx: redistributor %d failed to %s...\n", - mpidr, 64 + i, enable ? "wakeup" : "sleep"); + mpidr, 64 + i, enable ? "wakeup" : "sleep"); - rbase = rbase + SZ_128K; // next redistributor + rbase = rbase + SZ_128K; } } @@ -433,7 +377,7 @@ static u32 convert_offset_index(struct irq_data *d, u32 offset, u32 *index) static int gic_peek_irq(struct irq_data *d, u32 offset) { void __iomem *base; - u32 index, mask; + u32 index, mask, skt; offset = convert_offset_index(d, offset, &index); mask = 1 << (index % 32); @@ -441,8 +385,6 @@ static int gic_peek_irq(struct irq_data *d, u32 offset) if (gic_irq_in_rdist(d)) base = gic_data_rdist_sgi_base(); else { - unsigned int skt; - skt = mars3_irq_to_skt(gic_irq(d)); base = mars3_gic_dists[skt].dist_base; } @@ -452,13 +394,10 @@ static int gic_peek_irq(struct irq_data *d, u32 offset) static void gic_poke_irq(struct irq_data *d, u32 offset) { - void __iomem *base; - + void __iomem *base, *rbase; unsigned long mpidr; - void __iomem *rbase; int i; - unsigned int skt; - u32 index, mask; + u32 index, mask, skt; offset = convert_offset_index(d, offset, &index); mask = 1 << (index % 32); @@ -471,20 +410,20 @@ static void gic_poke_irq(struct irq_data *d, u32 offset) mpidr = (unsigned long)cpu_logical_map(smp_processor_id()); - if ((mpidr & 0xFFFF) == 0) { // both Aff1 and Aff0 are zero - rbase = base + 64*SZ_128K; // skip 64 Redistributors + if ((mpidr & 0xffff) == 0) { + rbase = base + 64*SZ_128K; for (i = 0; i < 4; i++) { writel_relaxed(mask, rbase + offset + (index / 32) * 4); - gic_do_wait_for_rwp(rbase - SZ_64K); // RD from SGI base + gic_do_wait_for_rwp(rbase - SZ_64K, GICR_CTLR_RWP); rbase = rbase + SZ_128K; } - } // core 0 of each socket + } } else { skt = mars3_irq_to_skt(gic_irq(d)); - base = mars3_gic_dists[skt].dist_base; + base = mars3_gic_dists[skt].dist_base; writel_relaxed(mask, base + offset + (index / 32) * 4); - gic_do_wait_for_rwp(base); + gic_do_wait_for_rwp(base, GICD_CTLR_RWP); } } @@ -517,10 +456,10 @@ static void gic_unmask_irq(struct irq_data *d) gic_poke_irq(d, GICD_ISENABLER); } -static inline bool gic_supports_nmi_ft2500(void) +static inline bool gic_supports_nmi(void) { return IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && - static_branch_likely(&supports_pseudo_nmis_ft2500); + static_branch_likely(&supports_pseudo_nmis); } static int gic_irq_set_irqchip_state(struct irq_data *d, @@ -613,7 +552,7 @@ static int gic_irq_nmi_setup(struct irq_data *d) { struct irq_desc *desc = irq_to_desc(d->irq); - if (!gic_supports_nmi_ft2500()) + if (!gic_supports_nmi()) return -EINVAL; if (gic_peek_irq(d, GICD_ISENABLER)) { @@ -650,7 +589,7 @@ static void gic_irq_nmi_teardown(struct irq_data *d) { struct irq_desc *desc = irq_to_desc(d->irq); - if (WARN_ON(!gic_supports_nmi_ft2500())) + if (WARN_ON(!gic_supports_nmi())) return; if (gic_peek_irq(d, GICD_ISENABLER)) { @@ -679,54 +618,21 @@ static void gic_irq_nmi_teardown(struct irq_data *d) gic_irq_set_prio(d, GICD_INT_DEF_PRI); } -static bool gic_arm64_erratum_2941627_needed(struct irq_data *d) -{ - enum gic_intid_range range; - - if (!static_branch_unlikely(&gic_arm64_2941627_erratum)) - return false; - - range = get_intid_range(d); - - /* - * The workaround is needed if the IRQ is an SPI and - * the target cpu is different from the one we are - * executing on. - */ - return (range == SPI_RANGE || range == ESPI_RANGE) && - !cpumask_test_cpu(raw_smp_processor_id(), - irq_data_get_effective_affinity_mask(d)); -} - static void gic_eoi_irq(struct irq_data *d) { write_gicreg(gic_irq(d), ICC_EOIR1_EL1); isb(); - - if (gic_arm64_erratum_2941627_needed(d)) { - /* - * Make sure the GIC stream deactivate packet - * issued by ICC_EOIR1_EL1 has completed before - * deactivating through GICD_IACTIVER. - */ - dsb(sy); - gic_poke_irq(d, GICD_ICACTIVER); - } } static void gic_eoimode1_eoi_irq(struct irq_data *d) { /* * No need to deactivate an LPI, or an interrupt that - * is is getting forwarded to a vcpu. + * is getting forwarded to a vcpu. */ if (gic_irq(d) >= 8192 || irqd_is_forwarded_to_vcpu(d)) return; - - if (!gic_arm64_erratum_2941627_needed(d)) - gic_write_dir(gic_irq(d)); - else - gic_poke_irq(d, GICD_ICACTIVER); + gic_write_dir(gic_irq(d)); } static int gic_set_type(struct irq_data *d, unsigned int type) @@ -739,7 +645,6 @@ static int gic_set_type(struct irq_data *d, unsigned int type) unsigned long mpidr; range = get_intid_range(d); - /* Interrupt configuration for SGIs can't be changed */ if (range == SGI_RANGE) return type != IRQ_TYPE_EDGE_RISING ? -EINVAL : 0; @@ -754,7 +659,6 @@ static int gic_set_type(struct irq_data *d, unsigned int type) if (gic_irq_in_rdist(d)) { base = gic_data_rdist_sgi_base(); ret = gic_configure_irq(index, type, base + offset, gic_redist_wait_for_rwp); - mpidr = (unsigned long)cpu_logical_map(smp_processor_id()); if ((mpidr & 0xffff) == 0) { @@ -762,7 +666,7 @@ static int gic_set_type(struct irq_data *d, unsigned int type) for (i = 0; i < 4; i++) { ret = gic_configure_irq(index, type, rbase + offset, NULL); - gic_do_wait_for_rwp(rbase - SZ_64K); + gic_do_wait_for_rwp(rbase - SZ_64K, GICR_CTLR_RWP); rbase = rbase + SZ_128K; } } @@ -770,9 +674,10 @@ static int gic_set_type(struct irq_data *d, unsigned int type) skt = mars3_irq_to_skt(gic_irq(d)); base = mars3_gic_dists[skt].dist_base; ret = gic_configure_irq(index, type, base + offset, NULL); - gic_do_wait_for_rwp(base); + gic_do_wait_for_rwp(base, GICD_CTLR_RWP); } + if (ret && (range == PPI_RANGE || range == EPPI_RANGE)) { /* Misconfigured PPIs are usually not fatal */ pr_warn("GIC: PPI INTID%d is secure or misconfigured\n", irq); @@ -794,16 +699,10 @@ static int gic_irq_set_vcpu_affinity(struct irq_data *d, void *vcpu) return 0; } -static u64 gic_cpu_to_affinity(int cpu) +static u64 gic_mpidr_to_affinity(unsigned long mpidr) { - u64 mpidr = cpu_logical_map(cpu); u64 aff; - /* ASR8601 needs to have its affinities shifted down... */ - if (unlikely(gic_data.flags & FLAGS_WORKAROUND_ASR_ERRATUM_8601001)) - mpidr = (MPIDR_AFFINITY_LEVEL(mpidr, 1) | - (MPIDR_AFFINITY_LEVEL(mpidr, 2) << 8)); - aff = ((u64)MPIDR_AFFINITY_LEVEL(mpidr, 3) << 32 | MPIDR_AFFINITY_LEVEL(mpidr, 2) << 16 | MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8 | @@ -852,7 +751,7 @@ static inline void gic_complete_ack(u32 irqnr) static bool gic_rpr_is_nmi_prio(void) { - if (!gic_supports_nmi_ft2500()) + if (!gic_supports_nmi()) return false; return unlikely(gic_read_rpr() == GICD_INT_RPR_PRI(GICD_INT_NMI_PRI)); @@ -962,7 +861,7 @@ static void __gic_handle_irq_from_irqsoff(struct pt_regs *regs) static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) { - if (unlikely(gic_supports_nmi_ft2500() && !interrupts_enabled(regs))) + if (unlikely(gic_supports_nmi() && !interrupts_enabled(regs))) __gic_handle_irq_from_irqsoff(regs); else __gic_handle_irq_from_irqson(regs); @@ -1021,7 +920,7 @@ static void __init gic_dist_init(void) /* Disable the distributor */ writel_relaxed(0, base + GICD_CTLR); - gic_do_wait_for_rwp(base); + gic_do_wait_for_rwp(base, GICD_CTLR_RWP); /* * Configure SPIs as non-secure Group-1. This will only matter @@ -1047,9 +946,9 @@ static void __init gic_dist_init(void) for (i = 0; i < GIC_ESPI_NR; i += 4) writel_relaxed(GICD_INT_DEF_PRI_X4, base + GICD_IPRIORITYRnE + i); - /* Now do the common stuff */ + /* Now do the common stuff, and wait for the distributor to drain */ gic_dist_config(base, GIC_LINE_NR, NULL); - gic_do_wait_for_rwp(base); + gic_do_wait_for_rwp(base, GICD_CTLR_RWP); // do sync outside of gic_dist_config val = GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1; if (gic_data.rdists.gicd_typer2 & GICD_TYPER2_nASSGIcap) { @@ -1057,15 +956,14 @@ static void __init gic_dist_init(void) val |= GICD_CTLR_nASSGIreq; } - /* Enable distributor with ARE, Group1, and wait for it to drain */ + /* Enable distributor with ARE, Group1 */ writel_relaxed(val, base + GICD_CTLR); - gic_dist_wait_for_rwp(); - /* + /* * Set all global interrupts to the boot CPU only. ARE must be * enabled. */ - affinity = gic_cpu_to_affinity(smp_processor_id()); + affinity = gic_mpidr_to_affinity(cpu_logical_map(smp_processor_id())); for (i = 32; i < GIC_LINE_NR; i++) gic_write_irouter(affinity, base + GICD_IROUTER + i * 8); @@ -1115,25 +1013,21 @@ static int gic_iterate_rdists(int (*fn)(struct redist_region *, void __iomem *)) static int __gic_populate_rdist(struct redist_region *region, void __iomem *ptr) { - unsigned long mpidr; + unsigned long mpidr = cpu_logical_map(smp_processor_id()); u64 typer; - u32 aff; - u32 aff2_skt; - u32 redist_skt; + u32 aff, aff2_skt, rdist_skt; /* * Convert affinity to a 32bit value that can be matched to * GICR_TYPER bits [63:32]. */ - mpidr = gic_cpu_to_affinity(smp_processor_id()); - aff = (MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8 | - MPIDR_AFFINITY_LEVEL(mpidr, 0)); + MPIDR_AFFINITY_LEVEL(mpidr, 0)); aff2_skt = MPIDR_AFFINITY_LEVEL(mpidr, 2) & 0x7; - redist_skt = (((u64)region->phys_base >> MARS3_ADDR_SKTID_SHIFT) & 0x7); + rdist_skt = (((u64)region->phys_base >> MARS3_ADDR_SKTID_SHIFT) & 0x7); - if (aff2_skt != redist_skt) + if (aff2_skt != rdist_skt) return 1; typer = gic_read_typer(ptr + GICR_TYPER); @@ -1173,7 +1067,7 @@ static int __gic_update_rdist_properties(struct redist_region *region, u64 typer = gic_read_typer(ptr + GICR_TYPER); u32 ctlr = readl_relaxed(ptr + GICR_CTLR); - /* Boot-time cleanup */ + /* Boot-time cleanip */ if ((typer & GICR_TYPER_VLPIS) && (typer & GICR_TYPER_RVPEID)) { u64 val; @@ -1224,7 +1118,7 @@ static void gic_update_rdist_properties(void) gic_iterate_rdists(__gic_update_rdist_properties); if (WARN_ON(gic_data.ppi_nr == UINT_MAX)) gic_data.ppi_nr = 0; - pr_info("GICv3 features: %d PPIs%s%s\n", + pr_info("GIC-2500 features: %d PPIs%s%s\n", gic_data.ppi_nr, gic_data.has_rss ? ", RSS" : "", gic_data.rdists.has_direct_lpi ? ", DirectLPI" : ""); @@ -1245,7 +1139,7 @@ static inline bool gic_dist_security_disabled(void) static void gic_cpu_sys_reg_init(void) { int i, cpu = smp_processor_id(); - u64 mpidr = gic_cpu_to_affinity(cpu); + u64 mpidr = cpu_logical_map(cpu); u64 need_rss = MPIDR_RS(mpidr); bool group0; u32 pribits; @@ -1267,7 +1161,7 @@ static void gic_cpu_sys_reg_init(void) /* Set priority mask register */ if (!gic_prio_masking_enabled()) { write_gicreg(DEFAULT_PMR_VALUE, ICC_PMR_EL1); - } else if (gic_supports_nmi_ft2500()) { + } else if (gic_supports_nmi()) { /* * Mismatch configuration with boot CPU, the system is likely * to die as interrupt masking will not work properly on all @@ -1338,17 +1232,17 @@ static void gic_cpu_sys_reg_init(void) gic_write_grpen1(1); /* Keep the RSS capability status in per_cpu variable */ - per_cpu(has_rss_ft2500, cpu) = !!(gic_read_ctlr() & ICC_CTLR_EL1_RSS); + per_cpu(has_rss, cpu) = !!(gic_read_ctlr() & ICC_CTLR_EL1_RSS); /* Check all the CPUs have capable of sending SGIs to other CPUs */ for_each_online_cpu(i) { - bool have_rss = per_cpu(has_rss_ft2500, i) && per_cpu(has_rss_ft2500, cpu); + bool have_rss = per_cpu(has_rss, i) && per_cpu(has_rss, cpu); - need_rss |= MPIDR_RS(gic_cpu_to_affinity(i)); + need_rss |= MPIDR_RS(cpu_logical_map(i)); if (need_rss && (!have_rss)) pr_crit("CPU%d (%lx) can't SGI CPU%d (%lx), no RSS\n", cpu, (unsigned long)mpidr, - i, (unsigned long)gic_cpu_to_affinity(i)); + i, (unsigned long)cpu_logical_map(i)); } /** @@ -1366,7 +1260,7 @@ static bool gicv3_nolpi; static int __init gicv3_nolpi_cfg(char *buf) { - return kstrtobool(buf, &gicv3_nolpi); + return strtobool(buf, &gicv3_nolpi); } early_param("irqchip.gicv3_nolpi", gicv3_nolpi_cfg); @@ -1404,18 +1298,17 @@ static void gic_cpu_init(void) mpidr = (unsigned long)cpu_logical_map(smp_processor_id()); - if ((mpidr & 0xFFFF) == 0) { // both Aff1 and Aff0 is zero - rbase = rbase + 64*SZ_128K; // skip 64 Redistributors + if ((mpidr & 0xffff) == 0) { + rbase = rbase + 64*SZ_128K; for (i = 0; i < 4; i++) { /* Configure SGIs/PPIs as non-secure Group-1 */ writel_relaxed(~0, rbase + GICR_IGROUPR0); gic_cpu_config(rbase, gic_data.ppi_nr + 16, NULL); - gic_do_wait_for_rwp(rbase - SZ_64K); + gic_do_wait_for_rwp(rbase - SZ_64K, GICR_CTLR_RWP); rbase = rbase + SZ_128K; - } } @@ -1442,11 +1335,9 @@ static u16 gic_compute_target_list(int *base_cpu, const struct cpumask *mask, unsigned long cluster_id) { int next_cpu, cpu = *base_cpu; - unsigned long mpidr; + unsigned long mpidr = cpu_logical_map(cpu); u16 tlist = 0; - mpidr = gic_cpu_to_affinity(cpu); - while (cpu < nr_cpu_ids) { tlist |= 1 << (mpidr & 0xf); @@ -1455,7 +1346,7 @@ static u16 gic_compute_target_list(int *base_cpu, const struct cpumask *mask, goto out; cpu = next_cpu; - mpidr = gic_cpu_to_affinity(cpu); + mpidr = cpu_logical_map(cpu); if (cluster_id != MPIDR_TO_SGI_CLUSTER_ID(mpidr)) { cpu--; @@ -1500,7 +1391,7 @@ static void gic_ipi_send_mask(struct irq_data *d, const struct cpumask *mask) dsb(ishst); for_each_cpu(cpu, mask) { - u64 cluster_id = MPIDR_TO_SGI_CLUSTER_ID(gic_cpu_to_affinity(cpu)); + u64 cluster_id = MPIDR_TO_SGI_CLUSTER_ID(cpu_logical_map(cpu)); u16 tlist; tlist = gic_compute_target_list(&cpu, mask, cluster_id); @@ -1520,11 +1411,13 @@ static void __init gic_smp_init(void) int base_sgi; cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING, - "irqchip/arm/gicv3:starting", + "irqchip/arm/gic_phytium_2500:starting", gic_starting_cpu, NULL); /* Register all 8 non-secure SGIs */ - base_sgi = irq_domain_alloc_irqs(gic_data.domain, 8, NUMA_NO_NODE, &sgi_fwspec); + base_sgi = __irq_domain_alloc_irqs(gic_data.domain, -1, 8, + NUMA_NO_NODE, &sgi_fwspec, + false, NULL); if (WARN_ON(base_sgi <= 0)) return; @@ -1535,22 +1428,26 @@ static int gic_cpumask_select(struct irq_data *d, const struct cpumask *mask_val { unsigned int skt, irq_skt, i; unsigned int cpu, cpus = 0; - unsigned int skt_cpu_cnt[MAX_MARS3_SOC_COUNT] = {0}; + irq_skt = mars3_irq_to_skt(gic_irq(d)); + for (i = 0; i < nr_cpu_ids; i++) { skt = (cpu_logical_map(i) >> 16) & 0xff; - if ((skt >= 0) && (skt < MAX_MARS3_SOC_COUNT)) + if ((skt >= 0) && (skt < MAX_MARS3_SOC_COUNT)) { + if ((is_kdump_kernel()) && (irq_skt == skt)) + return i; + skt_cpu_cnt[skt]++; - else if (skt != 0xff) + } else if (skt != 0xff) { pr_err("socket address: %d is out of range.", skt); + } } - irq_skt = mars3_irq_to_skt(gic_irq(d)); - - if (irq_skt != 0) + if (irq_skt) { for (i = 0; i < irq_skt; i++) cpus += skt_cpu_cnt[i]; + } cpu = cpumask_any_and(mask_val, cpu_online_mask); cpus = cpus + cpu % skt_cpu_cnt[irq_skt]; @@ -1561,12 +1458,11 @@ static int gic_cpumask_select(struct irq_data *d, const struct cpumask *mask_val static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val, bool force) { - unsigned int cpu; + unsigned int cpu, skt; u32 offset, index; void __iomem *reg; int enabled; u64 val; - unsigned int skt; if (force) cpu = cpumask_first(mask_val); @@ -1587,9 +1483,8 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val, offset = convert_offset_index(d, GICD_IROUTER, &index); skt = mars3_irq_to_skt(gic_irq(d)); - reg = mars3_gic_dists[skt].dist_base + offset + GICD_IROUTER + (index * 8); - reg = gic_dist_base(d) + offset + (index * 8); - val = gic_cpu_to_affinity(cpu); + reg = mars3_gic_dists[skt].dist_base + offset + (index * 8); + val = gic_mpidr_to_affinity(cpu_logical_map(cpu)); gic_write_irouter(val, reg); @@ -1644,7 +1539,7 @@ static inline void gic_cpu_pm_init(void) { } #endif /* CONFIG_CPU_PM */ static struct irq_chip gic_chip = { - .name = "GIC-phytium-2500", + .name = "GIC-Phytium-2500", .irq_mask = gic_mask_irq, .irq_unmask = gic_unmask_irq, .irq_eoi = gic_eoi_irq, @@ -1662,7 +1557,7 @@ static struct irq_chip gic_chip = { }; static struct irq_chip gic_eoimode1_chip = { - .name = "GICv3-phytium-2500", + .name = "GIC-Phytium-2500", .irq_mask = gic_eoimode1_mask_irq, .irq_unmask = gic_unmask_irq, .irq_eoi = gic_eoimode1_eoi_irq, @@ -1925,188 +1820,6 @@ static const struct irq_domain_ops partition_domain_ops = { .select = gic_irq_domain_select, }; -static bool gic_enable_quirk_msm8996(void *data) -{ - struct gic_chip_data *d = data; - - d->flags |= FLAGS_WORKAROUND_GICR_WAKER_MSM8996; - - return true; -} - -static bool gic_enable_quirk_mtk_gicr(void *data) -{ - struct gic_chip_data *d = data; - - d->flags |= FLAGS_WORKAROUND_MTK_GICR_SAVE; - - return true; -} - -static bool gic_enable_quirk_cavium_38539(void *data) -{ - struct gic_chip_data *d = data; - - d->flags |= FLAGS_WORKAROUND_CAVIUM_ERRATUM_38539; - - return true; -} - -static bool gic_enable_quirk_hip06_07(void *data) -{ - struct gic_chip_data *d = data; - - /* - * HIP06 GICD_IIDR clashes with GIC-600 product number (despite - * not being an actual ARM implementation). The saving grace is - * that GIC-600 doesn't have ESPI, so nothing to do in that case. - * HIP07 doesn't even have a proper IIDR, and still pretends to - * have ESPI. In both cases, put them right. - */ - if (d->rdists.gicd_typer & GICD_TYPER_ESPI) { - /* Zero both ESPI and the RES0 field next to it... */ - d->rdists.gicd_typer &= ~GENMASK(9, 8); - return true; - } - - return false; -} - -#define T241_CHIPN_MASK GENMASK_ULL(45, 44) -#define T241_CHIP_GICDA_OFFSET 0x1580000 -#define SMCCC_SOC_ID_T241 0x036b0241 - -static bool gic_enable_quirk_nvidia_t241(void *data) -{ - s32 soc_id = arm_smccc_get_soc_id_version(); - unsigned long chip_bmask = 0; - phys_addr_t phys; - u32 i; - - /* Check JEP106 code for NVIDIA T241 chip (036b:0241) */ - if ((soc_id < 0) || (soc_id != SMCCC_SOC_ID_T241)) - return false; - - /* Find the chips based on GICR regions PHYS addr */ - for (i = 0; i < gic_data.nr_redist_regions; i++) { - chip_bmask |= BIT(FIELD_GET(T241_CHIPN_MASK, - (u64)gic_data.redist_regions[i].phys_base)); - } - - if (hweight32(chip_bmask) < 3) - return false; - - /* Setup GICD alias regions */ - for (i = 0; i < ARRAY_SIZE(t241_dist_base_alias); i++) { - if (chip_bmask & BIT(i)) { - phys = gic_data.dist_phys_base + T241_CHIP_GICDA_OFFSET; - phys |= FIELD_PREP(T241_CHIPN_MASK, i); - t241_dist_base_alias[i] = ioremap(phys, SZ_64K); - WARN_ON_ONCE(!t241_dist_base_alias[i]); - } - } - static_branch_enable(&gic_nvidia_t241_erratum); - return true; -} - -static bool gic_enable_quirk_asr8601(void *data) -{ - struct gic_chip_data *d = data; - - d->flags |= FLAGS_WORKAROUND_ASR_ERRATUM_8601001; - - return true; -} - -static bool gic_enable_quirk_arm64_2941627(void *data) -{ - static_branch_enable(&gic_arm64_2941627_erratum); - return true; -} - -static bool rd_set_non_coherent(void *data) -{ - struct gic_chip_data *d = data; - - d->rdists.flags |= RDIST_FLAGS_FORCE_NON_SHAREABLE; - return true; -} - -static const struct gic_quirk gic_quirks[] = { - { - .desc = "GICv3: Qualcomm MSM8996 broken firmware", - .compatible = "qcom,msm8996-gic-v3", - .init = gic_enable_quirk_msm8996, - }, - { - .desc = "GICv3: ASR erratum 8601001", - .compatible = "asr,asr8601-gic-v3", - .init = gic_enable_quirk_asr8601, - }, - { - .desc = "GICv3: Mediatek Chromebook GICR save problem", - .property = "mediatek,broken-save-restore-fw", - .init = gic_enable_quirk_mtk_gicr, - }, - { - .desc = "GICv3: HIP06 erratum 161010803", - .iidr = 0x0204043b, - .mask = 0xffffffff, - .init = gic_enable_quirk_hip06_07, - }, - { - .desc = "GICv3: HIP07 erratum 161010803", - .iidr = 0x00000000, - .mask = 0xffffffff, - .init = gic_enable_quirk_hip06_07, - }, - { - /* - * Reserved register accesses generate a Synchronous - * External Abort. This erratum applies to: - * - ThunderX: CN88xx - * - OCTEON TX: CN83xx, CN81xx - * - OCTEON TX2: CN93xx, CN96xx, CN98xx, CNF95xx* - */ - .desc = "GICv3: Cavium erratum 38539", - .iidr = 0xa000034c, - .mask = 0xe8f00fff, - .init = gic_enable_quirk_cavium_38539, - }, - { - .desc = "GICv3: NVIDIA erratum T241-FABRIC-4", - .iidr = 0x0402043b, - .mask = 0xffffffff, - .init = gic_enable_quirk_nvidia_t241, - }, - { - /* - * GIC-700: 2941627 workaround - IP variant [0,1] - * - */ - .desc = "GICv3: ARM64 erratum 2941627", - .iidr = 0x0400043b, - .mask = 0xff0e0fff, - .init = gic_enable_quirk_arm64_2941627, - }, - { - /* - * GIC-700: 2941627 workaround - IP variant [2] - */ - .desc = "GICv3: ARM64 erratum 2941627", - .iidr = 0x0402043b, - .mask = 0xff0f0fff, - .init = gic_enable_quirk_arm64_2941627, - }, - { - .desc = "GICv3: non-coherent attribute", - .property = "dma-noncoherent", - .init = rd_set_non_coherent, - }, - { - } -}; - static void gic_enable_nmi_support(void) { int i; @@ -2114,11 +1827,6 @@ static void gic_enable_nmi_support(void) if (!gic_prio_masking_enabled()) return; - if (gic_data.flags & FLAGS_WORKAROUND_MTK_GICR_SAVE) { - pr_warn("Skipping NMI enable due to firmware issues\n"); - return; - } - ppi_nmi_refs = kcalloc(gic_data.ppi_nr, sizeof(*ppi_nmi_refs), GFP_KERNEL); if (!ppi_nmi_refs) return; @@ -2159,7 +1867,7 @@ static void gic_enable_nmi_support(void) if (gic_has_group0() && !gic_dist_security_disabled()) static_branch_enable(&gic_nonsecure_priorities); - static_branch_enable(&supports_pseudo_nmis_ft2500); + static_branch_enable(&supports_pseudo_nmis); if (static_branch_likely(&supports_deactivate_key)) gic_eoimode1_chip.flags |= IRQCHIP_SUPPORTS_NMI; @@ -2167,8 +1875,7 @@ static void gic_enable_nmi_support(void) gic_chip.flags |= IRQCHIP_SUPPORTS_NMI; } -static int __init gic_init_bases(phys_addr_t dist_phys_base, - void __iomem *dist_base, +static int __init gic_init_bases(void __iomem *dist_base, struct redist_region *rdist_regs, u32 nr_redist_regions, u64 redist_stride, @@ -2184,7 +1891,6 @@ static int __init gic_init_bases(phys_addr_t dist_phys_base, pr_info("GIC: Using split EOI/Deactivate mode\n"); gic_data.fwnode = handle; - gic_data.dist_phys_base = dist_phys_base; gic_data.dist_base = dist_base; gic_data.redist_regions = rdist_regs; gic_data.nr_redist_regions = nr_redist_regions; @@ -2196,29 +1902,18 @@ static int __init gic_init_bases(phys_addr_t dist_phys_base, typer = readl_relaxed(gic_data.dist_base + GICD_TYPER); gic_data.rdists.gicd_typer = typer; - gic_enable_quirks(readl_relaxed(gic_data.dist_base + GICD_IIDR), - gic_quirks, &gic_data); - pr_info("%d SPIs implemented\n", GIC_LINE_NR - 32); pr_info("%d Extended SPIs implemented\n", GIC_ESPI_NR); - /* - * ThunderX1 explodes on reading GICD_TYPER2, in violation of the - * architecture spec (which says that reserved registers are RES0). - */ - if (!(gic_data.flags & FLAGS_WORKAROUND_CAVIUM_ERRATUM_38539)) - gic_data.rdists.gicd_typer2 = readl_relaxed(gic_data.dist_base + GICD_TYPER2); + gic_data.rdists.gicd_typer2 = readl_relaxed(gic_data.dist_base + GICD_TYPER2); gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops, &gic_data); gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist)); - if (!static_branch_unlikely(&gic_nvidia_t241_erratum)) { - /* Disable GICv4.x features for the erratum T241-FABRIC-4 */ - gic_data.rdists.has_rvpeid = true; - gic_data.rdists.has_vlpis = true; - gic_data.rdists.has_direct_lpi = true; - gic_data.rdists.has_vpend_valid_dirty = true; - } + gic_data.rdists.has_rvpeid = true; + gic_data.rdists.has_vlpis = true; + gic_data.rdists.has_direct_lpi = true; + gic_data.rdists.has_vpend_valid_dirty = true; if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) { err = -ENOMEM; @@ -2247,7 +1942,7 @@ static int __init gic_init_bases(phys_addr_t dist_phys_base, if (gic_dist_supports_lpis()) { phytium_its_init(handle, &gic_data.rdists, gic_data.domain); phytium_its_cpu_init(); - its_lpi_memreserve_init(); + phytium_its_lpi_memreserve_init(); } else { if (IS_ENABLED(CONFIG_ARM_GIC_V2M)) gicv2m_init(handle, gic_data.domain); @@ -2334,13 +2029,13 @@ static void __init gic_populate_ppi_partitions(struct device_node *gic_node) continue; } - pr_info("%pOF[%d] ", cpu_node, cpu); + pr_cont("%pOF[%d] ", cpu_node, cpu); cpumask_set_cpu(cpu, &part->mask); of_node_put(cpu_node); } - pr_info("}\n"); + pr_cont("}\n"); part_idx++; } @@ -2424,13 +2119,12 @@ static void __iomem *gic_of_iomap(struct device_node *node, int idx, static int __init gic_of_init(struct device_node *node, struct device_node *parent) { - phys_addr_t dist_phys_base; void __iomem *dist_base; struct redist_region *rdist_regs; - struct resource res; u64 redist_stride; u32 nr_redist_regions; int err, i; + struct resource res; unsigned long skt; dist_base = gic_of_iomap(node, 0, "GICD", &res); @@ -2439,8 +2133,6 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare return PTR_ERR(dist_base); } - dist_phys_base = res.start; - err = gic_validate_dist_version(dist_base); if (err) { pr_err("%pOF: no distributor detected, giving up\n", node); @@ -2456,7 +2148,7 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare mars3_gic_dists[0].size = resource_size(&res); mars3_gic_dists[0].dist_base = dist_base; - if (of_property_read_u32(node, "#mars3_soc_bitmap", &mars3_sockets_bitmap)) + if (of_property_read_u32(node, "#mars3-soc-bitmap", &mars3_sockets_bitmap)) mars3_sockets_bitmap = 0x1; for (skt = 1; skt < MAX_MARS3_SOC_COUNT; skt++) { @@ -2493,8 +2185,8 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare if (of_property_read_u64(node, "redistributor-stride", &redist_stride)) redist_stride = 0; - err = gic_init_bases(dist_phys_base, dist_base, rdist_regs, - nr_redist_regions, redist_stride, &node->fwnode); + err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions, + redist_stride, &node->fwnode); if (err) goto out_unmap_rdist; @@ -2514,7 +2206,7 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare return err; } -IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init); +IRQCHIP_DECLARE(gic_phyt_2500, "arm,gic-phytium-2500", gic_of_init); #ifdef CONFIG_ACPI static struct @@ -2533,10 +2225,9 @@ static int gic_mars3_sockets_bitmap(void) { unsigned int skt, i; int skt_bitmap = 0; - unsigned int skt_cpu_cnt[MAX_MARS3_SOC_COUNT] = {0}; - for (i = 0; i < nr_cpu_ids; i++) { + for (i = 0; i < max_t(unsigned int, nr_cpu_ids, NR_CPUS); i++) { skt = (cpu_logical_map(i) >> 16) & 0xff; if ((skt >= 0) && (skt < MAX_MARS3_SOC_COUNT)) skt_cpu_cnt[skt]++; @@ -2544,9 +2235,10 @@ static int gic_mars3_sockets_bitmap(void) pr_err("socket address: %d is out of range.", skt); } - for (i = 0; i < MAX_MARS3_SOC_COUNT; i++) + for (i = 0; i < MAX_MARS3_SOC_COUNT; i++) { if (skt_cpu_cnt[i] > 0) skt_bitmap |= (1 << i); + } return skt_bitmap; } @@ -2554,7 +2246,7 @@ static int gic_mars3_sockets_bitmap(void) static void __init gic_acpi_register_redist(phys_addr_t phys_base, void __iomem *redist_base) { - static int count; + static int count = 0; acpi_data.redist_regs[count].phys_base = phys_base; acpi_data.redist_regs[count].redist_base = redist_base; @@ -2786,7 +2478,7 @@ static void __init gic_acpi_setup_kvm_info(void) static struct fwnode_handle *gsi_domain_handle; -static struct fwnode_handle *gic_v3_get_gsi_domain_id(u32 gsi) +static struct fwnode_handle *gic_s2500_get_gsi_domain_id(u32 gsi) { return gsi_domain_handle; } @@ -2796,8 +2488,7 @@ gic_acpi_init(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_generic_distributor *dist; size_t size; - int i, err; - int skt; + int i, err, skt; /* Get distributor base address */ dist = (struct acpi_madt_generic_distributor *)header; @@ -2820,24 +2511,22 @@ gic_acpi_init(union acpi_subtable_headers *header, const unsigned long end) mars3_gic_dists[0].size = ACPI_GICV3_DIST_MEM_SIZE; mars3_gic_dists[0].dist_base = acpi_data.dist_base; -#ifdef CONFIG_ACPI mars3_sockets_bitmap = gic_mars3_sockets_bitmap(); if (mars3_sockets_bitmap == 0) { mars3_sockets_bitmap = 0x1; - pr_err("No socket, please check cpus MPIDR_AFFINITY_LEVEL!!!"); + pr_err("No socket, please check cpus MPIDR_AFFINITY_LEVEL!"); } else pr_info("mars3_sockets_bitmap = 0x%x\n", mars3_sockets_bitmap); -#endif for (skt = 1; skt < MAX_MARS3_SOC_COUNT; skt++) { if (((1U << skt) & mars3_sockets_bitmap) == 0) continue; mars3_gic_dists[skt].phys_base = ((unsigned long)skt << MARS3_ADDR_SKTID_SHIFT) | - mars3_gic_dists[0].phys_base; + mars3_gic_dists[0].phys_base; mars3_gic_dists[skt].size = mars3_gic_dists[0].size; mars3_gic_dists[skt].dist_base = ioremap(mars3_gic_dists[skt].phys_base, - mars3_gic_dists[skt].size); + mars3_gic_dists[skt].size); } size = sizeof(*acpi_data.redist_regs) * acpi_data.nr_redist_regions; @@ -2857,13 +2546,12 @@ gic_acpi_init(union acpi_subtable_headers *header, const unsigned long end) goto out_redist_unmap; } - err = gic_init_bases(dist->base_address, acpi_data.dist_base, - acpi_data.redist_regs, acpi_data.nr_redist_regions, - 0, gsi_domain_handle); + err = gic_init_bases(acpi_data.dist_base, acpi_data.redist_regs, + acpi_data.nr_redist_regions, 0, gsi_domain_handle); if (err) goto out_fwhandle_free; - acpi_set_irq_model(ACPI_IRQ_MODEL_GIC, gic_v3_get_gsi_domain_id); + acpi_set_irq_model(ACPI_IRQ_MODEL_GIC, gic_s2500_get_gsi_domain_id); if (static_branch_likely(&supports_deactivate_key)) gic_acpi_setup_kvm_info(); @@ -2881,7 +2569,7 @@ gic_acpi_init(union acpi_subtable_headers *header, const unsigned long end) iounmap(acpi_data.dist_base); return err; } -IRQCHIP_ACPI_DECLARE(gic_phyt_2500, ACPI_MADT_TYPE_PHYTIUM_2500, +IRQCHIP_ACPI_DECLARE(gic_phyt_2500, ACPI_MADT_TYPE_OEM_RESERVED, acpi_validate_gic_table, ACPI_MADT_GIC_VERSION_V3, gic_acpi_init); #endif diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index d766dfe823b0f1059eda586007cd4ceab06f688c..ca81890ea19d3bfd689d280ea3aa26d49f1429f3 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -36,6 +36,7 @@ #include #include +#include #include "irq-gic-common.h" @@ -3083,7 +3084,7 @@ static void its_cpu_init_lpis(void) phys_addr_t paddr; u64 val, tmp; - if (gic_data_rdist()->flags & RD_LOCAL_LPI_ENABLED) + if ((gic_data_rdist()->flags & RD_LOCAL_LPI_ENABLED) && !is_cpu_ft2000pc()) return; val = readl_relaxed(rbase + GICR_CTLR); @@ -4886,6 +4887,7 @@ static void its_restore_enable(void) { struct its_node *its; int ret; + int cpu; raw_spin_lock(&its_lock); list_for_each_entry(its, &its_nodes, entry) { @@ -4939,6 +4941,23 @@ static void its_restore_enable(void) GITS_TYPER_HCC(gic_read_typer(base + GITS_TYPER))) its_cpu_init_collection(its); } + + /* + * Enable LPIs:firmware just restore GICR_CTLR_ENABLE_LPIs of boot + * CPU, the other CPUs also should be restored. + */ + for_each_possible_cpu(cpu) { + void __iomem *rbase = gic_data_rdist_cpu(cpu)->rd_base; + u32 val; + + /* Enable LPIs */ + val = readl_relaxed(rbase + GICR_CTLR); + if (val & GICR_CTLR_ENABLE_LPIS) + continue; + + val |= GICR_CTLR_ENABLE_LPIS; + writel_relaxed(val, rbase + GICR_CTLR); + } raw_spin_unlock(&its_lock); } diff --git a/drivers/irqchip/irq-loongarch-avec.c b/drivers/irqchip/irq-loongarch-avec.c new file mode 100644 index 0000000000000000000000000000000000000000..e8c43b3fd826695162156830e773c2d10ff9d23b --- /dev/null +++ b/drivers/irqchip/irq-loongarch-avec.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Loongson Technologies, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "irq-loongson.h" + +#define VECTORS_PER_REG 64 +#define IRR_VECTOR_MASK 0xffUL +#define IRR_INVALID_MASK 0x80000000UL +#define AVEC_MSG_OFFSET 0x100000 + +#ifdef CONFIG_SMP +struct pending_list { + struct list_head head; +}; + +static struct cpumask intersect_mask; +static DEFINE_PER_CPU(struct pending_list, pending_list); +#endif + +static DEFINE_PER_CPU(struct irq_desc * [NR_VECTORS], irq_map); + +struct avecintc_chip { + raw_spinlock_t lock; + struct fwnode_handle *fwnode; + struct irq_domain *domain; + struct irq_matrix *vector_matrix; + phys_addr_t msi_base_addr; +}; + +static struct avecintc_chip loongarch_avec; + +struct avecintc_data { + struct list_head entry; + unsigned int cpu; + unsigned int vec; + unsigned int prev_cpu; + unsigned int prev_vec; + unsigned int moving; +}; + +static inline void avecintc_ack_irq(struct irq_data *d) +{ +} + +static inline void avecintc_mask_irq(struct irq_data *d) +{ +} + +static inline void avecintc_unmask_irq(struct irq_data *d) +{ +} + +#ifdef CONFIG_SMP +static inline void pending_list_init(int cpu) +{ + struct pending_list *plist = per_cpu_ptr(&pending_list, cpu); + + INIT_LIST_HEAD(&plist->head); +} + +static void avecintc_sync(struct avecintc_data *adata) +{ + struct pending_list *plist; + + if (cpu_online(adata->prev_cpu)) { + plist = per_cpu_ptr(&pending_list, adata->prev_cpu); + list_add_tail(&adata->entry, &plist->head); + adata->moving = 1; + smp_ops.send_ipi_single(adata->prev_cpu, SMP_CLEAR_VECTOR); + } +} + +static int avecintc_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force) +{ + int cpu, ret, vector; + struct avecintc_data *adata; + + raw_spin_lock(&loongarch_avec.lock); + adata = irq_data_get_irq_chip_data(data); + + if (adata->moving) { + raw_spin_unlock(&loongarch_avec.lock); + return -EBUSY; + } + + if (adata->vec == UINT_MAX) { + raw_spin_unlock(&loongarch_avec.lock); + return -EINVAL; + } + + if (cpu_online(adata->cpu) && cpumask_test_cpu(adata->cpu, dest)) { + raw_spin_unlock(&loongarch_avec.lock); + return 0; + } + + cpumask_and(&intersect_mask, dest, cpu_online_mask); + + ret = irq_matrix_alloc(loongarch_avec.vector_matrix, &intersect_mask, false, &cpu); + if (ret < 0) { + raw_spin_unlock(&loongarch_avec.lock); + return ret; + } + + vector = ret; + adata->cpu = cpu; + adata->vec = vector; + per_cpu_ptr(irq_map, adata->cpu)[adata->vec] = irq_data_to_desc(data); + avecintc_sync(adata); + + raw_spin_unlock(&loongarch_avec.lock); + irq_data_update_effective_affinity(data, cpumask_of(cpu)); + + return IRQ_SET_MASK_OK; +} + +static int avecintc_cpu_online(unsigned int cpu) +{ + if (!loongarch_avec.vector_matrix) + return 0; + + raw_spin_lock(&loongarch_avec.lock); + + irq_matrix_online(loongarch_avec.vector_matrix); + + pending_list_init(cpu); + + raw_spin_unlock(&loongarch_avec.lock); + + return 0; +} + +static int avecintc_cpu_offline(unsigned int cpu) +{ + struct pending_list *plist = per_cpu_ptr(&pending_list, cpu); + + if (!loongarch_avec.vector_matrix) + return 0; + + raw_spin_lock(&loongarch_avec.lock); + + if (!list_empty(&plist->head)) + pr_warn("CPU#%d vector is busy\n", cpu); + irq_matrix_offline(loongarch_avec.vector_matrix); + + raw_spin_unlock(&loongarch_avec.lock); + + return 0; +} + +void complete_irq_moving(void) +{ + struct pending_list *plist = this_cpu_ptr(&pending_list); + struct avecintc_data *adata, *tdata; + int cpu, vector, bias; + uint64_t isr; + + raw_spin_lock(&loongarch_avec.lock); + + list_for_each_entry_safe(adata, tdata, &plist->head, entry) { + cpu = adata->prev_cpu; + vector = adata->prev_vec; + bias = vector / VECTORS_PER_REG; + switch (bias) { + case 0: + isr = csr_read64(LOONGARCH_CSR_ISR0); + break; + case 1: + isr = csr_read64(LOONGARCH_CSR_ISR1); + break; + case 2: + isr = csr_read64(LOONGARCH_CSR_ISR2); + break; + case 3: + isr = csr_read64(LOONGARCH_CSR_ISR3); + break; + } + + if (isr & (1UL << (vector % VECTORS_PER_REG))) { + smp_ops.send_ipi_single(cpu, SMP_CLEAR_VECTOR); + continue; + } + list_del(&adata->entry); + irq_matrix_free(loongarch_avec.vector_matrix, cpu, vector, false); + this_cpu_write(irq_map[vector], NULL); + adata->moving = 0; + adata->prev_cpu = adata->cpu; + adata->prev_vec = adata->vec; + } + + raw_spin_unlock(&loongarch_avec.lock); +} +#endif + +static void avecintc_compose_msi_msg(struct irq_data *d, struct msi_msg *msg) +{ + struct avecintc_data *adata = irq_data_get_irq_chip_data(d); + + msg->address_hi = 0x0; + msg->address_lo = (loongarch_avec.msi_base_addr | (adata->vec & 0xff) << 4) + | ((cpu_logical_map(adata->cpu & 0xffff)) << 12); + msg->data = 0x0; +} + +static struct irq_chip avec_irq_controller = { + .name = "AVECINTC", + .irq_ack = avecintc_ack_irq, + .irq_mask = avecintc_mask_irq, + .irq_unmask = avecintc_unmask_irq, +#ifdef CONFIG_SMP + .irq_set_affinity = avecintc_set_affinity, +#endif + .irq_compose_msi_msg = avecintc_compose_msi_msg, +}; + +static void avecintc_irq_dispatch(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct irq_desc *d; + + chained_irq_enter(chip, desc); + + while (true) { + unsigned long vector = csr_read64(LOONGARCH_CSR_IRR); + + if (vector & IRR_INVALID_MASK) + break; + + vector &= IRR_VECTOR_MASK; + + d = this_cpu_read(irq_map[vector]); + if (d) { + generic_handle_irq_desc(d); + } else { + spurious_interrupt(); + pr_warn("Unexpected IRQ occurs on CPU#%d [vector %ld]\n", + smp_processor_id(), vector); + } + } + + chained_irq_exit(chip, desc); +} + +static int avecintc_alloc_vector(struct irq_data *irqd, struct avecintc_data *adata) +{ + int cpu, ret; + unsigned long flags; + + raw_spin_lock_irqsave(&loongarch_avec.lock, flags); + + ret = irq_matrix_alloc(loongarch_avec.vector_matrix, cpu_online_mask, false, &cpu); + if (ret < 0) { + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); + return ret; + } + + adata->prev_cpu = adata->cpu = cpu; + adata->prev_vec = adata->vec = ret; + per_cpu_ptr(irq_map, adata->cpu)[adata->vec] = irq_data_to_desc(irqd); + + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); + + return 0; +} + +static int avecintc_domain_alloc(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs, void *arg) +{ + for (unsigned int i = 0; i < nr_irqs; i++) { + struct irq_data *irqd = irq_domain_get_irq_data(domain, virq + i); + struct avecintc_data *adata = kzalloc(sizeof(*adata), GFP_KERNEL); + int ret; + + if (!adata) + return -ENOMEM; + + ret = avecintc_alloc_vector(irqd, adata); + if (ret < 0) { + kfree(adata); + return ret; + } + + irq_domain_set_info(domain, virq + i, virq + i, &avec_irq_controller, + adata, handle_edge_irq, NULL, NULL); + irqd_set_single_target(irqd); + irqd_set_affinity_on_activate(irqd); + } + + return 0; +} + +static void avecintc_free_vector(struct irq_data *irqd, struct avecintc_data *adata) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&loongarch_avec.lock, flags); + + per_cpu(irq_map, adata->cpu)[adata->vec] = NULL; + irq_matrix_free(loongarch_avec.vector_matrix, adata->cpu, adata->vec, false); + +#ifdef CONFIG_SMP + if (!adata->moving) { + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); + return; + } + + per_cpu(irq_map, adata->prev_cpu)[adata->prev_vec] = NULL; + irq_matrix_free(loongarch_avec.vector_matrix, adata->prev_cpu, adata->prev_vec, false); + list_del_init(&adata->entry); +#endif + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); +} + +static void avecintc_domain_free(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs) +{ + for (unsigned int i = 0; i < nr_irqs; i++) { + struct irq_data *d = irq_domain_get_irq_data(domain, virq + i); + + if (d) { + struct avecintc_data *adata = irq_data_get_irq_chip_data(d); + + avecintc_free_vector(d, adata); + irq_domain_reset_irq_data(d); + kfree(adata); + } + } +} + +static const struct irq_domain_ops avecintc_domain_ops = { + .alloc = avecintc_domain_alloc, + .free = avecintc_domain_free, +}; + +static int __init irq_matrix_init(void) +{ + loongarch_avec.vector_matrix = irq_alloc_matrix(NR_VECTORS, 0, NR_VECTORS); + if (!loongarch_avec.vector_matrix) + return -ENOMEM; + + for (int i = 0; i < NR_LEGACY_VECTORS; i++) + irq_matrix_assign_system(loongarch_avec.vector_matrix, i, false); + + irq_matrix_online(loongarch_avec.vector_matrix); + + return 0; +} + +static int __init avecintc_init(struct irq_domain *parent) +{ + int ret, parent_irq; + unsigned long value; + + raw_spin_lock_init(&loongarch_avec.lock); + + loongarch_avec.fwnode = irq_domain_alloc_named_fwnode("AVECINTC"); + if (!loongarch_avec.fwnode) { + pr_err("Unable to allocate domain handle\n"); + ret = -ENOMEM; + goto out; + } + + loongarch_avec.domain = irq_domain_create_tree(loongarch_avec.fwnode, + &avecintc_domain_ops, NULL); + if (!loongarch_avec.domain) { + pr_err("Unable to create IRQ domain\n"); + ret = -ENOMEM; + goto out_free_handle; + } + + parent_irq = irq_create_mapping(parent, INT_AVEC); + if (!parent_irq) { + pr_err("Failed to mapping hwirq\n"); + ret = -EINVAL; + goto out_remove_domain; + } + + ret = irq_matrix_init(); + if (ret < 0) { + pr_err("Failed to init irq matrix\n"); + goto out_remove_domain; + } + irq_set_chained_handler_and_data(parent_irq, avecintc_irq_dispatch, NULL); + +#ifdef CONFIG_SMP + pending_list_init(0); + cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_AVECINTC_STARTING, + "irqchip/loongarch/avecintc:starting", + avecintc_cpu_online, avecintc_cpu_offline); +#endif + value = iocsr_read64(LOONGARCH_IOCSR_MISC_FUNC); + value |= IOCSR_MISC_FUNC_AVEC_EN; + iocsr_write64(value, LOONGARCH_IOCSR_MISC_FUNC); + + return ret; + +out_remove_domain: + irq_domain_remove(loongarch_avec.domain); +out_free_handle: + irq_domain_free_fwnode(loongarch_avec.fwnode); +out: + return ret; +} + +static int __init pch_msi_parse_madt(union acpi_subtable_headers *header, + const unsigned long end) +{ + struct acpi_madt_msi_pic *pchmsi_entry = (struct acpi_madt_msi_pic *)header; + + loongarch_avec.msi_base_addr = pchmsi_entry->msg_address - AVEC_MSG_OFFSET; + + return pch_msi_acpi_init_avec(loongarch_avec.domain); +} + +static inline int __init acpi_cascade_irqdomain_init(void) +{ + return acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 1); +} + +int __init avecintc_acpi_init(struct irq_domain *parent) +{ + int ret = avecintc_init(parent); + + if (ret < 0) { + pr_err("Failed to init IRQ domain\n"); + return ret; + } + + ret = acpi_cascade_irqdomain_init(); + if (ret < 0) { + pr_err("Failed to init cascade IRQ domain\n"); + return ret; + } + + return ret; +} diff --git a/drivers/irqchip/irq-loongarch-cpu.c b/drivers/irqchip/irq-loongarch-cpu.c index 4380b4d8dd2053ab1ac22649bb3b11119029dead..c6e0c9849ba91cf0158f53ad72bfd6fcf8676ffd 100644 --- a/drivers/irqchip/irq-loongarch-cpu.c +++ b/drivers/irqchip/irq-loongarch-cpu.c @@ -13,6 +13,8 @@ #include #include +#include "irq-loongson.h" + static struct irq_domain *irq_domain; struct fwnode_handle *cpuintc_handle; @@ -140,7 +142,10 @@ static int __init acpi_cascade_irqdomain_init(void) if (r < 0) return r; - return 0; + if (cpu_has_avecint) + r = avecintc_acpi_init(irq_domain); + + return r; } struct irq_domain *get_cpudomain(void) diff --git a/drivers/irqchip/irq-loongson-eiointc.c b/drivers/irqchip/irq-loongson-eiointc.c index 7e91362d707f062ebd67ba77fce895c260023147..f9317c9b72d5f98b2b4643a013ce32aad2ab3f90 100644 --- a/drivers/irqchip/irq-loongson-eiointc.c +++ b/drivers/irqchip/irq-loongson-eiointc.c @@ -17,6 +17,8 @@ #include #include +#include "irq-loongson.h" + #define EIOINTC_REG_NODEMAP 0x14a0 #define EIOINTC_REG_IPMAP 0x14c0 #define EIOINTC_REG_ENABLE 0x1600 @@ -396,6 +398,9 @@ static int __init acpi_cascade_irqdomain_init(void) if (r < 0) return r; + if (cpu_has_avecint) + return 0; + r = acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 1); if (r < 0) return r; @@ -443,8 +448,8 @@ static int __init eiointc_init(struct eiointc_priv *priv, int parent_irq, if (nr_pics == 1) { register_syscore_ops(&eiointc_syscore_ops); - cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_LOONGARCH_STARTING, - "irqchip/loongarch/intc:starting", + cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_EIOINTC_STARTING, + "irqchip/loongarch/eiointc:starting", eiointc_router_init, NULL); } diff --git a/drivers/irqchip/irq-loongson-htvec.c b/drivers/irqchip/irq-loongson-htvec.c index 0bff728b25e3da85ebb1d166f0cae5b7a7f0ea51..5da02c7ad0b3075c2ac0a0aa1d9a5724d8906b9c 100644 --- a/drivers/irqchip/irq-loongson-htvec.c +++ b/drivers/irqchip/irq-loongson-htvec.c @@ -17,6 +17,8 @@ #include #include +#include "irq-loongson.h" + /* Registers */ #define HTVEC_EN_OFF 0x20 #define HTVEC_MAX_PARENT_IRQ 8 diff --git a/drivers/irqchip/irq-loongson-liointc.c b/drivers/irqchip/irq-loongson-liointc.c index 7c4fe7ab4b830e499ec36140ae6cd39ad1325ab0..2b1bd4a96665b4eab6832dd95daca4468c3d661d 100644 --- a/drivers/irqchip/irq-loongson-liointc.c +++ b/drivers/irqchip/irq-loongson-liointc.c @@ -22,6 +22,8 @@ #include #endif +#include "irq-loongson.h" + #define LIOINTC_CHIP_IRQ 32 #define LIOINTC_NUM_PARENT 4 #define LIOINTC_NUM_CORES 4 diff --git a/drivers/irqchip/irq-loongson-pch-lpc.c b/drivers/irqchip/irq-loongson-pch-lpc.c index 9b35492fb6be9eacfbfe07a2b7d54f147843f094..2d4c3ec128b8f27056ccec04515c423174a1c0f9 100644 --- a/drivers/irqchip/irq-loongson-pch-lpc.c +++ b/drivers/irqchip/irq-loongson-pch-lpc.c @@ -15,6 +15,8 @@ #include #include +#include "irq-loongson.h" + /* Registers */ #define LPC_INT_CTL 0x00 #define LPC_INT_ENA 0x04 diff --git a/drivers/irqchip/irq-loongson-pch-msi.c b/drivers/irqchip/irq-loongson-pch-msi.c index dd4d699170f4ec5fe731e0e08c48acebb629d70f..2c9f58536fce403fdf22c2898811fba859b511d9 100644 --- a/drivers/irqchip/irq-loongson-pch-msi.c +++ b/drivers/irqchip/irq-loongson-pch-msi.c @@ -15,6 +15,8 @@ #include #include +#include "irq-loongson.h" + static int nr_pics; struct pch_msi_data { @@ -266,17 +268,17 @@ IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_of_init); #ifdef CONFIG_ACPI struct fwnode_handle *get_pch_msi_handle(int pci_segment) { - int i; + if (cpu_has_avecint) + return pch_msi_handle[0]; - for (i = 0; i < MAX_IO_PICS; i++) { + for (int i = 0; i < MAX_IO_PICS; i++) { if (msi_group[i].pci_segment == pci_segment) return pch_msi_handle[i]; } - return NULL; + return pch_msi_handle[0]; } -int __init pch_msi_acpi_init(struct irq_domain *parent, - struct acpi_madt_msi_pic *acpi_pchmsi) +int __init pch_msi_acpi_init(struct irq_domain *parent, struct acpi_madt_msi_pic *acpi_pchmsi) { int ret; struct fwnode_handle *domain_handle; @@ -289,4 +291,36 @@ int __init pch_msi_acpi_init(struct irq_domain *parent, return ret; } + +static struct irq_chip pch_msi_irq_chip_avec = { + .name = "PCH PCI MSI", + .irq_ack = irq_chip_ack_parent, +}; + +static struct msi_domain_info pch_msi_domain_info_avec = { + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX, + .chip = &pch_msi_irq_chip_avec, +}; + +int __init pch_msi_acpi_init_avec(struct irq_domain *parent) +{ + struct irq_domain *msi_domain; + + if (pch_msi_handle[0]) + return 0; + + pch_msi_handle[0] = parent->fwnode; + irq_domain_update_bus_token(parent, DOMAIN_BUS_NEXUS); + + msi_domain = pci_msi_create_irq_domain(pch_msi_handle[0], + &pch_msi_domain_info_avec, parent); + if (!msi_domain) { + pr_err("Failed to create PCI MSI domain\n"); + kfree(pch_msi_handle[0]); + return -ENOMEM; + } + + return 0; +} #endif diff --git a/drivers/irqchip/irq-loongson-pch-pic.c b/drivers/irqchip/irq-loongson-pch-pic.c index 3b150b6121fc3b292ff6a903441ec91d28715066..075d08aba75e64f5d29a16b9f8ed1b006ae5331f 100644 --- a/drivers/irqchip/irq-loongson-pch-pic.c +++ b/drivers/irqchip/irq-loongson-pch-pic.c @@ -17,6 +17,8 @@ #include #include +#include "irq-loongson.h" + /* Registers */ #define PCH_PIC_MASK 0x20 #define PCH_PIC_HTMSI_EN 0x40 @@ -33,6 +35,7 @@ #define PIC_COUNT (PIC_COUNT_PER_REG * PIC_REG_COUNT) #define PIC_REG_IDX(irq_id) ((irq_id) / PIC_COUNT_PER_REG) #define PIC_REG_BIT(irq_id) ((irq_id) % PIC_COUNT_PER_REG) +#define PIC_UNDEF_VECTOR 255 #define PIC_COUNT_PER_REG64 64 #define PIC_REG64_COUNT 1 #define PIC_REG64_IDX(irq_id) ((irq_id) / PIC_COUNT_PER_REG64) @@ -50,6 +53,8 @@ struct pch_pic { u32 saved_vec_en[PIC_REG_COUNT]; u32 saved_vec_pol[PIC_REG_COUNT]; u32 saved_vec_edge[PIC_REG_COUNT]; + u8 table[PIC_COUNT]; + int inuse; }; static struct pch_pic *pch_pic_priv[MAX_IO_PICS]; @@ -61,6 +66,11 @@ struct irq_domain *get_pchpic_irq_domain(void) return pch_pic_priv[0]->pic_domain; } +static inline u8 hwirq_to_bit(struct pch_pic *priv, int hirq) +{ + return priv->table[hirq]; +} + static void pch_pic_bitset(struct pch_pic *priv, int offset, int bit) { u32 reg; @@ -89,45 +99,47 @@ static void pch_pic_mask_irq(struct irq_data *d) { struct pch_pic *priv = irq_data_get_irq_chip_data(d); - pch_pic_bitset(priv, PCH_PIC_MASK, d->hwirq); + pch_pic_bitset(priv, PCH_PIC_MASK, hwirq_to_bit(priv, d->hwirq)); irq_chip_mask_parent(d); } static void pch_pic_unmask_irq(struct irq_data *d) { struct pch_pic *priv = irq_data_get_irq_chip_data(d); + int bit = hwirq_to_bit(priv, d->hwirq); writeq(BIT(PIC_REG64_BIT(d->hwirq)), - priv->base + PCH_PIC_CLR + PIC_REG64_IDX(d->hwirq) * 8); + priv->base + PCH_PIC_CLR + PIC_REG64_IDX(bit) * 8); irq_chip_unmask_parent(d); - pch_pic_bitclr(priv, PCH_PIC_MASK, d->hwirq); + pch_pic_bitclr(priv, PCH_PIC_MASK, bit); } static int pch_pic_set_type(struct irq_data *d, unsigned int type) { struct pch_pic *priv = irq_data_get_irq_chip_data(d); + int bit = hwirq_to_bit(priv, d->hwirq); int ret = 0; switch (type) { case IRQ_TYPE_EDGE_RISING: - pch_pic_bitset(priv, PCH_PIC_EDGE, d->hwirq); - pch_pic_bitclr(priv, PCH_PIC_POL, d->hwirq); + pch_pic_bitset(priv, PCH_PIC_EDGE, bit); + pch_pic_bitclr(priv, PCH_PIC_POL, bit); irq_set_handler_locked(d, handle_edge_irq); break; case IRQ_TYPE_EDGE_FALLING: - pch_pic_bitset(priv, PCH_PIC_EDGE, d->hwirq); - pch_pic_bitset(priv, PCH_PIC_POL, d->hwirq); + pch_pic_bitset(priv, PCH_PIC_EDGE, bit); + pch_pic_bitset(priv, PCH_PIC_POL, bit); irq_set_handler_locked(d, handle_edge_irq); break; case IRQ_TYPE_LEVEL_HIGH: - pch_pic_bitclr(priv, PCH_PIC_EDGE, d->hwirq); - pch_pic_bitclr(priv, PCH_PIC_POL, d->hwirq); + pch_pic_bitclr(priv, PCH_PIC_EDGE, bit); + pch_pic_bitclr(priv, PCH_PIC_POL, bit); irq_set_handler_locked(d, handle_level_irq); break; case IRQ_TYPE_LEVEL_LOW: - pch_pic_bitclr(priv, PCH_PIC_EDGE, d->hwirq); - pch_pic_bitset(priv, PCH_PIC_POL, d->hwirq); + pch_pic_bitclr(priv, PCH_PIC_EDGE, bit); + pch_pic_bitset(priv, PCH_PIC_POL, bit); irq_set_handler_locked(d, handle_level_irq); break; default: @@ -142,11 +154,12 @@ static void pch_pic_ack_irq(struct irq_data *d) { unsigned int reg; struct pch_pic *priv = irq_data_get_irq_chip_data(d); + int bit = hwirq_to_bit(priv, d->hwirq); - reg = readl(priv->base + PCH_PIC_EDGE + PIC_REG_IDX(d->hwirq) * 4); + reg = readl(priv->base + PCH_PIC_EDGE + PIC_REG_IDX(bit) * 4); if (reg & BIT(PIC_REG_BIT(d->hwirq))) { writeq(BIT(PIC_REG64_BIT(d->hwirq)), - priv->base + PCH_PIC_CLR + PIC_REG64_IDX(d->hwirq) * 8); + priv->base + PCH_PIC_CLR + PIC_REG64_IDX(bit) * 8); } irq_chip_ack_parent(d); } @@ -168,6 +181,8 @@ static int pch_pic_domain_translate(struct irq_domain *d, { struct pch_pic *priv = d->host_data; struct device_node *of_node = to_of_node(fwspec->fwnode); + unsigned long flags; + int i; if (of_node) { if (fwspec->param_count < 2) @@ -180,12 +195,33 @@ static int pch_pic_domain_translate(struct irq_domain *d, return -EINVAL; *hwirq = fwspec->param[0] - priv->gsi_base; + if (fwspec->param_count > 1) *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; else *type = IRQ_TYPE_NONE; } + raw_spin_lock_irqsave(&priv->pic_lock, flags); + /* Check pic-table to confirm if the hwirq has been assigned */ + for (i = 0; i < priv->inuse; i++) { + if (priv->table[i] == *hwirq) { + *hwirq = i; + break; + } + } + if (i == priv->inuse) { + /* Assign a new hwirq in pic-table */ + if (priv->inuse >= PIC_COUNT) { + pr_err("pch-pic domain has no free vectors\n"); + raw_spin_unlock_irqrestore(&priv->pic_lock, flags); + return -EINVAL; + } + priv->table[priv->inuse] = *hwirq; + *hwirq = priv->inuse++; + } + raw_spin_unlock_irqrestore(&priv->pic_lock, flags); + return 0; } @@ -203,6 +239,9 @@ static int pch_pic_alloc(struct irq_domain *domain, unsigned int virq, if (err) return err; + /* Write vector ID */ + writeb(priv->ht_vec_base + hwirq, priv->base + PCH_INT_HTVEC(hwirq_to_bit(priv, hwirq))); + parent_fwspec.fwnode = domain->parent->fwnode; parent_fwspec.param_count = 1; parent_fwspec.param[0] = hwirq + priv->ht_vec_base; @@ -231,7 +270,7 @@ static void pch_pic_reset(struct pch_pic *priv) for (i = 0; i < PIC_COUNT; i++) { /* Write vector ID */ - writeb(priv->ht_vec_base + i, priv->base + PCH_INT_HTVEC(i)); + writeb(i, priv->base + PCH_INT_HTVEC(hwirq_to_bit(priv, i))); /* Hardcode route to HT0 Lo */ writeb(1, priv->base + PCH_INT_ROUTE(i)); } @@ -295,6 +334,7 @@ static int pch_pic_init(phys_addr_t addr, unsigned long size, int vec_base, u32 gsi_base) { struct pch_pic *priv; + int i; priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) @@ -305,6 +345,10 @@ static int pch_pic_init(phys_addr_t addr, unsigned long size, int vec_base, if (!priv->base) goto free_priv; + priv->inuse = 0; + for (i = 0; i < PIC_COUNT; i++) + priv->table[i] = PIC_UNDEF_VECTOR; + priv->ht_vec_base = vec_base; priv->vec_count = ((readq(priv->base) >> 48) & 0xff) + 1; priv->gsi_base = gsi_base; diff --git a/drivers/irqchip/irq-loongson.h b/drivers/irqchip/irq-loongson.h new file mode 100644 index 0000000000000000000000000000000000000000..11fa138d1f4434ebdfb2ae49e5b781dee913c5ba --- /dev/null +++ b/drivers/irqchip/irq-loongson.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2024 Loongson Technology Corporation Limited + */ + +#ifndef _DRIVERS_IRQCHIP_IRQ_LOONGSON_H +#define _DRIVERS_IRQCHIP_IRQ_LOONGSON_H + +int find_pch_pic(u32 gsi); + +int liointc_acpi_init(struct irq_domain *parent, + struct acpi_madt_lio_pic *acpi_liointc); +int eiointc_acpi_init(struct irq_domain *parent, + struct acpi_madt_eio_pic *acpi_eiointc); +int avecintc_acpi_init(struct irq_domain *parent); + +int htvec_acpi_init(struct irq_domain *parent, + struct acpi_madt_ht_pic *acpi_htvec); +int pch_lpc_acpi_init(struct irq_domain *parent, + struct acpi_madt_lpc_pic *acpi_pchlpc); +int pch_pic_acpi_init(struct irq_domain *parent, + struct acpi_madt_bio_pic *acpi_pchpic); +int pch_msi_acpi_init(struct irq_domain *parent, + struct acpi_madt_msi_pic *acpi_pchmsi); +int pch_msi_acpi_init_avec(struct irq_domain *parent); + +#endif /* _DRIVERS_IRQCHIP_IRQ_LOONGSON_H */ diff --git a/drivers/irqchip/irq-phytium-ixic.c b/drivers/irqchip/irq-phytium-ixic.c new file mode 100644 index 0000000000000000000000000000000000000000..7862df80fb46cf14f388438f7fb29fb75bdf1461 --- /dev/null +++ b/drivers/irqchip/irq-phytium-ixic.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Phytium PCIe legacy INTx interrupt controller + * + * Copyright (c) 2020-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NUM_IRQS 4 + +#define CTR_BANK_NUM 6 +#define CTR_BANK_SIZE 0x10000 +#define CTR_BANK_ISTATUS_LOCAL 0x184 + +#define HPB_INTX_STATUS_0 0x0 +#define HPB_INTX_STATUS_1 0x1000 + +struct ixic_irq_data { + void __iomem *ctr; + void __iomem *hpb; + u32 spi_base; +}; + +static void phytium_ixic_irq_eoi(struct irq_data *d) +{ + struct ixic_irq_data *data = irq_data_get_irq_chip_data(d); + unsigned int intx = irqd_to_hwirq(d); + u32 gstatus = readl(data->hpb) | (readl(data->hpb + HPB_INTX_STATUS_1) << 12); + u32 imask, istatus; + int i; + + WARN_ON(intx >= NUM_IRQS); + imask = 1 << (3 - intx); + istatus = (1 << intx) << 24; + for (i = 0; i < CTR_BANK_NUM; i++, gstatus >>= 4) { + if (gstatus & imask) + writel(istatus, data->ctr + CTR_BANK_SIZE*i + CTR_BANK_ISTATUS_LOCAL); + } + + irq_chip_eoi_parent(d); +} + +static struct irq_chip phytium_ixic_irq_chip = { + .name = "IXIU", + .irq_eoi = phytium_ixic_irq_eoi, + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_set_type = irq_chip_set_type_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, + .flags = IRQCHIP_MASK_ON_SUSPEND, +}; + +static int phytium_ixic_translate(struct irq_domain *domain, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + struct ixic_irq_data *info = domain->host_data; + + if (is_of_node(fwspec->fwnode)) { + if (fwspec->param_count != 3) + return -EINVAL; + + if (fwspec->param[0] != GIC_SPI) + return -EINVAL; /* No PPI should point to this domain */ + + *hwirq = fwspec->param[1] - info->spi_base; + *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; + } else { + if (fwspec->param_count != 2) + return -EINVAL; + *hwirq = fwspec->param[0] - info->spi_base; + *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; + } + + return 0; +} + +static int phytium_ixic_alloc(struct irq_domain *dom, unsigned int virq, + unsigned int nr_irqs, void *data) +{ + struct irq_fwspec *fwspec = data; + struct irq_fwspec parent_fwspec; + struct ixic_irq_data *info = dom->host_data; + irq_hw_number_t hwirq; + + /* We assume the device use the parent's format directly */ + parent_fwspec = *fwspec; + if (is_of_node(dom->parent->fwnode)) { + if (fwspec->param_count != 3) + return -EINVAL; /* Not GIC compliant */ + if (fwspec->param[0] != GIC_SPI) + return -EINVAL; /* No PPI should point to this domain */ + + /* Get the local hwirq of IXIC */ + hwirq = fwspec->param[1] - info->spi_base; + } else { + hwirq = fwspec->param[0] - info->spi_base; + } + WARN_ON(nr_irqs != 1); + irq_domain_set_hwirq_and_chip(dom, virq, hwirq, &phytium_ixic_irq_chip, info); + + parent_fwspec.fwnode = dom->parent->fwnode; + return irq_domain_alloc_irqs_parent(dom, virq, nr_irqs, &parent_fwspec); +} + +static const struct irq_domain_ops ixic_domain_ops = { + .translate = phytium_ixic_translate, + .alloc = phytium_ixic_alloc, + .free = irq_domain_free_irqs_common, +}; + +static struct ixic_irq_data *phytium_ixic_init(const struct fwnode_handle *fwnode, + struct resource *ctr, struct resource *hpb) +{ + struct ixic_irq_data *data; + int err; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return ERR_PTR(-ENOMEM); + + if (fwnode_property_read_u32_array(fwnode, "intx-spi-base", + &data->spi_base, 1)) { + err = -ENODEV; + goto out_free; + } + + data->ctr = ioremap(ctr->start, resource_size(ctr)); + if (!data->ctr) { + err = -ENODEV; + goto out_free; + } + + data->hpb = ioremap(hpb->start, resource_size(hpb)); + if (!data->hpb) { + err = -ENODEV; + goto out_free; + } + + return data; + +out_free: + kfree(data); + return ERR_PTR(err); +} + +static int __init phytium_ixic_dt_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *pd, *d; + struct ixic_irq_data *data; + struct resource ctr, hpb; + + if (!parent) { + pr_err("%pOF: no parent, giving up\n", node); + return -ENODEV; + } + + pd = irq_find_host(parent); + if (!pd) { + pr_err("%pOF: unable to obtain parent domain\n", node); + return -ENXIO; + } + + if (of_address_to_resource(node, 0, &ctr)) { + pr_err("%pOF: failed to parse 'ctr' memory resource\n", node); + return -ENXIO; + } + + if (of_address_to_resource(node, 1, &hpb)) { + pr_err("%pOF: failed to parse 'hpb' memory resource\n", node); + return -ENXIO; + } + + data = phytium_ixic_init(of_node_to_fwnode(node), &ctr, &hpb); + if (IS_ERR(data)) + return PTR_ERR(data); + + d = irq_domain_add_hierarchy(pd, 0, NUM_IRQS, node, &ixic_domain_ops, data); + if (!d) { + pr_err("%pOF: failed to allocate domain\n", node); + goto out_unmap; + } + + pr_info("%pOF: %d interrupts forwarded to %pOF\n", node, NUM_IRQS, parent); + + return 0; + +out_unmap: + iounmap(data->ctr); + iounmap(data->hpb); + kfree(data); + return -ENOMEM; +} +IRQCHIP_DECLARE(ixic, "phytium,ixic", phytium_ixic_dt_init); + +#ifdef CONFIG_ACPI +static int phytium_ixic_acpi_probe(struct platform_device *pdev) +{ + struct irq_domain *domain; + struct ixic_irq_data *data; + struct resource *ctr, *hpb; + + ctr = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!ctr) { + dev_err(&pdev->dev, "failed to parse 'ctr' memory resource\n"); + return -ENXIO; + } + + hpb = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!hpb) { + dev_err(&pdev->dev, "failed to parse 'hpb' memory resource\n"); + return -ENXIO; + } + + data = phytium_ixic_init(dev_fwnode(&pdev->dev), ctr, hpb); + if (IS_ERR(data)) + return PTR_ERR(data); + + domain = acpi_irq_create_hierarchy(0, NUM_IRQS, dev_fwnode(&pdev->dev), + &ixic_domain_ops, data); + if (!domain) { + dev_err(&pdev->dev, "failed to create IRQ domain\n"); + goto out_unmap; + } + + dev_info(&pdev->dev, "%d interrupts forwarded\n", NUM_IRQS); + + return 0; + +out_unmap: + iounmap(data->ctr); + iounmap(data->hpb); + kfree(data); + return -ENOMEM; +} + +static const struct acpi_device_id phytium_ixic_acpi_ids[] = { + { "PHYT0013" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(acpi, phytium_ixic_acpi_ids); + +static struct platform_driver phytium_ixic_driver = { + .driver = { + .name = "phytium-ixic", + .acpi_match_table = phytium_ixic_acpi_ids, + }, + .probe = phytium_ixic_acpi_probe, +}; +builtin_platform_driver(phytium_ixic_driver); +#endif diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index bc2e265cb02d2aa5ec984d019099423b43d986de..771f8fabded3e093720d653ffe990361654106eb 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -41,6 +41,15 @@ config IMX_MBOX help Mailbox implementation for i.MX Messaging Unit (MU). +config PHYTIUM_MBOX + tristate "Phytium SoC Mailbox Support" + depends on ARCH_PHYTIUM || COMPILE_TEST + help + Mailbox driver implementation for the Phytium platform. It is used + to send message between application processors and on-chip management + firmware. Say Y here if you want to build this mailbox controller + driver. + config PLATFORM_MHU tristate "Platform MHU Mailbox" depends on OF diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index fc93761171113ea94d079de1b4772722467247e7..487f62094c9e7f5b487b01bf16ff1a3e37c18c99 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -62,3 +62,5 @@ obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o + +obj-$(CONFIG_PHYTIUM_MBOX) += phytium_mailbox.o diff --git a/drivers/mailbox/phytium_mailbox.c b/drivers/mailbox/phytium_mailbox.c new file mode 100644 index 0000000000000000000000000000000000000000..afab8f401556745ec9ef1a21a046ad57413cabe3 --- /dev/null +++ b/drivers/mailbox/phytium_mailbox.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SoC mailbox driver + * + * Copyright (c) 2020-2023, Phytium Technology Co., Ltd. + * + * Derived from drivers/mailbox/arm_mhu.c + * Copyright (C) 2013-2015 Fujitsu Semiconductor Ltd. + * Copyright (C) 2015 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INTR_STAT 0x0 +#define INTR_SET 0x8 +#define INTR_CLR 0x10 + +#define TX_REG 0x100 + +#define NR_CHANS 1 + +struct phytium_mbox_link { + unsigned int irq; + void __iomem *tx_reg; + void __iomem *rx_reg; +}; + +struct phytium_mbox { + void __iomem *base; + struct phytium_mbox_link mlink; + struct mbox_chan chan; + struct mbox_controller mbox; +}; + +static irqreturn_t phytium_mbox_rx_irq(int irq, void *ch) +{ + struct mbox_chan *chan = ch; + struct phytium_mbox_link *mlink = chan->con_priv; + u32 val; + + val = readl_relaxed(mlink->rx_reg + INTR_STAT); + if (!val) + return IRQ_NONE; + + mbox_chan_received_data(chan, (void *)&val); + + writel_relaxed(val, mlink->rx_reg + INTR_CLR); + + return IRQ_HANDLED; +} + +static int phytium_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct phytium_mbox_link *mlink = chan->con_priv; + u32 *arg = data; + + writel_relaxed(*arg, mlink->tx_reg + INTR_SET); + + return 0; +} + +static int phytium_mbox_startup(struct mbox_chan *chan) +{ + struct phytium_mbox_link *mlink = chan->con_priv; + u32 val; + int ret; + + val = readl_relaxed(mlink->tx_reg + INTR_STAT); + writel_relaxed(val, mlink->tx_reg + INTR_CLR); + + ret = request_irq(mlink->irq, phytium_mbox_rx_irq, + IRQF_SHARED, "phytium_mbox_link", chan); + if (ret) { + dev_err(chan->mbox->dev, + "Unable to acquire IRQ %d\n", mlink->irq); + } + + return ret; +} + +static void phytium_mbox_shutdown(struct mbox_chan *chan) +{ + struct phytium_mbox_link *mlink = chan->con_priv; + + free_irq(mlink->irq, chan); +} + +static bool phytium_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct phytium_mbox_link *mlink = chan->con_priv; + u32 val = readl_relaxed(mlink->tx_reg + INTR_STAT); + + return (val == (u32)(1U << 31)); +} + +static const struct mbox_chan_ops phytium_mbox_ops = { + .send_data = phytium_mbox_send_data, + .startup = phytium_mbox_startup, + .shutdown = phytium_mbox_shutdown, + .last_tx_done = phytium_mbox_last_tx_done, +}; + +static const struct acpi_device_id phytium_mbox_acpi_match[] = { + { "PHYT0009", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, phytium_mbox_acpi_match); + +static const struct of_device_id phytium_mbox_of_match[] = { + { .compatible = "phytium,mbox", }, + { }, +}; +MODULE_DEVICE_TABLE(of, phytium_mbox_of_match); + +static int phytium_mbox_probe(struct platform_device *pdev) +{ + struct phytium_mbox *mbox; + struct resource *res; + int err, irq; + + /* Allocate memory for device */ + mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mbox->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mbox->base)) { + dev_err(&pdev->dev, "ioremap base failed\n"); + return PTR_ERR(mbox->base); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "cannot obtain irq\n"); + return irq; + } + + mbox->chan.con_priv = &mbox->mlink; + mbox->mlink.irq = irq; + mbox->mlink.rx_reg = mbox->base; + mbox->mlink.tx_reg = mbox->mlink.rx_reg + TX_REG; + + mbox->mbox.dev = &pdev->dev; + mbox->mbox.chans = &mbox->chan; + mbox->mbox.num_chans = NR_CHANS; + mbox->mbox.ops = &phytium_mbox_ops; + mbox->mbox.txdone_irq = false; + mbox->mbox.txdone_poll = true; + mbox->mbox.txpoll_period = 1; + + platform_set_drvdata(pdev, mbox); + + err = mbox_controller_register(&mbox->mbox); + if (err) { + dev_err(&pdev->dev, "Failed to register mailboxes %d\n", err); + goto fail; + } + + dev_info(&pdev->dev, "Phytium SoC Mailbox registered\n"); +fail: + return err; +} + +static int phytium_mbox_remove(struct platform_device *pdev) +{ + struct phytium_mbox *mbox = platform_get_drvdata(pdev); + + mbox_controller_unregister(&mbox->mbox); + + return 0; +} + +static struct platform_driver phytium_mbox_driver = { + .probe = phytium_mbox_probe, + .remove = phytium_mbox_remove, + .driver = { + .name = "phytium-mbox", + .of_match_table = of_match_ptr(phytium_mbox_of_match), + .acpi_match_table = ACPI_PTR(phytium_mbox_acpi_match), + }, +}; + +module_platform_driver(phytium_mbox_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium SoC Mailbox Driver"); +MODULE_AUTHOR("Chen Baozi "); diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index ee579916f8744a5df7c8f805c6605dd3a67b4497..20a72238a49b1ee840d2b9078a26e865f1a3052e 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -75,6 +75,7 @@ source "drivers/media/platform/mediatek/Kconfig" source "drivers/media/platform/microchip/Kconfig" source "drivers/media/platform/nvidia/Kconfig" source "drivers/media/platform/nxp/Kconfig" +source "drivers/media/platform/phytium/Kconfig" source "drivers/media/platform/qcom/Kconfig" source "drivers/media/platform/renesas/Kconfig" source "drivers/media/platform/rockchip/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 5453bb868e6794d634fa3c32ee1b6a3de35c4880..ab61f38de30722c074c240895e33a7f91f14f8f7 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -18,6 +18,7 @@ obj-y += mediatek/ obj-y += microchip/ obj-y += nvidia/ obj-y += nxp/ +obj-y += phytium/ obj-y += qcom/ obj-y += renesas/ obj-y += rockchip/ diff --git a/drivers/media/platform/phytium/Kconfig b/drivers/media/platform/phytium/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..498314fbfc94763260f4898ba65195268ff0a378 --- /dev/null +++ b/drivers/media/platform/phytium/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Phytium media platform drivers" + +config VIDEO_PHYTIUM_JPEG + tristate "Phytium JPEG Encoder Engine driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + select VIDEOBUF2_DMA_CONTIG + help + Support for the Phytium JPEG Encoder Engine embedded + in the Phytium SOCs. The engine can capture and + compress video data from digital or analog sources. + diff --git a/drivers/media/platform/phytium/Makefile b/drivers/media/platform/phytium/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..79b6bfa52d5041b5f3651e7fd05e0286f0dd08ba --- /dev/null +++ b/drivers/media/platform/phytium/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +phytium_jpeg-objs := phytium_jpeg_core.o + +obj-$(CONFIG_VIDEO_PHYTIUM_JPEG) += phytium_jpeg.o diff --git a/drivers/media/platform/phytium/phytium_jpeg_core.c b/drivers/media/platform/phytium/phytium_jpeg_core.c new file mode 100644 index 0000000000000000000000000000000000000000..43a87c6e569d31fd2f5b903dc239f778b91ca030 --- /dev/null +++ b/drivers/media/platform/phytium/phytium_jpeg_core.c @@ -0,0 +1,1379 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Phytium JPEG Encoder Engine + * + * Copyright (c) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include "phytium_jpeg_reg.h" +#include "phytium_jpeg_core.h" +#include + +static u32 phytium_jpeg_header[PHYTIUM_JPEG_HEADER_SIZE] = { + 0xe0ffd8ff, 0x464a0100, 0x01004649, 0x01000001, + 0x00000100, 0x4300dbff, 0x0c0b1000, 0x100a0c0e, + 0x120e0d0e, 0x18131011, 0x16181a28, 0x23311816, + 0x3a281d25, 0x393c3d33, 0x40373833, 0x404e5c48, + 0x37455744, 0x516d5038, 0x67625f57, 0x4d3e6768, + 0x64707971, 0x67655c78, 0x00dbff63, 0x12110143, + 0x18151812, 0x2f1a1a2f, 0x42384263, 0x63636363, + 0x63636363, 0x63636363, 0x63636363, 0x63636363, + 0x63636363, 0x63636363, 0x63636363, 0x63636363, + 0x63636363, 0x63636363, 0x63636363, 0xc0ff6363, + /* h_index(40) indicates high 8 bits of the height + * w_index(41) contains the low 8 bits of the height, + * and the width. For example, height 480(0x01e0) + * locates at 0x<01> 081100 and 0x038002 . + * width 640 (0x0280) locates at 0x03 <80> <02> e0. + */ + + /* 0x0200 <11> 01 is a field marks YUV mode */ + 0x01081100, 0x038002e0, 0x02001101, 0x11030111, + 0x00c4ff01, 0x0100001f, 0x01010105, 0x00010101, + 0x00000000, 0x01000000, 0x05040302, 0x09080706, + 0xc4ff0b0a, 0x00011f00, 0x01010103, 0x01010101, + 0x00000101, 0x00000000, 0x04030201, 0x08070605, + 0xff0b0a09, 0x10b500c4, 0x03010200, 0x03040203, + 0x04040505, 0x7d010000, 0x00030201, 0x12051104, + 0x06413121, 0x07615113, 0x32147122, 0x08a19181, + 0xc1b14223, 0xf0d15215, 0x72623324, 0x160a0982, + 0x1a191817, 0x28272625, 0x35342a29, 0x39383736, + 0x4544433a, 0x49484746, 0x5554534a, 0x59585756, + 0x6564635a, 0x69686766, 0x7574736a, 0x79787776, + 0x8584837a, 0x89888786, 0x9493928a, 0x98979695, + 0xa3a29a99, 0xa7a6a5a4, 0xb2aaa9a8, 0xb6b5b4b3, + 0xbab9b8b7, 0xc5c4c3c2, 0xc9c8c7c6, 0xd4d3d2ca, + 0xd8d7d6d5, 0xe2e1dad9, 0xe6e5e4e3, 0xeae9e8e7, + 0xf4f3f2f1, 0xf8f7f6f5, 0xc4fffaf9, 0x0011b500, + 0x04020102, 0x07040304, 0x00040405, 0x00770201, + 0x11030201, 0x31210504, 0x51411206, 0x13716107, + 0x08813222, 0xa1914214, 0x2309c1b1, 0x15f05233, + 0x0ad17262, 0xe1342416, 0x1817f125, 0x27261a19, + 0x352a2928, 0x39383736, 0x4544433a, 0x49484746, + 0x5554534a, 0x59585756, 0x6564635a, 0x69686766, + 0x7574736a, 0x79787776, 0x8483827a, 0x88878685, + 0x93928a89, 0x97969594, 0xa29a9998, 0xa6a5a4a3, + 0xaaa9a8a7, 0xb5b4b3b2, 0xb9b8b7b6, 0xc4c3c2ba, + 0xc8c7c6c5, 0xd3d2cac9, 0xd7d6d5d4, 0xe2dad9d8, + 0xe6e5e4e3, 0xeae9e8e7, 0xf5f4f3f2, 0xf9f8f7f6, + 0x00fefffa, 0x0000008f, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xdaff0000, 0x01030c00, 0x03110200, 0x003f0011 +}; + +static char yuv_mode_str[YUV_MODE_STR_LEN] = { "yuv444" }; + +module_param_string(yuv_mode, yuv_mode_str, sizeof(yuv_mode_str), 0444); +MODULE_PARM_DESC(yuv_mode, "Users select one mode from such modes as 'yuv444', or 'yuv422', or 'yuv420'. If no mode is set, the driver adapts defaults mode 'yuv444'."); + +static u32 phytium_jpeg_read(struct phytium_jpeg_dev *jpeg_dev, u32 reg) +{ + u32 reg_val = readl(jpeg_dev->base_addr + reg); + + dev_dbg(jpeg_dev->dev, "read 0x%p + 0x%x -->val[0x%x]\n", + jpeg_dev->base_addr, reg, reg_val); + + return reg_val; +} + +static void phytium_jpeg_write(struct phytium_jpeg_dev *jpeg_dev, + u32 reg, u32 val) +{ + writel(val, jpeg_dev->base_addr + reg); + dev_dbg(jpeg_dev->dev, "write 0x%x to addr 0x%p + 0x%x\n", + val, jpeg_dev->base_addr, reg); +} + +static void phytium_jpeg_update(struct phytium_jpeg_dev *jpeg_dev, u32 reg, + u32 clear, u32 bits) +{ + u32 reg_val = readl(jpeg_dev->base_addr + reg); + u32 tmp = reg_val; + + reg_val &= ~clear; + reg_val |= bits; + writel(reg_val, jpeg_dev->base_addr + reg); + + dev_dbg(jpeg_dev->dev, "the val of addr 0x%p + 0x%x, from 0x%x to 0x%x\n", + jpeg_dev->base_addr, reg, tmp, readl(jpeg_dev->base_addr + reg)); +} + +static void phytium_jpeg_init_regs(struct phytium_jpeg_dev *jpeg_dev) +{ + u32 transform_info = 0; + u32 disable_all_interrupt = 0; + u32 clear_all_interrupt = INT_FIFO_OVERFLOW | INT_OCM_BUF_OVERFLOW | + INT_JPEG_ENCODE_COMPLETE | INT_VIDEO_FORMAT_CHANGE; + u32 rate_to_reg = 0; + + /* First, disable the JPEG engine, set bit0 = 0*/ + phytium_jpeg_write(jpeg_dev, TRANSFORM_INFO_REG, transform_info); + + /* Second, set VGAvideo_source_information. bit1 = 0 marks VGA */ + transform_info |= 0; + + /* Third, set AXI burst length bit[16:22]= 0xf , default value*/ + transform_info |= (0xF << TRANS_AXI_LEN_SHIFT) & TRANSINFO_AXI_LEN; + + /* Fourth, the default sampling format is YUV422, set bit13 to 0 */ + /* ignore setting sampling interval */ + phytium_jpeg_write(jpeg_dev, TRANSFORM_INFO_REG, transform_info); + udelay(5); + + /* Fifth, setting frame rate. + * Linux driver prohibit float point operations. So use the + * format: reg_val = (1 second * 10^8 / frame_rate / 134 *100) + * write reg_val to register. then enable Highest bit31 = 1 + */ + if (jpeg_dev->frame_rate) { + rate_to_reg = 100000000 / jpeg_dev->frame_rate / 134 * 100; + rate_to_reg |= FRAME_SAMPLE_CTRL_EN; + phytium_jpeg_write(jpeg_dev, FRAME_SAMPLE_CTRL, rate_to_reg); + } + /* Sixth, HUFF_MODE, driver needn't to configure, ignore */ + + /* disable all interrupts and then clear all interrupts */ + phytium_jpeg_write(jpeg_dev, INT_STATUS_CTRL_REG, + disable_all_interrupt); + udelay(5); + phytium_jpeg_write(jpeg_dev, INT_STATUS_CTRL_REG, clear_all_interrupt); + + /* Seventh, Sample_mode, hardware default is yuv444 */ + jpeg_dev->yuv420 = false; +} + +/* Turn on the clock of the jpeg engine */ +static void phytium_jpeg_on(struct phytium_jpeg_dev *jpeg_dev) +{ + if (test_bit(VIDEO_CLOCKS_ON, &jpeg_dev->status)) + return; + + /* Turn on the relevant clocks */ + set_bit(VIDEO_CLOCKS_ON, &jpeg_dev->status); +} + +/* Disable the jpeg engine */ +static void phytium_jpeg_off(struct phytium_jpeg_dev *jpeg_dev) +{ + u32 disable_all_interrupt = 0; + u32 clear_all_interrupt = INT_FIFO_OVERFLOW | INT_OCM_BUF_OVERFLOW | + INT_JPEG_ENCODE_COMPLETE | INT_VIDEO_FORMAT_CHANGE; + + if (!test_bit(VIDEO_CLOCKS_ON, &jpeg_dev->status)) { + dev_info(jpeg_dev->dev, "JPEG Engine is already off.\n"); + return; + } + + /* disable all interrupt */ + phytium_jpeg_write(jpeg_dev, INT_STATUS_CTRL_REG, disable_all_interrupt); + /* clear all interrupt */ + phytium_jpeg_write(jpeg_dev, INT_STATUS_CTRL_REG, clear_all_interrupt); + /* disable JPEG engine */ + phytium_jpeg_update(jpeg_dev, TRANSFORM_INFO_REG, TRANSINFO_ENABLE_ENGINE, 0); + + clear_bit(VIDEO_CLOCKS_ON, &jpeg_dev->status); + /* wait 50 ms */ + mdelay(50); + /* C08 bit7 1:busy */ +} + +static inline void phytium_jpeg_enable_source_detecting(struct phytium_jpeg_dev *jpeg_dev) +{ + /* + * Enable the dectection to discovery + * the source resolution is changed + */ + //phytium_jpeg_update(jpeg_dev, INT_STATUS_CTRL_REG, 0, DETECT_RESOLUTION_CHANGE_EN); + phytium_jpeg_update(jpeg_dev, TRANSFORM_INFO_REG, 0, TRANSINFO_SRC_SELECT); +} + +#define res_check(val) \ + test_and_clear_bit(VIDEO_MODE_DETECT_DONE, &(val)->status) + +static void phytium_jpeg_get_resolution(struct phytium_jpeg_dev *jpeg_dev) +{ + u32 source_info; + u32 width; + u32 height; + struct v4l2_bt_timings *detected_timings = &jpeg_dev->detected_timings; + + /* Before get a new resolution, maybe need to wait 10 us */ + detected_timings->width = MIN_WIDTH; + detected_timings->height = MIN_HEIGHT; + jpeg_dev->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + + + phytium_jpeg_enable_source_detecting(jpeg_dev); + source_info = phytium_jpeg_read(jpeg_dev, SRC_VGA_INFO_REG); + width = (source_info & SRC_HOR_PIXELS) >> SRC_WIDTH_SHIFT; + height = (source_info & SRC_VER_PIXELS) >> SRC_HEIGHT_SHIFT; + + if (width * height != 0) { + detected_timings->width = width; + detected_timings->height = height; + } + + jpeg_dev->v4l2_input_status = 0; + + /* + * Resolution is changed will trigger an interrupt, resolution detecting + * also is disable during process interrupt. So re-enable. + */ + phytium_jpeg_enable_source_detecting(jpeg_dev); + dev_info(jpeg_dev->dev, "Change resolution: %uX%u\n", width, height); +} + +static void phytium_jpeg_set_resolution(struct phytium_jpeg_dev *jpeg_dev) +{ + struct v4l2_bt_timings *active_timings = &jpeg_dev->active_timings; + int i; + int src_addrs[OCM_BUF_NUM]; + /* + * The OCM address space is 0x30C0_0000 ~ 0x30C7_FFFF, JPEG Engine uses the + * high-bottom address. src_0 uses 0x30C4_0000 ~ 0x30c6_0000 (total capacity is + * 128KB, greater than the requirements of the largest resolution). src_1 uses + * 0x30C6_0000 ~ 0x30C7_FFFF. + */ + + /* The OCM address should shift right 8 bits */ + for (i = 0; i < OCM_BUF_NUM; i++) + src_addrs[i] = jpeg_dev->src_addrs[i].dma_addr >> OCM_BUF_SHIFT; + + phytium_jpeg_write(jpeg_dev, OCM_BUF0_ADDR, src_addrs[0]); + phytium_jpeg_write(jpeg_dev, OCM_BUF1_ADDR, src_addrs[1]); + + /* + * In the worst case, the size of one image will be compressed to 25% the + * raw image's size. When a pixel is 4-byte, no need to divide 4. + */ + jpeg_dev->max_compressed_size = active_timings->width * active_timings->height; +} + +/* The below functions is implemented for various v4l2 ioctl operations */ +static int phytium_jpeg_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + strscpy(cap->driver, PHYTIUM_JPEG_NAME, sizeof(cap->driver)); + strscpy(cap->card, "Phytium JPEG Engine", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", dev_name(jpeg_dev->dev)); + + return 0; +} + +static int phytium_jpeg_enum_format(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + if (f->index) { + dev_err(jpeg_dev->dev, "Failed to enum format\n"); + return -EINVAL; + } + + f->pixelformat = V4L2_PIX_FMT_JPEG; + + return 0; +} + +static int phytium_jpeg_get_format(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + f->fmt.pix = jpeg_dev->pix_fmt; + + return 0; +} + +static int phytium_jpeg_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + if (input->index) { + dev_err(jpeg_dev->dev, "failed to enum input\n"); + return -EINVAL; + } + + strscpy(input->name, "Host DC Capture", sizeof(input->name)); + input->type = V4L2_INPUT_TYPE_CAMERA; + input->capabilities = V4L2_IN_CAP_DV_TIMINGS; + input->status = jpeg_dev->v4l2_input_status; + + return 0; +} + +static int phytium_jpeg_get_input(struct file *file, void *priv, + unsigned int *i) +{ + *i = 0; + return 0; +} + +static int phytium_jpeg_set_input(struct file *file, void *priv, + unsigned int i) +{ + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + if (i != 0) { + dev_err(jpeg_dev->dev, "Failed to set input\n"); + return -EINVAL; + } + + return 0; +} + +static int phytium_jpeg_get_parm(struct file *file, void *priv, + struct v4l2_streamparm *stream) +{ + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + /* Readbuffers num is 3 */ + stream->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + stream->parm.capture.readbuffers = CAPTURE_BUF_NUMBER; + stream->parm.capture.timeperframe.denominator = 1; + + if (jpeg_dev->frame_rate == 0) + stream->parm.capture.timeperframe.denominator = MAX_FRAME_RATE; + else + stream->parm.capture.timeperframe.denominator = jpeg_dev->frame_rate; + + return 0; +} + +static int phytium_jpeg_set_parm(struct file *file, void *priv, + struct v4l2_streamparm *stream) +{ + unsigned int frame_rate = 0; + u32 rate_to_reg = 0; + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + /* Readbuffers num is 3 */ + stream->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + stream->parm.capture.readbuffers = CAPTURE_BUF_NUMBER; + + if (stream->parm.capture.timeperframe.numerator) + frame_rate = stream->parm.capture.timeperframe.denominator / + stream->parm.capture.timeperframe.numerator; + + if (frame_rate == 0 || frame_rate > MAX_FRAME_RATE) { + frame_rate = MAX_FRAME_RATE; + stream->parm.capture.timeperframe.denominator = MAX_FRAME_RATE; + stream->parm.capture.timeperframe.numerator = 1; + } + /* + * reg_val = (1 second * 10^9 / frame_rate / 13.4) + * Linux driver prohibit float point operations. So use the + * format: reg_val = (1 second * 10^8 / frame_rate / 134 *100) + * write reg_val to register. then enable Highest bit31 = 1 + */ + if (jpeg_dev->frame_rate != frame_rate) { + jpeg_dev->frame_rate = frame_rate; + rate_to_reg = 100000000 / jpeg_dev->frame_rate / 134 * 100; + rate_to_reg |= FRAME_SAMPLE_CTRL_EN; + phytium_jpeg_write(jpeg_dev, FRAME_SAMPLE_CTRL, rate_to_reg); + } + + return 0; +} + +static int phytium_jpeg_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) + +{ + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + if (fsize->index != 0) { + dev_err(jpeg_dev->dev, "Failed to enum framesize.\n"); + return -EINVAL; + } + + if (fsize->pixel_format != V4L2_PIX_FMT_JPEG) { + dev_err(jpeg_dev->dev, "enum framesize pixel_format is not JPEG"); + return -EINVAL; + } + + fsize->discrete.width = jpeg_dev->pix_fmt.width; + fsize->discrete.height = jpeg_dev->pix_fmt.height; + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + + return 0; + +} + +static int phytium_jpeg_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + if (fival->index != 0) { + dev_err(jpeg_dev->dev, "enum frame intervals failed\n"); + return -EINVAL; + } + + if (fival->width != jpeg_dev->detected_timings.width || + fival->height != jpeg_dev->detected_timings.height) { + dev_err(jpeg_dev->dev, "interval isn't same with the detected_timings.\n"); + return -EINVAL; + } + + if (fival->pixel_format != V4L2_PIX_FMT_JPEG) { + dev_err(jpeg_dev->dev, "enum frame interval pixel fomat is incorrect.\n"); + return -EINVAL; + } + + fival->type = V4L2_FRMIVAL_TYPE_CONTINUOUS; + fival->stepwise.min.denominator = MAX_FRAME_RATE; + fival->stepwise.min.numerator = 1; + fival->stepwise.max.denominator = 1; + fival->stepwise.max.numerator = 1; + fival->stepwise.step = fival->stepwise.max; + + return 0; +} + +static int phytium_jpeg_set_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + /* the params are passed from user space are same with hardware's params */ + if (timings->bt.width == jpeg_dev->active_timings.width && + timings->bt.height == jpeg_dev->active_timings.height) + return 0; + + if (vb2_is_busy(&jpeg_dev->queue)) { + dev_err(jpeg_dev->dev, "queue is busy during setting dv timings.\n"); + return -EBUSY; + } + + jpeg_dev->active_timings = timings->bt; + phytium_jpeg_set_resolution(jpeg_dev); + jpeg_dev->pix_fmt.width = timings->bt.width; + jpeg_dev->pix_fmt.height = timings->bt.height; + jpeg_dev->pix_fmt.sizeimage = jpeg_dev->max_compressed_size; + timings->type = V4L2_DV_BT_656_1120; + + return 0; +} + +static int phytium_jpeg_get_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + timings->type = V4L2_DV_BT_656_1120; + timings->bt = jpeg_dev->active_timings; + + return 0; +} + +static int phytium_jpeg_query_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + int ret; + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + /* + * This blocks only if the driver is currently in the process of + * detecting a new resolution; in the event of no signal or timeout + * this function is woken up. + */ + if ((file->f_flags & O_NONBLOCK) && + test_bit(VIDEO_RES_CHANGE, &jpeg_dev->status)) + return -EAGAIN; + + ret = wait_event_interruptible(jpeg_dev->wait, !test_bit(VIDEO_RES_CHANGE, + &jpeg_dev->status)); + if (ret) { + dev_err(jpeg_dev->dev, "Failed to query dv timing\n"); + return -EINTR; + } + + timings->type = V4L2_DV_BT_656_1120; + timings->bt = jpeg_dev->detected_timings; + + return jpeg_dev->v4l2_input_status ? -ENOLINK : 0; +} + +static const struct v4l2_dv_timings_cap phytium_jpeg_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .bt = { + .min_width = MIN_WIDTH, + .max_width = MAX_WIDTH, + .min_height = MIN_HEIGHT, + .max_height = MAX_HEIGHT, + .min_pixelclock = 6574080, /* 640 x 480 x 24Hz */ + .max_pixelclock = 1244160000, /* 1920 x 1080 x 60Hz */ + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF, + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM, + }, +}; + +static int phytium_jpeg_enum_dv_timings(struct file *file, void *priv, + struct v4l2_enum_dv_timings *timings) +{ + return v4l2_enum_dv_timings_cap(timings, &phytium_jpeg_timings_cap, + NULL, NULL); +} + +static int phytium_jpeg_dv_timings_cap(struct file *file, void *priv, + struct v4l2_dv_timings_cap *cap) +{ + *cap = phytium_jpeg_timings_cap; + + return 0; +} + +/* The function is used to notify DV that video resolution is altered */ +static int phytium_jpeg_sub_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + default: + break; + } + + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static const struct v4l2_ioctl_ops phytium_jpeg_ioctl_ops = { + .vidioc_querycap = phytium_jpeg_querycap, + .vidioc_enum_fmt_vid_cap = phytium_jpeg_enum_format, + .vidioc_g_fmt_vid_cap = phytium_jpeg_get_format, + .vidioc_s_fmt_vid_cap = phytium_jpeg_get_format, + .vidioc_try_fmt_vid_cap = phytium_jpeg_get_format, + .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_enum_input = phytium_jpeg_enum_input, + .vidioc_g_input = phytium_jpeg_get_input, + .vidioc_s_input = phytium_jpeg_set_input, + .vidioc_g_parm = phytium_jpeg_get_parm, + .vidioc_s_parm = phytium_jpeg_set_parm, + .vidioc_enum_framesizes = phytium_jpeg_enum_framesizes, + .vidioc_enum_frameintervals = phytium_jpeg_enum_frameintervals, + .vidioc_s_dv_timings = phytium_jpeg_set_dv_timings, + .vidioc_g_dv_timings = phytium_jpeg_get_dv_timings, + .vidioc_query_dv_timings = phytium_jpeg_query_dv_timings, + .vidioc_enum_dv_timings = phytium_jpeg_enum_dv_timings, + .vidioc_dv_timings_cap = phytium_jpeg_dv_timings_cap, + .vidioc_subscribe_event = phytium_jpeg_sub_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static void phytium_jpeg_init_jpeg_quant(struct phytium_jpeg_dev *jpeg_dev) +{ + const u32 y_quant_table[QUANT_REG_NUM] = { + 0x08000000, 0x0ba2e8ba, 0x0aaaaaab, 0x09249249, 0x0aaaaaab, + 0x0ccccccc, 0x08000000, 0x09249249, 0x09d89d8a, 0x09249249, + 0x071c71c7, 0x07878788, 0x08000000, 0x06bca1af, 0x05555555, + 0x03333333, 0x04ec4ec5, 0x05555555, 0x05d1745d, 0x05d1745d, + 0x05555555, 0x029cbc15, 0x03a83a84, 0x03759f23, 0x0469ee58, + 0x03333333, 0x0234f72c, 0x02828283, 0x02192e2a, 0x02222222, + 0x023ee090, 0x02828283, 0x02492492, 0x0253c825, 0x02000000, + 0x01c71c72, 0x01642c86, 0x01a41a42, 0x02000000, 0x01e1e1e2, + 0x0178a4c8, 0x01dae607, 0x0253c825, 0x02492492, 0x0199999a, + 0x012c9fb5, 0x01948b10, 0x0178a4c8, 0x0158ed23, 0x014e5e0a, + 0x013e22cc, 0x013b13b1, 0x013e22cc, 0x02108421, 0x01a98ef6, + 0x0121fb78, 0x010ecf57, 0x01249249, 0x013e22cc, 0x01111111, + 0x01642c86, 0x01446f86, 0x013e22cc, 0x014afd6a + }; + + const u32 c_quant_table[QUANT_REG_NUM] = { + 0x07878788, 0x071c71c7, 0x071c71c7, 0x05555555, 0x06186186, + 0x05555555, 0x02b93105, 0x04ec4ec5, 0x04ec4ec5, 0x02b93105, + 0x014afd6a, 0x01f07c1f, 0x02492492, 0x01f07c1f, 0x014afd6a, + 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, + 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, + 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, + 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, + 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, + 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, + 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, + 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, + 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a, + 0x014afd6a, 0x014afd6a, 0x014afd6a, 0x014afd6a + }; + int i; + + for (i = 0; i < QUANT_REG_NUM; i++) { + phytium_jpeg_write(jpeg_dev, Y_QUANT_INDEX_ADDR_REG(i), y_quant_table[i]); + phytium_jpeg_write(jpeg_dev, C_QUANT_INDEX_ADDR_REG(i), c_quant_table[i]); + } + +} + +static void phytium_jpeg_start(struct phytium_jpeg_dev *jpeg_dev) +{ + phytium_jpeg_on(jpeg_dev); + phytium_jpeg_init_regs(jpeg_dev); + + /* Resolution set to 640x480 if no signal is found */ + phytium_jpeg_get_resolution(jpeg_dev); + + /* Set timings since the device is being opened for the first tiime */ + jpeg_dev->active_timings = jpeg_dev->detected_timings; + phytium_jpeg_set_resolution(jpeg_dev); + + jpeg_dev->pix_fmt.width = jpeg_dev->active_timings.width; + jpeg_dev->pix_fmt.height = jpeg_dev->active_timings.height; + jpeg_dev->pix_fmt.sizeimage = jpeg_dev->max_compressed_size; +} + +static void phytium_jpeg_stop(struct phytium_jpeg_dev *jpeg_dev) +{ + set_bit(VIDEO_STOPPED, &jpeg_dev->status); + cancel_delayed_work_sync(&jpeg_dev->res_work); + + phytium_jpeg_off(jpeg_dev); + + jpeg_dev->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + jpeg_dev->status = 0; +} + +static int phytium_jpeg_open(struct file *file) +{ + int ret; + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + mutex_lock(&jpeg_dev->video_lock); + + ret = v4l2_fh_open(file); + if (ret != 0) { + mutex_unlock(&jpeg_dev->video_lock); + dev_err(jpeg_dev->dev, "Failed to open the phytium jpeg device.\n"); + return ret; + } + + if (v4l2_fh_is_singular_file(file)) + phytium_jpeg_start(jpeg_dev); + + mutex_unlock(&jpeg_dev->video_lock); + + return 0; +} + +static int phytium_jpeg_release(struct file *file) +{ + int ret; + struct phytium_jpeg_dev *jpeg_dev = video_drvdata(file); + + mutex_lock(&jpeg_dev->video_lock); + + if (v4l2_fh_is_singular_file(file)) + phytium_jpeg_stop(jpeg_dev); + + ret = _vb2_fop_release(file, NULL); + mutex_unlock(&jpeg_dev->video_lock); + + return ret; +} + + +static const struct v4l2_file_operations phytium_jpeg_fops = { + .owner = THIS_MODULE, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .open = phytium_jpeg_open, + .release = phytium_jpeg_release, +}; + +static void phytium_jpeg_update_jpeg_header(u32 width, u32 height) +{ + const int h_index = PHYTIUM_JPEG_HEADER_H_INDEX; + const int w_index = PHYTIUM_JPEG_HEADER_W_INDEX; + + /* the high 8 bits of the height locates at bit24~bit31 */ + phytium_jpeg_header[h_index] = phytium_jpeg_header[h_index] & 0x00FFFFFF; + phytium_jpeg_header[h_index] |= ((height >> 8) & 0xFF) << 24; + + /* the low 8 bits of the height locates at bit0~bit7 */ + phytium_jpeg_header[w_index] = phytium_jpeg_header[w_index] & 0xFF000000; + phytium_jpeg_header[w_index] |= height & 0xFF; + + /* the high 8 bits of the width locates at bit8~bit15 */ + phytium_jpeg_header[w_index] |= ((width >> 8) & 0xFF) << 8; + /* the low 8 bits of the width locates at bit16~bit24 */ + phytium_jpeg_header[w_index] |= (width & 0xFF) << 16; +} + +static void phytium_jpeg_fill_header(struct phytium_jpeg_dev *jpeg_dev, + struct phytium_jpeg_buffer *jpeg_buf) +{ + void *vbuf = vb2_plane_vaddr(&jpeg_buf->vb.vb2_buf, 0); + u32 width = jpeg_dev->active_timings.width; + u32 height = jpeg_dev->active_timings.height; + + /* update the contents of the phytium jpeg header according to the resolution */ + phytium_jpeg_update_jpeg_header(width, height); + + /* replenish the contents of the JPEG header */ + memcpy(vbuf, phytium_jpeg_header, PHYTIUM_JPEG_HEADER_LEN); +} + +static int phytium_jpeg_start_frame(struct phytium_jpeg_dev *jpeg_dev) +{ + dma_addr_t dst_addr; + unsigned long status; + struct phytium_jpeg_buffer *jpeg_buf; + + if (jpeg_dev->v4l2_input_status) { + dev_err(jpeg_dev->dev, "No signal; needn't start frame\n"); + return 0; + } + + spin_lock_irqsave(&jpeg_dev->hw_lock, status); + jpeg_buf = list_first_entry_or_null(&jpeg_dev->buffers, + struct phytium_jpeg_buffer, link); + if (jpeg_buf == NULL) { + spin_unlock_irqrestore(&jpeg_dev->hw_lock, status); + dev_err(jpeg_dev->dev, "No buffers; doesn't start frame\n"); + return -EPROTO; + } + + set_bit(VIDEO_FRAME_INPRG, &jpeg_dev->status); + dst_addr = vb2_dma_contig_plane_dma_addr(&jpeg_buf->vb.vb2_buf, 0); + spin_unlock_irqrestore(&jpeg_dev->hw_lock, status); + + /* + * Because the JPEG Engine is unable to add a JPEG header, the phytium + * jpeg driver is required to fill the contents of a JPEG header before + * the jpeg engine write datas to the dma address. + */ + phytium_jpeg_fill_header(jpeg_dev, jpeg_buf); + dst_addr += PHYTIUM_JPEG_HEADER_LEN; + /* + * The ikvm application only using the last frame, so the driver replenish + * one output register with a dma address. + */ + dst_addr >>= JPEG_DST_ADDR_SHIFT; + phytium_jpeg_write(jpeg_dev, BUF_LIST_INDEX_ADDR(VB_BUF_NO), dst_addr); + /* Enable the validilty of the buffer marked with index */ + phytium_jpeg_write(jpeg_dev, BUF_LIST_INDEX_CTRL_STS_ADDR(VB_BUF_NO), + STS_JPEG_BUF_HIGH_LEVEL_VALID); + /* Enable the interruption which is used to identify an image was compressed */ + phytium_jpeg_update(jpeg_dev, INT_STATUS_CTRL_REG, 0, STS_VE_JPEG_CODE_COMP_EN); + /* Enable JPEG, start to capture and compress */ + phytium_jpeg_update(jpeg_dev, TRANSFORM_INFO_REG, TRANSINFO_ENABLE_ENGINE, 1); + + return 0; +} + +static void phytium_jpeg_resolution_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct phytium_jpeg_dev *jpeg_dev = container_of(dwork, + struct phytium_jpeg_dev, res_work); + u32 input_status = jpeg_dev->v4l2_input_status; + + phytium_jpeg_on(jpeg_dev); + + /* Exit early in the case no clients remain */ + if (test_bit(VIDEO_STOPPED, &jpeg_dev->status)) + goto done; + + phytium_jpeg_init_regs(jpeg_dev); + phytium_jpeg_get_resolution(jpeg_dev); + + /* if source's resolution is changed, the event should be enqueued */ + if (jpeg_dev->detected_timings.width != jpeg_dev->active_timings.width || + jpeg_dev->detected_timings.height != jpeg_dev->active_timings.height || + input_status != jpeg_dev->v4l2_input_status) { + + static const struct v4l2_event event = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + v4l2_event_queue(&jpeg_dev->vdev, &event); + clear_bit(VIDEO_FRAME_INPRG, &jpeg_dev->status); + dev_info(jpeg_dev->dev, "event notifies changing resolution\n"); + } else if (test_bit(VIDEO_STREAMING, &jpeg_dev->status)) { + /* No resolution change so just restart streaming */ + dev_info(jpeg_dev->dev, "resolution doesn't change\n"); + phytium_jpeg_set_resolution(jpeg_dev); + phytium_jpeg_start_frame(jpeg_dev); + } + +done: + clear_bit(VIDEO_RES_CHANGE, &jpeg_dev->status); + wake_up_interruptible_all(&jpeg_dev->wait); +} + +static int phytium_jpeg_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct phytium_jpeg_dev *jpeg_dev = vb2_get_drv_priv(q); + + if (*num_planes) { + if (sizes[0] < jpeg_dev->max_compressed_size) { + v4l2_err(&jpeg_dev->v4l2_dev, "queue v4l2_buf's size is invalid\n"); + return -EINVAL; + } + } + + *num_planes = 1; + sizes[0] = jpeg_dev->max_compressed_size; + return 0; +} + +static int phytium_jpeg_buf_prepare(struct vb2_buffer *vb) +{ + struct phytium_jpeg_dev *jpeg_dev = vb2_get_drv_priv(vb->vb2_queue); + + if (vb2_plane_size(vb, 0) < jpeg_dev->max_compressed_size) { + v4l2_err(&jpeg_dev->v4l2_dev, "failed to prepare buffer\n"); + return -EINVAL; + } + + return 0; +} + +static inline struct phytium_jpeg_buffer * +phytium_vb2buf_to_dstbuf(struct vb2_v4l2_buffer *buf) +{ + return container_of(buf, struct phytium_jpeg_buffer, vb); +} + +static void phytium_jpeg_buf_queue(struct vb2_buffer *vb) +{ + bool empty; + struct phytium_jpeg_dev *jpeg_dev = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct phytium_jpeg_buffer *jpeg_buf = phytium_vb2buf_to_dstbuf(vbuf); + unsigned long status; + + spin_lock_irqsave(&jpeg_dev->hw_lock, status); + empty = list_empty(&jpeg_dev->buffers); + list_add_tail(&jpeg_buf->link, &jpeg_dev->buffers); + spin_unlock_irqrestore(&jpeg_dev->hw_lock, status); + + /* the empty ensures the address of the first node's vb2_v4l2_buf + * in the list is written to output register + */ + if (test_bit(VIDEO_STREAMING, &jpeg_dev->status) && + (!test_bit(VIDEO_FRAME_INPRG, &jpeg_dev->status)) && + empty) + phytium_jpeg_start_frame(jpeg_dev); +} + +static void phytium_jpeg_bufs_done(struct phytium_jpeg_dev *jpeg_dev, + enum vb2_buffer_state state) +{ + unsigned long flags; + struct phytium_jpeg_buffer *buf; + + spin_lock_irqsave(&jpeg_dev->hw_lock, flags); + + list_for_each_entry(buf, &jpeg_dev->buffers, link) + vb2_buffer_done(&buf->vb.vb2_buf, state); + + INIT_LIST_HEAD(&jpeg_dev->buffers); + + spin_unlock_irqrestore(&jpeg_dev->hw_lock, flags); +} + +static void phytium_jpeg_irq_res_change(struct phytium_jpeg_dev *jpeg_dev, + ulong delay) +{ + dev_info(jpeg_dev->dev, "Source resolution is changed, resetting\n"); + + phytium_jpeg_off(jpeg_dev); + + schedule_delayed_work(&jpeg_dev->res_work, delay); +} + +static irqreturn_t phytium_jpeg_irq(int irq, void *arg) +{ + struct phytium_jpeg_dev *jpeg_dev = arg; + u32 status; + struct phytium_jpeg_buffer *buf; + u32 frame_size; + + if (test_bit(VIDEO_POWEROFF, &jpeg_dev->status)) { + dev_info(jpeg_dev->dev, "jpeg engine is requested to poweroff\n"); + return IRQ_HANDLED; + } + + status = phytium_jpeg_read(jpeg_dev, INT_STATUS_CTRL_REG); + + if (status & INT_VIDEO_FORMAT_CHANGE) { + dev_info(jpeg_dev->dev, "receive resolution changed interrupt\n"); + phytium_jpeg_update(jpeg_dev, INT_STATUS_CTRL_REG, + DETECT_RESOLUTION_CHANGE_EN, 0); + phytium_jpeg_write(jpeg_dev, INT_STATUS_CTRL_REG, INT_VIDEO_FORMAT_CHANGE); + phytium_jpeg_irq_res_change(jpeg_dev, RESOLUTION_CHANGE_DELAY); + return IRQ_HANDLED; + } + + /* + * JPEG engine finish compressing a image JPEG encoding to trigger + * a interruption. the status identifies the buffer number. Currently, + * the driver uses one buffer. + * + * Note: Because the JPEG doesn't support adding a JPEG header, and + * driver is also unable to add a JPEG header to vb2_buffers. One + * solution is that a JPEG header is added by an application. + */ + if (status & INT_JPEG_COMP_BUF_LIST_NO) { + frame_size = phytium_jpeg_read(jpeg_dev, jpeg_dev->comp_size_read); + frame_size &= JPEG_BUF_CAPACITY_SIZE; + frame_size >>= JPEG_BUF_CAPACITY_SIZE_SHIFT; + spin_lock(&jpeg_dev->hw_lock); + clear_bit(VIDEO_FRAME_INPRG, &jpeg_dev->status); + /* Delete first node from the queue */ + buf = list_first_entry_or_null(&jpeg_dev->buffers, + struct phytium_jpeg_buffer, link); + if (buf != NULL) { + frame_size += PHYTIUM_JPEG_HEADER_LEN; + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, frame_size); + if (!list_is_last(&buf->link, &jpeg_dev->buffers)) { + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = jpeg_dev->sequence++; + buf->vb.field = V4L2_FIELD_NONE; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + list_del(&buf->link); + } + } + + spin_unlock(&jpeg_dev->hw_lock); + /* Disable JPEG engine */ + phytium_jpeg_update(jpeg_dev, TRANSFORM_INFO_REG, TRANSINFO_ENABLE_ENGINE, 0); + /* Disable interruption */ + phytium_jpeg_update(jpeg_dev, INT_STATUS_CTRL_REG, STS_VE_JPEG_CODE_COMP_EN, 0); + /* clear all interruption of the hardware's buffers */ + phytium_jpeg_update(jpeg_dev, INT_STATUS_CTRL_REG, INT_JPEG_ENCODE_COMPLETE, 1); + + status &= ~INT_JPEG_COMP_BUF_LIST_NO; + if (test_bit(VIDEO_STREAMING, &jpeg_dev->status) && buf) + phytium_jpeg_start_frame(jpeg_dev); + } + + return IRQ_HANDLED; +} + +/* VIDIOC_STREAMON, all vb2_v4l2_buf' states are queue */ +static int phytium_jpeg_start_streaming(struct vb2_queue *q, unsigned int count) +{ + int ret; + struct phytium_jpeg_dev *jpeg_dev = vb2_get_drv_priv(q); + + jpeg_dev->sequence = 0; + ret = phytium_jpeg_start_frame(jpeg_dev); + if (ret != 0) { + phytium_jpeg_bufs_done(jpeg_dev, VB2_BUF_STATE_QUEUED); + return ret; + } + + /* set the states of the jpeg engine */ + set_bit(VIDEO_STREAMING, &jpeg_dev->status); + return ret; +} + +static void phytium_jpeg_stop_streaming(struct vb2_queue *q) +{ + int ret; + struct phytium_jpeg_dev *jpeg_dev = vb2_get_drv_priv(q); + + clear_bit(VIDEO_STREAMING, &jpeg_dev->status); + ret = wait_event_timeout(jpeg_dev->wait, + !test_bit(VIDEO_FRAME_INPRG, &jpeg_dev->status), + STOP_TIMEOUT); + + /* time out */ + if (ret == 0) { + dev_err(jpeg_dev->dev, "Timed out when stopping streaming.\n"); + /* + * Need to force stop any DMA and try and get HW into a good states + * for future calls to start streaming again. + */ + phytium_jpeg_off(jpeg_dev); + phytium_jpeg_on(jpeg_dev); + phytium_jpeg_init_regs(jpeg_dev); + phytium_jpeg_get_resolution(jpeg_dev); + } + /* first stop jpeg, wait, the free buffer */ + phytium_jpeg_bufs_done(jpeg_dev, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops phytium_jpeg_vb2_ops = { + .queue_setup = phytium_jpeg_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_prepare = phytium_jpeg_buf_prepare, + .buf_queue = phytium_jpeg_buf_queue, + .start_streaming = phytium_jpeg_start_streaming, + .stop_streaming = phytium_jpeg_stop_streaming, +}; + +static void phytium_jpeg_set_yuv_mode(struct phytium_jpeg_dev *jpeg_dev) +{ + const char *mode = yuv_mode_str; + enum jpeg_yuv_mode yuv_mode; + + if (strstr(mode, "yuv422") != NULL) + yuv_mode = YUV422; + else if (strstr(mode, "yuv420") != NULL) + yuv_mode = YUV420; + else + yuv_mode = YUV444; + + /* set the yuv mode register */ + phytium_jpeg_write(jpeg_dev, SAMPLE_MODE_REG, yuv_mode); + + /* update the field which indicates YUV mode locates in the JPEG header. */ + phytium_jpeg_header[YUVID] &= 0xFFFF00FF; + if (yuv_mode == YUV422) + phytium_jpeg_header[YUVID] |= 0x2100; + else if (yuv_mode == YUV420) + phytium_jpeg_header[YUVID] |= 0x2200; + else + phytium_jpeg_header[YUVID] |= 0x1100; + +} + +static irqreturn_t phytium_jpeg_timer31_irq(int irq, void *arg) +{ + struct phytium_jpeg_dev *jpeg_dev = arg; + + /* disable timer interrupt */ + writel(0, jpeg_dev->timer31_addr); + + /* clear timer interrupt status */ + writel(0x8, jpeg_dev->timer31_addr + 0x2c); + + /* clear JPEG Engine's poweroff status */ + clear_bit(VIDEO_POWEROFF, &jpeg_dev->status); + dev_info(jpeg_dev->dev, "timer31 set jpeg status 0x%lx\n", jpeg_dev->status); + + /* JPEG Engine is poweron, reconfig quntization table and YUV mode */ + phytium_jpeg_init_jpeg_quant(jpeg_dev); + phytium_jpeg_set_yuv_mode(jpeg_dev); + phytium_jpeg_update(jpeg_dev, INT_STATUS_CTRL_REG, 0, DETECT_RESOLUTION_CHANGE_EN); + phytium_jpeg_update(jpeg_dev, TRANSFORM_INFO_REG, 0, TRANSINFO_SRC_SELECT); + + dev_info(jpeg_dev->dev, "reconfigure quant table and yuv mode\n"); + + return IRQ_HANDLED; +} + +static int phytium_jpeg_parser_timer31_irq(struct phytium_jpeg_dev *jpeg_dev) +{ + int irq; + int ret; + struct device *dev = jpeg_dev->dev; + + irq = irq_of_parse_and_map(dev->of_node, 2); + if (!irq) { + dev_err(dev, "Failed to get timer31 IRQ\n"); + return -ENODEV; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, phytium_jpeg_timer31_irq, + IRQF_ONESHOT, PHYTIUM_JPEG_NAME, jpeg_dev); + if (ret < 0) + dev_err(dev, "Failed to request timer31 IRQ %d\n", irq); + + return ret; +} + +static irqreturn_t phytium_jpeg_timer30_irq(int irq, void *arg) +{ + struct phytium_jpeg_dev *jpeg_dev = arg; + struct arm_smccc_res res; + + /* disable timer interrupt */ + writel(0, jpeg_dev->timer30_addr); + /* clear timer interrupt status */ + writel(0x8, jpeg_dev->timer30_addr + 0x2c); + + /* Disable interruption */ + phytium_jpeg_update(jpeg_dev, INT_STATUS_CTRL_REG, STS_VE_JPEG_CODE_COMP_EN, 0); + + /* call SE to poweroff JPEG Engine */ + arm_smccc_smc(0xc300fff4, 0x9, 0x2, 0x80000020, 0, 0, 0, 0, &res); + + set_bit(VIDEO_RES_CHANGE, &jpeg_dev->status); + /* set JPEG Engine's status is poweroff */ + set_bit(VIDEO_POWEROFF, &jpeg_dev->status); + dev_info(jpeg_dev->dev, "timer30 set jpeg status 0x%lx\n", jpeg_dev->status); + + return IRQ_HANDLED; +} + +static int phytium_jpeg_parser_timer30_irq(struct phytium_jpeg_dev *jpeg_dev) +{ + int irq; + int ret; + struct device *dev = jpeg_dev->dev; + + irq = irq_of_parse_and_map(dev->of_node, 1); + if (!irq) { + dev_err(dev, "Failed to get timer30 IRQ\n"); + return -ENODEV; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, phytium_jpeg_timer30_irq, + IRQF_ONESHOT, PHYTIUM_JPEG_NAME, jpeg_dev); + if (ret < 0) + dev_err(dev, "Failed to request timer30 IRQ %d\n", irq); + + return ret; +} + +static int phytium_jpeg_init(struct phytium_jpeg_dev *jpeg_dev) +{ + int irq; + int ret; + struct device *dev = jpeg_dev->dev; + u32 ocm_buf_addr[OCM_BUF_NUM]; + int i; + + irq = irq_of_parse_and_map(dev->of_node, 0); + if (!irq) { + dev_err(dev, "Failed to get IRQ\n"); + return -ENODEV; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, phytium_jpeg_irq, + IRQF_ONESHOT, PHYTIUM_JPEG_NAME, jpeg_dev); + if (ret < 0) { + dev_err(dev, "Failed to request IRQ %d\n", irq); + return ret; + } + + ret = phytium_jpeg_parser_timer30_irq(jpeg_dev); + if (ret < 0) + return ret; + + ret = phytium_jpeg_parser_timer31_irq(jpeg_dev); + if (ret < 0) + return ret; + + ret = of_property_read_u32_array(dev->of_node, "phytium,ocm-buf-addr", + ocm_buf_addr, OCM_BUF_NUM); + if (ret != 0) { + dev_err(dev, "Failed to get the OCM address from device tree node.\n"); + return ret; + } + + for (i = 0; i < OCM_BUF_NUM; i++) + jpeg_dev->src_addrs[i].dma_addr = ocm_buf_addr[i]; + + /* CMA memory for JPEG device */ + of_reserved_mem_device_init(dev); + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret != 0) { + dev_err(dev, "Failed to set DMA mask\n"); + return ret; + } + + /* Initializing JPEG Y and CbCr quantization table */ + phytium_jpeg_init_jpeg_quant(jpeg_dev); + + /* Select YUV mode */ + phytium_jpeg_set_yuv_mode(jpeg_dev); + dev_info(dev, "successfully initialize jpeg engine\n"); + return 0; + +} + + +static int phytium_jpeg_setup_video(struct phytium_jpeg_dev *jpeg_dev) +{ + struct v4l2_device *v4l2_dev = &jpeg_dev->v4l2_dev; + struct vb2_queue *dst_vq = &jpeg_dev->queue; + struct video_device *vdev = &jpeg_dev->vdev; + int ret; + + jpeg_dev->pix_fmt.pixelformat = V4L2_PIX_FMT_JPEG; + jpeg_dev->pix_fmt.field = V4L2_FIELD_NONE; + jpeg_dev->pix_fmt.colorspace = V4L2_COLORSPACE_SRGB; /* maybe ARGB */ + jpeg_dev->pix_fmt.quantization = V4L2_QUANTIZATION_FULL_RANGE; + jpeg_dev->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + + ret = v4l2_device_register(jpeg_dev->dev, v4l2_dev); + if (ret != 0) { + dev_err(jpeg_dev->dev, "Failed to register v4l2 device\n"); + return ret; + } + + /* Register how many v4l2 controls to a handler */ + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; + dst_vq->dev = v4l2_dev->dev; + dst_vq->lock = &jpeg_dev->video_lock; + dst_vq->ops = &phytium_jpeg_vb2_ops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->drv_priv = jpeg_dev; + dst_vq->buf_struct_size = sizeof(struct phytium_jpeg_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + dst_vq->min_buffers_needed = CAPTURE_BUF_NUMBER; + ret = vb2_queue_init(dst_vq); + if (ret) { + dev_err(jpeg_dev->dev, "Failed to init vb2 queue\n"); + goto err_v4l2_register; + } + + vdev->queue = dst_vq; + vdev->fops = &phytium_jpeg_fops; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + vdev->v4l2_dev = v4l2_dev; + strscpy(vdev->name, PHYTIUM_JPEG_NAME, sizeof(vdev->name)); + vdev->vfl_type = VFL_TYPE_VIDEO; + vdev->vfl_dir = VFL_DIR_RX; + vdev->release = video_device_release_empty; + vdev->ioctl_ops = &phytium_jpeg_ioctl_ops; + vdev->lock = &jpeg_dev->video_lock; + + video_set_drvdata(vdev, jpeg_dev); + ret = video_register_device(vdev, VFL_TYPE_VIDEO, 0); + if (ret != 0) { + dev_err(jpeg_dev->dev, "Failed to register video device\n"); + goto err_video_register; + } + + v4l2_info(v4l2_dev, "phytium JPEG registered as /dev/video%d (%d, %d)\n", + jpeg_dev->vdev.num, VIDEO_MAJOR, jpeg_dev->vdev.minor); + return ret; + +err_video_register: + vb2_queue_release(dst_vq); + +err_v4l2_register: + v4l2_device_unregister(v4l2_dev); + return ret; +} + +static const struct phytium_jpeg_config phytium_jpeg_config = { + .comp_size_read = BUF_LIST_INDEX_CTRL_STS_ADDR(VB_BUF_NO), +}; + +static const struct of_device_id phytium_jpeg_match[] = { + { + .compatible = "phytium,jpeg", + .data = &phytium_jpeg_config, + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, phytium_jpeg_match); + +static int phytium_jpeg_probe(struct platform_device *pdev) +{ + struct phytium_jpeg_dev *jpeg_dev; + const struct of_device_id *match; + const struct phytium_jpeg_config *config; + struct resource *res; + int ret; + + jpeg_dev = devm_kzalloc(&pdev->dev, sizeof(*jpeg_dev), GFP_KERNEL); + if (jpeg_dev == NULL) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + jpeg_dev->base_addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(jpeg_dev->base_addr)) { + dev_err(jpeg_dev->dev, "Failed to ioremap.\n"); + return PTR_ERR(jpeg_dev->base_addr); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + jpeg_dev->timer30_addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(jpeg_dev->base_addr)) { + dev_err(jpeg_dev->dev, "Failed to parse timer30.\n"); + return PTR_ERR(jpeg_dev->timer30_addr); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + jpeg_dev->timer31_addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(jpeg_dev->base_addr)) { + dev_err(jpeg_dev->dev, "Failed to parse timer30.\n"); + return PTR_ERR(jpeg_dev->timer31_addr); + } + match = of_match_node(phytium_jpeg_match, pdev->dev.of_node); + if (match == NULL) { + dev_err(jpeg_dev->dev, "Failed to match.\n"); + return -EINVAL; + } + + config = match->data; + jpeg_dev->comp_size_read = config->comp_size_read; + + jpeg_dev->frame_rate = 30; + jpeg_dev->dev = &pdev->dev; + spin_lock_init(&jpeg_dev->hw_lock); + mutex_init(&jpeg_dev->video_lock); + init_waitqueue_head(&jpeg_dev->wait); + INIT_DELAYED_WORK(&jpeg_dev->res_work, phytium_jpeg_resolution_work); + INIT_LIST_HEAD(&jpeg_dev->buffers); + + ret = phytium_jpeg_init(jpeg_dev); + if (ret != 0) { + dev_err(jpeg_dev->dev, "Failed to initialize the JPEG engine.\n"); + return ret; + } + + ret = phytium_jpeg_setup_video(jpeg_dev); + + return ret; +} + +#define to_phytium_jpeg(x) container_of((x), struct phytium_jpeg_dev, v4l2_dev) +static int phytium_jpeg_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct phytium_jpeg_dev *jpeg_dev = to_phytium_jpeg(v4l2_dev); + + phytium_jpeg_off(jpeg_dev); + + video_unregister_device(&jpeg_dev->vdev); + + vb2_queue_release(&jpeg_dev->queue); + + v4l2_device_unregister(v4l2_dev); + + of_reserved_mem_device_release(dev); + + return 0; +} + +static struct platform_driver phytium_jpeg_driver = { + .probe = phytium_jpeg_probe, + .remove = phytium_jpeg_remove, + .driver = { + .name = PHYTIUM_JPEG_NAME, + .of_match_table = phytium_jpeg_match, + }, +}; + +module_platform_driver(phytium_jpeg_driver); + +MODULE_DESCRIPTION("Phytium JPEG Encoder driver"); +MODULE_AUTHOR("Wang Min "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/phytium/phytium_jpeg_core.h b/drivers/media/platform/phytium/phytium_jpeg_core.h new file mode 100644 index 0000000000000000000000000000000000000000..5cb98e08469389527e327f7b8bccfc37d9bb51a7 --- /dev/null +++ b/drivers/media/platform/phytium/phytium_jpeg_core.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef _PHYTIUM_JPEG_CORE_H +#define _PHYTIUM_JPEG_CORE_H + +#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 + +#define PHYTIUM_JPEG_NAME "phytium-jpeg" +#define MAX_FRAME_RATE 60 +#define MAX_HEIGHT 1080 +#define MAX_WIDTH 1920 +#define MIN_HEIGHT 480 +#define MIN_WIDTH 640 +#define MIN_PIXEL_CLOCK (640 * 480 * 60) /* 640 x 480 x 60Hz */ +#define MAX_PIXEL_CLOCK (1920 * 1080 * 60) /* 1920 x 1080 x 60Hz */ + +#define SOURCE_RESOLUTION_DETECT_TIMEOUT msecs_to_jiffies(500) +#define RESOLUTION_CHANGE_DELAY msecs_to_jiffies(0) +#define INVALID_RESOLUTION_DELAY msecs_to_jiffies(250) +#define STOP_TIMEOUT msecs_to_jiffies(1000) + +#define INVALID_RESOLUTION_RETRIES 2 +#define CAPTURE_BUF_NUMBER 3 /* using how many buffers */ +#define VB_BUF_NO 0 /* there are 16 buffer, use which one */ + +/* The below macros are defined for the JPEG header of the phytium JPEG Engine */ +#define PHYTIUM_JPEG_HEADER_LEN (256 * 3) +#define PHYTIUM_JPEG_HEADER_SIZE (PHYTIUM_JPEG_HEADER_LEN / sizeof(u32)) +#define PHYTIUM_JPEG_HEADER_H_INDEX 40 +#define PHYTIUM_JPEG_HEADER_W_INDEX 41 + +/* There are two ocm buffers that are used for storaging the inputing video data */ +#define OCM_BUF_NUM 2 + +enum phytium_jpeg_status { + VIDEO_MODE_DETECT_DONE, + VIDEO_RES_CHANGE, + VIDEO_RES_DETECT, + VIDEO_STREAMING, + VIDEO_FRAME_INPRG, + VIDEO_STOPPED, + VIDEO_CLOCKS_ON, + VIDEO_POWEROFF, +}; + +struct phytium_jpeg_addr { + unsigned int size; + dma_addr_t dma_addr; + void *virt_addr; +}; + +struct phytium_jpeg_buffer { + struct vb2_v4l2_buffer vb; + struct list_head link; +}; + +/** + * struct phytium_jpeg - JPEG IP abstraction + * @lock: the mutex protecting this structure + * @hw_lock: spinlock protecting the hw device resource + * @workqueue: decode work queue + * @dev: JPEG device + * @v4l2_dev: v4l2 device for mem2mem mode + * @m2m_dev: v4l2 mem2mem device data + * @alloc_ctx: videobuf2 memory allocator's context + * @dec_vdev: video device node for decoder mem2mem mode + * @dec_reg_base: JPEG registers mapping + * @clk_jdec: JPEG hw working clock + * @clk_jdec_smi: JPEG SMI bus clock + * @larb: SMI device + */ +struct phytium_jpeg_dev { + void __iomem *base_addr; + struct device *dev; + struct v4l2_device v4l2_dev; + struct v4l2_pix_format pix_fmt; + struct v4l2_bt_timings active_timings; + struct v4l2_bt_timings detected_timings; + u32 v4l2_input_status; + struct vb2_queue queue; + struct video_device vdev; + /* v4l2 and videobuf2 lock, protect the structure*/ + struct mutex video_lock; + u32 jpeg_mode; + u32 comp_size_read; + wait_queue_head_t wait; + /* buffer list lock, protecting the hw device resource */ + spinlock_t hw_lock; + struct delayed_work res_work; + struct list_head buffers; + unsigned long status; + unsigned int sequence; + unsigned int max_compressed_size; + struct phytium_jpeg_addr src_addrs[OCM_BUF_NUM]; + struct phytium_jpeg_addr dst_addrs[16]; + + bool yuv420; + unsigned int frame_rate; + void __iomem *timer30_addr; + void __iomem *timer31_addr; +}; + +struct phytium_jpeg_config { + u32 jpeg_mode; + u32 comp_size_read; +}; + +#define YUV_MODE_STR_LEN 8 +#define YUVID 42 + +enum jpeg_yuv_mode { + YUV444 = 0x0, + YUV422 = 0x1, + YUV420 = 0x2 +}; + +#endif /* _PHYTIUM_JPEG_CORE_H */ diff --git a/drivers/media/platform/phytium/phytium_jpeg_reg.h b/drivers/media/platform/phytium/phytium_jpeg_reg.h new file mode 100644 index 0000000000000000000000000000000000000000..3cdd623022b3210830f1dd08565853283035a540 --- /dev/null +++ b/drivers/media/platform/phytium/phytium_jpeg_reg.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef _PHYTIUM_JPEG_REG_H +#define _PHYTIUM_JPEG_REG_H + +#include +/* define the all kinds of control registers in a JPEG soc */ + +/* The register is used to set the information of the video comes from main memory */ +#define SRC_DDR_INFO_REG 0x00000800 + +/* The register is used to get the information of the video comes from external VGA */ +#define SRC_VGA_INFO_REG 0x00000894 + +#define SRC_FORMAT BIT(0) /* 0:RGB888, 1:RGB565 */ +#define SRC_DE_POLARITY BIT(1) /* 0:low level is effect, other */ +#define SRC_HS_POLARITY BIT(2) /* 0:low level is effect, other */ +#define SRC_VS_POLARITY BIT(3) /* 0:low level is effect, other */ +#define SRC_HOR_PIXELS GENMASK(15, 4) /* the number of the horizontal pixels */ +#define SRC_WIDTH_SHIFT 4 /* shift right to get width */ +#define SRC_VER_PIXELS GENMASK(26, 16) /* the number of the vertical pixels */ +#define SRC_HEIGHT_SHIFT 16 /* shift right to get height */ +/* The below bit fields is only used by image comes from main memory */ +#define SRC_COMP_DDR_IMG_EN BIT(27) /* 0: disable to JPEG compression, others */ + +/* marks which ocm buffer is occupied to store an image */ +#define SRC_DDR_IMG2OCM_VALID GENMASK(29, 28) + +/* The register controls starting work of the JPEG engine */ +#define TRANSFORM_INFO_REG 0x00000804 +#define TRANSINFO_ENABLE_ENGINE BIT(0) /* 1: enable the JPEG engine */ +/* 1: video comes from external VGA, 0: video comes from DDR */ +#define TRANSINFO_SRC_SELECT BIT(1) +/* 0: video comes from external VGA is cached to OCM, 1: DDR */ +#define TRANSINFO_IMAGE_STORE BIT(2) +#define TRANSINFO_FRAME_RATE GENMASK(9, 4) /* the value notes frame rate */ +#define TRANSINFO_BLOCK_SIZE BIT(12) /* 0: 8x8, 1: 16x16 */ +#define TRANSINFO_ENABLE_YUV422 BIT(13) /* 1: JPEG block is populated YUV422 */ +/* support burst with the values such as 1, 2, 4, 8, 16, 32, 64. using default value 0xf */ +#define TRANSINFO_AXI_LEN GENMASK(22, 16) +#define TRANS_AXI_LEN_SHIFT 16 + +/* The interrupt and status register */ +#define INT_STATUS_CTRL_REG 0x00000808 +#define INT_FIFO_OVERFLOW BIT(0) /* video fifo overflow, write 1 to clear */ +#define INT_OCM_BUF_OVERFLOW BIT(1) /* ocm buffer overflow, write 1 to clear */ +/* JPEG engine complete compression, write 1 to clear */ +#define INT_JPEG_ENCODE_COMPLETE BIT(2) +/* in VGA mode, video's format is changed */ +#define INT_VIDEO_FORMAT_CHANGE BIT(3) +/* enable the interrupt of th video fifo overflow and source resolution */ +#define DETECT_RESOLUTION_CHANGE_EN BIT(4) +/* enable the interrupt of the ocm buffer overflow */ +#define STS_VE_OCM_BUF_OVERFLOW_EN BIT(5) +/* enable the interrupt of the JPEG complete compression */ +#define STS_VE_JPEG_CODE_COMP_EN BIT(6) +/* in VGA mode, the bit notes ocm buff is busy */ +#define STS_VE_OCM_BUF_BUSY BIT(7) +/* in VGA mode, the bit notes sequence number of the frame */ +#define STS_VE_CUR_FRAME_NUMBER GENMASK(9, 8) +/* in VGA mode, the bit notes sequence number of the cached frame */ +#define STS_VE_BUF_CACHE_NUMBER GENMASK(11, 10) +/* in VGA mode, the buffer number in buffer list */ +#define STS_JPEG_COMP_BUF_NO GENMASK(15, 12) +#define INT_JPEG_COMP_BUF_LIST_NO GENMASK(31, 16) /* the interrupt number of the buffer */ + +#define OCM_BUF0_ADDR 0x0000080C +#define OCM_BUF1_ADDR 0x00000810 +#define OCM_BUF_SHIFT 8 + +#define BUF_LIST_BASE_ADDR 0x00000814 + +#define PHYTIUM_BUF_LIST_ACTRL_AND_STS_BASE_ADDR_REG 0x00000818 +#define STS_JPEG_BUF_HIGH_LEVEL_VALID BIT(0) /* Hight levle is validity */ +#define JPEG_BUF_CAPACITY_SIZE GENMASK(29, 8) /* the capacity of the buffer */ +#define JPEG_BUF_CAPACITY_SIZE_SHIFT 8 + +/* There are 16 buffers in the buffer list, the width between each other' address is 8 bytes */ +#define BUF_LIST_ADDR_OFFSET 0x8 +#define BUF_LIST_CTRL_AND_STS_OFFSET 0x8 + +/* Get the address of the specific index buffer */ +#define BUF_LIST_INDEX_ADDR(index) \ + (BUF_LIST_BASE_ADDR + (index) * BUF_LIST_ADDR_OFFSET) + +#define JPEG_DST_ADDR_SHIFT 8 + +#define BUF_LIST_INDEX_CTRL_STS_ADDR(index) \ + (PHYTIUM_BUF_LIST_ACTRL_AND_STS_BASE_ADDR_REG + (index) * BUF_LIST_CTRL_AND_STS_OFFSET) + +#define FRAME_SAMPLE_CTRL 0x00000898 +#define FRAME_SAMPLE_CTRL_EN BIT(31) +#define FRAME_SAMPLE_INTERVAL GENMASK(30, 0) + +/* The below registers are all related to quantilize */ +#define HUFF_MODE_REG 0x300 +#define SAMPLE_MODE_REG 0x304 + +#define Y_QUANT_BASE_ADDR_REG 0x400 +#define C_QUANT_BASE_ADDR_REG 0x500 + +#define QUANT_REG_NUM 64 + +#define Y_QUANT_INDEX_ADDR_REG(index) \ + (Y_QUANT_BASE_ADDR_REG + 4 * (index)) + +#define C_QUANT_INDEX_ADDR_REG(index) \ + (C_QUANT_BASE_ADDR_REG + 4 * (index)) + +#endif /* _PHYTIUM_JPEG_REG_H */ diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 68d71b4b55bd350af0e5cefbd1713ca206d131ca..160aadebd210849c897502fd22def7356cef8f3f 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1107,6 +1107,20 @@ config PCF50633_GPIO Say yes here if you want to include support GPIO for pins on the PCF50633 chip. +config MFD_PHYTIUM_I2S_LSD + bool "PHYTIUM Px210 I2S LSD MFD driver" + depends on (PCI && ARCH_PHYTIUM) + select MFD_CORE + help + This enables support for the Phytium Px210 LSD I2S controller. + +config MFD_PHYTIUM_I2S_MMD + bool "PHYTIUM Px210 I2S MMD MFD driver" + depends on (PCI && ARCH_PHYTIUM) + help + This enables support for the Phytium Px210 MMD I2S controllers + for Display Port. + config MFD_PM8XXX tristate "Qualcomm PM8xxx PMIC chips driver" depends on (ARM || HEXAGON || COMPILE_TEST) diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index db1ba39de3b590fe37fe39a22faaec30380134e9..3929921276a754c5f22373a214e0aedc41f223b4 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -282,3 +282,6 @@ obj-$(CONFIG_MFD_ATC260X_I2C) += atc260x-i2c.o obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o + +obj-$(CONFIG_MFD_PHYTIUM_I2S_LSD) += phytium_px210_i2s_lsd.o +obj-$(CONFIG_MFD_PHYTIUM_I2S_MMD) += phytium_px210_i2s_mmd.o diff --git a/drivers/mfd/phytium_px210_i2s_lsd.c b/drivers/mfd/phytium_px210_i2s_lsd.c new file mode 100644 index 0000000000000000000000000000000000000000..58de96a0cdcfb639f5f6b56ca30a0fbdc03fdca1 --- /dev/null +++ b/drivers/mfd/phytium_px210_i2s_lsd.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium I2S LSD MFD driver over PCI bus + * + * Copyright (C) 2020-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include + +struct phytium_px210_mfd { + struct device *dev; +}; + +struct pdata_px210_mfd { + struct device *dev; + char *name; + int clk_base; +}; + +static struct resource phytium_px210_i2s_res0[] = { + [0] = { + .flags = IORESOURCE_MEM, + }, + [1] = { + .flags = IORESOURCE_MEM, + }, + [2] = { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell phytium_px210_mfd_cells[] = { + { + .id = 0, + .name = "phytium-i2s", + .of_compatible = "phytium,i2s", + .resources = phytium_px210_i2s_res0, + .num_resources = ARRAY_SIZE(phytium_px210_i2s_res0), + .ignore_resource_conflicts = true, + }, +}; + +static void phytium_px210_i2s_setup(struct pci_dev *pdev) +{ + struct mfd_cell *cell = &phytium_px210_mfd_cells[0]; + struct resource *res = (struct resource *)cell->resources; + struct pdata_px210_mfd *pdata; + + res[0].start = pci_resource_start(pdev, 0); + res[0].end = pci_resource_start(pdev, 0) + 0x0fff; + + res[1].start = pci_resource_start(pdev, 0) + 0x1000; + res[1].end = pci_resource_start(pdev, 0) + 0x1fff; + + res[2].start = pdev->irq; + res[2].end = pdev->irq; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + + pdata->dev = &pdev->dev; + pdata->name = "phytium-i2s-lsd"; + pdata->clk_base = 480000000; + + cell->platform_data = pdata; + cell->pdata_size = sizeof(*pdata); +} + +static int phytium_px210_mfd_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct phytium_px210_mfd *phytium_mfd; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + pci_set_master(pdev); + + phytium_mfd = devm_kzalloc(&pdev->dev, sizeof(*phytium_mfd), GFP_KERNEL); + if (!phytium_mfd) + return -ENOMEM; + + phytium_mfd->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, phytium_mfd); + + phytium_px210_i2s_setup(pdev); + + ret = mfd_add_devices(&pdev->dev, 0, phytium_px210_mfd_cells, + ARRAY_SIZE(phytium_px210_mfd_cells), NULL, 0, + NULL); + if (ret) + return 0; + + return 0; +} + + +static void phytium_px210_mfd_remove(struct pci_dev *pdev) +{ + mfd_remove_devices(&pdev->dev); +} + +static const struct pci_device_id phytium_px210_mfd_ids[] = { + { + .vendor = 0x1DB7, + .device = 0xDC2B, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .class = 0x3, + .class_mask = 0, + }, + {}, +}; +MODULE_DEVICE_TABLE(pci, phytium_px210_mfd_ids); + +static struct pci_driver phytium_i2s_lsd_mfd_driver = { + .name = "phytium_px210_mfd_i2s", + .id_table = phytium_px210_mfd_ids, + .probe = phytium_px210_mfd_probe, + .remove = phytium_px210_mfd_remove, +}; + +module_pci_driver(phytium_i2s_lsd_mfd_driver); + +MODULE_AUTHOR("Zhang Yiqun "); +MODULE_DESCRIPTION("Phytium Px210 MFD PCI driver for I2S-LSD"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/phytium_px210_i2s_mmd.c b/drivers/mfd/phytium_px210_i2s_mmd.c new file mode 100644 index 0000000000000000000000000000000000000000..684e588793024b00c6741f3ac68ae4946025ee1f --- /dev/null +++ b/drivers/mfd/phytium_px210_i2s_mmd.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium I2S MMD MFD driver over PCI bus + * + * Copyright (C) 2020-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include + +struct phytium_px210_mfd { + struct device *dev; +}; + +struct pdata_px210_mfd { + struct device *dev; + char *name; + int clk_base; +}; + +static struct resource phytium_px210_i2s_res0[] = { + [0] = { + .flags = IORESOURCE_MEM, + }, + [1] = { + .flags = IORESOURCE_MEM, + }, + [2] = { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource phytium_px210_i2s_res1[] = { + [0] = { + .flags = IORESOURCE_MEM, + }, + [1] = { + .flags = IORESOURCE_MEM, + }, + [2] = { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource phytium_px210_i2s_res2[] = { + [0] = { + .flags = IORESOURCE_MEM, + }, + [1] = { + .flags = IORESOURCE_MEM, + }, + [2] = { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell phytium_px210_mfd_cells[] = { + { + .id = 1, + .name = "phytium-i2s", + .of_compatible = "phytium,i2s", + .resources = phytium_px210_i2s_res0, + .num_resources = ARRAY_SIZE(phytium_px210_i2s_res0), + .ignore_resource_conflicts = true, + }, + { + .id = 2, + .name = "phytium-i2s", + .of_compatible = "phytium,i2s", + .resources = phytium_px210_i2s_res1, + .num_resources = ARRAY_SIZE(phytium_px210_i2s_res1), + .ignore_resource_conflicts = true, + }, + { + .id = 3, + .name = "phytium-i2s", + .of_compatible = "phytium,i2s", + .resources = phytium_px210_i2s_res2, + .num_resources = ARRAY_SIZE(phytium_px210_i2s_res2), + .ignore_resource_conflicts = true, + }, +}; + +static void phytium_px210_i2s_setup(struct pci_dev *pdev, int i) +{ + struct mfd_cell *cell = &phytium_px210_mfd_cells[i]; + struct resource *res = (struct resource *)cell->resources; + struct pdata_px210_mfd *pdata; + + res[0].start = pci_resource_start(pdev, 0) + 0x2000 * i + 0x1000; + res[0].end = pci_resource_start(pdev, 0) + 0x2000 * i + 0x1fff; + + res[1].start = pci_resource_start(pdev, 0) + 0x2000 * i; + res[1].end = pci_resource_start(pdev, 0) + 0x2000 * i + 0x0fff; + + res[2].start = pdev->irq; + res[2].end = pdev->irq; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + + pdata->dev = &pdev->dev; + pdata->clk_base = 600000000; + switch (i) { + case 0: + pdata->name = "phytium-i2s-dp0"; + break; + case 1: + pdata->name = "phytium-i2s-dp1"; + break; + case 2: + pdata->name = "phytium-i2s-dp2"; + break; + default: + break; + } + + cell->platform_data = pdata; + cell->pdata_size = sizeof(*pdata); +} + +static int phytium_px210_mfd_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct phytium_px210_mfd *phytium_mfd; + int i; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + pci_set_master(pdev); + + phytium_mfd = devm_kzalloc(&pdev->dev, sizeof(*phytium_mfd), GFP_KERNEL); + if (!phytium_mfd) + return -ENOMEM; + + phytium_mfd->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, phytium_mfd); + + for (i = 0; i < 3; i++) + phytium_px210_i2s_setup(pdev, i); + + ret = mfd_add_devices(&pdev->dev, 0, phytium_px210_mfd_cells, + ARRAY_SIZE(phytium_px210_mfd_cells), NULL, 0, + NULL); + if (ret) + return 0; + + return 0; +} + + +static void phytium_px210_mfd_remove(struct pci_dev *pdev) +{ + mfd_remove_devices(&pdev->dev); +} + +static const struct pci_device_id phytium_px210_mfd_ids[] = { + { + .vendor = 0x1DB7, + .device = 0xDC23, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .class = 0x3, + .class_mask = 0, + }, + {}, +}; +MODULE_DEVICE_TABLE(pci, phytium_px210_mfd_ids); + +static struct pci_driver phytium_i2s_mmd_mfd_driver = { + .name = "phytium_px210_mfd_mmd", + .id_table = phytium_px210_mfd_ids, + .probe = phytium_px210_mfd_probe, + .remove = phytium_px210_mfd_remove, +}; + +module_pci_driver(phytium_i2s_mmd_mfd_driver); + +MODULE_AUTHOR("Zhang Yiqun "); +MODULE_DESCRIPTION("Phytium Px210 MFD PCI driver for I2S-DP"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b29d91be112e45ef6de2b3974371d238d019ccc0..774b38205b25f3846750eedc7f6690c9daab5914 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -104,6 +104,14 @@ config PHANTOM If you choose to build module, its name will be phantom. If unsure, say N here. +config PHYTIUM_LPC_SNOOP + tristate "Phytium HOST LPC snoop support" + depends on ARCH_PHYTIUM && REGMAP && MFD_SYSCON + help + Provides a driver to control the LPC snoop interface which + allows the BMC to listen on and save the data written by + the host to an arbitrary LPC I/O port. + config TIFM_CORE tristate "TI Flash Media interface support" depends on PCI @@ -466,6 +474,15 @@ config PCI_ENDPOINT_TEST Enable this configuration option to enable the host side test driver for PCI Endpoint. +config FT_LPC + bool "select LPC for phytium platform" + depends on ARCH_PHYTIUM + default y + select IRQ_DOMAIN + select GENERIC_IRQ_CHIP + help + This option enables LPC for phytium platform. + config XILINX_SDFEC tristate "Xilinx SDFEC 16" depends on HAS_IOMEM diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index bd12e9a3b8c55d73c9dff6c5fe51a83a81b3e2d0..df2869b64321aff1e29ba55d3db2f5ebb965aced 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_LKDTM) += lkdtm/ obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_PHANTOM) += phantom.o +obj-$(CONFIG_PHYTIUM_LPC_SNOOP) += phytium-lpc-snoop.o obj-$(CONFIG_QCOM_COINCELL) += qcom-coincell.o obj-$(CONFIG_QCOM_FASTRPC) += fastrpc.o obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o @@ -68,3 +69,4 @@ obj-$(CONFIG_TMR_MANAGER) += xilinx_tmr_manager.o obj-$(CONFIG_TMR_INJECT) += xilinx_tmr_inject.o obj-$(CONFIG_TPS6594_ESM) += tps6594-esm.o obj-$(CONFIG_TPS6594_PFSM) += tps6594-pfsm.o +obj-$(CONFIG_FT_LPC) += ft_lpc.o diff --git a/drivers/misc/ft_lpc.c b/drivers/misc/ft_lpc.c new file mode 100644 index 0000000000000000000000000000000000000000..8396016bc520a62f454cd024b0ed0167b577883f --- /dev/null +++ b/drivers/misc/ft_lpc.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define PHYTIUM_LPC_DRIVER_NAME "phytium-lpc" + +void __iomem *lpc_base; +EXPORT_SYMBOL_GPL(lpc_base); + +int lpc_irq = -1; +EXPORT_SYMBOL_GPL(lpc_irq); + +/* LPC sync mode */ +#define SYNC_CYCLE_SHORT 1 +#define SYNC_CYCLE_LONG 0 +#define SYNC_PROPERTY_SHORT "short" +#define SYNC_PROPERTY_LONG "long" +static int sync_cycle = SYNC_CYCLE_LONG; + +/* the lpc-gpio configure address */ +#define LPCGPIO_ADDR 0x28100c00 + +/* LPC register shift with different Phytium CPU */ +#define LPC_SHIFT (is_cpu_fte2000() || is_cpu_ftd3000() ? 0x30 : 0) +#define LPC_IO_OFFSET 0x7FF0000 + +/* LPC register offset */ +#define LPC_INT_MASK (0xFFD8 - LPC_SHIFT) +#define LPC_SERIRQ_CONFIG (0xFFE8 - LPC_SHIFT) +#define LPC_INT_STATE (0xFFF4 - LPC_SHIFT) +#define LPC_CLR_IRQ (0xFFF0 - LPC_SHIFT) + +/* + * Debug + */ +#define DEBUG +#ifdef DEBUG +static bool ft_lpc_debug; +module_param_named(debug, ft_lpc_debug, bool, 0600); +MODULE_PARM_DESC(debug, "Turn Phytium lpc debugging mode on and off"); + +#define lpc_dbg(format, arg...) \ + do { \ + if (ft_lpc_debug) \ + pr_debug(KBUILD_MODNAME " " format, ##arg); \ + } while (0) +#else +#define lpc_dbg(format, arg...) \ + do { \ + if (0) \ + pr_debug(pr_fmt(format), ##arg); \ + } while (0) +#endif + +#ifdef CONFIG_PM_SLEEP +/* Store LPC context across system-wide suspend/resume transitions */ +struct phytium_lpc_ctx { + u8 int_mask; + u32 sirq_cfg; + u32 int_state; + u8 wake_en; +} lpc_ctx; +#endif + +struct phytium_lpc { + struct device *dev; + struct fwnode_handle *fwnode; + void __iomem *regs; + struct irq_domain *domain; + struct irq_chip_generic *gc; + int irq; + int irq_num; +} *ft_lpc; + +static const struct dmi_system_id phytium_machine_table[] = { + { + .ident = "706 FT2000/4 laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HT706"), + DMI_MATCH(DMI_PRODUCT_NAME, "TR4251"), + }, + }, + { + .ident = "706 FT2000/4 laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HT706"), + DMI_MATCH(DMI_PRODUCT_NAME, "TR4252"), + }, + }, + { + .ident = "706 D2000/8 laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HT706"), + DMI_MATCH(DMI_PRODUCT_NAME, "TR4260"), + }, + }, + { + .ident = "706 D2000/8 laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HT706"), + DMI_MATCH(DMI_PRODUCT_NAME, "TR4261"), + }, + }, + { + .ident = "706 D2000/8 laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HT706"), + DMI_MATCH(DMI_PRODUCT_NAME, "TR4263"), + }, + }, + { } +}; + +#ifdef CONFIG_OF +static void phytium_lpc_probe_dt(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + void __iomem *lpc_gpio_vaddr; + + if (of_property_read_bool(np, "phytium,lpc-gpio")) { + lpc_gpio_vaddr = ioremap(LPCGPIO_ADDR, 4); + writel(0x6, lpc_gpio_vaddr); + iounmap(lpc_gpio_vaddr); + } +} +#else +static void phytium_lpc_probe_dt(struct platform_device *pdev) +{ +} +#endif + +/** + * ft_lpc_read - Read data from LPC. + * @addr: The offset address of LPC to read. + * + * It should use the exported symbol ft_lpc_{read, write} to read and write + * the LPC, and should not use the lpc_base directly elsewhere. + * + * Return: The value it read。 + */ +u8 ft_lpc_read(u8 addr) +{ + u8 value; + + if (!lpc_base) { + lpc_dbg("%s: phytium lpc not prepared!\n", __func__); + return 0; + } + + value = readb(lpc_base + addr); + if (sync_cycle == SYNC_CYCLE_SHORT) + value = readb(lpc_base + addr); + + return value; +} +EXPORT_SYMBOL(ft_lpc_read); + +/** + * ft_lpc_write - Write data to LPC. + * @value: The value to write + * @addr: The offset address of LPC to write. + * + * Use ft_lpc_write function to write data to LPC address, avoid use lpc_base + * directly elsewhere. + */ +u8 ft_lpc_write(u8 value, u8 addr) +{ + if (!lpc_base) { + lpc_dbg("%s: phytium lpc not prepared!\n", __func__); + return 0; + } + + writeb(value, lpc_base + addr); + + return 0; +} +EXPORT_SYMBOL(ft_lpc_write); + +#define FTLPC_CTL_TIMEOUT 1000 +static int ftlpc_i8042_wait_read(void) +{ + int i = 0; + + while ((~ft_lpc_read(0x64) & I8042_STR_OBF) && + (i < FTLPC_CTL_TIMEOUT)) { + udelay(50); + i++; + } + return -(i == FTLPC_CTL_TIMEOUT); +} + +static int ftlpc_i8042_wait_write(void) +{ + int i = 0; + + while ((ft_lpc_read(0x64) & I8042_STR_IBF) && + (i < FTLPC_CTL_TIMEOUT)) { + udelay(50); + i++; + } + return -(i == FTLPC_CTL_TIMEOUT); +} + +/* The sequence of disabling i8042 interface */ +static int ftlpc_disable_i8042_interfaces(void) +{ + u8 i8042_ctr; + int error; + + error = ftlpc_i8042_wait_write(); + if (error) + return error; + ft_lpc_write(I8042_CMD_CTL_RCTR & 0xff, 0x64); + error = ftlpc_i8042_wait_read(); + if (error) { + pr_debug(" -- ftlpc (wait read timeout)\n"); + return error; + } + i8042_ctr = ft_lpc_read(0x60); + i8042_ctr |= I8042_CTR_KBDDIS | I8042_CTR_AUXDIS; + i8042_ctr &= ~(I8042_CTR_KBDINT | I8042_CTR_AUXINT); + error = ftlpc_i8042_wait_write(); + if (error) + return error; + ft_lpc_write(I8042_CMD_CTL_WCTR & 0xff, 0x64); + error = ftlpc_i8042_wait_write(); + if (error) + return error; + ft_lpc_write(i8042_ctr, 0x60); + + return 0; +} + +/** + * ft_lpc_irq_set_type - Set the flow type of LPC irq. + * @data: Pointer to the irq_data structure that identifies the irq. + * @type: The irq type (IRQ_TYPE_LEVEL/etc.) to be set. + */ +static int ft_lpc_irq_set_type(struct irq_data *data, u32 type) +{ + if (type & IRQ_TYPE_LEVEL_MASK) + irq_set_handler_locked(data, handle_level_irq); + else if (type & IRQ_TYPE_EDGE_BOTH) + irq_set_handler_locked(data, handle_edge_irq); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +/** + * ft_lpc_irq_set_wake - Enable/disable power-management wake-on of LPC irq. + * @data: Pointer to the irq_data structure that identifies the irq. + * @enable: Enable or disable the irq power-management wake-on + * + * Enable or disable the power-management wake-on of LPC irq. According to + * Phytium Spec, the LPC irq can only be masked as a whole, not bit mask. + */ +static int ft_lpc_irq_set_wake(struct irq_data *data, unsigned int enable) +{ + if (enable) + lpc_ctx.wake_en = 0x3; + else + lpc_ctx.wake_en = 0x0; + + return 0; +} +#else +#define ft_lpc_irq_set_wake NULL +#endif + +/* LPC power-management wake-on */ +static const struct irq_chip ft_lpc_irq_chip = { + .name = PHYTIUM_LPC_DRIVER_NAME, + .irq_set_type = ft_lpc_irq_set_type, + .irq_set_wake = ft_lpc_irq_set_wake, +}; + +/** + * phytium_lpc_irq_handler - Handle the LPC irq. + * @desc: Pointer to the irq_desc structure that identifies the irq chip. + * + * Invoke the handler for a particular irq number read from LPC irq status + * register. + */ +static void phytium_lpc_irq_handler(struct irq_desc *desc) +{ + struct phytium_lpc *lpc = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned long sts; + irq_hw_number_t hwirq; + + chained_irq_enter(chip, desc); + + /* read irq status */ + sts = irq_reg_readl(lpc->gc, LPC_INT_STATE); + lpc_dbg("Interrupt %d, with irq status: %#lx\n", lpc->irq, sts); + + /* clear lpc irq */ + irq_reg_writel(lpc->gc, 0, LPC_CLR_IRQ); + + for_each_set_bit(hwirq, &sts, lpc->irq_num) { + int sirq = irq_find_mapping(lpc->domain, hwirq); + + generic_handle_irq(sirq); + } + + chained_irq_exit(chip, desc); +} + +/** + * phytium_lpc_irq_find_mapping- Find a linux irq from a LPC irq number. + * @offset: Hardware irq number in LPC domain sapce. + */ +int phytium_lpc_irq_find_mapping(u32 offset) +{ + if (!ft_lpc || !ft_lpc->domain) + return 0; + + return irq_find_mapping(ft_lpc->domain, offset); +} + +/** + * phytium_lpc_configure_irqs - Configure the LPC irq. + * @ft_lpc: Pointer to the phytium_lpc structure that represent the LPC. + * @pdev: Pointer to the platform device of LPC. + * + * Configure the LPC irq according to Phytium Spec. + */ +static int phytium_lpc_configure_irqs(struct phytium_lpc *ft_lpc, + struct platform_device *pdev) +{ + + struct irq_chip_generic *gc = NULL; + irq_hw_number_t hwirq; + int ret; + u32 sirq_config; + + /* + * NU_SERIRQ_CONFIG: + * bit 3~4: 2b01: Max serial irq number is 32, else 16. + */ + sirq_config = (readb(ft_lpc->regs + LPC_SERIRQ_CONFIG) >> 3) & 0x3; + if (sirq_config == 0x1) + ft_lpc->irq_num = 32; + else + ft_lpc->irq_num = 16; + + ft_lpc->domain = irq_domain_create_linear(dev_fwnode(&pdev->dev), + ft_lpc->irq_num, + &irq_generic_chip_ops, + ft_lpc); + if (!ft_lpc->domain) { + dev_err(&pdev->dev, "Cannot add Phytium irq domain.\n"); + return -EINVAL; + } + + ret = irq_alloc_domain_generic_chips(ft_lpc->domain, ft_lpc->irq_num, 1, + PHYTIUM_LPC_DRIVER_NAME, + handle_level_irq, 0, + IRQ_TYPE_DEFAULT, 0); + if (ret) { + dev_err(&pdev->dev, "Unable to register irq generic chip\n"); + goto out_free_domain; + } + + gc = irq_get_domain_generic_chip(ft_lpc->domain, 0); + if (!gc) { + dev_err(&pdev->dev, "Unable to get domain irq generic chip.\n"); + ret = -ENODEV; + goto out_free_domain; + } + + gc->private = ft_lpc; + gc->reg_base = ft_lpc->regs; + gc->domain = ft_lpc->domain; + gc->chip_types->chip = ft_lpc_irq_chip; + + ft_lpc->gc = gc; + + irq_set_chained_handler_and_data(ft_lpc->irq, + phytium_lpc_irq_handler, ft_lpc); + + /* Map a LPC interrupt into linux irq space */ + for (hwirq = 0; hwirq < ft_lpc->irq_num; hwirq++) + irq_create_mapping(ft_lpc->domain, hwirq); + + return 0; + +out_free_domain: + irq_domain_remove(ft_lpc->domain); + ft_lpc->domain = NULL; + + return ret; +} + +static int phytium_lpc_probe(struct platform_device *pdev) +{ + struct resource *mem = NULL; + const char *lpc_sync = SYNC_PROPERTY_LONG; + int err; + + ft_lpc = devm_kzalloc(&pdev->dev, sizeof(*ft_lpc), GFP_KERNEL); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem == NULL) { + pr_info("Phytium lpc get resource failed.\n"); + return -ENOMEM; + } + + lpc_base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(lpc_base)) { + pr_err("Phytium lpc ioremap base failed.\n"); + err = -ENOMEM; + goto err_resource; + } + + ft_lpc->regs = ioremap(mem->start + LPC_IO_OFFSET, 0xFFFD); + if (IS_ERR(ft_lpc->regs)) { + pr_err("Phytium lpc ioremap regs failed.\n"); + err = -ENOMEM; + goto err_ioremap; + } + + platform_set_drvdata(pdev, ft_lpc); + ft_lpc->dev = &pdev->dev; + ft_lpc->fwnode = dev_fwnode(&pdev->dev); + + ft_lpc->irq = platform_get_irq(pdev, 0); + if (ft_lpc->irq < 0) { + pr_err("Get Phytium lpc irq failed.\n"); + goto irq_failed; + } + + lpc_irq = ft_lpc->irq; + + err = phytium_lpc_configure_irqs(ft_lpc, pdev); + if (err) + dev_err(&pdev->dev, "Configure Phytium lpc irq failed %d\n", err); + + if (!is_cpu_ft2000pc()) + phytium_lpc_probe_dt(pdev); + + /* The 'Synchronize' property should be declared in ACPI or device-tree. */ + if (!device_property_read_string(&pdev->dev, "Synchronize", &lpc_sync)) + if (!strcmp(lpc_sync, SYNC_PROPERTY_SHORT)) + sync_cycle = SYNC_CYCLE_SHORT; + + /* Whether the Phytium EC acpi id present */ + if (acpi_dev_found("FTEC0001") || acpi_dev_found("PHYT000B")) { + /* + * Disable EC acpi irq. + * The LPC won't work normally, when enabled the ec apci + * without the ec driver to handle the irq. + * It enabled in the ec_sci event driver. + */ + ft_lpc_write(0x87, 0x66); + } + + /* + * Disable i8042 kbd/aux port in some phytium machines such as 706, + * enable it when i8042 present and i8042 driver probe. + */ + if (acpi_dev_found("KBCI8042") && + dmi_check_system(phytium_machine_table)) + ftlpc_disable_i8042_interfaces(); + + pr_info("phytium lpc probe\n"); + + return 0; + +irq_failed: + iounmap(ft_lpc->regs); + ft_lpc->regs = NULL; + +err_ioremap: + if (lpc_base) { + iounmap(lpc_base); + lpc_base = NULL; + } + +err_resource: + kfree(ft_lpc); + + return err; +} + +static int phytium_lpc_remove(struct platform_device *pdev) +{ + u32 offset; + int irq; + + iounmap(lpc_base); + lpc_base = NULL; + + iounmap(ft_lpc->regs); + ft_lpc->regs = NULL; + + if (ft_lpc->domain) { + for (offset = 0; offset < ft_lpc->irq_num; offset++) { + irq = irq_find_mapping(ft_lpc->domain, offset); + irq_dispose_mapping(irq); + } + + irq_destroy_generic_chip(ft_lpc->gc, 0, 0, 0); + irq_domain_remove(ft_lpc->domain); + } + + platform_set_drvdata(pdev, NULL); + kfree(ft_lpc); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int phytium_lpc_suspend(struct device *dev) +{ + if (ft_lpc && ft_lpc->regs) { + /* Read the LPC context */ + lpc_ctx.int_mask = readb(ft_lpc->regs + LPC_INT_MASK); + lpc_ctx.sirq_cfg = readl(ft_lpc->regs + LPC_SERIRQ_CONFIG); + + writeb(~lpc_ctx.wake_en & 0x3, ft_lpc->regs + LPC_INT_MASK); + } + + return 0; +} + +static int phytium_lpc_resume(struct device *dev) +{ + if (ft_lpc && ft_lpc->regs) { + /* Restore the LPC context */ + writeb(lpc_ctx.int_mask, ft_lpc->regs + LPC_INT_MASK); + writel(lpc_ctx.sirq_cfg, ft_lpc->regs + LPC_SERIRQ_CONFIG); + lpc_ctx.int_state = readl(ft_lpc->regs + LPC_INT_STATE); + } + + lpc_dbg("phytium LPC resume int mask: %X, mode: %X, status: %X\n", + lpc_ctx.int_mask, lpc_ctx.sirq_cfg, lpc_ctx.int_state); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(phytium_lpc_pm_ops, + phytium_lpc_suspend, + phytium_lpc_resume); +#endif + +#ifdef CONFIG_OF +static const struct of_device_id phytium_lpc_of_match[] = { + { .compatible = "phytium,lpc", }, + {}, +}; +MODULE_DEVICE_TABLE(of, phytium_lpc_of_match); +#endif + +static const struct acpi_device_id lpc_acpi_match[] = { + { "PHYT0007", 0 }, + { "LPC0001", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, lpc_acpi_match); + +static struct platform_driver phytium_lpc_driver = { + .probe = phytium_lpc_probe, + .remove = phytium_lpc_remove, + .driver = { + .name = PHYTIUM_LPC_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(phytium_lpc_of_match), + .acpi_match_table = ACPI_PTR(lpc_acpi_match), +#ifdef CONFIG_PM_SLEEP + .pm = &phytium_lpc_pm_ops, +#endif + } +}; + +static int __init phytium_lpc_init(void) +{ + return platform_driver_register(&phytium_lpc_driver); +} +subsys_initcall(phytium_lpc_init); + +MODULE_DESCRIPTION("Phytium Platform Spectic lpc Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/phytium-lpc-snoop.c b/drivers/misc/phytium-lpc-snoop.c new file mode 100644 index 0000000000000000000000000000000000000000..d7caa93d20b380fe78e82338e7bcc5cc5e6fb328 --- /dev/null +++ b/drivers/misc/phytium-lpc-snoop.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + * + * Provides a simple driver to control the PHYTIUM LPC snoop interface which + * allows the BMC to listen on and save the data written by + * the host to an arbitrary LPC I/O port. + * + * Typically used by the BMC to "watch" host boot progress via port + * 0x80 writes made by the BIOS during the boot process. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVICE_NAME "phytium-lpc-snoop" + +#define NUM_SNOOP_CHANNELS 2 +#define SNOOP_FIFO_SIZE 2048 + +#define snp_enable_reg 0x150 +#define snp_enable_reg_snp1_en BIT(0) +#define snp_enable_reg_snp1_int_en BIT(1) +#define snp_enable_reg_snp2_en BIT(2) +#define snp_enable_reg_snp2_int_en BIT(3) + +#define snp_status_reg 0x154 +#define snp_status_reg_snp1_int BIT(0) +#define snp_status_reg_snp2_int BIT(1) + +#define snp_addr_reg 0x158 +#define snp_addr_reg_snp1_addr GENMASK(15, 0) +#define snp_addr_reg_snp1_shift 0 +#define snp_addr_reg_snp2_addr GENMASK(31, 16) +#define snp_addr_reg_snp2_shift 16 + +#define snp_data_reg 0x15c +#define snp_data_reg_snp1_data_reg GENMASK(7, 0) +#define snp_data_reg_snp1_shift 0 +#define snp_data_reg_snp2_data_reg GENMASK(15, 8) +#define snp_data_reg_snp2_shift 8 + +struct phytium_lpc_snoop_channel { + struct kfifo fifo; + wait_queue_head_t wq; + struct miscdevice miscdev; +}; + +struct phytium_lpc_snoop { + struct regmap *regmap; + int irq; + struct phytium_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS]; +}; + +static struct phytium_lpc_snoop_channel *snoop_file_to_chan(struct file *file) +{ + return container_of(file->private_data, + struct phytium_lpc_snoop_channel, + miscdev); +} + +static ssize_t snoop_file_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct phytium_lpc_snoop_channel *chan = snoop_file_to_chan(file); + unsigned int copied; + int ret = 0; + + if (kfifo_is_empty(&chan->fifo)) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + ret = wait_event_interruptible(chan->wq, + !kfifo_is_empty(&chan->fifo)); + if (ret == -ERESTARTSYS) + return -EINTR; + } + ret = kfifo_to_user(&chan->fifo, buffer, count, &copied); + + return ret ? ret : copied; +} + +static unsigned int snoop_file_poll(struct file *file, + struct poll_table_struct *pt) +{ + struct phytium_lpc_snoop_channel *chan = snoop_file_to_chan(file); + + poll_wait(file, &chan->wq, pt); + return !kfifo_is_empty(&chan->fifo) ? POLLIN : 0; +} + +static const struct file_operations snoop_fops = { + .owner = THIS_MODULE, + .read = snoop_file_read, + .poll = snoop_file_poll, + .llseek = noop_llseek, +}; + +/* Save a byte to a FIFO and discard the oldest byte if FIFO is full */ +static void put_fifo_with_discard(struct phytium_lpc_snoop_channel *chan, u8 val) +{ + if (!kfifo_initialized(&chan->fifo)) + return; + if (kfifo_is_full(&chan->fifo)) + kfifo_skip(&chan->fifo); + kfifo_put(&chan->fifo, val); + wake_up_interruptible(&chan->wq); +} + +static irqreturn_t phytium_lpc_snoop_irq(int irq, void *arg) +{ + struct phytium_lpc_snoop *lpc_snoop = arg; + u32 reg, data; + + if (regmap_read(lpc_snoop->regmap, snp_status_reg, ®)) + return IRQ_NONE; + + /* Check if one of the snoop channels is interrupting */ + reg &= (snp_status_reg_snp1_int | snp_status_reg_snp2_int); + if (!reg) + return IRQ_NONE; + + /* Ack pending IRQs */ + regmap_write(lpc_snoop->regmap, snp_status_reg, reg); + + /* Read and save most recent snoop'ed data byte to FIFO */ + regmap_read(lpc_snoop->regmap, snp_data_reg, &data); + + if (reg & snp_status_reg_snp1_int) { + u8 val = (data & snp_data_reg_snp1_data_reg) >> snp_data_reg_snp1_shift; + + put_fifo_with_discard(&lpc_snoop->chan[0], val); + } + if (reg & snp_status_reg_snp2_int) { + u8 val = (data & snp_data_reg_snp2_data_reg) >> snp_data_reg_snp2_shift; + + put_fifo_with_discard(&lpc_snoop->chan[1], val); + } + + return IRQ_HANDLED; +} + +static int phytium_lpc_snoop_config_irq(struct phytium_lpc_snoop *lpc_snoop, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int rc; + + lpc_snoop->irq = platform_get_irq(pdev, 0); + if (!lpc_snoop->irq) + return -ENODEV; + + rc = devm_request_irq(dev, lpc_snoop->irq, + phytium_lpc_snoop_irq, IRQF_SHARED, + DEVICE_NAME, lpc_snoop); + if (rc < 0) { + dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq); + lpc_snoop->irq = 0; + return rc; + } + + return 0; +} + +static int phytium_lpc_enable_snoop(struct phytium_lpc_snoop *lpc_snoop, + struct device *dev, + int channel, u16 lpc_port) +{ + int rc = 0; + u32 snp_enable_reg_en, snp_addr_reg_mask, snp_addr_reg_shift; + + init_waitqueue_head(&lpc_snoop->chan[channel].wq); + /* Create FIFO datastructure */ + rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo, + SNOOP_FIFO_SIZE, GFP_KERNEL); + if (rc) + return rc; + + lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR; + lpc_snoop->chan[channel].miscdev.name = + devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel); + lpc_snoop->chan[channel].miscdev.fops = &snoop_fops; + lpc_snoop->chan[channel].miscdev.parent = dev; + rc = misc_register(&lpc_snoop->chan[channel].miscdev); + if (rc) + return rc; + + /* Enable LPC snoop channel at requested port */ + switch (channel) { + case 0: + snp_enable_reg_en = snp_enable_reg_snp1_en | snp_enable_reg_snp1_int_en; + snp_addr_reg_mask = snp_addr_reg_snp1_addr; + snp_addr_reg_shift = snp_addr_reg_snp1_shift; + break; + case 1: + snp_enable_reg_en = snp_enable_reg_snp2_en | snp_enable_reg_snp2_int_en; + snp_addr_reg_mask = snp_addr_reg_snp2_addr; + snp_addr_reg_shift = snp_addr_reg_snp2_shift; + break; + default: + return -EINVAL; + } + + regmap_update_bits(lpc_snoop->regmap, snp_enable_reg, snp_enable_reg_en, snp_enable_reg_en); + regmap_update_bits(lpc_snoop->regmap, snp_addr_reg, snp_addr_reg_mask, + lpc_port << snp_addr_reg_shift); + return rc; +} + +static void phytium_lpc_disable_snoop(struct phytium_lpc_snoop *lpc_snoop, + int channel) +{ + switch (channel) { + case 0: + regmap_update_bits(lpc_snoop->regmap, snp_enable_reg, + snp_enable_reg_snp1_en | snp_enable_reg_snp1_int_en, + 0); + break; + case 1: + regmap_update_bits(lpc_snoop->regmap, snp_enable_reg, + snp_enable_reg_snp2_en | snp_enable_reg_snp2_int_en, + 0); + break; + default: + return; + } + + kfifo_free(&lpc_snoop->chan[channel].fifo); + misc_deregister(&lpc_snoop->chan[channel].miscdev); +} + +static int phytium_lpc_snoop_probe(struct platform_device *pdev) +{ + struct phytium_lpc_snoop *lpc_snoop; + struct device *dev; + u32 port; + int rc; + + dev = &pdev->dev; + + lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL); + if (!lpc_snoop) + return -ENOMEM; + + lpc_snoop->regmap = syscon_node_to_regmap( + pdev->dev.parent->of_node); + if (IS_ERR(lpc_snoop->regmap)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + dev_set_drvdata(&pdev->dev, lpc_snoop); + + rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port); + if (rc) { + dev_err(dev, "no snoop ports configured\n"); + return -ENODEV; + } + + rc = phytium_lpc_snoop_config_irq(lpc_snoop, pdev); + if (rc) + return rc; + + rc = phytium_lpc_enable_snoop(lpc_snoop, dev, 0, port); + if (rc) + return rc; + + /* Configuration of 2nd snoop channel port is optional */ + if (of_property_read_u32_index(dev->of_node, "snoop-ports", + 1, &port) == 0) { + rc = phytium_lpc_enable_snoop(lpc_snoop, dev, 1, port); + if (rc) + phytium_lpc_disable_snoop(lpc_snoop, 0); + } + + return rc; +} + +static int phytium_lpc_snoop_remove(struct platform_device *pdev) +{ + struct phytium_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev); + + /* Disable both snoop channels */ + phytium_lpc_disable_snoop(lpc_snoop, 0); + phytium_lpc_disable_snoop(lpc_snoop, 1); + + return 0; +} + + +static const struct of_device_id phytium_lpc_snoop_match[] = { + { .compatible = "phytium,lpc-snoop"}, + { }, +}; + +static struct platform_driver phytium_lpc_snoop_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = phytium_lpc_snoop_match, + }, + .probe = phytium_lpc_snoop_probe, + .remove = phytium_lpc_snoop_remove, +}; + +module_platform_driver(phytium_lpc_snoop_driver); + +MODULE_DEVICE_TABLE(of, phytium_lpc_snoop_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lan Hengyu lanhengyu1395@phytium.com.cn"); +MODULE_DESCRIPTION("Driver to control Phytium LPC snoop functionality"); diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index bc7e2ad370021557154bbeda9d1d2c0c49b5c81b..a33cddf20d68bf5171cac54e21a85f64a6558d7c 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -1071,3 +1071,38 @@ config MMC_LITEX module will be called litex_mmc. If unsure, say N. + +config MMC_PHYTIUM_SDCI + tristate "Phytium SD Host Controller support" + depends on ARM64 + help + This selects support for the Phytium SD Host Controller on Phytium + Px210 chipset. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + +config MMC_PHYTIUM_MCI_PCI + tristate "Phytium PCI MultiMedia Card Interface support" + depends on ARCH_PHYTIUM + default y if ARCH_PHYTIUM + help + This selects support for the PCI MultiMedia Card Interface on Phytium + Px210 chipset. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + +config MMC_PHYTIUM_MCI_PLTFM + tristate "Phytium MultiMedia Card Interface support" + depends on ARCH_PHYTIUM && OF + default y if ARCH_PHYTIUM + help + This selects support for the MultiMedia Card Interface on Phytium + Px210 chipset. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index a693fa3d3f1cc6fc6703c16beda8e8bd0f9374cc..87bfbbe6d6c5c65570176152466fb2fbc5468df1 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -70,6 +70,9 @@ obj-$(CONFIG_MMC_USDHI6ROL0) += usdhi6rol0.o obj-$(CONFIG_MMC_TOSHIBA_PCI) += toshsd.o obj-$(CONFIG_MMC_BCM2835) += bcm2835.o obj-$(CONFIG_MMC_OWL) += owl-mmc.o +obj-$(CONFIG_MMC_PHYTIUM_SDCI) += phytium-sdci.o +obj-$(CONFIG_MMC_PHYTIUM_MCI_PCI) += phytium-mci-pci.o phytium-mci.o +obj-$(CONFIG_MMC_PHYTIUM_MCI_PLTFM) += phytium-mci-plat.o phytium-mci.o obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o diff --git a/drivers/mmc/host/phytium-mci-pci.c b/drivers/mmc/host/phytium-mci-pci.c new file mode 100644 index 0000000000000000000000000000000000000000..16c32d30d708ec75bfb2eb44ecbb60485630fd57 --- /dev/null +++ b/drivers/mmc/host/phytium-mci-pci.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Phytium Multimedia Card Interface PCI driver + * + * Copyright (C) 2020-2023, Phytium Technology Co., Ltd. + * + */ + +#include +#include +#include +#include +#include +#include +#include "phytium-mci.h" + +static u32 sd_caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_WAIT_WHILE_BUSY | + MMC_CAP_CMD23 | MMC_CAP_4_BIT_DATA; +static u32 sd_caps2 = MMC_CAP2_NO_MMC; + +static u32 emmc_caps = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA | MMC_CAP_WAIT_WHILE_BUSY | + MMC_CAP_CMD23 | MMC_CAP_HW_RESET | MMC_CAP_MMC_HIGHSPEED | + MMC_CAP_NONREMOVABLE; +static u32 emmc_caps2 = MMC_CAP2_NO_SDIO | MMC_CAP2_NO_SD; + +#define PCI_BAR_NO 0 + +#if defined CONFIG_PM && defined CONFIG_PM_SLEEP +static const struct dev_pm_ops phytium_mci_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(phytium_mci_suspend, + phytium_mci_resume) + SET_RUNTIME_PM_OPS(phytium_mci_runtime_suspend, + phytium_mci_runtime_resume, NULL) +}; +#else +#define phytium_mci_dev_pm_ops NULL +#endif + +static int +phytium_mci_pci_probe(struct pci_dev *pdev, const struct pci_device_id *pid) +{ + struct phytium_mci_host *host; + struct mmc_host *mmc; + int ret; + + ret = pcim_enable_device(pdev); + + if (ret) + return ret; + pci_set_master(pdev); + + mmc = mmc_alloc_host(sizeof(struct phytium_mci_host), &pdev->dev); + + if (!mmc) + return -ENOMEM; + + host = mmc_priv(mmc); + + pci_enable_msi(pdev); + + host->irq = pdev->irq; + host->irq_flags = IRQF_SHARED; + host->dev = &pdev->dev; + ret = pcim_iomap_regions(pdev, 1 << PCI_BAR_NO, pci_name(pdev)); + + if (ret) { + dev_err(&pdev->dev, "I/O memory remapping failed\n"); + goto host_free; + } + + host->base = pcim_iomap_table(pdev)[PCI_BAR_NO]; + host->is_use_dma = 1; + host->is_device_x100 = 1; + + if (pdev->devfn == 2) { + host->caps = emmc_caps; + host->caps2 = emmc_caps2; + } else { + host->caps = sd_caps; + host->caps2 = sd_caps2; + mmc->f_max = 25000000; /* stable frequency */ + } + + host->mmc = mmc; + host->clk_rate = MCI_CLK; + + dev_info(&pdev->dev, "%s %d: [bar %d] addr: 0x%llx size: 0x%llx km: 0x%llx devfn:%d\n", + __func__, __LINE__, PCI_BAR_NO, pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0), (uint64_t)host->base, pdev->devfn); + + dev_dbg(&pdev->dev, "%s %d:irq:0x%x\n", __func__, __LINE__, host->irq); + + ret = phytium_mci_common_probe(host); + + if (ret == MCI_REALEASE_MEM) { + ret = -ENOMEM; + goto release_mem; + } else if (ret) { + goto release; + } + pci_set_drvdata(pdev, mmc); + dev_info(&pdev->dev, "%s %d: probe phytium mci successful.\n", __func__, __LINE__); + return 0; + +release: + phytium_mci_deinit_hw(host); +release_mem: + + if (host->dma.adma_table) { + dma_free_coherent(&pdev->dev, + MAX_BD_NUM * sizeof(struct phytium_adma2_64_desc), + host->dma.adma_table, host->dma.adma_addr); + } +host_free: + mmc_free_host(mmc); + pci_disable_device(pdev); + return ret; +} + +static void phytium_mci_pci_remove(struct pci_dev *pdev) +{ + struct phytium_mci_host *host; + struct mmc_host *mmc; + + mmc = pci_get_drvdata(pdev); + if (!mmc) { + dev_info(&pdev->dev, "%s %d: mmc is null.\n", __func__, __LINE__); + return; + } + host = mmc_priv(mmc); + if (!host) { + dev_info(&pdev->dev, "%s %d: host is null.\n", __func__, __LINE__); + mmc_remove_host(mmc); + mmc_free_host(mmc); + return; + } + + del_timer(&host->hotplug_timer); + + mmc_remove_host(host->mmc); + + if (host->dma.adma_table) { + dma_free_coherent(&pdev->dev, + MAX_BD_NUM * sizeof(struct phytium_adma2_64_desc), + host->dma.adma_table, host->dma.adma_addr); + } + phytium_mci_deinit_hw(host); + mmc_free_host(mmc); + pci_set_drvdata(pdev, NULL); +} + +static const struct pci_device_id phytium_mci_pci_tbl[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_PHYTIUM, 0xdc28), + .class = 0x5, + .class_mask = 0, + }, + {} +}; +MODULE_DEVICE_TABLE(pci, phytium_mci_pci_tbl); + +static struct pci_driver phytium_mci_pci_driver = { + .name = "phytium-mci-pci", + .id_table = phytium_mci_pci_tbl, + .probe = phytium_mci_pci_probe, + .remove = phytium_mci_pci_remove, + .driver = { + .pm = &phytium_mci_dev_pm_ops, + } +}; +module_pci_driver(phytium_mci_pci_driver); + +MODULE_DESCRIPTION("Phytium Multimedia Card Interface PCI driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cheng Quan "); diff --git a/drivers/mmc/host/phytium-mci-plat.c b/drivers/mmc/host/phytium-mci-plat.c new file mode 100644 index 0000000000000000000000000000000000000000..a774f09a7a2e176588f6255c5ec4a303a574fd14 --- /dev/null +++ b/drivers/mmc/host/phytium-mci-plat.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Phytium Multimedia Card Interface PCI driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "phytium-mci.h" + +static u32 mci_caps = MMC_CAP_CMD23 | MMC_CAP_WAIT_WHILE_BUSY; + +#if defined CONFIG_PM && defined CONFIG_PM_SLEEP + +static const struct dev_pm_ops phytium_mci_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(phytium_mci_suspend, + phytium_mci_resume) + SET_RUNTIME_PM_OPS(phytium_mci_runtime_suspend, + phytium_mci_runtime_resume, NULL) +}; +#else +#define phytium_mci_dev_pm_ops NULL +#endif + +static int phytium_mci_probe(struct platform_device *pdev) +{ + struct mmc_host *mmc; + struct phytium_mci_host *host; + struct resource *res; + const struct acpi_device_id *match; + struct device *dev = &pdev->dev; + int ret; + + mmc = mmc_alloc_host(sizeof(struct phytium_mci_host), &pdev->dev); + if (!mmc) + return -ENOMEM; + host = mmc_priv(mmc); + ret = mmc_of_parse(mmc); + if (ret) + goto host_free; + + if (device_property_read_bool(dev, "use-hold")) + host->use_hold = 1; + + if (device_property_read_bool(dev, "clk-set")) + host->clk_set = 1; + + if (host->clk_set) { + host->clk_smpl_drv_25m = -1; + host->clk_smpl_drv_50m = -1; + host->clk_smpl_drv_66m = -1; + host->clk_smpl_drv_100m = -1; + device_property_read_u32(dev, "clk-smpl-drv-25m", &host->clk_smpl_drv_25m); + device_property_read_u32(dev, "clk-smpl-drv-50m", &host->clk_smpl_drv_50m); + device_property_read_u32(dev, "clk-smpl-drv-66m", &host->clk_smpl_drv_66m); + device_property_read_u32(dev, "clk-smpl-drv-100m", &host->clk_smpl_drv_100m); + } + dev_info(dev, "mci clk set %d %d 0x%x 0x%x 0x%x 0x%x\n", + host->use_hold, host->clk_set, host->clk_smpl_drv_25m, + host->clk_smpl_drv_50m, host->clk_smpl_drv_66m, host->clk_smpl_drv_100m); + + if (dev->of_node) { + host->src_clk = devm_clk_get(&pdev->dev, "phytium_mci_clk"); + if (IS_ERR(host->src_clk)) { + ret = PTR_ERR(host->src_clk); + goto host_free; + } + + host->clk_rate = clk_get_rate(host->src_clk); + } else if (has_acpi_companion(dev)) { + match = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!match) { + dev_err(dev, "Error ACPI match data is missing\n"); + return -ENODEV; + } + host->clk_rate = 1200000000; + } + + host->is_use_dma = 1; + host->is_device_x100 = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->base)) { + ret = PTR_ERR(host->base); + goto host_free; + } + + host->irq = platform_get_irq(pdev, 0); + + if (host->irq < 0) { + ret = -EINVAL; + goto host_free; + } + host->irq_flags = IRQF_SHARED; + dev_dbg(&pdev->dev, "%s %d:irq:%d\n", __func__, __LINE__, host->irq); + host->dev = &pdev->dev; + host->caps = mci_caps; + host->mmc = mmc; + ret = phytium_mci_common_probe(host); + if (ret == MCI_REALEASE_MEM) { + ret = -ENOMEM; + goto release_mem; + } else if (ret) { + goto release; + } + platform_set_drvdata(pdev, mmc); + dev_info(&pdev->dev, "%s %d: probe phytium mci successful.\n", __func__, __LINE__); + return 0; + +release: + phytium_mci_deinit_hw(host); +release_mem: + if (host->dma.adma_table) { + dma_free_coherent(&pdev->dev, + MAX_BD_NUM * sizeof(struct phytium_adma2_64_desc), + host->dma.adma_table, host->dma.adma_addr); + } +host_free: + mmc_free_host(mmc); + return ret; +} + +static int phytium_mci_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc; + struct phytium_mci_host *host; + + mmc = platform_get_drvdata(pdev); + if (!mmc) { + dev_info(&pdev->dev, "%s %d: mmc is null.\n", __func__, __LINE__); + return -1; + } + host = mmc_priv(mmc); + if (!host) { + dev_info(&pdev->dev, "%s %d: host is null.\n", __func__, __LINE__); + mmc_remove_host(mmc); + mmc_free_host(mmc); + return -1; + } + del_timer(&host->hotplug_timer); + mmc_remove_host(host->mmc); + + if (host->dma.adma_table) { + dma_free_coherent(&pdev->dev, + MAX_BD_NUM * sizeof(struct phytium_adma2_64_desc), + host->dma.adma_table, host->dma.adma_addr); + } + phytium_mci_deinit_hw(host); + mmc_free_host(mmc); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id phytium_mci_of_ids[] = { + { .compatible = "phytium,mci", }, + {} +}; + +MODULE_DEVICE_TABLE(of, phytium_mci_of_ids); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_mci_acpi_ids[] = { + { .id = "PHYT0017" }, + { } +}; + +MODULE_DEVICE_TABLE(acpi, phytium_mci_acpi_ids); +#else +#define phytium_mci_acpi_ids NULL +#endif + +static struct platform_driver phytium_mci_driver = { + .probe = phytium_mci_probe, + .remove = phytium_mci_remove, + .driver = { + .name = "phytium-mci-platform", + .of_match_table = phytium_mci_of_ids, + .acpi_match_table = phytium_mci_acpi_ids, + .pm = &phytium_mci_dev_pm_ops, + }, +}; + +module_platform_driver(phytium_mci_driver); + +MODULE_DESCRIPTION("Phytium Multimedia Card Interface driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cheng Quan "); diff --git a/drivers/mmc/host/phytium-mci.c b/drivers/mmc/host/phytium-mci.c new file mode 100644 index 0000000000000000000000000000000000000000..852f1f0067a516eab34524a35a9b66c0a38f9041 --- /dev/null +++ b/drivers/mmc/host/phytium-mci.c @@ -0,0 +1,1530 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Phytium Multimedia Card Interface + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#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 "phytium-mci.h" + +static const u32 cmd_ints_mask = MCI_INT_MASK_RE | MCI_INT_MASK_CMD | MCI_INT_MASK_RCRC | + MCI_INT_MASK_RTO | MCI_INT_MASK_HTO | MCI_RAW_INTS_HLE; + +static const u32 data_ints_mask = MCI_INT_MASK_DTO | MCI_INT_MASK_DCRC | MCI_INT_MASK_DRTO | + MCI_INT_MASK_SBE_BCI; +static const u32 cmd_err_ints_mask = MCI_INT_MASK_RTO | MCI_INT_MASK_RCRC | MCI_INT_MASK_RE | + MCI_INT_MASK_DCRC | MCI_INT_MASK_DRTO | + MCI_MASKED_INTS_SBE_BCI; + +static const u32 dmac_ints_mask = MCI_DMAC_INT_ENA_FBE | MCI_DMAC_INT_ENA_DU | + MCI_DMAC_INT_ENA_NIS | MCI_DMAC_INT_ENA_AIS; +static const u32 dmac_err_ints_mask = MCI_DMAC_INT_ENA_FBE | MCI_DMAC_INT_ENA_DU | + MCI_DMAC_INT_ENA_AIS; + +static void phytium_mci_cmd_next(struct phytium_mci_host *host, + struct mmc_request *mrq, + struct mmc_command *cmd); +static void phytium_mci_adma_reset(struct phytium_mci_host *host); +static void phytium_mci_send_cmd(struct phytium_mci_host *host, u32 cmd, u32 arg); +static bool phytium_mci_data_xfer_done(struct phytium_mci_host *host, u32 events, + struct mmc_request *mrq, struct mmc_data *data); +static void phytium_mci_init_adma_table(struct phytium_mci_host *host, + struct phytium_mci_dma *dma); +static void phytium_mci_init_hw(struct phytium_mci_host *host); +static int phytium_mci_get_cd(struct mmc_host *mmc); +static int phytium_mci_err_irq(struct phytium_mci_host *host, u32 dmac_events, u32 events); + +static void sdr_set_bits(void __iomem *reg, u32 bs) +{ + u32 val = readl(reg); + + val |= bs; + writel(val, reg); +} + +static void sdr_clr_bits(void __iomem *reg, u32 bs) +{ + u32 val = readl(reg); + + val &= ~bs; + writel(val, reg); +} + +static void phytium_mci_reset_hw(struct phytium_mci_host *host) +{ + sdr_set_bits(host->base + MCI_CNTRL, MCI_CNTRL_FIFO_RESET | MCI_CNTRL_DMA_RESET); + + while (readl(host->base + MCI_CNTRL) & (MCI_CNTRL_FIFO_RESET | MCI_CNTRL_DMA_RESET)) + cpu_relax(); + phytium_mci_send_cmd(host, MCI_CMD_UPD_CLK, 0); +} + +static void phytium_mci_update_external_clk(struct phytium_mci_host *host, u32 uhs_reg_value) +{ + writel(0, host->base + MCI_UHS_REG_EXT); + writel(uhs_reg_value, host->base + MCI_UHS_REG_EXT); + while (!(readl(host->base + MCI_CCLK_RDY) & 0x1)) + cpu_relax(); + +} + +static void phytium_mci_prepare_data(struct phytium_mci_host *host, + struct mmc_request *mrq) +{ + struct mmc_data *data = mrq->data; + + if (!(data->host_cookie & MCI_PREPARE_FLAG)) { + data->host_cookie |= MCI_PREPARE_FLAG; + data->sg_count = dma_map_sg(host->dev, data->sg, data->sg_len, + mmc_get_dma_dir(data)); + } +} + +static void phytium_mci_unprepare_data(struct phytium_mci_host *host, + struct mmc_request *mrq) +{ + struct mmc_data *data = mrq->data; + + if (data->host_cookie & MCI_ASYNC_FLAG) + return; + + if (data->host_cookie & MCI_PREPARE_FLAG) { + dma_unmap_sg(host->dev, data->sg, data->sg_len, mmc_get_dma_dir(data)); + data->host_cookie &= ~MCI_PREPARE_FLAG; + } +} + +static void phytium_mci_send_cmd(struct phytium_mci_host *host, u32 cmd, u32 arg) +{ + int rc; + u32 data; + + writel(arg, host->base + MCI_CMDARG); + wmb(); /* drain writebuffer */ + + rc = readl_relaxed_poll_timeout(host->base + MCI_STATUS, + data, + !(data & MCI_STATUS_CARD_BUSY), + 0, 5000 * 1000); + if (rc == -ETIMEDOUT) + pr_debug("%s %d, timeout mci_status: 0x%08x\n", __func__, __LINE__, data); + + writel(MCI_CMD_START | cmd, host->base + MCI_CMD); + + rc = readl_relaxed_poll_timeout(host->base + MCI_CMD, + data, + !(data & MCI_CMD_START), + 0, 5000 * 1000); + if (rc == -ETIMEDOUT) + pr_debug("%s %d, timeout mci_cmd: 0x%08x\n", __func__, __LINE__, data); +} + +static void phytium_mci_update_cmd11(struct phytium_mci_host *host, u32 cmd) +{ + writel(MCI_CMD_START | cmd, host->base + MCI_CMD); + + while (readl(host->base + MCI_CMD) & MCI_CMD_START) + cpu_relax(); +} + +static void phytium_mci_set_clk(struct phytium_mci_host *host, struct mmc_ios *ios) +{ + u32 div = 0xff, drv = 0, sample = 0; + unsigned long clk_rate; + u32 mci_cmd_bits = MCI_CMD_UPD_CLK; + u32 cmd_reg; + u32 cur_cmd_index; + u32 first_uhs_div, tmp_ext_reg; + + cmd_reg = readl(host->base + MCI_CMD); + cur_cmd_index = cmd_reg & 0x3F; + + if (cur_cmd_index == SD_SWITCH_VOLTAGE) + mci_cmd_bits |= MCI_CMD_VOLT_SWITCH; + if (ios->clock) { + if (host->current_ios_clk == ios->clock) + return; + + dev_dbg(host->dev, "will change clock, host->clk_rate: %ld, ios->clock: %d\n", + host->clk_rate, ios->clock); + + if (ios->clock >= 25000000) + tmp_ext_reg = 0x202; + else if (ios->clock == 400000) + tmp_ext_reg = 0x502; + else + tmp_ext_reg = 0x302; + + phytium_mci_update_external_clk(host, tmp_ext_reg); + sdr_clr_bits(host->base + MCI_CLKENA, MCI_CLKENA_CCLK_ENABLE); + + if (cur_cmd_index == SD_SWITCH_VOLTAGE) + phytium_mci_update_cmd11(host, mci_cmd_bits | cmd_reg); + else + phytium_mci_send_cmd(host, mci_cmd_bits, 0); + + clk_rate = host->clk_rate; + first_uhs_div = 1 + ((tmp_ext_reg >> 8)&0xFF); + div = clk_rate / (2 * first_uhs_div * ios->clock); + + if (host->clk_smpl_drv_25m >= 0 + && ios->clock == 25000000 && host->clk_set) { + writel((host->clk_smpl_drv_25m << 8) | (div & 0xff), + host->base + MCI_CLKDIV); + } else if (host->clk_smpl_drv_50m >= 0 + && ios->clock == 50000000 && host->clk_set){ + writel((host->clk_smpl_drv_50m << 8) | (div & 0xff), + host->base + MCI_CLKDIV); + } else if (host->clk_smpl_drv_66m >= 0 + && ios->clock == 66000000 && host->clk_set){ + writel((host->clk_smpl_drv_66m << 8) | (div & 0xff), + host->base + MCI_CLKDIV); + } else if (host->clk_smpl_drv_100m >= 0 + && ios->clock == 100000000 && host->clk_set){ + writel((host->clk_smpl_drv_100m << 8) | (div & 0xff), + host->base + MCI_CLKDIV); + } else { + if (div > 2) { + sample = div / 2 + 1; + drv = sample - 1; + writel((sample << 16) | (drv << 8) | (div & 0xff), + host->base + MCI_CLKDIV); + } else if (div == 2) { + drv = 0; + sample = 1; + writel((drv << 8) | (sample << 16) | (div & 0xff), + host->base + MCI_CLKDIV); + } + } + + dev_dbg(host->dev, "UHS_REG_EXT ext: %x, CLKDIV: %x\n", + readl(host->base + MCI_UHS_REG_EXT), readl(host->base + MCI_CLKDIV)); + + sdr_set_bits(host->base + MCI_CLKENA, MCI_CLKENA_CCLK_ENABLE); + + if (cur_cmd_index == SD_SWITCH_VOLTAGE) + phytium_mci_update_cmd11(host, mci_cmd_bits | cmd_reg); + else + phytium_mci_send_cmd(host, mci_cmd_bits, 0); + + host->current_ios_clk = ios->clock; + + dev_dbg(host->dev, "host->clk_rate: %ld, ios->clock: %d\n", + host->clk_rate, ios->clock); + } else { + host->current_ios_clk = 0; + sdr_clr_bits(host->base + MCI_CLKENA, MCI_CLKENA_CCLK_ENABLE); + + if (cur_cmd_index == SD_SWITCH_VOLTAGE) + phytium_mci_update_cmd11(host, mci_cmd_bits | cmd_reg); + else + phytium_mci_send_cmd(host, mci_cmd_bits, 0); + + sdr_clr_bits(host->base + MCI_UHS_REG_EXT, MCI_EXT_CLK_ENABLE); + dev_dbg(host->dev, "host->clk_rate: %ld, ios->clock: %d\n", + host->clk_rate, ios->clock); + } +} + +static inline u32 +phytium_mci_cmd_find_resp(struct phytium_mci_host *host, + struct mmc_request *mrq, + struct mmc_command *cmd) +{ + u32 resp; + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1: + case MMC_RSP_R1B: + resp = 0x5; + break; + + case MMC_RSP_R2: + resp = 0x7; + break; + + case MMC_RSP_R3: + resp = 0x1; + break; + + case MMC_RSP_NONE: + default: + resp = 0x0; + break; + } + + return resp; +} + +static inline +u32 phytium_mci_cmd_prepare_raw_cmd(struct phytium_mci_host *host, + struct mmc_request *mrq, + struct mmc_command *cmd) +{ + u32 opcode = cmd->opcode; + u32 resp = phytium_mci_cmd_find_resp(host, mrq, cmd); + u32 rawcmd = ((opcode & 0x3f) | ((resp & 0x7) << 6)); + + if (opcode == MMC_GO_INACTIVE_STATE || + (opcode == SD_IO_RW_DIRECT && ((cmd->arg >> 9) & 0x1FFFF) == SDIO_CCCR_ABORT)) + rawcmd |= (0x1 << 14); + else if (opcode == SD_SWITCH_VOLTAGE) + rawcmd |= (0x1 << 28); + + if (test_and_clear_bit(MCI_CARD_NEED_INIT, &host->flags)) + rawcmd |= (0x1 << 15); + + if (cmd->data) { + struct mmc_data *data = cmd->data; + + rawcmd |= (0x1 << 9); + + if (data->flags & MMC_DATA_WRITE) + rawcmd |= (0x1 << 10); + } + if (host->use_hold) + rawcmd |= (0x1 << 29); + + return (rawcmd | (0x1 << 31)); +} + +static inline void +phytium_mci_adma_write_desc(struct phytium_mci_host *host, + struct phytium_adma2_64_desc *desc, + dma_addr_t addr, u32 len, u32 attribute) +{ + desc->attribute = attribute; + desc->len = len; + desc->addr_lo = lower_32_bits(addr); + desc->addr_hi = upper_32_bits(addr); + dev_dbg(host->dev, "%s %d:addr_lo:0x%x ddr_hi:0x%x\n", __func__, + __LINE__, desc->addr_lo, desc->addr_hi); + + if ((attribute == 0x80000004) || (attribute == 0x8000000c)) { + desc->desc_lo = 0; + desc->desc_hi = 0; + } +} + +static void +phytium_mci_data_sg_write_2_admc_table(struct phytium_mci_host *host, struct mmc_data *data) +{ + struct phytium_adma2_64_desc *desc; + u32 dma_len, i; + dma_addr_t dma_address; + struct scatterlist *sg; + + phytium_mci_init_adma_table(host, &host->dma); + + desc = host->dma.adma_table; + for_each_sg(data->sg, sg, data->sg_count, i) { + dma_address = sg_dma_address(sg); + dma_len = sg_dma_len(sg); + + if (i == 0) { + if (sg_is_last(sg) || (data->sg_count == 1 && dma_len == SD_BLOCK_SIZE)) + phytium_mci_adma_write_desc(host, desc, dma_address, + dma_len, 0x8000000c); + else + phytium_mci_adma_write_desc(host, desc, dma_address, + dma_len, 0x8000001a); + } else if (sg_is_last(sg)) { + phytium_mci_adma_write_desc(host, desc, dma_address, + dma_len, 0x80000004); + } else { + phytium_mci_adma_write_desc(host, desc, dma_address, + dma_len, 0x80000012); + } + + desc++; + } +} + +static void +phytium_mci_data_sg_write_2_fifo(struct phytium_mci_host *host, struct mmc_data *data) +{ + struct scatterlist *sg; + u32 dma_len, i, j; + u32 *virt_addr; + + if (mmc_get_dma_dir(data) == DMA_TO_DEVICE) { + writel(0x1<<10, host->base + MCI_CMD); + for_each_sg(data->sg, sg, data->sg_count, i) { + dma_len = sg_dma_len(sg); + virt_addr = sg_virt(data->sg); + for (j = 0; j < (dma_len / 4); j++) { + writel(*virt_addr, host->base + MCI_DATA); + virt_addr++; + } + } + } +} + +static void phytium_mci_restart_clk(struct phytium_mci_host *host) +{ + u32 clk_div, uhs; + + while (readl(host->base + MCI_CMD) & MCI_CMD_START) + cpu_relax(); + sdr_clr_bits(host->base + MCI_CLKENA, MCI_CLKENA_CCLK_ENABLE); + clk_div = readl(host->base + MCI_CLKDIV); + uhs = readl(host->base + MCI_UHS_REG_EXT); + writel(0, host->base + MCI_UHS_REG_EXT); + writel(uhs, host->base + MCI_UHS_REG_EXT); + while (!(readl(host->base + MCI_CCLK_RDY) & 0x1)) + cpu_relax(); + + writel(clk_div, host->base + MCI_CLKDIV); + sdr_set_bits(host->base + MCI_CLKENA, MCI_CLKENA_CCLK_ENABLE); + writel(MCI_CMD_START | MCI_CMD_UPD_CLK, host->base + MCI_CMD); + while (readl(host->base + MCI_CMD) & MCI_CMD_START) + cpu_relax(); +} + +static int +phytim_mci_start_multiple_write(struct phytium_mci_host *host, + struct mmc_request *mrq, u32 cnts, u32 offset) +{ + u32 rawcmd, cmd_status; + struct mmc_command *cmd = mrq->cmd; + u32 *rsp = cmd->resp; + unsigned long deadline_time; + + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE) && readl(host->base + MCI_CARD_DETECT)) + return -ESHUTDOWN; + + while ((readl(host->base + MCI_STATUS) & (MCI_STATUS_CARD_BUSY))) + cpu_relax(); + + writel(0xffffe, host->base + MCI_RAW_INTS); + rawcmd = phytium_mci_cmd_prepare_raw_cmd(host, mrq, cmd); + writel(mrq->data->blksz, host->base + MCI_BLKSIZ); + writel(cnts * mrq->data->blksz, host->base + MCI_BYTCNT); + writel(cmd->arg + offset, host->base + MCI_CMDARG); + writel(rawcmd, host->base + MCI_CMD); + deadline_time = jiffies + msecs_to_jiffies(200); + + cmd_status = readl(host->base + MCI_RAW_INTS); + while (!(cmd_status & MCI_MASKED_INTS_CMD)) { + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE) && + readl(host->base + MCI_CARD_DETECT)) + return -ESHUTDOWN; + + cmd_status = readl(host->base + MCI_RAW_INTS); + if (cmd_err_ints_mask & cmd_status) + return -ESHUTDOWN; + + if (cmd_status & MCI_MASKED_INTS_CMD) + break; + + if (time_after(jiffies, deadline_time)) + return -ESHUTDOWN; + } + + if (cmd_status & MCI_MASKED_INTS_CMD) { + if (cmd->flags & MMC_RSP_136) { + rsp[3] = readl(host->base + MCI_RESP0); + rsp[2] = readl(host->base + MCI_RESP1); + rsp[1] = readl(host->base + MCI_RESP2); + rsp[0] = readl(host->base + MCI_RESP3); + } else { + rsp[0] = readl(host->base + MCI_RESP0); + } + } + deadline_time = jiffies + msecs_to_jiffies(1000); + while (!(cmd_status & MCI_MASKED_INTS_DTO)) { + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE) && + readl(host->base + MCI_CARD_DETECT)) + return -ESHUTDOWN; + cmd_status = readl(host->base + MCI_RAW_INTS); + if (cmd_err_ints_mask & cmd_status) + return -ESHUTDOWN; + if (cmd_status & MCI_MASKED_INTS_DTO) + return 0; + if (time_after(jiffies, deadline_time)) + return -ESHUTDOWN; + } + return 0; +} + +static int +phytium_mci_start_sbc_stop_cmd(struct phytium_mci_host *host, struct mmc_request *mrq, + struct mmc_command *cmd, u32 arg) +{ + u32 rawcmd, cmd_status; + u32 *rsp = cmd->resp; + unsigned long deadline_time; + + writel(0xffffe, host->base + MCI_RAW_INTS); + + while ((readl(host->base + MCI_STATUS) & (MCI_STATUS_CARD_BUSY))) + cpu_relax(); + + rawcmd = phytium_mci_cmd_prepare_raw_cmd(host, mrq, cmd); + writel(arg, host->base + MCI_CMDARG); + writel(rawcmd, host->base + MCI_CMD); + + deadline_time = jiffies + msecs_to_jiffies(200); + cmd_status = readl(host->base + MCI_RAW_INTS); + while (!(cmd_status & MCI_MASKED_INTS_CMD)) { + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE) && + readl(host->base + MCI_CARD_DETECT)) + return -ENOMEDIUM; + + cmd_status = readl(host->base + MCI_RAW_INTS); + if (cmd_err_ints_mask & cmd_status) + return -ETIMEDOUT; + + if (cmd_status & MCI_MASKED_INTS_CMD) + break; + + if (time_after(jiffies, deadline_time)) + return -ETIMEDOUT; + } + + if (cmd_status & MCI_MASKED_INTS_CMD) { + if (cmd->flags & MMC_RSP_136) { + rsp[3] = readl(host->base + MCI_RESP0); + rsp[2] = readl(host->base + MCI_RESP1); + rsp[1] = readl(host->base + MCI_RESP2); + rsp[0] = readl(host->base + MCI_RESP3); + } else { + rsp[0] = readl(host->base + MCI_RESP0); + } + } + + if (cmd_err_ints_mask & cmd_status) + return -ETIMEDOUT; + + return 0; +} + +static void +phytium_mci_start_write_multiple_non_dma(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct phytium_mci_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + u32 write_cnts, last_cnts; + u32 i, j, k, send_cnt_one_sg, block_offset; + int ret = 0, dma_len; + struct scatterlist *sg; + u32 *virt_addr = NULL; + + write_cnts = data->blocks / 4; + (data->blocks % 4) ? write_cnts++ : write_cnts; + last_cnts = data->blocks % 4; + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE) && readl(host->base + MCI_CARD_DETECT)) { + ret = -ENOMEDIUM; + goto write_err; + } + + dev_dbg(host->dev, "%s: cmd:%d, block counts:%d\n", + __func__, mrq->cmd->opcode, data->blocks); + + sdr_clr_bits(host->base + MCI_CNTRL, MCI_CNTRL_USE_INTERNAL_DMAC); + sdr_set_bits(host->base + MCI_CNTRL, MCI_CNTRL_FIFO_RESET); + while (readl(host->base + MCI_CNTRL) & MCI_CNTRL_FIFO_RESET) + cpu_relax(); + sdr_clr_bits(host->base + MCI_BUS_MODE, MCI_BUS_MODE_DE); + + if (mmc_get_dma_dir(data) == DMA_TO_DEVICE) { + block_offset = 0; + for_each_sg(data->sg, sg, data->sg_count, i) { + /* Each SG data transfor starts */ + dma_len = sg_dma_len(sg); + send_cnt_one_sg = (dma_len / MCI_MAX_FIFO_CNT) + 1; + virt_addr = sg_virt(sg); + for (k = 0; k < send_cnt_one_sg; k++) { + if (dma_len && dma_len >= MCI_MAX_FIFO_CNT) { + /*first write sbc cmd*/ + ret = phytium_mci_start_sbc_stop_cmd(host, mrq, + mrq->sbc, 4); + if (ret) + goto write_err; + writel(0x1 << 10, host->base + MCI_CMD); + for (j = 0; j < (MCI_MAX_FIFO_CNT / 4); j++) { + writel(*virt_addr, host->base + MCI_DATA); + virt_addr++; + } + + /*second write cmd25 here*/ + ret = phytim_mci_start_multiple_write(host, mrq, 4, + block_offset); + if (ret) + goto write_err; + block_offset += 4; + dma_len -= MCI_MAX_FIFO_CNT; + } else if (dma_len > 0) { + /*first write sbc cmd*/ + last_cnts = dma_len / 512; + ret = phytium_mci_start_sbc_stop_cmd(host, mrq, mrq->sbc, + last_cnts); + if (ret) + goto write_err; + writel(0x1 << 10, host->base + MCI_CMD); + for (j = 0; j < (dma_len / 4); j++) { + writel(*virt_addr, host->base + MCI_DATA); + virt_addr++; + } + /*second write cmd25 here*/ + ret = phytim_mci_start_multiple_write(host, mrq, last_cnts, + block_offset); + if (ret) + goto write_err; + block_offset += last_cnts; + dma_len = 0; + } else { + dev_dbg(host->dev, "%s: sg %d end\n", __func__, i); + break; + } + } + } + } + +write_err: + host->data = NULL; + host->cmd = NULL; + host->mrq = NULL; + writel(0xffffe, host->base + MCI_RAW_INTS); + if (ret) { + data->bytes_xfered = 0; + if (ret == -ESHUTDOWN) { + sdr_set_bits(host->base + MCI_CNTRL, MCI_CNTRL_FIFO_RESET); + while (readl(host->base + MCI_CNTRL) & MCI_CNTRL_FIFO_RESET) + cpu_relax(); + + sdr_set_bits(host->base + MCI_CNTRL, MCI_CNTRL_CONTROLLER_RESET); + while (readl(host->base + MCI_STATUS) & MCI_STATUS_CARD_BUSY) + sdr_set_bits(host->base + MCI_CNTRL, MCI_CNTRL_CONTROLLER_RESET); + phytium_mci_restart_clk(host); + phytium_mci_start_sbc_stop_cmd(host, mrq, mrq->stop, mrq->stop->arg); + } + data->error = -ETIMEDOUT; + mrq->cmd->error = -ETIMEDOUT; + mmc_request_done(host->mmc, mrq); + return; + } + data->bytes_xfered = data->blocks * data->blksz; + mmc_request_done(host->mmc, mrq); +} + +static void +phytium_mci_start_data(struct phytium_mci_host *host, struct mmc_request *mrq, + struct mmc_command *cmd, struct mmc_data *data) +{ + bool read; + u32 rawcmd; + unsigned long flags; + + + WARN_ON(host->cmd); + host->cmd = cmd; + cmd->error = 0; + WARN_ON(host->data); + host->data = data; + read = data->flags & MMC_DATA_READ; + + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE) && readl(host->base + MCI_CARD_DETECT)) { + phytium_mci_err_irq(host, 0, MCI_INT_MASK_RTO); + return; + } + /* clear interrupts */ + writel(0xffffe, host->base + MCI_RAW_INTS); + + sdr_set_bits(host->base + MCI_CNTRL, MCI_CNTRL_FIFO_RESET | MCI_CNTRL_DMA_RESET); + + while (readl(host->base + MCI_CNTRL) & (MCI_CNTRL_FIFO_RESET | MCI_CNTRL_DMA_RESET)) + cpu_relax(); + + if (host->adtc_type == COMMOM_ADTC) + sdr_clr_bits(host->base + MCI_CNTRL, MCI_CNTRL_USE_INTERNAL_DMAC); + else + sdr_set_bits(host->base + MCI_CNTRL, MCI_CNTRL_USE_INTERNAL_DMAC); + wmb(); /* drain writebuffer */ + sdr_clr_bits(host->base + MCI_CNTRL, MCI_CNTRL_INT_ENABLE); + + rawcmd = phytium_mci_cmd_prepare_raw_cmd(host, mrq, cmd); + if (host->is_use_dma && host->adtc_type == BLOCK_RW_ADTC) + phytium_mci_data_sg_write_2_admc_table(host, data); + else + phytium_mci_data_sg_write_2_fifo(host, data); + + spin_lock_irqsave(&host->lock, flags); + sdr_set_bits(host->base + MCI_INT_MASK, cmd_ints_mask | data_ints_mask); + if (host->is_use_dma && host->adtc_type == BLOCK_RW_ADTC) { + sdr_set_bits(host->base + MCI_DMAC_INT_ENA, dmac_ints_mask); + /* Enable the IDMAC */ + sdr_set_bits(host->base + MCI_BUS_MODE, MCI_BUS_MODE_DE); + writel((u32)host->dma.adma_addr, host->base + MCI_DESC_LIST_ADDRL); + writel((u32)(host->dma.adma_addr >> 32), host->base + MCI_DESC_LIST_ADDRH); + } + writel(mrq->data->blksz, host->base + MCI_BLKSIZ); + writel(mrq->data->blocks * mrq->data->blksz, host->base + MCI_BYTCNT); + sdr_set_bits(host->base + MCI_CNTRL, MCI_CNTRL_INT_ENABLE); + writel(cmd->arg, host->base + MCI_CMDARG); + wmb(); /* drain writebuffer */ + writel(rawcmd, host->base + MCI_CMD); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void phytium_mci_track_cmd_data(struct phytium_mci_host *host, + struct mmc_command *cmd, + struct mmc_data *data) +{ + if (host->error) + dev_dbg(host->dev, "%s: cmd=%d arg=%08X; host->error=0x%08X\n", + __func__, cmd->opcode, cmd->arg, host->error); +} + +static void phytium_mci_request_done(struct phytium_mci_host *host, struct mmc_request *mrq) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + host->mrq = NULL; + if (host->cmd) + host->cmd = NULL; + spin_unlock_irqrestore(&host->lock, flags); + phytium_mci_track_cmd_data(host, mrq->cmd, mrq->data); + + if (mrq->data) + phytium_mci_unprepare_data(host, mrq); + + mmc_request_done(host->mmc, mrq); +} + +static bool phytium_mci_cmd_done(struct phytium_mci_host *host, int events, + struct mmc_request *mrq, struct mmc_command *cmd) +{ + bool done = false; + unsigned long flags; + u32 *rsp = cmd->resp; + + if (!(events & (MCI_RAW_INTS_RCRC | MCI_RAW_INTS_RE | MCI_RAW_INTS_CMD | + MCI_RAW_INTS_RTO | MCI_INT_MASK_HTO))) { + dev_err(host->dev, "No interrupt generation:h%x\n", events); + return done; + } + + spin_lock_irqsave(&host->lock, flags); + done = !host->cmd; + host->cmd = NULL; + if (done) { + spin_unlock_irqrestore(&host->lock, flags); + return true; + } + sdr_clr_bits(host->base + MCI_INT_MASK, cmd_ints_mask); + spin_unlock_irqrestore(&host->lock, flags); + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) { + rsp[3] = readl(host->base + MCI_RESP0); + rsp[2] = readl(host->base + MCI_RESP1); + rsp[1] = readl(host->base + MCI_RESP2); + rsp[0] = readl(host->base + MCI_RESP3); + } else { + rsp[0] = readl(host->base + MCI_RESP0); + } + + if (cmd->opcode == SD_SEND_RELATIVE_ADDR) + host->current_rca = rsp[0] & 0xFFFF0000; + } + if (!(events & (MCI_RAW_INTS_CMD | MCI_INT_MASK_HTO))) { + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE) && (events & MCI_RAW_INTS_RTO) + && readl(host->base + MCI_CARD_DETECT)) { + cmd->error = -ENOMEDIUM; + rsp[0] = 0; + } else if (events & MCI_RAW_INTS_RTO || + (cmd->opcode != MMC_SEND_TUNING_BLOCK && + cmd->opcode != MMC_SEND_TUNING_BLOCK_HS200)) { + cmd->error = -ETIMEDOUT; + } else if (events & MCI_RAW_INTS_RCRC) { + cmd->error = -EILSEQ; + } else { + cmd->error = -ETIMEDOUT; + } + } + phytium_mci_cmd_next(host, mrq, cmd); + return true; +} + +static void phytium_mci_start_command(struct phytium_mci_host *host, + struct mmc_request *mrq, + struct mmc_command *cmd) +{ + u32 rawcmd; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + WARN_ON(host->cmd); + host->cmd = cmd; + cmd->error = 0; + writel(0xffffe, host->base + MCI_RAW_INTS); + + rawcmd = phytium_mci_cmd_prepare_raw_cmd(host, mrq, cmd); + spin_unlock_irqrestore(&host->lock, flags); + + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE) && readl(host->base + MCI_CARD_DETECT)) { + phytium_mci_cmd_done(host, MCI_RAW_INTS_RTO, mrq, cmd); + return; + } + + spin_lock_irqsave(&host->lock, flags); + sdr_set_bits(host->base + MCI_INT_MASK, cmd_ints_mask); + writel(cmd->arg, host->base + MCI_CMDARG); + writel(rawcmd, host->base + MCI_CMD); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void +phytium_mci_cmd_next(struct phytium_mci_host *host, struct mmc_request *mrq, + struct mmc_command *cmd) +{ + if ((cmd->error && !(cmd->opcode == MMC_SEND_TUNING_BLOCK || + cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200)) || + (mrq->sbc && mrq->sbc->error)) { + phytium_mci_request_done(host, mrq); + } else if (cmd == mrq->sbc) { + if ((mrq->cmd->opcode == MMC_READ_MULTIPLE_BLOCK) || + (mrq->cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) || + (mrq->cmd->opcode == MMC_READ_SINGLE_BLOCK) || + (mrq->cmd->opcode == MMC_WRITE_BLOCK)) { + dev_dbg(host->dev, "%s %d:sbc done and next cmd :%d length:%d\n", + __func__, __LINE__, mrq->cmd->opcode, mrq->data->sg->length); + phytium_mci_prepare_data(host, mrq); + if (host->is_use_dma) + host->adtc_type = BLOCK_RW_ADTC; + else + host->adtc_type = COMMOM_ADTC; + phytium_mci_start_data(host, mrq, mrq->cmd, mrq->data); + } else { + dev_err(host->dev, "%s %d:ERROR: cmd %d followers the SBC\n", + __func__, __LINE__, cmd->opcode); + } + } else if (!cmd->data) { + phytium_mci_request_done(host, mrq); + } +} + +static void phytium_mci_ops_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct phytium_mci_host *host = mmc_priv(mmc); + u32 data; + int rc; + + host->error = 0; + WARN_ON(host->mrq); + host->mrq = mrq; + + rc = readl_relaxed_poll_timeout(host->base + MCI_STATUS, + data, + !(data & MCI_STATUS_CARD_BUSY), + 0, 2000 * 1000); + if (rc == -ETIMEDOUT) + pr_debug("%s %d, timeout mci_status: 0x%08x\n", __func__, __LINE__, data); + + dev_dbg(host->dev, "%s %d: cmd:%d arg:0x%x\n", __func__, __LINE__, + mrq->cmd->opcode, mrq->cmd->arg); + + if (host->is_device_x100 && mrq->sbc && mrq->cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) { + phytium_mci_start_write_multiple_non_dma(mmc, mrq); + return; + } + + if (mrq->sbc) { + phytium_mci_start_command(host, mrq, mrq->sbc); + return; + } + if (mrq->data) { + phytium_mci_prepare_data(host, mrq); + + if ((mrq->data->sg->length >= 512) && host->is_use_dma && + ((mrq->cmd->opcode == MMC_READ_MULTIPLE_BLOCK) || + (mrq->cmd->opcode == MMC_READ_SINGLE_BLOCK) || + (mrq->cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) || + (mrq->cmd->opcode == MMC_WRITE_BLOCK) || + (mrq->cmd->opcode == SD_IO_RW_EXTENDED))) + + host->adtc_type = BLOCK_RW_ADTC; + else + host->adtc_type = COMMOM_ADTC; + + phytium_mci_start_data(host, mrq, mrq->cmd, mrq->data); + return; + } + phytium_mci_start_command(host, mrq, mrq->cmd); +} + +static void phytium_mci_pre_req(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct phytium_mci_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (!data) + return; + + phytium_mci_prepare_data(host, mrq); + data->host_cookie |= MCI_ASYNC_FLAG; +} + +static void phytium_mci_post_req(struct mmc_host *mmc, struct mmc_request *mrq, + int err) +{ + struct phytium_mci_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (!data) + return; + + if (data->host_cookie & MCI_ASYNC_FLAG) { + data->host_cookie &= ~MCI_ASYNC_FLAG; + phytium_mci_unprepare_data(host, mrq); + } +} + +static void phytium_mci_data_read_without_dma(struct phytium_mci_host *host, + struct mmc_data *data) +{ + u32 length, i, data_val, dma_len, tmp = 0; + u32 *virt_addr; + unsigned long flags; + struct scatterlist *sg; + + length = data->blocks * data->blksz; + + if (mmc_get_dma_dir(data) == DMA_FROM_DEVICE) { + spin_lock_irqsave(&host->lock, flags); + if (data->host_cookie & MCI_ASYNC_FLAG) { + tmp = MCI_ASYNC_FLAG; + phytium_mci_post_req(host->mmc, data->mrq, 0); + } else { + phytium_mci_unprepare_data(host, data->mrq); + } + + for_each_sg(data->sg, sg, data->sg_count, i) { + dma_len = sg_dma_len(sg); + virt_addr = sg_virt(data->sg); + + for (i = 0; i < (dma_len / 4); i++) { + data_val = readl(host->base + MCI_DATA); + memcpy(virt_addr, &data_val, 4); + ++virt_addr; + } + } + + if (tmp & MCI_ASYNC_FLAG) + phytium_mci_pre_req(host->mmc, data->mrq); + else + phytium_mci_prepare_data(host, data->mrq); + + spin_unlock_irqrestore(&host->lock, flags); + } + data->bytes_xfered = length; +} + +static void phytium_mci_data_xfer_next(struct phytium_mci_host *host, + struct mmc_request *mrq, + struct mmc_data *data) +{ + if (mmc_op_multi(mrq->cmd->opcode) && mrq->stop && + (data->error || !mrq->sbc)) { + while ((readl(host->base + MCI_STATUS) & (MCI_STATUS_CARD_BUSY))) + cpu_relax(); + phytium_mci_start_command(host, mrq, mrq->stop); + } else { + phytium_mci_request_done(host, mrq); + } +} + +static bool phytium_mci_data_xfer_done(struct phytium_mci_host *host, u32 events, + struct mmc_request *mrq, struct mmc_data *data) +{ + unsigned long flags; + bool done; + + unsigned int check_data = events & (MCI_RAW_INTS_DTO | MCI_RAW_INTS_RCRC | + MCI_RAW_INTS_DCRC | MCI_RAW_INTS_RE | + MCI_RAW_INTS_DRTO | MCI_RAW_INTS_EBE | + MCI_DMAC_STATUS_AIS | MCI_DMAC_STATUS_DU | + MCI_RAW_INTS_SBE_BCI | MCI_INT_MASK_RTO); + + spin_lock_irqsave(&host->lock, flags); + done = !host->data; + + if (check_data || host->data) + host->data = NULL; + spin_unlock_irqrestore(&host->lock, flags); + + if (done) + return true; + if (check_data) { + spin_lock_irqsave(&host->lock, flags); + sdr_clr_bits(host->base + MCI_DMAC_INT_ENA, dmac_ints_mask); + sdr_clr_bits(host->base + MCI_INT_MASK, data_ints_mask); + /* Stop the IDMAC running */ + sdr_clr_bits(host->base + MCI_BUS_MODE, MCI_BUS_MODE_DE); + dev_dbg(host->dev, "DMA stop\n"); + spin_unlock_irqrestore(&host->lock, flags); + + if (events & MCI_RAW_INTS_DTO) { + if (!host->is_use_dma || + (host->is_use_dma && host->adtc_type == COMMOM_ADTC && + (mrq->cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC)) + phytium_mci_data_read_without_dma(host, data); + else + data->bytes_xfered = data->blocks * data->blksz; + } else { + data->bytes_xfered = 0; + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE) + && readl(host->base + MCI_CARD_DETECT) + && (events & cmd_err_ints_mask)) { + data->error = -ENOMEDIUM; + data->mrq->cmd->error = -ENOMEDIUM; + } else if (events & (MCI_RAW_INTS_DCRC | MCI_RAW_INTS_EBE | + MCI_RAW_INTS_SBE_BCI)) { + data->error = -EILSEQ; + host->cmd = NULL; + } else { + data->error = -ETIMEDOUT; + host->cmd = NULL; + } + } + + phytium_mci_data_xfer_next(host, mrq, data); + done = true; + } + return done; +} + +static int phytium_mci_card_busy(struct mmc_host *mmc) +{ + struct phytium_mci_host *host = mmc_priv(mmc); + u32 status; + + status = readl(host->base + MCI_STATUS); + + return !!(status & MCI_STATUS_CARD_BUSY); +} + +static void __phytium_mci_enable_sdio_irq(struct phytium_mci_host *host, int enable) +{ + if (enable) + sdr_set_bits(host->base + MCI_INT_MASK, MCI_INT_MASK_SDIO); + else + sdr_clr_bits(host->base + MCI_INT_MASK, MCI_INT_MASK_SDIO); +} + +static void phytium_mci_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct phytium_mci_host *host = mmc_priv(mmc); + + __phytium_mci_enable_sdio_irq(host, enable); +} + +static void hotplug_timer_func(struct timer_list *t) +{ + struct phytium_mci_host *host; + u32 status; + + host = from_timer(host, t, hotplug_timer); + if (!host) + return; + + status = readl(host->base + MCI_CARD_DETECT); + + if (status & 0x1) { + if (host->mmc->card) { + cancel_delayed_work(&host->mmc->detect); + mmc_detect_change(host->mmc, msecs_to_jiffies(100)); + } + } else { + cancel_delayed_work(&host->mmc->detect); + mmc_detect_change(host->mmc, msecs_to_jiffies(200)); + } +} + +static int phytium_mci_err_irq(struct phytium_mci_host *host, u32 dmac_events, u32 events) +{ + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + + mrq = host->mrq; + cmd = host->cmd; + data = host->data; + + if (cmd && (cmd == mrq->sbc)) { + phytium_mci_cmd_done(host, MCI_RAW_INTS_RTO, mrq, mrq->sbc); + } else if (cmd && (cmd == mrq->stop)) { + phytium_mci_cmd_done(host, MCI_RAW_INTS_RTO, mrq, mrq->stop); + } else if (data) { + data->error = -ETIMEDOUT; + if ((data->flags & MMC_DATA_READ) == MMC_DATA_READ || + (data->flags & MMC_DATA_WRITE) == MMC_DATA_WRITE) + phytium_mci_data_xfer_done(host, events | dmac_events, mrq, data); + } else if (cmd) { + phytium_mci_cmd_done(host, MCI_RAW_INTS_RTO, mrq, mrq->cmd); + } + + return 0; +} + +static irqreturn_t phytium_mci_irq(int irq, void *dev_id) +{ + struct phytium_mci_host *host = (struct phytium_mci_host *) dev_id; + unsigned long flags; + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + u32 events, event_mask, dmac_events, dmac_evt_mask; + + if (!host) + return IRQ_NONE; + writel(0, host->base + 0xfd0); + + spin_lock_irqsave(&host->lock, flags); + events = readl(host->base + MCI_RAW_INTS); + dmac_events = readl(host->base + MCI_DMAC_STATUS); + event_mask = readl(host->base + MCI_INT_MASK); + dmac_evt_mask = readl(host->base + MCI_DMAC_INT_ENA); + if ((!events) && (!(dmac_events&0x1fff))) { + spin_unlock_irqrestore(&host->lock, flags); + return IRQ_NONE; + } + dev_dbg(host->dev, "%s:events:%x,mask:0x%x,dmac_events:%x,dmac_mask:0x%x,cmd:%d\n", + __func__, events, event_mask, dmac_events, dmac_evt_mask, + host->mrq ? host->mrq->cmd->opcode : 255); + + mrq = host->mrq; + cmd = host->cmd; + data = host->data; + + if ((events & event_mask) & MCI_RAW_INTS_SDIO) + __phytium_mci_enable_sdio_irq(host, 0); + + writel((events & event_mask), host->base + MCI_RAW_INTS); + writel(dmac_events, host->base + MCI_DMAC_STATUS); + spin_unlock_irqrestore(&host->lock, flags); + + if ((events & event_mask) & MCI_RAW_INTS_SDIO) + sdio_signal_irq(host->mmc); + + if (((events & event_mask) == 0) && ((dmac_evt_mask & dmac_events) == 0)) + goto irq_out; + + if (((events & event_mask) & MCI_RAW_INTS_CD) && + !(host->mmc->caps & MMC_CAP_NONREMOVABLE)) { + mod_timer(&host->hotplug_timer, jiffies + usecs_to_jiffies(20000)); + dev_dbg(host->dev, "sd status changed here ! status:[%d] [%s %d]", + readl(host->base + MCI_CARD_DETECT), __func__, __LINE__); + + if ((events & event_mask) == MCI_RAW_INTS_CD) + goto irq_out; + } + + if (!mrq) { + if (events & MCI_RAW_INTS_HLE) + dev_dbg(host->dev, + "%s: MRQ=NULL and HW write locked, events=%08x,event_mask=%08x\n", + __func__, events, event_mask); + else + dev_dbg(host->dev, "%s: MRQ=NULL events:%08X evt_mask=%08X,sd_status:%d\n", + __func__, events, event_mask, readl(host->base + MCI_CARD_DETECT)); + goto irq_out; + } + + if ((dmac_events & dmac_err_ints_mask) || (events & cmd_err_ints_mask)) { + dev_dbg(host->dev, "ERR:events:%x,mask:0x%x,dmac_evts:%x,dmac_mask:0x%x,cmd:%d\n", + events, event_mask, dmac_events, dmac_evt_mask, mrq->cmd->opcode); + phytium_mci_err_irq(host, dmac_events & dmac_err_ints_mask, + events & cmd_err_ints_mask); + goto irq_out; + } + + if (cmd && (events & MCI_MASKED_INTS_DTO) && (events & MCI_MASKED_INTS_CMD)) { + phytium_mci_cmd_done(host, events, mrq, cmd); + phytium_mci_data_xfer_done(host, (events & data_ints_mask) | + (dmac_events & dmac_ints_mask), mrq, data); + } else if (cmd && (events & MCI_MASKED_INTS_CMD || + ((events & MCI_INT_MASK_HTO) && (cmd->opcode == SD_SWITCH_VOLTAGE)))) { + phytium_mci_cmd_done(host, events, mrq, cmd); + } else if (events & MCI_MASKED_INTS_DTO) { + phytium_mci_data_xfer_done(host, (events & data_ints_mask) | + (dmac_events & dmac_ints_mask), mrq, data); + } + +irq_out: + return IRQ_HANDLED; +} + +static void phytium_mci_init_hw(struct phytium_mci_host *host) +{ + u32 val; + int uhs_reg_value = 0x502; + + writel(MCI_SET_FIFOTH(0x2, 0x7, 0x100), host->base + MCI_FIFOTH); + writel(0x800001, host->base + MCI_CARD_THRCTL); + sdr_clr_bits(host->base + MCI_CLKENA, MCI_CLKENA_CCLK_ENABLE); + phytium_mci_update_external_clk(host, uhs_reg_value); + + sdr_set_bits(host->base + MCI_PWREN, MCI_PWREN_ENABLE); + sdr_set_bits(host->base + MCI_CLKENA, MCI_CLKENA_CCLK_ENABLE); + sdr_set_bits(host->base + MCI_UHS_REG_EXT, MCI_EXT_CLK_ENABLE); + sdr_clr_bits(host->base + MCI_UHS_REG, MCI_UHS_REG_VOLT); + + phytium_mci_reset_hw(host); + + if (host->mmc->caps & MMC_CAP_NONREMOVABLE) + sdr_set_bits(host->base + MCI_CARD_RESET, MCI_CARD_RESET_ENABLE); + else + sdr_clr_bits(host->base + MCI_CARD_RESET, MCI_CARD_RESET_ENABLE); + + writel(0, host->base + MCI_INT_MASK); + val = readl(host->base + MCI_RAW_INTS); + writel(val, host->base + MCI_RAW_INTS); + writel(0, host->base + MCI_DMAC_INT_ENA); + val = readl(host->base + MCI_DMAC_STATUS); + writel(val, host->base + MCI_DMAC_STATUS); + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE)) + writel(MCI_INT_MASK_CD, host->base + MCI_INT_MASK); + + sdr_set_bits(host->base + MCI_CNTRL, MCI_CNTRL_INT_ENABLE | + MCI_CNTRL_USE_INTERNAL_DMAC); + + writel(0xFFFFFFFF, host->base + MCI_TMOUT); + dev_info(host->dev, "init hardware done!"); + +} + +void phytium_mci_deinit_hw(struct phytium_mci_host *host) +{ + u32 val; + + sdr_clr_bits(host->base + MCI_PWREN, MCI_PWREN_ENABLE); + sdr_clr_bits(host->base + MCI_CLKENA, MCI_CLKENA_CCLK_ENABLE); + sdr_clr_bits(host->base + MCI_UHS_REG_EXT, MCI_EXT_CLK_ENABLE); + sdr_clr_bits(host->base + MCI_UHS_REG, MCI_UHS_REG_VOLT); + writel(0, host->base + MCI_INT_MASK); + val = readl(host->base + MCI_RAW_INTS); + writel(val, host->base + MCI_RAW_INTS); + writel(0, host->base + MCI_DMAC_INT_ENA); + val = readl(host->base + MCI_DMAC_STATUS); + writel(val, host->base + MCI_DMAC_STATUS); + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE)) + writel(MCI_INT_MASK_CD, host->base + MCI_INT_MASK); +} +EXPORT_SYMBOL_GPL(phytium_mci_deinit_hw); + +static void phytium_mci_adma_reset(struct phytium_mci_host *host) +{ + u32 bmod = readl(host->base + MCI_BUS_MODE); + + bmod |= MCI_BUS_MODE_SWR; + writel(bmod, host->base + MCI_BUS_MODE); +} + +static void phytium_mci_init_adma_table(struct phytium_mci_host *host, + struct phytium_mci_dma *dma) +{ + struct phytium_adma2_64_desc *adma_table = dma->adma_table; + dma_addr_t dma_addr; + int i; + + memset(adma_table, 0, sizeof(struct phytium_adma2_64_desc) * MAX_BD_NUM); + + for (i = 0; i < (MAX_BD_NUM - 1); i++) { + dma_addr = dma->adma_addr + sizeof(*adma_table) * (i + 1); + adma_table[i].desc_lo = lower_32_bits(dma_addr); + adma_table[i].desc_hi = upper_32_bits(dma_addr); + adma_table[i].attribute = 0; + adma_table[i].NON1 = 0; + adma_table[i].len = 0; + adma_table[i].NON2 = 0; + } + + phytium_mci_adma_reset(host); +} + +static void phytium_mci_set_buswidth(struct phytium_mci_host *host, u32 width) +{ + u32 val; + + switch (width) { + case MMC_BUS_WIDTH_1: + val = MCI_BUS_1BITS; + break; + + case MMC_BUS_WIDTH_4: + val = MCI_BUS_4BITS; + break; + + case MMC_BUS_WIDTH_8: + val = MCI_BUS_8BITS; + break; + default: + val = MCI_BUS_4BITS; + break; + } + writel(val, host->base + MCI_CTYPE); + dev_dbg(host->dev, "Bus Width = %d, set value:0x%x\n", width, val); +} + +static void phytium_mci_ops_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct phytium_mci_host *host = mmc_priv(mmc); + + if (ios->timing == MMC_TIMING_MMC_DDR52 || ios->timing == MMC_TIMING_UHS_DDR50) + sdr_set_bits(host->base + MCI_UHS_REG, MCI_UHS_REG_DDR); + else + sdr_clr_bits(host->base + MCI_UHS_REG, MCI_UHS_REG_DDR); + + phytium_mci_set_buswidth(host, ios->bus_width); + + switch (ios->power_mode) { + case MMC_POWER_UP: + set_bit(MCI_CARD_NEED_INIT, &host->flags); + writel(MCI_POWER_ON, host->base + MCI_PWREN); + break; + + case MMC_POWER_ON: + break; + + case MMC_POWER_OFF: + writel(MCI_POWER_OFF, host->base + MCI_PWREN); + break; + + default: + break; + } + phytium_mci_set_clk(host, ios); +} + +static void phytium_mci_ack_sdio_irq(struct mmc_host *mmc) +{ + unsigned long flags; + struct phytium_mci_host *host = mmc_priv(mmc); + + spin_lock_irqsave(&host->lock, flags); + __phytium_mci_enable_sdio_irq(host, 1); + spin_unlock_irqrestore(&host->lock, flags); +} + +static int phytium_mci_get_cd(struct mmc_host *mmc) +{ + struct phytium_mci_host *host = mmc_priv(mmc); + u32 status; + + if (mmc->caps & MMC_CAP_NONREMOVABLE) + return 1; + + status = readl(host->base + MCI_CARD_DETECT); + + if ((status & 0x1) == 0x1) + return 0; + + return 1; +} + +static int phytium_mci_ops_switch_volt(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct phytium_mci_host *host = mmc_priv(mmc); + unsigned int is_voltage_180 = 0; + + is_voltage_180 = readl(host->base + MCI_UHS_REG); + if ((mmc->caps & MMC_CAP_NONREMOVABLE) && (ios->signal_voltage != MMC_SIGNAL_VOLTAGE_180)) + return -EINVAL; + + if ((ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) && (is_voltage_180 & 0x1)) + sdr_clr_bits(host->base + MCI_UHS_REG, MCI_UHS_REG_VOLT); + else if ((ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180) && (!(is_voltage_180 & 0x1))) + sdr_set_bits(host->base + MCI_UHS_REG, MCI_UHS_REG_VOLT); + else if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_120) + return -EINVAL; + return 0; +} + +static void phytium_mci_hw_reset(struct mmc_host *mmc) +{ + struct phytium_mci_host *host = mmc_priv(mmc); + u32 reset_flag; + + if (host->is_use_dma) { + reset_flag = MCI_CNTRL_FIFO_RESET | MCI_CNTRL_DMA_RESET; + phytium_mci_adma_reset(host); + sdr_set_bits(host->base + MCI_CNTRL, reset_flag); + } else { + reset_flag = MCI_CNTRL_FIFO_RESET; + sdr_set_bits(host->base + MCI_CNTRL, reset_flag); + } + + while (readl(host->base + MCI_CNTRL) & reset_flag) + cpu_relax(); + + sdr_clr_bits(host->base + MCI_CARD_RESET, MCI_CARD_RESET_ENABLE); + udelay(5); + sdr_set_bits(host->base + MCI_CARD_RESET, MCI_CARD_RESET_ENABLE); + usleep_range(200, 300); +} + +#ifdef CONFIG_PM_SLEEP +int phytium_mci_suspend(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct phytium_mci_host *host = mmc_priv(mmc); + + phytium_mci_deinit_hw(host); + return 0; +} +EXPORT_SYMBOL(phytium_mci_suspend); + +int phytium_mci_resume(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct phytium_mci_host *host = mmc_priv(mmc); + + phytium_mci_init_hw(host); + return 0; +} +EXPORT_SYMBOL(phytium_mci_resume); + +#endif + +#ifdef CONFIG_PM +int phytium_mci_runtime_suspend(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct phytium_mci_host *host = mmc_priv(mmc); + + phytium_mci_deinit_hw(host); + return 0; +} +EXPORT_SYMBOL(phytium_mci_runtime_suspend); + +int phytium_mci_runtime_resume(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct phytium_mci_host *host = mmc_priv(mmc); + + phytium_mci_init_hw(host); + return 0; +} +EXPORT_SYMBOL(phytium_mci_runtime_resume); + +#endif + +static struct mmc_host_ops phytium_mci_ops = { + .post_req = phytium_mci_post_req, + .pre_req = phytium_mci_pre_req, + .request = phytium_mci_ops_request, + .set_ios = phytium_mci_ops_set_ios, + .get_cd = phytium_mci_get_cd, + .enable_sdio_irq = phytium_mci_enable_sdio_irq, + .ack_sdio_irq = phytium_mci_ack_sdio_irq, + .card_busy = phytium_mci_card_busy, + .start_signal_voltage_switch = phytium_mci_ops_switch_volt, + .card_hw_reset = phytium_mci_hw_reset, +}; + +int phytium_mci_common_probe(struct phytium_mci_host *host) +{ + struct mmc_host *mmc = host->mmc; + struct device *dev = host->dev; + int ret; + + dma_set_mask(dev, DMA_BIT_MASK(64)); + dma_set_coherent_mask(dev, DMA_BIT_MASK(64)); + + timer_setup(&host->hotplug_timer, hotplug_timer_func, 0); + + mmc->f_min = MCI_F_MIN; + if (!mmc->f_max) + mmc->f_max = MCI_F_MAX; + + mmc->ops = &phytium_mci_ops; + mmc->ocr_avail_sdio = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->ocr_avail_sd = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->ocr_avail_mmc = MMC_VDD_165_195; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + mmc->caps |= host->caps; + + if (mmc->caps & MMC_CAP_SDIO_IRQ) { + mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD; + dev_dbg(host->dev, "%s %d: MMC_CAP_SDIO_IRQ\n", __func__, __LINE__); + } + mmc->caps2 |= host->caps2; + if (host->is_use_dma) { + /* MMC core transfer sizes tunable parameters */ + mmc->max_segs = MAX_BD_NUM; + mmc->max_seg_size = 4 * 1024; + mmc->max_blk_size = 512; + mmc->max_req_size = 512 * 1024; + mmc->max_blk_count = mmc->max_req_size / 512; + host->dma.adma_table = dma_alloc_coherent(host->dev, + MAX_BD_NUM * + sizeof(struct phytium_adma2_64_desc), + &host->dma.adma_addr, GFP_KERNEL); + if (!host->dma.adma_table) + return MCI_REALEASE_MEM; + + host->dma.desc_sz = ADMA2_64_DESC_SZ; + phytium_mci_init_adma_table(host, &host->dma); + } else { + mmc->max_segs = MAX_BD_NUM; + mmc->max_seg_size = 4 * 1024; + mmc->max_blk_size = 512; + mmc->max_req_size = 4 * 512; + mmc->max_blk_count = mmc->max_req_size / 512; + } + + spin_lock_init(&host->lock); + + phytium_mci_init_hw(host); + ret = devm_request_irq(host->dev, host->irq, phytium_mci_irq, + host->irq_flags, "phytium-mci", host); + + if (ret) + return ret; + + ret = mmc_add_host(mmc); + + if (ret) { + dev_err(host->dev, "%s %d: mmc add host!\n", __func__, __LINE__); + return ret; + } + return 0; +} +EXPORT_SYMBOL(phytium_mci_common_probe); + +MODULE_DESCRIPTION("Phytium Multimedia Card Interface driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cheng Quan "); diff --git a/drivers/mmc/host/phytium-mci.h b/drivers/mmc/host/phytium-mci.h new file mode 100644 index 0000000000000000000000000000000000000000..8006fb4f6340ed40bed9a17be2d4301f45a19410 --- /dev/null +++ b/drivers/mmc/host/phytium-mci.h @@ -0,0 +1,355 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for Phytium Multimedia Card Interface + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_MCI_H +#define __PHYTIUM_MCI_H + +#include +#include +#include +#include +#include +#include +#include + +/*------------------------------------------------------*/ +/* Common Definition */ +/*------------------------------------------------------*/ +#define MAX_BD_NUM 128 +#define SD_BLOCK_SIZE 512 + +#define MCI_BUS_1BITS 0x0 +#define MCI_BUS_4BITS 0x1 +#define MCI_BUS_8BITS (0x1 << 16) + +#define MCI_SD_DRV_VALUE 0 +#define MCI_SD_SAMP_VALUE_MAX 0 +#define MCI_SD_SAMP_VALUE_MIN 0 + +#define MCI_TIMEOUT_CMD_VALUE 0xFFFFFFFF +#define MCI_POWER_ON 1 +#define MCI_POWER_OFF 0 + +#define MCI_PREPARE_FLAG (0x1 << 0) +#define MCI_ASYNC_FLAG (0x1 << 1) +#define MCI_MMAP_FLAG (0x1 << 2) + +#define MCI_CMD_TIMEOUT (HZ/10 * 50) /* 100ms x5 */ +#define MCI_DATA_TIMEOUT (HZ * 10) /* 1000ms x5 */ + +#define MCI_CMD_TYPE_ADTC 0x2 + +#define MCI_F_MIN 400000 +#define MCI_F_MAX 50000000 + +#define MCI_CLK 1200000000 +#define MCI_REALEASE_MEM 0x1 +#define MCI_MAX_FIFO_CNT 0x800 + +/* FIFOTH register defines */ +#define MCI_SET_FIFOTH(m, r, t) (((m) & 0x7) << 28 | \ + ((r) & 0xFFF) << 16 | ((t) & 0xFFF)) +/* Card read threshold */ +#define MCI_SET_THLD(v, x) (((v) & 0xFFF) << 16 | (x)) +#define MCI_CARD_WR_THR_EN BIT(2) +#define MCI_CARD_RD_THR_EN BIT(0) + +/*----------------------------------------------------------------------*/ +/* Register Offset */ +/*----------------------------------------------------------------------*/ +#define MCI_CNTRL 0x00 /* the controller config reg */ +#define MCI_PWREN 0x04 /* the power enable reg */ +#define MCI_CLKDIV 0x08 /* the clock divider reg */ +#define MCI_CLKENA 0x10 /* the clock enable reg */ +#define MCI_TMOUT 0x14 /* the timeout reg */ +#define MCI_CTYPE 0x18 /* the card type reg */ +#define MCI_BLKSIZ 0x1C /* the block size reg */ +#define MCI_BYTCNT 0x20 /* the byte count reg */ +#define MCI_INT_MASK 0x24 /* the interrupt mask reg */ +#define MCI_CMDARG 0x28 /* the command argument reg */ +#define MCI_CMD 0x2C /* the command reg */ +#define MCI_RESP0 0x30 /* the response reg0 */ +#define MCI_RESP1 0x34 /* the response reg1 */ +#define MCI_RESP2 0x38 /* the response reg2 */ +#define MCI_RESP3 0X3C /* the response reg3 */ +#define MCI_MASKED_INTS 0x40 /* the masked interrupt status reg */ +#define MCI_RAW_INTS 0x44 /* the raw interrupt status reg */ +#define MCI_STATUS 0x48 /* the status reg */ +#define MCI_FIFOTH 0x4C /* the FIFO threshold watermark reg */ +#define MCI_CARD_DETECT 0x50 /* the card detect reg */ +#define MCI_CARD_WRTPRT 0x54 /* the card write protect reg */ +#define MCI_CCLK_RDY 0x58 /* first div is ready? 1:ready,0:not ready*/ +#define MCI_TRAN_CARD_CNT 0x5C /* the transferred CIU card byte count reg */ +#define MCI_TRAN_FIFO_CNT 0x60 /* the transferred host to FIFO byte count reg */ +#define MCI_DEBNCE 0x64 /* the debounce count reg */ +#define MCI_UID 0x68 /* the user ID reg */ +#define MCI_VID 0x6C /* the controller version ID reg */ +#define MCI_HWCONF 0x70 /* the hardware configuration reg */ +#define MCI_UHS_REG 0x74 /* the UHS-I reg */ +#define MCI_CARD_RESET 0x78 /* the card reset reg */ +#define MCI_BUS_MODE 0x80 /* the bus mode reg */ +#define MCI_DESC_LIST_ADDRL 0x88 /* the descriptor list low base address reg */ +#define MCI_DESC_LIST_ADDRH 0x8C /* the descriptor list high base address reg */ +#define MCI_DMAC_STATUS 0x90 /* the internal DMAC status reg */ +#define MCI_DMAC_INT_ENA 0x94 /* the internal DMAC interrupt enable reg */ +#define MCI_CUR_DESC_ADDRL 0x98 /* the current host descriptor low address reg */ +#define MCI_CUR_DESC_ADDRH 0x9C /* the current host descriptor high address reg */ +#define MCI_CUR_BUF_ADDRL 0xA0 /* the current buffer low address reg */ +#define MCI_CUR_BUF_ADDRH 0xA4 /* the current buffer high address reg */ +#define MCI_CARD_THRCTL 0x100 /* the card threshold control reg */ +#define MCI_UHS_REG_EXT 0x108 /* the UHS register extension */ +#define MCI_EMMC_DDR_REG 0x10C /* the EMMC DDR reg */ +#define MCI_ENABLE_SHIFT 0x110 /* the enable phase shift reg */ +#define MCI_DATA 0x200 /* the data FIFO access */ + +/* Command register defines */ +#define MCI_CMD_START BIT(31) +#define MCI_CMD_USE_HOLD_REG BIT(29) +#define MCI_CMD_VOLT_SWITCH BIT(28) +#define MCI_CMD_CCS_EXP BIT(23) +#define MCI_CMD_CEATA_RD BIT(22) +#define MCI_CMD_UPD_CLK BIT(21) +#define MCI_CMD_INIT BIT(15) +#define MCI_CMD_STOP BIT(14) +#define MCI_CMD_PRV_DAT_WAIT BIT(13) +#define MCI_CMD_SEND_STOP BIT(12) +#define MCI_CMD_STRM_MODE BIT(11) +#define MCI_CMD_DAT_WR BIT(10) +#define MCI_CMD_DAT_EXP BIT(9) +#define MCI_CMD_RESP_CRC BIT(8) +#define MCI_CMD_RESP_LONG BIT(7) +#define MCI_CMD_RESP_EXP BIT(6) +#define MCI_CMD_INDX(n) ((n) & 0x1F) + +/*------------------------------------------------------*/ +/* Register Mask */ +/*------------------------------------------------------*/ +/* MCI_CNTRL mask */ +#define MCI_CNTRL_CONTROLLER_RESET (0x1 << 0) /* RW */ +#define MCI_CNTRL_FIFO_RESET (0x1 << 1) /* RW */ +#define MCI_CNTRL_DMA_RESET (0x1 << 2) /* RW */ +#define MCI_CNTRL_RES (0x1 << 3) /* */ +#define MCI_CNTRL_INT_ENABLE (0x1 << 4) /* RW */ +#define MCI_CNTRL_DMA_ENABLE (0x1 << 5) /* RW */ +#define MCI_CNTRL_READ_WAIT (0x1 << 6) /* RW */ +#define MCI_CNTRL_SEND_IRQ_RESPONSE (0x1 << 7) /* RW */ +#define MCI_CNTRL_ABORT_READ_DATA (0x1 << 8) /* RW */ +#define MCI_CNTRL_ENDIAN (0x1 << 11) /* RW */ +//#define MCI_CNTRL_CARD_VOLTAGE_A (0xF << 16) /* RW */ +//#define MCI_CNTRL_CARD_VOLTAGE_B (0xF << 20) /* RW */ +#define MCI_CNTRL_ENABLE_OD_PULLUP (0x1 << 24) /* RW */ +#define MCI_CNTRL_USE_INTERNAL_DMAC (0x1 << 25) /* RW */ + +/* MCI_PWREN mask */ +#define MCI_PWREN_ENABLE (0x1 << 0) /* RW */ + +/* MCI_CLKENA mask */ +#define MCI_CLKENA_CCLK_ENABLE (0x1 << 0) /* RW */ +#define MCI_CLKENA_CCLK_LOW_POWER (0x1 << 16) /* RW */ +#define MCI_EXT_CLK_ENABLE (0x1 << 1) + +/* MCI_INT_MASK mask */ +#define MCI_INT_MASK_CD (0x1 << 0) /* RW */ +#define MCI_INT_MASK_RE (0x1 << 1) /* RW */ +#define MCI_INT_MASK_CMD (0x1 << 2) /* RW */ +#define MCI_INT_MASK_DTO (0x1 << 3) /* RW */ +#define MCI_INT_MASK_TXDR (0x1 << 4) /* RW */ +#define MCI_INT_MASK_RXDR (0x1 << 5) /* RW */ +#define MCI_INT_MASK_RCRC (0x1 << 6) /* RW */ +#define MCI_INT_MASK_DCRC (0x1 << 7) /* RW */ +#define MCI_INT_MASK_RTO (0x1 << 8) /* RW */ +#define MCI_INT_MASK_DRTO (0x1 << 9) /* RW */ +#define MCI_INT_MASK_HTO (0x1 << 10) /* RW */ +#define MCI_INT_MASK_FRUN (0x1 << 11) /* RW */ +#define MCI_INT_MASK_HLE (0x1 << 12) /* RW */ +#define MCI_INT_MASK_SBE_BCI (0x1 << 13) /* RW */ +#define MCI_INT_MASK_ACD (0x1 << 14) /* RW */ +#define MCI_INT_MASK_EBE (0x1 << 15) /* RW */ +#define MCI_INT_MASK_SDIO (0x1 << 16) /* RW */ + +/* MCI_MASKED_INTS mask */ +#define MCI_MASKED_INTS_CD (0x1 << 0) /* RO */ +#define MCI_MASKED_INTS_RE (0x1 << 1) /* RO */ +#define MCI_MASKED_INTS_CMD (0x1 << 2) /* RO */ +#define MCI_MASKED_INTS_DTO (0x1 << 3) /* RO */ +#define MCI_MASKED_INTS_TXDR (0x1 << 4) /* RO */ +#define MCI_MASKED_INTS_RXDR (0x1 << 5) /* RO */ +#define MCI_MASKED_INTS_RCRC (0x1 << 6) /* RO */ +#define MCI_MASKED_INTS_DCRC (0x1 << 7) /* RO */ +#define MCI_MASKED_INTS_RTO (0x1 << 8) /* RO */ +#define MCI_MASKED_INTS_DRTO (0x1 << 9) /* RO */ +#define MCI_MASKED_INTS_HTO (0x1 << 10) /* RO */ +#define MCI_MASKED_INTS_FRUN (0x1 << 11) /* RO */ +#define MCI_MASKED_INTS_HLE (0x1 << 12) /* RO */ +#define MCI_MASKED_INTS_SBE_BCI (0x1 << 13) /* RO */ +#define MCI_MASKED_INTS_ACD (0x1 << 14) /* RO */ +#define MCI_MASKED_INTS_EBE (0x1 << 15) /* RO */ +#define MCI_MASKED_INTS_SDIO (0x1 << 16) /* RO */ + +/* MCI_RAW_INTS mask */ +#define MCI_RAW_INTS_CD (0x1 << 0) /* W1C */ +#define MCI_RAW_INTS_RE (0x1 << 1) /* W1C */ +#define MCI_RAW_INTS_CMD (0x1 << 2) /* W1C */ +#define MCI_RAW_INTS_DTO (0x1 << 3) /* W1C */ +#define MCI_RAW_INTS_TXDR (0x1 << 4) /* W1C */ +#define MCI_RAW_INTS_RXDR (0x1 << 5) /* W1C */ +#define MCI_RAW_INTS_RCRC (0x1 << 6) /* W1C */ +#define MCI_RAW_INTS_DCRC (0x1 << 7) /* W1C */ +#define MCI_RAW_INTS_RTO (0x1 << 8) /* W1C */ +#define MCI_RAW_INTS_DRTO (0x1 << 9) /* W1C */ +#define MCI_RAW_INTS_HTO (0x1 << 10) /* W1C */ +#define MCI_RAW_INTS_FRUN (0x1 << 11) /* W1C */ +#define MCI_RAW_INTS_HLE (0x1 << 12) /* W1C */ +#define MCI_RAW_INTS_SBE_BCI (0x1 << 13) /* W1C */ +#define MCI_RAW_INTS_ACD (0x1 << 14) /* W1C */ +#define MCI_RAW_INTS_EBE (0x1 << 15) /* W1C */ +#define MCI_RAW_INTS_SDIO (0x1 << 16) /* W1C */ + +/* MCI_STATUS mask */ +#define MCI_STATUS_FIFO_RX (0x1 << 0) /* RO */ +#define MCI_STATUS_FIFO_TX (0x1 << 1) /* RO */ +#define MCI_STATUS_FIFO_EMPTY (0x1 << 2) /* RO */ +#define MCI_STATUS_FIFO_FULL (0x1 << 3) /* RO */ +#define MCI_STATUS_CARD_STATUS (0x1 << 8) /* RO */ +#define MCI_STATUS_CARD_BUSY (0x1 << 9) /* RO */ +#define MCI_STATUS_DATA_BUSY (0x1 << 10) /* RO */ +#define MCI_STATUS_DMA_ACK (0x1 << 31) /* RO */ +#define MCI_STATUS_DMA_REQ (0x1 << 32) /* RO */ + +/* MCI_UHS_REG mask */ +#define MCI_UHS_REG_VOLT (0x1 << 0) /* RW */ +#define MCI_UHS_REG_DDR (0x1 << 16) /* RW */ + +/* MCI_CARD_RESET mask */ +#define MCI_CARD_RESET_ENABLE (0x1 << 0) /* RW */ + +/* MCI_BUS_MODE mask */ +#define MCI_BUS_MODE_SWR (0x1 << 0) /* RW */ +#define MCI_BUS_MODE_FB (0x1 << 1) /* RW */ +#define MCI_BUS_MODE_DE (0x1 << 7) /* RW */ + +/* MCI_DMAC_STATUS mask */ +#define MCI_DMAC_STATUS_TI (0x1 << 0) /* RW */ +#define MCI_DMAC_STATUS_RI (0x1 << 1) /* RW */ +#define MCI_DMAC_STATUS_FBE (0x1 << 2) /* RW */ +#define MCI_DMAC_STATUS_DU (0x1 << 4) /* RW */ +#define MCI_DMAC_STATUS_NIS (0x1 << 8) /* RW */ +#define MCI_DMAC_STATUS_AIS (0x1 << 9) /* RW */ + +/* MCI_DMAC_INT_ENA mask */ +#define MCI_DMAC_INT_ENA_TI (0x1 << 0) /* RW */ +#define MCI_DMAC_INT_ENA_RI (0x1 << 1) /* RW */ +#define MCI_DMAC_INT_ENA_FBE (0x1 << 2) /* RW */ +#define MCI_DMAC_INT_ENA_DU (0x1 << 4) /* RW */ +#define MCI_DMAC_INT_ENA_CES (0x1 << 5) /* RW */ +#define MCI_DMAC_INT_ENA_NIS (0x1 << 8) /* RW */ +#define MCI_DMAC_INT_ENA_AIS (0x1 << 9) /* RW */ + +/* MCI_CARD_THRCTL mask */ +#define MCI_CARD_THRCTL_CARDRD (0x1 << 0) /* RW */ +#define MCI_CARD_THRCTL_BUSY_CLR (0x1 << 1) /* RW */ +#define MCI_CARD_THRCTL_CARDWR (0x1 << 2) /* RW */ + +/* MCI_UHS_REG_EXT mask */ +#define MCI_UHS_REG_EXT_MMC_VOLT (0x1 << 0) /* RW */ +#define MCI_UHS_REG_EXT_CLK_ENA (0x1 << 1) /* RW */ + +/* MCI_EMMC_DDR_REG mask */ +#define MCI_EMMC_DDR_CYCLE (0x1 << 0) /* RW */ + +/*--------------------------------------*/ +/* Structure Type */ +/*--------------------------------------*/ +/* Maximum segments assuming a 512KiB maximum requisition */ +/* size and a minimum4KiB page size. */ +#define MCI_MAX_SEGS 128 +/* ADMA2 64-bit DMA descriptor size */ +#define ADMA2_64_DESC_SZ 32 + +/* Each descriptor can transfer up to 4KB of data in chained mode */ +/*ADMA2 64-bit descriptor.*/ +struct phytium_adma2_64_desc { + u32 attribute; +#define IDMAC_DES0_DIC BIT(1) +#define IDMAC_DES0_LD BIT(2) +#define IDMAC_DES0_FD BIT(3) +#define IDMAC_DES0_CH BIT(4) +#define IDMAC_DES0_ER BIT(5) +#define IDMAC_DES0_CES BIT(30) +#define IDMAC_DES0_OWN BIT(31) + u32 NON1; + u32 len; + u32 NON2; + u32 addr_lo; /* Lower 32-bits of Buffer Address Pointer 1*/ + u32 addr_hi; /* Upper 32-bits of Buffer Address Pointer 1*/ + u32 desc_lo; /* Lower 32-bits of Next Descriptor Address */ + u32 desc_hi; /* Upper 32-bits of Next Descriptor Address */ +} __packed __aligned(4); + +struct phytium_mci_dma { + struct scatterlist *sg; /* I/O scatter list */ + /* ADMA descriptor table, pointer to adma_table array */ + struct phytium_adma2_64_desc *adma_table; + /* Mapped ADMA descr. table, the physical address of adma_table array */ + dma_addr_t adma_addr; + unsigned int desc_sz; /* ADMA descriptor size */ +}; + +enum adtc_t { + COMMOM_ADTC = 0, + BLOCK_RW_ADTC = 1 +}; + +struct phytium_mci_host { + struct device *dev; + struct mmc_host *mmc; + u32 caps; + u32 caps2; + spinlock_t lock; + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + int error; + void __iomem *base; /* host base address */ + void *adma_table1; + dma_addr_t adma_addr1; + struct phytium_mci_dma dma_rx; /* dma channel */ + struct phytium_mci_dma dma_tx; /* dma channel */ + struct phytium_mci_dma dma; /* dma channel */ + u64 dma_mask; + bool vqmmc_enabled; + u32 *sg_virt_addr; + enum adtc_t adtc_type; /* 0:common adtc cmd; 1:block r/w adtc cmd;*/ + struct timer_list hotplug_timer; + struct delayed_work req_timeout; + int irq; /* host interrupt */ + u32 current_rca; /*the current rca value*/ + u32 current_ios_clk; + u32 is_use_dma; + u32 is_device_x100; + struct clk *src_clk; /* phytium_mci source clock */ + unsigned long clk_rate; + unsigned long clk_div; + unsigned long irq_flags; + unsigned long flags; +#define MCI_CARD_NEED_INIT 1 + bool use_hold; /*use hold reg*/ + bool clk_set; /*clock set function enable*/ + s32 clk_smpl_drv_25m; /*25M clk smpl & drv*/ + s32 clk_smpl_drv_50m; /*50M clk smpl & drv*/ + s32 clk_smpl_drv_66m; /*66M clk smpl & drv*/ + s32 clk_smpl_drv_100m; /*100M clk smpl & drv*/ +}; + +int phytium_mci_common_probe(struct phytium_mci_host *host); +void phytium_mci_deinit_hw(struct phytium_mci_host *host); +int phytium_mci_runtime_suspend(struct device *dev); +int phytium_mci_runtime_resume(struct device *dev); +int phytium_mci_resume(struct device *dev); +int phytium_mci_suspend(struct device *dev); + +#endif /* __PHYTIUM_MCI_HW_H */ diff --git a/drivers/mmc/host/phytium-sdci.c b/drivers/mmc/host/phytium-sdci.c new file mode 100644 index 0000000000000000000000000000000000000000..9453cc95e43018e9c85e4e66d09dc75aad47f021 --- /dev/null +++ b/drivers/mmc/host/phytium-sdci.c @@ -0,0 +1,1440 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SDCI dirver + * + * Copyright (C) 2019-2023, 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 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 +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "phytium-sdci.h" + +static const u32 cmd_ints_mask = SDCI_SDCI_NORMAL_ISER_ECC_EN | SDCI_SDCI_NORMAL_ISER_EEI_EN; +static const u32 data_ints_mask = SDCI_BD_ISER_ETRS_EN; +static const u32 err_ints_mask = SDCI_ERROR_ISER_ECTE_EN | SDCI_ERROR_ISR_CCRCE_EN | + SDCI_ERROR_ISR_CIR_EN | SDCI_ERROR_ISR_CNR_EN; + +static void hotplug_timer_func(struct timer_list *t); +static bool phytium_sdci_private_send_cmd(struct phytium_sdci_host *host, + u32 cmd, u32 resp_type, u32 arg); +static bool phytium_sdci_cmd_done(struct phytium_sdci_host *host, int events, + struct mmc_request *mrq, + struct mmc_command *cmd); +static bool phytium_sdci_data_xfer_done(struct phytium_sdci_host *host, + u32 events, struct mmc_request *mrq, + struct mmc_data *data); +static void phytium_sdci_cmd_next(struct phytium_sdci_host *host, + struct mmc_request *mrq, + struct mmc_command *cmd); + +static int phytium_sdci_cmd13_process(struct phytium_sdci_host *host, + struct mmc_request *mrq, + struct mmc_data *data, + u32 wait_timeout_ms, + u32 send_once_time_ms); + +static int phytium_sd_error(struct phytium_sdci_host *host) +{ + int temp; + + temp = readl(host->base + SDCI_NORMAL_ISR); + dev_err(host->dev, "[%s %d]SDCI_NORMAL_ISR:%x\n", __func__, __LINE__, temp); + temp = readl(host->base + SDCI_BD_ISR); + temp = readl(host->base + SDCI_ERROR_ISR); + dev_err(host->dev, "[%s %d]SDCI_ERROR_ISR:%x\n", __func__, __LINE__, temp); + temp = readl(host->base + SDCI_BD_ISR); + dev_err(host->dev, "[%s %d]SDCI_BD_ISR:%x\n", __func__, __LINE__, temp); + temp = readl(host->base + SDCI_RESP0); + dev_err(host->dev, "[%s %d]SDCI_RESP0:%x\n", __func__, __LINE__, temp); + + return 0; +} + +static void sdr_set_bits(void __iomem *reg, u32 bs) +{ + u32 val; + + val = readl(reg); + val |= bs; + + writel(val, reg); +} + +static void sdr_clr_bits(void __iomem *reg, u32 bs) +{ + u32 val; + + val = readl(reg); + val &= ~bs; + + writel(val, reg); +} + +static void phytium_sdci_reset_hw(struct phytium_sdci_host *host) +{ + sdr_set_bits(host->base + SDCI_SOFTWARE, + SDCI_SOFTWARE_SRST); + sdr_clr_bits(host->base + SDCI_SOFTWARE, + SDCI_SOFTWARE_SRST); + while (!(readl(host->base + SDCI_STATUS) & SDCI_STATUS_IDIE)) + cpu_relax(); +} + +static void phytium_sdci_prepare_data(struct phytium_sdci_host *host, + struct mmc_request *mrq) +{ + struct mmc_data *data = mrq->data; + bool read; + + read = (data->flags & MMC_DATA_READ) != 0; + data->sg_count = dma_map_sg(host->dev, data->sg, data->sg_len, + read ? DMA_FROM_DEVICE : DMA_TO_DEVICE); +} + +static void phytium_sdci_unprepare_data(struct phytium_sdci_host *host, + struct mmc_request *mrq) +{ + bool read; + struct mmc_data *data = mrq->data; + + read = (data->flags & MMC_DATA_READ) != 0; + dma_unmap_sg(host->dev, data->sg, data->sg_len, + read ? DMA_FROM_DEVICE : DMA_TO_DEVICE); +} + +static void phytium_sdci_set_clk(struct phytium_sdci_host *host, + struct mmc_ios *ios) +{ + unsigned long clk_rate; + u32 div = 0xffffffff, div_reg; + + if (ios->clock) { + clk_rate = host->clk_rate; + div = ((clk_rate / (2 * ios->clock)) - 1); + div_reg = readl(host->base + SDCI_CLOCK_D); + if (div_reg == div) + return; + writel(div, host->base + SDCI_CLOCK_D); + writel(0, host->base + SDCI_SD_DRV); + writel(5, host->base + SDCI_SD_SAMP); + + sdr_set_bits(host->base + SDCI_SOFTWARE, SDCI_SOFTWARE_SRST); + sdr_clr_bits(host->base + SDCI_SOFTWARE, SDCI_SOFTWARE_SRST); + while (!(readl(host->base + SDCI_STATUS) & SDCI_STATUS_IDIE)) + cpu_relax(); + dev_dbg(host->dev, "host->clk_rate: %ld, ios->clock: %d\n", + host->clk_rate, ios->clock); + } +} + + +static inline u32 phytium_sdci_cmd_find_resp(struct phytium_sdci_host *host, + struct mmc_request *mrq, + struct mmc_command *cmd) +{ + u32 resp; + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1: + resp = 0x2; + break; + case MMC_RSP_R1B: + resp = 0x2; + break; + case MMC_RSP_R2: + resp = 0x1; + break; + case MMC_RSP_R3: + resp = 0x3; + break; + case MMC_RSP_NONE: + default: + resp = 0x0; + break; + } + + return resp; +} + +static inline u32 phytium_sdci_cmd_prepare_raw_cmd(struct phytium_sdci_host *host, + struct mmc_request *mrq, struct mmc_command *cmd) +{ + /* + * rawcmd : + * trty << 14 | opcode << 8 | cmdw << 6 | cice << 4 | crce << 3 | resp + */ + u32 resp, rawcmd; + u32 opcode = cmd->opcode; + + resp = phytium_sdci_cmd_find_resp(host, mrq, cmd); + rawcmd = ((opcode << 8) | resp); + + if ((cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC) + rawcmd = (rawcmd | (SDCI_CMD_TYPE_ADTC << 14)); + + return rawcmd; +} + +static void +phytium_sdci_unexpected_error_handler(struct phytium_sdci_host *host, + struct mmc_request *mrq, + struct mmc_data *data, + int err_type) +{ + unsigned long flags; + int error; + + spin_lock_irqsave(&host->lock, flags); + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + spin_unlock_irqrestore(&host->lock, flags); + + if (err_type & ERR_CARD_ABSENT) { + host->mmc->detect_change = 1; + dev_dbg(host->dev, "SD is absent when send cmd:%d\n", mrq->cmd->opcode); + } + + switch (err_type) { + case ERR_CARD_ABSENT: + error = -ENOMEDIUM; + break; + case ERR_TIMEOUT: + error = -ETIMEDOUT; + break; + case ERR_CMD_RESPONED: + error = -EIO; + break; + default: + error = -ETIMEDOUT; + break; + } + + if (data) { + data->error = error; + phytium_sdci_unprepare_data(host, mrq); + + if ((data->flags & MMC_DATA_READ) == MMC_DATA_READ || + (data->flags & MMC_DATA_WRITE) == MMC_DATA_WRITE) + phytium_sdci_data_xfer_done(host, SDCI_BD_ISR_TRS_R, mrq, data); + } else { + mrq->cmd->error = error; + } + + mmc_request_done(host->mmc, mrq); +} + +static bool phytium_sdci_start_data(struct phytium_sdci_host *host, struct mmc_request *mrq, + struct mmc_command *cmd, struct mmc_data *data) +{ + bool read, res; + u32 sg_dma_addrh, sg_dma_addrl; + u32 sd_block_addrh, sd_block_addrl; + u32 temp, timeout, sd_status; + u32 block_cnt = 0; + u32 sd_block_addr = cmd->arg; + u32 private_cmd, resp_type, arg; + u32 j, dma_len; + unsigned long deadline_time; + dma_addr_t dma_address; + struct scatterlist *sg; + int ret; + + WARN_ON(host->cmd); + host->cmd = cmd; + + WARN_ON(host->data); + host->data = data; + read = data->flags & MMC_DATA_READ; + + for_each_sg(data->sg, sg, data->sg_count, j) { + writel(0, host->base + SDCI_COMMAND); + + dma_address = sg_dma_address(sg); + sg_dma_addrh = (u32) (dma_address >> 32); + sg_dma_addrl = (u32) dma_address; + + dma_len = sg_dma_len(sg); + block_cnt = (dma_len / SD_BLOCK_SIZE); + + sd_block_addrh = 0; + sd_block_addrl = sd_block_addr; + + sdr_set_bits(host->base + SDCI_SOFTWARE, SDCI_SOFTWARE_BDRST); + sdr_clr_bits(host->base + SDCI_SOFTWARE, SDCI_SOFTWARE_BDRST); + writel(block_cnt, host->base + SDCI_BLK_CNT); + + if ((mrq->data->flags & MMC_DATA_READ) == MMC_DATA_READ) { + writel(sg_dma_addrl, host->base + SDCI_BD_RX); + writel(sg_dma_addrh, host->base + SDCI_BD_RX); + writel(sd_block_addrl, host->base + SDCI_BD_RX); + writel(sd_block_addrh, host->base + SDCI_BD_RX); + timeout = 100 * block_cnt; + } else { + timeout = 250 * block_cnt; + ret = phytium_sdci_cmd13_process(host, mrq, data, timeout, 1); + if (ret != SDCI_CMD13_OK) + return false; + + writel(sg_dma_addrl, host->base + SDCI_BD_TX); + writel(sg_dma_addrh, host->base + SDCI_BD_TX); + writel(sd_block_addrl, host->base + SDCI_BD_TX); + writel(sd_block_addrh, host->base + SDCI_BD_TX); + } + + deadline_time = jiffies + msecs_to_jiffies(timeout); + + temp = readl(host->base + SDCI_BD_ISR); + if ((mrq->data->flags & MMC_DATA_READ) == MMC_DATA_READ) { + while ((temp & SDCI_BD_ISR_TRS_R) != SDCI_BD_ISR_TRS_R) { + sd_status = readl(host->base + SDCI_STATUS); + if (sd_status & SDCI_STATUS_CDSL) { + phytium_sdci_unexpected_error_handler(host, mrq, data, + ERR_CARD_ABSENT); + if (temp & SDCI_BD_ISR_DAIS) + writel(1, host->base + SDCI_BD_ISR); + return false; + } + + temp = readl(host->base + SDCI_BD_ISR); + if (time_after(jiffies, deadline_time)) { + phytium_sdci_unexpected_error_handler(host, mrq, data, + ERR_TIMEOUT); + dev_err(host->dev, + "Read Data timeout:jiffies:0x%lx,dt_jiffies:0x%lx, BD_isr_reg:0x%x,cmd:%d, REG_D0:0x%x\n", + jiffies, jiffies - deadline_time, temp, + cmd->opcode, readl(host->base + SDCI_STATUS)); + + return false; + } + } + } else { + while ((temp & SDCI_BD_ISR_TRS_W) != SDCI_BD_ISR_TRS_W) { + sd_status = readl(host->base + SDCI_STATUS); + if (sd_status & SDCI_STATUS_CDSL) { + phytium_sdci_unexpected_error_handler(host, mrq, data, + ERR_CARD_ABSENT); + dev_err(host->dev, "[%s][%d]: Card absent ! cmd(%d)\n", + __func__, __LINE__, mrq->cmd->opcode); + return false; + } + + temp = readl(host->base + SDCI_BD_ISR); + if (time_after(jiffies, deadline_time)) { + phytium_sdci_unexpected_error_handler(host, mrq, data, + ERR_TIMEOUT); + dev_err(host->dev, + "Write Date timeout: jiffies:0x%lx,dt_jiffies:0x%lx,BD_isr_reg:0x%x\n", + jiffies, jiffies - deadline_time, temp); + return false; + } + } + } + writel(1, host->base + SDCI_BD_ISR); + writel(1, host->base + SDCI_NORMAL_ISR); + sd_block_addr = sd_block_addr + block_cnt; + + if (j < (data->sg_count - 1) && 1 < block_cnt) { + private_cmd = MMC_STOP_TRANSMISSION; + resp_type = 0x2; + arg = 0; + res = phytium_sdci_private_send_cmd(host, private_cmd, + resp_type, arg); + if (!res) { + sd_status = readl(host->base + SDCI_STATUS); + if (sd_status & SDCI_STATUS_CDSL) { + phytium_sdci_unexpected_error_handler(host, mrq, data, + ERR_CARD_ABSENT); + writel(1, host->base + SDCI_BD_ISR); + dev_err(host->dev, + "[%s][%d]:Card absent ! private_cmd(%d)\n", + __func__, __LINE__, private_cmd); + } else { + phytium_sdci_unexpected_error_handler(host, mrq, data, + ERR_CMD_RESPONED); + dev_err(host->dev, + "[%s][%d] cmd(%d) response errored\n", + __func__, __LINE__, mrq->cmd->opcode); + phytium_sd_error(host); + } + writel(1, host->base + SDCI_NORMAL_ISR); + return false; + } + writel(1, host->base + SDCI_NORMAL_ISR); + } + } + + host->is_multi_rw_only_one_blkcnt = false; + + if ((cmd->opcode == MMC_READ_MULTIPLE_BLOCK && block_cnt == 1) || + (cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK && block_cnt == 1)) + host->is_multi_rw_only_one_blkcnt = true; + + phytium_sdci_cmd_done(host, SDCI_NORMAL_ISR_CC, mrq, cmd); + if ((mrq->data->flags & MMC_DATA_READ) == MMC_DATA_READ) + phytium_sdci_data_xfer_done(host, SDCI_BD_ISR_TRS_R, + mrq, data); + else + phytium_sdci_data_xfer_done(host, SDCI_BD_ISR_TRS_W, + mrq, data); + + return true; +} + +static int phytium_sdci_auto_cmd_done(struct phytium_sdci_host *host, + int events, struct mmc_command *cmd) +{ + u32 *rsp = cmd->resp; + + rsp[0] = readl(host->base + SDCI_RESP0); + + if (events & SDCI_NORMAL_ISR_CC) + cmd->error = 0; + else { + phytium_sdci_reset_hw(host); + dev_err(host->dev, + "%s: AUTO_CMD%d arg=%08X; rsp %08X; cmd_error=%d\n", + __func__, cmd->opcode, cmd->arg, rsp[0], cmd->error); + } + + return cmd->error; +} + +static void phytium_sdci_track_cmd_data(struct phytium_sdci_host *host, + struct mmc_command *cmd, + struct mmc_data *data) +{ + if (host->error) + dev_dbg(host->dev, "%s: cmd=%d arg=%08X; host->error=0x%08X\n", + __func__, cmd->opcode, cmd->arg, host->error); +} + +static void phytium_sdci_request_done(struct phytium_sdci_host *host, + struct mmc_request *mrq) +{ + unsigned long flags; + + dev_dbg(host->dev, + "%s_%d:mrq->cmd->opcode:%d, mrq->cmd->arg:0x%x resp 0x%x 0x%x 0x%x 0x%x\n", + __func__, __LINE__, mrq->cmd->opcode, mrq->cmd->arg, + mrq->cmd->resp[0], mrq->cmd->resp[1], mrq->cmd->resp[2], + mrq->cmd->resp[3]); + + spin_lock_irqsave(&host->lock, flags); + host->mrq = NULL; + spin_unlock_irqrestore(&host->lock, flags); + + phytium_sdci_track_cmd_data(host, mrq->cmd, mrq->data); + if (mrq->data && host->adtc_type != COMMOM_ADTC) + phytium_sdci_unprepare_data(host, mrq); + mmc_request_done(host->mmc, mrq); +} + +static bool +phytium_sdci_auto_command_done(struct phytium_sdci_host *host, int events, + struct mmc_request *mrq, struct mmc_command *cmd) +{ + u32 *rsp = cmd->resp; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + host->cmd = NULL; + spin_unlock_irqrestore(&host->lock, flags); + + sdr_clr_bits(host->base + SDCI_NORMAL_ISER, cmd_ints_mask); + + rsp[0] = 0x900; + phytium_sdci_request_done(host, mrq); + return true; +} + +/* returns true if command is fully handled; returns false otherwise */ +static bool phytium_sdci_cmd_done(struct phytium_sdci_host *host, int events, + struct mmc_request *mrq, + struct mmc_command *cmd) +{ + bool done = false; + bool sbc_error; + unsigned long flags; + u32 *rsp = cmd->resp; + + if (mrq->sbc && cmd == mrq->cmd && + (events & SDCI_NORMAL_ISR_CC)) + phytium_sdci_auto_cmd_done(host, events, mrq->sbc); + + sbc_error = mrq->sbc && mrq->sbc->error; + + if (!sbc_error && !(events & (SDCI_NORMAL_ISR_CC | + SDCI_NORMAL_ISR_CR | + SDCI_NORMAL_ISR_TIMEOUT))) + return done; + + spin_lock_irqsave(&host->lock, flags); + done = !host->cmd; + host->cmd = NULL; + spin_unlock_irqrestore(&host->lock, flags); + + if (done) + return true; + + sdr_clr_bits(host->base + SDCI_NORMAL_ISER, cmd_ints_mask); + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) { + rsp[0] = readl(host->base + SDCI_RESP0); + rsp[1] = readl(host->base + SDCI_RESP1); + rsp[2] = readl(host->base + SDCI_RESP2); + rsp[3] = readl(host->base + SDCI_RESP3); + } else { + rsp[0] = readl(host->base + SDCI_RESP0); + } + + if (cmd->opcode == SD_SEND_RELATIVE_ADDR) + host->current_rca = rsp[0] & 0xFFFF0000; + } + + if (!sbc_error && + !(events & SDCI_NORMAL_ISR_CC) && + (events & SDCI_NORMAL_ISR_TIMEOUT)) + cmd->error = -ETIMEDOUT; + + if (cmd->error) + dev_dbg(host->dev, + "%s: cmd=%d arg=%08X; rsp %08X; cmd_error=%d\n", + __func__, cmd->opcode, cmd->arg, rsp[0], + cmd->error); + + phytium_sdci_cmd_next(host, mrq, cmd); + + return true; +} + +static bool set_databus_width(struct phytium_sdci_host *host) +{ + bool res; + u32 cmd, resp_type, arg; + + cmd = SD_APP_SET_BUS_WIDTH; + resp_type = 0x2; + arg = 0x2; + res = phytium_sdci_private_send_cmd(host, cmd, resp_type, arg); + if (!res) + return false; + + cmd = MMC_APP_CMD; + resp_type = 0x2; + arg = host->current_rca; + res = phytium_sdci_private_send_cmd(host, cmd, resp_type, arg); + if (!res) + return false; + + return true; +} + + +static void phytium_sdci_start_command(struct phytium_sdci_host *host, + struct mmc_request *mrq, + struct mmc_command *cmd) +{ + u32 rawcmd; + struct mmc_data *data = mrq->data; + dma_addr_t dma_adtc_buf; + u32 dma_bufh, dma_bufl; + u32 block_cnt = 0; + + WARN_ON(host->cmd); + host->cmd = cmd; + + cmd->error = 0; + rawcmd = phytium_sdci_cmd_prepare_raw_cmd(host, mrq, cmd); + if (cmd->opcode == MMC_STOP_TRANSMISSION || + cmd->opcode == MMC_SEND_STATUS) + writel(1, host->base + SDCI_ERROR_ISR); + sdr_set_bits(host->base + SDCI_NORMAL_ISER, cmd_ints_mask); + writel(rawcmd, host->base + SDCI_COMMAND); + + if ((cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC) { + WARN_ON(host->data); + host->data = data; + + dma_adtc_buf = host->dma_rx.bd_addr; + dma_bufh = (u32) (dma_adtc_buf >> 32); + dma_bufl = (u32) dma_adtc_buf; + block_cnt = mrq->data->blocks; + sdr_set_bits(host->base + SDCI_BD_ISER, data_ints_mask); + writel(block_cnt, host->base + SDCI_BLK_CNT); + + if ((mrq->data->flags & MMC_DATA_READ) == MMC_DATA_READ) { + writel(dma_bufl, host->base + SDCI_BD_RX); + writel(dma_bufh, host->base + SDCI_BD_RX); + writel(cmd->arg, host->base + SDCI_BD_RX); + writel(0, host->base + SDCI_BD_RX); + } else { + writel(dma_bufl, host->base + SDCI_BD_TX); + writel(dma_bufh, host->base + SDCI_BD_TX); + writel(cmd->arg, host->base + SDCI_BD_TX); + writel(0, host->base + SDCI_BD_TX); + } + } else { + writel(cmd->arg, host->base + SDCI_ARGUMENT); + } +} + +static void phytium_sdci_cmd_next(struct phytium_sdci_host *host, + struct mmc_request *mrq, + struct mmc_command *cmd) +{ + if (cmd->error || (mrq->sbc && mrq->sbc->error)) + phytium_sdci_request_done(host, mrq); + else if (cmd == mrq->sbc) + phytium_sdci_start_command(host, mrq, mrq->cmd); + else if (!cmd->data) + phytium_sdci_request_done(host, mrq); +} + +static int phytium_sdci_cmd13_process(struct phytium_sdci_host *host, + struct mmc_request *mrq, + struct mmc_data *data, + u32 wait_timeout_ms, + u32 send_once_time_ms) +{ + u32 private_cmd, resp_type, arg, temp, sd_status; + unsigned long deadline_time; + bool res; + + deadline_time = jiffies + msecs_to_jiffies(wait_timeout_ms); + + do { + private_cmd = MMC_SEND_STATUS; + resp_type = 0x2; + arg = host->current_rca; + + res = phytium_sdci_private_send_cmd(host, private_cmd, resp_type, arg); + if (!res) { + sd_status = readl(host->base + SDCI_STATUS); + if (sd_status & SDCI_STATUS_CDSL) { + phytium_sdci_unexpected_error_handler(host, mrq, data, + ERR_CARD_ABSENT); + dev_err(host->dev, + "[%s][%d] Card absent! private_cmd(%d)\n", + __func__, __LINE__, private_cmd); + } else { + phytium_sdci_unexpected_error_handler(host, mrq, data, + ERR_CMD_RESPONED); + + dev_err(host->dev, + "[%s][%d] private_cmd(%d) response errored\n", + __func__, __LINE__, private_cmd); + phytium_sd_error(host); + } + writel(1, host->base + SDCI_BD_ISR); + return SDCI_CMD13_FAILED; + } + + temp = readl(host->base + SDCI_RESP0); + + if (time_after(jiffies, deadline_time)) { + + if (mrq->cmd->opcode == MMC_SEND_STATUS) + return SDCI_CMD13_OK; + + dev_err(host->dev, + "SD card is not in transfer mode,timeout:%d,rsp[0]:%x\n", + wait_timeout_ms, temp); + + phytium_sdci_unexpected_error_handler(host, mrq, data, + ERR_TIMEOUT); + phytium_sd_error(host); + return SDCI_CMD13_FAILED; + } + + writel(1, host->base + SDCI_NORMAL_ISR); + + if (CARD_TRAN_STATE != (temp & CARD_CURRENT_STATE) && send_once_time_ms) + mdelay(send_once_time_ms); + + } while (CARD_TRAN_STATE != (temp & CARD_CURRENT_STATE)); + + return SDCI_CMD13_OK; +} + +static void phytium_sdci_ops_request(struct mmc_host *mmc, + struct mmc_request *mrq) +{ + struct phytium_sdci_host *host = mmc_priv(mmc); + unsigned long flags; + bool res; + u32 status_sd; + int res_cmd13; + + host->error = 0; + WARN_ON(host->mrq); + host->mrq = mrq; + + dev_dbg(host->dev, "%s: mrq->cmd->opcode:%d, mrq->cmd->arg:0x%x\n", + __func__, mrq->cmd->opcode, mrq->cmd->arg); + + if (mrq->cmd->opcode == MMC_SEND_STATUS && + (mrq->cmd->flags & MMC_CMD_MASK) != MMC_CMD_ADTC) { + u32 status = readl(host->base + SDCI_STATUS); + + if (status & SDCI_STATUS_CDSL) { + phytium_sdci_unexpected_error_handler(host, mrq, NULL, + ERR_CARD_ABSENT); + return; + } + + res_cmd13 = phytium_sdci_cmd13_process(host, mrq, NULL, 400, 5); + if (res_cmd13 == SDCI_CMD13_FAILED) + return; + } else if (mrq->cmd->opcode == MMC_STOP_TRANSMISSION) { + status_sd = readl(host->base + SDCI_STATUS); + if (status_sd & SDCI_STATUS_CDSL) { + phytium_sdci_unexpected_error_handler(host, mrq, NULL, + ERR_CARD_ABSENT); + return; + } + } + + if (mrq->data) { + if (mrq->cmd->opcode == MMC_READ_MULTIPLE_BLOCK || + mrq->cmd->opcode == MMC_READ_SINGLE_BLOCK || + mrq->cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK || + mrq->cmd->opcode == MMC_WRITE_BLOCK) { + host->adtc_type = BLOCK_RW_ADTC; + phytium_sdci_prepare_data(host, mrq); + phytium_sdci_start_data(host, mrq, + mrq->cmd, mrq->data); + return; + } + host->adtc_type = COMMOM_ADTC; + } + + if (mrq->cmd->opcode == SD_IO_RW_DIRECT || + mrq->cmd->opcode == SD_IO_SEND_OP_COND) { + spin_lock_irqsave(&host->lock, flags); + host->mrq = NULL; + host->cmd = NULL; + spin_unlock_irqrestore(&host->lock, flags); + mrq->cmd->error = -EINVAL; + mmc_request_done(host->mmc, mrq); + + return; + } + + if (mrq->cmd->opcode == SD_APP_SEND_SCR) { + res = set_databus_width(host); + if (!res) { + phytium_sdci_unexpected_error_handler(host, mrq, NULL, ERR_CMD_RESPONED); + return; + } + } + + /* if SBC is required, we have HW option and SW option. + * if HW option is enabled, and SBC does not have "special" flags, + * use HW option, otherwise use SW option + */ + if (mrq->sbc && + (!mmc_card_mmc(mmc->card) || (mrq->sbc->arg & 0xFFFF0000))) + phytium_sdci_start_command(host, mrq, mrq->sbc); + else + phytium_sdci_start_command(host, mrq, mrq->cmd); +} + +static void phytium_sdci_data_xfer_next(struct phytium_sdci_host *host, + struct mmc_request *mrq, + struct mmc_data *data) +{ + if (mmc_op_multi(mrq->cmd->opcode) && + mrq->stop && !mrq->stop->error && + !mrq->sbc && host->is_multi_rw_only_one_blkcnt) { + host->is_multi_rw_only_one_blkcnt = false; + phytium_sdci_auto_command_done(host, SDCI_NORMAL_ISR_CC, mrq, mrq->stop); + } else if (mmc_op_multi(mrq->cmd->opcode) && + mrq->stop && !mrq->stop->error && + !mrq->sbc) + phytium_sdci_start_command(host, mrq, mrq->stop); + else + phytium_sdci_request_done(host, mrq); +} + +static inline void get_data_buffer(struct mmc_data *data, + u32 *bytes, u32 **pointer) +{ + struct scatterlist *sg; + + sg = &data->sg[0]; + *bytes = sg->length; + *pointer = sg_virt(sg); +} + +static bool phytium_sdci_data_xfer_done(struct phytium_sdci_host *host, + u32 events, struct mmc_request *mrq, + struct mmc_data *data) +{ + struct mmc_command *stop = data->stop; + unsigned long flags; + bool done; + unsigned int check_data; + u32 sg_length, i; + u32 *sg_virt_addr; + + check_data = events & (SDCI_BD_ISR_TRS_R | SDCI_BD_ISR_TRS_W | SDCI_BD_ISR_EDTE); + + spin_lock_irqsave(&host->lock, flags); + done = !host->data; + if (check_data) + host->data = NULL; + spin_unlock_irqrestore(&host->lock, flags); + + if (done) + return true; + + if (check_data || (stop && stop->error)) { + sdr_clr_bits(host->base + SDCI_BD_ISER, data_ints_mask); + dev_dbg(host->dev, "DMA stop\n"); + + if (((events & SDCI_BD_ISR_TRS_R) || + (events & SDCI_BD_ISR_TRS_W)) && + (!stop || !stop->error)) { + if ((mrq->cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC && + (host->adtc_type == COMMOM_ADTC)) { + get_data_buffer(data, &sg_length, + &host->sg_virt_addr); + sg_virt_addr = host->sg_virt_addr; + + for (i = 0; i < (sg_length/4); i++) { + *sg_virt_addr = host->dma_rx.buf[i]; + sg_virt_addr++; + } + } + data->bytes_xfered = data->blocks * data->blksz; + } else { + dev_dbg(host->dev, "interrupt events: %x\n", events); + phytium_sdci_reset_hw(host); + data->bytes_xfered = 0; + dev_dbg(host->dev, "%s: cmd=%d; blocks=%d", + __func__, mrq->cmd->opcode, data->blocks); + dev_dbg(host->dev, "data_error=%d xfer_size=%d\n", + (int)data->error, data->bytes_xfered); + } + + phytium_sdci_data_xfer_next(host, mrq, data); + done = true; + } + + return done; +} + + +static int phytium_sdci_card_busy(struct mmc_host *mmc) +{ + struct phytium_sdci_host *host = mmc_priv(mmc); + u32 status; + + /* check if any pin between dat[0:3] is low */ + status = readl(host->base + SDCI_STATUS); + if (((status >> 20) & 0xf) != 0xf) + return 1; + + return 0; +} + +static void phytium_sdci_request_timeout(struct work_struct *work) +{ + struct phytium_sdci_host *host; + + host = container_of(work, struct phytium_sdci_host, req_timeout.work); + dev_err(host->dev, "%s: aborting cmd/data/mrq\n", __func__); + if (host->mrq) { + dev_err(host->dev, "%s: aborting mrq=%p cmd=%d\n", __func__, + host->mrq, host->mrq->cmd->opcode); + if (host->cmd) { + dev_err(host->dev, "%s: aborting cmd=%d\n", + __func__, host->cmd->opcode); + phytium_sdci_cmd_done(host, SDCI_NORMAL_ISR_TIMEOUT, + host->mrq, host->cmd); + } else if (host->data) { + dev_err(host->dev, "%s: abort data: cmd%d; %d blocks\n", + __func__, host->mrq->cmd->opcode, + host->data->blocks); + phytium_sdci_data_xfer_done(host, SDCI_BD_ISR_EDTE, + host->mrq, host->data); + } + } +} + +static void hotplug_timer_func(struct timer_list *t) +{ + struct phytium_sdci_host *host; + u32 status; + + host = from_timer(host, t, hotplug_timer); + if (!host) + dev_err(host->dev, "%s: Not find host!\n", __func__); + status = readl(host->base + SDCI_STATUS); + + if (status & SDCI_STATUS_CDSL) { /* card absent */ + if (host->mmc->card) { + cancel_delayed_work(&host->mmc->detect); + mmc_detect_change(host->mmc, + msecs_to_jiffies(100)); + } + } else { /* card insert */ + cancel_delayed_work(&host->mmc->detect); + mmc_detect_change(host->mmc, msecs_to_jiffies(200)); + } +} + +static irqreturn_t phytium_sdci_irq(int irq, void *dev_id) +{ + struct phytium_sdci_host *host = (struct phytium_sdci_host *) dev_id; + unsigned long flags; + struct mmc_request *mrq; + struct mmc_command *cmd; + u32 events; + + if (!host) + return IRQ_NONE; + + spin_lock_irqsave(&host->lock, flags); + events = readl(host->base + SDCI_NORMAL_ISR); + /* clear interrupts */ + writel(1, host->base + SDCI_NORMAL_ISR); + + mrq = host->mrq; + cmd = host->cmd; + spin_unlock_irqrestore(&host->lock, flags); + + if (events & (SDCI_NORMAL_ISR_CR | SDCI_NORMAL_ISR_CI)) { + mod_timer(&host->hotplug_timer, + jiffies + usecs_to_jiffies(30000)); + goto irq_out; + } + + if (!(events & cmd_ints_mask)) + goto irq_out; + + if (!mrq) { + dev_err(host->dev, "%s: MRQ=NULL; events=%08X\n", + __func__, events); + WARN_ON(1); + goto irq_out; + } + + dev_dbg(host->dev, "%s: events=%08X\n", __func__, events); + + if (cmd) + phytium_sdci_cmd_done(host, events, mrq, cmd); + +irq_out: + return IRQ_HANDLED; +} + +static irqreturn_t phytium_sdci_dma_irq(int irq, void *dev_id) +{ + struct phytium_sdci_host *host = (struct phytium_sdci_host *) dev_id; + unsigned long flags; + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + u32 events; + + spin_lock_irqsave(&host->lock, flags); + events = readl(host->base + SDCI_BD_ISR); + writel(1, host->base + SDCI_BD_ISR); + + mrq = host->mrq; + cmd = host->cmd; + data = host->data; + spin_unlock_irqrestore(&host->lock, flags); + + if (!(events & data_ints_mask)) + goto dma_irq_out; + + if (!mrq) { + dev_err(host->dev, + "%s: MRQ=NULL; events=%08X\n", + __func__, events); + goto dma_irq_out; + } + + dev_dbg(host->dev, "%s: events=%08X\n", __func__, events); + + if (data) + phytium_sdci_data_xfer_done(host, events, mrq, data); + +dma_irq_out: + return IRQ_HANDLED; +} + +static irqreturn_t phytium_sdci_err_irq(int irq, void *dev_id) +{ + struct phytium_sdci_host *host = (struct phytium_sdci_host *) dev_id; + unsigned long flags; + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + u32 events; + + if (!host) + return IRQ_NONE; + + spin_lock_irqsave(&host->lock, flags); + events = readl(host->base + SDCI_ERROR_ISR); + mrq = host->mrq; + cmd = host->cmd; + data = host->data; + spin_unlock_irqrestore(&host->lock, flags); + + if (!(events&err_ints_mask)) + goto err_irq_out; + + if (!mrq) { + sdr_clr_bits(host->base + SDCI_NORMAL_ISER, SDCI_NORMAL_ISR_EI); + writel(1, host->base + SDCI_ERROR_ISR); + dev_err(host->dev, "%s: MRQ=NULL; events=%08X\n", __func__, events); + goto err_irq_out; + } + sdr_clr_bits(host->base + SDCI_NORMAL_ISER, SDCI_NORMAL_ISR_EI); + if (data) { + dev_err(host->dev, + "[%s][%d]: cmd(%d); %d read blocks, status:%x,flag:%x\n", + __func__, __LINE__, mrq->cmd->opcode, data->blocks, events, data->flags); + data->error = -ETIMEDOUT; + if ((data->flags & MMC_DATA_READ) == MMC_DATA_READ || + (data->flags & MMC_DATA_WRITE) == MMC_DATA_WRITE) + phytium_sdci_data_xfer_done(host, SDCI_BD_ISR_EDTE | SDCI_BD_ISR_TRS_R, + mrq, data); + mrq->cmd->error = -ETIMEDOUT; + mmc_request_done(host->mmc, mrq); + } else if (cmd) { + phytium_sdci_cmd_done(host, SDCI_NORMAL_ISR_TIMEOUT, mrq, cmd); + } + + writel(1, host->base + SDCI_NORMAL_ISR); + writel(1, host->base + SDCI_ERROR_ISR); +err_irq_out: + return IRQ_HANDLED; +} + +static void phytium_sdci_init_hw(struct phytium_sdci_host *host) +{ + u32 val; + + /* Reset */ + phytium_sdci_reset_hw(host); + + val = SDCI_SEN_CREFR_VAL | SDCI_SEN_DEBNCE_VAL; + writel(val, host->base + SDCI_SD_SEN); + + /* Disable and clear all interrupts */ + writel(0, host->base + SDCI_NORMAL_ISER); + writel(0, host->base + SDCI_ERROR_ISER); + writel(0, host->base + SDCI_BD_ISER); + + writel(1, host->base + SDCI_NORMAL_ISR); + writel(1, host->base + SDCI_ERROR_ISR); + writel(1, host->base + SDCI_BD_ISR); + + sdr_set_bits(host->base + SDCI_NORMAL_ISER, + SDCI_SDCI_NORMAL_ISER_ECI|SDCI_SDCI_NORMAL_ISER_ECR); + /* Configure default cmd timeout to 0.1(s)s = val/25M */ + val = SDCI_F_MAX / 10; + writel(val, host->base + SDCI_TIMEOUT_CMD); + writel(SDCI_TIMEOUT_DATA_VALUE, host->base + SDCI_TIMEOUT_DATA); + + val = 0x0F00; + writel(val, host->base + SDCI_CONTROLLER); + + dev_dbg(host->dev, "init hardware done!"); +} + +static void phytium_sdci_deinit_hw(struct phytium_sdci_host *host) +{ + /* Disable and clear all interrupts */ + writel(0, host->base + SDCI_NORMAL_ISER); + writel(0, host->base + SDCI_ERROR_ISER); + writel(0, host->base + SDCI_BD_ISER); + + writel(0, host->base + SDCI_NORMAL_ISR); + writel(0, host->base + SDCI_ERROR_ISR); + writel(0, host->base + SDCI_BD_ISR); +} + +static void phytium_sdci_ops_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct phytium_sdci_host *host = mmc_priv(mmc); + + if (ios->bus_width == MMC_BUS_WIDTH_4) + mmc->caps = mmc->caps & (~MMC_CAP_4_BIT_DATA); + + /* Suspend/Resume will do power off/on */ + switch (ios->power_mode) { + case MMC_POWER_UP: + writel(SDCI_POWER_ON, host->base + SDCI_POWER); + break; + case MMC_POWER_ON: + phytium_sdci_set_clk(host, ios); + break; + case MMC_POWER_OFF: + writel(SDCI_POWER_OFF, host->base + SDCI_POWER); + break; + default: + break; + } +} + +static int phytium_sdci_get_cd(struct mmc_host *mmc) +{ + struct phytium_sdci_host *host = mmc_priv(mmc); + u32 status = readl(host->base + SDCI_STATUS); + + if (((status >> 19) & 0x1) == 0x1) + return 0; + + return 1; +} + +static void phytium_sdci_hw_reset(struct mmc_host *mmc) +{ + struct phytium_sdci_host *host = mmc_priv(mmc); + + sdr_set_bits(host->base + SDCI_SOFTWARE, SDCI_SOFTWARE_SRST); + sdr_clr_bits(host->base + SDCI_SOFTWARE, SDCI_SOFTWARE_SRST); + while (!(readl(host->base + SDCI_STATUS) & SDCI_STATUS_IDIE)) + cpu_relax(); +} + +static struct mmc_host_ops phytium_sdci_ops = { + .request = phytium_sdci_ops_request, + .set_ios = phytium_sdci_ops_set_ios, + .get_cd = phytium_sdci_get_cd, + .card_busy = phytium_sdci_card_busy, + .card_hw_reset = phytium_sdci_hw_reset, +}; + +static bool phytium_sdci_private_send_cmd(struct phytium_sdci_host *host, + u32 cmd, u32 resp_type, u32 arg) +{ + u32 temp, sd_cmd, sd_arg, sd_status; + unsigned long deadline_time; + + writel(1, host->base + SDCI_NORMAL_ISR); + writel(1, host->base + SDCI_ERROR_ISR); + + sd_cmd = (cmd << 8) | resp_type; + sd_arg = arg; + writel(sd_cmd, host->base + SDCI_COMMAND); + writel(sd_arg, host->base + SDCI_ARGUMENT); + + if (cmd == MMC_STOP_TRANSMISSION) + deadline_time = jiffies + msecs_to_jiffies(1000); + else + deadline_time = jiffies + msecs_to_jiffies(100); + + temp = readl(host->base + SDCI_NORMAL_ISR); + while ((temp & SDCI_NORMAL_ISR_CC) != SDCI_NORMAL_ISR_CC) { + sd_status = readl(host->base + SDCI_STATUS); + if (sd_status & SDCI_STATUS_CDSL) + return false; + + temp = readl(host->base + SDCI_NORMAL_ISR); + if (time_after(jiffies, deadline_time)) + return false; + + if (cmd == MMC_STOP_TRANSMISSION) + mdelay(1); + } + + return true; +} + +static int phytium_sdci_probe(struct platform_device *pdev) +{ + struct mmc_host *mmc; + struct phytium_sdci_host *host; + struct resource *res; + int ret; + const struct acpi_device_id *match; + struct device *dev = &pdev->dev; + + /* Allocate MMC host for this device */ + mmc = mmc_alloc_host(sizeof(struct phytium_sdci_host), &pdev->dev); + if (!mmc) + return -ENOMEM; + + host = mmc_priv(mmc); + ret = mmc_of_parse(mmc); + if (ret) + goto host_free; + + if (dev->of_node) { + host->src_clk = devm_clk_get(&pdev->dev, "phytium_sdc_clk"); + if (IS_ERR(host->src_clk)) { + ret = PTR_ERR(host->src_clk); + goto host_free; + } + + host->clk_rate = clk_get_rate(host->src_clk); + if (device_property_read_bool(dev, "no-dma-coherent")) + dev->dma_coherent = false; + } else if (has_acpi_companion(dev)) { + match = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!match) { + dev_err(dev, "Error ACPI match data is missing\n"); + return -ENODEV; + } + + acpi_dma_configure(dev, DEV_DMA_NON_COHERENT); + + host->clk_rate = 600000000; + } else { + dev_err(&pdev->dev, "No DT found\n"); + return -EINVAL; + } + + dma_set_mask(dev, DMA_BIT_MASK(40)); + dma_set_coherent_mask(dev, DMA_BIT_MASK(40)); + + timer_setup(&host->hotplug_timer, hotplug_timer_func, 0); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->base)) { + ret = PTR_ERR(host->base); + goto host_free; + } + + host->irq = platform_get_irq(pdev, 1); + if (host->irq < 0) { + ret = -EINVAL; + goto host_free; + } + + host->irq_err = platform_get_irq(pdev, 2); + if (host->irq_err < 0) { + ret = -EINVAL; + goto host_free; + } + + host->irq_bd = platform_get_irq(pdev, 0); + if (host->irq_bd < 0) { + ret = -EINVAL; + goto host_free; + } + + host->dev = &pdev->dev; + host->mmc = mmc; + + if ((4 * SDCI_F_MAX) > host->clk_rate) + host->clk_div = 1; + else + host->clk_div = ((host->clk_rate / (2 * SDCI_F_MAX)) - 1); + + /* Set host parameters to mmc */ + mmc->f_min = SDCI_F_MIN; + mmc->f_max = (host->clk_rate / ((host->clk_div + 1) * 2)); + mmc->ops = &phytium_sdci_ops; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + + mmc->caps |= host->caps; + /* MMC core transfer sizes tunable parameters */ + mmc->max_segs = MAX_BD_NUM; + mmc->max_seg_size = 512 * 1024; + mmc->max_blk_size = 512; + mmc->max_req_size = 512 * 1024; + mmc->max_blk_count = mmc->max_req_size / 512; + + host->dma_rx.buf = dma_alloc_coherent(&pdev->dev, + MAX_BD_NUM, + &host->dma_rx.bd_addr, + GFP_KERNEL); + if (!host->dma_rx.buf) { + ret = -ENOMEM; + goto release_mem; + } + + host->cmd_timeout = msecs_to_jiffies(100); + host->data_timeout = msecs_to_jiffies(250); + + INIT_DELAYED_WORK(&host->req_timeout, phytium_sdci_request_timeout); + spin_lock_init(&host->lock); + + platform_set_drvdata(pdev, mmc); + phytium_sdci_init_hw(host); + + ret = devm_request_irq(&pdev->dev, host->irq, phytium_sdci_irq, + IRQF_SHARED, pdev->name, host); + if (ret) + goto release; + + ret = devm_request_irq(&pdev->dev, host->irq_err, phytium_sdci_err_irq, + IRQF_SHARED, pdev->name, host); + if (ret) + goto release; + + ret = devm_request_irq(&pdev->dev, host->irq_bd, phytium_sdci_dma_irq, + IRQF_SHARED, pdev->name, host); + if (ret) + goto release; + + ret = mmc_add_host(mmc); + if (ret) + goto release; + + return 0; + +release: + platform_set_drvdata(pdev, NULL); + phytium_sdci_deinit_hw(host); +release_mem: + if (host->dma_rx.buf) + dma_free_coherent(&pdev->dev, MAX_BD_NUM, + host->dma_rx.buf, + host->dma_rx.bd_addr); +host_free: + mmc_free_host(mmc); + + return ret; +} + +static int phytium_sdci_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc; + struct phytium_sdci_host *host; + + mmc = platform_get_drvdata(pdev); + host = mmc_priv(mmc); + + cancel_delayed_work_sync(&host->req_timeout); + platform_set_drvdata(pdev, NULL); + mmc_remove_host(host->mmc); + phytium_sdci_deinit_hw(host); + + if (host->dma_rx.buf) + dma_free_coherent(&pdev->dev, MAX_BD_NUM, + host->dma_rx.buf, host->dma_rx.bd_addr); + + mmc_free_host(host->mmc); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int phytium_sdci_suspend(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct phytium_sdci_host *host = mmc_priv(mmc); + + phytium_sdci_deinit_hw(host); + return 0; +} + +static int phytium_sdci_resume(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct phytium_sdci_host *host = mmc_priv(mmc); + + phytium_sdci_init_hw(host); + mmc->caps = mmc->caps | MMC_CAP_4_BIT_DATA; + + return 0; +} +#endif + +#ifdef CONFIG_PM +static int phytium_sdci_runtime_suspend(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct phytium_sdci_host *host = mmc_priv(mmc); + + phytium_sdci_deinit_hw(host); + + return 0; +} + +static int phytium_sdci_runtime_resume(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct phytium_sdci_host *host = mmc_priv(mmc); + + phytium_sdci_init_hw(host); + + return 0; +} + +static const struct dev_pm_ops phytium_sdci_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(phytium_sdci_suspend, + phytium_sdci_resume) + SET_RUNTIME_PM_OPS(phytium_sdci_runtime_suspend, + phytium_sdci_runtime_resume, NULL) +}; +#else +#define phytium_sdci_dev_pm_ops NULL +#endif + +static const struct of_device_id phytium_sdci_of_ids[] = { + { .compatible = "phytium,sdci", }, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_sdci_of_ids); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_sdci_acpi_ids[] = { + { .id = "PHYT0005" }, + { } +}; + +MODULE_DEVICE_TABLE(acpi, phytium_sdci_acpi_ids); +#else +#define phytium_sdci_acpi_ids NULL +#endif + +static struct platform_driver phytium_sdci_driver = { + .probe = phytium_sdci_probe, + .remove = phytium_sdci_remove, + .driver = { + .name = "sdci-phytium", + .of_match_table = phytium_sdci_of_ids, + .acpi_match_table = phytium_sdci_acpi_ids, + .pm = &phytium_sdci_dev_pm_ops, + }, +}; + +module_platform_driver(phytium_sdci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cheng Quan "); +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Phytium SD Card Interface driver"); diff --git a/drivers/mmc/host/phytium-sdci.h b/drivers/mmc/host/phytium-sdci.h new file mode 100644 index 0000000000000000000000000000000000000000..a50cf42ef98a22fce249e7fdf070eb74c5c6ca47 --- /dev/null +++ b/drivers/mmc/host/phytium-sdci.h @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium SDCI dirver + * + * Copyright (C) 2019-2023, Phytium Technology Co., Ltd. + */ + +/*---------------------------------------------------------------------------*/ +/* Common Definition */ +/*---------------------------------------------------------------------------*/ +#define MAX_BD_NUM 0x1000 +#define SD_BLOCK_SIZE 512 + +/*---------------------------------------------------------------------------*/ +/* Register Offset */ +/*---------------------------------------------------------------------------*/ +#define SDCI_CONTROLLER 0x00 /* controller config reg */ +#define SDCI_ARGUMENT 0x04 /* argument reg */ +#define SDCI_COMMAND 0x08 /* command reg */ +#define SDCI_CLOCK_D 0x0C /* clock divide reg */ +#define SDCI_SOFTWARE 0x10 /* controller reset reg */ +#define SDCI_POWER 0X14 /* POWRE CONTROL REG */ +#define SDCI_TIMEOUT_CMD 0x18 /* cmd timeout config reg */ +#define SDCI_TIMEOUT_DATA 0x1C /* data timeout reg */ +#define SDCI_NORMAL_ISER 0x20 /* normal ISR config reg */ +#define SDCI_ERROR_ISER 0x24 /* erroe ISR config reg */ +#define SDCI_BD_ISER 0x28 /* BD ISR config reg */ +#define SDCI_CAPA 0x2C /* BD ISR config reg */ +#define SDCI_SD_DRV 0x30 /* SD card driving phase position reg */ +#define SDCI_SD_SAMP 0x34 /* SD card sampling phase position reg */ +#define SDCI_SD_SEN 0x38 /* SD card detection reg */ +#define SDCI_HDS_AXI 0x3C /* AXI boundary config reg */ +#define SDCI_BD_RX 0x40 /* BD rx addr reg */ +#define SDCI_BD_TX 0x60 /* BD tx addr reg */ +#define SDCI_BLK_CNT 0x80 /* r/w block num reg */ +#define SDCI_NORMAL_ISR 0xC0 /* normal ISR status reg */ +#define SDCI_ERROR_ISR 0xC4 /* error ISR status reg */ +#define SDCI_BD_ISR 0xC8 /* BD ISR status reg */ +#define SDCI_BD_STATUS 0xCC /* BD descriptor status reg */ +#define SDCI_STATUS 0xD0 /* status reg */ +#define SDCI_BLOCK 0xD4 /* block len reg */ +#define SDCI_RESP0 0xE0 /* response reg0 */ +#define SDCI_RESP1 0xE4 /* response reg1 */ +#define SDCI_RESP2 0xE8 /* response reg2 */ +#define SDCI_RESP3 0XEC /* response reg3 */ + +/*---------------------------------------------------------------------------*/ +/* Register Mask */ +/*---------------------------------------------------------------------------*/ +/* SDCI_CONTROLLER mask */ +#define SDCI_CONTROLLER_ECRCWR (0x1 << 0) /* RW */ +#define SDCI_CONTROLLER_ECRCRD (0x1 << 1) /* RW */ +#define SDCI_CONTROLLER_RESEDE (0x1 << 2) /* RW */ +#define SDCI_CONTROLLER_PERMDR (0x3 << 8) /* RW */ +#define SDCI_CONTROLLER_PERMDX (0x3 << 10) /* RW */ + +/* SDCI_SOFTWARE mask */ +#define SDCI_SOFTWARE_SRST (0x1 << 0) /* RW */ +#define SDCI_SOFTWARE_SCRST (0x1 << 1) /* RW */ +#define SDCI_SOFTWARE_BDRST (0x1 << 2) /* RW */ +#define SDCI_SOFTWARE_CFCLF (0x1 << 3) /* RW */ +#define SDCI_SOFTWARE_SDRST (0x1 << 4) /* RW */ + +/* SDCI_NORMAL_ISER mask */ +#define SDCI_SDCI_NORMAL_ISER_ECC_EN (0x1 << 0) /* RW */ +#define SDCI_SDCI_NORMAL_ISER_ECR (0x1 << 1) /* RW */ +#define SDCI_SDCI_NORMAL_ISER_ECI (0x1 << 2) /* RW */ +#define SDCI_SDCI_NORMAL_ISER_EEI_EN (0x1 << 15) /* RW */ + +/* SDCI_NORMAL_ISR mask */ +#define SDCI_NORMAL_ISR_CC (0x1 << 0) /* R */ +#define SDCI_NORMAL_ISR_CR (0x1 << 1) /* R */ +#define SDCI_NORMAL_ISR_CI (0x1 << 2) /* R */ +#define SDCI_NORMAL_ISR_TIMEOUT (0x1 << 3) /* R */ +#define SDCI_NORMAL_ISR_EI (0x1 << 15) /* R */ + +/* SDCI_ERROR_ISER mask */ +#define SDCI_ERROR_ISER_ECTE_EN (0x1 << 0) /* RW */ +#define SDCI_ERROR_ISR_CCRCE_EN (0x1 << 1) /* RW */ +#define SDCI_ERROR_ISR_CIR_EN (0x1 << 3) /* RW */ +#define SDCI_ERROR_ISR_CNR_EN (0x1 << 4) /* RW */ +/* SDCI_ERROR_ISR mask */ +#define SDCI_ERROR_ISR_CTE (0x1 << 0) /* R */ +#define SDCI_ERROR_ISR_CCRCE (0x1 << 1) /* R */ +#define SDCI_ERROR_ISR_CIR (0x1 << 3) /* R */ +#define SDCI_ERROR_ISR_CNR (0x1 << 4) /* R */ + +/* SDCI_BD_ISER mask */ +#define SDCI_BD_ISER_ETRS_EN (0x1 << 8) /* RW */ +#define SDCI_BD_ISER_DATFRAX_EN (0x1 << 7) /* RW */ + +/* SDCI_BD_ISR mask */ +#define SDCI_BD_ISR_TRS_W (0x1 << 0) /* R */ +#define SDCI_BD_ISR_TRS_R (0x1 << 8) /* R */ +#define SDCI_BD_ISR_EDTE (0x1 << 3) /* R */ +#define SDCI_BD_ISR_DAIS (0x1 << 15) /* R */ +#define SDCI_BD_ISR_DATFRAX (0x1 << 7) /* R */ + +/* SDCI_HDS_AXI mask */ +#define SDCI_HDS_AXI_AWDOMAIN (0x1 << 0) /* RW */ +#define SDCI_HDS_AXI_ARDOMAIN (0x1 << 12) /* RW */ +#define SDCI_HDS_AXI_AWCACHE (0x6 << 24) /* RW */ +#define SDCI_HDS_AXI_ARCACHE (0xB << 28) /* RW */ + +/* SDCI_STATUS mask */ +#define SDCI_STATUS_CMD_BUSY (0x0 << 0) /* R */ +#define SDCI_STATUS_CMD_READY (0x1 << 0) /* R */ +#define SDCI_STATUS_IDIE (0x1 << 12) /* R */ +#define SDCI_CARD_BUSY_IN_PRG (0x1 << 20) /* R D0 BUSY:0,IDLE:1 */ + +/* SDCI_STATUS */ +#define SDCI_STATUS_CDSL (0x1 << 19) /* R */ + +/*---------------------------------------------------------------------------*/ +/* Register Value */ +/*---------------------------------------------------------------------------*/ +#define SDCI_SD_DRV_VALUE 0 +#define SDCI_SD_SAMP_VALUE_MAX 50 +#define SDCI_SD_SAMP_VALUE_MIN 0 + +#define SDCI_TIMEOUT_CMD_VALUE 0xFFFFFFFF +#define SDCI_TIMEOUT_DATA_VALUE 0xFFFFFFFF +#define SDCI_POWER_ON 1 +#define SDCI_POWER_OFF 0 + +#define SDCI_CMD_TIMEOUT 10 +#define SDCI_DAT_TIMEOUT 5000 + +#define SDCI_CMD_TYPE_ADTC 0x2 + +#define SDCI_F_MIN 400000 +#define SDCI_F_MAX 25000000 + +#define SDCI_SEN_CREFR_VAL (0x1 << 1) +#define SDCI_SEN_DEBNCE_VAL (0xB << 8) + +#define CARD_CURRENT_STATE (0xF << 9) +#define CARD_PRG_STATE (0x7 << 9) +#define CARD_TRAN_STATE (0x4 << 9) + +#define SDCI_CMD13_OK 1 +#define SDCI_CMD13_FAILED 0 + +#define ERR_TIMEOUT (0x1 << 0) +#define ERR_CARD_ABSENT (0x1 << 1) +#define ERR_CMD_RESPONED (0x1 << 2) + +/*---------------------------------------------------------------------------*/ +/* Structure Type */ +/*---------------------------------------------------------------------------*/ +struct phytium_sdci_dma { + struct scatterlist *sg; + u32 *buf; + dma_addr_t bd_addr; + size_t bytes; +}; + +enum adtc_type { + COMMOM_ADTC = 0, + BLOCK_RW_ADTC = 1 +}; + +struct phytium_sdci_host { + struct device *dev; + struct mmc_host *mmc; + u32 caps; + spinlock_t lock; + + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + int error; + + void __iomem *base; + + struct phytium_sdci_dma dma_rx; + struct phytium_sdci_dma dma_tx; + + u32 *sg_virt_addr; + enum adtc_type adtc_type; + + struct timer_list hotplug_timer; + + struct delayed_work req_timeout; + u32 cmd_timeout; + u32 data_timeout; + + int irq; + int irq_err; + int irq_bd; + + struct clk *src_clk; + unsigned long clk_rate; + unsigned long clk_div; + unsigned long real_rate; + + u32 current_rca; + bool is_multi_rw_only_one_blkcnt; +}; + diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 97ca2a897f1d49f65155244acd628b773f624cfb..c5a59c48812322c011fef670673f4b17f13c4e2f 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -743,7 +743,8 @@ int add_mtd_device(struct mtd_info *mtd) dev_set_name(&mtd->dev, "mtd%d", i); dev_set_drvdata(&mtd->dev, mtd); mtd_check_of_node(mtd); - of_node_get(mtd_get_of_node(mtd)); + if (mtd->dev.of_node) + of_node_get(mtd_get_of_node(mtd)); error = device_register(&mtd->dev); if (error) { put_device(&mtd->dev); @@ -905,7 +906,8 @@ static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd, config.ignore_wp = true; config.reg_read = reg_read; config.size = size; - config.of_node = np; + if (IS_ENABLED(CONFIG_OF)) + config.of_node = np; config.priv = mtd; nvmem = nvmem_register(&config); @@ -913,7 +915,8 @@ static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd, if (IS_ERR(nvmem) && PTR_ERR(nvmem) == -EOPNOTSUPP) nvmem = NULL; - of_node_put(np); + if (IS_ENABLED(CONFIG_OF)) + of_node_put(np); return nvmem; } diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 23483db8f30c90e17f832c9cb7cab7bdb2610ce6..3cda38902b95b8f27e8cf25d961283088f08219d 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -19,6 +19,8 @@ #include #include +#include +#include #include "mtdcore.h" /* @@ -504,12 +506,14 @@ EXPORT_SYMBOL_GPL(deregister_mtd_parser); static const char * const default_mtd_part_types[] = { "cmdlinepart", "ofpart", + "acpipart", NULL }; /* Check DT only when looking for subpartitions. */ static const char * const default_subpartition_types[] = { "ofpart", + "acpipart", NULL }; @@ -573,6 +577,45 @@ static struct mtd_part_parser *mtd_part_get_compatible_parser(const char *compat return ret; } +static int mtd_part_acpi_parse(struct mtd_info *master, + struct mtd_partitions *pparts) +{ + struct mtd_part_parser *parser; + struct acpi_device *adev; + struct fwnode_handle *child; + const char *compat; + const char *fixed = "acpi-fixed-partitions"; + int ret, err = 0; + int compare = 1; + struct device *dev = &master->dev; + + if (has_acpi_companion(dev)) + adev = ACPI_COMPANION(dev); + if (!mtd_is_partition(master)) { + fwnode_property_read_string(dev->fwnode, "fixed", &compat); + if (compat) + compare = strcmp(compat, fixed); + } + + //all child node + device_for_each_child_node(dev, child) { + if (compat && !compare) { + parser = mtd_part_parser_get(fixed); + if (!parser && !request_module("%s", fixed)) + parser = mtd_part_parser_get(fixed); + if (parser) { + ret = mtd_part_do_parse(parser, master, pparts, NULL); + if (ret > 0) + return ret; + mtd_part_parser_put(parser); + if (ret < 0 && !err) + err = ret; + } + } + } + return err; +} + static int mtd_part_of_parse(struct mtd_info *master, struct mtd_partitions *pparts) { @@ -591,7 +634,7 @@ static int mtd_part_of_parse(struct mtd_info *master, dev = master->dev.parent; np = mtd_get_of_node(master); - if (mtd_is_partition(master)) + if (mtd_is_partition(master) && np) of_node_get(np); else np = of_get_child_by_name(np, "partitions"); @@ -612,7 +655,8 @@ static int mtd_part_of_parse(struct mtd_info *master, ret = mtd_part_do_parse(parser, master, pparts, NULL); if (ret > 0) { of_platform_populate(np, NULL, NULL, dev); - of_node_put(np); + if (np) + of_node_put(np); return ret; } mtd_part_parser_put(parser); @@ -620,7 +664,8 @@ static int mtd_part_of_parse(struct mtd_info *master, err = ret; } of_platform_populate(np, NULL, NULL, dev); - of_node_put(np); + if (np) + of_node_put(np); /* * For backward compatibility we have to try the "fixed-partitions" @@ -678,7 +723,9 @@ int parse_mtd_partitions(struct mtd_info *master, const char *const *types, * should be used. It requires a bit different logic so it is * handled in a separated function. */ - if (!strcmp(*types, "ofpart")) { + if (!strcmp(*types, "acpipart")) { + ret = mtd_part_acpi_parse(master, &pparts); + } else if (!strcmp(*types, "ofpart")) { ret = mtd_part_of_parse(master, &pparts); } else { pr_debug("%s: parsing partitions %s\n", master->name, diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 6142573085169ae7d820888edb054d35363ed5dd..3d5730ead11374dc83b57b9d9045c068480d15de 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -150,6 +150,25 @@ config MTD_NAND_ORION No board specific support is done by this driver, each board must advertise a platform_device for the driver to attach. +config MTD_NAND_PHYTIUM + tristate + +config MTD_NAND_PHYTIUM_PCI + tristate "Support Phytium NAND controller as a PCI device" + select MTD_NAND_PHYTIUM + depends on PCI + help + Enable the driver for NAND flash controller of Phytium Px210 chipset, + using the Phytium NAND controller core. + +config MTD_NAND_PHYTIUM_PLAT + tristate "Support Phytium NAND controller as a platform device" + select MTD_NAND_PHYTIUM + depends on ARCH_PHYTIUM + help + Enable the driver for NAND flash controller of Phytium CPU chipset, + using the Phytium NAND controller core. + config MTD_NAND_MARVELL tristate "Marvell EBU NAND controller" depends on PXA3xx || ARCH_MMP || PLAT_ORION || ARCH_MVEBU || \ diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile index 25120a4afadac2cefaaf18a2500c6ed9960bf159..0898acbe14bdef13b304b387c65e7a8e22cc59fd 100644 --- a/drivers/mtd/nand/raw/Makefile +++ b/drivers/mtd/nand/raw/Makefile @@ -57,6 +57,9 @@ obj-$(CONFIG_MTD_NAND_INTEL_LGM) += intel-nand-controller.o obj-$(CONFIG_MTD_NAND_ROCKCHIP) += rockchip-nand-controller.o obj-$(CONFIG_MTD_NAND_PL35X) += pl35x-nand-controller.o obj-$(CONFIG_MTD_NAND_RENESAS) += renesas-nand-controller.o +obj-$(CONFIG_MTD_NAND_PHYTIUM) += phytium_nand.o +obj-$(CONFIG_MTD_NAND_PHYTIUM_PCI) += phytium_nand_pci.o +obj-$(CONFIG_MTD_NAND_PHYTIUM_PLAT) += phytium_nand_plat.o nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o nand-objs += nand_onfi.o diff --git a/drivers/mtd/nand/raw/phytium_nand.c b/drivers/mtd/nand/raw/phytium_nand.c new file mode 100644 index 0000000000000000000000000000000000000000..d05c2939d12890ec7380c51f314940e5c2a563a0 --- /dev/null +++ b/drivers/mtd/nand/raw/phytium_nand.c @@ -0,0 +1,2092 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Core driver for Phytium NAND flash controller + * + * Copyright (C) 2020-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phytium_nand.h" + +u16 timing_asy_mode0[TIMING_ASY_NUM] = { /* x100 pass, sample: 1 */ + 0x03, 0x03, 0x28, 0x28, 0x03, 0x03, 0x06, 0x06, 0x28, 0x70, 0x30, 0x50}; +u16 timing_asy_mode1[TIMING_ASY_NUM] = { /* x100 pass, sample: 1 */ + 0x03, 0x03, 0x14, 0x14, 0x03, 0x03, 0x06, 0x06, 0x14, 0x70, 0x30, 0x28}; +u16 timing_asy_mode2[TIMING_ASY_NUM] = { /* x100 pass, sample: 7/8 (unlic) */ + 0x03, 0x03, 0x0D, 0x0D, 0x03, 0x03, 0x06, 0x06, 0x0D, 0x70, 0x20, 0x1A}; +u16 timing_asy_mode3[TIMING_ASY_NUM] = { /* x100 pass, sample: 4-7 */ + 0x03, 0x03, 0x0A, 0x0A, 0x03, 0x03, 0x06, 0x06, 0x0A, 0x70, 0x20, 0x14}; +u16 timing_asy_mode4[TIMING_ASY_NUM] = { /* x100 1.8v pass */ + 0x03, 0x03, 0x08, 0x08, 0x03, 0x03, 0x06, 0x06, 0x08, 0x70, 0x15, 0x10}; +u16 timing_asy_mode5[TIMING_ASY_NUM] = { /* x100 1.8v pass */ + 0x03, 0x03, 0x07, 0x07, 0x03, 0x03, 0x06, 0x06, 0x07, 0x20, 0x15, 0x0E}; +u16 timing_syn_mode0[TIMING_SYN_NUM] = { /* x100 1.8v pass */ + 0x20, 0x41, 0x05, 0x20, 0x10, 0x19, 0x62, 0x40, 0x38, 0x20, 0x00, 0x09, + 0x50, 0x20}; +u16 timing_syn_mode1[TIMING_SYN_NUM] = { /* x100 1.8v pass */ + 0x18, 0x32, 0x06, 0x18, 0x0C, 0x10, 0x76, 0x40, 0x2A, 0x1E, 0x00, 0x12, + 0x24, 0x18}; +u16 timing_syn_mode2[TIMING_SYN_NUM] = { /* x100 1.8v pass */ + 0x10, 0x0A, 0x04, 0x10, 0x08, 0x0A, 0x6E, 0x50, 0x1D, 0x10, 0x00, 0x0C, + 0x18, 0x10}; +u16 timing_syn_mode3[TIMING_SYN_NUM] = { /* x100 1.8v pass */ + 0x0C, 0x1A, 0x02, 0x0C, 0x06, 0x08, 0x78, 0x7C, 0x15, 0x0C, 0x00, 0x08, + 0x12, 0x0C}; +u16 timing_syn_mode4[TIMING_SYN_NUM] = { /* x100 1.8v failed */ + 0x08, 0x17, 0x05, 0x08, 0x04, 0x01, 0x73, 0x40, 0x0C, 0x08, 0x00, 0x06, + 0x0C, 0x10}; +u16 timing_tog_ddr_mode0[TIMING_TOG_NUM] = { /* 600M clk */ + 0x14, 0x0a, 0x08, 0x08, 0xc8, 0xc8, 0x08, 0x08, 0x20, 0x0a, 0x14, 0x08}; + +static u32 nfc_ecc_errover; +static u32 nfc_ecc_err; +static u32 nfc_irq_st; +static u32 nfc_irq_en; +static u32 nfc_irq_complete; + +static DECLARE_WAIT_QUEUE_HEAD(wait_done); + +/* + * Internal helper to conditionnally apply a delay (from the above structure, + * most of the time). + */ +static void cond_delay(unsigned int ns) +{ + if (!ns) + return; + + if (ns < 10000) + ndelay(ns); + else + udelay(DIV_ROUND_UP(ns, 1000)); +} + +static inline struct phytium_nfc *to_phytium_nfc(struct nand_controller *ctrl) +{ + return container_of(ctrl, struct phytium_nfc, controller); +} + +static inline struct phytium_nand_chip *to_phytium_nand(struct nand_chip *chip) +{ + return container_of(chip, struct phytium_nand_chip, chip); +} + +static u32 phytium_read(struct phytium_nfc *nfc, u32 reg) +{ + return readl_relaxed(nfc->regs + reg); +} + +static void phytium_write(struct phytium_nfc *nfc, u32 reg, u32 value) +{ + return writel_relaxed(value, nfc->regs + reg); +} + +static inline int phytium_wait_busy(struct phytium_nfc *nfc) +{ + u32 status; + + if (nfc_ecc_errover) { + nfc_ecc_errover = 0; + return 0; + } + + return readl_relaxed_poll_timeout(nfc->regs + NDSR, status, + !(status & NDSR_BUSY), 10, 10000); +} + +static void phytium_nfc_disable_int(struct phytium_nfc *nfc, u32 int_mask) +{ + u32 reg; + + reg = phytium_read(nfc, NDIR_MASK); + phytium_write(nfc, NDIR_MASK, reg | int_mask); +} + +static void phytium_nfc_enable_int(struct phytium_nfc *nfc, u32 int_mask) +{ + u32 reg; + + reg = phytium_read(nfc, NDIR_MASK); + phytium_write(nfc, NDIR_MASK, reg & (~int_mask)); +} + +static void phytium_nfc_clear_int(struct phytium_nfc *nfc, u32 int_mask) +{ + phytium_write(nfc, NDIR_MASK, int_mask); +} + +static int phytium_nfc_cmd_correct(struct phytium_nfc_op *nfc_op) +{ + if (!nfc_op) + return -EINVAL; + + if (nfc_op->cmd_len == 0x01) { + nfc_op->cmd[1] = nfc_op->cmd[0]; + nfc_op->cmd[0] = 0; + } + + return 0; +} + +static int phytium_nfc_addr_correct(struct phytium_nfc_op *nfc_op) +{ + u32 len; + int i, j; + + if (!nfc_op) + return -EINVAL; + + len = nfc_op->addr_len > PHYTIUM_NFC_ADDR_MAX_LEN ? + PHYTIUM_NFC_ADDR_MAX_LEN : nfc_op->addr_len; + + if (len == PHYTIUM_NFC_ADDR_MAX_LEN) + return 0; + + for (i = len-1, j = PHYTIUM_NFC_ADDR_MAX_LEN - 1; i >= 0; i--, j--) { + nfc_op->addr[j] = nfc_op->addr[i]; + nfc_op->addr[i] = 0; + } + + return 0; +} + +static void phytium_nfc_parse_instructions(struct nand_chip *chip, + const struct nand_subop *subop, + struct phytium_nfc_op *nfc_op) +{ + struct nand_op_instr *instr = NULL; + bool first_cmd = true; + u32 op_id; + int i; + + /* Reset the input structure as most of its fields will be OR'ed */ + memset(nfc_op, 0, sizeof(struct phytium_nfc_op)); + + for (op_id = 0; op_id < subop->ninstrs; op_id++) { + unsigned int offset, naddrs; + const u8 *addrs; + int len; + + instr = (struct nand_op_instr *)&subop->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + if (first_cmd) { + nfc_op->cmd[0] = instr->ctx.cmd.opcode; + } else { + nfc_op->cmd[1] = instr->ctx.cmd.opcode; + nfc_op->cmd_ctrl.nfc_ctrl.dbc = 1; + } + + nfc_op->cle_ale_delay_ns = instr->delay_ns; + first_cmd = false; + nfc_op->cmd_len++; + + break; + + case NAND_OP_ADDR_INSTR: + offset = nand_subop_get_addr_start_off(subop, op_id); + naddrs = nand_subop_get_num_addr_cyc(subop, op_id); + addrs = &instr->ctx.addr.addrs[offset]; + + nfc_op->cmd_ctrl.nfc_ctrl.addr_cyc = naddrs; + + for (i = 0; i < min_t(u32, PHYTIUM_NFC_ADDR_MAX_LEN, naddrs); i++) + nfc_op->addr[i] = addrs[i]; + + nfc_op->cle_ale_delay_ns = instr->delay_ns; + + nfc_op->addr_len = naddrs; + break; + + case NAND_OP_DATA_IN_INSTR: + nfc_op->data_instr = instr; + nfc_op->data_instr_idx = op_id; + nfc_op->cmd_ctrl.nfc_ctrl.dc = 1; + len = nand_subop_get_data_len(subop, op_id); + nfc_op->page_cnt = len; + nfc_op->data_delay_ns = instr->delay_ns; + + break; + + case NAND_OP_DATA_OUT_INSTR: + nfc_op->data_instr = instr; + nfc_op->data_instr_idx = op_id; + nfc_op->cmd_ctrl.nfc_ctrl.dc = 1; + len = nand_subop_get_data_len(subop, op_id); + nfc_op->page_cnt = len; + nfc_op->data_delay_ns = instr->delay_ns; + break; + + case NAND_OP_WAITRDY_INSTR: + nfc_op->rdy_timeout_ms = instr->ctx.waitrdy.timeout_ms; + nfc_op->rdy_delay_ns = instr->delay_ns; + break; + } + } +} + +int phytium_nfc_prepare_cmd(struct nand_chip *chip, + struct phytium_nfc_op *nfc_op, + enum dma_data_direction direction) +{ + struct phytium_nand_chip *phytium_nand = to_phytium_nand(chip); + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + int i; + + phytium_nfc_cmd_correct(nfc_op); + phytium_nfc_addr_correct(nfc_op); + + nfc_op->cmd_ctrl.nfc_ctrl.csel = 0x0F ^ (0x01 << phytium_nand->selected_die); + + for (i = 0; i < PHYTIUM_NFC_ADDR_MAX_LEN; i++) + nfc_op->mem_addr_first[i] = (nfc->dma_phy_addr >> (8 * i)) & 0xFF; + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_nfc_prepare_cmd); + +int phytium_nfc_send_cmd(struct nand_chip *chip, + struct phytium_nfc_op *nfc_op) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + u32 value = 0; + + memset((u8 *)nfc->dsp_addr, 0, PAGE_SIZE); + memcpy((u8 *)nfc->dsp_addr, (u8 *)nfc_op, PHYTIUM_NFC_DSP_SIZE); + + if (phytium_wait_busy(nfc) != 0) { + dev_err(nfc->dev, "NFC was always busy\n"); + dev_err(nfc->dev, "NFC state: %x\n", phytium_read(nfc, NDSR)); + dev_err(nfc->dev, "NFC debug: %x\n", phytium_read(nfc, ND_DEBUG)); + return 0; + } + + spin_lock(&nfc->spinlock); + value = nfc->dsp_phy_addr & 0xFFFFFFFF; + phytium_write(nfc, NDAR0, value); + + /* Don't modify NDAR1_DMA_RLEN & NDAR1_DMA_WLEN */ + value = phytium_read(nfc, NDAR1); + value |= NDAR1_H8((nfc->dsp_phy_addr >> 32) & 0xFF); + phytium_write(nfc, NDAR1, value); + + phytium_nfc_enable_int(nfc, NDIR_CMD_FINISH_MASK); + + value |= NDAR1_DMA_EN; + phytium_write(nfc, NDAR1, value); + spin_unlock(&nfc->spinlock); + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_nfc_send_cmd); + +int phytium_nfc_prepare_cmd2(struct nand_chip *chip, + struct phytium_nfc_op *nfc_op, + enum dma_data_direction direction, + u32 cmd_num) +{ + struct phytium_nand_chip *phytium_nand = to_phytium_nand(chip); + int i; + + for (i = 0; i < cmd_num; i++) { + phytium_nfc_cmd_correct(nfc_op); + phytium_nfc_addr_correct(nfc_op); + nfc_op->cmd_ctrl.nfc_ctrl.csel = 0x0F ^ (0x01 << phytium_nand->selected_die); + nfc_op++; + } + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_nfc_prepare_cmd2); + +int phytium_nfc_send_cmd2(struct nand_chip *chip, + struct phytium_nfc_op *nfc_op, + u32 cmd_num) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + u32 value = 0; + int i; + + memset((u8 *)nfc->dsp_addr, 0, PAGE_SIZE); + + for (i = 0; i < cmd_num; i++) { + memcpy((u8 *)nfc->dsp_addr + i*PHYTIUM_NFC_DSP_SIZE, + (u8 *)nfc_op, PHYTIUM_NFC_DSP_SIZE); + nfc_op++; + } + + if (phytium_wait_busy(nfc) != 0) { + dev_err(nfc->dev, "NFC was always busy\n"); + dev_err(nfc->dev, "NFC state: %x\n", phytium_read(nfc, NDSR)); + dev_err(nfc->dev, "NFC debug: %x\n", phytium_read(nfc, ND_DEBUG)); + return 0; + } + + spin_lock(&nfc->spinlock); + value = nfc->dsp_phy_addr & 0xFFFFFFFF; + phytium_write(nfc, NDAR0, value); + + /* Don't modify NDAR1_DMA_RLEN & NDAR1_DMA_WLEN */ + value = phytium_read(nfc, NDAR1); + value |= NDAR1_H8((nfc->dsp_phy_addr >> 32) & 0xFF); + phytium_write(nfc, NDAR1, value); + + phytium_nfc_enable_int(nfc, NDIR_DMA_FINISH_MASK | NDIR_ECC_ERR_MASK); + + value |= NDAR1_DMA_EN; + phytium_write(nfc, NDAR1, value); + spin_unlock(&nfc->spinlock); + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_nfc_send_cmd2); + +int phytium_nfc_wait_op(struct nand_chip *chip, + u32 timeout_ms) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + int ret; + + /* Timeout is expressed in ms */ + if (!timeout_ms) + timeout_ms = IRQ_TIMEOUT; + else if (timeout_ms > 1000) + timeout_ms = 1000; + else if (timeout_ms < 100) + timeout_ms = 100; + + ret = wait_event_interruptible_timeout(wait_done, nfc_irq_complete, + msecs_to_jiffies(timeout_ms)); + nfc_irq_complete = false; + if (!ret) { + dev_err(nfc->dev, "Timeout waiting for RB signal\n"); + dev_err(nfc->dev, "NFC state: %x\n", phytium_read(nfc, NDSR)); + dev_err(nfc->dev, "NFC irq state: %x, irq en:%x\n", + phytium_read(nfc, NDIR), phytium_read(nfc, NDIR_MASK)); + dev_err(nfc->dev, "NFC debug: %x\n", phytium_read(nfc, ND_DEBUG)); + + phytium_nfc_clear_int(nfc, NDIR_ALL_INT(nfc->caps->int_mask_bits)); + return -ETIMEDOUT; + } + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_nfc_wait_op); + +static int phytium_nfc_xfer_data_pio(struct phytium_nfc *nfc, + const struct nand_subop *subop, + struct phytium_nfc_op *nfc_op) +{ + const struct nand_op_instr *instr = nfc_op->data_instr; + unsigned int op_id = nfc_op->data_instr_idx; + unsigned int len = nand_subop_get_data_len(subop, op_id); + unsigned int offset = nand_subop_get_data_start_off(subop, op_id); + bool reading = (instr->type == NAND_OP_DATA_IN_INSTR); + + if (reading) { + u8 *in = instr->ctx.data.buf.in + offset; + + memcpy(in, nfc->dma_buf, len); + + nfc->dma_offset = 0; + } else { + const u8 *out = instr->ctx.data.buf.out + offset; + + memcpy(nfc->dma_buf, out, len); + } + + return 0; +} + +static int memcpy_to_reg16(struct phytium_nfc *nfc, u32 reg, u16 *buf, size_t len) +{ + int i; + u32 val = 0; + + if (!nfc || !buf || (len >= 16)) + return -EINVAL; + + for (i = 0; i < len; i++) { + val = (val << 16) + buf[i]; + if (i % 2) { + phytium_write(nfc, reg, val); + val = 0; + reg += 4; + } + } + + return 0; +} + +int phytium_nfc_default_data_interface(struct phytium_nfc *nfc) +{ + int value; + + value = phytium_read(nfc, NDCR0); + value &= (~NDCR0_IN_MODE(3)); + value |= NDCR0_IN_MODE(nfc->inter_mode); + phytium_write(nfc, NDCR0, value); + + value = phytium_read(nfc, NDCR1); + value &= (~NDCR1_SAMPL_PHASE(0xFFFF)); + + switch (nfc->inter_mode) { + case ASYN_SDR: + if (nfc->timing_mode == ASY_MODE4) { + memcpy_to_reg16(nfc, NDTR0, timing_asy_mode4, TIMING_ASY_NUM); + phytium_write(nfc, NDCR1, value | NDCR1_SAMPL_PHASE(4)); + } else if (nfc->timing_mode == ASY_MODE3) { + memcpy_to_reg16(nfc, NDTR0, timing_asy_mode3, TIMING_ASY_NUM); + phytium_write(nfc, NDCR1, value | NDCR1_SAMPL_PHASE(5)); + } else if (nfc->timing_mode == ASY_MODE2) { + memcpy_to_reg16(nfc, NDTR0, timing_asy_mode2, TIMING_ASY_NUM); + phytium_write(nfc, NDCR1, value | NDCR1_SAMPL_PHASE(3)); + } else if (nfc->timing_mode == ASY_MODE1) { + memcpy_to_reg16(nfc, NDTR0, timing_asy_mode1, TIMING_ASY_NUM); + phytium_write(nfc, NDCR1, value | NDCR1_SAMPL_PHASE(2)); + } else { + memcpy_to_reg16(nfc, NDTR0, timing_asy_mode0, TIMING_ASY_NUM); + phytium_write(nfc, NDCR1, value | NDCR1_SAMPL_PHASE(1)); + } + phytium_write(nfc, ND_INTERVAL_TIME, 0x01); + break; + case ONFI_DDR: + if (nfc->timing_mode == SYN_MODE4) { + memcpy_to_reg16(nfc, NDTR6, timing_syn_mode4, TIMING_SYN_NUM); + phytium_write(nfc, NDCR1, value | NDCR1_SAMPL_PHASE(0x0D)); + phytium_write(nfc, ND_INTERVAL_TIME, 0x30); + } else if (nfc->timing_mode == SYN_MODE3) { + memcpy_to_reg16(nfc, NDTR6, timing_syn_mode3, TIMING_SYN_NUM); + phytium_write(nfc, NDCR1, value | NDCR1_SAMPL_PHASE(0x05)); + phytium_write(nfc, ND_INTERVAL_TIME, 0x18); + } else if (nfc->timing_mode == SYN_MODE2) { + memcpy_to_reg16(nfc, NDTR6, timing_syn_mode2, TIMING_SYN_NUM); + phytium_write(nfc, NDCR1, value | NDCR1_SAMPL_PHASE(0x08)); + phytium_write(nfc, ND_INTERVAL_TIME, 0x20); + } else if (nfc->timing_mode == SYN_MODE1) { + memcpy_to_reg16(nfc, NDTR6, timing_syn_mode1, TIMING_SYN_NUM); + phytium_write(nfc, NDCR1, value | NDCR1_SAMPL_PHASE(0x12)); + phytium_write(nfc, ND_INTERVAL_TIME, 0x40); + } else { + memcpy_to_reg16(nfc, NDTR6, timing_syn_mode0, TIMING_SYN_NUM); + phytium_write(nfc, NDCR1, value | NDCR1_SAMPL_PHASE(0x12)); + phytium_write(nfc, ND_INTERVAL_TIME, 0x40); + } + break; + case TOG_ASYN_DDR: + phytium_write(nfc, NDCR1, value | NDCR1_SAMPL_PHASE(8)); + phytium_write(nfc, ND_INTERVAL_TIME, 0xC8); + memcpy_to_reg16(nfc, NDTR13, timing_tog_ddr_mode0, TIMING_TOG_NUM); + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_nfc_default_data_interface); + +static int phytium_nfc_naked_waitrdy_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + struct phytium_nfc_op nfc_op; + enum dma_data_direction direction; + int ret = 0; + + phytium_nfc_parse_instructions(chip, subop, &nfc_op); + + dev_info(nfc->dev, "Phytium nand command 0x%02x 0x%02x.\n", + nfc_op.cmd[0], nfc_op.cmd[1]); + + switch (nfc_op.cmd[0]) { + case NAND_CMD_PARAM: + memset(nfc->dma_buf, 0, PAGE_SIZE); + direction = DMA_FROM_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_READ_PARAM; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + if (nfc->inter_pro == NAND_ONFI) + nfc_op.page_cnt = 3 * sizeof(struct nand_onfi_params); + else if (nfc->inter_pro == NAND_JEDEC) + nfc_op.page_cnt = 3 * sizeof(struct nand_jedec_params); + if (nfc_op.page_cnt) + nfc_op.cmd_ctrl.nfc_ctrl.dc = 1; + nfc->dma_offset = 0; + break; + case NAND_CMD_SET_FEATURES: + direction = DMA_TO_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_SET_FTR; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + nfc_op.cmd_ctrl.nfc_ctrl.dc = 1; + if (nfc->inter_mode != ASYN_SDR) { + dev_err(nfc->dev, "Not support SET_FEATURES command!\n"); + return 0; + } + break; + case NAND_CMD_GET_FEATURES: + direction = DMA_FROM_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_GET_FTR; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + nfc_op.cmd_ctrl.nfc_ctrl.dc = 1; + break; + case NAND_CMD_READ0: + if (nfc_op.cmd[1] == NAND_CMD_READSTART) { /* large page */ + direction = DMA_FROM_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_READ; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + } else if (nfc_op.cmd[1] == NAND_CMD_SEQIN) { /* program page begin */ + nfc_op.cmd[0] = NAND_CMD_SEQIN; + nfc_op.cmd[1] = NAND_CMD_PAGEPROG; + direction = DMA_TO_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_PAGE_PRO; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + } else { /* small page */ + direction = DMA_FROM_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_READ; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + } + break; + case NAND_CMD_RNDOUT: /* change read column */ + direction = DMA_FROM_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_CH_READ_COL; + nfc_op.cmd_ctrl.nfc_ctrl.dc = 1; + break; + case NAND_CMD_READSTART: /* large page */ + direction = DMA_FROM_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_READ; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + break; + case NAND_CMD_RNDOUTSTART: /* change read column */ + direction = DMA_FROM_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_CH_READ_COL; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + nfc->dma_offset = nfc_op.addr[1]; + nfc->dma_offset = (nfc->dma_offset << 8) + nfc_op.addr[0]; + break; + case NAND_CMD_SEQIN: /* program begin */ + if (nfc_op.cmd[0] == NAND_CMD_READ0) { + nfc_op.cmd[0] = NAND_CMD_SEQIN; + nfc_op.cmd[1] = NAND_CMD_PAGEPROG; + } + direction = DMA_TO_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_PAGE_PRO; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + break; + case NAND_CMD_RNDIN: /* change write column */ + direction = DMA_TO_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_CH_WR_COL; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + break; + case NAND_CMD_PAGEPROG: /* program end */ + nfc_op.cmd[0] = NAND_CMD_RNDIN; + nfc_op.cmd[1] = NAND_CMD_PAGEPROG; + direction = DMA_TO_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_PAGE_PRO; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + break; + default: + dev_err(nfc->dev, "Not support cmd %d.\n", nfc_op.cmd[1]); + ret = -EINVAL; + goto out; + } + + if ((nfc_op.data_instr) && (direction == DMA_TO_DEVICE)) + phytium_nfc_xfer_data_pio(nfc, subop, &nfc_op); + + phytium_nfc_prepare_cmd(chip, &nfc_op, direction); + phytium_nfc_send_cmd(chip, &nfc_op); + cond_delay(nfc_op.cle_ale_delay_ns); + nfc_op.rdy_timeout_ms = nfc_op.rdy_timeout_ms; + ret = phytium_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + goto out; + + cond_delay(nfc_op.rdy_delay_ns); + + if ((nfc_op.data_instr) && (direction == DMA_FROM_DEVICE)) + phytium_nfc_xfer_data_pio(nfc, subop, &nfc_op); + +out: + return ret; +} + +static int phytium_nfc_read_id_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + struct phytium_nfc_op nfc_op; + enum dma_data_direction direction; + u16 read_len = 0; + int ret; + u8 *buf = nfc->dma_buf; + + memset(nfc->dma_buf, 0, PAGE_SIZE); + direction = DMA_FROM_DEVICE; + + phytium_nfc_parse_instructions(chip, subop, &nfc_op); + read_len = nfc_op.page_cnt; + nfc_op.page_cnt = (read_len & 0x03) ? ((read_len & 0xFFFC) + 4) : read_len; + + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_READ_ID; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 0; + + phytium_nfc_prepare_cmd(chip, &nfc_op, direction); + phytium_nfc_send_cmd(chip, &nfc_op); + cond_delay(nfc_op.cle_ale_delay_ns); + ret = phytium_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + return ret; + + cond_delay(nfc_op.rdy_delay_ns); + + if (!strncmp(nfc->dma_buf, "ONFI", 4)) { + nfc->inter_pro = NAND_ONFI; + } else if (!strncmp(nfc->dma_buf, "JEDEC", 5)) { + nfc->inter_pro = NAND_JEDEC; + if (buf[5] == 1) + nfc->inter_mode = ASYN_SDR; + else if (buf[5] == 2) + nfc->inter_mode = TOG_ASYN_DDR; + else if (buf[5] == 4) + nfc->inter_mode = ASYN_SDR; + } else { + nfc->inter_pro = NAND_OTHER; + } + + dev_info(nfc->dev, "Nand protocol: %d, interface mode: %d\n", + nfc->inter_pro, nfc->inter_mode); + + phytium_nfc_xfer_data_pio(nfc, subop, &nfc_op); + + return 0; +} + +static int phytium_nfc_read_status_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + struct phytium_nfc_op nfc_op; + enum dma_data_direction direction; + u16 read_len = 0; + u32 timeout, count = 0; + int ret = 0; + + direction = DMA_FROM_DEVICE; + + phytium_nfc_parse_instructions(chip, subop, &nfc_op); + read_len = nfc_op.page_cnt; + nfc_op.page_cnt = (read_len & 0x03) ? ((read_len & 0xFFFC) + 4) : read_len; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_READ_STATUS; + phytium_nfc_prepare_cmd(chip, &nfc_op, direction); + +read_status_retry: + count++; + phytium_nfc_send_cmd(chip, &nfc_op); + cond_delay(nfc_op.cle_ale_delay_ns); + timeout = nfc_op.rdy_timeout_ms ? nfc_op.rdy_timeout_ms : 10; + ret = phytium_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + goto out; + + phytium_nfc_xfer_data_pio(nfc, subop, &nfc_op); + + if (0xE0 != *(u8 *)(nfc->dma_buf)) { + dev_info(nfc->dev, "Retry to read status (%x)\n", *(u8 *)(nfc->dma_buf)); + + if (count < 10) + goto read_status_retry; + } + +out: + return ret; +} + +static int phytium_nfc_reset_cmd_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct phytium_nfc_op nfc_op; + enum dma_data_direction direction; + + phytium_nfc_parse_instructions(chip, subop, &nfc_op); + + direction = DMA_NONE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_RESET; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + phytium_nfc_prepare_cmd(chip, &nfc_op, direction); + phytium_nfc_send_cmd(chip, &nfc_op); + cond_delay(nfc_op.cle_ale_delay_ns); + + return phytium_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); +} + +static int phytium_nfc_erase_cmd_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct phytium_nfc_op nfc_op; + enum dma_data_direction direction; + + phytium_nfc_parse_instructions(chip, subop, &nfc_op); + direction = DMA_NONE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_ERASE; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + phytium_nfc_prepare_cmd(chip, &nfc_op, direction); + phytium_nfc_send_cmd(chip, &nfc_op); + cond_delay(nfc_op.cle_ale_delay_ns); + + return phytium_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); +} + +static int phytium_nfc_data_in_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + struct phytium_nfc_op nfc_op; + struct nand_op_instr *instr; + unsigned int op_id; + unsigned int len; + unsigned int offset; + u8 *in = NULL; + + phytium_nfc_parse_instructions(chip, subop, &nfc_op); + if (nfc_op.data_instr->type != NAND_OP_DATA_IN_INSTR) { + dev_err(nfc->dev, "Phytium nfc instrs parser failed!\n"); + return -EINVAL; + } + + instr = nfc_op.data_instr; + op_id = nfc_op.data_instr_idx; + len = nand_subop_get_data_len(subop, op_id); + offset = nand_subop_get_data_start_off(subop, op_id); + in = instr->ctx.data.buf.in + offset; + + memcpy(in, nfc->dma_buf + nfc->dma_offset, len); + nfc->dma_offset += len; + + return 0; +} + +static const struct nand_op_parser phytium_nfc_op_parser = NAND_OP_PARSER( + /* Naked commands not supported, use a function for each pattern */ + NAND_OP_PARSER_PATTERN( + phytium_nfc_read_id_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, PHYTIUM_NFC_ADDR_MAX_LEN), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 8)), + NAND_OP_PARSER_PATTERN( + phytium_nfc_erase_cmd_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, PHYTIUM_NFC_ADDR_MAX_LEN), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + NAND_OP_PARSER_PATTERN( + phytium_nfc_read_status_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 1)), + NAND_OP_PARSER_PATTERN( + phytium_nfc_reset_cmd_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + NAND_OP_PARSER_PATTERN( + phytium_nfc_naked_waitrdy_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, PHYTIUM_NFC_ADDR_MAX_LEN), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_CHUNK_SIZE)), + NAND_OP_PARSER_PATTERN( + phytium_nfc_naked_waitrdy_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, PHYTIUM_NFC_ADDR_MAX_LEN), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + NAND_OP_PARSER_PATTERN( + phytium_nfc_naked_waitrdy_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, PHYTIUM_NFC_ADDR_MAX_LEN), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 8), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + NAND_OP_PARSER_PATTERN( + phytium_nfc_naked_waitrdy_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, PHYTIUM_NFC_ADDR_MAX_LEN), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_CHUNK_SIZE)), + NAND_OP_PARSER_PATTERN( + phytium_nfc_data_in_type_exec, + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_CHUNK_SIZE)), + ); + +static int phytium_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + return nand_op_parser_exec_op(chip, &phytium_nfc_op_parser, + op, check_only); +} + +static int phytium_nfc_reset(struct phytium_nfc *nfc) +{ + u32 value; + + phytium_write(nfc, NDIR_MASK, NDIR_ALL_INT(nfc->caps->int_mask_bits)); + phytium_write(nfc, NDSR, NDIR_ALL_INT(nfc->caps->int_mask_bits)); + + phytium_write(nfc, ND_ERR_CLR, 0x0F); + phytium_write(nfc, NDFIFO_CLR, 1); + + value = phytium_read(nfc, NDCR0); + phytium_write(nfc, NDCR0, value & ~(NDCR0_ECC_EN | NDCR0_SPARE_EN)); + + return 0; +} + +static void phytium_nfc_select_chip(struct nand_chip *chip, int die_nr) +{ + struct phytium_nand_chip *phytium_nand = to_phytium_nand(chip); + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + + dev_dbg(nfc->dev, "Phytium nand selected chip %d\n", die_nr); + + if (chip == nfc->selected_chip && die_nr == phytium_nand->selected_die) + return; + + if (die_nr < 0 || die_nr >= phytium_nand->nsels) { + nfc->selected_chip = NULL; + phytium_nand->selected_die = -1; + return; + } + + phytium_nfc_reset(nfc); + + nfc->selected_chip = chip; + phytium_nand->selected_die = die_nr; +} + +static int phytium_nand_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + oobregion->length = chip->ecc.total; + oobregion->offset = mtd->oobsize - oobregion->length; + + return 0; +} + +static int phytium_nand_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + /* + * Bootrom looks in bytes 0 & 5 for bad blocks for the + * 4KB page / 4bit BCH combination. + */ + if (mtd->writesize >= SZ_4K) + oobregion->offset = 6; + else + oobregion->offset = 2; + + oobregion->length = mtd->oobsize - chip->ecc.total - oobregion->offset; + + return 0; +} + +static const struct mtd_ooblayout_ops phytium_nand_ooblayout_ops = { + .ecc = phytium_nand_ooblayout_ecc, + .free = phytium_nand_ooblayout_free, +}; + +static void phytium_nfc_enable_hw_ecc(struct nand_chip *chip) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + u32 ndcr0 = phytium_read(nfc, NDCR0); + + if (!(ndcr0 & NDCR0_ECC_EN)) + phytium_write(nfc, NDCR0, ndcr0 | NDCR0_ECC_EN); +} + +static void phytium_nfc_disable_hw_ecc(struct nand_chip *chip) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + u32 ndcr0 = phytium_read(nfc, NDCR0); + + if (ndcr0 & NDCR0_ECC_EN) + phytium_write(nfc, NDCR0, ndcr0 & ~NDCR0_ECC_EN); +} + +irqreturn_t phytium_nfc_isr(int irq, void *dev_id) +{ + struct phytium_nfc *nfc = dev_id; + u32 st = phytium_read(nfc, NDIR); + u32 ien = (~phytium_read(nfc, NDIR_MASK)) & NDIR_ALL_INT(nfc->caps->int_mask_bits); + + if (!(st & ien)) + return IRQ_NONE; + + nfc_irq_st = st; + nfc_irq_en = ien; + phytium_nfc_disable_int(nfc, st & NDIR_ALL_INT(nfc->caps->int_mask_bits)); + phytium_write(nfc, 0xFD0, 0); + + if (st & (NDIR_CMD_FINISH | NDIR_DMA_FINISH)) { + if (st & NDIR_ECC_ERR) + nfc_ecc_err = 1; + phytium_write(nfc, NDIR, st); + nfc_irq_complete = 1; + } else if (st & (NDIR_FIFO_TIMEOUT | NDIR_PGFINISH)) { + phytium_write(nfc, NDIR, st); + phytium_nfc_enable_int(nfc, (~st) & (NDIR_DMA_FINISH_MASK | + NDIR_PGFINISH_MASK | + NDIR_FIFO_TIMEOUT_MASK | + NDIR_CMD_FINISH_MASK)); + nfc_irq_complete = 0; + } else if (st & NDIR_ECC_ERR) { + phytium_write(nfc, ND_ERR_CLR, 0x08); + phytium_write(nfc, NDIR, st); + phytium_write(nfc, NDFIFO_CLR, 0x01); + nfc_irq_complete = 1; + nfc_ecc_errover = 1; + } else { + phytium_write(nfc, NDIR, st); + nfc_irq_complete = 1; + } + + wake_up(&wait_done); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL(phytium_nfc_isr); + +static int phytium_nfc_hw_ecc_correct(struct nand_chip *chip, + char *buf, int len) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + u32 i, j, value, tmp; + u32 ecc_err_reg_nums; + int stat = 0; + + if (!buf) + return -EINVAL; + + if (nfc->caps->hw_ver == 1) + ecc_err_reg_nums = 2; + else + ecc_err_reg_nums = 4; + + for (i = 0; i < chip->ecc.steps; i++) { + for (j = 0; j < ecc_err_reg_nums; j++) { + value = phytium_read(nfc, 0xB8 + 4 * (ecc_err_reg_nums * i + j)); + dev_info(nfc->dev, "ECC_FLAG: offset:%x value:0x%08x\n", + 0xB8 + 4 * (2 * i + j), value); + + tmp = value & 0xFFFF; + if (tmp && (tmp <= 4096)) { + tmp--; + stat++; + buf[chip->ecc.size * i + (tmp >> 3)] ^= (0x01 << tmp % 8); + dev_info(nfc->dev, "ECC_CORRECT %x %02x\n", + chip->ecc.size * i + (tmp >> 3), + buf[chip->ecc.size * i + (tmp >> 3)]); + dev_info(nfc->dev, "ECC_CORRECT xor %x %02x\n", + 0x01 << (tmp % 8), buf[chip->ecc.size * i + (tmp >> 3)]); + } else if (tmp > 4096) { + dev_info(nfc->dev, "ECC_CORRECT offset > 4096!\n"); + } + + tmp = (value >> 16) & 0xFFFF; + if (tmp && (tmp <= 4096)) { + tmp--; + stat++; + buf[chip->ecc.size * i + (tmp >> 3)] ^= (0x01 << tmp % 8); + dev_info(nfc->dev, "ECC_CORRECT %x %02x\n", + chip->ecc.size * i + (tmp >> 3), + buf[chip->ecc.size * i + (tmp >> 3)]); + dev_info(nfc->dev, "ECC_CORRECT xor %x %02x\n", + chip->ecc.size * i + (tmp >> 3), + buf[chip->ecc.size * i + (tmp >> 3)]); + } else if (tmp > 4096) { + dev_info(nfc->dev, "ECC_CORRECT offset > 4096!\n"); + } + } + } + + return stat; +} + +static int phytium_nand_page_read(struct mtd_info *mtd, struct nand_chip *chip, + u8 *buf, u8 *oob_buf, int oob_len, int page, + bool read) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + struct phytium_nand_chip *phytium_nand = NULL; + const struct nand_sdr_timings *sdr = NULL; + struct phytium_nfc_op nfc_op; + enum dma_data_direction direction; + int ret = 0; + + memset(&nfc_op, 0, sizeof(nfc_op)); + phytium_nand = to_phytium_nand(chip); + sdr = nand_get_sdr_timings(nand_get_interface_config(&phytium_nand->chip)); + + memset(nfc->dma_buf, 0x0, mtd->writesize + mtd->oobsize); + direction = DMA_FROM_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_READ; + nfc_op.cmd[0] = NAND_CMD_READ0; + nfc_op.cmd[1] = NAND_CMD_READSTART; + nfc_op.cmd_len = 2; + nfc_op.cle_ale_delay_ns = PSEC_TO_NSEC(sdr->tWB_max); + nfc_op.rdy_timeout_ms = PSEC_TO_MSEC(sdr->tR_max); + nfc_op.rdy_delay_ns = PSEC_TO_NSEC(sdr->tRR_min); + + nfc_op.cmd_ctrl.nfc_ctrl.dbc = 1; + nfc_op.addr[2] = page; + nfc_op.addr[3] = page >> 8; + nfc_op.addr[4] = page >> 16; + nfc_op.addr_len = 5; + nfc_op.cmd_ctrl.nfc_ctrl.addr_cyc = 0x05; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + + nfc_op.page_cnt = mtd->writesize; + nfc_op.cmd_ctrl.nfc_ctrl.dc = 1; + nfc_op.cmd_ctrl.nfc_ctrl.ecc_en = 0; + + /* For data read/program */ + phytium_nfc_prepare_cmd(chip, &nfc_op, direction); + phytium_nfc_send_cmd(chip, &nfc_op); + cond_delay(nfc_op.cle_ale_delay_ns); + + ret = phytium_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + return ret; + + if ((direction == DMA_FROM_DEVICE) && buf) + memcpy(buf, nfc->dma_buf, mtd->writesize); + + return ret; +} + +static int phytium_nand_oob_read(struct mtd_info *mtd, struct nand_chip *chip, + u8 *buf, u8 *oob_buf, int oob_len, int page, + bool read) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + struct phytium_nand_chip *phytium_nand = NULL; + const struct nand_sdr_timings *sdr = NULL; + struct phytium_nfc_op nfc_op; + enum dma_data_direction direction; + int ret = 0; + + memset(&nfc_op, 0, sizeof(nfc_op)); + phytium_nand = to_phytium_nand(chip); + sdr = nand_get_sdr_timings(nand_get_interface_config(&phytium_nand->chip)); + + memset(nfc->dma_buf, 0x00, mtd->writesize + mtd->oobsize); + direction = DMA_FROM_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_READ; + nfc_op.cmd[0] = NAND_CMD_READ0; + nfc_op.cmd[1] = NAND_CMD_READSTART; + nfc_op.cmd_len = 2; + nfc_op.cle_ale_delay_ns = PSEC_TO_NSEC(sdr->tWB_max); + nfc_op.rdy_timeout_ms = PSEC_TO_MSEC(sdr->tR_max); + nfc_op.rdy_delay_ns = PSEC_TO_NSEC(sdr->tRR_min); + + nfc_op.cmd_ctrl.nfc_ctrl.dbc = 1; + nfc_op.addr[2] = page; + nfc_op.addr[3] = page >> 8; + nfc_op.addr[4] = page >> 16; + nfc_op.addr_len = 5; + nfc_op.cmd_ctrl.nfc_ctrl.addr_cyc = 0x05; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + + nfc_op.page_cnt = oob_len; + nfc_op.cmd_ctrl.nfc_ctrl.dc = 1; + nfc_op.cmd_ctrl.nfc_ctrl.ecc_en = 0; + nfc_op.addr[0] = mtd->writesize & 0xFF; + nfc_op.addr[1] = (mtd->writesize >> 8) & 0xFF; + + /* For data read/program */ + phytium_nfc_prepare_cmd(chip, &nfc_op, direction); + phytium_nfc_send_cmd(chip, &nfc_op); + cond_delay(nfc_op.cle_ale_delay_ns); + + ret = phytium_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + return ret; + + cond_delay(nfc_op.rdy_delay_ns); + + if (direction == DMA_FROM_DEVICE) + memcpy(oob_buf, nfc->dma_buf, oob_len); + + return ret; +} + +static int phytium_nand_get_ecc_total(struct mtd_info *mtd, + struct nand_ecc_ctrl *ecc) +{ + int ecc_total = 0; + + switch (mtd->writesize) { + case 0x200: + if (ecc->strength == 8) + ecc_total = 0x0D; + else if (ecc->strength == 4) + ecc_total = 7; + else if (ecc->strength == 2) + ecc_total = 4; + else + ecc_total = 0; + break; + case 0x800: + if (ecc->strength == 8) + ecc_total = 0x34; + else if (ecc->strength == 4) + ecc_total = 0x1a; + else if (ecc->strength == 2) + ecc_total = 0xd; + else + ecc_total = 0; + break; + case 0x1000: + if (ecc->strength == 8) + ecc_total = 0x68; + else if (ecc->strength == 4) + ecc_total = 0x34; + else if (ecc->strength == 2) + ecc_total = 0x1a; + else + ecc_total = 0; + break; + case 0x2000: + if (ecc->strength == 8) + ecc_total = 0xD0; + else if (ecc->strength == 4) + ecc_total = 0x68; + else if (ecc->strength == 2) + ecc_total = 0x34; + else + ecc_total = 0; + break; + case 0x4000: + if (ecc->strength == 8) + ecc_total = 0x1A0; + if (ecc->strength == 4) + ecc_total = 0xD0; + else if (ecc->strength == 2) + ecc_total = 0x68; + else + ecc_total = 0; + break; + default: + ecc_total = 0; + break; + } + + return ecc_total; +} + +static int phytium_nand_page_read_hwecc(struct mtd_info *mtd, struct nand_chip *chip, + u8 *buf, u8 *oob_buf, int oob_len, int page, + bool read) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + struct phytium_nand_chip *phytium_nand = NULL; + const struct nand_sdr_timings *sdr = NULL; + struct phytium_nfc_op *nfc_op = NULL; + enum dma_data_direction direction; + u32 ecc_offset; + int max_bitflips = 0; + u32 nfc_state = 0; + int ret = 0; + int i; + + phytium_nand = to_phytium_nand(chip); + sdr = nand_get_sdr_timings(nand_get_interface_config(&phytium_nand->chip)); + + ecc_offset = phytium_nand->ecc.offset; + memset(nfc->dma_buf, 0x00, mtd->writesize + mtd->oobsize); + nfc_op = kzalloc(2 * sizeof(struct phytium_nfc_op), GFP_KERNEL); + if (!nfc_op) { + dev_err(nfc->dev, "Can't malloc space for phytium_nfc_op\n"); + return 0; + } + + nfc_op->cle_ale_delay_ns = PSEC_TO_NSEC(sdr->tWB_max); + nfc_op->rdy_timeout_ms = PSEC_TO_MSEC(sdr->tR_max); + nfc_op->rdy_delay_ns = PSEC_TO_NSEC(sdr->tRR_min); + + direction = DMA_FROM_DEVICE; + nfc_op->cmd_ctrl.nfc_ctrl.cmd_type = TYPE_READ; + nfc_op->cmd[0] = NAND_CMD_READ0; + nfc_op->cmd[1] = NAND_CMD_READSTART; + nfc_op->cmd_len = 2; + nfc_op->addr_len = 5; + nfc_op->cmd_ctrl.nfc_ctrl.dbc = 1; + nfc_op->addr[2] = page; + nfc_op->addr[3] = page >> 8; + nfc_op->addr[4] = page >> 16; + nfc_op->cmd_ctrl.nfc_ctrl.addr_cyc = 0x05; + nfc_op->cmd_ctrl.nfc_ctrl.dc = 1; + nfc_op->cmd_ctrl.nfc_ctrl.auto_rs = 1; + nfc_op->page_cnt = mtd->writesize; + nfc_op->cmd_ctrl.nfc_ctrl.nc = 1; + for (i = 0; i < PHYTIUM_NFC_ADDR_MAX_LEN; i++) + nfc_op->mem_addr_first[i] = (nfc->dma_phy_addr >> (8 * i)) & 0xFF; + + nfc_op++; + memcpy(nfc_op, nfc_op - 1, sizeof(struct phytium_nfc_op)); + nfc_op->cmd_ctrl.nfc_ctrl.cmd_type = TYPE_CH_READ_COL; + nfc_op->cmd[0] = NAND_CMD_RNDOUT; + nfc_op->cmd[1] = NAND_CMD_RNDOUTSTART; + memset(&nfc_op->addr, 0, PHYTIUM_NFC_ADDR_MAX_LEN); + nfc_op->addr_len = 2; + nfc_op->addr[0] = mtd->writesize + phytium_nand->ecc.offset; + nfc_op->addr[1] = (mtd->writesize + phytium_nand->ecc.offset) >> 8; + nfc_op->cmd_ctrl.nfc_ctrl.addr_cyc = 0x02; + nfc_op->page_cnt = phytium_nand_get_ecc_total(mtd, &chip->ecc); + nfc_op->cmd_ctrl.nfc_ctrl.nc = 0; + nfc_op->cmd_ctrl.nfc_ctrl.auto_rs = 0; + nfc_op->cmd_ctrl.nfc_ctrl.ecc_en = 1; + for (i = 0; i < PHYTIUM_NFC_ADDR_MAX_LEN; i++) + nfc_op->mem_addr_first[i] = + ((nfc->dma_phy_addr + mtd->writesize) >> (8 * i)) & 0xFF; + + nfc_op--; + phytium_nfc_prepare_cmd2(chip, nfc_op, direction, 2); + phytium_nfc_send_cmd2(chip, nfc_op, 2); + cond_delay(nfc_op->cle_ale_delay_ns); + + ret = phytium_nfc_wait_op(chip, nfc_op->rdy_timeout_ms); + if (ret) { + kfree(nfc_op); + return ret; + } + + cond_delay(nfc_op->rdy_delay_ns * 1000); + + if ((direction == DMA_FROM_DEVICE) && buf) { + nfc_state = phytium_read(nfc, NDSR); + if ((nfc_state & NDSR_ECC_ERROVER) || (nfc_ecc_errover == 1)) { + for (i = 0; i < mtd->writesize/16; i++) { + if (0xFF != *(u8 *)(nfc->dma_buf + i)) { + dev_info(nfc->dev, "NFC: NDSR_ECC_ERROVER %x\n", page); + mtd->ecc_stats.failed++; + mtd->ecc_stats.corrected += max_bitflips; + break; + } + } + } else if (nfc_state & NDSR_ECC_ERR) { + max_bitflips = phytium_nfc_hw_ecc_correct(chip, + nfc->dma_buf, mtd->writesize); + mtd->ecc_stats.corrected += max_bitflips; + dev_info(nfc->dev, "NFC: NDSR_ECC_ERR page:%x, bit:%d\n", + page, max_bitflips); + } + + memcpy(buf, nfc->dma_buf, mtd->writesize); + } + + kfree(nfc_op); + return max_bitflips; +} + +static int phytium_nand_page_write(struct mtd_info *mtd, struct nand_chip *chip, + const u8 *buf, u8 *oob_buf, int oob_len, int page, + bool read) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + struct phytium_nand_chip *phytium_nand = NULL; + const struct nand_sdr_timings *sdr = NULL; + struct phytium_nfc_op nfc_op; + enum dma_data_direction direction; + int ret = 0; + + memset(&nfc_op, 0, sizeof(nfc_op)); + phytium_nand = to_phytium_nand(chip); + sdr = nand_get_sdr_timings(nand_get_interface_config(&phytium_nand->chip)); + + memcpy(nfc->dma_buf, buf, mtd->writesize); + direction = DMA_TO_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_PAGE_PRO; + nfc_op.cmd[0] = NAND_CMD_SEQIN; + nfc_op.cmd[1] = NAND_CMD_PAGEPROG; + nfc_op.cmd_len = 2; + nfc_op.addr_len = 5; + nfc_op.cle_ale_delay_ns = PSEC_TO_NSEC(sdr->tWB_max); + nfc_op.rdy_timeout_ms = PSEC_TO_MSEC(sdr->tPROG_max); + nfc_op.rdy_delay_ns = 0; + + nfc_op.cmd_ctrl.nfc_ctrl.dbc = 1; + nfc_op.addr[2] = page; + nfc_op.addr[3] = page >> 8; + nfc_op.addr[4] = page >> 16; + nfc_op.cmd_ctrl.nfc_ctrl.addr_cyc = 0x05; + nfc_op.cmd_ctrl.nfc_ctrl.dc = 1; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + nfc_op.page_cnt = mtd->writesize; + + /* For data read/program */ + phytium_nfc_prepare_cmd(chip, &nfc_op, direction); + phytium_nfc_send_cmd(chip, &nfc_op); + cond_delay(nfc_op.cle_ale_delay_ns); + + ret = phytium_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + goto out; + + cond_delay(nfc_op.rdy_delay_ns); +out: + return ret; +} + +static int phytium_nand_oob_write(struct mtd_info *mtd, struct nand_chip *chip, + u8 *buf, u8 *oob_buf, int oob_len, int page, + bool read) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + struct phytium_nand_chip *phytium_nand = NULL; + const struct nand_sdr_timings *sdr = NULL; + struct phytium_nfc_op nfc_op; + enum dma_data_direction direction; + int ret = 0; + + memset(&nfc_op, 0, sizeof(nfc_op)); + phytium_nand = to_phytium_nand(chip); + sdr = nand_get_sdr_timings(nand_get_interface_config(&phytium_nand->chip)); + + direction = DMA_TO_DEVICE; + nfc_op.cmd_ctrl.nfc_ctrl.cmd_type = TYPE_PAGE_PRO; + nfc_op.cmd[0] = NAND_CMD_SEQIN; + nfc_op.cmd[1] = NAND_CMD_PAGEPROG; + nfc_op.cmd_len = 2; + nfc_op.addr_len = 5; + nfc_op.cle_ale_delay_ns = PSEC_TO_NSEC(sdr->tWB_max); + nfc_op.rdy_timeout_ms = PSEC_TO_MSEC(sdr->tPROG_max); + nfc_op.rdy_delay_ns = 0; + + nfc_op.cmd_ctrl.nfc_ctrl.dbc = 1; + nfc_op.addr[2] = page; + nfc_op.addr[3] = page >> 8; + nfc_op.addr[4] = page >> 16; + nfc_op.cmd_ctrl.nfc_ctrl.addr_cyc = 0x05; + nfc_op.cmd_ctrl.nfc_ctrl.dc = 1; + nfc_op.cmd_ctrl.nfc_ctrl.auto_rs = 1; + + nfc_op.page_cnt = oob_len; + nfc_op.cmd_ctrl.nfc_ctrl.ecc_en = 0; + nfc_op.addr[0] = mtd->writesize & 0xFF; + nfc_op.addr[1] = (mtd->writesize >> 8) & 0xFF; + nfc_op.cmd_ctrl.nfc_ctrl.ecc_en = 0; + memcpy(nfc->dma_buf, oob_buf, mtd->oobsize); + + /* For data read/program */ + phytium_nfc_prepare_cmd(chip, &nfc_op, direction); + phytium_nfc_send_cmd(chip, &nfc_op); + cond_delay(nfc_op.cle_ale_delay_ns); + + ret = phytium_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + goto out; + + cond_delay(nfc_op.rdy_delay_ns); +out: + return ret; +} + +static int phytium_nand_page_write_hwecc(struct mtd_info *mtd, struct nand_chip *chip, + const u8 *buf, u8 *oob_buf, int oob_len, int page, + bool read) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + struct phytium_nand_chip *phytium_nand = NULL; + const struct nand_sdr_timings *sdr = NULL; + struct phytium_nfc_op *nfc_op; + enum dma_data_direction direction; + u32 ecc_offset; + int ret = 0; + int i; + + phytium_nand = to_phytium_nand(chip); + sdr = nand_get_sdr_timings(nand_get_interface_config(&phytium_nand->chip)); + ecc_offset = phytium_nand->ecc.offset; + + nfc_op = kzalloc(2 * sizeof(struct phytium_nfc_op), GFP_KERNEL); + if (!nfc_op) + return 0; + + nfc_op->cle_ale_delay_ns = PSEC_TO_NSEC(sdr->tWB_max); + nfc_op->rdy_timeout_ms = PSEC_TO_MSEC(sdr->tR_max); + nfc_op->rdy_delay_ns = PSEC_TO_NSEC(sdr->tRR_min); + + direction = DMA_TO_DEVICE; + nfc_op->cmd_ctrl.nfc_ctrl.cmd_type = TYPE_CH_ROW_ADDR; + nfc_op->cmd[0] = NAND_CMD_SEQIN; + nfc_op->cmd_len = 1; + nfc_op->addr_len = 5; + nfc_op->cmd_ctrl.nfc_ctrl.dbc = 0; + nfc_op->addr[2] = page; + nfc_op->addr[3] = page >> 8; + nfc_op->addr[4] = page >> 16; + nfc_op->cmd_ctrl.nfc_ctrl.addr_cyc = 0x05; + nfc_op->cmd_ctrl.nfc_ctrl.auto_rs = 0; + nfc_op->cmd_ctrl.nfc_ctrl.nc = 1; + for (i = 0; i < PHYTIUM_NFC_ADDR_MAX_LEN; i++) + nfc_op->mem_addr_first[i] = (nfc->dma_phy_addr >> (8 * i)) & 0xFF; + + /* The first dsp must have data to transfer */ + memcpy(nfc->dma_buf, buf, mtd->writesize); + nfc_op->page_cnt = mtd->writesize; + nfc_op->cmd_ctrl.nfc_ctrl.dc = 1; + + nfc_op++; + memcpy(nfc_op, nfc_op - 1, sizeof(struct phytium_nfc_op)); + nfc_op->cmd_ctrl.nfc_ctrl.cmd_type = TYPE_PAGE_PRO; + nfc_op->cmd_ctrl.nfc_ctrl.dbc = 1; + nfc_op->cmd_ctrl.nfc_ctrl.auto_rs = 1; + nfc_op->cmd[0] = NAND_CMD_RNDIN; + nfc_op->cmd[1] = NAND_CMD_PAGEPROG; + memset(&nfc_op->addr, 0, PHYTIUM_NFC_ADDR_MAX_LEN); + nfc_op->addr_len = 2; + nfc_op->cmd_len = 2; + nfc_op->addr[0] = mtd->writesize + ecc_offset; + nfc_op->addr[1] = (mtd->writesize + ecc_offset) >> 8; + nfc_op->cmd_ctrl.nfc_ctrl.addr_cyc = 0x02; + nfc_op->page_cnt = phytium_nand_get_ecc_total(mtd, &chip->ecc); + nfc_op->cmd_ctrl.nfc_ctrl.nc = 0; + nfc_op->cmd_ctrl.nfc_ctrl.dc = 1; + nfc_op->cmd_ctrl.nfc_ctrl.ecc_en = 1; + for (i = 0; i < PHYTIUM_NFC_ADDR_MAX_LEN; i++) + nfc_op->mem_addr_first[i] = + ((nfc->dma_phy_addr + mtd->writesize + ecc_offset) >> (8 * i)) & 0xFF; + + /* when enable ECC, must offer ecc_offset of oob, but no oobdata */ + nfc_op--; + phytium_nfc_prepare_cmd2(chip, nfc_op, direction, 2); + phytium_nfc_send_cmd2(chip, nfc_op, 2); + cond_delay(nfc_op->cle_ale_delay_ns); + + ret = phytium_nfc_wait_op(chip, nfc_op->rdy_timeout_ms); + if (ret) + goto out; + + cond_delay(nfc_op->rdy_delay_ns * 1000); +out: + kfree(nfc_op); + return ret; +} + +static int phytium_nfc_hw_ecc_bch_read_page_raw(struct nand_chip *chip, + u8 *buf, int oob_required, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + u32 oob_len = oob_required ? mtd->oobsize : 0; + int ret; + + ret = phytium_nand_page_read(mtd, chip, buf, NULL, 0, page, true); + if (oob_required) + ret = phytium_nand_oob_read(mtd, chip, NULL, chip->oob_poi, + oob_len, page, true); + + return ret; +} + +static int phytium_nfc_hw_ecc_bch_read_oob_raw(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + /* Invalidate page cache */ + chip->pagecache.page = -1; + memset(chip->oob_poi, 0xFF, mtd->oobsize); + + ret = phytium_nand_oob_read(mtd, chip, NULL, chip->oob_poi, + mtd->oobsize, page, true); + + return ret; +} + +static int phytium_nfc_hw_ecc_bch_read_page(struct nand_chip *chip, + u8 *buf, int oob_required, + int page) +{ + int ret; + struct mtd_info *mtd = nand_to_mtd(chip); + u32 oob_len = oob_required ? mtd->oobsize : 0; + struct phytium_nand_chip *phytium_nand = NULL; + + phytium_nand = to_phytium_nand(chip); + + phytium_nfc_enable_hw_ecc(chip); + cond_delay(20*1000); + + ret = phytium_nand_page_read_hwecc(mtd, chip, buf, NULL, + 0, page, true); + + phytium_nfc_disable_hw_ecc(chip); + + if (oob_required) { + oob_len = mtd->oobsize; + ret = phytium_nand_oob_read(mtd, chip, NULL, chip->oob_poi, + oob_len, page, true); + } + + return ret; +} + +static int phytium_nfc_hw_ecc_bch_read_oob(struct nand_chip *chip, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + u32 oob_len = mtd->oobsize; + int ret; + + ret = phytium_nand_oob_read(mtd, chip, NULL, chip->oob_poi, + oob_len, page, true); + + return ret; +} + +static int phytium_nfc_hw_ecc_bch_write_page_raw(struct nand_chip *chip, + const u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + void *oob_buf = oob_required ? chip->oob_poi : NULL; + + if (oob_required) + phytium_nand_oob_write(mtd, chip, NULL, oob_buf, + mtd->oobsize, page, false); + + return phytium_nand_page_write(mtd, chip, buf, NULL, + 0, page, false); +} + +static int phytium_nfc_hw_ecc_bch_write_page(struct nand_chip *chip, + const u8 *buf, + int oob_required, int page) +{ + int ret; + struct mtd_info *mtd = nand_to_mtd(chip); + void *oob_buf = oob_required ? chip->oob_poi : NULL; + u32 oob_len; + + if (oob_required) { + oob_len = mtd->oobsize; + phytium_nand_oob_write(mtd, chip, NULL, oob_buf, + oob_len, page, false); + } + + phytium_nfc_enable_hw_ecc(chip); + cond_delay(20*1000); + ret = phytium_nand_page_write_hwecc(mtd, chip, buf, NULL, + 0, page, false); + phytium_nfc_disable_hw_ecc(chip); + + return ret; +} + +static int phytium_nfc_hw_ecc_bch_write_oob_raw(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return phytium_nand_oob_write(mtd, chip, NULL, chip->oob_poi, + mtd->oobsize, page, false); +} + +static int phytium_nfc_hw_ecc_bch_write_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct phytium_nand_chip *phytium_nand = to_phytium_nand(chip); + u32 oob_len = mtd->oobsize - phytium_nand->ecc.length; + + return phytium_nand_oob_write(mtd, chip, NULL, chip->oob_poi, + oob_len, page, false); +} + +static int phytium_nand_hw_ecc_ctrl_init(struct mtd_info *mtd, + struct nand_ecc_ctrl *ecc) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if ((mtd->writesize + mtd->oobsize > MAX_CHUNK_SIZE)) + return -EOPNOTSUPP; + + chip->ecc.algo = NAND_ECC_ALGO_BCH; + ecc->read_page_raw = phytium_nfc_hw_ecc_bch_read_page_raw; + ecc->read_page = phytium_nfc_hw_ecc_bch_read_page; + ecc->read_oob_raw = phytium_nfc_hw_ecc_bch_read_oob_raw; + ecc->read_oob = phytium_nfc_hw_ecc_bch_read_oob; + ecc->write_page_raw = phytium_nfc_hw_ecc_bch_write_page_raw; + ecc->write_page = phytium_nfc_hw_ecc_bch_write_page; + ecc->write_oob_raw = phytium_nfc_hw_ecc_bch_write_oob_raw; + ecc->write_oob = phytium_nfc_hw_ecc_bch_write_oob; + + return 0; +} + +static int phytium_nand_ecc_init(struct mtd_info *mtd, + struct nand_ecc_ctrl *ecc) +{ + int ret = 0; + + mtd_set_ooblayout(mtd, &phytium_nand_ooblayout_ops); + + switch (ecc->engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: + ret = phytium_nand_hw_ecc_ctrl_init(mtd, ecc); + break; + case NAND_ECC_ENGINE_TYPE_NONE: + ecc->read_page_raw = phytium_nfc_hw_ecc_bch_read_page_raw; + ecc->read_oob_raw = phytium_nfc_hw_ecc_bch_read_oob; + ecc->write_page_raw = phytium_nfc_hw_ecc_bch_write_page_raw; + ecc->write_oob_raw = phytium_nfc_hw_ecc_bch_write_oob_raw; + ecc->read_page = ecc->read_page_raw; + ecc->read_oob = ecc->read_oob_raw; + ecc->write_page = ecc->write_page_raw; + ecc->write_oob = ecc->write_oob_raw; + break; + case NAND_ECC_ENGINE_TYPE_SOFT: + case NAND_ECC_ENGINE_TYPE_ON_DIE: + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static u8 bbt_pattern[] = {'P', 'H', 'Y', 'b', 't', '0' }; +static u8 bbt_mirror_pattern[] = {'1', 't', 'b', 'Y', 'H', 'P' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 8, + .len = 6, + .veroffs = 14, + .maxblocks = 8, /* Last 8 blocks in each chip */ + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 8, + .len = 6, + .veroffs = 14, + .maxblocks = 8, /* Last 8 blocks in each chip */ + .pattern = bbt_mirror_pattern +}; + +static int phytium_nand_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct phytium_nand_chip *phytium_nand = to_phytium_nand(chip); + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + u32 value; + int ret = 0; + + if (nfc->caps->flash_bbt) + chip->bbt_options |= NAND_BBT_USE_FLASH; + + if (chip->bbt_options & NAND_BBT_USE_FLASH) { + /* + * We'll use a bad block table stored in-flash and don't + * allow writing the bad block marker to the flash. + */ + chip->bbt_options |= NAND_BBT_NO_OOB_BBM; + chip->bbt_td = &bbt_main_descr; + chip->bbt_md = &bbt_mirror_descr; + } + + if (chip->options & NAND_BUSWIDTH_16) + phytium_nand->ndcr |= NDCR0_WIDTH; + + /* + * On small page NANDs, only one cycle is needed to pass the + * column address. + */ + if (mtd->writesize <= 512) + phytium_nand->addr_cyc = 1; + else + phytium_nand->addr_cyc = 2; + + /* + * Now add the number of cycles needed to pass the row + * address. + * + * Addressing a chip using CS 2 or 3 should also need the third row + * cycle but due to inconsistance in the documentation and lack of + * hardware to test this situation, this case is not supported. + */ + if (chip->options & NAND_ROW_ADDR_3) + phytium_nand->addr_cyc += 3; + else + phytium_nand->addr_cyc += 2; + + if (nfc->caps) { + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) { + chip->ecc.size = nfc->caps->ecc_step_size ? + nfc->caps->ecc_step_size : + chip->ecc.size; + chip->ecc.strength = nfc->caps->ecc_strength ? + nfc->caps->ecc_strength : + chip->ecc.strength; + chip->ecc.strength = nfc->caps->ecc_strength; + chip->ecc.bytes = 7; + } else { + chip->ecc.size = 512; + chip->ecc.strength = 1; + chip->ecc.bytes = 0; + } + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + } + + if (nfc->caps->hw_ver == 1) { + if (chip->ecc.strength == 0x04) + phytium_nand->ndcr |= NDCR0_ECC_STREN(4); + else if (chip->ecc.strength == 0x02) + phytium_nand->ndcr |= NDCR0_ECC_STREN(2); + else + phytium_nand->ndcr |= NDCR0_ECC_STREN(0); + } else { + if (chip->ecc.strength == 0x08) + phytium_nand->ndcr |= NDCR0_ECC_STREN(7); + else if (chip->ecc.strength == 0x04) + phytium_nand->ndcr |= NDCR0_ECC_STREN(3); + else if (chip->ecc.strength == 0x02) + phytium_nand->ndcr |= NDCR0_ECC_STREN(1); + else + phytium_nand->ndcr |= NDCR0_ECC_STREN(0); + } + + value = phytium_read(nfc, NDCR0); + value &= ~NDCR0_EN; + phytium_write(nfc, NDCR0, value); + + value &= ~NDCR0_ECC_STREN(7); + value |= phytium_nand->ndcr; + phytium_write(nfc, NDCR0, value | NDCR0_EN); + + ret = phytium_nand_ecc_init(mtd, &chip->ecc); + if (ret) { + dev_err(nfc->dev, "ECC init failed: %d\n", ret); + goto out; + } + + /* + * Subpage write not available with hardware ECC, prohibit also + * subpage read as in userspace subpage access would still be + * allowed and subpage write, if used, would lead to numerous + * uncorrectable ECC errors. + */ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) + chip->options |= NAND_NO_SUBPAGE_WRITE; + + /* + * We keep the MTD name unchanged to avoid breaking platforms + * where the MTD cmdline parser is used and the bootloader + * has not been updated to use the new naming scheme. + */ + if (nfc->caps->legacy_of_bindings) + mtd->name = "phytium_nand-0"; + +out: + return ret; +} + +static void phytium_nand_chips_cleanup(struct phytium_nfc *nfc) +{ + struct phytium_nand_chip *entry, *temp; + + list_for_each_entry_safe(entry, temp, &nfc->chips, node) { + /* Unregister device */ + WARN_ON(mtd_device_unregister(nand_to_mtd(&entry->chip))); + nand_cleanup(&entry->chip); + list_del(&entry->node); + } +} + +static int phytium_nfc_init_dma(struct phytium_nfc *nfc) +{ + int ret; + + ret = dma_set_mask_and_coherent(nfc->dev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + nfc->dsp_addr = dma_alloc_coherent(nfc->dev, PAGE_SIZE, + &nfc->dsp_phy_addr, GFP_KERNEL | GFP_DMA); + if (!nfc->dsp_addr) + return -ENOMEM; + + nfc->dma_buf = dma_alloc_coherent(nfc->dev, MAX_CHUNK_SIZE, + &nfc->dma_phy_addr, GFP_KERNEL | GFP_DMA); + if (!nfc->dma_buf) + return -ENOMEM; + + dev_info(nfc->dev, "NFC address dsp_phy_addr:%llx, dma_phy_addr:%llx\n", + nfc->dsp_phy_addr, nfc->dma_phy_addr); + + return 0; +} + +int phytium_nfc_init(struct phytium_nfc *nfc) +{ + u32 value; + + nfc->inter_mode = ASYN_SDR; + nfc->timing_mode = ASY_MODE0; + + value = phytium_read(nfc, NDCR1); + value &= (~NDCR1_SAMPL_PHASE(0xFFFF)); + value &= ~NDCR1_ECC_DATA_FIRST_EN; + value |= NDCR1_SAMPL_PHASE(1); + value |= NDCR1_ECC_BYPASS; + phytium_write(nfc, NDCR1, value); + phytium_write(nfc, ND_INTERVAL_TIME, 1); + phytium_write(nfc, NDFIFO_LEVEL0, 4); + phytium_write(nfc, NDFIFO_LEVEL1, 4); + phytium_write(nfc, NDFIFO_CLR, 1); + phytium_write(nfc, ND_ERR_CLR, 1); + + /* Configure the DMA */ + phytium_nfc_init_dma(nfc); + + + + phytium_nfc_reset(nfc); + + value = phytium_read(nfc, NDCR0); + value &= (~NDCR0_IN_MODE(3)); + value |= NDCR0_IN_MODE(nfc->inter_mode); + value |= NDCR0_EN; + + phytium_write(nfc, NDCR0, value); + + nfc_ecc_errover = 0; + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_nfc_init); + +static int phytium_nfc_setup_interface(struct nand_chip *chip, int chipnr, + const struct nand_interface_config *conf) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + unsigned int period_ns = 2; + const struct nand_sdr_timings *sdr; + struct phytium_nfc_timings nfc_tmg; + int read_delay; + + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + + nfc_tmg.tRP = TO_CYCLES(DIV_ROUND_UP(sdr->tRC_min, 2), period_ns) - 1; + nfc_tmg.tRH = nfc_tmg.tRP; + nfc_tmg.tWP = TO_CYCLES(DIV_ROUND_UP(sdr->tWC_min, 2), period_ns) - 1; + nfc_tmg.tWH = nfc_tmg.tWP; + nfc_tmg.tCS = TO_CYCLES(sdr->tCS_min, period_ns); + nfc_tmg.tCH = TO_CYCLES(sdr->tCH_min, period_ns) - 1; + nfc_tmg.tADL = TO_CYCLES(sdr->tADL_min, period_ns); + dev_info(nfc->dev, "[nfc_tmg]tRP: %d, tRH:%d, tWP:%d tWH:%d\n", + nfc_tmg.tRP, nfc_tmg.tRH, nfc_tmg.tWP, nfc_tmg.tWH); + dev_info(nfc->dev, "[nfc_tmg]tCS: %d, tCH:%d, tADL:%d\n", + nfc_tmg.tCS, nfc_tmg.tCH, nfc_tmg.tADL); + + read_delay = sdr->tRC_min >= 30000 ? + MIN_RD_DEL_CNT : MIN_RD_DEL_CNT + nfc_tmg.tRH; + + nfc_tmg.tAR = TO_CYCLES(sdr->tAR_min, period_ns); + nfc_tmg.tWHR = TO_CYCLES(max_t(int, sdr->tWHR_min, sdr->tCCS_min), + period_ns) - 2, + nfc_tmg.tRHW = TO_CYCLES(max_t(int, sdr->tRHW_min, sdr->tCCS_min), + period_ns); + dev_info(nfc->dev, "[nfc_tmg]tAR: %d, tWHR:%d, tRHW:%d\n", + nfc_tmg.tAR, nfc_tmg.tWHR, nfc_tmg.tRHW); + + nfc_tmg.tR = TO_CYCLES(sdr->tWB_max, period_ns); + + if (chipnr < 0) + return 0; + + if (nfc_tmg.tWP > 0x10) + nfc->timing_mode = ASY_MODE1; + else if (nfc_tmg.tWP < 0x0D) + nfc->timing_mode = ASY_MODE3; + + if (nfc->inter_mode == ONFI_DDR) + nfc->timing_mode = SYN_MODE3; + + phytium_nfc_default_data_interface(nfc); + + return 0; +} + +static int phytium_nand_chip_init(struct phytium_nfc *nfc) +{ + struct device *dev = nfc->dev; + struct phytium_nand_chip *phytium_nand; + struct mtd_info *mtd; + struct nand_chip *chip; + int ret; + + /* Alloc the nand chip structure */ + phytium_nand = devm_kzalloc(dev, sizeof(*phytium_nand), GFP_KERNEL); + if (!phytium_nand) + return -ENOMEM; + + phytium_nand->nsels = 1; + phytium_nand->selected_die = -1; + + chip = &phytium_nand->chip; + chip->controller = &nfc->controller; + chip->legacy.select_chip = phytium_nfc_select_chip; + phytium_nfc_default_data_interface(nfc); + + mtd = nand_to_mtd(chip); + mtd->dev.parent = dev; + mtd->owner = THIS_MODULE; + + /* + * Default to HW ECC engine mode. If the nand-ecc-mode property is given + * in the DT node, this entry will be overwritten in nand_scan_ident(). + */ + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + + chip->options |= NAND_BUSWIDTH_AUTO; + chip->options |= NAND_SKIP_BBTSCAN; + chip->bbt_options |= NAND_BBT_NO_OOB; + + ret = nand_scan(chip, phytium_nand->nsels); + if (ret) { + dev_err(dev, "could not scan the nand chip\n"); + goto out; + } + + if (nfc->caps->parts) { + ret = mtd_device_register(mtd, nfc->caps->parts, nfc->caps->nr_parts - 1); + } else if (dev->of_node) { + nand_set_flash_node(chip, dev->of_node); + ret = mtd_device_register(mtd, NULL, 0); + } else { + ret = -EINVAL; + } + + if (ret) { + dev_err(dev, "failed to register mtd device: %d\n", ret); + /* Unregister device */ + WARN_ON(mtd_device_unregister(mtd)); + nand_cleanup(mtd_to_nand(mtd)); + return ret; + } + + phytium_nand->ecc.length = phytium_nand_get_ecc_total(mtd, &chip->ecc); + phytium_nand->ecc.offset = mtd->oobsize - phytium_nand->ecc.length; + chip->ecc.total = phytium_nand_get_ecc_total(mtd, &chip->ecc); + + mtd_ooblayout_ecc(mtd, 0, &phytium_nand->ecc); + + dev_info(dev, "ooblayout ecc offset: %x, length: %x\n", + phytium_nand->ecc.offset, phytium_nand->ecc.length); + +out: + list_add_tail(&phytium_nand->node, &nfc->chips); + return 0; +} + +static const struct nand_controller_ops phytium_nand_controller_ops = { + .attach_chip = phytium_nand_attach_chip, + .exec_op = phytium_nfc_exec_op, + .setup_interface = phytium_nfc_setup_interface, +}; + +int phytium_nand_init(struct phytium_nfc *nfc) +{ + int ret; + + nand_controller_init(&nfc->controller); + nfc->controller.ops = &phytium_nand_controller_ops; + INIT_LIST_HEAD(&nfc->chips); + + /* Init the controller and then probe the chips */ + ret = phytium_nfc_init(nfc); + if (ret) + goto out; + + ret = phytium_nand_chip_init(nfc); + if (ret) + goto out; + + spin_lock_init(&nfc->spinlock); + +out: + return ret; +} +EXPORT_SYMBOL_GPL(phytium_nand_init); + +int phytium_nand_remove(struct phytium_nfc *nfc) +{ + phytium_nand_chips_cleanup(nfc); + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_nand_remove); + +static int phytium_nfc_wait_ndrun(struct nand_chip *chip) +{ + struct phytium_nfc *nfc = to_phytium_nfc(chip->controller); + int ret = 0; + u32 val; + + ret = readl_relaxed_poll_timeout(nfc->regs + NDSR, val, + (val & NDSR_RB) == 0, + 0, 100 * 1000); + if (ret) { + dev_err(nfc->dev, "Timeout on NAND controller run mode\n"); + ret = -EAGAIN; + } + + return ret; +} + +int phytium_nand_prepare(struct phytium_nfc *nfc) +{ + struct phytium_nand_chip *chip = NULL; + + list_for_each_entry(chip, &nfc->chips, node) + phytium_nfc_wait_ndrun(&chip->chip); + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_nand_prepare); + +int phytium_nand_resume(struct phytium_nfc *nfc) +{ + nfc->selected_chip = NULL; + phytium_nfc_init(nfc); + phytium_nfc_default_data_interface(nfc); + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_nand_resume); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium NAND controller platform driver"); +MODULE_AUTHOR("Zhu Mingshuai "); diff --git a/drivers/mtd/nand/raw/phytium_nand.h b/drivers/mtd/nand/raw/phytium_nand.h new file mode 100644 index 0000000000000000000000000000000000000000..c9347305f2e493ef1b7e61c002ee21348a8a8daa --- /dev/null +++ b/drivers/mtd/nand/raw/phytium_nand.h @@ -0,0 +1,449 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium NAND flash controller driver + * + * Copyright (C) 2020-2023, Phytium Technology, Co., Ltd. + */ +#ifndef PHYTIUM_NAND_H +#define PHYTIUM_NAND_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* NFC does not support transfers of larger chunks at a time */ +#define MAX_PAGE_NUM 16 +#define MAX_CHUNK_SIZE ((1024 + 76) * 16) + +#define POLL_PERIOD 0 +#define POLL_TIMEOUT 100000 +/* Interrupt maximum wait period in ms */ +#define IRQ_TIMEOUT 1000 + +/* Latency in clock cycles between SoC pins and NFC logic */ +#define MIN_RD_DEL_CNT 3 + +#define PHYTIUM_NFC_ADDR_MAX_LEN 5 +#define PHYTIUM_NFC_DSP_SIZE 16 + +/* NAND controller flash control register */ +#define NDCR0 0x00 +#define NDCR0_EN BIT(0) +#define NDCR0_WIDTH BIT(1) +#define NDCR0_IN_MODE(x) (min_t(u32, x, 0x3) << 2) +#define NDCR0_ECC_EN BIT(4) +#define NDCR0_ECC_STREN(x) (min_t(u32, x, 0x7) << 5) +#define NDCR0_SPARE_EN BIT(8) +#define NDCR0_SPARE_SIZE(x) (min_t(u32, x, 0xFFF) << 9) +#define NDCR0_GENERIC_FIELDS_MASK + +#define NDCR1 0x04 +#define NDCR1_SAMPL_PHASE(x) min_t(u32, x, 0xFFFF) +#define NDCR1_ECC_DATA_FIRST_EN BIT(16) +#define NDCR1_RB_SHARE_EN BIT(17) +#define NDCR1_ECC_BYPASS BIT(18) + +#define NDAR0 0x08 + +#define NDAR1 0x0C +#define NDAR1_H8(x) min_t(u32, x, 0xFF) +#define NDAR1_DMA_EN BIT(8) +#define NDAR1_EMPTY(x) (min_t(u32, x, 0x7F) << 9) +#define NDAR1_DMA_RLEN(x) (min_t(u32, x, 0xFF) << 9) +#define NDAR1_DMA_WLEN(x) (min_t(u32, x, 0xFF) << 9) + +#define NDTR0 0x10 +#define NDTR0_TCS_TCLS(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR0_TCLS_TWP(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR1 0x14 +#define NDTR1_TWH(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR1_TWP(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR2 0x18 +#define NDTR2_TCH_TCLH(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR2_TCLH_TWH(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR3 0x1c +#define NDTR3_TDQ_EN(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR3_TCH_TWH(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR4 0x20 +#define NDTR4_TWHR_SMX(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR4_TREH(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR5 0x24 +#define NDTR5_TRC(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR5_TADL_SMX(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR6 0x28 +#define NDTR6_TCAD_TCS_SMX(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR6_RES(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR7 0x2c +#define NDTR7_TCK(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR7_TDQ_EN(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR8 0x30 +#define NDTR8_TCAD_TCK_SMX(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR8_HF_TCK(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR9 0x34 +#define NDTR9_TWHR(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR9_TCCS_TCALS_SMX(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR10 0x38 +#define NDTR10_TCK(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR10_MTCK(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR11 0x3c +#define NDTR11_TCK_TCALS(x) (min_t(u32, x, 0xFFFF) << 16) +#define NDTR11_RES(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR12 0x40 +#define NDTR12_TWRCK(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR12_RES(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR13 0x44 +#define NDTR13_TWRHCA(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR13_TRLCA(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR14 0x48 +#define NDTR14_TWRHCE(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR14_RES(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR15 0x4c +#define NDTR15_TCDQSS_TWPRE_TDS(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR15_HFTDSC(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR16 0x50 +#define NDTR16_TWPST_TDH(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR16_TWPSTH(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR17 0x54 +#define NDTR17_TCS_TRPRE(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR17_TRELDQS(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDTR18 0x58 +#define NDTR18_TRPST_TDQSRE(x) (min_t(u32, x, 0xFFFF) << 0) +#define NDTR18_RES(x) (min_t(u32, x, 0xFFFF) << 16) + +#define NDFIFO 0x5c +#define NDFIFO_REV (min_t(u32, x, 0) << 12) +#define NDFIFO_FULL BIT(11) +#define NDFIFO_EMP BIT(10) +#define NDFIFO_CNT(x) (min_t(u32, x, 0x3F) << 0) + +#define ND_INTERVAL_TIME 0x60 +#define NDCMD_INTERVAL_TIME 0x64 +#define NDFIFO_TIMEOUT 0x68 +#define NDFIFO_LEVEL0 0x6c +#define NDFIFO_LEVEL1 0x70 +#define NDWP 0x74 +#define NDFIFO_CLR 0x78 + +#define NDSR 0x7c +#define NDSR_BUSY BIT(0) +#define NDSR_DMA_BUSY BIT(1) +#define NDSR_DMA_PGFINISH BIT(2) +#define NDSR_DMA_FINISH BIT(3) +#define NDSR_FIFO_EMP BIT(4) +#define NDSR_FIFO_FULL BIT(5) +#define NDSR_FIFO_TIMEOUT BIT(6) +#define NDSR_CS(x) (min_t(u32, x, 0xF) << 7) +#define NDSR_CMD_PGFINISH BIT(11) +#define NDSR_PG_PGFINISH BIT(12) +#define NDSR_RE BIT(13) +#define NDSR_DQS BIT(14) +#define NDSR_RB BIT(15) +#define NDSR_ECC_BUSY BIT(16) +#define NDSR_ECC_FINISH BIT(17) +#define NDSR_ECC_RIGHT BIT(18) +#define NDSR_ECC_ERR BIT(19) +#define NDSR_ECC_ERROVER BIT(20) +#define NDSR_AXI_DSP_ERR BIT(21) +#define NDSR_AXI_RD_ERR BIT(22) +#define NDSR_AXI_WR_ERR BIT(23) +#define NDSR_RB_STATUS(x) (min_t(u32, x, 0xF) << 24) +#define NDSR_PROT_ERR BIT(28) +#define NDSR_ECC_BYPASS BIT(29) + +#define NDIR_MASK 0x80 +#define NDIR_BUSY_MASK BIT(0) +#define NDIR_DMA_BUSY_MASK BIT(1) +#define NDIR_DMA_PGFINISH_MASK BIT(2) +#define NDIR_DMA_FINISH_MASK BIT(3) +#define NDIR_FIFO_EMP_MASK BIT(4) +#define NDIR_FIFO_FULL_MASK BIT(5) +#define NDIR_FIFO_TIMEOUT_MASK BIT(6) +#define NDIR_CMD_FINISH_MASK BIT(7) +#define NDIR_PGFINISH_MASK BIT(8) +#define NDIR_RE_MASK BIT(9) +#define NDIR_DQS_MASK BIT(10) +#define NDIR_RB_MASK BIT(11) +#define NDIR_ECC_FINISH_MASK BIT(12) +#define NDIR_ECC_ERR_MASK BIT(13) + +#define NDIR 0x84 +#define NDIR_ALL_INT(x) GENMASK(x, 0) +#define NDIR_BUSY BIT(0) +#define NDIR_DMA_BUSY BIT(1) +#define NDIR_DMA_PGFINISH BIT(2) +#define NDIR_DMA_FINISH BIT(3) +#define NDIR_FIFO_EMP BIT(4) +#define NDIR_FIFO_FULL BIT(5) +#define NDIR_FIFO_TIMEOUT BIT(6) +#define NDIR_CMD_FINISH BIT(7) +#define NDIR_PGFINISH BIT(8) +#define NDIR_RE BIT(9) +#define NDIR_DQS BIT(10) +#define NDIR_RB BIT(11) +#define NDIR_ECC_FINISH BIT(12) +#define NDIR_ECC_ERR BIT(13) + +#define ND_DEBUG 0x88 + +#define ND_ERR_CLR 0x8c +#define ND_DSP_ERR_CLR BIT(0) +#define ND_AXI_RD_ERR_CLR BIT(1) +#define ND_AXI_WR_ERR_CLR BIT(2) +#define ND_ECC_ERR_CLR BIT(3) + +#define NDCR_PAGE_SZ(x) (x >= 2048 ? BIT(24) : 0) + +enum nand_inter_pro { + NAND_ONFI, + NAND_JEDEC, + NAND_OTHER, +}; + +enum nand_inter_mode { + ASYN_SDR, + ONFI_DDR, + TOG_ASYN_DDR, +}; + +enum asy_timing_mode { + ASY_MODE0, + ASY_MODE1, + ASY_MODE2, + ASY_MODE3, + ASY_MODE4, +}; + +enum onfi_syn_timing_mode { + SYN_MODE0 = 0x10, + SYN_MODE1, + SYN_MODE2, + SYN_MODE3, + SYN_MODE4, +}; + +/** + * NAND controller timings expressed in NAND Controller clock cycles + * + * @tRP: ND_nRE pulse width + * @tRH: ND_nRE high duration + * @tWP: ND_nWE pulse time + * @tWH: ND_nWE high duration + * @tCS: Enable signal setup time + * @tCH: Enable signal hold time + * @tADL: Address to write data delay + * @tAR: ND_ALE low to ND_nRE low delay + * @tWHR: ND_nWE high to ND_nRE low for status read + * @tRHW: ND_nRE high duration, read to write delay + * @tR: ND_nWE high to ND_nRE low for read + */ +struct phytium_nfc_timings { + u16 tRP; + u16 tRH; + u16 tWP; /* NDTR1_TWP */ + u16 tWH; /* NDTR1_TWH */ + u16 tCS; + u16 tCH; + u16 tADL; + u16 tAR; + u16 tWHR; + u16 tRHW; + u16 tR; +}; + +/** + * NAND chip structure: stores NAND chip device related information + * + * @chip: Base NAND chip structure + * @node: Used to store NAND chips into a list + * @ndcr: Controller register value for this NAND chip + * @ndtr0: Timing registers 0 value for this NAND chip + * @ndtr1: Timing registers 1 value for this NAND chip + * @selected_die: Current active CS + * @nsels: Number of CS lines required by the NAND chip + */ +struct phytium_nand_chip { + struct nand_chip chip; + struct list_head node; + u32 ndcr; + u32 ndtr0; + u32 ndtr1; + int addr_cyc; + int selected_die; + unsigned int nsels; + struct mtd_oob_region ecc; +}; + +/** + * NAND controller capabilities for distinction between compatible strings + * + * @max_cs_nb: Number of Chip Select lines available + * @max_rb_nb: Number of Ready/Busy lines available + * @legacy_of_bindings: Indicates if DT parsing must be done using the old + * fashion way + * @flash_bbt: + * @ecc_strength: + * @ecc_step_size: + * @parts: + * @nr_parts: + */ +struct phytium_nfc_caps { + unsigned int hw_ver; + unsigned int int_mask_bits; + unsigned int max_cs_nb; + unsigned int max_rb_nb; + bool legacy_of_bindings; + bool flash_bbt; + int ecc_strength; + int ecc_step_size; + struct mtd_partition *parts; + unsigned int nr_parts; +}; + +/** + * NAND controller structure: stores Marvell NAND controller information + * + * @controller: Base controller structure + * @dev: Parent device (used to print error messages) + * @regs: NAND controller registers + * @reg_clk: Regiters clock + * @complete: Completion object to wait for NAND controller events + * @chips: List containing all the NAND chips attached to + * this NAND controller + * @caps: NAND controller capabilities for each compatible string + * @dma_buf: 32-bit aligned buffer for DMA transfers (NFCv1 only) + */ +struct phytium_nfc { + struct nand_controller controller; + struct device *dev; + void __iomem *regs; + int irq; + struct list_head chips; + struct nand_chip *selected_chip; + struct phytium_nfc_caps *caps; + + void *dsp_addr; + dma_addr_t dsp_phy_addr; + + void *dma_buf; + u32 dma_offset; + dma_addr_t dma_phy_addr; + + enum nand_inter_pro inter_pro; + enum nand_inter_mode inter_mode; + u32 timing_mode; + + spinlock_t spinlock; +}; + +/** + * Derives a duration in numbers of clock cycles. + * + * @ps: Duration in pico-seconds + * @period_ns: Clock period in nano-seconds + * + * Convert the duration in nano-seconds, then divide by the period and + * return the number of clock periods. + */ +#define TO_CYCLES(ps, period_ns) (DIV_ROUND_UP(ps / 1000, period_ns)) +#define TO_CYCLES64(ps, period_ns) (DIV_ROUND_UP_ULL(div_u64(ps, 1000), \ + period_ns)) + +struct phytium_nfc_cmd_ctrl { + u16 csel:4; + u16 dbc:1; + u16 addr_cyc:3; + u16 nc:1; +#define TYPE_RESET 0x00 +#define TYPE_SET_FTR 0x01 +#define TYPE_GET_FTR 0x02 +#define TYPE_READ_ID 0x03 +#define TYPE_PAGE_PRO 0x04 +#define TYPE_ERASE 0x05 +#define TYPE_READ 0x06 +#define TYPE_TOGGLE 0x07 +#define TYPE_READ_PARAM 0x02 +#define TYPE_READ_STATUS 0x03 +#define TYPE_CH_READ_COL 0x03 +#define TYPE_CH_ROW_ADDR 0x01 +#define TYPE_CH_WR_COL 0x01 + u16 cmd_type:4; + u16 dc:1; + u16 auto_rs:1; + u16 ecc_en:1; +}; + +/** + * NAND driver structure filled during the parsing of the ->exec_op() subop + * subset of instructions. + * + * @cle_ale_delay_ns: Optional delay after the last CMD or ADDR cycle + * @rdy_timeout_ms: Timeout for waits on Ready/Busy pin + * @rdy_delay_ns: Optional delay after waiting for the RB pin + * @data_delay_ns: Optional delay after the data xfer + * @data_instr_idx: Index of the data instruction in the subop + * @data_instr: Pointer to the data instruction in the subop + */ +struct phytium_nfc_op { + u8 cmd[2]; + union { + u16 ctrl; + struct phytium_nfc_cmd_ctrl nfc_ctrl; + } cmd_ctrl; + u8 addr[PHYTIUM_NFC_ADDR_MAX_LEN]; + u16 page_cnt; + u8 mem_addr_first[PHYTIUM_NFC_ADDR_MAX_LEN]; + + u32 cmd_len; + u32 addr_len; + + u32 cle_ale_delay_ns; + u32 rdy_timeout_ms; + u32 rdy_delay_ns; + u32 data_delay_ns; + u32 data_instr_idx; + struct nand_op_instr *data_instr; +} __packed; + +#define TIMING_ASY_NUM 12 +#define TIMING_SYN_NUM 14 +#define TIMING_TOG_NUM 12 + +#define TMP_DMA_DEBUG 0 /* Temporary dma space */ + +int phytium_nand_init(struct phytium_nfc *nfc); +int phytium_nand_remove(struct phytium_nfc *nfc); +int phytium_nand_prepare(struct phytium_nfc *nfc); +int phytium_nand_suspend(struct phytium_nfc *nfc); +int phytium_nand_resume(struct phytium_nfc *nfc); + +irqreturn_t phytium_nfc_isr(int irq, void *dev_id); + +#endif /* NAND_PHYTIUM_NAND_H */ diff --git a/drivers/mtd/nand/raw/phytium_nand_pci.c b/drivers/mtd/nand/raw/phytium_nand_pci.c new file mode 100644 index 0000000000000000000000000000000000000000..03dab1faa5f334ec304657b851e910a2241ef0e3 --- /dev/null +++ b/drivers/mtd/nand/raw/phytium_nand_pci.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PCI driver for Phytium NAND flash controller + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ +#include +#include +#include + +#include "phytium_nand.h" + +#define DRV_NAME "phytium_nand_pci" + +static struct mtd_partition partition_info[] = { + { + .name = "Flash partition 1", + .offset = 0x0000000, + .size = 0x4000000 }, + { + .name = "Flash partition 2", + .offset = 0x4000000, + .size = 0x8000000 }, + { + .name = "Flash partition 3", + .offset = 0x8000000, + .size = 0x10000000 }, + { + .name = "Flash partition 4", + .offset = 0x10000000, + .size = 0x12000000 }, + { + .name = "Flash partition 5", + .offset = 0x12000000, + .size = 0x14000000 }, +}; + +static struct phytium_nfc_caps x100_nfc_caps = { + .hw_ver = 1, + .int_mask_bits = 13, + .max_cs_nb = 2, + .max_rb_nb = 1, + .legacy_of_bindings = true, + .ecc_strength = 4, + .ecc_step_size = 512, + .nr_parts = 5, + .parts = partition_info, +}; + +static int phytium_pci_probe(struct pci_dev *pdev, const struct pci_device_id *pid) +{ + struct phytium_nfc *nfc; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + ret = pcim_iomap_regions(pdev, 0x1, pci_name(pdev)); + if (ret) { + dev_err(&pdev->dev, "I/O memory remapping failed\n"); + return ret; + } + + pci_set_master(pdev); + pci_try_set_mwi(pdev); + + nfc = devm_kzalloc(&pdev->dev, sizeof(struct phytium_nfc), + GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = &pdev->dev; + nfc->regs = pcim_iomap_table(pdev)[0]; + nfc->irq = pdev->irq; + nfc->caps = &x100_nfc_caps; + + ret = devm_request_irq(nfc->dev, nfc->irq, phytium_nfc_isr, + IRQF_SHARED, "phytium-nfc-pci", nfc); + if (ret) { + dev_err(nfc->dev, "Failed to register NFC interrupt.\n"); + return ret; + } + + ret = phytium_nand_init(nfc); + if (ret) + return ret; + + pci_set_drvdata(pdev, nfc); + + return ret; +} + +static void phytium_pci_remove(struct pci_dev *pdev) +{ + struct phytium_nfc *nfc = pci_get_drvdata(pdev); + int ret; + + ret = phytium_nand_remove(nfc); + if (ret) + dev_warn(&pdev->dev, "can't remove device properly: %d\n", ret); +} + +static int __maybe_unused phytium_nfc_prepare(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct phytium_nfc *nfc = pci_get_drvdata(pci); + int ret; + + ret = phytium_nand_prepare(nfc); + + return 0; +} + +static int __maybe_unused phytium_nfc_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct phytium_nfc *nfc = pci_get_drvdata(pci); + int ret; + + ret = phytium_nand_resume(nfc); + + return ret; +} + +static const struct dev_pm_ops phytium_pci_dev_pm_ops = { + .prepare = phytium_nfc_prepare, + .resume = phytium_nfc_resume, +}; + +static const struct pci_device_id phytium_pci_id_table[] = { + { PCI_VDEVICE(PHYTIUM, 0xdc29) }, + { } +}; +MODULE_DEVICE_TABLE(pci, phytium_pci_id_table); + +static struct pci_driver phytium_pci_driver = { + .name = DRV_NAME, + .id_table = phytium_pci_id_table, + .probe = phytium_pci_probe, + .remove = phytium_pci_remove, + .driver = { + .pm = &phytium_pci_dev_pm_ops, + }, +}; +module_pci_driver(phytium_pci_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("PCI driver for Phytium NAND controller"); +MODULE_AUTHOR("Zhu Mingshuai "); diff --git a/drivers/mtd/nand/raw/phytium_nand_plat.c b/drivers/mtd/nand/raw/phytium_nand_plat.c new file mode 100644 index 0000000000000000000000000000000000000000..48260f5f59ba64ff16d417af4697f794b998b77f --- /dev/null +++ b/drivers/mtd/nand/raw/phytium_nand_plat.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Core driver for Phytium NAND flash controller + * + * Copyright (C) 2020-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phytium_nand.h" + +#define DRV_NAME "phytium_nand_plat" + +static int phytium_nfc_plat_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *r; + struct phytium_nfc *nfc; + unsigned int ecc_strength; + unsigned int ecc_step_size; + int ret; + + nfc = devm_kzalloc(&pdev->dev, sizeof(struct phytium_nfc), + GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = dev; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + nfc->regs = devm_ioremap_resource(dev, r); + if (IS_ERR(nfc->regs)) + return PTR_ERR(nfc->regs); + + dev_info(nfc->dev, "NFC register address :%p, phy address:%llx\n", + nfc->regs, r->start); + + nfc->irq = platform_get_irq(pdev, 0); + if (nfc->irq < 0) { + dev_err(dev, "failed to retrieve irq\n"); + return nfc->irq; + } + + ret = devm_request_irq(dev, nfc->irq, phytium_nfc_isr, 0, + "phytium-nfc-plat", nfc); + if (ret) { + dev_err(nfc->dev, "Failed to register NFC interrupt.\n"); + return ret; + } + + nfc->caps = devm_kzalloc(dev, sizeof(struct phytium_nfc_caps), GFP_KERNEL); + if (!nfc->caps) + return -ENOMEM; + + /* Currently hard-coded parameters */ + nfc->caps->hw_ver = 2; + nfc->caps->int_mask_bits = 17; + nfc->caps->max_cs_nb = 4; + nfc->caps->max_rb_nb = 4; + nfc->caps->nr_parts = 0; + nfc->caps->parts = NULL; + + device_property_read_u32(&pdev->dev, "nand-ecc-strength", &ecc_strength); + nfc->caps->ecc_strength = ecc_strength ? ecc_strength : 8; + + device_property_read_u32(&pdev->dev, "nand-ecc-step-size", &ecc_step_size); + nfc->caps->ecc_step_size = ecc_step_size ? ecc_step_size : 512; + + ret = phytium_nand_init(nfc); + if (ret) + return ret; + + platform_set_drvdata(pdev, nfc); + + return ret; +} + +static int phytium_nfc_plat_remove(struct platform_device *pdev) +{ + struct phytium_nfc *nfc = platform_get_drvdata(pdev); + + return phytium_nand_remove(nfc); +} + +static int __maybe_unused phytium_nfc_plat_prepare(struct device *dev) +{ + struct phytium_nfc *nfc = dev_get_drvdata(dev); + + return phytium_nand_prepare(nfc); +} + +static int __maybe_unused phytium_nfc_plat_resume(struct device *dev) +{ + struct phytium_nfc *nfc = dev_get_drvdata(dev); + int ret; + + ret = phytium_nand_resume(nfc); + + return ret; +} + +static const struct dev_pm_ops phytium_dev_pm_ops = { + .prepare = phytium_nfc_plat_prepare, + .resume = phytium_nfc_plat_resume, +}; + +#ifdef CONFIG_OF +static const struct of_device_id phytium_nfc_of_ids[] = { + { .compatible = "phytium,nfc", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, phytium_nfc_of_ids); +#endif + +static struct platform_driver phytium_nfc_plat_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = phytium_nfc_of_ids, + .pm = &phytium_dev_pm_ops, + }, + .probe = phytium_nfc_plat_probe, + .remove = phytium_nfc_plat_remove, +}; +module_platform_driver(phytium_nfc_plat_driver) + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium NAND controller Platform driver"); +MODULE_AUTHOR("Zhu Mingshuai "); diff --git a/drivers/mtd/parsers/Kconfig b/drivers/mtd/parsers/Kconfig index 60738edcd5d561d54bb33466c6862d016ed96361..ae1870d271b5624eb6495c70a9c293ea3ea1bb0c 100644 --- a/drivers/mtd/parsers/Kconfig +++ b/drivers/mtd/parsers/Kconfig @@ -96,6 +96,15 @@ config MTD_OF_PARTS_LINKSYS_NS two "firmware" partitions. Currently used firmware has to be detected using CFE environment variable. +config MTD_ACPI_PARTS + tristate "ACPI partitioning parser" + default y + depends on ACPI + help + This provides an acpi partition parser, which is used to parse the + partition map described in ACPI table, as the children of the flash + memory struct. + config MTD_PARSER_IMAGETAG tristate "Parser for BCM963XX Image Tag format partitions" depends on BCM63XX || BMIPS_GENERIC || COMPILE_TEST diff --git a/drivers/mtd/parsers/Makefile b/drivers/mtd/parsers/Makefile index 0e70b621a1d84e8d13d10240c548290bdc1ed7a8..2679fd76684a2540e5f321d4427072fd899c2d16 100644 --- a/drivers/mtd/parsers/Makefile +++ b/drivers/mtd/parsers/Makefile @@ -8,6 +8,8 @@ obj-$(CONFIG_MTD_OF_PARTS) += ofpart.o ofpart-y += ofpart_core.o ofpart-$(CONFIG_MTD_OF_PARTS_BCM4908) += ofpart_bcm4908.o ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)+= ofpart_linksys_ns.o +obj-$(CONFIG_MTD_ACPI_PARTS) += acpipart.o +acpipart-y += acpipart_core.o obj-$(CONFIG_MTD_PARSER_IMAGETAG) += parser_imagetag.o obj-$(CONFIG_MTD_AFS_PARTS) += afs.o obj-$(CONFIG_MTD_PARSER_TPLINK_SAFELOADER) += tplink_safeloader.o diff --git a/drivers/mtd/parsers/acpipart_core.c b/drivers/mtd/parsers/acpipart_core.c new file mode 100644 index 0000000000000000000000000000000000000000..4ed29fc5e49b70763f8369fa3010ef2b275428b6 --- /dev/null +++ b/drivers/mtd/parsers/acpipart_core.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Flash partitions described by the acpi table + * + * Author: Wang Hanmo + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct acpi_device_id parse_acpipart_match_table[]; + +static int parse_acpi_fixed_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct acpi_device_id *acpi_id; + const char *partname; + int nr_parts, i, ret = 0; + struct acpi_device *adev; + struct fwnode_handle *child; + struct fwnode_handle *child_handle; + bool dedicated = true; + struct device *dev; + + dev = &master->dev; + adev = ACPI_COMPANION(&master->dev); + + if (!master->parent) {/*master*/ + device_get_next_child_node(dev, child_handle); + if (!child_handle) { + pr_debug("%s: 'partitions' subnode not found on %pOF. Trying to parse direct subnodes as partitions.\n", + master->name, child_handle); + dedicated = false; + } + } + + acpi_id = acpi_match_device(parse_acpipart_match_table, dev); + if (dedicated && !acpi_id) + return 0; + + nr_parts = 0; + device_for_each_child_node(dev, child_handle) { + nr_parts++; + } + + if (nr_parts == 0) + return 0; + parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + i = 0; + device_for_each_child_node(dev, child_handle) { + u64 offset, length; + bool bool_match; + + fwnode_property_read_u64(child_handle, "offset", &offset); + fwnode_property_read_u64(child_handle, "length", &length); + if (!offset && !length) { + if (dedicated) { + pr_debug("%s: acpipart partition %pOF (%pOF) missing reg property.\n", + master->name, child_handle, + dev->fwnode); + goto acpipart_fail; + } else { + nr_parts--; + continue; + } + } + + parts[i].offset = offset; + parts[i].size = length; + parts[i].fwnode = child_handle; + if (!fwnode_property_read_string(child_handle, "label", &partname)) + parts[i].name = partname; + bool_match = fwnode_property_read_bool(child_handle, "read-only"); + if (bool_match) + parts[i].mask_flags |= MTD_WRITEABLE; + bool_match = fwnode_property_read_bool(child_handle, "lock"); + if (bool_match) + parts[i].mask_flags |= MTD_POWERUP_LOCK; + bool_match = fwnode_property_read_bool(child_handle, "slc-mode"); + if (bool_match) + parts[i].mask_flags |= MTD_SLC_ON_MLC_EMULATION; + i++; + } + + if (!nr_parts) + goto acpipart_none; + + *pparts = parts; + ret = nr_parts; + return ret; + +acpipart_fail: + pr_err("%s: error parsing acpipart partition %pOF (%pOF)\n", + master->name, child_handle, dev->fwnode); + ret = -EINVAL; +acpipart_none: + kfree(parts); + return ret; +} + +static const struct acpi_device_id parse_acpipart_match_table[] = { + /* Generic */ + { "acpi-fixed-partitions", 0 }, + /* Customized */ + {}, +}; + +MODULE_DEVICE_TABLE(acpi, parse_acpipart_match_table); + +static struct mtd_part_parser acpipart_parser = { + .parse_fn = parse_acpi_fixed_partitions, + .name = "acpi-fixed-partitions", + .acpi_match_table = ACPI_PTR(parse_acpipart_match_table), +}; + +static int __init acpipart_parser_init(void) +{ + register_mtd_parser(&acpipart_parser); + return 0; +} + +static void __exit acpipart_parser_exit(void) +{ + deregister_mtd_parser(&acpipart_parser); +} + +module_init(acpipart_parser_init); +module_exit(acpipart_parser_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parser for MTD partitioning information in acpi table"); +MODULE_AUTHOR("wanghanmo "); +MODULE_ALIAS("acpi-fixed-partitions"); diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 1b0c6770c14e46419d9362bc7111128873e71780..0c67402f15b7d0bf7cb85f788690707d4be9f127 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -7,6 +7,7 @@ * Copyright (C) 2014, Freescale Semiconductor, Inc. */ +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -3621,6 +3623,7 @@ static int spi_nor_probe(struct spi_mem *spimem) struct spi_device *spi = spimem->spi; struct flash_platform_data *data = dev_get_platdata(&spi->dev); struct spi_nor *nor; + struct acpi_device *adev; /* * Enable all caps by default. The core will mask them after * checking what's really supported using spi_mem_supports_op(). @@ -3636,6 +3639,10 @@ static int spi_nor_probe(struct spi_mem *spimem) nor->spimem = spimem; nor->dev = &spi->dev; spi_nor_set_flash_node(nor, spi->dev.of_node); + adev = ACPI_COMPANION(nor->dev); + nor->mtd.dev.fwnode = spi->dev.fwnode; + + device_property_read_string(&spi->dev, "_HID", &nor->mtd.name); spi_mem_set_drvdata(spimem, nor); @@ -3775,6 +3782,11 @@ static const struct of_device_id spi_nor_of_table[] = { }; MODULE_DEVICE_TABLE(of, spi_nor_of_table); +static const struct acpi_device_id spi_nor_acpi_table[] = { + {"PHYT8009", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, spi_nor_acpi_table); /* * REVISIT: many of these chips have deep power-down modes, which * should clearly be entered on suspend() to minimize power use. @@ -3786,6 +3798,7 @@ static struct spi_mem_driver spi_nor_driver = { .name = "spi-nor", .of_match_table = spi_nor_of_table, .dev_groups = spi_nor_sysfs_groups, + .acpi_match_table = spi_nor_acpi_table, }, .id_table = spi_nor_dev_ids, }, diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index f8cde9f9f554d5a0f7d0c48c81aef7793d9555fd..71833166066523b370a3bda2ed9219dbba324ef9 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -221,6 +221,7 @@ source "drivers/net/can/ifi_canfd/Kconfig" source "drivers/net/can/m_can/Kconfig" source "drivers/net/can/mscan/Kconfig" source "drivers/net/can/peak_canfd/Kconfig" +source "drivers/net/can/phytium/Kconfig" source "drivers/net/can/rcar/Kconfig" source "drivers/net/can/sja1000/Kconfig" source "drivers/net/can/softing/Kconfig" diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index ff8f76295d13bcbb95f9816dee68afcbe19523f1..99eb7ea48207da4bb9d417d2697677de66e683ef 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -31,5 +31,5 @@ obj-$(CONFIG_CAN_SJA1000) += sja1000/ obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o obj-$(CONFIG_CAN_XILINXCAN) += xilinx_can.o - +obj-$(CONFIG_CAN_PHYTIUM) += phytium/ subdir-ccflags-$(CONFIG_CAN_DEBUG_DEVICES) += -DDEBUG diff --git a/drivers/net/can/phytium/Kconfig b/drivers/net/can/phytium/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..a23216d23c22626a97244e0e48f66411ca4ed1b7 --- /dev/null +++ b/drivers/net/can/phytium/Kconfig @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig CAN_PHYTIUM + tristate "Phytium CAN support" + help + Say Y here if you want support for Phytium CAN controller framework. + This is common support for devices that embed the Phytium CAN IP. + + To compile this driver as a module, choose M here: the module will + be called phytium_can. + +if CAN_PHYTIUM + +config CAN_PHYTIUM_PLATFORM + tristate "Phytium CAN support for io-mapped devices" + depends on HAS_IOMEM + help + Say Y here is you want to support for IO Mapped Phytium CAN controller. + This support is for devices that have the Phytium CAN controller IP + embedded into the device and the IP is IO Mapped to the processor. + + To compile this driver as a module, choose M here: the module will + be called phytium_can_platform. + +config CAN_PHYTIUM_PCI + tristate "Phytium CAN support for PCI devices" + depends on PCI + help + Say Y here is you want to support for Phytium CAN controller connected + to the PCI bus. This support is for devices that have the Phytium CAN + controller IP embedded into a PCI device. + + To compile this driver as a module, choose M here: the module will + be called phytium_can_pci. +endif diff --git a/drivers/net/can/phytium/Makefile b/drivers/net/can/phytium/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..7ef554fca58e4b5ded60b3bcdb6f9c282d5deda3 --- /dev/null +++ b/drivers/net/can/phytium/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the Phytium CAN controller drivers. +# +# + +obj-$(CONFIG_CAN_PHYTIUM) += phytium_can.o +obj-$(CONFIG_CAN_PHYTIUM_PLATFORM) += phytium_can_platform.o +obj-$(CONFIG_CAN_PHYTIUM_PCI) += phytium_can_pci.o diff --git a/drivers/net/can/phytium/phytium_can.c b/drivers/net/can/phytium/phytium_can.c new file mode 100644 index 0000000000000000000000000000000000000000..0be8dd3876edfc7ef63b0dd3c1dea44fe07e5e9e --- /dev/null +++ b/drivers/net/can/phytium/phytium_can.c @@ -0,0 +1,1222 @@ +// SPDX-License-Identifier: GPL-2.0 +/* CAN bus driver for Phytium CAN controller + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include + +#include "phytium_can.h" + +/* register definition */ +enum phytium_can_reg { + CAN_CTRL = 0x00, /* Global control register */ + CAN_INTR = 0x04, /* Interrupt register */ + CAN_ARB_RATE_CTRL = 0x08, /* Arbitration rate control register */ + CAN_DAT_RATE_CTRL = 0x0c, /* Data rate control register */ + CAN_ACC_ID0 = 0x10, /* Acceptance identifier0 register */ + CAN_ACC_ID1 = 0x14, /* Acceptance identifier1 register */ + CAN_ACC_ID2 = 0x18, /* Acceptance identifier2 register */ + CAN_ACC_ID3 = 0x1c, /* Acceptance identifier3 register */ + CAN_ACC_ID0_MASK = 0x20, /* Acceptance identifier0 mask register */ + CAN_ACC_ID1_MASK = 0x24, /* Acceptance identifier1 mask register */ + CAN_ACC_ID2_MASK = 0x28, /* Acceptance identifier2 mask register */ + CAN_ACC_ID3_MASK = 0x2c, /* Acceptance identifier3 mask register */ + CAN_XFER_STS = 0x30, /* Transfer status register */ + CAN_PHYTIUM_ERR_CNT = 0x34, /* Error counter register */ + CAN_FIFO_CNT = 0x38, /* FIFO counter register */ + CAN_DMA_CTRL = 0x3c, /* DMA request control register */ + CAN_XFER_EN = 0x40, /* Transfer enable register */ + CAN_INTR1 = 0x44, /* Interrupt register 1 */ + CAN_FRM_INFO = 0x48, /* Frame valid number register */ + CAN_TIME_OUT = 0x4c, /* Timeout register */ + CAN_TIME_OUT_CNT = 0x50, /* Timeout counter register */ + CAN_INTR2 = 0x54, /* Interrupt register 2 */ + CAN_TX_FIFO = 0x100, /* TX FIFO shadow register */ + CAN_RX_FIFO = 0x200, /* RX FIFO shadow register */ + CAN_RX_INFO_FIFO = 0x300, /* RX information FIFO shadow register */ + CAN_PIDR4 = 0xfd0, /* Peripheral Identification Register 4 */ + CAN_PIDR0 = 0xfe0, /* Peripheral Identification Register 0 */ + CAN_PIDR1 = 0xfe4, /* Peripheral Identification Register 1 */ + CAN_PIDR2 = 0xfe8, /* Peripheral Identification Register 2 */ + CAN_PIDR3 = 0xfec, /* Peripheral Identification Register 3 */ + CAN_CIDR0 = 0xff0, /* Component Identification Register 0 */ + CAN_CIDR1 = 0xff4, /* Component Identification Register 1 */ + CAN_CIDR2 = 0xff8, /* Component Identification Register 2 */ + CAN_CIDR3 = 0xffc, /* Component Identification Register 3 */ +}; + +/* Global control register (CTRL) */ +#define CTRL_XFER BIT(0) /* Transfer enable */ +#define CTRL_TXREQ BIT(1) /* Transmit request */ +#define CTRL_AIME BIT(2) /* Acceptance identifier mask enable */ +#define CTRL_TTS BIT(3) /* Transmit trigger strategy */ +#define CTRL_RST BIT(7) /* Write 1 to soft reset and self clear */ +#define CTRL_RFEIDF BIT(8) /* Allow RX frame end interrupt during ID filtered frame */ +#define CTRL_RFEDT BIT(9) /* Allow RX frame end interrupt during TX frame */ +#define CTRL_IOF BIT(10) /* Ignore overload flag internally */ +#define CTRL_FDCRC BIT(11) /* CANFD CRC mode */ + +/* Interrupt register (INTR) */ +#define INTR_BOIS BIT(0) /* Bus off interrupt status */ +#define INTR_PWIS BIT(1) /* Passive warning interrupt status */ +#define INTR_PEIS BIT(2) /* Passive error interrupt status */ +#define INTR_RFIS BIT(3) /* RX FIFO full interrupt status */ +#define INTR_TFIS BIT(4) /* TX FIFO empty interrupt status */ +#define INTR_REIS BIT(5) /* RX frame end interrupt status */ +#define INTR_TEIS BIT(6) /* TX frame end interrupt status */ +#define INTR_EIS BIT(7) /* Error interrupt status */ +#define INTR_BOIE BIT(8) /* Bus off interrupt enable */ +#define INTR_PWIE BIT(9) /* Passive warning interrupt enable */ +#define INTR_PEIE BIT(10) /* Passive error interrupt enable */ +#define INTR_RFIE BIT(11) /* RX FIFO full interrupt enable */ +#define INTR_TFIE BIT(12) /* TX FIFO empty interrupt enable */ +#define INTR_REIE BIT(13) /* RX frame end interrupt enable */ +#define INTR_TEIE BIT(14) /* TX frame end interrupt enable */ +#define INTR_EIE BIT(15) /* Error interrupt enable */ +#define INTR_BOIC BIT(16) /* Bus off interrupt clear */ +#define INTR_PWIC BIT(17) /* Passive warning interrupt clear */ +#define INTR_PEIC BIT(18) /* Passive error interrupt clear */ +#define INTR_RFIC BIT(19) /* RX FIFO full interrupt clear */ +#define INTR_TFIC BIT(20) /* TX FIFO empty interrupt clear */ +#define INTR_REIC BIT(21) /* RX frame end interrupt clear */ +#define INTR_TEIC BIT(22) /* TX frame end interrupt clear */ +#define INTR_EIC BIT(23) /* Error interrupt clear */ + +#define INTR_STATUS_MASK (INTR_BOIS | INTR_PWIS | INTR_PEIS | INTR_RFIS | \ + INTR_TFIS | INTR_REIS | INTR_TEIS | INTR_EIS) + +#define INTR_EN_MASK (INTR_BOIE | INTR_PWIE | INTR_PEIE | INTR_RFIE | \ + INTR_TFIE | INTR_REIE | INTR_TEIE | INTR_EIE) + +#define INTR_CLEAR_MASK (INTR_BOIC | INTR_PWIC | INTR_PEIC | INTR_RFIC | \ + INTR_TFIC | INTR_REIC | INTR_TEIC | INTR_EIC) + +/* Arbitration rate control register (ARB_RATE_CTRL) */ +#define ARB_RATE_CTRL_ARJW GENMASK(1, 0) /* Arbitration field resync jump width */ +#define ARB_RATE_CTRL_APRS GENMASK(4, 2) /* Arbitration field propagation segment */ +#define ARB_RATE_CTRL_APH1S GENMASK(7, 5) /* Arbitration field phase1 segment */ +#define ARB_RATE_CTRL_APH2S GENMASK(10, 8) /* Arbitration field phase2 segment */ +#define ARB_RATE_CTRL_APD GENMASK(28, 16) /* Arbitration field prescaler divider */ + +/* Data rate control register (DAT_RATE_CTRL) */ +#define DAT_RATE_CTRL_DRJW GENMASK(1, 0) /* Data field resync jump width */ +#define DAT_RATE_CTRL_DPRS GENMASK(4, 2) /* Data field propagation segment */ +#define DAT_RATE_CTRL_DPH1S GENMASK(7, 5) /* Data field phase1 segment */ +#define DAT_RATE_CTRL_DPH2S GENMASK(10, 8) /* Data field phase2 segment */ +#define DAT_RATE_CTRL_DPD GENMASK(28, 16) /* Data field prescaler divider */ + +/* Acceptance identifierX register (ACC_IDX) */ +#define ACC_IDX_AID_MASK GENMASK(28, 0) /* Acceptance identifier */ + +/* Acceptance identifier0 mask register (ACC_ID0_MASK) */ +#define ACC_IDX_MASK_AID_MASK GENMASK(28, 0) /* Acceptance identifier mask */ + +/* Transfer status register (XFER_STS) */ +#define XFER_STS_FRAS GENMASK(2, 0) /* Frame status */ +#define XFER_STS_FIES GENMASK(7, 3) /* Field status */ +#define XFER_STS_FIES_IDLE (0x0) /* idle */ +#define XFER_STS_FIES_ARBITRATION (0x1) /* arbitration */ +#define XFER_STS_FIES_TX_CTRL (0x2) /* transmit control */ +#define XFER_STS_FIES_TX_DATA (0x3) /* transmit data */ +#define XFER_STS_FIES_TX_CRC (0x4) /* transmit crc */ +#define XFER_STS_FIES_TX_FRM (0x5) /* transmit frame */ +#define XFER_STS_FIES_RX_CTRL (0x6) /* receive control */ +#define XFER_STS_FIES_RX_DATA (0x7) /* receive data */ +#define XFER_STS_FIES_RX_CRC (0x8) /* receive crc */ +#define XFER_STS_FIES_RX_FRM (0x9) /* receive frame */ +#define XFER_STS_FIES_INTERMISSION (0xa) /* intermission */ +#define XFER_STS_FIES_TX_SUSPD (0xb) /* transmit suspend */ +#define XFER_STS_FIES_BUS_IDLE (0xc) /* bus idle */ +#define XFER_STS_FIES_OVL_FLAG (0xd) /* overload flag */ +#define XFER_STS_FIES_OVL_DLM (0xe) /* overload delimiter */ +#define XFER_STS_FIES_ERR_FLAG (0xf) /* error flag */ +#define XFER_STS_FIES_ERR_DLM (0x10) /* error delimiter */ +#define XFER_STS_FIES_BUS_OFF (0x11) /* bus off */ +#define XFER_STS_TS BIT(8) /* Transmit status */ +#define XFER_STS_RS BIT(9) /* Receive status */ +#define XFER_STS_XFERS BIT(10) /* Transfer status */ + +/* Error counter register (ERR_CNT) */ +#define ERR_CNT_REC GENMASK(8, 0) /* Receive error counter */ +#define ERR_CNT_TEC GENMASK(24, 16) /* Transmit error counter */ + +/* FIFO counter register (FIFO_CNT) */ +#define FIFO_CNT_RFN GENMASK(6, 0) /* Receive FIFO valid data number */ +#define FIFO_CNT_TFN GENMASK(22, 16) /* Transmit FIFO valid data number */ + +/* DMA request control register (DMA_CTRL) */ +#define DMA_CTRL_RFTH GENMASK(5, 0) /* Receive FIFO DMA request threshold */ +#define DMA_CTRL_RFRE BIT(6) /* Receive FIFO DMA request enable */ +#define DMA_CTRL_TFTH GENMASK(21, 16) /* Transmit FIFO DMA request threshold */ +#define DMA_CTRL_TFRE BIT(22) /* Transmit FIFO DMA request enable */ + +/* Transfer enable register (XFER_EN) */ +#define XFER_EN_XFER BIT(0) /* Transfer enable */ + +/* Interrupt register 1 (INTR1) */ +#define INTR1_RF1IS BIT(0) /* RX FIFO 1/4 interrupt status */ +#define INTR1_RF2IS BIT(1) /* RX FIFO 1/2 interrupt status */ +#define INTR1_RF3IS BIT(2) /* RX FIFO 3/4 interrupt status */ +#define INTR1_RF4IS BIT(3) /* RX FIFO full interrupt status */ +#define INTR1_TF1IS BIT(4) /* TX FIFO 1/4 interrupt status */ +#define INTR1_TF2IS BIT(5) /* TX FIFO 1/2 interrupt status */ +#define INTR1_TF3IS BIT(6) /* TX FIFO 3/4 interrupt status */ +#define INTR1_TF4IS BIT(7) /* TX FIFO empty interrupt status */ +#define INTR1_RF1IE BIT(8) /* RX FIFO 1/4 interrupt enable */ +#define INTR1_RF2IE BIT(9) /* RX FIFO 1/2 interrupt enable */ +#define INTR1_RF3IE BIT(10) /* RX FIFO 3/4 interrupt enable */ +#define INTR1_RF4IE BIT(11) /* RX FIFO full interrupt enable */ +#define INTR1_TF1IE BIT(12) /* TX FIFO 1/4 interrupt enable */ +#define INTR1_TF2IE BIT(13) /* TX FIFO 1/2 interrupt enable */ +#define INTR1_TF3IE BIT(14) /* TX FIFO 3/4 interrupt enable */ +#define INTR1_TF4IE BIT(15) /* TX FIFO empty interrupt enable */ +#define INTR1_RF1IC BIT(16) /* RX FIFO 1/4 interrupt clear */ +#define INTR1_RF2IC BIT(17) /* RX FIFO 1/2 interrupt clear */ +#define INTR1_RF3IC BIT(18) /* RX FIFO 3/4 interrupt clear */ +#define INTR1_RF4IC BIT(19) /* RX FIFO full interrupt clear */ +#define INTR1_TF1IC BIT(20) /* TX FIFO 1/4 interrupt clear */ +#define INTR1_TF2IC BIT(21) /* TX FIFO 1/2 interrupt clear */ +#define INTR1_TF3IC BIT(22) /* TX FIFO 3/4 interrupt clear */ +#define INTR1_TF4IC BIT(23) /* TX FIFO empty interrupt clear */ +#define INTR1_RF1RIS BIT(24) /* RX FIFO 1/4 raw interrupt status */ +#define INTR1_RF2RIS BIT(25) /* RX FIFO 1/2 raw interrupt status */ +#define INTR1_RF3RIS BIT(26) /* RX FIFO 3/4 raw interrupt status */ +#define INTR1_RF4RIS BIT(27) /* RX FIFO full raw interrupt status */ +#define INTR1_TF1RIS BIT(28) /* TX FIFO 1/4 raw interrupt status */ +#define INTR1_TF2RIS BIT(29) /* TX FIFO 1/2 raw interrupt status */ +#define INTR1_TF3RIS BIT(30) /* TX FIFO 3/4 raw interrupt status */ +#define INTR1_TF4RIS BIT(31) /* TX FIFO empty raw interrupt status */ + +/* Frame valid number register (FRM_INFO) */ +#define FRM_INFO_RXFC GENMASK(5, 0) /* Valid frame number in RX FIFO */ +#define FRM_INFO_SSPD GENMASK(31, 16) /* Secondary sample point delay */ + +/* Interrupt register 2 (INTR2) */ +#define INTR2_TOIS BIT(0) /* RX FIFO time out interrupt status */ +#define INTR2_TOIM BIT(8) /* RX FIFO time out interrupt mask */ +#define INTR2_TOIC BIT(16) /* RX FIFO time out interrupt clear */ +#define INTR2_TORIS BIT(24) /* RX FIFO time out raw interrupt status */ + +/* RX information FIFO shadow register (RX_INFO_FIFO) */ +#define RX_INFO_FIFO_WNORF GENMASK(4, 0) /* Word (4-byte) number of current receive frame */ +#define RX_INFO_FIFO_RORF BIT(5) /* RTR value of current receive frame */ +#define RX_INFO_FIFO_FORF BIT(6) /* FDF value of current receive frame */ +#define RX_INFO_FIFO_IORF BIT(7) /* IDE value of current receive frame */ + +/* Arbitration Bits */ +#define CAN_ID1_MASK GENMASK(31, 21) /* Base identifer */ +/* Standard Remote Transmission Request */ +#define CAN_ID1_RTR_MASK BIT(20) +/* Extended Substitute remote TXreq */ +#define CAN_ID2_SRR_MASK BIT(20) +#define CAN_IDE_MASK BIT(19) /* IDentifier extension flag */ +#define CAN_ID2_MASK GENMASK(18, 1) /* Identifier extension */ +/* Extended frames remote TX request */ +#define CAN_ID2_RTR_MASK BIT(0) +#define CAN_ID1_FDF_MASK BIT(18) +#define CAN_ID1_DLC_MASK GENMASK(17, 14) +#define CANFD_ID1_BRS_MASK BIT(16) +#define CANFD_ID1_ESI_MASK BIT(15) +#define CANFD_ID1_DLC_MASK GENMASK(14, 11) + +#define CAN_ID2_FDF_MASK BIT(31) +#define CAN_ID2_DLC_MASK GENMASK(29, 26) +#define CANFD_ID2_BRS_MASK BIT(29) +#define CANFD_ID2_ESI_MASK BIT(28) +#define CANFD_ID2_DLC_MASK GENMASK(27, 24) + +#define CAN_ID1_DLC_OFF 14 +#define CANFD_ID1_DLC_OFF 11 +#define CAN_ID2_DLC_OFF 26 +#define CANFD_ID2_DLC_OFF 24 + +#define CAN_IDR_ID1_SHIFT 21 /* Standard Messg Identifier */ +#define CAN_IDR_ID2_SHIFT 1 /* Extended Message Identifier */ +#define CAN_IDR_SDLC_SHIFT 14 +#define CAN_IDR_EDLC_SHIFT 26 + +/* CANFD Standard msg padding 1 */ +#define CANFD_IDR_PAD_MASK 0x000007FF +#define CAN_IDR_PAD_MASK 0x00003FFF /* Standard msg padding 1 */ + +/** + * phytium_can_set_reg_bits - set a bit value to the device register + * @cdev: Driver private data structure + * @reg: Register offset + * @bs: The bit mask + * + * Read data from the particular CAN register + * Return: value read from the CAN register + */ +static void +phytium_can_set_reg_bits(const struct phytium_can_dev *cdev, + enum phytium_can_reg reg, u32 bs) +{ + u32 val = readl(cdev->base + reg); + + val |= bs; + writel(val, cdev->base + reg); +} + +/** + * phytium_can_clr_reg_bits - clear a bit value to the device register + * @cdev: Driver private data structure + * @reg: Register offset + * @bs: The bit mask + * + * Read data from the particular CAN register + * Return: value read from the CAN register + */ +static void +phytium_can_clr_reg_bits(const struct phytium_can_dev *cdev, + enum phytium_can_reg reg, u32 bs) +{ + u32 val = readl(cdev->base + reg); + + val &= ~bs; + writel(val, cdev->base + reg); +} + +static inline u32 phytium_can_read(const struct phytium_can_dev *cdev, enum phytium_can_reg reg) +{ + return readl(cdev->base + reg); +} + +static inline void +phytium_can_write(const struct phytium_can_dev *cdev, enum phytium_can_reg reg, u32 val) +{ + writel(val, cdev->base + reg); +} + +static inline void phytium_can_enable_all_interrupts(struct phytium_can_dev *cdev) +{ + phytium_can_write(cdev, CAN_INTR, INTR_EN_MASK); +} + +static inline void phytium_can_disable_all_interrupt(struct phytium_can_dev *cdev) +{ + phytium_can_write(cdev, CAN_INTR, 0x0); +} + +static int phytium_can_get_berr_counter(const struct net_device *dev, + struct can_berr_counter *bec) +{ + struct phytium_can_dev *cdev = netdev_priv(dev); + + bec->rxerr = phytium_can_read(cdev, CAN_PHYTIUM_ERR_CNT) & ERR_CNT_REC; + bec->txerr = (phytium_can_read(cdev, CAN_PHYTIUM_ERR_CNT) & ERR_CNT_TEC) >> 16; + + return 0; +} + +static int phytium_can_read_fifo(struct net_device *dev) +{ + struct net_device_stats *stats = &dev->stats; + struct phytium_can_dev *cdev = netdev_priv(dev); + struct canfd_frame *cf; + struct sk_buff *skb; + u32 id, dlc, i; + + /* Read the frame header from FIFO */ + id = phytium_can_read(cdev, CAN_RX_FIFO); + id = be32_to_cpup(&id); + if (id & CAN_IDE_MASK) { + /* Received an extended frame */ + dlc = phytium_can_read(cdev, CAN_RX_FIFO); + dlc = be32_to_cpup(&dlc); + if (dlc & CAN_ID2_FDF_MASK) + skb = alloc_canfd_skb(dev, &cf); + else + skb = alloc_can_skb(dev, (struct can_frame **)&cf); + + if (unlikely(!skb)) { + stats->rx_dropped++; + return 0; + } + + if (dlc & CAN_ID2_FDF_MASK) { + /* CAN FD extended frame */ + if (dlc & CANFD_ID2_BRS_MASK) + cf->flags |= CANFD_BRS; + if (dlc & CANFD_ID2_ESI_MASK) + cf->flags |= CANFD_ESI; + cf->len = can_fd_dlc2len((dlc & CANFD_ID2_DLC_MASK) >> CANFD_ID2_DLC_OFF); + } else { + /* CAN extended frame */ + cf->len = can_cc_dlc2len((dlc & CAN_ID2_DLC_MASK) >> CAN_ID2_DLC_OFF); + } + + cf->can_id = (id & CAN_ID1_MASK) >> 3; + cf->can_id |= (id & CAN_ID2_MASK) >> 1; + cf->can_id |= CAN_EFF_FLAG; + + if (id & CAN_ID2_RTR_MASK) + cf->can_id |= CAN_RTR_FLAG; + } else { + /* Received a standard frame */ + if (id & CAN_ID1_FDF_MASK) + skb = alloc_canfd_skb(dev, &cf); + else + skb = alloc_can_skb(dev, (struct can_frame **)&cf); + + if (unlikely(!skb)) { + stats->rx_dropped++; + return 0; + } + + if (id & CAN_ID1_FDF_MASK) { + /* CAN FD extended frame */ + if (id & CANFD_ID1_BRS_MASK) + cf->flags |= CANFD_BRS; + if (id & CANFD_ID1_ESI_MASK) + cf->flags |= CANFD_ESI; + cf->len = can_fd_dlc2len((id & CANFD_ID1_DLC_MASK) >> CANFD_ID1_DLC_OFF); + } else { + /* CAN extended frame */ + cf->len = can_cc_dlc2len((id & CAN_ID1_DLC_MASK) >> CAN_ID1_DLC_OFF); + } + + cf->can_id = (id & CAN_ID1_MASK) >> 21; + + if (id & CAN_ID1_RTR_MASK) + cf->can_id |= CAN_RTR_FLAG; + } + + if (!(cf->can_id & CAN_RTR_FLAG)) + /* Receive data frames */ + for (i = 0; i < cf->len; i += 4) + *(__be32 *)(cf->data + i) = phytium_can_read(cdev, CAN_RX_FIFO); + + stats->rx_packets++; + stats->rx_bytes += cf->len; + netif_receive_skb(skb); + + return 1; +} + +static int phytium_can_do_rx_poll(struct net_device *dev, int quota) +{ + struct phytium_can_dev *cdev = netdev_priv(dev); + u32 rxfs, pkts = 0; + int isr; + + isr = cdev->isr; + + rxfs = phytium_can_read(cdev, CAN_FIFO_CNT) & FIFO_CNT_RFN; + if (!rxfs) { + netdev_dbg(dev, "no messages in RX FIFO\n"); + return 0; + } + + while ((rxfs != 0) && (quota > 0)) { + if (isr & INTR_REIS) { + pkts += phytium_can_read_fifo(dev); + quota--; + } else { + break; + } + rxfs = phytium_can_read(cdev, CAN_FIFO_CNT) & FIFO_CNT_RFN; + netdev_dbg(dev, "Next received %d frame again.\n", rxfs); + } + + return pkts; +} + +static int phytium_can_rx_handler(struct net_device *dev, int quota) +{ + struct phytium_can_dev *cdev = netdev_priv(dev); + int work_done = 0; + u32 isr; + + isr = cdev->isr | phytium_can_read(cdev, CAN_INTR); + if (!isr) + goto end; + + /* Handle RX IRQ */ + if (isr & INTR_REIS) { + int rx_work_or_err; + + rx_work_or_err = phytium_can_do_rx_poll(dev, (quota - work_done)); + if (rx_work_or_err < 0) + return rx_work_or_err; + + work_done += rx_work_or_err; + } + +end: + return 0; +} + +static int phytium_can_poll(struct napi_struct *napi, int quota) +{ + struct net_device *dev = napi->dev; + struct phytium_can_dev *cdev = netdev_priv(dev); + int work_done; + unsigned long flags; + + netdev_dbg(dev, "The receive processing is going on !\n"); + + work_done = phytium_can_rx_handler(dev, quota); + + /* Don't re-enable interrupts if the driver had a fatal error + * (e.g., FIFO read failure) + */ + if (work_done >= 0 && work_done < quota) { + napi_complete_done(napi, work_done); + + spin_lock_irqsave(&cdev->lock, flags); + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_REIE); + spin_unlock_irqrestore(&cdev->lock, flags); + } + + return work_done; +} + +static void phytium_can_write_frame(struct phytium_can_dev *cdev) +{ + struct canfd_frame *cf = (struct canfd_frame *)cdev->tx_skb->data; + struct net_device *dev = cdev->net; + struct net_device_stats *stats = &dev->stats; + struct sk_buff *skb = cdev->tx_skb; + u32 i, id, dlc = 0, frame_head[2] = {0, 0}; + u32 data_len, tmp_len; + + data_len = can_fd_len2dlc(cf->len); + cdev->tx_skb = NULL; + + /* Watch carefully on the bit sequence */ + if (cf->can_id & CAN_EFF_FLAG) { + /* Extended CAN ID format */ + id = ((cf->can_id & CAN_EFF_MASK) << 1) & CAN_ID2_MASK; + id |= (((cf->can_id & CAN_EFF_MASK) >> + (CAN_EFF_ID_BITS - CAN_SFF_ID_BITS)) << + CAN_IDR_ID1_SHIFT) & CAN_ID1_MASK; + + /* The substibute remote TX request bit should be "1" + * for extended frames as in the Phytium CAN datasheet + */ + id |= CAN_IDE_MASK | CAN_ID2_SRR_MASK; + + if (cf->can_id & CAN_RTR_FLAG) + /* Extended frames remote TX request */ + id |= CAN_ID2_RTR_MASK; + if ((cdev->can.ctrlmode & CAN_CTRLMODE_FD) && + can_is_canfd_skb(skb)) + dlc = data_len << CANFD_ID2_DLC_OFF; + else + dlc = data_len << CAN_ID2_DLC_OFF; + + if (cdev->can.ctrlmode & CAN_CTRLMODE_FD) { + dlc |= CAN_ID2_FDF_MASK; + if (cf->flags & CANFD_BRS) + dlc |= CANFD_ID2_BRS_MASK; + if (cf->flags & CANFD_ESI) + dlc |= CANFD_ID2_ESI_MASK; + } + + frame_head[0] = cpu_to_be32p(&id); + frame_head[1] = cpu_to_be32p(&dlc); + + /* Write the Frame to Phytium CAN TX FIFO */ + phytium_can_write(cdev, CAN_TX_FIFO, frame_head[0]); + phytium_can_write(cdev, CAN_TX_FIFO, frame_head[1]); + netdev_dbg(dev, "Write atbitration field [0]:0x%x [1]:0x%x\n", + frame_head[0], frame_head[1]); + } else { + /* Standard CAN ID format */ + id = ((cf->can_id & CAN_SFF_MASK) << CAN_IDR_ID1_SHIFT) + & CAN_ID1_MASK; + + if (cf->can_id & CAN_RTR_FLAG) + /* Standard frames remote TX request */ + id |= CAN_ID1_RTR_MASK; + + if (cdev->can.ctrlmode & CAN_CTRLMODE_FD) + dlc = (data_len << CANFD_ID1_DLC_OFF) + | CANFD_IDR_PAD_MASK; + else + dlc = (data_len << CAN_ID1_DLC_OFF) | CAN_IDR_PAD_MASK; + + id |= dlc; + + if (cdev->can.ctrlmode & CAN_CTRLMODE_FD) { + id |= CAN_ID1_FDF_MASK; + if (cf->flags & CANFD_BRS) + id |= CANFD_ID1_BRS_MASK; + if (cf->flags & CANFD_ESI) + id |= CANFD_ID1_ESI_MASK; + } + + frame_head[0] = cpu_to_be32p(&id); + /* Write the Frame to Phytium CAN TX FIFO */ + phytium_can_write(cdev, CAN_TX_FIFO, frame_head[0]); + netdev_dbg(dev, "Write atbitration field [0] 0x%x\n", + frame_head[0]); + } + + if (!(cf->can_id & CAN_RTR_FLAG)) { + netdev_dbg(dev, "Write CAN data frame\n"); + for (i = 0; i < cf->len; i += 4) { + phytium_can_write(cdev, CAN_TX_FIFO, + *(__be32 *)(cf->data + i)); + netdev_dbg(dev, "[%d]:%x\n", i, + *(__be32 *)(cf->data + i)); + } + } + + stats->tx_bytes += cf->len; + stats->tx_packets++; + + cdev->is_tx_done = false; + cdev->is_need_stop_xmit = true; + mod_timer(&cdev->timer, jiffies + HZ / 10); + + netdev_dbg(dev, "Trigger send message!\n"); + can_put_echo_skb(skb, dev, 0, 0); + tmp_len = can_get_echo_skb(dev, 0, 0); +} + +static netdev_tx_t phytium_can_tx_handler(struct phytium_can_dev *cdev) +{ + struct net_device *dev = cdev->net; + u32 tx_fifo_used; + unsigned long flags; + + phytium_can_write_frame(cdev); + /* Check if the TX buffer is full */ + tx_fifo_used = 4 * ((phytium_can_read(cdev, CAN_FIFO_CNT) & FIFO_CNT_TFN) >> 16); + if (cdev->can.ctrlmode & CAN_CTRLMODE_FD) { + if (CAN_FIFO_BYTE_LEN - tx_fifo_used <= KEEP_CANFD_FIFO_MIN_LEN) { + netif_stop_queue(dev); + spin_lock_irqsave(&cdev->lock, flags); + cdev->is_stop_queue_flag = STOP_QUEUE_TRUE; + spin_unlock_irqrestore(&cdev->lock, flags); + } + } else { + if (CAN_FIFO_BYTE_LEN - tx_fifo_used <= KEEP_CAN_FIFO_MIN_LEN) { + netif_stop_queue(dev); + spin_lock_irqsave(&cdev->lock, flags); + cdev->is_stop_queue_flag = STOP_QUEUE_TRUE; + spin_unlock_irqrestore(&cdev->lock, flags); + } + } + + return NETDEV_TX_OK; +} + +/** + * phytium_can_tx_interrupt - Tx Done Isr + * @ndev: net_device pointer + * @isr: Interrupt status register value + */ +static void phytium_can_tx_interrupt(struct net_device *ndev, u32 isr) +{ + struct phytium_can_dev *cdev = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + u32 tx_fifo_used = 0; + + if (isr & INTR_TEIS) + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_TEIC); + + /* Check if the TX buffer is full */ + if (cdev->is_stop_queue_flag) { + tx_fifo_used = 4 * ((phytium_can_read(cdev, CAN_FIFO_CNT) & FIFO_CNT_TFN) >> 16); + if (cdev->can.ctrlmode & CAN_CTRLMODE_FD) { + if (CAN_FIFO_BYTE_LEN - tx_fifo_used > KEEP_CANFD_FIFO_MIN_LEN) { + netif_wake_queue(ndev); + cdev->is_stop_queue_flag = STOP_QUEUE_FALSE; + } + } else { + if (CAN_FIFO_BYTE_LEN - tx_fifo_used > KEEP_CAN_FIFO_MIN_LEN) { + netif_wake_queue(ndev); + cdev->is_stop_queue_flag = STOP_QUEUE_FALSE; + } + } + } + + cdev->is_tx_done = true; + cdev->is_need_stop_xmit = false; + del_timer(&cdev->timer); + + netdev_dbg(ndev, "Finish transform packets %lu\n", stats->tx_packets); + + phytium_can_set_reg_bits(cdev, CAN_INTR, (INTR_BOIE | + INTR_PWIE | INTR_PEIE)); +} + +static void phytium_can_tx_done_timeout(struct timer_list *t) +{ + struct phytium_can_dev *priv = from_timer(priv, t, timer); + struct net_device *ndev = priv->net; + + if (!priv->is_tx_done) { + if (priv->is_need_stop_xmit) { + netdev_dbg(ndev, "%s stop xmit\n", __func__); + priv->is_need_stop_xmit = false; + phytium_can_clr_reg_bits(priv, CAN_CTRL, CTRL_XFER); + phytium_can_clr_reg_bits(priv, CAN_INTR, (INTR_BOIE | + INTR_PWIE | INTR_PEIE)); + /* stop xmit and restart after 500ms */ + mod_timer(&priv->timer, jiffies + HZ / 2); + } else { + netdev_dbg(ndev, "%s start xmit\n", __func__); + priv->is_need_stop_xmit = true; + phytium_can_set_reg_bits(priv, CAN_CTRL, CTRL_XFER); + /* start xmit and stop after 250ms */ + mod_timer(&priv->timer, jiffies + HZ / 4); + } + } +} + +static void phytium_can_err_interrupt(struct net_device *ndev, u32 isr) +{ + struct phytium_can_dev *cdev = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + struct can_frame *cf; + struct sk_buff *skb; + u32 txerr = 0, rxerr = 0; + + skb = alloc_can_err_skb(ndev, &cf); + + rxerr = phytium_can_read(cdev, CAN_PHYTIUM_ERR_CNT) & ERR_CNT_REC; + txerr = ((phytium_can_read(cdev, CAN_PHYTIUM_ERR_CNT) & ERR_CNT_TEC) >> 16); + + if (isr & INTR_BOIS) { + netdev_dbg(ndev, "bus_off %s: txerr :%u rxerr :%u\n", + __func__, txerr, rxerr); + cdev->can.state = CAN_STATE_BUS_OFF; + cdev->can.can_stats.bus_off++; + /* Leave device in Config Mode in bus-off state */ + phytium_can_write(cdev, CAN_CTRL, CTRL_RST); + can_bus_off(ndev); + if (skb) + cf->can_id |= CAN_ERR_BUSOFF; + } else if ((isr & INTR_PEIS) == INTR_PEIS) { + netdev_dbg(ndev, "error_passive %s: txerr :%u rxerr :%u\n", + __func__, txerr, rxerr); + cdev->can.state = CAN_STATE_ERROR_PASSIVE; + cdev->can.can_stats.error_passive++; + /* Clear interrupt condition */ + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_PEIC); + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_PWIC); + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_TEIC); + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_EIC); + if (skb) { + cf->can_id |= CAN_ERR_CRTL; + cf->data[1] = (rxerr > 127) ? + CAN_ERR_CRTL_RX_PASSIVE : + CAN_ERR_CRTL_TX_PASSIVE; + cf->data[6] = txerr; + cf->data[7] = rxerr; + } + } else if (isr & INTR_PWIS) { + netdev_dbg(ndev, "error_warning %s: txerr :%u rxerr :%u\n", + __func__, txerr, rxerr); + cdev->can.state = CAN_STATE_ERROR_WARNING; + cdev->can.can_stats.error_warning++; + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_PWIC); + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_TEIC); + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_EIC); + if (skb) { + cf->can_id |= CAN_ERR_CRTL; + cf->data[1] |= (txerr > rxerr) ? + CAN_ERR_CRTL_TX_WARNING : + CAN_ERR_CRTL_RX_WARNING; + cf->data[6] = txerr; + cf->data[7] = rxerr; + } + } + + /* Check for RX FIFO Overflow interrupt */ + if (isr & INTR_RFIS) { + stats->rx_over_errors++; + stats->rx_errors++; + + if (skb) { + cf->can_id |= CAN_ERR_CRTL; + cf->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW; + } + } + + if (skb) { + stats->rx_packets++; + stats->rx_bytes += cf->can_dlc; + netif_rx(skb); + } +} + +/** + * phytium_can_isr - CAN Isr + * @irq: irq number + * @dev_id: device id poniter + * + * This is the phytium CAN 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 phytium_can_isr(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct phytium_can_dev *cdev = netdev_priv(dev); + u32 isr; + + /* Get the interrupt status */ + isr = phytium_can_read(cdev, CAN_INTR) & INTR_STATUS_MASK; + if (!isr) + return IRQ_NONE; + + spin_lock(&cdev->lock); + /* Check for FIFO full interrupt and alarm */ + if ((isr & INTR_RFIS)) { + netdev_dbg(dev, "rx_fifo is full!.\n"); + isr &= (~INTR_RFIS); + phytium_can_clr_reg_bits(cdev, CAN_INTR, INTR_RFIE); + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_RFIC); + napi_schedule(&cdev->napi); + } + + /* Check for FIFO empty interrupt and alarm */ + if ((isr & INTR_TFIS)) { + netdev_dbg(dev, "tx_fifo is empty!.\n"); + isr &= (~INTR_TFIS); + phytium_can_clr_reg_bits(cdev, CAN_INTR, INTR_TFIE); + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_TFIC); + } + + /* Check for the type of error interrupt and Processing it */ + if (isr & (INTR_EIS | INTR_RFIS | INTR_BOIS | INTR_PWIS | INTR_PEIS)) { + phytium_can_clr_reg_bits(cdev, CAN_INTR, (INTR_EIE | INTR_RFIE | + INTR_BOIE | INTR_PWIE | INTR_PEIE)); + phytium_can_err_interrupt(dev, isr); + phytium_can_set_reg_bits(cdev, CAN_INTR, (INTR_EIC | INTR_RFIC | + INTR_BOIC | INTR_PWIC | INTR_PEIC)); + spin_unlock(&cdev->lock); + return IRQ_HANDLED; + } + + /* Check for Tx interrupt and Processing it */ + if ((isr & INTR_TEIS)) { + isr &= (~INTR_REIS); + phytium_can_tx_interrupt(dev, isr); + } + + /* Check for the type of receive interrupt and Processing it */ + if (isr & (INTR_REIS)) { + cdev->isr = (isr & INTR_REIS); + phytium_can_clr_reg_bits(cdev, CAN_INTR, INTR_REIE); + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_REIC); + napi_schedule(&cdev->napi); + } + spin_unlock(&cdev->lock); + return IRQ_HANDLED; +} + +/** + * phytium_can_set_bittiming - CAN set bit timing routine + * @dev: Pointer to net_device structure + * + * This is the driver set bittiming routine. + * Return: 0 on success and failure value on error + */ +static int phytium_can_set_bittiming(struct net_device *dev) +{ + struct phytium_can_dev *cdev = netdev_priv(dev); + const struct can_bittiming *bt = &cdev->can.bittiming; + const struct can_bittiming *dbt = &cdev->can.data_bittiming; + u32 btr, dbtr; + u32 is_config_mode; + + /** + * Check whether Phytium CAN is in configuration mode. + * It cannot set bit timing if Phytium CAN is not in configuration mode. + */ + is_config_mode = phytium_can_read(cdev, CAN_CTRL) & CTRL_XFER; + if (is_config_mode) { + netdev_alert(dev, "BUG! Cannot set bittiming - CAN is not in config mode\n"); + return -EPERM; + } + + /* Setting Baud Rate prescalar value in BRPR Register */ + btr = (bt->brp - 1) << 16; + + /* Setting Time Segment 1 in BTR Register */ + btr |= (bt->prop_seg - 1) << 2; + + btr |= (bt->phase_seg1 - 1) << 5; + + /* Setting Time Segment 2 in BTR Register */ + btr |= (bt->phase_seg2 - 1) << 8; + + /* Setting Synchronous jump width in BTR Register */ + btr |= (bt->sjw - 1); + + dbtr = (dbt->brp - 1) << 16; + dbtr |= (dbt->prop_seg - 1) << 2; + dbtr |= (dbt->phase_seg1 - 1) << 5; + dbtr |= (dbt->phase_seg2 - 1) << 8; + dbtr |= (dbt->sjw - 1); + + if (cdev->can.ctrlmode & CAN_CTRLMODE_FD) { + phytium_can_write(cdev, CAN_ARB_RATE_CTRL, btr); + phytium_can_write(cdev, CAN_DAT_RATE_CTRL, dbtr); + } else { + phytium_can_write(cdev, CAN_ARB_RATE_CTRL, btr); + phytium_can_write(cdev, CAN_DAT_RATE_CTRL, btr); + } + + netdev_dbg(dev, "DAT=0x%08x, ARB=0x%08x\n", + phytium_can_read(cdev, CAN_DAT_RATE_CTRL), + phytium_can_read(cdev, CAN_ARB_RATE_CTRL)); + + return 0; +} + +/** + * phytium_can_start - This the drivers start routine + * @dev: Pointer to net_device structure + * + * This is the drivers start routine. + * Based on the State of the CAN device it puts + * the CAN device into a proper mode. + * + * Return: 0 on success and failure value on error + */ +static void phytium_can_start(struct net_device *dev) +{ + struct phytium_can_dev *cdev = netdev_priv(dev); + u32 ctrl; + + /* Disable transfer */ + ctrl = phytium_can_read(cdev, CAN_CTRL); + ctrl &= ~CTRL_XFER; + phytium_can_write(cdev, CAN_CTRL, ctrl); + + /* XXX: If CANFD, reset the controller */ + phytium_can_write(cdev, CAN_CTRL, (ctrl | CTRL_RST)); + + /* Bittiming setup */ + phytium_can_set_bittiming(dev); + + /* Acceptance identifier mask setup */ + phytium_can_write(cdev, CAN_ACC_ID0_MASK, ACC_IDX_MASK_AID_MASK); + phytium_can_write(cdev, CAN_ACC_ID1_MASK, ACC_IDX_MASK_AID_MASK); + phytium_can_write(cdev, CAN_ACC_ID2_MASK, ACC_IDX_MASK_AID_MASK); + phytium_can_write(cdev, CAN_ACC_ID3_MASK, ACC_IDX_MASK_AID_MASK); + ctrl |= CTRL_AIME; + + if (cdev->can.ctrlmode & CAN_CTRLMODE_FD) + ctrl |= CTRL_IOF | CTRL_FDCRC; + + phytium_can_write(cdev, CAN_CTRL, ctrl); + + cdev->can.state = CAN_STATE_ERROR_ACTIVE; + + phytium_can_enable_all_interrupts(cdev); + + if (cdev->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) + ctrl |= CTRL_XFER; + else + ctrl |= CTRL_XFER | CTRL_TXREQ; + + phytium_can_write(cdev, CAN_CTRL, ctrl); +} + +/** + * phytium_can_stop - Driver stop routine + * @dev: Pointer to net_device structure + * + * This is the drivers stop routine. It will disable the + * interrupts and put the device into configuration mode. + */ +static void phytium_can_stop(struct net_device *dev) +{ + struct phytium_can_dev *cdev = netdev_priv(dev); + u32 ctrl; + + /* Disable all interrupts */ + phytium_can_disable_all_interrupt(cdev); + + /* Disable transfer and switch to receive-only mode */ + ctrl = phytium_can_read(cdev, CAN_CTRL); + ctrl &= ~(CTRL_XFER | CTRL_TXREQ); + phytium_can_write(cdev, CAN_CTRL, ctrl); + + del_timer(&cdev->timer); + + /* Set the state as STOPPED */ + cdev->can.state = CAN_STATE_STOPPED; +} + +static void phytium_can_clean(struct net_device *dev) +{ + struct phytium_can_dev *cdev = netdev_priv(dev); + + if (cdev->tx_skb) { + dev->stats.tx_errors++; + can_free_echo_skb(cdev->net, 0, NULL); + cdev->tx_skb = NULL; + } +} + +static int phytium_can_set_mode(struct net_device *dev, enum can_mode mode) +{ + switch (mode) { + case CAN_MODE_START: + phytium_can_clean(dev); + phytium_can_start(dev); + netif_wake_queue(dev); + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +/** + * phytium_can_open - Driver open routine + * @dev: Pointer to net_device structure + * + * This is the driver open routine. + * Return: 0 on success and failure value on error + */ +static int phytium_can_open(struct net_device *dev) +{ + struct phytium_can_dev *cdev = netdev_priv(dev); + int ret; + + ret = pm_runtime_get_sync(cdev->dev); + if (ret < 0) { + netdev_err(dev, "%s: pm_runtime_get failed(%d)\n", + __func__, ret); + return ret; + } + + /* Open the CAN device */ + ret = open_candev(dev); + if (ret) { + netdev_err(dev, "failed to open can device\n"); + goto disable_clk; + } + + /* Register interrupt handler */ + ret = request_irq(dev->irq, phytium_can_isr, + IRQF_SHARED, dev->name, dev); + if (ret < 0) { + netdev_err(dev, "failed to request interrupt\n"); + goto fail; + } + + /* Start the controller */ + phytium_can_start(dev); + + netdev_dbg(dev, "%s is going on\n", __func__); + + napi_enable(&cdev->napi); + cdev->is_stop_queue_flag = STOP_QUEUE_FALSE; + netif_start_queue(dev); + + return 0; + +fail: + pm_runtime_put(cdev->dev); + close_candev(dev); +disable_clk: + pm_runtime_put_sync(cdev->dev); + return ret; +} + +/** + * phytium_can_close - Driver close routine + * @dev: Pointer to net_device structure + * + * Return: 0 always + */ +static int phytium_can_close(struct net_device *dev) +{ + struct phytium_can_dev *cdev = netdev_priv(dev); + + netif_stop_queue(dev); + napi_disable(&cdev->napi); + + phytium_can_stop(dev); + free_irq(dev->irq, dev); + pm_runtime_put_sync(cdev->dev); + + close_candev(dev); + + return 0; +} + +/** + * phytium_can_start_xmit - Starts the transmission + * + * Return: 0 on success. + */ +static netdev_tx_t phytium_can_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct phytium_can_dev *cdev = netdev_priv(dev); + + if (can_dropped_invalid_skb(dev, skb)) + return NETDEV_TX_OK; + + cdev->tx_skb = skb; + + return phytium_can_tx_handler(cdev); +} + +static const struct net_device_ops phytium_can_netdev_ops = { + .ndo_open = phytium_can_open, + .ndo_stop = phytium_can_close, + .ndo_start_xmit = phytium_can_start_xmit, + .ndo_change_mtu = can_change_mtu, +}; + +static int register_phytium_can_dev(struct net_device *dev) +{ + dev->flags |= IFF_ECHO; + dev->netdev_ops = &phytium_can_netdev_ops; + + return register_candev(dev); +} + +static int phytium_can_dev_setup(struct phytium_can_dev *cdev) +{ + struct net_device *dev = cdev->net; + + netif_napi_add(dev, &cdev->napi, phytium_can_poll); + + cdev->can.do_set_mode = phytium_can_set_mode; + cdev->can.do_get_berr_counter = phytium_can_get_berr_counter; + + cdev->can.ctrlmode_supported = CAN_CTRLMODE_LISTENONLY | + CAN_CTRLMODE_BERR_REPORTING; + cdev->can.bittiming_const = cdev->bit_timing; + + if (cdev->fdmode) { + cdev->can.ctrlmode_supported |= CAN_CTRLMODE_FD; + dev->mtu = CANFD_MTU; + cdev->can.ctrlmode = CAN_CTRLMODE_FD; + cdev->can.data_bittiming_const = cdev->bit_timing; + } + spin_lock_init(&cdev->lock); + return 0; +} + +struct phytium_can_dev *phytium_can_allocate_dev(struct device *dev, int sizeof_priv, + int tx_fifo_depth) +{ + struct phytium_can_dev *cdev = NULL; + struct net_device *net_dev; + + /* Allocate the can device struct */ + net_dev = alloc_candev(sizeof_priv, tx_fifo_depth); + if (!net_dev) { + dev_err(dev, "Failed to allocate CAN device.\n"); + goto out; + } + + cdev = netdev_priv(net_dev); + cdev->net = net_dev; + cdev->dev = dev; + SET_NETDEV_DEV(net_dev, dev); + +out: + return cdev; +} +EXPORT_SYMBOL(phytium_can_allocate_dev); + +void phytium_can_free_dev(struct net_device *net) +{ + free_candev(net); +} +EXPORT_SYMBOL(phytium_can_free_dev); + +int phytium_can_register(struct phytium_can_dev *cdev) +{ + int ret; + + ret = phytium_can_dev_setup(cdev); + if (ret) + goto fail; + + ret = register_phytium_can_dev(cdev->net); + if (ret) { + dev_err(cdev->dev, "registering %s failed (err=%d)\n", + cdev->net->name, ret); + goto fail; + } + + cdev->is_tx_done = true; + cdev->is_need_stop_xmit = false; + timer_setup(&cdev->timer, phytium_can_tx_done_timeout, 0); + + dev_info(cdev->dev, "%s device registered (irq=%d)\n", + KBUILD_MODNAME, cdev->net->irq); + + /* Probe finished + * Stop clocks. They will be reactivated once the device is opened. + */ + pm_runtime_put_sync(cdev->dev); + + return 0; + +fail: + pm_runtime_put_sync(cdev->dev); + return ret; +} +EXPORT_SYMBOL(phytium_can_register); + +void phytium_can_unregister(struct phytium_can_dev *cdev) +{ + unregister_candev(cdev->net); +} +EXPORT_SYMBOL(phytium_can_unregister); + +int phytium_can_suspend(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct phytium_can_dev *cdev = netdev_priv(ndev); + + if (netif_running(ndev)) { + netif_stop_queue(ndev); + netif_device_detach(ndev); + phytium_can_stop(ndev); + pm_runtime_put_sync(cdev->dev); + } + + cdev->can.state = CAN_STATE_SLEEPING; + + return 0; +} +EXPORT_SYMBOL(phytium_can_suspend); + +int phytium_can_resume(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct phytium_can_dev *cdev = netdev_priv(ndev); + int ret; + + cdev->can.state = CAN_STATE_ERROR_ACTIVE; + + if (netif_running(ndev)) { + ret = pm_runtime_resume(cdev->dev); + if (ret) + return ret; + + phytium_can_start(ndev); + netif_device_attach(ndev); + netif_start_queue(ndev); + } + + return 0; +} +EXPORT_SYMBOL(phytium_can_resume); + +MODULE_AUTHOR("Cheng Quan "); +MODULE_AUTHOR("Chen Baozi "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CAN bus driver for Phytium CAN controller"); + diff --git a/drivers/net/can/phytium/phytium_can.h b/drivers/net/can/phytium/phytium_can.h new file mode 100644 index 0000000000000000000000000000000000000000..802bb36a131160d960585987b245c9320b49b725 --- /dev/null +++ b/drivers/net/can/phytium/phytium_can.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Phytium CAN controller driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef _PHYTIUM_CAN_H_ +#define _PHYTIUM_CAN_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define KEEP_CAN_FIFO_MIN_LEN 16 +#define KEEP_CANFD_FIFO_MIN_LEN 128 +#define CAN_FIFO_BYTE_LEN 256 +#define STOP_QUEUE_TRUE 1 +#define STOP_QUEUE_FALSE 0 + +enum phytium_can_ip_type { + PHYTIUM_CAN = 0, + PHYTIUM_CANFD, +}; + +struct phytium_can_devtype { + enum phytium_can_ip_type cantype; + const struct can_bittiming_const *bittiming_const; +}; + +struct phytium_can_dev { + struct can_priv can; + + struct napi_struct napi; + struct net_device *net; + struct device *dev; + struct clk *clk; + + struct sk_buff *tx_skb; + + const struct can_bittiming_const *bit_timing; + spinlock_t lock; /*spinlock*/ + int fdmode; + u32 isr; + u32 tx_fifo_depth; + unsigned int is_stop_queue_flag; + void __iomem *base; + + struct timer_list timer; /* xmit done timer */ + u32 is_tx_done; + u32 is_need_stop_xmit; +}; + +struct phytium_can_dev *phytium_can_allocate_dev(struct device *dev, int sizeof_priv, + int tx_fifo_depth); +void phytium_can_free_dev(struct net_device *net); + +int phytium_can_register(struct phytium_can_dev *cdev); +void phytium_can_unregister(struct phytium_can_dev *cdev); + +int phytium_can_suspend(struct device *dev); +int phytium_can_resume(struct device *dev); +#endif /* _PHYTIUM_CAN_H_ */ diff --git a/drivers/net/can/phytium/phytium_can_pci.c b/drivers/net/can/phytium/phytium_can_pci.c new file mode 100644 index 0000000000000000000000000000000000000000..af7f606f432a0dca090e7a6da2be4d276bc0f6be --- /dev/null +++ b/drivers/net/can/phytium/phytium_can_pci.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Platform CAN bus driver for Phytium CAN controller + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include + +#include "phytium_can.h" + +struct phytium_can_pci_config { + const struct phytium_can_devtype *devtype; + unsigned int clock_freq; + unsigned int tx_fifo_depth; +}; + +#define cdev2priv(dev) container_of(dev, struct phytium_can_pci, cdev) + +struct phytium_can_pci { + struct phytium_can_dev cdev; + + void __iomem *base; +}; + +static const struct can_bittiming_const phytium_bittiming_const_8192 = { + .name = "phytium_can", + .tseg1_min = 1, /* Time segment 1 = prop_seg + phase_seg1 */ + .tseg1_max = 16, + .tseg2_min = 1, /* Time segment 2 = phase_seg2 */ + .tseg2_max = 8, + .sjw_max = 4, /* Synchronisation jump width */ + .brp_min = 1, /* Bit-rate prescaler */ + .brp_max = 8192, + .brp_inc = 2, +}; + +static const struct phytium_can_devtype phytium_can_pci = { + .cantype = PHYTIUM_CAN, + .bittiming_const = &phytium_bittiming_const_8192, +}; + +static const struct phytium_can_pci_config phytium_can_pci_data = { + .devtype = &phytium_can_pci, + .clock_freq = 480000000, + .tx_fifo_depth = 64, +}; + +static int phytium_can_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + const struct phytium_can_pci_config *cfg; + struct phytium_can_dev *cdev; + struct phytium_can_pci *priv; + int ret; + + cfg = (const struct phytium_can_pci_config *)id->driver_data; + + ret = pcim_enable_device(pdev); + if (ret) + goto err; + + ret = pcim_iomap_regions(pdev, 0x1, pci_name(pdev)); + if (ret) + goto err; + + cdev = phytium_can_allocate_dev(&pdev->dev, sizeof(struct phytium_can_pci), + cfg->tx_fifo_depth); + if (!cdev) + return -ENOMEM; + + priv = cdev2priv(cdev); + priv->base = pcim_iomap_table(pdev)[0]; + + cdev->dev = &pdev->dev; + cdev->fdmode = cfg->devtype->cantype; + cdev->bit_timing = cfg->devtype->bittiming_const; + cdev->can.clock.freq = cfg->clock_freq; + cdev->tx_fifo_depth = cfg->tx_fifo_depth; + + cdev->base = priv->base; + cdev->net->irq = pdev->irq; + + pci_set_drvdata(pdev, cdev->net); + + if (!pm_runtime_enabled(cdev->dev)) + pm_runtime_enable(cdev->dev); + ret = pm_runtime_get_sync(cdev->dev); + if (ret < 0) { + netdev_err(cdev->net, "%s: pm_runtime_get failed(%d)\n", + __func__, ret); + goto err_pmdisable; + } + ret = phytium_can_register(cdev); + if (ret) + goto err; + + return 0; + +err_pmdisable: + pm_runtime_disable(&pdev->dev); +err: + return ret; +} + +static void phytium_can_pci_remove(struct pci_dev *pdev) +{ + struct net_device *dev = pci_get_drvdata(pdev); + struct phytium_can_dev *cdev = netdev_priv(dev); + + pm_runtime_disable(cdev->dev); + + phytium_can_unregister(cdev); + phytium_can_free_dev(cdev->net); +} + +static __maybe_unused int phytium_can_pci_suspend(struct device *dev) +{ + return phytium_can_suspend(dev); +} + +static __maybe_unused int phytium_can_pci_resume(struct device *dev) +{ + return phytium_can_resume(dev); +} + +static SIMPLE_DEV_PM_OPS(phytium_can_pci_pm_ops, + phytium_can_pci_suspend, phytium_can_pci_resume); + +static const struct pci_device_id phytium_can_pci_id_table[] = { + { PCI_VDEVICE(PHYTIUM, 0xdc2d), (kernel_ulong_t)&phytium_can_pci_data, }, + { /* sentinel */ }, +}; + +static struct pci_driver phytium_can_pci_driver = { + .name = KBUILD_MODNAME, + .probe = phytium_can_pci_probe, + .remove = phytium_can_pci_remove, + .id_table = phytium_can_pci_id_table, + .driver = { + .pm = &phytium_can_pci_pm_ops, + }, +}; + +module_pci_driver(phytium_can_pci_driver); + +MODULE_AUTHOR("Cheng Quan +#include +#include + +#include "phytium_can.h" + +#define cdev2priv(dev) container_of(dev, struct phytium_can_plat, cdev) + +struct phytium_can_plat { + struct phytium_can_dev cdev; + struct phytium_can_devtype *devtype; + + int irq; + void __iomem *reg_base; +}; + +static const struct can_bittiming_const phytium_bittiming_const_512 = { + .name = "phytium_can", + .tseg1_min = 1, /* Time segment 1 = prop_seg + phase_seg1 */ + .tseg1_max = 16, + .tseg2_min = 1, /* Time segment 2 = phase_seg2 */ + .tseg2_max = 8, + .sjw_max = 4, /* Synchronisation jump width */ + .brp_min = 1, /* Bit-rate prescaler */ + .brp_max = 512, + .brp_inc = 2, +}; + +static const struct can_bittiming_const phytium_bittiming_const_8192 = { + .name = "phytium_can", + .tseg1_min = 1, /* Time segment 1 = prop_seg + phase_seg1 */ + .tseg1_max = 16, + .tseg2_min = 1, /* Time segment 2 = phase_seg2 */ + .tseg2_max = 8, + .sjw_max = 4, /* Synchronisation jump width */ + .brp_min = 1, /* Bit-rate prescaler */ + .brp_max = 8192, + .brp_inc = 2, +}; + +static const struct phytium_can_devtype phytium_can_data = { + .cantype = PHYTIUM_CAN, + .bittiming_const = &phytium_bittiming_const_512, +}; + +static const struct phytium_can_devtype phytium_canfd_data = { + .cantype = PHYTIUM_CANFD, + .bittiming_const = &phytium_bittiming_const_8192, +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_can_acpi_ids[] = { + { "PHYT000A", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(acpi, phytium_can_acpi_ids); +#endif + +#ifdef CONFIG_OF +static const struct of_device_id phytium_can_of_ids[] = { + { .compatible = "phytium,can", .data = &phytium_can_data }, + { .compatible = "phytium,canfd", .data = &phytium_canfd_data }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, phytium_can_of_ids); +#endif + +static int phytium_can_plat_probe(struct platform_device *pdev) +{ + struct phytium_can_dev *cdev; + struct phytium_can_plat *priv; + struct resource *res; + const struct of_device_id *of_id; + const struct phytium_can_devtype *devtype = &phytium_can_data; + u32 tx_fifo_depth; + int ret; + const char *str = "can"; + + ret = fwnode_property_read_u32(dev_fwnode(&pdev->dev), "tx-fifo-depth", &tx_fifo_depth); + if (ret) + tx_fifo_depth = 64; + + cdev = phytium_can_allocate_dev(&pdev->dev, sizeof(struct phytium_can_plat), + tx_fifo_depth); + if (!cdev) + return -ENOMEM; + + priv = cdev2priv(cdev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->reg_base = devm_ioremap_resource(&pdev->dev, res); + priv->irq = platform_get_irq(pdev, 0); + if (IS_ERR(priv->reg_base) || cdev->net->irq < 0) { + ret = -EINVAL; + goto fail; + } + + if (pdev->dev.of_node) { + cdev->clk = devm_clk_get(&pdev->dev, "can_clk"); + if (IS_ERR(cdev->clk)) { + dev_err(&pdev->dev, "no clock found\n"); + ret = -ENODEV; + goto fail; + } + cdev->can.clock.freq = clk_get_rate(cdev->clk); + + of_id = of_match_device(phytium_can_of_ids, &pdev->dev); + if (of_id && of_id->data) + devtype = of_id->data; + } else if (has_acpi_companion(&pdev->dev)) { + ret = fwnode_property_read_u32(dev_fwnode(&pdev->dev), + "clock-frequency", + &cdev->can.clock.freq); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get clock frequency.\n"); + goto fail; + } + + fwnode_property_read_string(dev_fwnode(&pdev->dev), + "mode-select", &str); + if (!(strcmp(str, "canfd"))) + devtype = &phytium_canfd_data; + } + + cdev->tx_fifo_depth = tx_fifo_depth; + + if (devtype->cantype == PHYTIUM_CANFD) + cdev->fdmode = 1; + else + cdev->fdmode = 0; + + if (fwnode_property_present(dev_fwnode(&pdev->dev), "extend_brp")) + cdev->bit_timing = &phytium_bittiming_const_8192; + else + cdev->bit_timing = devtype->bittiming_const; + cdev->can.bittiming_const = devtype->bittiming_const; + cdev->base = priv->reg_base; + cdev->net->irq = priv->irq; + + platform_set_drvdata(pdev, cdev->net); + + pm_runtime_enable(cdev->dev); + ret = phytium_can_register(cdev); + if (ret) + goto out_runtime_disable; + + return ret; + +out_runtime_disable: + pm_runtime_disable(cdev->dev); +fail: + phytium_can_free_dev(cdev->net); + return ret; +} + +static __maybe_unused int phytium_can_plat_suspend(struct device *dev) +{ + return phytium_can_suspend(dev); +} + +static __maybe_unused int phytium_can_plat_resume(struct device *dev) +{ + return phytium_can_resume(dev); +} + +static int phytium_can_plat_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct phytium_can_dev *cdev = netdev_priv(dev); + + phytium_can_unregister(cdev); + + phytium_can_free_dev(cdev->net); + + return 0; +} + +static int __maybe_unused phytium_can_runtime_suspend(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct phytium_can_dev *cdev = netdev_priv(ndev); + + clk_disable_unprepare(cdev->clk); + + return 0; +} + +static int __maybe_unused phytium_can_runtime_resume(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct phytium_can_dev *cdev = netdev_priv(ndev); + + return clk_prepare_enable(cdev->clk); +} + +static const struct dev_pm_ops phytium_can_plat_pm_ops = { + SET_RUNTIME_PM_OPS(phytium_can_runtime_suspend, + phytium_can_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(phytium_can_suspend, phytium_can_resume) +}; + +static struct platform_driver phytium_can_plat_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = of_match_ptr(phytium_can_of_ids), + .acpi_match_table = ACPI_PTR(phytium_can_acpi_ids), + .pm = &phytium_can_plat_pm_ops, + }, + .probe = phytium_can_plat_probe, + .remove = phytium_can_plat_remove, +}; + +module_platform_driver(phytium_can_plat_driver); + +MODULE_AUTHOR("Cheng Quan "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium CAN driver for IO Mapped controllers"); diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index af05e2d291701041f803f1de51ed0530abcb4913..d51553eb92d82552adeb38d5ed1f37e8eb2ef97a 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -164,6 +164,7 @@ config ETHOC source "drivers/net/ethernet/packetengines/Kconfig" 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 1bdca975d8d125695db4a1490e92094c55629ba3..c2822a5a2be0111f6d816b92532b498c86050fd2 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_NET_VENDOR_OKI) += oki-semi/ obj-$(CONFIG_ETHOC) += ethoc.o obj-$(CONFIG_NET_VENDOR_PACKET_ENGINES) += packetengines/ 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/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index 78c972bb1d962368ed0c62b5f3d28e47047f1b34..3a5441dcc7337744e9de84094c84b6e4fbbc4e0e 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -85,6 +85,7 @@ #define GEM_PBUFRXCUT 0x0044 /* RX Partial Store and Forward */ #define GEM_JML 0x0048 /* Jumbo Max Length */ #define GEM_HS_MAC_CONFIG 0x0050 /* GEM high speed config */ +#define GEM_AXI_PIPE 0x0054 /* Axi max pipeline register*/ #define GEM_HRB 0x0080 /* Hash Bottom */ #define GEM_HRT 0x0084 /* Hash Top */ #define GEM_SA1B 0x0088 /* Specific1 Bottom */ @@ -220,6 +221,36 @@ #define GEM_IDR(hw_q) (0x0620 + ((hw_q) << 2)) #define GEM_IMR(hw_q) (0x0640 + ((hw_q) << 2)) +#define GEM_SRC_SEL_LN 0x1C04 +#define GEM_DIV_SEL0_LN 0x1C08 +#define GEM_DIV_SEL1_LN 0x1C0C +#define GEM_PMA_XCVR_POWER_STATE 0x1C10 +#define GEM_SPEED_MODE 0x1C14 +#define GEM_MII_SELECT 0x1C18 +#define GEM_SEL_MII_ON_RGMII 0x1C1C +#define GEM_TX_CLK_SEL0 0x1C20 +#define GEM_TX_CLK_SEL1 0x1C24 +#define GEM_TX_CLK_SEL2 0x1C28 +#define GEM_TX_CLK_SEL3 0x1C2C +#define GEM_RX_CLK_SEL0 0x1C30 +#define GEM_RX_CLK_SEL1 0x1C34 +#define GEM_CLK_250M_DIV10_DIV100_SEL 0x1C38 +#define GEM_TX_CLK_SEL5 0x1C3C +#define GEM_TX_CLK_SEL6 0x1C40 +#define GEM_RX_CLK_SEL4 0x1C44 +#define GEM_RX_CLK_SEL5 0x1C48 +#define GEM_TX_CLK_SEL3_0 0x1C70 +#define GEM_TX_CLK_SEL4_0 0x1C74 +#define GEM_RX_CLK_SEL3_0 0x1C78 +#define GEM_RX_CLK_SEL4_0 0x1C7C +#define GEM_RGMII_TX_CLK_SEL0 0x1C80 +#define GEM_RGMII_TX_CLK_SEL1 0x1C84 + +#define GEM_PHY_INT_ENABLE 0x1C88 +#define GEM_PHY_INT_CLEAR 0x1C8C +#define GEM_PHY_INT_STATE 0x1C90 +#define GEM_INTX_IRQ_MASK 0x1C7C /* Phytium: irq mask */ + /* Bitfields in NCR */ #define MACB_LB_OFFSET 0 /* reserved */ #define MACB_LB_SIZE 1 @@ -254,6 +285,8 @@ #define MACB_OSSMODE_SIZE 1 #define MACB_MIIONRGMII_OFFSET 28 /* MII Usage on RGMII Interface */ #define MACB_MIIONRGMII_SIZE 1 +#define MACB_2PT5G_OFFSET 29 /* 2.5G operation selected */ +#define MACB_2PT5G_SIZE 1 /* Bitfields in NCFGR */ #define MACB_SPD_OFFSET 0 /* Speed */ @@ -562,6 +595,8 @@ #define GEM_RX_SCR_BYPASS_SIZE 1 #define GEM_TX_SCR_BYPASS_OFFSET 8 #define GEM_TX_SCR_BYPASS_SIZE 1 +#define GEM_RX_SYNC_RESET_OFFSET 2 +#define GEM_RX_SYNC_RESET_SIZE 1 #define GEM_TX_EN_OFFSET 1 #define GEM_TX_EN_SIZE 1 #define GEM_SIGNAL_OK_OFFSET 0 @@ -731,8 +766,10 @@ #define MACB_CAPS_GEM_HAS_PTP 0x00000040 #define MACB_CAPS_BD_RD_PREFETCH 0x00000080 #define MACB_CAPS_NEEDS_RSTONUBR 0x00000100 +#define MACB_CAPS_SEL_CLK 0x00000800 #define MACB_CAPS_MIIONRGMII 0x00000200 #define MACB_CAPS_NEED_TSUCLK 0x00000400 +#define MACB_CAPS_SEL_CLK 0x00000800 #define MACB_CAPS_PCS 0x01000000 #define MACB_CAPS_HIGH_SPEED 0x02000000 #define MACB_CAPS_CLK_HW_CHG 0x04000000 @@ -741,6 +778,8 @@ #define MACB_CAPS_GIGABIT_MODE_AVAILABLE 0x20000000 #define MACB_CAPS_SG_DISABLED 0x40000000 #define MACB_CAPS_MACB_IS_GEM 0x80000000 +#define MACB_CAPS_PCS 0x01000000 +#define MACB_CAPS_HIGH_SPEED 0x02000000 /* LSO settings */ #define MACB_LSO_UFO_ENABLE 0x01 @@ -783,6 +822,8 @@ #define gem_readl_n(port, reg, idx) (port)->macb_reg_readl((port), GEM_##reg + idx * 4) #define gem_writel_n(port, reg, idx, value) (port)->macb_reg_writel((port), GEM_##reg + idx * 4, (value)) +#define PTP_TS_BUFFER_SIZE 128 /* must be power of 2 */ + /* Conditional GEM/MACB macros. These perform the operation to the correct * register dependent on whether the device is a GEM or a MACB. For registers * and bitfields that are common across both devices, use macb_{read,write}l @@ -832,6 +873,11 @@ struct macb_dma_desc_ptp { u32 ts_1; u32 ts_2; }; + +struct gem_tx_ts { + struct sk_buff *skb; + struct macb_dma_desc_ptp desc_ptp; +}; #endif /* DMA descriptor bitfields */ @@ -1193,6 +1239,7 @@ struct macb_config { unsigned int max_tx_length; int jumbo_max_len; const struct macb_usrio_config *usrio; + void (*sel_clk_hw)(struct macb *bp, int speed); }; struct tsu_incr { @@ -1231,8 +1278,15 @@ struct macb_queue { struct macb_dma_desc *rx_ring; struct sk_buff **rx_skbuff; void *rx_buffers; + struct napi_struct napi; struct napi_struct napi_rx; struct queue_stats stats; + +#ifdef CONFIG_MACB_USE_HWSTAMP + struct work_struct tx_ts_task; + unsigned int tx_ts_head, tx_ts_tail; + struct gem_tx_ts tx_timestamps[PTP_TS_BUFFER_SIZE]; +#endif }; struct ethtool_rx_fs_item { @@ -1270,6 +1324,7 @@ struct macb { struct clk *rx_clk; struct clk *tsu_clk; struct net_device *dev; + struct ncsi_dev *ndev; union { struct macb_stats macb; struct gem_stats gem; @@ -1282,6 +1337,10 @@ struct macb { struct phylink_config phylink_config; struct phylink_pcs phylink_usx_pcs; struct phylink_pcs phylink_sgmii_pcs; + int link; + int speed; + int duplex; + int use_ncsi; u32 caps; unsigned int dma_burst_length; @@ -1330,6 +1389,8 @@ struct macb { struct macb_pm_data pm_data; const struct macb_usrio_config *usrio; + + void (*sel_clk_hw)(struct macb *bp, int speed); }; #ifdef CONFIG_MACB_USE_HWSTAMP diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index b940dcd3ace681d6dd4cba431600f89d9a3913a6..f065b9abd149f25b4d76a5907f71dec4695df1b0 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,8 @@ #include #include #include +#include +#include #include "macb.h" /* This structure is only used for MACB on SiFive FU540 devices */ @@ -47,6 +50,9 @@ struct sifive_fu540_macb_mgmt { struct clk_hw hw; }; +#define MAX_RING_ADDR_ALLOC_TIMES 3 +#define RING_ADDR_INTERVAL 128 + #define MACB_RX_BUFFER_SIZE 128 #define RX_BUFFER_MULTIPLE 64 /* bytes */ @@ -87,7 +93,12 @@ struct sifive_fu540_macb_mgmt { #define MACB_WOL_HAS_MAGIC_PACKET (0x1 << 0) #define MACB_WOL_ENABLED (0x1 << 1) -#define HS_SPEED_10000M 4 +#define HS_SPEED_100M 0 +#define HS_SPEED_1000M 1 +#define HS_SPEED_2500M 2 +#define HS_SPEED_5000M 3 +#define HS_SPEED_10000M 4 +#define MACB_SERDES_RATE_5G 0 #define MACB_SERDES_RATE_10G 1 /* Graceful stop timeouts in us. We should allow up to @@ -98,6 +109,10 @@ struct sifive_fu540_macb_mgmt { #define MACB_MDIO_TIMEOUT 1000000 /* in usecs */ +static void macb_tx_unmap(struct macb *bp, + struct macb_tx_skb *tx_skb, + int budget); + /* DMA buffer descriptor might be different size * depends on hardware configuration: * @@ -562,7 +577,7 @@ static void macb_set_tx_clk(struct macb *bp, int speed) netdev_err(bp->dev, "adjusting tx_clk failed.\n"); } -static void macb_usx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, +static void macb_usx_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode, phy_interface_t interface, int speed, int duplex) { @@ -570,10 +585,25 @@ static void macb_usx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, u32 config; config = gem_readl(bp, USX_CONTROL); - config = GEM_BFINS(SERDES_RATE, MACB_SERDES_RATE_10G, config); - config = GEM_BFINS(USX_CTRL_SPEED, HS_SPEED_10000M, config); + if (speed == SPEED_10000) { + config = GEM_BFINS(SERDES_RATE, MACB_SERDES_RATE_10G, config); + config = GEM_BFINS(USX_CTRL_SPEED, HS_SPEED_10000M, config); + } else if (speed == SPEED_5000) { + config = GEM_BFINS(SERDES_RATE, MACB_SERDES_RATE_5G, config); + config = GEM_BFINS(USX_CTRL_SPEED, HS_SPEED_5000M, config); + } + config &= ~(GEM_BIT(TX_SCR_BYPASS) | GEM_BIT(RX_SCR_BYPASS)); - config |= GEM_BIT(TX_EN); + /* reset */ + config &= ~(GEM_BIT(SIGNAL_OK) | GEM_BIT(TX_EN)); + config |= GEM_BIT(RX_SYNC_RESET); + + gem_writel(bp, USX_CONTROL, config); + + /* enable rx and tx */ + config &= ~(GEM_BIT(RX_SYNC_RESET)); + config |= GEM_BIT(SIGNAL_OK) | GEM_BIT(TX_EN); + gem_writel(bp, USX_CONTROL, config); } @@ -583,7 +613,12 @@ static void macb_usx_pcs_get_state(struct phylink_pcs *pcs, struct macb *bp = container_of(pcs, struct macb, phylink_usx_pcs); u32 val; - state->speed = SPEED_10000; + if (state->interface == PHY_INTERFACE_MODE_5GBASER) + state->speed = SPEED_5000; + else if (state->interface == PHY_INTERFACE_MODE_10GBASER || + state->interface == PHY_INTERFACE_MODE_USXGMII) + state->speed = bp->speed; + state->duplex = 1; state->an_complete = 1; @@ -661,9 +696,12 @@ static void macb_mac_config(struct phylink_config *config, unsigned int mode, ctrl &= ~(GEM_BIT(SGMIIEN) | GEM_BIT(PCSSEL)); ncr &= ~GEM_BIT(ENABLE_HS_MAC); - if (state->interface == PHY_INTERFACE_MODE_SGMII) { + if (state->interface == PHY_INTERFACE_MODE_SGMII || + state->interface == PHY_INTERFACE_MODE_2500BASEX) { ctrl |= GEM_BIT(SGMIIEN) | GEM_BIT(PCSSEL); - } else if (state->interface == PHY_INTERFACE_MODE_10GBASER) { + } else if (state->interface == PHY_INTERFACE_MODE_10GBASER || + state->interface == PHY_INTERFACE_MODE_USXGMII || + state->interface == PHY_INTERFACE_MODE_5GBASER) { ctrl |= GEM_BIT(PCSSEL); ncr |= GEM_BIT(ENABLE_HS_MAC); } else if (bp->caps & MACB_CAPS_MIIONRGMII && @@ -683,7 +721,8 @@ static void macb_mac_config(struct phylink_config *config, unsigned int mode, * Must be written after PCSSEL is set in NCFGR, * otherwise writes will not take effect. */ - if (macb_is_gem(bp) && state->interface == PHY_INTERFACE_MODE_SGMII) { + if (macb_is_gem(bp) && (state->interface == PHY_INTERFACE_MODE_SGMII || + PHY_INTERFACE_MODE_2500BASEX)) { u32 pcsctrl, old_pcsctrl; old_pcsctrl = gem_readl(bp, PCSCNTRL); @@ -703,9 +742,14 @@ static void macb_mac_link_down(struct phylink_config *config, unsigned int mode, { struct net_device *ndev = to_net_dev(config->dev); struct macb *bp = netdev_priv(ndev); + struct macb_tx_skb *tx_skb; struct macb_queue *queue; unsigned int q; u32 ctrl; + int i; + + if (bp->use_ncsi) + ncsi_stop_dev(bp->ndev); if (!(bp->caps & MACB_CAPS_MACB_IS_EMAC)) for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) @@ -713,12 +757,172 @@ static void macb_mac_link_down(struct phylink_config *config, unsigned int mode, bp->rx_intr_mask | MACB_TX_INT_FLAGS | MACB_BIT(HRESP)); /* Disable Rx and Tx */ - ctrl = macb_readl(bp, NCR) & ~(MACB_BIT(RE) | MACB_BIT(TE)); + ctrl = macb_readl(bp, NCR) & ~(MACB_BIT(RE) | + MACB_BIT(TE)) & ~(MACB_BIT(2PT5G)); macb_writel(bp, NCR, ctrl); + /* Tx clean */ + for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { + for (i = 0; i < bp->tx_ring_size; i++) { + tx_skb = macb_tx_skb(queue, i); + /* free unsent skb buffers */ + if (tx_skb) + macb_tx_unmap(bp, tx_skb, 0); + } + } + netif_tx_stop_all_queues(ndev); } +static void phytium_gem1p0_sel_clk(struct macb *bp, int speed) +{ + if (bp->phy_interface == PHY_INTERFACE_MODE_10GBASER || + bp->phy_interface == PHY_INTERFACE_MODE_USXGMII) { + gem_writel(bp, SRC_SEL_LN, 0x1); /*0x1c04*/ + if (speed == SPEED_5000) { + gem_writel(bp, DIV_SEL0_LN, 0x8); /*0x1c08*/ + gem_writel(bp, DIV_SEL1_LN, 0x2); /*0x1c0c*/ + gem_writel(bp, PMA_XCVR_POWER_STATE, 0x0); /*0x1c10*/ + } else { + gem_writel(bp, DIV_SEL0_LN, 0x4); /*0x1c08*/ + gem_writel(bp, DIV_SEL1_LN, 0x1); /*0x1c0c*/ + gem_writel(bp, PMA_XCVR_POWER_STATE, 0x1); /*0x1c10*/ + } + } else if (bp->phy_interface == PHY_INTERFACE_MODE_5GBASER) { + gem_writel(bp, SRC_SEL_LN, 0x1); /*0x1c04*/ + gem_writel(bp, DIV_SEL0_LN, 0x8); /*0x1c08*/ + gem_writel(bp, DIV_SEL1_LN, 0x2); /*0x1c0c*/ + gem_writel(bp, PMA_XCVR_POWER_STATE, 0x0); /*0x1c10*/ + } else if (bp->phy_interface == PHY_INTERFACE_MODE_2500BASEX) { + gem_writel(bp, SRC_SEL_LN, 0x1); /*0x1c04*/ + gem_writel(bp, DIV_SEL0_LN, 0x1); /*0x1c08*/ + gem_writel(bp, DIV_SEL1_LN, 0x2); /*0x1c0c*/ + gem_writel(bp, PMA_XCVR_POWER_STATE, 0x1); /*0x1c10*/ + gem_writel(bp, TX_CLK_SEL0, 0x0); /*0x1c20*/ + gem_writel(bp, TX_CLK_SEL1, 0x1); /*0x1c24*/ + gem_writel(bp, TX_CLK_SEL2, 0x1); /*0x1c28*/ + gem_writel(bp, TX_CLK_SEL3, 0x1); /*0x1c2c*/ + gem_writel(bp, RX_CLK_SEL0, 0x1); /*0x1c30*/ + gem_writel(bp, RX_CLK_SEL1, 0x0); /*0x1c34*/ + gem_writel(bp, TX_CLK_SEL3_0, 0x0); /*0x1c70*/ + gem_writel(bp, TX_CLK_SEL4_0, 0x0); /*0x1c74*/ + gem_writel(bp, RX_CLK_SEL3_0, 0x0); /*0x1c78*/ + gem_writel(bp, RX_CLK_SEL4_0, 0x0); /*0x1c7c*/ + } else if (bp->phy_interface == PHY_INTERFACE_MODE_SGMII) { + if (speed == SPEED_1000) { + gem_writel(bp, SRC_SEL_LN, 0x1); /*0x1c04*/ + gem_writel(bp, DIV_SEL0_LN, 0x4); /*0x1c08*/ + gem_writel(bp, DIV_SEL1_LN, 0x8); /*0x1c0c*/ + gem_writel(bp, PMA_XCVR_POWER_STATE, 0x1); /*0x1c10*/ + gem_writel(bp, TX_CLK_SEL0, 0x0); /*0x1c20*/ + gem_writel(bp, TX_CLK_SEL1, 0x0); /*0x1c24*/ + gem_writel(bp, TX_CLK_SEL2, 0x0); /*0x1c28*/ + gem_writel(bp, TX_CLK_SEL3, 0x1); /*0x1c2c*/ + gem_writel(bp, RX_CLK_SEL0, 0x1); /*0x1c30*/ + gem_writel(bp, RX_CLK_SEL1, 0x0); /*0x1c34*/ + gem_writel(bp, TX_CLK_SEL3_0, 0x0); /*0x1c70*/ + gem_writel(bp, TX_CLK_SEL4_0, 0x0); /*0x1c74*/ + gem_writel(bp, RX_CLK_SEL3_0, 0x0); /*0x1c78*/ + gem_writel(bp, RX_CLK_SEL4_0, 0x0); /*0x1c7c*/ + } else if (speed == SPEED_100 || speed == SPEED_10) { + gem_writel(bp, SRC_SEL_LN, 0x1); /*0x1c04*/ + gem_writel(bp, DIV_SEL0_LN, 0x4); /*0x1c08*/ + gem_writel(bp, DIV_SEL1_LN, 0x8); /*0x1c0c*/ + gem_writel(bp, PMA_XCVR_POWER_STATE, 0x1); /*0x1c10*/ + gem_writel(bp, TX_CLK_SEL0, 0x0); /*0x1c20*/ + gem_writel(bp, TX_CLK_SEL1, 0x0); /*0x1c24*/ + gem_writel(bp, TX_CLK_SEL2, 0x1); /*0x1c28*/ + gem_writel(bp, TX_CLK_SEL3, 0x1); /*0x1c2c*/ + gem_writel(bp, RX_CLK_SEL0, 0x1); /*0x1c30*/ + gem_writel(bp, RX_CLK_SEL1, 0x0); /*0x1c34*/ + gem_writel(bp, TX_CLK_SEL3_0, 0x1); /*0x1c70*/ + gem_writel(bp, TX_CLK_SEL4_0, 0x0); /*0x1c74*/ + gem_writel(bp, RX_CLK_SEL3_0, 0x0); /*0x1c78*/ + gem_writel(bp, RX_CLK_SEL4_0, 0x1); /*0x1c7c*/ + } + } else if (bp->phy_interface == PHY_INTERFACE_MODE_RGMII || + bp->phy_interface == PHY_INTERFACE_MODE_RGMII_ID) { + if (speed == SPEED_1000) { + gem_writel(bp, MII_SELECT, 0x1); /*0x1c18*/ + gem_writel(bp, SEL_MII_ON_RGMII, 0x0); /*0x1c1c*/ + gem_writel(bp, TX_CLK_SEL0, 0x0); /*0x1c20*/ + gem_writel(bp, TX_CLK_SEL1, 0x1); /*0x1c24*/ + gem_writel(bp, TX_CLK_SEL2, 0x0); /*0x1c28*/ + gem_writel(bp, TX_CLK_SEL3, 0x0); /*0x1c2c*/ + gem_writel(bp, RX_CLK_SEL0, 0x0); /*0x1c30*/ + gem_writel(bp, RX_CLK_SEL1, 0x1); /*0x1c34*/ + gem_writel(bp, CLK_250M_DIV10_DIV100_SEL, 0x0); /*0x1c38*/ + gem_writel(bp, RX_CLK_SEL5, 0x1); /*0x1c48*/ + gem_writel(bp, RGMII_TX_CLK_SEL0, 0x1); /*0x1c80*/ + gem_writel(bp, RGMII_TX_CLK_SEL1, 0x0); /*0x1c84*/ + } else if (speed == SPEED_100) { + gem_writel(bp, MII_SELECT, 0x1); /*0x1c18*/ + gem_writel(bp, SEL_MII_ON_RGMII, 0x0); /*0x1c1c*/ + gem_writel(bp, TX_CLK_SEL0, 0x0); /*0x1c20*/ + gem_writel(bp, TX_CLK_SEL1, 0x1); /*0x1c24*/ + gem_writel(bp, TX_CLK_SEL2, 0x0); /*0x1c28*/ + gem_writel(bp, TX_CLK_SEL3, 0x0); /*0x1c2c*/ + gem_writel(bp, RX_CLK_SEL0, 0x0); /*0x1c30*/ + gem_writel(bp, RX_CLK_SEL1, 0x1); /*0x1c34*/ + gem_writel(bp, CLK_250M_DIV10_DIV100_SEL, 0x0); /*0x1c38*/ + gem_writel(bp, RX_CLK_SEL5, 0x1); /*0x1c48*/ + gem_writel(bp, RGMII_TX_CLK_SEL0, 0x0); /*0x1c80*/ + gem_writel(bp, RGMII_TX_CLK_SEL1, 0x0); /*0x1c84*/ + } else { + gem_writel(bp, MII_SELECT, 0x1); /*0x1c18*/ + gem_writel(bp, SEL_MII_ON_RGMII, 0x0); /*0x1c1c*/ + gem_writel(bp, TX_CLK_SEL0, 0x0); /*0x1c20*/ + gem_writel(bp, TX_CLK_SEL1, 0x1); /*0x1c24*/ + gem_writel(bp, TX_CLK_SEL2, 0x0); /*0x1c28*/ + gem_writel(bp, TX_CLK_SEL3, 0x0); /*0x1c2c*/ + gem_writel(bp, RX_CLK_SEL0, 0x0); /*0x1c30*/ + gem_writel(bp, RX_CLK_SEL1, 0x1); /*0x1c34*/ + gem_writel(bp, CLK_250M_DIV10_DIV100_SEL, 0x1); /*0x1c38*/ + gem_writel(bp, RX_CLK_SEL5, 0x1); /*0x1c48*/ + gem_writel(bp, RGMII_TX_CLK_SEL0, 0x0); /*0x1c80*/ + gem_writel(bp, RGMII_TX_CLK_SEL1, 0x0); /*0x1c84*/ + } + } else if (bp->phy_interface == PHY_INTERFACE_MODE_RMII) { + gem_writel(bp, RX_CLK_SEL5, 0x1); /*0x1c48*/ + } + + if (speed == SPEED_100) + gem_writel(bp, HS_MAC_CONFIG, GEM_BFINS(HS_MAC_SPEED, HS_SPEED_100M, + gem_readl(bp, HS_MAC_CONFIG))); + else if (speed == SPEED_1000) + gem_writel(bp, HS_MAC_CONFIG, GEM_BFINS(HS_MAC_SPEED, HS_SPEED_1000M, + gem_readl(bp, HS_MAC_CONFIG))); + else if (speed == SPEED_2500) + gem_writel(bp, HS_MAC_CONFIG, GEM_BFINS(HS_MAC_SPEED, HS_SPEED_2500M, + gem_readl(bp, HS_MAC_CONFIG))); + else if (speed == SPEED_5000) + gem_writel(bp, HS_MAC_CONFIG, GEM_BFINS(HS_MAC_SPEED, HS_SPEED_5000M, + gem_readl(bp, HS_MAC_CONFIG))); + else if (speed == SPEED_10000) + gem_writel(bp, HS_MAC_CONFIG, GEM_BFINS(HS_MAC_SPEED, HS_SPEED_10000M, + gem_readl(bp, HS_MAC_CONFIG))); +} + +static void phytium_gem2p0_sel_clk(struct macb *bp, int speed) +{ + if (bp->phy_interface == PHY_INTERFACE_MODE_SGMII) { + if (speed == SPEED_100 || speed == SPEED_10) { + gem_writel(bp, SRC_SEL_LN, 0x1); /*0x1c04*/ + gem_writel(bp, DIV_SEL1_LN, 0x1); /*0x1c0c*/ + } + } + + if (speed == SPEED_100 || speed == SPEED_10) + gem_writel(bp, HS_MAC_CONFIG, GEM_BFINS(HS_MAC_SPEED, HS_SPEED_100M, + gem_readl(bp, HS_MAC_CONFIG))); + else if (speed == SPEED_1000) + gem_writel(bp, HS_MAC_CONFIG, GEM_BFINS(HS_MAC_SPEED, HS_SPEED_1000M, + gem_readl(bp, HS_MAC_CONFIG))); + else if (speed == SPEED_2500) + gem_writel(bp, HS_MAC_CONFIG, GEM_BFINS(HS_MAC_SPEED, HS_SPEED_2500M, + gem_readl(bp, HS_MAC_CONFIG))); +} + static void macb_mac_link_up(struct phylink_config *config, struct phy_device *phy, unsigned int mode, phy_interface_t interface, @@ -731,9 +935,13 @@ static void macb_mac_link_up(struct phylink_config *config, unsigned long flags; unsigned int q; u32 ctrl; + int err; spin_lock_irqsave(&bp->lock, flags); + if (bp->caps & MACB_CAPS_SEL_CLK) + bp->sel_clk_hw(bp, speed); + ctrl = macb_or_gem_readl(bp, NCFGR); ctrl &= ~(MACB_BIT(SPD) | MACB_BIT(FD)); @@ -749,13 +957,15 @@ static void macb_mac_link_up(struct phylink_config *config, if (macb_is_gem(bp)) { ctrl &= ~GEM_BIT(GBE); - if (speed == SPEED_1000) + if (speed == SPEED_1000 || speed == SPEED_2500) ctrl |= GEM_BIT(GBE); } if (rx_pause) ctrl |= MACB_BIT(PAE); + macb_set_tx_clk(bp, speed); + /* Initialize rings & buffers as clearing MACB_BIT(TE) in link down * cleared the pipeline and control registers. */ @@ -769,9 +979,28 @@ static void macb_mac_link_up(struct phylink_config *config, macb_or_gem_writel(bp, NCFGR, ctrl); - if (bp->phy_interface == PHY_INTERFACE_MODE_10GBASER) - gem_writel(bp, HS_MAC_CONFIG, GEM_BFINS(HS_MAC_SPEED, HS_SPEED_10000M, - gem_readl(bp, HS_MAC_CONFIG))); + if (speed == SPEED_2500) { + u32 network_ctrl; + + network_ctrl = macb_readl(bp, NCR); + network_ctrl |= MACB_BIT(2PT5G); + macb_writel(bp, NCR, network_ctrl); + } + + if (bp->phy_interface == PHY_INTERFACE_MODE_10GBASER || + bp->phy_interface == PHY_INTERFACE_MODE_USXGMII) { + if (speed == SPEED_5000) + gem_writel(bp, HS_MAC_CONFIG, + GEM_BFINS(HS_MAC_SPEED, HS_SPEED_5000M, + gem_readl(bp, HS_MAC_CONFIG))); + else + gem_writel(bp, HS_MAC_CONFIG, + GEM_BFINS(HS_MAC_SPEED, HS_SPEED_10000M, + gem_readl(bp, HS_MAC_CONFIG))); + } else if (bp->phy_interface == PHY_INTERFACE_MODE_5GBASER) + gem_writel(bp, HS_MAC_CONFIG, + GEM_BFINS(HS_MAC_SPEED, HS_SPEED_5000M, + gem_readl(bp, HS_MAC_CONFIG))); spin_unlock_irqrestore(&bp->lock, flags); @@ -785,6 +1014,15 @@ static void macb_mac_link_up(struct phylink_config *config, macb_writel(bp, NCR, ctrl | MACB_BIT(RE) | MACB_BIT(TE)); + if (bp->use_ncsi) { + /* Start the NCSI device */ + err = ncsi_start_dev(bp->ndev); + if (err) { + netdev_err(bp->dev, "Ncsi start dev failed (error %d)\n", err); + return; + } + } + netif_tx_wake_all_queues(ndev); } @@ -794,12 +1032,16 @@ static struct phylink_pcs *macb_mac_select_pcs(struct phylink_config *config, struct net_device *ndev = to_net_dev(config->dev); struct macb *bp = netdev_priv(ndev); - if (interface == PHY_INTERFACE_MODE_10GBASER) + if (interface == PHY_INTERFACE_MODE_10GBASER || + interface == PHY_INTERFACE_MODE_5GBASER || + interface == PHY_INTERFACE_MODE_USXGMII) { return &bp->phylink_usx_pcs; - else if (interface == PHY_INTERFACE_MODE_SGMII) + } else if (interface == PHY_INTERFACE_MODE_SGMII || + interface == PHY_INTERFACE_MODE_2500BASEX) { return &bp->phylink_sgmii_pcs; - else + } else { return NULL; + } } static const struct phylink_mac_ops macb_phylink_ops = { @@ -856,6 +1098,20 @@ static void macb_get_pcs_fixed_state(struct phylink_config *config, state->link = (macb_readl(bp, NSR) & MACB_BIT(NSR_LINK)) != 0; } +static void macb_get_usx_pcs_fixed_state(struct phylink_config *config, + struct phylink_link_state *state) +{ + u32 val; + struct net_device *ndev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(ndev); + + val = gem_readl(bp, USX_STATUS); + state->link = !!(val & GEM_BIT(USX_BLOCK_LOCK)); + val = gem_readl(bp, NCFGR); + if (val & GEM_BIT(PAE)) + state->pause = MLO_PAUSE_RX; +} + /* based on au1000_eth. c*/ static int macb_mii_probe(struct net_device *dev) { @@ -873,6 +1129,10 @@ static int macb_mii_probe(struct net_device *dev) if (bp->phy_interface == PHY_INTERFACE_MODE_SGMII) { bp->phylink_config.poll_fixed_state = true; bp->phylink_config.get_fixed_state = macb_get_pcs_fixed_state; + } else if (bp->phy_interface == PHY_INTERFACE_MODE_2500BASEX || + bp->phy_interface == PHY_INTERFACE_MODE_USXGMII) { + bp->phylink_config.poll_fixed_state = true; + bp->phylink_config.get_fixed_state = macb_get_usx_pcs_fixed_state; } bp->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | @@ -900,10 +1160,18 @@ static int macb_mii_probe(struct net_device *dev) if (bp->caps & MACB_CAPS_HIGH_SPEED) { __set_bit(PHY_INTERFACE_MODE_10GBASER, bp->phylink_config.supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_USXGMII, + bp->phylink_config.supported_interfaces); bp->phylink_config.mac_capabilities |= MAC_10000FD; } } + if (bp->phy_interface == PHY_INTERFACE_MODE_SGMII || + bp->phy_interface == PHY_INTERFACE_MODE_2500BASEX) { + bp->phylink_config.poll_fixed_state = true; + bp->phylink_config.get_fixed_state = macb_get_pcs_fixed_state; + } + bp->phylink = phylink_create(&bp->phylink_config, bp->pdev->dev.fwnode, bp->phy_interface, &macb_phylink_ops); if (IS_ERR(bp->phylink)) { @@ -1316,6 +1584,7 @@ static void gem_rx_refill(struct macb_queue *queue) /* Make hw descriptor updates visible to CPU */ rmb(); + queue->rx_prepared_head++; desc = macb_rx_desc(queue, entry); if (!queue->rx_skbuff[entry]) { @@ -1354,7 +1623,6 @@ static void gem_rx_refill(struct macb_queue *queue) dma_wmb(); desc->addr &= ~MACB_BIT(RX_USED); } - queue->rx_prepared_head++; } /* Make descriptor updates visible to hardware */ @@ -2473,27 +2741,48 @@ static void macb_free_rx_buffers(struct macb *bp) static void macb_free_consistent(struct macb *bp) { + struct macb_dma_desc *tx_ring_base = NULL; + struct macb_dma_desc *rx_ring_base = NULL; + dma_addr_t tx_ring_base_addr; + dma_addr_t rx_ring_base_addr; struct macb_queue *queue; unsigned int q; int size; bp->macbgem_ops.mog_free_rx_buffers(bp); + queue = bp->queues; + if (queue->tx_ring) { + tx_ring_base = queue->tx_ring; + tx_ring_base_addr = queue->tx_ring_dma; + } + if (queue->rx_ring) { + rx_ring_base = queue->rx_ring; + rx_ring_base_addr = queue->rx_ring_dma; + } + for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { kfree(queue->tx_skb); queue->tx_skb = NULL; - if (queue->tx_ring) { - size = TX_RING_BYTES(bp) + bp->tx_bd_rd_prefetch; - dma_free_coherent(&bp->pdev->dev, size, - queue->tx_ring, queue->tx_ring_dma); + if (queue->tx_ring) queue->tx_ring = NULL; - } - if (queue->rx_ring) { - size = RX_RING_BYTES(bp) + bp->rx_bd_rd_prefetch; - dma_free_coherent(&bp->pdev->dev, size, - queue->rx_ring, queue->rx_ring_dma); + if (queue->rx_ring) queue->rx_ring = NULL; - } + } + + if (tx_ring_base) { + size = bp->num_queues * (TX_RING_BYTES(bp) + + bp->tx_bd_rd_prefetch + + RING_ADDR_INTERVAL); + dma_free_coherent(&bp->pdev->dev, size, tx_ring_base, + tx_ring_base_addr); + } + if (rx_ring_base) { + size = bp->num_queues * (RX_RING_BYTES(bp) + + bp->rx_bd_rd_prefetch + + RING_ADDR_INTERVAL); + dma_free_coherent(&bp->pdev->dev, size, rx_ring_base, + rx_ring_base_addr); } } @@ -2533,17 +2822,87 @@ static int macb_alloc_rx_buffers(struct macb *bp) return 0; } +static int macb_queue_phyaddr_check(struct macb *bp, dma_addr_t ring_base_addr, + int offset) +{ + u32 bus_addr_high; + int i; + + bus_addr_high = upper_32_bits(ring_base_addr); + for (i = 1; i < bp->num_queues; i++) { + ring_base_addr += offset; + if (bus_addr_high != upper_32_bits(ring_base_addr)) + return -1; + } + + return 0; +} + static int macb_alloc_consistent(struct macb *bp) { + struct macb_dma_desc *tx_ring_base, *rx_ring_base; + dma_addr_t tx_ring_base_addr, rx_ring_base_addr; struct macb_queue *queue; + int tx_offset, rx_offset; + int tx_size, rx_size; unsigned int q; + int ret, i; int size; + tx_offset = TX_RING_BYTES(bp) + bp->tx_bd_rd_prefetch + + RING_ADDR_INTERVAL; + tx_size = bp->num_queues * tx_offset; + for (i = 0; i < MAX_RING_ADDR_ALLOC_TIMES + 1; i++) { + if (i == MAX_RING_ADDR_ALLOC_TIMES) + return -ENOMEM; + + tx_ring_base = dma_alloc_coherent(&bp->pdev->dev, tx_size, + &tx_ring_base_addr, + GFP_KERNEL); + if (!tx_ring_base) + continue; + + ret = macb_queue_phyaddr_check(bp, tx_ring_base_addr, + tx_offset); + if (ret) { + dma_free_coherent(&bp->pdev->dev, tx_size, tx_ring_base, + tx_ring_base_addr); + continue; + } else { + break; + } + } + + rx_offset = RX_RING_BYTES(bp) + bp->rx_bd_rd_prefetch + + RING_ADDR_INTERVAL; + rx_size = bp->num_queues * rx_offset; + for (i = 0; i < MAX_RING_ADDR_ALLOC_TIMES + 1; i++) { + if (i == MAX_RING_ADDR_ALLOC_TIMES) { + dma_free_coherent(&bp->pdev->dev, tx_size, tx_ring_base, + tx_ring_base_addr); + return -ENOMEM; + } + + rx_ring_base = dma_alloc_coherent(&bp->pdev->dev, rx_size, + &rx_ring_base_addr, + GFP_KERNEL); + if (!rx_ring_base) + continue; + + ret = macb_queue_phyaddr_check(bp, rx_ring_base_addr, + rx_offset); + if (ret) { + dma_free_coherent(&bp->pdev->dev, rx_size, rx_ring_base, + rx_ring_base_addr); + continue; + } else { + break; + } + } + for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { - size = TX_RING_BYTES(bp) + bp->tx_bd_rd_prefetch; - queue->tx_ring = dma_alloc_coherent(&bp->pdev->dev, size, - &queue->tx_ring_dma, - GFP_KERNEL); + queue->tx_ring = (void *)tx_ring_base + q * tx_offset; + queue->tx_ring_dma = tx_ring_base_addr + q * tx_offset; if (!queue->tx_ring) goto out_err; netdev_dbg(bp->dev, @@ -2552,13 +2911,12 @@ static int macb_alloc_consistent(struct macb *bp) queue->tx_ring); size = bp->tx_ring_size * sizeof(struct macb_tx_skb); - queue->tx_skb = kmalloc(size, GFP_KERNEL); + queue->tx_skb = kzalloc(size, GFP_KERNEL); if (!queue->tx_skb) goto out_err; - size = RX_RING_BYTES(bp) + bp->rx_bd_rd_prefetch; - queue->rx_ring = dma_alloc_coherent(&bp->pdev->dev, size, - &queue->rx_ring_dma, GFP_KERNEL); + queue->rx_ring = (void *)rx_ring_base + q * rx_offset; + queue->rx_ring_dma = rx_ring_base_addr + q * rx_offset; if (!queue->rx_ring) goto out_err; netdev_dbg(bp->dev, @@ -2592,6 +2950,15 @@ static void gem_init_rings(struct macb *bp) queue->tx_head = 0; queue->tx_tail = 0; + for (i = 0; i < bp->rx_ring_size; i++) { + desc = macb_rx_desc(queue, i); + desc->ctrl = 0; + /* make sure ctrl is cleared first, + * and bit RX_USED is set to avoid a race. + */ + dma_wmb(); + desc->addr |= MACB_BIT(RX_USED); + } queue->rx_tail = 0; queue->rx_prepared_head = 0; @@ -2768,6 +3135,46 @@ static void macb_configure_dma(struct macb *bp) } } +static int phytium_mac_config(struct macb *bp) +{ + u32 old_ctrl, ctrl; + u32 old_ncr, ncr; + + netdev_dbg(bp->dev, "phytium mac config"); + + ncr = macb_readl(bp, NCR); + old_ncr = ncr; + ctrl = macb_or_gem_readl(bp, NCFGR); + old_ctrl = ctrl; + + ncr &= ~(GEM_BIT(ENABLE_HS_MAC) | MACB_BIT(2PT5G)); + ctrl &= ~(GEM_BIT(SGMIIEN) | GEM_BIT(PCSSEL) | + MACB_BIT(SPD) | MACB_BIT(FD)); + if (macb_is_gem(bp)) + ctrl &= ~GEM_BIT(GBE); + + if (bp->phy_interface == PHY_INTERFACE_MODE_2500BASEX) { + ctrl |= GEM_BIT(PCSSEL) | GEM_BIT(SGMIIEN); + ncr |= MACB_BIT(2PT5G); + } else if (bp->phy_interface == PHY_INTERFACE_MODE_USXGMII || + bp->phy_interface == PHY_INTERFACE_MODE_5GBASER) { + ctrl |= GEM_BIT(PCSSEL); + ncr |= GEM_BIT(ENABLE_HS_MAC); + } + + if (bp->duplex) + ctrl |= MACB_BIT(FD); + + /* Apply the new configuration, if any */ + if (old_ctrl ^ ctrl) + macb_or_gem_writel(bp, NCFGR, ctrl); + + if (old_ncr ^ ncr) + macb_or_gem_writel(bp, NCR, ncr); + + return 0; +} + static void macb_init_hw(struct macb *bp) { u32 config; @@ -2796,11 +3203,27 @@ static void macb_init_hw(struct macb *bp) if (bp->caps & MACB_CAPS_JUMBO) bp->rx_frm_len_mask = MACB_RX_JFRMLEN_MASK; - macb_configure_dma(bp); + gem_writel(bp, AXI_PIPE, 0x1010); + + if (bp->phy_interface == PHY_INTERFACE_MODE_USXGMII || + bp->phy_interface == PHY_INTERFACE_MODE_5GBASER || + bp->phy_interface == PHY_INTERFACE_MODE_2500BASEX) { + /* phytium need hwclock */ + if (bp->caps & MACB_CAPS_SEL_CLK) + bp->sel_clk_hw(bp, bp->speed); + phytium_mac_config(bp); + if (bp->link) + macb_usx_pcs_link_up(&bp->phylink_usx_pcs, 0, + bp->phy_interface, bp->speed, bp->duplex); + } else { + bp->speed = SPEED_10; + bp->duplex = DUPLEX_HALF; + } /* Enable RX partial store and forward and set watermark */ if (bp->rx_watermark) gem_writel(bp, PBUFRXCUT, (bp->rx_watermark | GEM_BIT(ENCUTTHRU))); + macb_configure_dma(bp); } /* The hash address register is 64 bits long and takes up two @@ -3884,6 +4307,8 @@ static const struct net_device_ops macb_netdev_ops = { #endif .ndo_set_features = macb_set_features, .ndo_features_check = macb_features_check, + .ndo_vlan_rx_add_vid = ncsi_vlan_rx_add_vid, + .ndo_vlan_rx_kill_vid = ncsi_vlan_rx_kill_vid, }; /* Configure peripheral capabilities according to device tree @@ -4048,6 +4473,97 @@ static int macb_clk_init(struct platform_device *pdev, struct clk **pclk, return err; } +static int phytium_clk_init(struct platform_device *pdev, struct clk **pclk, + struct clk **hclk, struct clk **tx_clk, + struct clk **rx_clk, struct clk **tsu_clk) +{ + struct macb_platform_data *pdata; + struct device_node *np = pdev->dev.of_node; + int err; + + pdata = dev_get_platdata(&pdev->dev); + if (pdata) { + *pclk = pdata->pclk; + *hclk = pdata->hclk; + } else { + if (has_acpi_companion(&pdev->dev)) { + *pclk = NULL; + *hclk = NULL; + } else if (np) { + *pclk = devm_clk_get(&pdev->dev, "pclk"); + *hclk = devm_clk_get(&pdev->dev, "hclk"); + } + } + + if (IS_ERR(*pclk)) + return dev_err_probe(&pdev->dev, + IS_ERR(*pclk) ? PTR_ERR(*pclk) : -ENODEV, + "failed to get pclk\n"); + + if (IS_ERR(*hclk)) + return dev_err_probe(&pdev->dev, + IS_ERR(*hclk) ? PTR_ERR(*hclk) : -ENODEV, + "failed to get hclk\n"); + + *tx_clk = devm_clk_get_optional(&pdev->dev, "tx_clk"); + if (IS_ERR(*tx_clk)) + return PTR_ERR(*tx_clk); + + *rx_clk = devm_clk_get_optional(&pdev->dev, "rx_clk"); + if (IS_ERR(*rx_clk)) + return PTR_ERR(*rx_clk); + + *tsu_clk = devm_clk_get_optional(&pdev->dev, "tsu_clk"); + if (IS_ERR(*tsu_clk)) + return PTR_ERR(*tsu_clk); + + err = clk_prepare_enable(*pclk); + if (err) { + dev_err(&pdev->dev, "failed to enable pclk (%d)\n", err); + return err; + } + + err = clk_prepare_enable(*hclk); + if (err) { + dev_err(&pdev->dev, "failed to enable hclk (%d)\n", err); + goto err_disable_pclk; + } + + err = clk_prepare_enable(*tx_clk); + if (err) { + dev_err(&pdev->dev, "failed to enable tx_clk (%d)\n", err); + goto err_disable_hclk; + } + + err = clk_prepare_enable(*rx_clk); + if (err) { + dev_err(&pdev->dev, "failed to enable rx_clk (%d)\n", err); + goto err_disable_txclk; + } + + err = clk_prepare_enable(*tsu_clk); + if (err) { + dev_err(&pdev->dev, "failed to enable tsu_clk (%d)\n", err); + goto err_disable_rxclk; + } + + return 0; + +err_disable_rxclk: + clk_disable_unprepare(*rx_clk); + +err_disable_txclk: + clk_disable_unprepare(*tx_clk); + +err_disable_hclk: + clk_disable_unprepare(*hclk); + +err_disable_pclk: + clk_disable_unprepare(*pclk); + + return err; +} + static int macb_init(struct platform_device *pdev) { struct net_device *dev = platform_get_drvdata(pdev); @@ -4126,12 +4642,14 @@ static int macb_init(struct platform_device *pdev) /* setup appropriated routines according to adapter type */ if (macb_is_gem(bp)) { + bp->max_tx_length = GEM_MAX_TX_LEN; bp->macbgem_ops.mog_alloc_rx_buffers = gem_alloc_rx_buffers; bp->macbgem_ops.mog_free_rx_buffers = gem_free_rx_buffers; bp->macbgem_ops.mog_init_rings = gem_init_rings; bp->macbgem_ops.mog_rx = gem_rx; dev->ethtool_ops = &gem_ethtool_ops; } else { + bp->max_tx_length = MACB_MAX_TX_LEN; bp->macbgem_ops.mog_alloc_rx_buffers = macb_alloc_rx_buffers; bp->macbgem_ops.mog_free_rx_buffers = macb_free_rx_buffers; bp->macbgem_ops.mog_init_rings = macb_init_rings; @@ -4901,6 +5419,34 @@ static const struct macb_config versal_config = { .usrio = &macb_default_usrio, }; +static const struct macb_config phytium_gem1p0_config = { + .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | + MACB_CAPS_JUMBO | + MACB_CAPS_GEM_HAS_PTP | + MACB_CAPS_BD_RD_PREFETCH | + MACB_CAPS_SEL_CLK, + .dma_burst_length = 16, + .clk_init = phytium_clk_init, + .init = macb_init, + .jumbo_max_len = 10240, + .sel_clk_hw = phytium_gem1p0_sel_clk, + .usrio = &macb_default_usrio, +}; + +static const struct macb_config phytium_gem2p0_config = { + .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | + MACB_CAPS_JUMBO | + MACB_CAPS_GEM_HAS_PTP | + MACB_CAPS_BD_RD_PREFETCH | + MACB_CAPS_SEL_CLK, + .dma_burst_length = 16, + .clk_init = phytium_clk_init, + .init = macb_init, + .jumbo_max_len = 10240, + .sel_clk_hw = phytium_gem2p0_sel_clk, + .usrio = &macb_default_usrio, +}; + static const struct of_device_id macb_dt_ids[] = { { .compatible = "cdns,at91sam9260-macb", .data = &at91sam9260_config }, { .compatible = "cdns,macb" }, @@ -4924,11 +5470,24 @@ static const struct of_device_id macb_dt_ids[] = { { .compatible = "xlnx,zynqmp-gem", .data = &zynqmp_config}, { .compatible = "xlnx,zynq-gem", .data = &zynq_config }, { .compatible = "xlnx,versal-gem", .data = &versal_config}, + { .compatible = "cdns,phytium-gem-1.0", .data = &phytium_gem1p0_config }, + { .compatible = "cdns,phytium-gem-2.0", .data = &phytium_gem2p0_config }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, macb_dt_ids); #endif /* CONFIG_OF */ +#ifdef CONFIG_ACPI +static const struct acpi_device_id macb_acpi_ids[] = { + { .id = "PHYT0036", .driver_data = (kernel_ulong_t)&phytium_gem1p0_config }, + { } +}; + +MODULE_DEVICE_TABLE(acpi, macb_acpi_ids); +#else +#define macb_acpi_ids NULL +#endif + static const struct macb_config default_gem_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO | @@ -4940,6 +5499,32 @@ static const struct macb_config default_gem_config = { .jumbo_max_len = 10240, }; +static void gem_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"); +} + +static int macb_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 macb_probe(struct platform_device *pdev) { const struct macb_config *macb_config = &default_gem_config; @@ -4952,7 +5537,6 @@ static int macb_probe(struct platform_device *pdev) struct clk *tsu_clk = NULL; unsigned int queue_mask, num_queues; bool native_io; - phy_interface_t interface; struct net_device *dev; struct resource *regs; u32 wtrmrk_rst_val; @@ -4973,6 +5557,15 @@ static int macb_probe(struct platform_device *pdev) clk_init = macb_config->clk_init; init = macb_config->init; } + } else if (has_acpi_companion(&pdev->dev)) { + const struct acpi_device_id *match; + + match = acpi_match_device(macb_acpi_ids, &pdev->dev); + if (match && match->driver_data) { + macb_config = (void *)match->driver_data; + clk_init = macb_config->clk_init; + init = macb_config->init; + } } err = clk_init(pdev, &pclk, &hclk, &tx_clk, &rx_clk, &tsu_clk); @@ -5021,6 +5614,9 @@ static int macb_probe(struct platform_device *pdev) if (macb_config) bp->jumbo_max_len = macb_config->jumbo_max_len; + if (macb_config) + bp->sel_clk_hw = macb_config->sel_clk_hw; + if (!hw_is_gem(bp->regs, bp->native_io)) bp->max_tx_length = MACB_MAX_TX_LEN; else if (macb_config->max_tx_length) @@ -5029,7 +5625,7 @@ static int macb_probe(struct platform_device *pdev) bp->max_tx_length = GEM_MAX_TX_LEN; bp->wol = 0; - if (of_property_read_bool(np, "magic-packet")) + if (device_property_read_bool(&pdev->dev, "magic-packet")) bp->wol |= MACB_WOL_HAS_MAGIC_PACKET; device_set_wakeup_capable(&pdev->dev, bp->wol & MACB_WOL_HAS_MAGIC_PACKET); @@ -5102,12 +5698,12 @@ static int macb_probe(struct platform_device *pdev) else if (err) macb_get_hwaddr(bp); - err = of_get_phy_mode(np, &interface); - if (err) - /* not found in DT, MII by default */ + err = macb_get_phy_mode(pdev); + if (err < 0) bp->phy_interface = PHY_INTERFACE_MODE_MII; else - bp->phy_interface = interface; + bp->phy_interface = err; + /* IP specific init */ err = init(pdev); @@ -5118,6 +5714,20 @@ static int macb_probe(struct platform_device *pdev) if (err) goto err_out_phy_exit; + if (device_property_read_bool(&pdev->dev, "use-ncsi")) { + if (!IS_ENABLED(CONFIG_NET_NCSI)) { + dev_err(&pdev->dev, "NCSI stack not enabled\n"); + goto err_out_free_netdev; + } + dev_notice(&pdev->dev, "Using NCSI interface\n"); + bp->use_ncsi = 1; + bp->ndev = ncsi_register_dev(dev, gem_ncsi_handler); + if (!bp->ndev) + goto err_out_free_netdev; + } else { + bp->use_ncsi = 0; + } + netif_carrier_off(dev); err = register_netdev(dev); @@ -5349,6 +5959,8 @@ static int __maybe_unused macb_resume(struct device *dev) macb_set_rx_mode(netdev); macb_restore_features(bp); rtnl_lock(); + if (!device_may_wakeup(&bp->dev->dev)) + phy_init(bp->sgmii_phy); phylink_start(bp->phylink); rtnl_unlock(); @@ -5402,6 +6014,7 @@ static struct platform_driver macb_driver = { .driver = { .name = "macb", .of_match_table = of_match_ptr(macb_dt_ids), + .acpi_match_table = macb_acpi_ids, .pm = &macb_pm_ops, }, }; diff --git a/drivers/net/ethernet/phytium/Kconfig b/drivers/net/ethernet/phytium/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..14a77adbfdc12fac3a7ca84fb7b10afb154f9dc9 --- /dev/null +++ b/drivers/net/ethernet/phytium/Kconfig @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Phytium device configuration +# + +config NET_VENDOR_PHYTIUM + bool "Phytium devices" + 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 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 0000000000000000000000000000000000000000..6e710d4d54b6641bc1679405d6909827cfd09eeb --- /dev/null +++ b/drivers/net/ethernet/phytium/Makefile @@ -0,0 +1,17 @@ + +# SPDX-License-Identifier: GPL-2.0 +# +# 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 0000000000000000000000000000000000000000..b90e1551499ef77b1a228d3adf29327ad3be7ae5 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac.h @@ -0,0 +1,591 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _PHYTMAC_H +#define _PHYTMAC_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PHYTMAC_DRV_NAME "phytium-mac" +#define PHYTMAC_DRV_DESC "PHYTIUM Ethernet Driver" + +#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 DEFAULT_RX_DESC_MIN_FREE 64 + +#define MEMORY_SIZE 4096 +#define MHU_SIZE 0x20 + +#define PHYTMAC_POWEROFF 1 +#define PHYTMAC_POWERON 2 + +#define PHYTMAC_WOL_MAGIC_PACKET 1 + +#define DEFAULT_MSG_RING_SIZE 16 + +#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_LPI 0x0000400 +#define PHYTMAC_CAPS_MSG 0x0000800 + +#define PHYTMAC_TX 0x1 +#define PHYTMAC_RX 0x2 + +#define PHYTMAC_GREGS_LEN 16 + +#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 PHYTMAC_WRITE(_pdata, _reg, _val) \ + __raw_writel((_val), (_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 + +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; + u32 tsu_rate; + 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 + +struct phytmac_tx_skb { + struct sk_buff *skb; + 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_queue { + struct phytmac *pdata; + int irq; + int index; + + /* 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; + struct phytmac_dma_desc *rx_ring; + struct sk_buff **rx_skb; + struct napi_struct rx_napi; + struct phytmac_queue_stats stats; + +#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_tail; + u32 rx_msg_head; + u32 rx_msg_tail; + /* 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 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 lpi; + u32 power_state; + struct work_struct restart_task; + /* Lock to protect mac config */ + spinlock_t lock; + /* Lock to protect msg tx */ + spinlock_t msg_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; + 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; +}; + +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_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); + unsigned int (*rx_clean)(struct phytmac_queue *queue, u32 cleaned_count); + void (*transmit)(struct phytmac_queue *queue); + void (*restart)(struct phytmac *pdata); + int (*tx_complete)(const struct phytmac_dma_desc *desc); + int (*rx_complete)(const struct phytmac_dma_desc *desc); + int (*get_rx_pkt_len)(struct phytmac *pdata, const struct phytmac_dma_desc *desc); + dma_addr_t (*get_desc_addr)(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); + 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 */ + +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); +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); +#endif diff --git a/drivers/net/ethernet/phytium/phytmac_ethtool.c b/drivers/net/ethernet/phytium/phytmac_ethtool.c new file mode 100644 index 0000000000000000000000000000000000000000..592d2d9dc6d4b27b87594738e5ce2ab09ce20ded --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_ethtool.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#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_GREGS_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_GREGS_LEN * sizeof(u32)); + + 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); + + phylink_ethtool_get_wol(pdata->phylink, wol); + + if (pdata->wol & PHYTMAC_WAKE_MAGIC) { + wol->wolopts |= WAKE_MAGIC; + wol->supported |= WAKE_MAGIC; + } + if (pdata->wol & PHYTMAC_WAKE_ARP) { + wol->wolopts |= WAKE_ARP; + wol->supported |= WAKE_ARP; + } + if (pdata->wol & PHYTMAC_WAKE_UCAST) { + wol->wolopts |= WAKE_UCAST; + wol->supported |= WAKE_UCAST; + } + if (pdata->wol & PHYTMAC_WAKE_MCAST) { + wol->wolopts |= WAKE_MCAST; + wol->supported |= 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); + + 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 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) { + 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; + kset->base.port = PORT_FIBRE; + kset->base.transceiver = XCVR_INTERNAL; + kset->base.duplex = DUPLEX_FULL; + kset->base.speed = SPEED_10000; + } else if (pdata->phy_interface == PHY_INTERFACE_MODE_2500BASEX) { + supported = SUPPORTED_2500baseX_Full | SUPPORTED_Pause; + advertising = ADVERTISED_2500baseX_Full | ADVERTISED_Pause; + kset->base.port = PORT_FIBRE; + kset->base.transceiver = XCVR_INTERNAL; + kset->base.duplex = DUPLEX_FULL; + kset->base.speed = SPEED_2500; + } else if (pdata->phy_interface == PHY_INTERFACE_MODE_1000BASEX) { + supported = SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full + | SUPPORTED_10baseT_Full | SUPPORTED_FIBRE + | SUPPORTED_Pause; + advertising = ADVERTISED_1000baseT_Full | ADVERTISED_100baseT_Full + | ADVERTISED_10baseT_Full | ADVERTISED_FIBRE + | ADVERTISED_Pause; + kset->base.port = PORT_FIBRE; + kset->base.transceiver = XCVR_INTERNAL; + kset->base.duplex = DUPLEX_FULL; + kset->base.speed = SPEED_100; + } else if (pdata->phy_interface == PHY_INTERFACE_MODE_SGMII) { + supported = SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full + | SUPPORTED_10baseT_Full | SUPPORTED_FIBRE | SUPPORTED_Pause; + advertising = ADVERTISED_1000baseT_Full | ADVERTISED_100baseT_Full + | ADVERTISED_10baseT_Full | ADVERTISED_FIBRE | ADVERTISED_Pause; + kset->base.port = PORT_FIBRE; + kset->base.transceiver = XCVR_INTERNAL; + kset->base.duplex = DUPLEX_FULL; + kset->base.speed = SPEED_1000; + } + + 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, "fixed link interface not supported set link\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 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, +}; + +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 0000000000000000000000000000000000000000..c172103a97362803d1d7db79e43718fbd3c3e90f --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_main.c @@ -0,0 +1,2244 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Phytium Ethernet Controller driver + * + * 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 "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 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 Cadence 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_change_mtu(struct net_device *ndev, int new_mtu) +{ + if (netif_running(ndev)) + return -EBUSY; + + 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); + + 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); + return 0; + } + dev_info(pdata->dev, "invalid hw address, using random\n"); + eth_hw_addr_random(pdata->ndev); + + 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; +} + +static int phytmac_calc_rx_buf_len(struct phytmac *pdata, u32 mtu) +{ + unsigned int size = mtu + ETH_HLEN + ETH_FCS_LEN; + int rx_buf_len = roundup(size, RX_BUFFER_MULTIPLE); + + netdev_dbg(pdata->ndev, "mtu [%u] rx_buffer_size [%u]\n", + mtu, rx_buf_len); + + return rx_buf_len; +} + +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 sk_buff *phytmac_get_rx_skb(struct phytmac_queue *queue, + unsigned int index) +{ + return queue->rx_skb[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 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 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) { + size = pdata->queues_num * (TX_RING_BYTES(pdata) + pdata->tx_bd_prefetch + + RING_ADDR_INTERVAL); + 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 sk_buff *skb; + struct phytmac_dma_desc *desc; + struct phytmac_hw_if *hw_if = pdata->hw_if; + struct phytmac_dma_desc *rx_ring_base = NULL; + dma_addr_t rx_ring_base_addr; + dma_addr_t addr; + unsigned int q; + int size, i; + + 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) { + if (queue->rx_skb) { + for (i = 0; i < pdata->rx_ring_size; i++) { + skb = phytmac_get_rx_skb(queue, i); + if (skb) { + desc = &queue->rx_ring[i]; + addr = hw_if->get_desc_addr(desc); + dma_unmap_single(pdata->dev, addr, pdata->rx_buffer_len, + DMA_FROM_DEVICE); + dev_kfree_skb_any(skb); + skb = NULL; + } + } + + kfree(queue->rx_skb); + queue->rx_skb = NULL; + } + + if (queue->rx_ring) + queue->rx_ring = NULL; + } + + if (rx_ring_base) { + size = pdata->queues_num * (RX_RING_BYTES(pdata) + pdata->rx_bd_prefetch + + RING_ADDR_INTERVAL); + 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 size; + + size = pdata->queues_num * (TX_RING_BYTES(pdata) + pdata->tx_bd_prefetch + + RING_ADDR_INTERVAL); + tx_ring_base = dma_alloc_coherent(pdata->dev, size, + &tx_ring_base_addr, GFP_KERNEL); + if (!tx_ring_base) + goto err; + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + size = TX_RING_BYTES(pdata) + pdata->tx_bd_prefetch + RING_ADDR_INTERVAL; + queue->tx_ring = (void *)tx_ring_base + q * size; + queue->tx_ring_addr = tx_ring_base_addr + q * size; + 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, size, (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); + } + tx_ring_base = NULL; + + 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_hw_if *hw_if = pdata->hw_if; + struct phytmac_dma_desc *rx_ring_base; + dma_addr_t rx_ring_base_addr; + unsigned int q; + int size; + int i; + + size = pdata->queues_num * (RX_RING_BYTES(pdata) + pdata->rx_bd_prefetch + + RING_ADDR_INTERVAL); + rx_ring_base = dma_alloc_coherent(pdata->dev, size, + &rx_ring_base_addr, GFP_KERNEL); + if (!rx_ring_base) + goto err; + + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + size = RX_RING_BYTES(pdata) + pdata->rx_bd_prefetch + RING_ADDR_INTERVAL; + queue->rx_ring = (void *)rx_ring_base + q * size; + queue->rx_ring_addr = rx_ring_base_addr + q * size; + 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, size, (unsigned long)queue->rx_ring_addr); + + for (i = 0; i < pdata->rx_ring_size; i++) + hw_if->init_rx_map(queue, i); + + size = pdata->rx_ring_size * sizeof(struct sk_buff *); + queue->rx_skb = kzalloc(size, GFP_KERNEL); + if (!queue->rx_skb) + goto err; + + if (netif_msg_drv(pdata)) + netdev_info(pdata->ndev, + "Allocated %d RX struct sk_buff entries at %p\n", + pdata->rx_ring_size, queue->rx_skb); + } + rx_ring_base = NULL; + + return 0; +err: + phytmac_free_rx_resource(pdata); + + return -ENOMEM; +} + +static int phytmac_alloc_resource(struct phytmac *pdata) +{ + struct net_device *ndev = pdata->ndev; + int ret; + + pdata->rx_buffer_len = phytmac_calc_rx_buf_len(pdata, ndev->mtu); + + 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 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 sk_buff *skb; + unsigned int len; + dma_addr_t addr; + + skb = phytmac_get_rx_skb(queue, queue->rx_tail); + if (unlikely(!skb)) { + netdev_err(pdata->ndev, + "inconsistent Rx descriptor chain\n"); + pdata->ndev->stats.rx_dropped++; + queue->stats.rx_dropped++; + return NULL; + } + + queue->rx_skb[queue->rx_tail & (pdata->rx_ring_size - 1)] = NULL; + len = hw_if->get_rx_pkt_len(pdata, desc); + addr = hw_if->get_desc_addr(desc); + + skb_put(skb, len); + dma_unmap_single(pdata->dev, addr, + pdata->rx_buffer_len, DMA_FROM_DEVICE); + 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, int len) +{ + unsigned int offset = 0; + unsigned int frag = 0; + unsigned int entry = 0; + dma_addr_t addr = 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; + + if (netif_msg_drv(pdata)) + netdev_info(pdata->ndev, "rx frame %u - %u (len %u)\n", + first_frag, last_frag, len); + + skb = netdev_alloc_skb(pdata->ndev, len); + if (!skb) { + pdata->ndev->stats.rx_dropped++; + netdev_err(pdata->ndev, "rx frame alloc skb failed\n"); + return NULL; + } + + 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_put(skb, len); + + for (frag = first_frag; ; frag++) { + if (offset + frag_len > len) { + if (unlikely(frag != last_frag)) { + dev_kfree_skb_any(skb); + return NULL; + } + frag_len = len - offset; + } + + desc = phytmac_get_rx_desc(queue, frag); + addr = hw_if->get_desc_addr(desc); + dma_sync_single_for_cpu(pdata->dev, addr, frag_len, + DMA_FROM_DEVICE); + + entry = frag & (pdata->rx_ring_size - 1); + skb_copy_to_linear_data_offset(skb, offset, queue->rx_skb[entry]->data, frag_len); + + offset += pdata->rx_buffer_len; + + dma_sync_single_for_device(pdata->dev, addr, frag_len, + DMA_FROM_DEVICE); + + if (frag == last_frag) + break; + } + + 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; + int len; + + for (rx_tail = queue->rx_tail; ; rx_tail++) { + desc = phytmac_get_rx_desc(queue, rx_tail); + 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; + dma_addr_t paddr; + struct sk_buff *skb; + unsigned int rx_unclean = 0; + + space = CIRC_SPACE(queue->rx_head, queue->rx_tail, + pdata->rx_ring_size); + + if (space < DEFAULT_RX_DESC_MIN_FREE) + return; + + index = queue->rx_head & (pdata->rx_ring_size - 1); + while (space > 0) { + if (!queue->rx_skb[index]) { + skb = netdev_alloc_skb(pdata->ndev, pdata->rx_buffer_len); + if (unlikely(!skb)) { + netdev_err(pdata->ndev, "rx clean alloc skb failed\n"); + break; + } + + paddr = dma_map_single(pdata->dev, skb->data, + pdata->rx_buffer_len, DMA_FROM_DEVICE); + if (dma_mapping_error(pdata->dev, paddr)) { + dev_kfree_skb(skb); + break; + } + + queue->rx_skb[index] = skb; + + hw_if->rx_map(queue, index, paddr); + } + + index = (index + 1) & (pdata->rx_ring_size - 1); + rx_unclean++; + space--; + } + + /* make newly descriptor to hardware */ + wmb(); + hw_if->rx_clean(queue, rx_unclean); + /* make newly descriptor to hardware */ + wmb(); + queue->rx_head += rx_unclean; + if (queue->rx_head >= pdata->rx_ring_size) + queue->rx_head &= (pdata->rx_ring_size - 1); +} + +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; + 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_single(queue, desc); + else + skb = phytmac_rx_mbuffer(queue); + + if (!skb) { + netdev_warn(pdata->ndev, "phytmac rx skb is NULL\n"); + break; + } + + pdata->ndev->stats.rx_packets++; + queue->stats.rx_packets++; + 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_REACHABLE(CONFIG_PHYTMAC_ENABLE_PTP)) + phytmac_ptp_rxstamp(pdata, skb, desc); + + napi_gro_receive(napi, skb); + } + + 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->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 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(&pdata->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); + skb = tx_skb->skb; + + if (skb) { + complete = 1; + 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); + } + } + + 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++; + } + + /* 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(&pdata->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) +{ + 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); + 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; + + if ((!(pdata->ndev->features & NETIF_F_HW_CSUM)) && + skb->ip_summed != CHECKSUM_PARTIAL && + !is_lso && + !phytmac_ptp_one_step(skb)) + packet->nocrc = 1; + else + packet->nocrc = 0; + + 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 = queue->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->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->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; + + for (queue = pdata->queues; q < pdata->queues_num; ++q) { + queue->tx_head = 0; + queue->tx_tail = 0; + hw_if->clear_tx_desc(queue); + + queue->rx_head = 0; + queue->rx_tail = 0; + phytmac_rx_clean(queue); + ++queue; + } + + hw_if->init_ring_hw(pdata); +} + +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; + unsigned long flags; + + 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_irqsave(&pdata->lock, flags); + /* 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_irqrestore(&pdata->lock, flags); + 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; +} + +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; +} + +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; + 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); + + /* Tx clean */ + for (q = 0, queue = pdata->queues; q < pdata->queues_num; ++q, ++queue) { + 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); + } + } + + spin_unlock_irqrestore(&pdata->lock, flags); + + 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; + } + + 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); +} + +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; + } + + 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 void phytmac_validate(struct phylink_config *config, + unsigned long *supported, + struct phylink_link_state *state) +{ + struct net_device *ndev = to_net_dev(config->dev); + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; + struct phytmac *pdata = netdev_priv(ndev); + + if (state->interface != PHY_INTERFACE_MODE_SGMII && + state->interface != PHY_INTERFACE_MODE_1000BASEX && + state->interface != PHY_INTERFACE_MODE_2500BASEX && + state->interface != PHY_INTERFACE_MODE_5GBASER && + state->interface != PHY_INTERFACE_MODE_10GBASER && + state->interface != PHY_INTERFACE_MODE_USXGMII && + !phy_interface_mode_is_rgmii(state->interface)) { + bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS); + return; + } + + phylink_set_port_modes(mask); + phylink_set(mask, Autoneg); + phylink_set(mask, Asym_Pause); + + if (state->interface == PHY_INTERFACE_MODE_10GBASER || + state->interface == PHY_INTERFACE_MODE_USXGMII) { + pdata->speed = state->speed; + pdata->duplex = state->duplex; + if (pdata->speed == SPEED_5000) { + phylink_set(mask, 5000baseT_Full); + } else { + phylink_set(mask, 10000baseCR_Full); + phylink_set(mask, 10000baseER_Full); + phylink_set(mask, 10000baseKR_Full); + phylink_set(mask, 10000baseLR_Full); + phylink_set(mask, 10000baseLRM_Full); + phylink_set(mask, 10000baseSR_Full); + phylink_set(mask, 10000baseT_Full); + } + } + + if (state->interface == PHY_INTERFACE_MODE_2500BASEX) + phylink_set(mask, 2500baseX_Full); + + if (state->interface == PHY_INTERFACE_MODE_5GBASER) + phylink_set(mask, 5000baseT_Full); + + if (state->interface == PHY_INTERFACE_MODE_1000BASEX || + state->interface == PHY_INTERFACE_MODE_SGMII || + phy_interface_mode_is_rgmii(state->interface)) { + phylink_set(mask, 1000baseT_Full); + phylink_set(mask, 1000baseX_Full); + phylink_set(mask, 1000baseT_Half); + phylink_set(mask, 10baseT_Half); + phylink_set(mask, 10baseT_Full); + phylink_set(mask, 100baseT_Half); + phylink_set(mask, 100baseT_Full); + } + + bitmap_and(supported, supported, mask, __ETHTOOL_LINK_MODE_MASK_NBITS); + bitmap_and(state->advertising, state->advertising, mask, + __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static const struct phylink_mac_ops phytmac_phylink_ops = { + .validate = phytmac_validate, + .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_10GBASER, 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; + 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 = 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"); + + /* phytmac_powerup */ + if (pdata->power_state == PHYTMAC_POWEROFF) + hw_if->poweron(pdata, PHYTMAC_POWERON); + + 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 = phytmac_get_mac_address(pdata); + if (ret) { + netdev_err(ndev, "phytmac get mac address failed\n"); + goto reset_hw; + } + + 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; + } + + phytmac_init_ring(pdata); + hw_if->init_hw(pdata); + + ret = phytmac_phylink_connect(pdata); + if (ret) { + netdev_err(ndev, "phylink connet failed,(error %d)\n", + ret); + goto reset_hw; + } + + phylink_start(pdata->phylink); + + 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); + + /* phytmac_powerup */ + if (pdata->power_state == PHYTMAC_POWERON) + hw_if->poweron(pdata, PHYTMAC_POWEROFF); + + return 0; +} + +static int phytmac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct phytmac *pdata = netdev_priv(dev); + int ret; + + 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; + + 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; + } + return features; +} + +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 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, +}; + +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); + eth_hw_addr_random(pdata->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; +} + +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; + + 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; +} + +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; + 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; + } + + netif_carrier_off(ndev); + ret = register_netdev(ndev); + if (ret) { + dev_err(pdata->dev, "Cannot register net device, aborting.\n"); + goto err_out; + } + + 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; + } + + 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); + + unregister_netdev(ndev); + +err_out: + return ret; +} +EXPORT_SYMBOL_GPL(phytmac_drv_probe); + +int phytmac_drv_remove(struct phytmac *pdata) +{ + struct net_device *ndev = pdata->ndev; + + if (ndev) { + if (pdata->use_ncsi && pdata->ncsidev) + ncsi_unregister_dev(pdata->ncsidev); + + unregister_netdev(ndev); + + 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) { + napi_disable(&queue->tx_napi); + napi_disable(&queue->rx_napi); + } + + if (pdata->wol) { + hw_if->set_wol(pdata, pdata->wol); + } else { + rtnl_lock(); + phylink_stop(pdata->phylink); + rtnl_unlock(); + spin_lock_irqsave(&pdata->lock, flags); + hw_if->reset_hw(pdata); + hw_if->poweron(pdata, PHYTMAC_POWEROFF); + spin_unlock_irqrestore(&pdata->lock, flags); + } + + 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; + + if (!netif_running(pdata->ndev)) + return 0; + + 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->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_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); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium Ethernet driver"); +MODULE_AUTHOR("Wenting Song"); +MODULE_ALIAS("platform:phytmac"); + diff --git a/drivers/net/ethernet/phytium/phytmac_pci.c b/drivers/net/ethernet/phytium/phytmac_pci.c new file mode 100644 index 0000000000000000000000000000000000000000..fd21bf80f1388350d2692f11ebacadc97c701e10 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_pci.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Phytium GMAC PCI wrapper. + * + */ + +#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; + u32 tsu_rate; + 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_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); + pcim_iounmap_regions(pdev, bar_mask); + + 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_SGMII, + .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_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"); diff --git a/drivers/net/ethernet/phytium/phytmac_platform.c b/drivers/net/ethernet/phytium/phytmac_platform.c new file mode 100644 index 0000000000000000000000000000000000000000..305ff5866e2fe0fdc71b0a347275b95e674f4308 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_platform.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Phytium GMAC Platform wrapper. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include "phytmac.h" +#include "phytmac_v1.h" +#include "phytmac_v2.h" + +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, + .tsu_rate = 300000000, +}; + +static const struct phytmac_config phytium_2p0_config = { + .hw_if = &phytmac_2p0_hw, + .caps = PHYTMAC_CAPS_TAILPTR + | PHYTMAC_CAPS_LPI + | PHYTMAC_CAPS_LSO + | PHYTMAC_CAPS_MSG + | PHYTMAC_CAPS_JUMBO, + .queue_num = 2, + .tsu_rate = 300000000, +}; + +#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 }, + { } +}; + +MODULE_DEVICE_TABLE(acpi, phytmac_acpi_ids); +#else +#define phytmac_acpi_ids NULL +#endif + +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; + struct device_node *np = pdev->dev.of_node; + struct resource *regs; + struct phytmac *pdata; + int ret, i; + u32 queue_num; + + 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) { + const struct of_device_id *match; + + 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)) { + const struct acpi_device_id *match; + + match = acpi_match_device(phytmac_acpi_ids, &pdev->dev); + if (match && match->driver_data) { + phytmac_config = (void *)match->driver_data; + pdata->hw_if = phytmac_config->hw_if; + pdata->capacities = phytmac_config->caps; + pdata->queues_max_num = phytmac_config->queue_num; + } + } + + i = 0; + pdata->mac_regs = devm_platform_get_and_ioremap_resource(pdev, i, ®s); + 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 (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, "lpi")) + pdata->capacities |= PHYTMAC_CAPS_LPI; + + if (pdata->capacities & PHYTMAC_CAPS_LPI) { + /* lpi resource */ + ++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; + + 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 int phytmac_plat_remove(struct platform_device *pdev) +{ + struct phytmac *pdata = platform_get_drvdata(pdev); + + phytmac_drv_remove(pdata); + phytmac_free_pdata(pdata); + + return 0; +} + +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_DRV_NAME, + .of_match_table = of_match_ptr(phytmac_dt_ids), + .acpi_match_table = phytmac_acpi_ids, + .pm = &phytmac_plat_pm_ops, + }, +}; + +module_platform_driver(phytmac_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium Ethernet driver"); +MODULE_AUTHOR("Wenting Song"); +MODULE_ALIAS("platform:phytmac"); diff --git a/drivers/net/ethernet/phytium/phytmac_ptp.c b/drivers/net/ethernet/phytium/phytmac_ptp.c new file mode 100644 index 0000000000000000000000000000000000000000..26b1b75edbde1ded359a5e6fbef51c3306dc7379 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_ptp.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** + * 1588 PTP support for Phytium GMAC device. + * + */ +#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 0000000000000000000000000000000000000000..72c8b7c67413706496d44d7c7fac110a70513aa4 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_ptp.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Phytium Ethernet Controller driver + * + * + * 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 0000000000000000000000000000000000000000..ec95c6c79b06681158396f16ab3f0d024ad558d9 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_v1.c @@ -0,0 +1,1400 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#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)); + + 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); + + 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) +{ + struct phytmac_queue *queue; + 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, queue = pdata->queues; q < pdata->queues_max_num; ++q, ++queue) { + 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_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; + + 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) + PHYTMAC_WRITE(pdata, PHYTMAC_TAIL_ENABLE, 0x80000001); + + 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; + + if (pdata->capacities & PHYTMAC_CAPS_LPI) { + 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"); + + 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 ? "up" : "down", data1, rdata1); + else + netdev_err(pdata->ndev, "gmac power %s failed, data1 = %x, rdata1=%x\n", + on ? "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); + + return 0; +} + +static void phytmac_mdio_idle(struct phytmac *pdata) +{ + u32 val; + + /* wait for end of transfer */ + val = PHYTMAC_READ(pdata, PHYTMAC_NSTATUS); + while (!(val & PHYTMAC_BIT(MDIO_IDLE))) { + cpu_relax(); + val = PHYTMAC_READ(pdata, PHYTMAC_NSTATUS); + } +} + +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 (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_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); + desc->desc0 = lower_32_bits(addr) | PHYTMAC_BIT(RX_USED); + } + return 0; +} + +static unsigned int phytmac_rx_clean_desc(struct phytmac_queue *queue, u32 count) +{ + struct phytmac_dma_desc *desc; + u32 index = queue->rx_head + count - 1; + + while (count) { + desc = phytmac_get_rx_desc(queue, index); + desc->desc0 &= ~PHYTMAC_BIT(RX_USED); + dma_wmb(); + index--; + count--; + } + + 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_TAILPTR(queue->index), + BIT(31) | queue->tx_tail); + + if (pdata->capacities & PHYTMAC_CAPS_START) + PHYTMAC_WRITE(pdata, PHYTMAC_NCTRL, + PHYTMAC_READ(pdata, PHYTMAC_NCTRL) | PHYTMAC_BIT(TSTART)); +} + +static void phytmac_restart(struct phytmac *pdata) +{ + int q; + struct phytmac_queue *queue; + + for (q = 0; q < pdata->queues_num; q++) { + queue = &pdata->queues[q]; + if (queue->tx_head != queue->tx_tail) { + PHYTMAC_WRITE(pdata, PHYTMAC_NCTRL, + PHYTMAC_READ(pdata, PHYTMAC_NCTRL) | PHYTMAC_BIT(TSTART)); + break; + } + } +} + +static int phytmac_tx_complete(const struct phytmac_dma_desc *desc) +{ + return PHYTMAC_GET_BITS(desc->desc1, TX_USED); +} + +static int phytmac_rx_complete(const struct phytmac_dma_desc *desc) +{ + return (desc->desc0 & PHYTMAC_BIT(RX_USED)) != 0; +} + +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 dma_addr_t phytmac_get_desc_addr(const struct phytmac_dma_desc *desc) +{ + dma_addr_t addr = 0; + + addr = ((u64)(desc->desc2) << 32); + + addr |= (desc->desc0 & 0xfffffffc); + return addr; +} + +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 != end; 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) + config |= PHYTMAC_BIT(2PT5G); + } 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) { + config |= PHYTMAC_BIT(2PT5G) | PHYTMAC_BIT(PCS_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 configuration, enable otherwise.*/ + if (state->interface == PHY_INTERFACE_MODE_SGMII) + phytmac_enable_autoneg(pdata, mode == MLO_AN_FIXED ? 0 : 1); + if (state->interface == PHY_INTERFACE_MODE_1000BASEX) + phytmac_enable_autoneg(pdata, 1); +} + +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_TAILPTR(queue->index), queue->tx_tail); +} + +static void phytmac_get_hw_stats(struct phytmac *pdata) +{ + u32 stats[45]; + int i, j; + u64 val; + u64 *p = &pdata->stats.tx_octets; + + for (i = 0 ; i < 45; i++) + stats[i] = PHYTMAC_READ(pdata, PHYTMAC_OCTTX + i * 4); + + for (i = 0, j = 0; i < 45; 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_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, + .restart = phytmac_restart, + .tx_complete = phytmac_tx_complete, + .rx_complete = phytmac_rx_complete, + .get_rx_pkt_len = phytmac_rx_pkt_len, + .get_desc_addr = phytmac_get_desc_addr, + .init_rx_map = phytmac_init_rx_map_desc, + .rx_map = phytmac_rx_map_desc, + .rx_clean = phytmac_rx_clean_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, + /* 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 0000000000000000000000000000000000000000..d8de2c26cab4b2543167db3a8f5bb6672bb85ec5 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_v1.h @@ -0,0 +1,455 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#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 + +/* 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_TAILPTR(i) (0x0e80 + ((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_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 + +#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 0000000000000000000000000000000000000000..df142aa6797f687d35ddb0355fe9c0e57d21a8a4 --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_v2.c @@ -0,0 +1,1254 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phytmac.h" +#include "phytmac_v2.h" + +static int phytmac_msg_send(struct phytmac *pdata, u16 cmd_id, + u16 cmd_subid, void *data, int len, int wait) +{ + int index = 0; + struct phytmac_msg_info msg; + struct phytmac_msg_info msg_rx; + int ret = 0; + + ++pdata->msg_ring.tx_msg_tail; + if (pdata->msg_ring.tx_msg_tail > pdata->msg_ring.tx_msg_ring_size) + pdata->msg_ring.tx_msg_tail = 1; + index = pdata->msg_ring.tx_msg_tail; + + wait = 1; + memset(&msg, 0, sizeof(msg)); + memset(&msg_rx, 0, sizeof(msg_rx)); + msg.module_id = PHYTMAC_MODULE_ID_GMAC; + msg.cmd_id = cmd_id; + msg.cmd_subid = cmd_subid; + msg.flags = PHYTMAC_FLAGS_MSG_NOINT; + + if (len) + memcpy(&msg.para[0], data, len); + + if (netif_msg_hw(pdata)) { + netdev_info(pdata->ndev, "tx msg: cmdid=%d, subid=%d, flags=%d, len=%d, tail=%d\n", + msg.cmd_id, msg.cmd_subid, msg.flags, len, pdata->msg_ring.tx_msg_tail); + } + + memcpy(pdata->msg_regs + PHYTMAC_MSG(index), &msg, sizeof(msg)); + PHYTMAC_WRITE(pdata, PHYTMAC_TX_MSG_TAIL, + pdata->msg_ring.tx_msg_tail | PHYTMAC_BIT(TX_MSG_INT)); + + if (wait) { + memcpy(&msg_rx, pdata->msg_regs + PHYTMAC_MSG(index), MSG_HDR_LEN); + while (!(msg_rx.flags & 0x1)) { + cpu_relax(); + memcpy(&msg_rx, pdata->msg_regs + PHYTMAC_MSG(index), MSG_HDR_LEN); + } + } + + return ret; +} + +void phytmac_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_TAIL_PTR(q), 0); + } + + /* reset hw rx/tx enable */ + cmd_id = PHYTMAC_MSG_CMD_DEFAULT; + cmd_subid = PHYTMAC_MSG_CMD_DEFAULT_RESET_HW; + phytmac_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_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_msg_send(pdata, cmd_id, cmd_subid, (void *)(&ring), sizeof(ring), 1); +} + +static int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + index = pdata->msg_ring.tx_msg_tail; + if (index <= 0) + index += pdata->msg_ring.tx_msg_ring_size; + memcpy(¶, pdata->msg_regs + PHYTMAC_MSG(index) + 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; +} + +int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); + + return 0; +} + +static int phytmac_init_hw(struct phytmac *pdata) +{ + u16 cmd_id, cmd_subid; + struct phytmac_dma_info dma; + struct phytmac_eth_info eth; + + phytmac_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_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_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_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 0); + } + + if (!(pdata->ndev->flags & IFF_BROADCAST)) { + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_BC; + phytmac_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 0); + } + + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_PAUSE; + phytmac_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 0); + + cmd_subid = PHYTMAC_MSG_CMD_SET_ENABLE_STRIPCRC; + phytmac_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_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_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_msg_send(pdata, cmd_id, cmd_subid, (void *)ð, sizeof(eth), 1); + + return 0; +} + +static int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + return 0; +} + +static int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); + + return 0; +} + +static int phytmac_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_TAIL_PTR(q), queue->tx_head); + txring.addr[q] = queue->tx_ring_addr; + rxring.addr[q] = queue->rx_ring_addr; + } + + phytmac_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_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_msg_send(pdata, cmd_id, cmd_subid, (void *)(&rxring), sizeof(rxring), 0); + + return 0; +} + +int phytmac_init_msg_ring(struct phytmac *pdata) +{ + u32 size = 0; + + pdata->msg_ring.tx_msg_tail = PHYTMAC_READ(pdata, PHYTMAC_TX_MSG_TAIL) & 0xff; + size = PHYTMAC_READ_BITS(pdata, PHYTMAC_SIZE, TXRING_SIZE); + pdata->msg_ring.tx_msg_ring_size = size; + if (pdata->msg_ring.tx_msg_tail == size) + pdata->msg_ring.tx_msg_tail = 0; + + PHYTMAC_WRITE(pdata, PHYTMAC_MSG_IMR, 0xfffffffe); + if (netif_msg_hw(pdata)) + netdev_info(pdata->ndev, "mac msg ring: tx_msg_ring_size=%d, tx_msg_tail=%d\n", + size, pdata->msg_ring.tx_msg_tail); + + return 0; +} + +static int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + index = pdata->msg_ring.tx_msg_tail; + if (index <= 0) + index += pdata->msg_ring.tx_msg_ring_size; + + memcpy(¶, pdata->msg_regs + PHYTMAC_MSG(index) + 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; + pdata->tx_bd_prefetch = (2 << (para.tx_bd_prefetch - 1)) * + sizeof(struct phytmac_dma_desc); + 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; +} + +void phytmac_get_regs(struct phytmac *pdata, u32 *reg_buff) +{ + u16 cmd_id, cmd_subid; + u16 reg_num; + int index; + + cmd_id = PHYTMAC_MSG_CMD_GET; + cmd_subid = PHYTMAC_MSG_CMD_GET_REG_READ; + reg_num = 16; + phytmac_msg_send(pdata, cmd_id, cmd_subid, (void *)®_num, 2, 1); + + index = pdata->msg_ring.tx_msg_tail; + if (index <= 0) + index += pdata->msg_ring.tx_msg_ring_size; + + memcpy(reg_buff, pdata->msg_regs + PHYTMAC_MSG(index) + MSG_HDR_LEN, 64); +} + +static void phytmac_get_hw_stats(struct phytmac *pdata) +{ + u16 cmd_id, cmd_subid; + u8 count; + int i, j, index; + u32 stats[48]; + u64 val; + u64 *p = &pdata->stats.tx_octets; + + cmd_id = PHYTMAC_MSG_CMD_GET; + cmd_subid = PHYTMAC_MSG_CMD_GET_STATS; + count = 1; + phytmac_msg_send(pdata, cmd_id, cmd_subid, (void *)&count, sizeof(count), 0); + + cmd_id = PHYTMAC_MSG_CMD_GET; + cmd_subid = PHYTMAC_MSG_CMD_GET_STATS; + count = 2; + phytmac_msg_send(pdata, cmd_id, cmd_subid, (void *)&count, sizeof(count), 0); + + cmd_id = PHYTMAC_MSG_CMD_GET; + cmd_subid = PHYTMAC_MSG_CMD_GET_STATS; + count = 3; + phytmac_msg_send(pdata, cmd_id, cmd_subid, (void *)&count, sizeof(count), 1); + + for (i = 0; i < 3; i++) { + index = pdata->msg_ring.tx_msg_tail + i - 2; + if (index <= 0) + index += pdata->msg_ring.tx_msg_ring_size; + memcpy(&stats[i * 16], pdata->msg_regs + PHYTMAC_MSG(index) + MSG_HDR_LEN, 64); + } + + for (i = 0, j = 0; i < 44; 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_mdio_idle(struct phytmac *pdata) +{ + u32 val; + + /* wait for end of transfer */ + val = PHYTMAC_READ(pdata, PHYTMAC_NETWORK_STATUS); + while (!(val & PHYTMAC_BIT(MIDLE))) { + cpu_relax(); + val = PHYTMAC_READ(pdata, PHYTMAC_NETWORK_STATUS); + } +} + +static int phytmac_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_mdio_idle(pdata); + data = PHYTMAC_READ(pdata, PHYTMAC_MDIO) & 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_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_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_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_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_mdio_idle(pdata); + data = PHYTMAC_READ(pdata, PHYTMAC_MDIO) & 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_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_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_mdio_idle(pdata); + + return 0; +} + +static int phytmac_powerup_hw(struct phytmac *pdata, int on) +{ + u32 status, data0, data1, rdata1; + int ret; + + if (pdata->capacities & PHYTMAC_CAPS_LPI) { + 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 ? "up" : "down", data1, rdata1); + else + netdev_err(pdata->ndev, "gmac power %s failed, data1 = %x, rdata1=%x\n", + on ? "up" : "down", data1, rdata1); + } + + pdata->power_state = on; + + return 0; +} + +static int phytmac_set_wake(struct phytmac *pdata, int wake) +{ + u16 cmd_id, cmd_subid; + u8 wol = (u8)wake; + + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_WOL; + + phytmac_msg_send(pdata, cmd_id, cmd_subid, (void *)(&wol), 1, 1); + + return 0; +} + +static int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, (void *)(&rxcsum), 1, 1); + + return 0; +} + +static int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + return 0; +} + +static int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + return 0; +} + +static int phytmac_enable_mdio(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_MDIO; + else + cmd_subid = PHYTMAC_MSG_CMD_SET_DISABLE_MDIO; + + phytmac_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + return 0; +} + +static int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + pdata->autoneg = enable; + return 0; +} + +static int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + return 0; +} + +static int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, NULL, 0, 1); + + 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; + 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 = rx_flow->location; + fdir.queue = 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_msg_send(pdata, cmd_id, cmd_subid, (void *)(&fdir), sizeof(fdir), 1); + } + + return 0; +} + +static int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, (void *)(&fdir), sizeof(fdir), 1); + + return 0; +} + +static void phytmac_tx_start(struct phytmac_queue *queue) +{ + struct phytmac *pdata = queue->pdata; + + PHYTMAC_WRITE(pdata, PHYTMAC_TAIL_PTR(queue->index), queue->tx_tail); + queue->tx_xmit_more = 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(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_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_enable_irq(struct phytmac *pdata, + int queue_index, u32 mask) +{ + u32 value; + + value = phytmac_get_irq_mask(mask); + PHYTMAC_WRITE(pdata, PHYTMAC_INT_ER(queue_index), value); +} + +static void phytmac_disable_irq(struct phytmac *pdata, + int queue_index, u32 mask) +{ + u32 value; + + value = phytmac_get_irq_mask(mask); + PHYTMAC_WRITE(pdata, PHYTMAC_INT_DR(queue_index), value); +} + +static void phytmac_clear_irq(struct phytmac *pdata, + int queue_index, u32 mask) +{ + u32 value; + + value = phytmac_get_irq_mask(mask); + PHYTMAC_WRITE(pdata, PHYTMAC_INT_SR(queue_index), value); +} + +static unsigned int phytmac_get_irq(struct phytmac *pdata, int queue_index) +{ + u32 status; + u32 value; + + value = PHYTMAC_READ(pdata, PHYTMAC_INT_SR(queue_index)); + status = phytmac_get_irq_status(value); + + return status; +} + +static void phytmac_interface_config(struct phytmac *pdata, unsigned int mode, + const struct phylink_link_state *state) +{ + struct phytmac_interface_info para; + u16 cmd_id, cmd_subid; + + memset(¶, 0, sizeof(para)); + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_MAC_CONFIG; + para.interface = state->interface; + para.autoneg = (mode == MLO_AN_FIXED ? 0 : 1); + para.speed = state->speed; + para.duplex = state->duplex; + pdata->autoneg = para.autoneg; + phytmac_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); +} + +static int phytmac_interface_linkup(struct phytmac *pdata, phy_interface_t interface, + int speed, int duplex) +{ + struct phytmac_interface_info para; + u16 cmd_id, cmd_subid; + + memset(¶, 0, sizeof(para)); + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_MAC_LINK_CONFIG; + para.interface = interface; + para.duplex = duplex; + para.speed = speed; + para.autoneg = pdata->autoneg; + phytmac_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); + + return 0; +} + +static int phytmac_interface_linkdown(struct phytmac *pdata) +{ + return 0; +} + +static int phytmac_pcs_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_USXGMII || + interface == PHY_INTERFACE_MODE_10GBASER) { + memset(¶, 0, sizeof(para)); + cmd_id = PHYTMAC_MSG_CMD_SET; + cmd_subid = PHYTMAC_MSG_CMD_SET_PCS_LINK_UP; + para.interface = interface; + para.duplex = duplex; + para.speed = speed; + para.autoneg = 0; + phytmac_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); + } + + return 0; +} + +static int phytmac_pcs_linkdown(struct phytmac *pdata) +{ + return 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_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_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_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_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 */ + dma_wmb(); + desc->desc0 = lower_32_bits(addr); + } else { + desc->desc1 = 0; + /* make newly descriptor to hardware */ + dma_wmb(); + desc->desc0 &= ~PHYTMAC_BIT(RXUSED); + } + + return 0; +} + +static int phytmac_tx_complete(const struct phytmac_dma_desc *desc) +{ + return PHYTMAC_GET_BITS(desc->desc1, TXUSED); +} + +static int phytmac_rx_complete(const struct phytmac_dma_desc *desc) +{ + return PHYTMAC_GET_BITS(desc->desc0, RXUSED); +} + +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_RXJFRMLEN_MASK; + else + return desc->desc1 & PHYTMAC_RXFRMLEN_MASK; +} + +static dma_addr_t phytmac_get_desc_addr(const struct phytmac_dma_desc *desc) +{ + dma_addr_t addr = 0; + + addr = ((u64)(desc->desc2) << 32); + + addr |= (desc->desc0 & 0xfffffffc); + return addr; +} + +static bool phytmac_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_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_rx_sof(const struct phytmac_dma_desc *desc) +{ + u32 value = desc->desc1; + + return (value & PHYTMAC_BIT(RXSOF)); +} + +static bool phytmac_rx_eof(const struct phytmac_dma_desc *desc) +{ + u32 value = desc->desc1; + + return (value & PHYTMAC_BIT(RXEOF)); +} + +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 != end; frag++) { + desc = phytmac_get_rx_desc(queue, frag); + desc->desc0 &= ~PHYTMAC_BIT(RXUSED); + } +} + +static void phytmac_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_TAIL_PTR(queue->index), queue->tx_tail); +} + +static void phytmac_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; +} + +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_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); +} + +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_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); +} + +int phytmac_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_msg_send(pdata, cmd_id, cmd_subid, (void *)(¶), sizeof(para), 1); + + return 0; +} + +static int phytmac_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_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; + + /* 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_set_tsincr(pdata, &ts_incr); + + return 0; +} + +int phytmac_adjust_time(struct phytmac *pdata, s64 delta, int neg) +{ + u32 adj; + + if (delta > PHYTMAC_ASEC_MAX) { + struct timespec64 now, then; + + 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_AADD_INDEX) | delta; + PHYTMAC_WRITE(pdata, PHYTMAC_TIMER_ADJUST, 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(TXTSVALID); + else if (direction == PHYTMAC_RX) + ts_valid = desc->desc0 & PHYTMAC_BIT(RXTSVALID); + 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, TS_SECH) << 2) | + PHYTMAC_GET_BITS(ts_1, TS_SECL); + ts->tv_nsec = PHYTMAC_GET_BITS(ts_1, TS_NSEC); + + phytmac_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_get_ts_rate(struct phytmac *pdata) +{ + return 300000000; +} + +struct phytmac_hw_if phytmac_2p0_hw = { + .init_msg_ring = phytmac_init_msg_ring, + .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_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_interface_config, + .mac_linkup = phytmac_interface_linkup, + .mac_linkdown = phytmac_interface_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, + + /* tx and rx */ + .tx_map = phytmac_tx_map_desc, + .transmit = phytmac_tx_start, + .tx_complete = phytmac_tx_complete, + .rx_complete = phytmac_rx_complete, + .get_rx_pkt_len = phytmac_rx_pkt_len, + .get_desc_addr = phytmac_get_desc_addr, + .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, + + /* 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_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 0000000000000000000000000000000000000000..92e4806ac2c1f7f72be6f0f74d34b6624d23800c --- /dev/null +++ b/drivers/net/ethernet/phytium/phytmac_v2.h @@ -0,0 +1,362 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _PHYTMAC_V2_H +#define _PHYTMAC_V2_H + +extern struct phytmac_hw_if phytmac_2p0_hw; + +#define PHYTMAC_MSG_SRAM_SIZE 4096 +#define MSG_HDR_LEN 8 + +#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_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) - 1) * 0x48) + +#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_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_TAIL_PTR(i) (0x0100 + ((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 + +#define HW_DMA_CAP_64B 0x1 +#define HW_DMA_CAP_CSUM 0x2 +#define HW_DMA_CAP_PTP 0x4 +#define HW_DMA_CAP_DDW64 0x8 +#define HW_DMA_CAP_DDW128 0x10 + +#define PHYTMAC_DBW64 2 +#define PHYTMAC_DBW128 4 + +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 = 0, + 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_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, +}; + +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, +}; + +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_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_msg_info { + u16 module_id; + u16 cmd_id; + u16 cmd_subid; + u16 flags; + u8 para[64]; +} __packed; + +#endif diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig index 92d7d5a00b84c7ee60b0635627bb5cb80e80203f..adc632948c36234df310a9456a3369d74d7c279f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig @@ -121,6 +121,15 @@ config DWMAC_MESON the stmmac device driver. This driver is used for Meson6, Meson8, Meson8b and GXBB SoCs. +config DWMAC_PHYTIUM + tristate "Phytium dwmac support" + depends on (OF || ACPI) + 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 5b57aee19267ff2dddc2e38fe61ed5c08f66c1e0..d6160c8dff18e80fe884613b9cba2403ba5dda5d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Makefile +++ b/drivers/net/ethernet/stmicro/stmmac/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_DWMAC_GENERIC) += dwmac-generic.o obj-$(CONFIG_DWMAC_IMX8) += dwmac-imx.o obj-$(CONFIG_DWMAC_TEGRA) += dwmac-tegra.o obj-$(CONFIG_DWMAC_VISCONTI) += dwmac-visconti.o +obj-$(CONFIG_DWMAC_PHYTIUM) += dwmac-phytium.o stmmac-platform-objs:= stmmac_platform.o dwmac-altr-socfpga-objs := dwmac-socfpga.o diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index f9a3f3321e596f5789905440db91fc071b06516a..82fc3e30787afdc792e8759e3ca63e2de236173d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -51,10 +51,10 @@ */ #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_GET_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 0000000000000000000000000000000000000000..23f0e4a47be20b7c6772118d5cd681c4662d613e --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-phytium.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SWMAC specific glue layer + * + * Copyright (C) 2022, 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); + + 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); + if (plat->phy_interface < 0) + return plat->phy_interface; + + plat->mac_interface = phytium_get_mac_mode(fwnode); + if (plat->mac_interface < 0) + plat->mac_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); + plat->phy_node = np; + } + + 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->has_gmac = 1; + 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"); + + 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; + plat->axi->axi_blen[0] = 16; + + 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; + stmmac_res.lpi_irq = -1; + + return stmmac_dvr_probe(&pdev->dev, plat, &stmmac_res); +} + +int 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); + + return 0; +} + +#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 = "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", + .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/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index 68a7cfcb1d8f3fd5628bb2c77084d11c24d1fb96..40088a390f7b3bf0e80e58b423655160b3b13504 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -200,6 +200,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/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index c0c3f28249907460fa7394e94253839f66a06ede..f67f27162397240b3a9d2bff40a2da6f82b62780 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -215,6 +215,16 @@ config PCIE_MT7621 help This selects a driver for the MediaTek MT7621 PCIe Controller. +config PCIE_PHYTIUM_EP + tristate "Phytium PCIe endpoint controller" + depends on OF + depends on PCI_ENDPOINT + help + Say Y here if you want to support Phytium PCIe controller in + endpoint mode on Phytium SoC. The controller can act as Root Port + or End Point with different phytium firmware. But End Point mode only support + one physical function. + config PCIE_MICROCHIP_HOST tristate "Microchip AXI PCIe controller" depends on PCI_MSI && OF diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile index 37c8663de7fe1ff7c9c948cd39f4b6ce1a912f5b..74c69a5be23c29739c6bcebde7fe963c1ed4f8da 100644 --- a/drivers/pci/controller/Makefile +++ b/drivers/pci/controller/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_PCI_LOONGSON) += pci-loongson.o obj-$(CONFIG_PCIE_HISI_ERR) += pcie-hisi-error.o obj-$(CONFIG_PCIE_APPLE) += pcie-apple.o obj-$(CONFIG_PCIE_MT7621) += pcie-mt7621.o +obj-$(CONFIG_PCIE_PHYTIUM_EP) += pcie-phytium-ep.o # pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW obj-y += dwc/ diff --git a/drivers/pci/controller/pcie-phytium-ep.c b/drivers/pci/controller/pcie-phytium-ep.c new file mode 100644 index 0000000000000000000000000000000000000000..edab8fb369ead6250b7c92d36b6204daf5f995b4 --- /dev/null +++ b/drivers/pci/controller/pcie-phytium-ep.c @@ -0,0 +1,470 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium PCIe Endpoint controller driver + * + * Copyright (c) 2021-2023, Phytium Technology Co., Ltd. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcie-phytium-ep.h" +#include "pcie-phytium-register.h" + +#define PHYTIUM_PCIE_EP_IRQ_PCI_ADDR_NONE 0x0 +#define PHYTIUM_PCIE_EP_IRQ_PCI_ADDR_LEGACY 0x1 + +static int phytium_pcie_ep_write_header(struct pci_epc *epc, unsigned char fn, u8 vfn, + struct pci_epf_header *hdr) +{ + struct phytium_pcie_ep *priv = epc_get_drvdata(epc); + u16 tmp = 0; + + phytium_pcie_writew(priv, fn, PHYTIUM_PCI_VENDOR_ID, hdr->vendorid); + phytium_pcie_writew(priv, fn, PHYTIUM_PCI_DEVICE_ID, hdr->deviceid); + phytium_pcie_writeb(priv, fn, PHYTIUM_PCI_REVISION_ID, hdr->revid); + phytium_pcie_writeb(priv, fn, PHYTIUM_PCI_CLASS_PROG, hdr->progif_code); + phytium_pcie_writew(priv, fn, PHYTIUM_PCI_CLASS_DEVICE, + hdr->subclass_code | (hdr->baseclass_code << 8)); + + phytium_pcie_writew(priv, fn, PHYTIUM_PCI_SUBSYS_VENDOR_ID, + hdr->subsys_vendor_id); + phytium_pcie_writew(priv, fn, PHYTIUM_PCI_SUBSYS_DEVICE_ID, + hdr->subsys_id); + + tmp = phytium_pcie_readw(priv, fn, PHYTIUM_PCI_INTERRUPT_PIN); + tmp = ((tmp & (~INTERRUPT_PIN_MASK)) | hdr->interrupt_pin); + phytium_pcie_writew(priv, fn, PHYTIUM_PCI_INTERRUPT_PIN, tmp); + + tmp = phytium_pcie_readw(priv, fn, PHYTIUM_PCI_MSIX_CAP); + phytium_pcie_writew(priv, fn, PHYTIUM_PCI_MSIX_CAP, MSIX_DISABLE); + + return 0; +} + +static int phytium_pcie_ep_set_bar(struct pci_epc *epc, u8 fn, u8 vfn, + struct pci_epf_bar *epf_bar) +{ + struct phytium_pcie_ep *priv = epc_get_drvdata(epc); + u64 sz = 0, sz_mask, atr_size; + int flags = epf_bar->flags; + u32 setting, src_addr0, src_addr1, trsl_addr0, trsl_addr1, trsl_param; + enum pci_barno barno = epf_bar->barno; + struct pci_epc_mem *mem = epc->mem; + + if ((flags & PCI_BASE_ADDRESS_MEM_TYPE_64) && (barno & 1)) { + dev_err(&epc->dev, "bar %d do not support mem64\n", barno); + return -EINVAL; + } + + if (barno & 1) { + dev_err(&epc->dev, "not support bar 1/3/5\n"); + return -EINVAL; + } + dev_dbg(epc->dev.parent, "set bar%d mapping address 0x%pa size 0x%lx\n", + barno, &(epf_bar->phys_addr), epf_bar->size); + + if ((flags & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) { + setting = BAR_IO_TYPE; + sz = max_t(size_t, epf_bar->size, BAR_IO_MIN_APERTURE); + sz = 1 << fls64(sz - 1); + sz_mask = ~(sz - 1); + setting |= sz_mask; + trsl_param = TRSL_ID_IO; + } else { + setting = BAR_MEM_TYPE; + sz = max_t(size_t, epf_bar->size, BAR_MEM_MIN_APERTURE); + sz = 1 << fls64(sz - 1); + sz_mask = ~(sz - 1); + setting |= lower_32_bits(sz_mask); + + if (flags & PCI_BASE_ADDRESS_MEM_TYPE_64) + setting |= BAR_MEM_64BIT; + + if (flags & PCI_BASE_ADDRESS_MEM_PREFETCH) + setting |= BAR_MEM_PREFETCHABLE; + + trsl_param = TRSL_ID_MASTER; + } + + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_BAR(barno), setting); + if (flags & PCI_BASE_ADDRESS_MEM_TYPE_64) + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_BAR(barno + 1), + upper_32_bits(sz_mask)); + dev_dbg(epc->dev.parent, "set bar%d mapping address 0x%pa size 0x%llx 0x%x\n", + barno, &(epf_bar->phys_addr), sz, lower_32_bits(epf_bar->phys_addr)); + sz = ALIGN(sz, mem->window.page_size); + atr_size = fls64(sz - 1) - 1; + src_addr0 = ATR_IMPL | ((atr_size & ATR_SIZE_MASK) << ATR_SIZE_SHIFT); + src_addr1 = 0; + trsl_addr0 = (lower_32_bits(epf_bar->phys_addr) & TRSL_ADDR_32_12_MASK); + trsl_addr1 = upper_32_bits(epf_bar->phys_addr); + + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_WIN0_SRC_ADDR0(barno), + src_addr0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_WIN0_SRC_ADDR1(barno), + src_addr1); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_WIN0_TRSL_ADDR0(barno), + trsl_addr0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_WIN0_TRSL_ADDR1(barno), + trsl_addr1); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_WIN0_TRSL_PARAM(barno), + trsl_param); + + return 0; +} + +static void phytium_pcie_ep_clear_bar(struct pci_epc *epc, u8 fn, u8 vfn, + struct pci_epf_bar *epf_bar) +{ + struct phytium_pcie_ep *priv = epc_get_drvdata(epc); + int flags = epf_bar->flags; + enum pci_barno barno = epf_bar->barno; + + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_BAR(barno), 0); + if (flags & PCI_BASE_ADDRESS_MEM_TYPE_64) + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_BAR(barno + 1), 0); + + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_WIN0_SRC_ADDR0(barno), 0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_WIN0_SRC_ADDR1(barno), 0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_WIN0_TRSL_ADDR0(barno), 0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_WIN0_TRSL_ADDR1(barno), 0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_WIN0_TRSL_PARAM(barno), 0); +} + +static int phytium_pcie_ep_map_addr(struct pci_epc *epc, u8 fn, u8 vfn, + phys_addr_t addr, u64 pci_addr, + size_t size) +{ + struct phytium_pcie_ep *priv = epc_get_drvdata(epc); + u32 src_addr0, src_addr1, trsl_addr0, trsl_addr1, trsl_param, atr_size; + u64 sz = 0; + u32 r; + struct pci_epc_mem *mem = epc->mem; + + r = find_first_zero_bit(&priv->ob_region_map, + sizeof(priv->ob_region_map) * BITS_PER_LONG); + if (r >= priv->max_regions) { + dev_err(&epc->dev, "no free outbound region\n"); + return -EINVAL; + } + + dev_dbg(epc->dev.parent, "set slave %d: mapping address 0x%pa to pci 0x%llx, size 0x%zx\n", + r, &addr, pci_addr, size); + + sz = ALIGN(size, mem->window.page_size); + atr_size = fls64(sz - 1) - 1; + src_addr0 = ATR_IMPL | ((atr_size & ATR_SIZE_MASK) << ATR_SIZE_SHIFT); + src_addr0 |= (lower_32_bits(addr) & SRC_ADDR_32_12_MASK); + src_addr1 = upper_32_bits(addr); + trsl_addr0 = (lower_32_bits(pci_addr) & TRSL_ADDR_32_12_MASK); + trsl_addr1 = upper_32_bits(pci_addr); + trsl_param = TRSL_ID_PCIE_TR; + + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_SRC_ADDR0(r), + src_addr0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_SRC_ADDR1(r), + src_addr1); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_TRSL_ADDR0(r), + trsl_addr0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_TRSL_ADDR1(r), + trsl_addr1); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_TRSL_PARAM(r), + trsl_param); + set_bit(r, &priv->ob_region_map); + priv->ob_addr[r] = addr; + + return 0; +} + +static void phytium_pcie_ep_unmap_addr(struct pci_epc *epc, u8 fn, u8 vfn, + phys_addr_t addr) +{ + struct phytium_pcie_ep *priv = epc_get_drvdata(epc); + u32 r; + + for (r = 0; r < priv->max_regions; r++) + if (priv->ob_addr[r] == addr) + break; + + if (r == priv->max_regions) { + dev_err(&epc->dev, "used unmap addr 0x%pa\n", &addr); + return; + } + dev_dbg(epc->dev.parent, "set slave %d: unmapping address 0x%pa\n", r, &addr); + + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_SRC_ADDR0(r), 0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_SRC_ADDR1(r), 0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_TRSL_ADDR0(r), 0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_TRSL_ADDR1(r), 0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_TRSL_PARAM(r), 0); + priv->ob_addr[r] = 0; + clear_bit(r, &priv->ob_region_map); +} + +static int phytium_pcie_ep_set_msi(struct pci_epc *epc, u8 fn, u8 vfn, u8 mmc) +{ + struct phytium_pcie_ep *priv = epc_get_drvdata(epc); + u16 flags = 0; + + flags = (mmc & MSI_NUM_MASK) << MSI_NUM_SHIFT; + flags &= ~MSI_MASK_SUPPORT; + phytium_pcie_writew(priv, fn, PHYTIUM_PCI_INTERRUPT_PIN, flags); + + return 0; +} + +static int phytium_pcie_ep_get_msi(struct pci_epc *epc, u8 fn, u8 vfn) +{ + struct phytium_pcie_ep *priv = epc_get_drvdata(epc); + u16 flags, mme; + u32 cap = PHYTIUM_PCI_CF_MSI_BASE; + + flags = phytium_pcie_readw(priv, fn, cap + PCI_MSI_FLAGS); + if (!(flags & PCI_MSI_FLAGS_ENABLE)) + return -EINVAL; + + mme = (flags & PCI_MSI_FLAGS_QSIZE) >> 4; + + return mme; +} + +static int phytium_pcie_ep_send_msi_irq(struct phytium_pcie_ep *priv, u8 fn, + u8 interrupt_num) +{ + u32 cap = PHYTIUM_PCI_CF_MSI_BASE; + u16 flags, mme, data_mask, data; + u8 msi_count; + u64 pci_addr, pci_addr_mask = IRQ_MAPPING_SIZE - 1; + u32 src_addr0, src_addr1, trsl_addr0, trsl_addr1, trsl_param, atr_size; + + flags = phytium_pcie_readw(priv, fn, cap + PCI_MSI_FLAGS); + if (!(flags & PCI_MSI_FLAGS_ENABLE)) + return -EINVAL; + + mme = (flags & PCI_MSI_FLAGS_QSIZE) >> 4; + msi_count = 1 << mme; + if (!interrupt_num || interrupt_num > msi_count) + return -EINVAL; + + data_mask = msi_count - 1; + data = phytium_pcie_readw(priv, fn, cap + PCI_MSI_DATA_64); + data = (data & ~data_mask) | ((interrupt_num - 1) & data_mask); + + /* Get the PCI address */ + pci_addr = phytium_pcie_readl(priv, fn, cap + PCI_MSI_ADDRESS_HI); + pci_addr <<= 32; + pci_addr |= phytium_pcie_readl(priv, fn, cap + PCI_MSI_ADDRESS_LO); + pci_addr &= GENMASK_ULL(63, 2); + + if (priv->irq_pci_addr != (pci_addr & ~pci_addr_mask) || (priv->irq_pci_fn != fn)) { + /* First region for IRQ writes. */ + atr_size = fls64(pci_addr_mask) - 1; + src_addr0 = ATR_IMPL | ((atr_size & ATR_SIZE_MASK) << ATR_SIZE_SHIFT); + src_addr0 |= (lower_32_bits(priv->irq_phys_addr) & SRC_ADDR_32_12_MASK); + src_addr1 = upper_32_bits(priv->irq_phys_addr); + trsl_addr0 = (lower_32_bits(pci_addr) & TRSL_ADDR_32_12_MASK); + trsl_addr1 = upper_32_bits(pci_addr); + trsl_param = TRSL_ID_PCIE_TR; + + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_SRC_ADDR0(0), + src_addr0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_SRC_ADDR1(0), + src_addr1); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_TRSL_ADDR0(0), + trsl_addr0); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_TRSL_ADDR1(0), + trsl_addr1); + phytium_pcie_writel(priv, fn, PHYTIUM_PCI_SLAVE0_TRSL_PARAM(0), + trsl_param); + priv->irq_pci_addr = (pci_addr & ~pci_addr_mask); + priv->irq_pci_fn = fn; + } + + dev_dbg(priv->epc->dev.parent, "send event %d\n", data); + writew(data, priv->irq_cpu_addr + (pci_addr & pci_addr_mask)); + + return 0; +} + +static int phytium_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, u8 vfn, + enum pci_epc_irq_type type, + u16 interrupt_num) +{ + struct phytium_pcie_ep *priv = epc_get_drvdata(epc); + + switch (type) { + case PCI_EPC_IRQ_MSI: + return phytium_pcie_ep_send_msi_irq(priv, fn, interrupt_num); + + default: + break; + } + + return -EINVAL; +} + +static int phytium_pcie_ep_start(struct pci_epc *epc) +{ + struct pci_epf *epf; + u32 cfg; + + cfg = BIT(0); + list_for_each_entry(epf, &epc->pci_epf, list) + cfg |= BIT(epf->func_no); + + return 0; +} + +static const struct pci_epc_ops phytium_pcie_epc_ops = { + .write_header = phytium_pcie_ep_write_header, + .set_bar = phytium_pcie_ep_set_bar, + .clear_bar = phytium_pcie_ep_clear_bar, + .map_addr = phytium_pcie_ep_map_addr, + .unmap_addr = phytium_pcie_ep_unmap_addr, + .set_msi = phytium_pcie_ep_set_msi, + .get_msi = phytium_pcie_ep_get_msi, + .raise_irq = phytium_pcie_ep_raise_irq, + .start = phytium_pcie_ep_start, +}; + + + +static int phytium_pcie_ep_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phytium_pcie_ep *priv = NULL; + struct resource *res; + struct device_node *np = dev->of_node; + struct pci_epc *epc; + int ret = 0, value; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "reg"); + priv->reg_base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->reg_base)) { + dev_err(dev, "missing \"reg\"\n"); + return PTR_ERR(priv->reg_base); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mem"); + if (!res) { + dev_err(dev, "missing \"mem\"\n"); + return -EINVAL; + } + priv->mem_res = res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hpb"); + priv->hpb_base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->hpb_base)) { + dev_err(dev, "missing \"hpb\"\n"); + return PTR_ERR(priv->hpb_base); + } + + ret = of_property_read_u32(np, "max-outbound-regions", &priv->max_regions); + if (ret < 0) { + dev_err(dev, "missing \"max-outbound-regions\"\n"); + return ret; + } + dev_info(dev, "%s max-outbound-regions %d\n", __func__, priv->max_regions); + + priv->ob_addr = devm_kcalloc(dev, priv->max_regions, + sizeof(*priv->ob_addr), GFP_KERNEL); + if (!priv->ob_addr) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + epc = devm_pci_epc_create(dev, &phytium_pcie_epc_ops); + if (IS_ERR(epc)) { + dev_err(dev, "failed to create epc device\n"); + return PTR_ERR(epc); + } + + priv->epc = epc; + epc_set_drvdata(epc, priv); + + if (of_property_read_u8(np, "max-functions", &epc->max_functions) < 0) + epc->max_functions = 1; + dev_info(dev, "%s epc->max_functions %d\n", __func__, epc->max_functions); + + + ret = pci_epc_mem_init(epc, priv->mem_res->start, + resource_size(priv->mem_res), PAGE_SIZE); + if (ret < 0) { + dev_err(dev, "failed to initialize the memory space\n"); + return ret; + } + + priv->irq_cpu_addr = pci_epc_mem_alloc_addr(epc, &priv->irq_phys_addr, + SZ_4K); + if (!priv->irq_cpu_addr) { + dev_err(dev, "failed to reserve memory space for MSI\n"); + ret = -ENOMEM; + goto err_alloc_irq_mem; + } + priv->irq_pci_addr = PHYTIUM_PCIE_EP_IRQ_PCI_ADDR_NONE; + /* Reserve region 0 for IRQS */ + set_bit(0, &priv->ob_region_map); + + value = ((lower_32_bits(priv->mem_res->start) >> C0_PREF_VALUE_SHIFT) + & C0_PREF_BASE_MASK) << C0_PREF_BASE_SHIFT; + value |= (((lower_32_bits(priv->mem_res->end) >> C0_PREF_VALUE_SHIFT) + & C0_PREF_LIMIT_MASK) << C0_PREF_LIMIT_SHIFT); + phytium_hpb_writel(priv, PHYTIUM_HPB_C0_PREF_BASE_LIMIT, value); + + value = ((upper_32_bits(priv->mem_res->start) >> C0_PREF_UP32_VALUE_SHIFT) + & C0_PREF_BASE_UP32_MASK) << C0_PREF_BASE_UP32_SHIFT; + value |= (((upper_32_bits(priv->mem_res->end) >> C0_PREF_UP32_VALUE_SHIFT) + & C0_PREF_LIMIT_UP32_MASK) << C0_PREF_LIMIT_UP32_SHIFT); + phytium_hpb_writel(priv, PHYTIUM_HPB_C0_PREF_BASE_LIMIT_UP32, value); + + dev_dbg(dev, "exit %s successful\n", __func__); + return 0; + +err_alloc_irq_mem: + pci_epc_mem_exit(epc); + return ret; +} + +static int phytium_pcie_ep_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phytium_pcie_ep *priv = dev_get_drvdata(dev); + struct pci_epc *epc = priv->epc; + + pci_epc_mem_exit(epc); + + return 0; +} + +static const struct of_device_id phytium_pcie_ep_of_match[] = { + { .compatible = "phytium,pd2008-pcie-ep" }, + { }, +}; + +static struct platform_driver phytium_pcie_ep_driver = { + .driver = { + .name = "phytium-pcie-ep", + .of_match_table = phytium_pcie_ep_of_match, + }, + .probe = phytium_pcie_ep_probe, + .remove = phytium_pcie_ep_remove, +}; + +module_platform_driver(phytium_pcie_ep_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yang Xun "); +MODULE_DESCRIPTION("Phytium PCIe Controller Endpoint driver"); diff --git a/drivers/pci/controller/pcie-phytium-ep.h b/drivers/pci/controller/pcie-phytium-ep.h new file mode 100644 index 0000000000000000000000000000000000000000..1c38181fc19d9dc61cc27594bc67179d5e28d544 --- /dev/null +++ b/drivers/pci/controller/pcie-phytium-ep.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium endpoint driver + * + * Copyright (c) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PCIE_PHYTIUM_EP_H__ +#define __PCIE_PHYTIUM_EP_H__ + +#include "pcie-phytium-register.h" + +#define IRQ_MAPPING_SIZE 0x1000 +struct phytium_pcie_ep { + void __iomem *reg_base; + struct resource *mem_res; + void __iomem *hpb_base; + unsigned int max_regions; + unsigned long ob_region_map; + phys_addr_t *ob_addr; + phys_addr_t irq_phys_addr; + void __iomem *irq_cpu_addr; + unsigned long irq_pci_addr; + u8 irq_pci_fn; + struct pci_epc *epc; +}; + +static inline void +phytium_pcie_writeb(struct phytium_pcie_ep *priv, u8 fn, u32 reg, u8 value) +{ + pr_debug("Write 32'h%08lx 32'h%08x\n", PHYTIUM_PCIE_FUNC_BASE(fn) + reg, value); + writeb(value, priv->reg_base + PHYTIUM_PCIE_FUNC_BASE(fn) + reg); +} + +static inline unsigned char +phytium_pcie_readb(struct phytium_pcie_ep *priv, u8 fn, u32 reg) +{ + unsigned char value; + + value = readb(priv->reg_base + PHYTIUM_PCIE_FUNC_BASE(fn) + reg); + pr_debug("Read 32'h%08lx 32'h%08x\n", PHYTIUM_PCIE_FUNC_BASE(fn) + reg, value); + + return value; +} + +static inline void +phytium_pcie_writew(struct phytium_pcie_ep *priv, u8 fn, u32 reg, u16 value) +{ + pr_debug("Write 32'h%08lx 32'h%08x\n", PHYTIUM_PCIE_FUNC_BASE(fn) + reg, value); + writew(value, priv->reg_base + PHYTIUM_PCIE_FUNC_BASE(fn) + reg); +} + +static inline unsigned short +phytium_pcie_readw(struct phytium_pcie_ep *priv, u8 fn, u32 reg) +{ + unsigned short value; + + value = readw(priv->reg_base + PHYTIUM_PCIE_FUNC_BASE(fn) + reg); + pr_debug("Read 32'h%08lx 32'h%08x\n", PHYTIUM_PCIE_FUNC_BASE(fn) + reg, value); + + return value; +} + +static inline void +phytium_pcie_writel(struct phytium_pcie_ep *priv, u8 fn, u32 reg, u32 value) +{ + pr_debug("Write 32'h%08lx 32'h%08x\n", PHYTIUM_PCIE_FUNC_BASE(fn) + reg, value); + writel(value, priv->reg_base + PHYTIUM_PCIE_FUNC_BASE(fn) + reg); +} + +static inline unsigned int +phytium_pcie_readl(struct phytium_pcie_ep *priv, u8 fn, u32 reg) +{ + unsigned int value; + + value = readl(priv->reg_base + PHYTIUM_PCIE_FUNC_BASE(fn) + reg); + pr_debug("Read 32'h%08lx 32'h%08x\n", PHYTIUM_PCIE_FUNC_BASE(fn) + reg, value); + + return value; +} + +static inline void +phytium_hpb_writel(struct phytium_pcie_ep *priv, u32 reg, u32 value) +{ + pr_debug("Write 32'h%08x 32'h%08x\n", reg, value); + writel(value, priv->hpb_base + reg); +} +#endif diff --git a/drivers/pci/controller/pcie-phytium-register.h b/drivers/pci/controller/pcie-phytium-register.h new file mode 100644 index 0000000000000000000000000000000000000000..f8ca56992a78dbbb09ac2be5c62ff7856a23dd7e --- /dev/null +++ b/drivers/pci/controller/pcie-phytium-register.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium PCIe Ednpoint controllr driver + * + * Copyright (c) 2021-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PCIE_PHYTIUM_REGISTER_H__ +#define __PCIE_PHYTIUM_REGISTER_H__ + +#define PHYTIUM_PCIE_FUNC_BASE(fn) (((fn) << 14) & GENMASK(16, 14)) +#define PHYTIUM_PCI_VENDOR_ID 0x98 +#define PHYTIUM_PCI_DEVICE_ID 0x9a +#define PHYTIUM_PCI_REVISION_ID 0x9c +#define PHYTIUM_PCI_CLASS_PROG 0x9d +#define PHYTIUM_PCI_CLASS_DEVICE 0x9e +#define PHYTIUM_PCI_SUBSYS_VENDOR_ID 0xa0 +#define PHYTIUM_PCI_SUBSYS_DEVICE_ID 0xa2 +#define PHYTIUM_PCI_INTERRUPT_PIN 0xa8 +#define INTERRUPT_PIN_MASK 0x7 +#define MSI_DISABLE (1 << 3) +#define MSI_NUM_MASK (0x7) +#define MSI_NUM_SHIFT 4 +#define MSI_MASK_SUPPORT (1 << 7) +#define PHYTIUM_PCI_MSIX_CAP 0xaa + #define MSIX_DISABLE (0 << 15) + +#define PHYTIUM_PCI_BAR_0 0xe4 +#define PHYTIUM_PCI_BAR(bar_num) (0xe4 + bar_num * 4) +#define BAR_IO_TYPE (1 << 0) +#define BAR_MEM_TYPE (0 << 0) +#define BAR_MEM_64BIT (1 << 2) +#define BAR_MEM_PREFETCHABLE (1 << 3) +#define BAR_IO_MIN_APERTURE 4 +#define BAR_MEM_MIN_APERTURE 16 + + +#define PHYTIUM_PCI_WIN0_BASE 0x600 +#define PHYTIUM_PCI_WIN0_SRC_ADDR0(table) (PHYTIUM_PCI_WIN0_BASE + 0X20 * table + 0x0) +#define ATR_IMPL 0x1 +#define ATR_SIZE_MASK 0x3f +#define ATR_SIZE_SHIFT 1 +#define ATR_SIZE_ALIGN 0x1000 +#define SRC_ADDR_32_12_MASK 0xfffff000 + +#define PHYTIUM_PCI_WIN0_SRC_ADDR1(table) (PHYTIUM_PCI_WIN0_BASE + 0X20 * table + 0x4) +#define PHYTIUM_PCI_WIN0_TRSL_ADDR0(table) (PHYTIUM_PCI_WIN0_BASE + 0X20 * table + 0x8) +#define TRSL_ADDR_32_12_MASK 0xfffff000 + +#define PHYTIUM_PCI_WIN0_TRSL_ADDR1(table) (PHYTIUM_PCI_WIN0_BASE + 0X20 * table + 0xc) +#define PHYTIUM_PCI_WIN0_TRSL_PARAM(table) (PHYTIUM_PCI_WIN0_BASE + 0X20 * table + 0x10) +#define TRSL_ID_IO 0x1 +#define TRSL_ID_MASTER 0x4 +#define TRSL_ID_PCIE_TR 0x0 + +#define PHYTIUM_PCI_SLAVE0_BASE 0x800 +#define PHYTIUM_PCI_SLAVE0_SRC_ADDR0(table) (PHYTIUM_PCI_SLAVE0_BASE + 0X20 * table + 0x0) +#define PHYTIUM_PCI_SLAVE0_SRC_ADDR1(table) (PHYTIUM_PCI_SLAVE0_BASE + 0X20 * table + 0x4) +#define PHYTIUM_PCI_SLAVE0_TRSL_ADDR0(table) (PHYTIUM_PCI_SLAVE0_BASE + 0X20 * table + 0x8) +#define PHYTIUM_PCI_SLAVE0_TRSL_ADDR1(table) (PHYTIUM_PCI_SLAVE0_BASE + 0X20 * table + 0xc) +#define PHYTIUM_PCI_SLAVE0_TRSL_PARAM(table) (PHYTIUM_PCI_SLAVE0_BASE + 0X20 * table + 0x10) + +#define PHYTIUM_PCI_CF_MSI_BASE 0x10e0 +#define PHYTIUM_PCI_CF_MSI_CONTROL 0x10e2 + +#define PHYTIUM_HPB_C0_PREF_BASE_LIMIT 0xa30 + #define C0_PREF_LIMIT_MASK 0xfff + #define C0_PREF_LIMIT_SHIFT 20 + #define C0_PREF_BASE_MASK 0xfff + #define C0_PREF_BASE_SHIFT 4 + #define C0_PREF_VALUE_SHIFT 20 +#define PHYTIUM_HPB_C0_PREF_BASE_LIMIT_UP32 0xa34 + #define C0_PREF_LIMIT_UP32_MASK 0xff + #define C0_PREF_LIMIT_UP32_SHIFT 8 + #define C0_PREF_BASE_UP32_MASK 0xff + #define C0_PREF_BASE_UP32_SHIFT 0 + #define C0_PREF_UP32_VALUE_SHIFT 0 +#endif + + diff --git a/drivers/pci/msi/msi.c b/drivers/pci/msi/msi.c index 8566370b9f3a3ebd5c3a1934d683401df35e9638..161c3ac171a3b7a05197a922e2aeb4c1c4bcaa50 100644 --- a/drivers/pci/msi/msi.c +++ b/drivers/pci/msi/msi.c @@ -408,12 +408,34 @@ static int msi_capability_init(struct pci_dev *dev, int nvec, return ret; } +#ifdef CONFIG_LOONGARCH +static unsigned int pci_irq_numbers = 32; + +static int __init pci_irq_limit(char *str) +{ + get_option(&str, &pci_irq_numbers); + + if (pci_irq_numbers == 0) + pci_irq_numbers = 32; + return 0; +} + +early_param("pci_irq_limit", pci_irq_limit); +#endif + int __pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec, struct irq_affinity *affd) { int nvec; int rc; +#ifdef CONFIG_LOONGARCH + if (maxvec > 32) { + maxvec = pci_irq_numbers; + minvec = min_t(int, pci_irq_numbers, minvec); + } +#endif + if (!pci_msi_supported(dev, minvec) || dev->current_state != PCI_D0) return -EINVAL; @@ -795,6 +817,13 @@ int __pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries, int { int hwsize, rc, nvec = maxvec; +#ifdef CONFIG_LOONGARCH + if (maxvec > 32) { + nvec = pci_irq_numbers; + minvec = min_t(int, pci_irq_numbers, minvec); + } +#endif + if (maxvec < minvec) return -ERANGE; diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 5163923ab13e1a16e1941d8858a647212fc02885..e8f2f50c01226b30c890f019801e84c64e575637 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -620,7 +620,11 @@ 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. */ +#ifdef CONFIG_LOONGARCH + if (kexec_in_progress && !pci_is_bridge(pci_dev) && (pci_dev->current_state <= PCI_D3hot)) +#else if (kexec_in_progress && (pci_dev->current_state <= PCI_D3hot)) +#endif pci_clear_master(pci_dev); } diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 40bedf4e189626a98821ce6163735e54543d70ec..861aa11385c211e195136160a280d8abe2b055cc 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -383,6 +383,20 @@ 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 +static void loongson_pcie_msi_quirk(struct pci_dev *dev) +{ + u16 val; + u16 class; + + class = dev->class >> 8; + if (class == PCI_CLASS_BRIDGE_HOST) { + pci_read_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, &val); + val |= PCI_MSI_FLAGS_ENABLE; + pci_write_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, val); + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, 0x7a59, loongson_pcie_msi_quirk); + /* Chipsets where PCI->PCI transfers vanish or hang */ static void quirk_nopcipci(struct pci_dev *dev) { @@ -5533,6 +5547,7 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, 0x0142, quirk_no_ext_tags); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, 0x0144, quirk_no_ext_tags); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, 0x0420, quirk_no_ext_tags); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, 0x0422, quirk_no_ext_tags); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_PHYTIUM, 0xdc3a, quirk_no_ext_tags); #ifdef CONFIG_PCI_ATS static void quirk_no_ats(struct pci_dev *pdev) diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig index 273d67ecf6d2530f0f31c8dd393b31b1fde560ee..88a6e70ba3ac0bacc0799274e74661b23f60de0c 100644 --- a/drivers/perf/Kconfig +++ b/drivers/perf/Kconfig @@ -234,4 +234,6 @@ config CXL_PMU If unsure say 'm'. +source "drivers/perf/phytium/Kconfig" + endmenu diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile index 16b3ec4db916d0bcb5f3895a82c7d9b1a77e18a2..51603317fcbcf7e5941ba8706e9934d6dfc8733f 100644 --- a/drivers/perf/Makefile +++ b/drivers/perf/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_ALIBABA_UNCORE_DRW_PMU) += alibaba_uncore_drw_pmu.o obj-$(CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += arm_cspmu/ obj-$(CONFIG_MESON_DDR_PMU) += amlogic/ obj-$(CONFIG_CXL_PMU) += cxl_pmu.o +obj-$(CONFIG_PHYTIUM_PMU) += phytium/ diff --git a/drivers/perf/phytium/Kconfig b/drivers/perf/phytium/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..e9dabba7227eb47e4cda5bf935d8868d1eb84a91 --- /dev/null +++ b/drivers/perf/phytium/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0 +menuconfig PHYTIUM_PMU + bool "Phytium PMU support" + help + Say Y here if you want to support Phytium performance monitoring + unit (PMU) drivers. + +if PHYTIUM_PMU + +config PHYT_DDR_PMU + tristate "Phytium SoC DDR PMU driver" + depends on (ARCH_PHYTIUM && ACPI) || COMPILE_TEST + default m + help + Provides support for Phytium SoC DDR Controller performance + monitoring unit (PMU). + +config PHYT_PCIE_PMU + tristate "Phytium SoC PCIE PMU driver" + depends on (ARCH_PHYTIUM && ACPI) || COMPILE_TEST + default m + help + Provides support for Phytium SoC PCIe Controller performance + monitoring unit (PMU). + +endif + diff --git a/drivers/perf/phytium/Makefile b/drivers/perf/phytium/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..af37afc6920c57169c16c053eb3ceb70dff064d2 --- /dev/null +++ b/drivers/perf/phytium/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +# +obj-$(CONFIG_PHYT_DDR_PMU) += phytium_ddr_pmu.o +obj-$(CONFIG_PHYT_PCIE_PMU) += phytium_pcie_pmu.o diff --git a/drivers/perf/phytium/phytium_ddr_pmu.c b/drivers/perf/phytium/phytium_ddr_pmu.c new file mode 100644 index 0000000000000000000000000000000000000000..19f39e1fc3411b933d0b618147b4949a86becae4 --- /dev/null +++ b/drivers/perf/phytium/phytium_ddr_pmu.c @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SoC DDR performance monitoring unit support + * + * Copyright (c) 2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#undef pr_fmt +#define pr_fmt(fmt) "phytium_ddr_pmu: " fmt + +#define PHYTIUM_DDR_MAX_COUNTERS 8 + +#define DDR_START_TIMER 0x000 +#define DDR_STOP_TIMER 0x004 +#define DDR_CLEAR_EVENT 0x008 +#define DDR_SET_TIMER_L 0x00c +#define DDR_SET_TIMER_H 0x010 +#define DDR_TRIG_MODE 0x014 +#define DDR_NOW_STATE 0x0e0 +#define DDR_EVENT_CYCLES 0x0e4 +#define DDR_TPOINT_END_L 0x0e4 +#define DDR_TPOINT_END_H 0x0e8 +#define DDR_STATE_STOP 0x0ec +#define DDR_EVENT_RXREQ 0x100 +#define DDR_EVENT_RXDAT 0x104 +#define DDR_EVENT_TXDAT 0x108 +#define DDR_EVENT_RXREQ_RNS 0x10c +#define DDR_EVENT_RXREQ_WNSP 0x110 +#define DDR_EVENT_RXREQ_WNSF 0x114 +#define DDR_EVENT_BANDWIDTH 0x200 +#define DDR_W_DATA_BASE 0x200 +#define DDR_CLK_FRE 0xe00 +#define DDR_DATA_WIDTH 0xe04 + +#define to_phytium_ddr_pmu(p) (container_of(p, struct phytium_ddr_pmu, pmu)) + +static int phytium_ddr_pmu_hp_state; + +struct phytium_ddr_pmu_hwevents { + struct perf_event *hw_events[PHYTIUM_DDR_MAX_COUNTERS]; + DECLARE_BITMAP(used_mask, PHYTIUM_DDR_MAX_COUNTERS); +}; + +struct phytium_ddr_pmu { + struct device *dev; + void __iomem *base; + void __iomem *csr_base; + struct pmu pmu; + struct phytium_ddr_pmu_hwevents pmu_events; + u32 die_id; + u32 ddr_id; + u32 pmu_id; + int bit_idx; + int on_cpu; + int irq; + struct hlist_node node; +}; + +#define GET_DDR_EVENTID(hwc) (hwc->config_base & 0x7) +#define EVENT_VALID(idx) ((idx >= 0) && (idx < PHYTIUM_DDR_MAX_COUNTERS)) + +static const u32 ddr_counter_reg_offset[] = { + DDR_EVENT_CYCLES, DDR_EVENT_RXREQ, DDR_EVENT_RXDAT, + DDR_EVENT_TXDAT, DDR_EVENT_RXREQ_RNS, DDR_EVENT_RXREQ_WNSP, + DDR_EVENT_RXREQ_WNSF, DDR_EVENT_BANDWIDTH +}; + +ssize_t phytium_ddr_pmu_format_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *eattr; + + eattr = container_of(attr, struct dev_ext_attribute, attr); + + return sprintf(buf, "%s\n", (char *)eattr->var); +} + +ssize_t phytium_ddr_pmu_event_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *page) +{ + struct dev_ext_attribute *eattr; + + eattr = container_of(attr, struct dev_ext_attribute, attr); + + return sprintf(page, "config=0x%lx\n", (unsigned long)eattr->var); +} + +static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct phytium_ddr_pmu *ddr_pmu = + to_phytium_ddr_pmu(dev_get_drvdata(dev)); + + return cpumap_print_to_pagebuf(true, buf, cpumask_of(ddr_pmu->on_cpu)); +} + +#define PHYTIUM_PMU_ATTR(_name, _func, _config) \ + (&((struct dev_ext_attribute[]){ \ + { __ATTR(_name, 0444, _func, NULL), (void *)_config } })[0] \ + .attr.attr) + +#define PHYTIUM_DDR_PMU_FORMAT_ATTR(_name, _config) \ + PHYTIUM_PMU_ATTR(_name, phytium_ddr_pmu_format_sysfs_show, \ + (void *)_config) + +#define PHYTIUM_DDR_PMU_EVENT_ATTR(_name, _config) \ + PHYTIUM_PMU_ATTR(_name, phytium_ddr_pmu_event_sysfs_show, \ + (unsigned long)_config) + +static struct attribute *phytium_ddr_pmu_format_attr[] = { + PHYTIUM_DDR_PMU_FORMAT_ATTR(event, "config:0-2"), + PHYTIUM_DDR_PMU_FORMAT_ATTR(timer, "config1:0-31"), + NULL, +}; + +static const struct attribute_group phytium_ddr_pmu_format_group = { + .name = "format", + .attrs = phytium_ddr_pmu_format_attr, +}; + +static struct attribute *phytium_ddr_pmu_events_attr[] = { + PHYTIUM_DDR_PMU_EVENT_ATTR(cycles, 0x00), + PHYTIUM_DDR_PMU_EVENT_ATTR(rxreq, 0x01), + PHYTIUM_DDR_PMU_EVENT_ATTR(rxdat, 0x02), + PHYTIUM_DDR_PMU_EVENT_ATTR(txdat, 0x03), + PHYTIUM_DDR_PMU_EVENT_ATTR(rxreq_RNS, 0x04), + PHYTIUM_DDR_PMU_EVENT_ATTR(rxreq_WNSP, 0x05), + PHYTIUM_DDR_PMU_EVENT_ATTR(rxreq_WNSF, 0x06), + PHYTIUM_DDR_PMU_EVENT_ATTR(bandwidth, 0x07), + NULL, +}; + +static const struct attribute_group phytium_ddr_pmu_events_group = { + .name = "events", + .attrs = phytium_ddr_pmu_events_attr, +}; + +static DEVICE_ATTR_RO(cpumask); + +static struct attribute *phytium_ddr_pmu_cpumask_attrs[] = { + &dev_attr_cpumask.attr, + NULL, +}; + +static const struct attribute_group phytium_ddr_pmu_cpumask_attr_group = { + .attrs = phytium_ddr_pmu_cpumask_attrs, +}; + +static const struct attribute_group *phytium_ddr_pmu_attr_groups[] = { + &phytium_ddr_pmu_format_group, + &phytium_ddr_pmu_events_group, + &phytium_ddr_pmu_cpumask_attr_group, + NULL, +}; + +static u32 phytium_ddr_pmu_get_event_timer(struct perf_event *event) +{ + return FIELD_GET(GENMASK(31, 0), event->attr.config1); +} + +static u64 phytium_ddr_pmu_read_counter(struct phytium_ddr_pmu *ddr_pmu, + struct hw_perf_event *hwc) +{ + u32 idx = GET_DDR_EVENTID(hwc); + u32 cycle_l, cycle_h, w_data, ddr_data_width; + u64 val64 = 0; + int i; + u32 counter_offset = ddr_counter_reg_offset[idx]; + + if (!EVENT_VALID(idx)) { + dev_err(ddr_pmu->dev, "Unsupported event index:%d!\n", idx); + return 0; + } + + switch (idx) { + case 0: + cycle_l = readl(ddr_pmu->base + counter_offset); + cycle_h = readl(ddr_pmu->base + counter_offset + 4); + val64 = (u64)cycle_h << 32 | (u64)cycle_l; + break; + case 7: + ddr_data_width = readl(ddr_pmu->base + DDR_DATA_WIDTH); + for (i = 0; i < (ddr_data_width / 8); i++) { + w_data = readl(ddr_pmu->base + counter_offset + 4 * i); + val64 += w_data; + } + break; + default: + val64 = readl(ddr_pmu->base + counter_offset); + break; + } + + return val64; +} + +static void phytium_ddr_pmu_enable_clk(struct phytium_ddr_pmu *ddr_pmu) +{ + u32 val; + + val = readl(ddr_pmu->csr_base); + val |= 0xF; + writel(val, ddr_pmu->csr_base); +} + +static void phytium_ddr_pmu_disable_clk(struct phytium_ddr_pmu *ddr_pmu) +{ + u32 val; + + val = readl(ddr_pmu->csr_base); + val &= ~(0xF); + writel(val, ddr_pmu->csr_base); +} + +static void phytium_ddr_pmu_clear_all_counters(struct phytium_ddr_pmu *ddr_pmu) +{ + writel(0x1, ddr_pmu->base + DDR_CLEAR_EVENT); +} + +static void phytium_ddr_pmu_start_all_counters(struct phytium_ddr_pmu *ddr_pmu) +{ + writel(0x1, ddr_pmu->base + DDR_START_TIMER); +} + +static void phytium_ddr_pmu_stop_all_counters(struct phytium_ddr_pmu *ddr_pmu) +{ + writel(0x1, ddr_pmu->base + DDR_STOP_TIMER); +} + +static void phytium_ddr_pmu_set_timer(struct phytium_ddr_pmu *ddr_pmu, + u32 th_val) +{ + u32 val; + + val = readl(ddr_pmu->base + DDR_SET_TIMER_L); + val = readl(ddr_pmu->base + DDR_SET_TIMER_H); + + writel(th_val, ddr_pmu->base + DDR_SET_TIMER_L); + writel(0, ddr_pmu->base + DDR_SET_TIMER_H); +} + +static void phytium_ddr_pmu_reset_timer(struct phytium_ddr_pmu *ddr_pmu) +{ + u32 val; + + val = readl(ddr_pmu->base + DDR_SET_TIMER_L); + val = readl(ddr_pmu->base + DDR_SET_TIMER_H); + + writel(0xFFFFFFFF, ddr_pmu->base + DDR_SET_TIMER_L); + writel(0xFFFFFFFF, ddr_pmu->base + DDR_SET_TIMER_H); +} + +static unsigned long +phytium_ddr_pmu_get_stop_state(struct phytium_ddr_pmu *ddr_pmu) +{ + unsigned long val; + + val = (unsigned long)readl(ddr_pmu->base + DDR_STATE_STOP); + return val; +} + +static unsigned long +phytium_ddr_pmu_get_irq_flag(struct phytium_ddr_pmu *ddr_pmu) +{ + unsigned long val; + + val = (unsigned long)readl(ddr_pmu->csr_base + 4); + return val; +} + +static int phytium_ddr_pmu_mark_event(struct perf_event *event) +{ + struct phytium_ddr_pmu *ddr_pmu = to_phytium_ddr_pmu(event->pmu); + unsigned long *used_mask = ddr_pmu->pmu_events.used_mask; + struct hw_perf_event *hwc = &event->hw; + + int idx = GET_DDR_EVENTID(hwc); + + if (test_bit(idx, used_mask)) + return -EAGAIN; + + set_bit(idx, used_mask); + + return idx; +} + +static void phytium_ddr_pmu_unmark_event(struct phytium_ddr_pmu *ddr_pmu, + int idx) +{ + if (!EVENT_VALID(idx)) { + dev_err(ddr_pmu->dev, "Unsupported event index:%d!\n", idx); + return; + } + + clear_bit(idx, ddr_pmu->pmu_events.used_mask); +} + +int phytium_ddr_pmu_event_init(struct perf_event *event) +{ + struct hw_perf_event *hwc = &event->hw; + struct phytium_ddr_pmu *ddr_pmu; + u32 event_timer; + + if (event->attr.type != event->pmu->type) + return -ENOENT; + + if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK) + return -EOPNOTSUPP; + + ddr_pmu = to_phytium_ddr_pmu(event->pmu); + + if (event->cpu < 0) { + dev_warn(ddr_pmu->dev, "Can't provide per-task data!\n"); + return -EINVAL; + } + + if (event->attr.config > PHYTIUM_DDR_MAX_COUNTERS) + return -EINVAL; + + if (ddr_pmu->on_cpu == -1) + return -EINVAL; + + event_timer = phytium_ddr_pmu_get_event_timer(event); + if (event_timer != 0) + phytium_ddr_pmu_set_timer(ddr_pmu, event_timer); + + hwc->idx = -1; + hwc->config_base = event->attr.config; + + event->cpu = ddr_pmu->on_cpu; + + return 0; +} + +void phytium_ddr_pmu_event_update(struct perf_event *event) +{ + struct phytium_ddr_pmu *ddr_pmu = to_phytium_ddr_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + u64 delta; + + delta = phytium_ddr_pmu_read_counter(ddr_pmu, hwc); + local64_add(delta, &event->count); +} + +void phytium_ddr_pmu_event_start(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + + hwc->state = 0; + perf_event_update_userpage(event); +} + +void phytium_ddr_pmu_event_stop(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + + hwc->state |= PERF_HES_STOPPED; + + if (flags & PERF_EF_UPDATE) + phytium_ddr_pmu_event_update(event); +} + +int phytium_ddr_pmu_event_add(struct perf_event *event, int flags) +{ + struct phytium_ddr_pmu *ddr_pmu = to_phytium_ddr_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + int idx; + + hwc->state |= PERF_HES_STOPPED; + + idx = phytium_ddr_pmu_mark_event(event); + if (idx < 0) + return idx; + + event->hw.idx = idx; + ddr_pmu->pmu_events.hw_events[idx] = event; + + return 0; +} + +void phytium_ddr_pmu_event_del(struct perf_event *event, int flags) +{ + struct phytium_ddr_pmu *ddr_pmu = to_phytium_ddr_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + unsigned long val; + u32 event_timer; + + phytium_ddr_pmu_event_stop(event, PERF_EF_UPDATE); + val = phytium_ddr_pmu_get_irq_flag(ddr_pmu); + val = phytium_ddr_pmu_get_stop_state(ddr_pmu); + phytium_ddr_pmu_unmark_event(ddr_pmu, hwc->idx); + + event_timer = phytium_ddr_pmu_get_event_timer(event); + if (event_timer != 0) + phytium_ddr_pmu_reset_timer(ddr_pmu); + + perf_event_update_userpage(event); + ddr_pmu->pmu_events.hw_events[hwc->idx] = NULL; +} + +void phytium_ddr_pmu_enable(struct pmu *pmu) +{ + struct phytium_ddr_pmu *ddr_pmu = to_phytium_ddr_pmu(pmu); + int event_added = bitmap_weight(ddr_pmu->pmu_events.used_mask, + PHYTIUM_DDR_MAX_COUNTERS); + + if (event_added) { + phytium_ddr_pmu_clear_all_counters(ddr_pmu); + phytium_ddr_pmu_start_all_counters(ddr_pmu); + } +} + +void phytium_ddr_pmu_disable(struct pmu *pmu) +{ + struct phytium_ddr_pmu *ddr_pmu = to_phytium_ddr_pmu(pmu); + int event_added = bitmap_weight(ddr_pmu->pmu_events.used_mask, + PHYTIUM_DDR_MAX_COUNTERS); + + if (event_added) + phytium_ddr_pmu_stop_all_counters(ddr_pmu); +} + +static const struct acpi_device_id phytium_ddr_pmu_acpi_match[] = { + { + "PHYT0043", + }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, phytium_ddr_pmu_acpi_match); + +static irqreturn_t phytium_ddr_pmu_overflow_handler(int irq, void *dev_id) +{ + struct phytium_ddr_pmu *ddr_pmu = dev_id; + struct perf_event *event; + unsigned long overflown, stop_state; + int idx; + unsigned long *used_mask = ddr_pmu->pmu_events.used_mask; + + int event_added = bitmap_weight(used_mask, PHYTIUM_DDR_MAX_COUNTERS); + + overflown = phytium_ddr_pmu_get_irq_flag(ddr_pmu); + + if (!test_bit(ddr_pmu->bit_idx, &overflown)) + return IRQ_NONE; + + stop_state = phytium_ddr_pmu_get_stop_state(ddr_pmu); + + if (bitmap_weight(&stop_state, 6)) { + for_each_set_bit(idx, used_mask, PHYTIUM_DDR_MAX_COUNTERS) { + event = ddr_pmu->pmu_events.hw_events[idx]; + if (!event) + continue; + phytium_ddr_pmu_event_update(event); + } + phytium_ddr_pmu_clear_all_counters(ddr_pmu); + phytium_ddr_pmu_start_all_counters(ddr_pmu); + + return IRQ_HANDLED; + } + + if (!event_added) { + phytium_ddr_pmu_clear_all_counters(ddr_pmu); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int phytium_ddr_pmu_init_irq(struct phytium_ddr_pmu *ddr_pmu, + struct platform_device *pdev) +{ + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, + phytium_ddr_pmu_overflow_handler, + IRQF_NOBALANCING | IRQF_NO_THREAD | IRQF_SHARED, + dev_name(&pdev->dev), ddr_pmu); + if (ret < 0) { + dev_err(&pdev->dev, "Fail to request IRQ:%d ret:%d\n", irq, + ret); + return ret; + } + + ddr_pmu->irq = irq; + + return 0; +} + +static int phytium_ddr_pmu_init_data(struct platform_device *pdev, + struct phytium_ddr_pmu *ddr_pmu) +{ + struct resource *res, *clkres; + + if (device_property_read_u32(&pdev->dev, "phytium,die-id", + &ddr_pmu->die_id)) { + dev_err(&pdev->dev, "Can not read phytium,die-id!\n"); + return -EINVAL; + } + + if (device_property_read_u32(&pdev->dev, "phytium,ddr-id", + &ddr_pmu->ddr_id)) { + dev_err(&pdev->dev, "Can not read phytium,ddr-id!\n"); + return -EINVAL; + } + + if (device_property_read_u32(&pdev->dev, "phytium,pmu-id", + &ddr_pmu->pmu_id)) { + dev_err(&pdev->dev, "Can not read ddr pmu-id!\n"); + return -EINVAL; + } + + ddr_pmu->bit_idx = ddr_pmu->ddr_id * 2 + ddr_pmu->pmu_id; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ddr_pmu->base = devm_ioremap_resource(&pdev->dev, res); + + if (IS_ERR(ddr_pmu->base)) { + dev_err(&pdev->dev, + "ioremap failed for ddr_pmu base resource\n"); + return PTR_ERR(ddr_pmu->base); + } + + clkres = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!clkres) { + dev_err(&pdev->dev, "failed for get ddr_pmu clk resource.\n"); + return -EINVAL; + } + + ddr_pmu->csr_base = + devm_ioremap(&pdev->dev, clkres->start, resource_size(clkres)); + if (IS_ERR(ddr_pmu->csr_base)) { + dev_err(&pdev->dev, + "ioremap failed for ddr_pmu csr resource\n"); + return PTR_ERR(ddr_pmu->csr_base); + } + + return 0; +} + +static int phytium_ddr_pmu_dev_probe(struct platform_device *pdev, + struct phytium_ddr_pmu *ddr_pmu) +{ + int ret; + + ret = phytium_ddr_pmu_init_data(pdev, ddr_pmu); + if (ret) + return ret; + + ret = phytium_ddr_pmu_init_irq(ddr_pmu, pdev); + if (ret) + return ret; + + ddr_pmu->dev = &pdev->dev; + ddr_pmu->on_cpu = raw_smp_processor_id(); + WARN_ON(irq_set_affinity(ddr_pmu->irq, cpumask_of(ddr_pmu->on_cpu))); + + return 0; +} + +static int phytium_ddr_pmu_probe(struct platform_device *pdev) +{ + struct phytium_ddr_pmu *ddr_pmu; + char *name; + int ret; + + ddr_pmu = devm_kzalloc(&pdev->dev, sizeof(*ddr_pmu), GFP_KERNEL); + if (!ddr_pmu) + return -ENOMEM; + + platform_set_drvdata(pdev, ddr_pmu); + + ret = phytium_ddr_pmu_dev_probe(pdev, ddr_pmu); + if (ret) + return ret; + + ret = cpuhp_state_add_instance_nocalls( + phytium_ddr_pmu_hp_state, &ddr_pmu->node); + if (ret) { + dev_err(&pdev->dev, "Error %d registering hotplug;\n", ret); + return ret; + } + + name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "phyt%u_ddr%u_pmu%u", + ddr_pmu->die_id, ddr_pmu->ddr_id, + ddr_pmu->pmu_id); + ddr_pmu->pmu = (struct pmu){ + .name = name, + .module = THIS_MODULE, + .task_ctx_nr = perf_invalid_context, + .event_init = phytium_ddr_pmu_event_init, + .pmu_enable = phytium_ddr_pmu_enable, + .pmu_disable = phytium_ddr_pmu_disable, + .add = phytium_ddr_pmu_event_add, + .del = phytium_ddr_pmu_event_del, + .start = phytium_ddr_pmu_event_start, + .stop = phytium_ddr_pmu_event_stop, + .read = phytium_ddr_pmu_event_update, + .attr_groups = phytium_ddr_pmu_attr_groups, + .capabilities = PERF_PMU_CAP_NO_EXCLUDE, + }; + + ret = perf_pmu_register(&ddr_pmu->pmu, name, -1); + if (ret) { + dev_err(ddr_pmu->dev, "DDR PMU register failed!\n"); + cpuhp_state_remove_instance_nocalls( + phytium_ddr_pmu_hp_state, + &ddr_pmu->node); + } + + phytium_ddr_pmu_enable_clk(ddr_pmu); + + pr_info("Phytium DDR PMU: "); + pr_info(" die_id = %d ddr_id = %d pmu_id = %d.\n", ddr_pmu->die_id, + ddr_pmu->ddr_id, ddr_pmu->pmu_id); + + return ret; +} + +static int phytium_ddr_pmu_remove(struct platform_device *pdev) +{ + struct phytium_ddr_pmu *ddr_pmu = platform_get_drvdata(pdev); + + phytium_ddr_pmu_disable_clk(ddr_pmu); + + perf_pmu_unregister(&ddr_pmu->pmu); + cpuhp_state_remove_instance_nocalls( + phytium_ddr_pmu_hp_state, &ddr_pmu->node); + + return 0; +} + +static struct platform_driver phytium_ddr_pmu_driver = { + .driver = { + .name = "phytium_ddr_pmu", + .acpi_match_table = ACPI_PTR(phytium_ddr_pmu_acpi_match), + .suppress_bind_attrs = true, + }, + .probe = phytium_ddr_pmu_probe, + .remove = phytium_ddr_pmu_remove, +}; + +int phytium_ddr_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node) +{ + struct phytium_ddr_pmu *ddr_pmu = + hlist_entry_safe(node, struct phytium_ddr_pmu, node); + unsigned int target; + cpumask_t available_cpus; + + if (ddr_pmu->on_cpu != cpu) + return 0; + + cpumask_and(&available_cpus, + cpumask_of_node(ddr_pmu->die_id), cpu_online_mask); + target = cpumask_any_but(&available_cpus, cpu); + if (target >= nr_cpu_ids) { + target = cpumask_any_but(cpu_online_mask, cpu); + if (target >= nr_cpu_ids) + return 0; + } + + perf_pmu_migrate_context(&ddr_pmu->pmu, cpu, target); + WARN_ON(irq_set_affinity(ddr_pmu->irq, cpumask_of(target))); + ddr_pmu->on_cpu = target; + + return 0; +} + +static int __init phytium_ddr_pmu_module_init(void) +{ + int ret; + + phytium_ddr_pmu_hp_state = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, + "perf/phytium/ddrpmu:offline", + NULL, phytium_ddr_pmu_offline_cpu); + if (phytium_ddr_pmu_hp_state < 0) { + pr_err("DDR PMU: setup hotplug, phytium_ddr_pmu_hp_state = %d\n", + phytium_ddr_pmu_hp_state); + return phytium_ddr_pmu_hp_state; + } + + ret = platform_driver_register(&phytium_ddr_pmu_driver); + if (ret) + cpuhp_remove_multi_state( + phytium_ddr_pmu_hp_state); + + return ret; +} +module_init(phytium_ddr_pmu_module_init); + +static void __exit phytium_ddr_pmu_module_exit(void) +{ + platform_driver_unregister(&phytium_ddr_pmu_driver); + cpuhp_remove_multi_state(phytium_ddr_pmu_hp_state); +} +module_exit(phytium_ddr_pmu_module_exit); + +MODULE_DESCRIPTION("Phytium DDR PMU driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Hu Xianghua "); diff --git a/drivers/perf/phytium/phytium_pcie_pmu.c b/drivers/perf/phytium/phytium_pcie_pmu.c new file mode 100644 index 0000000000000000000000000000000000000000..72abf3dcfc75af1f4cbd6c53b0602548ac6bbfe2 --- /dev/null +++ b/drivers/perf/phytium/phytium_pcie_pmu.c @@ -0,0 +1,873 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium Soc PCIe performance monitoring unit support + * + * Copyright (c) 2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#undef pr_fmt +#define pr_fmt(fmt) "phytium_pcie_pmu: " fmt + +#define PHYTIUM_PCIE_MAX_COUNTERS 18 + +#define PCIE_START_TIMER 0x000 +#define PCIE_STOP_TIMER 0x004 +#define PCIE_CLEAR_EVENT 0x008 +#define PCIE_SET_TIMER_L 0x00c +#define PCIE_SET_TIMER_H 0x010 +#define PCIE_TRIG_MODE 0x014 + +#define PCIE_NOW_STATE 0x0e0 +#define PCIE_EVENT_CYCLES 0x0e4 +#define PCIE_TPOINT_END_L 0x0e4 +#define PCIE_TPOINT_END_H 0x0e8 +#define PCIE_STATE_STOP 0x0ec + +#define PCIE_EVENT_AW 0x100 +#define PCIE_EVENT_W_LAST 0x104 +#define PCIE_EVENT_B 0x108 +#define PCIE_EVENT_AR 0x10c +#define PCIE_EVENT_R_LAST 0x110 +#define PCIE_EVENT_R_FULL 0x114 +#define PCIE_EVENT_R_ERR 0x118 +#define PCIE_EVENT_W_ERR 0x11c +#define PCIE_EVENT_DELAY_RD 0x120 +#define PCIE_EVENT_DELAY_WR 0x124 +#define PCIE_EVENT_RD_MAX 0x128 +#define PCIE_EVENT_RD_MIN 0x12c +#define PCIE_EVENT_WR_MAX 0x130 +#define PCIE_EVENT_WR_MIN 0x134 + +#define PCIE_EVENT_W_DATA 0x200 +#define PCIE_W_DATA_BASE 0x200 + +#define PCIE_EVENT_RDELAY_TIME 0x300 +#define PCIE_RDELAY_TIME_BASE 0x300 + +#define PCIE_EVENT_WDELAY_TIME 0x700 +#define PCIE_WDELAY_TIME_BASE 0x700 + +#define PCIE_CLK_FRE 0xe00 +#define PCIE_DATA_WIDTH 0xe04 + +#define to_phytium_pcie_pmu(p) (container_of(p, struct phytium_pcie_pmu, pmu)) + +static int phytium_pcie_pmu_hp_state; + +struct phytium_pcie_pmu_hwevents { + struct perf_event *hw_events[PHYTIUM_PCIE_MAX_COUNTERS]; + DECLARE_BITMAP(used_mask, PHYTIUM_PCIE_MAX_COUNTERS); +}; + +struct phytium_pcie_pmu { + struct device *dev; + void __iomem *base; + void __iomem *csr_base; + void __iomem *irq_reg; + struct pmu pmu; + struct phytium_pcie_pmu_hwevents pmu_events; + u32 die_id; + u32 pmu_id; + int on_cpu; + int irq; + struct hlist_node node; + int ctrler_id; + int real_ctrler; + u32 clk_bits; +}; + +#define GET_PCIE_EVENTID(hwc) (hwc->config_base & 0x1F) + +#define EVENT_VALID(idx) ((idx >= 0) && (idx < PHYTIUM_PCIE_MAX_COUNTERS)) + +static const u32 pcie_counter_reg_offset[] = { + PCIE_EVENT_CYCLES, PCIE_EVENT_AW, PCIE_EVENT_W_LAST, + PCIE_EVENT_B, PCIE_EVENT_AR, PCIE_EVENT_R_LAST, + PCIE_EVENT_R_FULL, PCIE_EVENT_R_ERR, PCIE_EVENT_W_ERR, + PCIE_EVENT_DELAY_RD, PCIE_EVENT_DELAY_WR, PCIE_EVENT_RD_MAX, + PCIE_EVENT_RD_MIN, PCIE_EVENT_WR_MAX, PCIE_EVENT_WR_MIN, + PCIE_EVENT_W_DATA, PCIE_EVENT_RDELAY_TIME, PCIE_EVENT_WDELAY_TIME +}; + +ssize_t phytium_pcie_pmu_format_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *eattr; + + eattr = container_of(attr, struct dev_ext_attribute, attr); + + return sprintf(buf, "%s\n", (char *)eattr->var); +} + +ssize_t phytium_pcie_pmu_event_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *page) +{ + struct dev_ext_attribute *eattr; + + eattr = container_of(attr, struct dev_ext_attribute, attr); + + return sprintf(page, "config=0x%lx\n", (unsigned long)eattr->var); +} + +static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct phytium_pcie_pmu *pcie_pmu = + to_phytium_pcie_pmu(dev_get_drvdata(dev)); + + return cpumap_print_to_pagebuf(true, buf, cpumask_of(pcie_pmu->on_cpu)); +} + +#define PHYTIUM_PMU_ATTR(_name, _func, _config) \ + (&((struct dev_ext_attribute[]){ \ + { __ATTR(_name, 0444, _func, NULL), (void *)_config } })[0] \ + .attr.attr) + +#define PHYTIUM_PCIE_PMU_FORMAT_ATTR(_name, _config) \ + PHYTIUM_PMU_ATTR(_name, phytium_pcie_pmu_format_sysfs_show, \ + (void *)_config) + +#define PHYTIUM_PCIE_PMU_EVENT_ATTR(_name, _config) \ + PHYTIUM_PMU_ATTR(_name, phytium_pcie_pmu_event_sysfs_show, \ + (unsigned long)_config) + +static struct attribute *phytium_pcie_pmu_format_attr[] = { + PHYTIUM_PCIE_PMU_FORMAT_ATTR(event, "config:0-4"), + PHYTIUM_PCIE_PMU_FORMAT_ATTR(ctrler, "config:8-10"), + PHYTIUM_PCIE_PMU_FORMAT_ATTR(timer, "config1:0-31"), + NULL, +}; + +static const struct attribute_group phytium_pcie_pmu_format_group = { + .name = "format", + .attrs = phytium_pcie_pmu_format_attr, +}; + +static struct attribute *phytium_pcie_pmu_events_attr[] = { + PHYTIUM_PCIE_PMU_EVENT_ATTR(cycles, 0x00), + PHYTIUM_PCIE_PMU_EVENT_ATTR(aw, 0x01), + PHYTIUM_PCIE_PMU_EVENT_ATTR(w_last, 0x02), + PHYTIUM_PCIE_PMU_EVENT_ATTR(b, 0x03), + PHYTIUM_PCIE_PMU_EVENT_ATTR(ar, 0x04), + PHYTIUM_PCIE_PMU_EVENT_ATTR(r_last, 0x05), + PHYTIUM_PCIE_PMU_EVENT_ATTR(r_full, 0x06), + PHYTIUM_PCIE_PMU_EVENT_ATTR(r_err, 0x07), + PHYTIUM_PCIE_PMU_EVENT_ATTR(w_err, 0x08), + PHYTIUM_PCIE_PMU_EVENT_ATTR(delay_rd, 0x09), + PHYTIUM_PCIE_PMU_EVENT_ATTR(delay_wr, 0x0a), + PHYTIUM_PCIE_PMU_EVENT_ATTR(rd_max, 0x0b), + PHYTIUM_PCIE_PMU_EVENT_ATTR(rd_min, 0x0c), + PHYTIUM_PCIE_PMU_EVENT_ATTR(wr_max, 0x0d), + PHYTIUM_PCIE_PMU_EVENT_ATTR(wr_min, 0x0e), + PHYTIUM_PCIE_PMU_EVENT_ATTR(w_data, 0x0f), + PHYTIUM_PCIE_PMU_EVENT_ATTR(rdelay_time, 0x10), + PHYTIUM_PCIE_PMU_EVENT_ATTR(wdelay_time, 0x11), + NULL, +}; + +static const struct attribute_group phytium_pcie_pmu_events_group = { + .name = "events", + .attrs = phytium_pcie_pmu_events_attr, +}; + +static DEVICE_ATTR_RO(cpumask); + +static struct attribute *phytium_pcie_pmu_cpumask_attrs[] = { + &dev_attr_cpumask.attr, + NULL, +}; + +static const struct attribute_group phytium_pcie_pmu_cpumask_attr_group = { + .attrs = phytium_pcie_pmu_cpumask_attrs, +}; + +static const struct attribute_group *phytium_pcie_pmu_attr_groups[] = { + &phytium_pcie_pmu_format_group, + &phytium_pcie_pmu_events_group, + &phytium_pcie_pmu_cpumask_attr_group, + NULL, +}; + +static u32 phytium_pcie_pmu_get_event_ctrler(struct perf_event *event) +{ + return FIELD_GET(GENMASK(10, 8), event->attr.config); +} + +static u32 phytium_pcie_pmu_get_event_timer(struct perf_event *event) +{ + return FIELD_GET(GENMASK(31, 0), event->attr.config1); +} + +static u64 phytium_pcie_pmu_read_counter(struct phytium_pcie_pmu *pcie_pmu, + struct hw_perf_event *hwc) +{ + u32 idx = GET_PCIE_EVENTID(hwc); + u32 cycle_l, cycle_h, rdelay_l, rdelay_h, w_data, wdelay_l, wdelay_h, + pcie_data_width; + u64 val64 = 0; + int i; + u32 counter_offset = pcie_counter_reg_offset[idx]; + + if (!EVENT_VALID(idx)) { + dev_err(pcie_pmu->dev, "Unsupported event index:%d!\n", idx); + return 0; + } + + switch (idx) { + case 0: + cycle_l = readl(pcie_pmu->base + counter_offset); + cycle_h = readl(pcie_pmu->base + counter_offset + 4); + val64 = (u64)cycle_h << 32 | (u64)cycle_l; + break; + case 15: + pcie_data_width = readl(pcie_pmu->base + PCIE_DATA_WIDTH); + for (i = 0; i < (pcie_data_width / 8); i++) { + w_data = readl(pcie_pmu->base + counter_offset + 4 * i); + val64 += w_data; + } + break; + case 16: + for (i = 0; i <= 127; i = i + 2) { + rdelay_l = + readl(pcie_pmu->base + counter_offset + 4 * i); + rdelay_h = readl(pcie_pmu->base + counter_offset + + 4 * i + 4); + val64 += (u64)rdelay_h << 32 | (u64)rdelay_l; + } + break; + case 17: + for (i = 0; i <= 63; i++) { + wdelay_l = + readl(pcie_pmu->base + counter_offset + 4 * i); + wdelay_h = readl(pcie_pmu->base + counter_offset + + 4 * i + 4); + val64 += (u64)wdelay_h << 32 | (u64)wdelay_l; + } + break; + default: + val64 = readl(pcie_pmu->base + counter_offset); + break; + } + return val64; +} + +static void phytium_pcie_pmu_enable_clk(struct phytium_pcie_pmu *pcie_pmu) +{ + u32 val; + + val = readl(pcie_pmu->csr_base); + val |= (pcie_pmu->clk_bits); + writel(val, pcie_pmu->csr_base); +} + +static void phytium_pcie_pmu_disable_clk(struct phytium_pcie_pmu *pcie_pmu) +{ + u32 val; + + val = readl(pcie_pmu->csr_base); + val &= ~(pcie_pmu->clk_bits); + writel(val, pcie_pmu->csr_base); +} + +static void phytium_pcie_pmu_select_ctrler(struct phytium_pcie_pmu *pcie_pmu) +{ + u32 val, offset = 0; + + if (pcie_pmu->pmu_id != 2) + offset = 0xc; + + val = readl(pcie_pmu->csr_base + offset); + + if (pcie_pmu->pmu_id == 2) { + val &= 0xffffffcf; + val |= pcie_pmu->real_ctrler; + } else { + val &= 0xfffffffc; + val |= pcie_pmu->real_ctrler; + } + + writel(val, pcie_pmu->csr_base + offset); +} + +static void +phytium_pcie_pmu_clear_all_counters(struct phytium_pcie_pmu *pcie_pmu) +{ + writel(0x1, pcie_pmu->base + PCIE_CLEAR_EVENT); +} + +static void +phytium_pcie_pmu_start_all_counters(struct phytium_pcie_pmu *pcie_pmu) +{ + writel(0x1, pcie_pmu->base + PCIE_START_TIMER); +} + +static void +phytium_pcie_pmu_stop_all_counters(struct phytium_pcie_pmu *pcie_pmu) +{ + writel(0x1, pcie_pmu->base + PCIE_STOP_TIMER); +} + +static void phytium_pcie_pmu_set_timer(struct phytium_pcie_pmu *pcie_pmu, + u32 th_val) +{ + u32 val; + + val = readl(pcie_pmu->base + PCIE_SET_TIMER_L); + val = readl(pcie_pmu->base + PCIE_SET_TIMER_H); + + writel(th_val, pcie_pmu->base + PCIE_SET_TIMER_L); + writel(0, pcie_pmu->base + PCIE_SET_TIMER_H); +} + +static void phytium_pcie_pmu_reset_timer(struct phytium_pcie_pmu *pcie_pmu) +{ + u32 val; + + val = readl(pcie_pmu->base + PCIE_SET_TIMER_L); + val = readl(pcie_pmu->base + PCIE_SET_TIMER_H); + + writel(0xFFFFFFFF, pcie_pmu->base + PCIE_SET_TIMER_L); + writel(0xFFFFFFFF, pcie_pmu->base + PCIE_SET_TIMER_H); +} + +static unsigned long +phytium_pcie_pmu_get_stop_state(struct phytium_pcie_pmu *pcie_pmu) +{ + unsigned long val; + + val = (unsigned long)readl(pcie_pmu->base + PCIE_STATE_STOP); + return val; +} + +static unsigned long +phytium_pcie_pmu_get_irq_flag(struct phytium_pcie_pmu *pcie_pmu) +{ + unsigned long val; + + val = (unsigned long)readl(pcie_pmu->irq_reg); + return val; +} + +static int phytium_pcie_pmu_mark_event(struct perf_event *event) +{ + struct phytium_pcie_pmu *pcie_pmu = to_phytium_pcie_pmu(event->pmu); + unsigned long *used_mask = pcie_pmu->pmu_events.used_mask; + struct hw_perf_event *hwc = &event->hw; + + int idx = GET_PCIE_EVENTID(hwc); + + if (test_bit(idx, used_mask)) + return -EAGAIN; + + set_bit(idx, used_mask); + + return idx; +} + +static void phytium_pcie_pmu_unmark_event(struct phytium_pcie_pmu *pcie_pmu, + int idx) +{ + if (!EVENT_VALID(idx)) { + dev_err(pcie_pmu->dev, "Unsupported event index:%d!\n", idx); + return; + } + + clear_bit(idx, pcie_pmu->pmu_events.used_mask); +} + +int phytium_pcie_pmu_event_init(struct perf_event *event) +{ + struct hw_perf_event *hwc = &event->hw; + struct phytium_pcie_pmu *pcie_pmu; + u32 event_ctrler, event_timer; + + if (event->attr.type != event->pmu->type) + return -ENOENT; + + if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK) + return -EOPNOTSUPP; + + pcie_pmu = to_phytium_pcie_pmu(event->pmu); + + if (event->cpu < 0) { + dev_warn(pcie_pmu->dev, "Can't provide per-task data!\n"); + return -EINVAL; + } + + if ((event->attr.config & 0x1F) > PHYTIUM_PCIE_MAX_COUNTERS) + return -EINVAL; + + if (pcie_pmu->on_cpu == -1) + return -EINVAL; + + event_timer = phytium_pcie_pmu_get_event_timer(event); + if (event_timer != 0) + phytium_pcie_pmu_set_timer(pcie_pmu, event_timer); + + event_ctrler = phytium_pcie_pmu_get_event_ctrler(event); + switch (pcie_pmu->pmu_id) { + case 0: + if (event_ctrler != 0) { + dev_warn(pcie_pmu->dev, + "Wrong ctrler id(%d) for pcie-pmu0!\n", + event_ctrler); + return -EINVAL; + } + break; + case 1: + if ((event_ctrler < 1) || (event_ctrler > 3)) { + dev_warn(pcie_pmu->dev, + "Wrong ctrler id(%d) for pcie-pmu1!\n", + event_ctrler); + return -EINVAL; + } + break; + case 2: + if ((event_ctrler < 4) || (event_ctrler > 7)) { + dev_warn(pcie_pmu->dev, + "Wrong ctrler id(%d) for pcie-pmu2!\n", + event_ctrler); + return -EINVAL; + } + break; + default: + dev_err(pcie_pmu->dev, "Unsupported pmu id:%d!\n", + pcie_pmu->pmu_id); + return -EINVAL; + } + + pcie_pmu->ctrler_id = event_ctrler; + switch (pcie_pmu->pmu_id) { + case 0: + case 1: + pcie_pmu->real_ctrler = pcie_pmu->ctrler_id; + break; + case 2: + pcie_pmu->real_ctrler = (pcie_pmu->ctrler_id - 4) * 16; + break; + default: + dev_err(pcie_pmu->dev, "Unsupported pmu id:%d!\n", + pcie_pmu->pmu_id); + return -EINVAL; + } + phytium_pcie_pmu_select_ctrler(pcie_pmu); + + hwc->idx = -1; + hwc->config_base = event->attr.config; + + event->cpu = pcie_pmu->on_cpu; + return 0; +} + +void phytium_pcie_pmu_event_update(struct perf_event *event) +{ + struct phytium_pcie_pmu *pcie_pmu = to_phytium_pcie_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + u64 delta; + + delta = phytium_pcie_pmu_read_counter(pcie_pmu, hwc); + local64_add(delta, &event->count); +} + +void phytium_pcie_pmu_event_start(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + + hwc->state = 0; + perf_event_update_userpage(event); +} + +void phytium_pcie_pmu_event_stop(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + + hwc->state |= PERF_HES_STOPPED; + + if (flags & PERF_EF_UPDATE) + phytium_pcie_pmu_event_update(event); +} + +int phytium_pcie_pmu_event_add(struct perf_event *event, int flags) +{ + struct phytium_pcie_pmu *pcie_pmu = to_phytium_pcie_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + int idx; + + hwc->state |= PERF_HES_STOPPED; + + idx = phytium_pcie_pmu_mark_event(event); + if (idx < 0) + return idx; + + event->hw.idx = idx; + pcie_pmu->pmu_events.hw_events[idx] = event; + + return 0; +} + +void phytium_pcie_pmu_event_del(struct perf_event *event, int flags) +{ + struct phytium_pcie_pmu *pcie_pmu = to_phytium_pcie_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + unsigned long val; + u32 event_timer; + + phytium_pcie_pmu_event_stop(event, PERF_EF_UPDATE); + val = phytium_pcie_pmu_get_irq_flag(pcie_pmu); + val = phytium_pcie_pmu_get_stop_state(pcie_pmu); + phytium_pcie_pmu_unmark_event(pcie_pmu, hwc->idx); + + event_timer = phytium_pcie_pmu_get_event_timer(event); + if (event_timer != 0) + phytium_pcie_pmu_reset_timer(pcie_pmu); + + perf_event_update_userpage(event); + pcie_pmu->pmu_events.hw_events[hwc->idx] = NULL; +} + +void phytium_pcie_pmu_enable(struct pmu *pmu) +{ + struct phytium_pcie_pmu *pcie_pmu = to_phytium_pcie_pmu(pmu); + int event_added = bitmap_weight(pcie_pmu->pmu_events.used_mask, + PHYTIUM_PCIE_MAX_COUNTERS); + + if (event_added) { + phytium_pcie_pmu_clear_all_counters(pcie_pmu); + phytium_pcie_pmu_start_all_counters(pcie_pmu); + } +} + +void phytium_pcie_pmu_disable(struct pmu *pmu) +{ + struct phytium_pcie_pmu *pcie_pmu = to_phytium_pcie_pmu(pmu); + int event_added = bitmap_weight(pcie_pmu->pmu_events.used_mask, + PHYTIUM_PCIE_MAX_COUNTERS); + + if (event_added) + phytium_pcie_pmu_stop_all_counters(pcie_pmu); +} + +static const struct acpi_device_id phytium_pcie_pmu_acpi_match[] = { + { + "PHYT0044", + }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, phytium_pcie_pmu_acpi_match); + +static irqreturn_t phytium_pcie_pmu_overflow_handler(int irq, void *dev_id) +{ + struct phytium_pcie_pmu *pcie_pmu = dev_id; + struct perf_event *event; + unsigned long overflown, stop_state; + int idx; + unsigned long *used_mask = pcie_pmu->pmu_events.used_mask; + int event_added = bitmap_weight(used_mask, PHYTIUM_PCIE_MAX_COUNTERS); + + overflown = phytium_pcie_pmu_get_irq_flag(pcie_pmu); + + if (!test_bit(pcie_pmu->pmu_id + 4, &overflown)) + return IRQ_NONE; + + stop_state = phytium_pcie_pmu_get_stop_state(pcie_pmu); + + if (bitmap_weight(&stop_state, 6)) { + for_each_set_bit(idx, used_mask, PHYTIUM_PCIE_MAX_COUNTERS) { + event = pcie_pmu->pmu_events.hw_events[idx]; + if (!event) + continue; + phytium_pcie_pmu_event_update(event); + } + phytium_pcie_pmu_clear_all_counters(pcie_pmu); + phytium_pcie_pmu_start_all_counters(pcie_pmu); + + return IRQ_HANDLED; + } + if (!event_added) { + phytium_pcie_pmu_clear_all_counters(pcie_pmu); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static int phytium_pcie_pmu_init_irq(struct phytium_pcie_pmu *pcie_pmu, + struct platform_device *pdev) +{ + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, + phytium_pcie_pmu_overflow_handler, + IRQF_NOBALANCING | IRQF_NO_THREAD | IRQF_SHARED, + dev_name(&pdev->dev), pcie_pmu); + if (ret < 0) { + dev_err(&pdev->dev, "Fail to request IRQ:%d ret:%d\n", irq, + ret); + return ret; + } + + pcie_pmu->irq = irq; + + return 0; +} + +static int phytium_pcie_pmu_init_data(struct platform_device *pdev, + struct phytium_pcie_pmu *pcie_pmu) +{ + struct resource *res, *clkres, *irqres; + + if (device_property_read_u32(&pdev->dev, "phytium,die-id", + &pcie_pmu->die_id)) { + dev_err(&pdev->dev, "Can not read phytium,die-id!\n"); + return -EINVAL; + } + + if (device_property_read_u32(&pdev->dev, "phytium,pmu-id", + &pcie_pmu->pmu_id)) { + dev_err(&pdev->dev, "Can not read phytium,pmu-id!\n"); + return -EINVAL; + } + + switch (pcie_pmu->pmu_id) { + case 0: + pcie_pmu->clk_bits = 0x1; + break; + case 1: + pcie_pmu->clk_bits = 0xe; + break; + case 2: + pcie_pmu->clk_bits = 0xf; + break; + default: + dev_err(&pdev->dev, "Unsupported pmu id:%d!\n", + pcie_pmu->pmu_id); + break; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pcie_pmu->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pcie_pmu->base)) { + dev_err(&pdev->dev, "ioremap failed for pcie_pmu resource\n"); + return PTR_ERR(pcie_pmu->base); + } + + clkres = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!clkres) { + dev_err(&pdev->dev, "failed for get pcie_pmu clk resource.\n"); + return -EINVAL; + } + + pcie_pmu->csr_base = + devm_ioremap(&pdev->dev, clkres->start, resource_size(clkres)); + if (IS_ERR(pcie_pmu->csr_base)) { + dev_err(&pdev->dev, + "ioremap failed for pcie_pmu csr resource\n"); + return PTR_ERR(pcie_pmu->csr_base); + } + + irqres = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (!irqres) { + dev_err(&pdev->dev, + "failed for get pcie_pmu irq reg resource.\n"); + return -EINVAL; + } + + pcie_pmu->irq_reg = + devm_ioremap(&pdev->dev, irqres->start, resource_size(irqres)); + if (IS_ERR(pcie_pmu->irq_reg)) { + dev_err(&pdev->dev, + "ioremap failed for pcie_pmu irq resource\n"); + return PTR_ERR(pcie_pmu->irq_reg); + } + + return 0; +} + +static int phytium_pcie_pmu_dev_probe(struct platform_device *pdev, + struct phytium_pcie_pmu *pcie_pmu) +{ + int ret; + + ret = phytium_pcie_pmu_init_data(pdev, pcie_pmu); + if (ret) + return ret; + + ret = phytium_pcie_pmu_init_irq(pcie_pmu, pdev); + if (ret) + return ret; + pcie_pmu->dev = &pdev->dev; + pcie_pmu->on_cpu = raw_smp_processor_id(); + WARN_ON(irq_set_affinity(pcie_pmu->irq, cpumask_of(pcie_pmu->on_cpu))); + pcie_pmu->ctrler_id = -1; + + return 0; +} + +static int phytium_pcie_pmu_probe(struct platform_device *pdev) +{ + struct phytium_pcie_pmu *pcie_pmu; + char *name; + int ret; + + pcie_pmu = devm_kzalloc(&pdev->dev, sizeof(*pcie_pmu), GFP_KERNEL); + if (!pcie_pmu) + return -ENOMEM; + + platform_set_drvdata(pdev, pcie_pmu); + + ret = phytium_pcie_pmu_dev_probe(pdev, pcie_pmu); + if (ret) + return ret; + + ret = cpuhp_state_add_instance_nocalls( + phytium_pcie_pmu_hp_state, &pcie_pmu->node); + if (ret) { + dev_err(&pdev->dev, "Error %d registering hotplug;\n", ret); + return ret; + } + + name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "phyt%u_pcie_pmu%u", + pcie_pmu->die_id, pcie_pmu->pmu_id); + pcie_pmu->pmu = (struct pmu){ + .name = name, + .module = THIS_MODULE, + .task_ctx_nr = perf_invalid_context, + .event_init = phytium_pcie_pmu_event_init, + .pmu_enable = phytium_pcie_pmu_enable, + .pmu_disable = phytium_pcie_pmu_disable, + .add = phytium_pcie_pmu_event_add, + .del = phytium_pcie_pmu_event_del, + .start = phytium_pcie_pmu_event_start, + .stop = phytium_pcie_pmu_event_stop, + .read = phytium_pcie_pmu_event_update, + .attr_groups = phytium_pcie_pmu_attr_groups, + .capabilities = PERF_PMU_CAP_NO_EXCLUDE, + }; + + ret = perf_pmu_register(&pcie_pmu->pmu, name, -1); + if (ret) { + dev_err(pcie_pmu->dev, "PCIE PMU register failed!\n"); + cpuhp_state_remove_instance_nocalls( + phytium_pcie_pmu_hp_state, + &pcie_pmu->node); + } + + phytium_pcie_pmu_enable_clk(pcie_pmu); + + pr_info("Phytium PCIe PMU: "); + pr_info("die_id = %d pmu_id = %d.\n", pcie_pmu->die_id, + pcie_pmu->pmu_id); + + return ret; +} + +static int phytium_pcie_pmu_remove(struct platform_device *pdev) +{ + struct phytium_pcie_pmu *pcie_pmu = platform_get_drvdata(pdev); + + phytium_pcie_pmu_disable_clk(pcie_pmu); + + perf_pmu_unregister(&pcie_pmu->pmu); + cpuhp_state_remove_instance_nocalls( + phytium_pcie_pmu_hp_state, &pcie_pmu->node); + + return 0; +} + +static struct platform_driver phytium_pcie_pmu_driver = { + .driver = { + .name = "phytium_pcie_pmu", + .acpi_match_table = ACPI_PTR(phytium_pcie_pmu_acpi_match), + .suppress_bind_attrs = true, + }, + .probe = phytium_pcie_pmu_probe, + .remove = phytium_pcie_pmu_remove, +}; + +int phytium_pcie_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node) +{ + struct phytium_pcie_pmu *pcie_pmu = + hlist_entry_safe(node, struct phytium_pcie_pmu, node); + unsigned int target; + cpumask_t available_cpus; + + if (pcie_pmu->on_cpu != cpu) + return 0; + + cpumask_and(&available_cpus, + cpumask_of_node(pcie_pmu->die_id), cpu_online_mask); + + target = cpumask_any_but(&available_cpus, cpu); + if (target >= nr_cpu_ids) { + target = cpumask_any_but(cpu_online_mask, cpu); + if (target >= nr_cpu_ids) + return 0; + } + + perf_pmu_migrate_context(&pcie_pmu->pmu, cpu, target); + WARN_ON(irq_set_affinity(pcie_pmu->irq, cpumask_of(target))); + pcie_pmu->on_cpu = target; + + return 0; +} + +static int __init phytium_pcie_pmu_module_init(void) +{ + int ret; + + phytium_pcie_pmu_hp_state = + cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, + "perf/phytium/pciepmu:offline", NULL, + phytium_pcie_pmu_offline_cpu); + if (phytium_pcie_pmu_hp_state < 0) { + pr_err("PCIE PMU: setup hotplug, ret = %d\n", + phytium_pcie_pmu_hp_state); + return phytium_pcie_pmu_hp_state; + } + + ret = platform_driver_register(&phytium_pcie_pmu_driver); + if (ret) + cpuhp_remove_multi_state( + phytium_pcie_pmu_hp_state); + + return ret; +} +module_init(phytium_pcie_pmu_module_init); + +static void __exit phytium_pcie_pmu_module_exit(void) +{ + platform_driver_unregister(&phytium_pcie_pmu_driver); + cpuhp_remove_multi_state(phytium_pcie_pmu_hp_state); +} +module_exit(phytium_pcie_pmu_module_exit); + +MODULE_DESCRIPTION("Phytium PCIe PMU driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Hu Xianghua "); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 8ebcddf91f7b78582ab3b182879477a7be4f5d38..3bd556182ef57944cb3ba2e33d2b4aa22bf4d011 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -454,6 +454,16 @@ config PWM_PCA9685 To compile this driver as a module, choose M here: the module will be called pwm-pca9685. +config PWM_PHYTIUM + tristate "Phytium PWM support" + depends on ARCH_PHYTIUM + help + Generic PWM framework driver for the PWM controller found on + Phytium SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-phytium. + config PWM_PXA tristate "PXA PWM support" depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index c822389c2a24c2dd76b106ebb64b4a215f522889..e3fb6e95b389678a3175ed48ffa512e333b30cd7 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o +obj-$(CONFIG_PWM_PHYTIUM) += pwm-phytium.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o diff --git a/drivers/pwm/pwm-phytium.c b/drivers/pwm/pwm-phytium.c new file mode 100644 index 0000000000000000000000000000000000000000..cd64e48b1f13551b297d09fbf3ad60ff825ff475 --- /dev/null +++ b/drivers/pwm/pwm-phytium.c @@ -0,0 +1,590 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium PWM driver + * + * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_TCNT 0x00 +#define REG_TCTRL 0x04 +#define REG_STAT 0x08 + +#define REG_TPERIOD 0x0c +#define REG_PWMCTRL 0x10 +#define REG_PWMCCR 0x14 + +#define TCTRL_DIV_MASK 0x1ff8 +#define TCTRL_PWMMOD_MASK 0x4 +#define TCTRL_CAPMOD_MASK 0x3 +#define PWM_PERIOD_MASK 0xffff +#define PWM_DUTY_MASK 0xffff +#define PWM_MODE_MASK 0x4 +#define PWM_CTRL_INIT 0xc4 + +#define PWM_NUM 2 + +#define REG_DBCTRL 0x00 +#define REG_DBCLY 0x04 +#define PWM_UPDBCLY_MASK 0x3ff +#define PWM_DWDBCLY_MASK 0xffc00 +#define PWM_DB_POLARITY_MASK 0xc + +#define PWM_N(x) ((0x400)*(x)) +#define MAX_PARAMETER 2 + +struct phytium_pwm_state { + int rst; + int cntmod; + int dutymod; + unsigned int div; + int db_rst; + unsigned int updbcly; + unsigned int dwdbcly; + unsigned int dbpolarity; +}; + +struct phytium_pwm_param { + int cntmod; + int dutymod; + unsigned int div; + unsigned int updbcly; + unsigned int dwdbcly; + unsigned int dbpolarity; +}; + +struct phytium_pwm_variant { + u8 rst_mask; + u8 div; + int counter_mode; + int periodns; + int duty_ns; + int pwm_mode; + u8 duty_mode; + int updbcly; + int dwdbcly; +}; + +struct phytium_pwm_channel { + u32 period_ns; + u32 duty_ns; + u32 tin_ns; +}; + +struct phytium_pwm_chip { + struct pwm_chip chip; + struct pwm_state state_pm[PWM_NUM]; + struct phytium_pwm_variant variant; + struct phytium_pwm_state state; + u8 inverter_mask; + u8 disabled_mask; + int db_init; + void __iomem *base; + void __iomem *base1; + struct phytium_pwm_param parameter[MAX_PARAMETER]; + unsigned int num_parameters; + + struct clk *base_clk; +}; + +static inline struct phytium_pwm_chip *to_phytium_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct phytium_pwm_chip, chip); +} + + +static void pwm_phytium_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + devm_kfree(chip->dev, pwm_get_chip_data(pwm)); + pwm_set_chip_data(pwm, NULL); +} + +static int pwm_phytium_enable(struct pwm_chip *chip, struct pwm_device *pwm, int n) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + u32 reg; + + reg = readl(our_chip->base + PWM_N(n) + REG_TCTRL); + reg |= 0x2; + our_chip->state_pm[n].enabled = 1; + writel(reg, our_chip->base + PWM_N(n) + REG_TCTRL); + + return 0; +} + +static void pwm_phytium_disable(struct pwm_chip *chip, struct pwm_device *pwm, int n) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + u32 reg; + + reg = readl(our_chip->base + PWM_N(n) + REG_TCTRL); + reg &= 0xfffffffd; + our_chip->state_pm[n].enabled = 0; + writel(reg, our_chip->base + PWM_N(n) + REG_TCTRL); +} + +static void pwm_phytium_dutymod(struct pwm_chip *chip, int dutymod, int n) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + u32 reg; + + reg = readl(our_chip->base + PWM_N(n) + REG_PWMCTRL); + + if (dutymod == 0) + reg &= 0xfffffeff; + else if (dutymod == 1) + reg |= 0x100; + + writel(reg, our_chip->base + PWM_N(n) + REG_PWMCTRL); +} + +static void pwm_phytium_set_div(struct pwm_chip *chip, unsigned int div, int n) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + u32 reg; + + reg = readl(our_chip->base + PWM_N(n) + REG_TCTRL); + reg &= 0xffff; + reg |= (div<<16); + writel(reg, our_chip->base + PWM_N(n) + REG_TCTRL); +} + +static void pwm_phytium_set_tmode(struct pwm_chip *chip, int tmode, int n) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + u32 reg; + + reg = readl(our_chip->base + PWM_N(n) + REG_TCTRL); + if (tmode == 0) + reg &= 0xfffffffb; + else if (tmode == 1) + reg |= 0x4; + + writel(reg, our_chip->base + PWM_N(n) + REG_TCTRL); +} + +static void pwm_phytium_set_periodns(struct pwm_chip *chip, unsigned int periodns, int n) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + u32 reg; + int div = our_chip->state.div; + u64 cycles; + + if (has_acpi_companion(chip->dev)) + device_property_read_u64(chip->dev, "clock-frequency", &cycles); + else + cycles = clk_get_rate(our_chip->base_clk); + cycles *= (periodns / (div + 1)); + do_div(cycles, NSEC_PER_SEC); + + reg = readl(our_chip->base + PWM_N(n) + REG_TPERIOD); + cycles = (cycles & PWM_PERIOD_MASK) - 0x1; + our_chip->state_pm[n].period = cycles; + + writel(cycles, our_chip->base + PWM_N(n) + REG_TPERIOD); +} + +static void pwm_phytium_set_duty(struct pwm_chip *chip, unsigned int duty, int n) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + u32 reg; + int div = our_chip->state.div; + u64 cycles; + + if (has_acpi_companion(chip->dev)) + device_property_read_u64(chip->dev, "clock-frequency", &cycles); + else + cycles = clk_get_rate(our_chip->base_clk); + cycles *= (duty / (div + 1)); + do_div(cycles, NSEC_PER_SEC); + + reg = readl(our_chip->base + PWM_N(n) + REG_PWMCCR); + cycles = (cycles & PWM_DUTY_MASK) - 0x1; + our_chip->state_pm[n].duty_cycle = cycles; + + writel(cycles, our_chip->base + PWM_N(n) + REG_PWMCCR); +} + +static int pwm_phytium_set_dbcly(struct pwm_chip *chip, unsigned int updbcly, unsigned int dwdbcly) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + u32 reg; + u64 dbcly, cycles, upcycles, dwcycles; + + reg = readl(our_chip->base + REG_TPERIOD); + if (has_acpi_companion(chip->dev)) + device_property_read_u64(chip->dev, "clock-frequency", &cycles); + else + cycles = clk_get_rate(our_chip->base_clk); + dbcly &= 0x0; + if (updbcly) { + upcycles = cycles * updbcly; + do_div(upcycles, NSEC_PER_SEC); + + if (upcycles < reg) + dbcly |= (upcycles & PWM_UPDBCLY_MASK); + else + return -EINVAL; + } + + if (dwdbcly) { + dwcycles = cycles * dwdbcly; + do_div(dwcycles, NSEC_PER_SEC); + + if (dwcycles < reg) + dbcly |= ((dwcycles << 10) & PWM_DWDBCLY_MASK); + else + return -EINVAL; + } + + writel(dbcly, our_chip->base1 + REG_DBCLY); + + reg = readl(our_chip->base1 + REG_DBCTRL); + reg |= 0x30; + writel(reg, our_chip->base1 + REG_DBCTRL); + + return 0; +} + +static void pwm_phytium_set_dbpolarity(struct pwm_chip *chip, unsigned int db_polarity) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + u32 reg; + + reg = readl(our_chip->base1 + REG_DBCTRL); + reg &= 0x33; + reg |= ((db_polarity<<2) & PWM_DB_POLARITY_MASK); + writel(reg, our_chip->base1 + REG_DBCTRL); +} + +static int pwm_phytium_init(struct pwm_chip *chip, struct pwm_device *pwm, int n) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + + writel(PWM_CTRL_INIT, our_chip->base + PWM_N(n) + REG_PWMCTRL); + + pwm_phytium_dutymod(chip, our_chip->state.dutymod, n); + pwm_phytium_set_div(chip, our_chip->state.div, n); + pwm_phytium_set_tmode(chip, our_chip->state.cntmod, n); + + return 0; +} + +static int pwm_phytium_db_init(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + + pwm_phytium_set_dbcly(chip, our_chip->state.updbcly, our_chip->state.dwdbcly); + pwm_phytium_set_dbpolarity(chip, our_chip->state.dbpolarity); + + return 0; +} + +static int __pwm_phytium_config(struct pwm_chip *chip, struct pwm_device *pwm) +{ + pwm_phytium_init(chip, pwm, 0); + pwm_phytium_init(chip, pwm, 1); + return 0; +} + +static int pwm_phytium_set_polarity(struct pwm_chip *chip, enum pwm_polarity polarity, int n) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + u32 value; + + value = readl(our_chip->base + PWM_N(n) + REG_PWMCTRL); + + if (polarity == PWM_POLARITY_INVERSED) { + value &= 0xffffff0f; + value |= 0x30; + } else if (polarity == PWM_POLARITY_NORMAL) { + value &= 0xffffff0f; + value |= 0x40; + } + + our_chip->state_pm[n].polarity = polarity; + writel(value, our_chip->base + PWM_N(n) + REG_PWMCTRL); + + return 0; +} + +static int pwm_phytium_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct phytium_pwm_chip *phytium_pwm = to_phytium_pwm_chip(chip); + struct pwm_state cstate; + u32 reg; + int n; + + pwm_get_state(pwm, &cstate); + + n = pwm->hwpwm & BIT(0); + + if ((state->polarity != cstate.polarity) && !state->enabled) + pwm_phytium_set_polarity(chip, state->polarity, n); + + if (state->enabled && !cstate.enabled) + pwm_phytium_enable(chip, pwm, n); + + if (!state->enabled && cstate.enabled) + pwm_phytium_disable(chip, pwm, n); + + if (state->period != cstate.period) { + pwm_phytium_set_periodns(chip, state->period, n); + if ((phytium_pwm->db_init == 1) && (n == 0)) + pwm_phytium_db_init(chip, pwm); + } + + if (state->duty_cycle != cstate.duty_cycle) { + if (phytium_pwm->state.dutymod == true) { + reg = readl(phytium_pwm->base + PWM_N(n) + REG_STAT); + if ((reg & 0x8) != 0x8) + pwm_phytium_set_duty(chip, state->duty_cycle, n); + } else { + pwm_phytium_set_duty(chip, state->duty_cycle, n); + } + } + + return 0; +} + +static int pwm_phytium_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct phytium_pwm_chip *our_chip = to_phytium_pwm_chip(chip); + struct phytium_pwm_channel *our_chan; + + our_chan = devm_kzalloc(chip->dev, sizeof(*our_chan), GFP_KERNEL); + if (!our_chan) + return -ENOMEM; + + pwm_set_chip_data(pwm, our_chan); + __pwm_phytium_config(&our_chip->chip, our_chip->chip.pwms); + + return 0; +} + +static const struct pwm_ops pwm_phytium_ops = { + .request = pwm_phytium_request, + .free = pwm_phytium_free, + .apply = pwm_phytium_apply, + .owner = THIS_MODULE, +}; + +static int phytium_pwm_set_parameter(struct phytium_pwm_chip *priv) +{ + unsigned int i; + + for (i = 0; i < priv->num_parameters; i++) { + if (priv->parameter[i].updbcly > 0 || priv->parameter[i].dwdbcly > 0) { + priv->db_init = 1; + priv->state.db_rst = 1; + } + + priv->state.cntmod = priv->parameter[i].cntmod; + priv->state.dutymod = priv->parameter[i].dutymod; + priv->state.div = priv->parameter[i].div; + priv->state.updbcly = priv->parameter[i].updbcly; + priv->state.dwdbcly = priv->parameter[i].dwdbcly; + priv->state.dbpolarity = priv->parameter[i].dbpolarity; + } + priv->state.rst = 1; + + return 0; +} + +static int pwm_phytium_probe_parameter(struct phytium_pwm_chip *priv, + struct device_node *np) +{ + int nb, ret, array_size; + unsigned int i; + + if (has_acpi_companion(priv->chip.dev)) { + priv->num_parameters = 1; + array_size = sizeof(struct phytium_pwm_param) / sizeof(u32); + ret = fwnode_property_read_u32_array(dev_fwnode(priv->chip.dev), + "phytium,db", (u32 *)priv->parameter, + array_size); + if (ret < 0) + return ret; + } else { + nb = of_property_count_elems_of_size(np, "phytium,db", + sizeof(struct phytium_pwm_param)); + if (nb <= 0 || nb > MAX_PARAMETER) + return -EINVAL; + + priv->num_parameters = nb; + array_size = nb * sizeof(struct phytium_pwm_param) / sizeof(u32); + ret = of_property_read_u32_array(np, "phytium,db", + (u32 *)priv->parameter, array_size); + if (ret) + return ret; + } + + for (i = 0; i < priv->num_parameters; i++) { + if (priv->parameter[i].cntmod > 1 || + priv->parameter[i].dutymod > 1 || + priv->parameter[i].div > 4096 || + priv->parameter[i].dbpolarity > 3) + return -EINVAL; + } + + return phytium_pwm_set_parameter(priv); +} +static int pwm_phytium_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct phytium_pwm_chip *chip; + struct resource *res; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + + if (chip == NULL) + return -ENOMEM; + + chip->chip.dev = &pdev->dev; + chip->chip.ops = &pwm_phytium_ops; + chip->chip.base = -1; + chip->chip.npwm = PWM_NUM; + chip->inverter_mask = BIT(PWM_NUM) - 1; + + if (pdev->dev.of_node) { + chip->chip.of_xlate = of_pwm_xlate_with_flags; + chip->chip.of_pwm_n_cells = 3; + } + + ret = pwm_phytium_probe_parameter(chip, np); + if (ret) { + dev_err(dev, "failed to set parameter\n"); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + chip->base1 = devm_ioremap_resource(&pdev->dev, res); + chip->base = (chip->base1 + 0x400); + + if (!has_acpi_companion(&pdev->dev)) { + if (IS_ERR(chip->base)) { + dev_err(dev, "failed to get base_addr\n"); + return PTR_ERR(chip->base); + } + + if (pdev->dev.of_node) { + chip->base_clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(chip->base_clk)) { + dev_err(dev, "failed to get clk\n"); + return PTR_ERR(chip->base_clk); + } + + ret = clk_prepare_enable(chip->base_clk); + if (ret < 0) { + dev_err(dev, "failed to enable clk\n"); + return ret; + } + } + } + + platform_set_drvdata(pdev, chip); + + ret = pwmchip_add(&chip->chip); + + if (ret < 0) { + dev_err(dev, "failed to register PWM chip\n"); + return ret; + } + + return 0; +} + +static int pwm_phytium_remove(struct platform_device *pdev) +{ + struct phytium_pwm_chip *chip = platform_get_drvdata(pdev); + + pwmchip_remove(&chip->chip); + + clk_disable_unprepare(chip->base_clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pwm_phytium_pm_init(struct phytium_pwm_chip *priv) +{ + int i; + + __pwm_phytium_config(&priv->chip, priv->chip.pwms); + for (i = 0; i < priv->chip.npwm; i++) { + writel(priv->state_pm[i].period, priv->base + PWM_N(i) + REG_TPERIOD); + if ((priv->db_init == 1) && (i == 0)) + pwm_phytium_db_init(&priv->chip, priv->chip.pwms); + writel(priv->state_pm[i].duty_cycle, priv->base + PWM_N(i) + REG_PWMCTRL); + pwm_phytium_set_polarity(&priv->chip, priv->state_pm[i].polarity, i); + if (priv->state_pm[i].enabled) + pwm_phytium_enable(&priv->chip, priv->chip.pwms, i); + } + + return 0; +} + +static int pwm_phytium_suspend(struct device *dev) +{ + return 0; +} + +static int pwm_phytium_resume(struct device *dev) +{ + struct phytium_pwm_chip *priv = dev_get_drvdata(dev); + + pwm_phytium_pm_init(priv); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_pwm_dev_pm_ops, pwm_phytium_suspend, pwm_phytium_resume); + +static const struct of_device_id phytium_pwm_matches[] = { + { .compatible = "phytium,pwm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, phytium_pwm_matches); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_pwm_acpi_matches[] = { + { "PHYT0029", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, phytium_pwm_acpi_matches); +#endif + +static struct platform_driver pwm_phytium_driver = { + .driver = { + .name = "phytium-pwm", + .pm = &phytium_pwm_dev_pm_ops, + .of_match_table = phytium_pwm_matches, +#ifdef CONFIG_ACPI + .acpi_match_table = phytium_pwm_acpi_matches, +#endif + }, + .probe = pwm_phytium_probe, + .remove = pwm_phytium_remove, +}; +module_platform_driver(pwm_phytium_driver); + +MODULE_DESCRIPTION("Phytium SoC PWM driver"); +MODULE_AUTHOR("Yang Liu "); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c index 052ccadbdabfe630f5d03630bd182929ae2e29ba..7d19bf061d696ba23879103e849ca1a1264dae8c 100644 --- a/drivers/pwm/sysfs.c +++ b/drivers/pwm/sysfs.c @@ -260,8 +260,15 @@ static int pwm_export_child(struct device *parent, struct pwm_device *pwm) export->child.parent = parent; export->child.devt = MKDEV(0, 0); export->child.groups = pwm_groups; - dev_set_name(&export->child, "pwm%u", pwm->hwpwm); +#ifdef CONFIG_ARCH_PHYTIUM + if (read_cpuid_implementor() == ARM_CPU_IMP_PHYTIUM) + dev_set_name(&export->child, "pwm%u", pwm->pwm); + else + dev_set_name(&export->child, "pwm%u", pwm->hwpwm); +#else + dev_set_name(&export->child, "pwm%u", pwm->hwpwm); +#endif ret = device_register(&export->child); if (ret) { clear_bit(PWMF_EXPORTED, &pwm->flags); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 3ce0fd5df8e9ca471e67de3653091aace94f31ad..d157a7d9169d6611e89cc279821cea88d54d809c 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -765,14 +765,43 @@ config SPI_ORION This enables using the SPI master controller on the Orion and MVEBU chips. -config SPI_PCI1XXXX - tristate "PCI1XXXX SPI Bus support" +config SPI_PHYTIUM + tristate + depends on ARCH_PHYTIUM || COMPILE_TEST + +config SPI_PHYTIUM_PLAT + tristate "Phytium SPI controller platform support" + select SPI_PHYTIUM + help + This selects a platform driver for Phytium SPI controller. + + If you say yes to this option, support will be included for + Phytium SoC families of SPI controller. + +config SPI_PHYTIUM_PCI + tristate "Phytium SPI controller PCI support" depends on PCI + select SPI_PHYTIUM help - Say "yes" to Enable the SPI Bus support for the PCI1xxxx card - This is a PCI to SPI Bus driver - This driver can be built as module. If so, the module will be - called as spi-pci1xxxx. + This selects a PCI driver for Phytium SPI controller. + + If you say yes to this option, support will be included for + Phytium PCIe chipsets of SPI controller. + + If unsure, say N. + +config SPI_PHYTIUM_QSPI + tristate "Phytium Quad SPI controller" + depends on ARCH_PHYTIUM || COMPILE_TEST + depends on OF + depends on SPI_MEM + help + This enables support for Phytium Quad SPI flash controller. + + This driver does not support generic SPI. The implementation only + supports spi-mem interface. + + If unsure, say N. config SPI_PIC32 tristate "Microchip PIC32 series SPI" diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 6af54842b9fa4f2b95355ada8418ad54173649b3..d7e4296988395831c2457186c8eddab65ff0e711 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -100,7 +100,11 @@ obj-$(CONFIG_SPI_OMAP_UWIRE) += spi-omap-uwire.o obj-$(CONFIG_SPI_OMAP24XX) += spi-omap2-mcspi.o obj-$(CONFIG_SPI_TI_QSPI) += spi-ti-qspi.o obj-$(CONFIG_SPI_ORION) += spi-orion.o -obj-$(CONFIG_SPI_PCI1XXXX) += spi-pci1xxxx.o +obj-$(CONFIG_SPI_PHYTIUM) += spi-phytium.o +obj-$(CONFIG_SPI_PHYTIUM_PLAT) += spi-phytium-plat.o +obj-$(CONFIG_SPI_PHYTIUM_PCI) += spi-phytium-pci.o +obj-$(CONFIG_SPI_PHYTIUM_QSPI) += spi-phytium-qspi.o +obj-$(CONFIG_SPI_PHYTIUM) += spi-phytium-dma.o obj-$(CONFIG_SPI_PIC32) += spi-pic32.o obj-$(CONFIG_SPI_PIC32_SQI) += spi-pic32-sqi.o obj-$(CONFIG_SPI_PL022) += spi-pl022.o diff --git a/drivers/spi/spi-phytium-dma.c b/drivers/spi/spi-phytium-dma.c new file mode 100644 index 0000000000000000000000000000000000000000..4387dadcd850588397925ed7f1b4a5938877d4e9 --- /dev/null +++ b/drivers/spi/spi-phytium-dma.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Special handling for phytium DMA core + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd.. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-phytium.h" + +#define RX_BUSY 0 +#define RX_BURST_LEVEL 16 +#define TX_BUSY 1 +#define TX_BURST_LEVEL 16 + +#define DMA_MAX_BUF_SIZE 4096 + +static void phytium_spi_dma_maxburst_init(struct phytium_spi *fts) +{ + struct dma_slave_caps caps; + u32 max_burst, def_burst; + int ret; + + def_burst = fts->fifo_len / 2; + + ret = dma_get_slave_caps(fts->rxchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = RX_BURST_LEVEL; + + fts->rxburst = min(max_burst, def_burst); + phytium_writel(fts, DMARDLR, 0x0); + + ret = dma_get_slave_caps(fts->txchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = TX_BURST_LEVEL; + + /* + * Having a Rx DMA channel serviced with higher priority than a Tx DMA + * channel might not be enough to provide a well balanced DMA-based + * SPI transfer interface. There might still be moments when the Tx DMA + * channel is occasionally handled faster than the Rx DMA channel. + * That in its turn will eventually cause the SPI Rx FIFO overflow if + * SPI bus speed is high enough to fill the SPI Rx FIFO in before it's + * cleared by the Rx DMA channel. In order to fix the problem the Tx + * DMA activity is intentionally slowed down by limiting the SPI Tx + * FIFO depth with a value twice bigger than the Tx burst length. + */ + fts->txburst = min(max_burst, def_burst); + /* set dmatdlr to 0 + 1 */ + phytium_writel(fts, DMATDLR, 0); +} + +static int phytium_spi_dma_init(struct device *dev, + struct phytium_spi *fts) +{ + fts->rxchan = dma_request_chan(dev, "rx"); + if (IS_ERR_OR_NULL(fts->rxchan)) + return -ENODEV; + + fts->txchan = dma_request_chan(dev, "tx"); + if (IS_ERR_OR_NULL(fts->txchan)) { + dev_err(dev, "can't request chan\n"); + dma_release_channel(fts->rxchan); + fts->rxchan = NULL; + return -ENODEV; + } + + fts->master->dma_rx = fts->rxchan; + fts->master->dma_tx = fts->txchan; + init_completion(&fts->dma_completion); + + phytium_spi_dma_maxburst_init(fts); + fts->dma_sg_burst = 0; + + return 0; +} + +static void phytium_spi_dma_exit(struct phytium_spi *fts) +{ + if (fts->txchan) { + dmaengine_terminate_sync(fts->txchan); + dma_release_channel(fts->txchan); + } + + if (fts->rxchan) { + dmaengine_terminate_sync(fts->rxchan); + dma_release_channel(fts->rxchan); + } +} + +static irqreturn_t phytium_spi_dma_transfer_handler +(struct phytium_spi *fts) +{ + phytium_spi_check_status(fts, false); + + complete(&fts->dma_completion); + + return IRQ_HANDLED; +} + +static bool phytium_spi_can_dma(struct spi_controller *master, + struct spi_device *spi, struct spi_transfer *xfer) +{ + struct phytium_spi *fts = spi_controller_get_devdata(master); + + return xfer->len > fts->fifo_len; +} + +static enum dma_slave_buswidth phytium_spi_dma_convert_width(u8 n_bytes) +{ + if (n_bytes == 1) + return DMA_SLAVE_BUSWIDTH_1_BYTE; + else if (n_bytes == 2) + return DMA_SLAVE_BUSWIDTH_2_BYTES; + + return DMA_SLAVE_BUSWIDTH_UNDEFINED; +} + +static int phytium_spi_dma_wait(struct phytium_spi *fts, unsigned int len, + u32 speed) +{ + unsigned long long ms; + + ms = len * MSEC_PER_SEC * BITS_PER_BYTE; + do_div(ms, speed); + ms += ms + 200; + + if (ms > UINT_MAX) + ms = UINT_MAX; + + ms = wait_for_completion_timeout(&fts->dma_completion, + msecs_to_jiffies(ms)); + + if (ms == 0) { + dev_err(&fts->master->cur_msg->spi->dev, + "DMA transaction timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static inline bool phytium_spi_dma_tx_busy(struct phytium_spi *fts) +{ + return !(phytium_readl(fts, SR) & SR_TF_EMPT); +} + +static void spi_transfer_delay_ns(u32 ns) +{ + if (!ns) + return; + if (ns <= 1000) { + ndelay(ns); + } else { + u32 us = DIV_ROUND_UP(ns, 1000); + + if (us <= 10) + udelay(us); + else + usleep_range(us, us + DIV_ROUND_UP(us, 10)); + } +} + +static int phytium_spi_dma_wait_tx_done(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + int retry = SPI_WAIT_RETRIES; + u32 ns = 0; + u32 nents = 0; + + nents = phytium_readl(fts, TXFLR); + ns = nents * fts->n_bytes * BITS_PER_BYTE; + ns *= DIV_ROUND_UP(1000000000, xfer->speed_hz / 2); + + while (phytium_spi_dma_tx_busy(fts) && retry--) + spi_transfer_delay_ns(ns); + + if (retry < 0) { + dev_err(&fts->master->dev, "Tx hanged up\n"); + return -EIO; + } + + return 0; +} + +/* + * fts->dma_chan_busy is set before the dma transfer starts, + * callback for tx + * channel will clear a corresponding bit. + */ +static void phytium_spi_dma_tx_done(void *arg) +{ + struct phytium_spi *fts = arg; + + clear_bit(TX_BUSY, &fts->dma_chan_busy); + if (test_bit(RX_BUSY, &fts->dma_chan_busy)) + return; + + complete(&fts->dma_completion); +} + +static int phytium_spi_dma_config_tx(struct phytium_spi *fts) +{ + struct dma_slave_config txconf; + + memset(&txconf, 0, sizeof(txconf)); + txconf.direction = DMA_MEM_TO_DEV; + txconf.dst_addr = fts->dma_addr; + txconf.dst_maxburst = fts->txburst; + txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + txconf.dst_addr_width = phytium_spi_dma_convert_width(fts->n_bytes); + txconf.device_fc = false; + + return dmaengine_slave_config(fts->txchan, &txconf); +} + +static int phytium_spi_dma_submit_tx(struct phytium_spi *fts, + struct scatterlist *sgl, unsigned int nents) +{ + struct dma_async_tx_descriptor *txdesc; + dma_cookie_t cookie; + int ret; + + txdesc = dmaengine_prep_slave_sg(fts->txchan, sgl, nents, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!txdesc) + return -ENOMEM; + + txdesc->callback = phytium_spi_dma_tx_done; + txdesc->callback_param = fts; + + cookie = dmaengine_submit(txdesc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_sync(fts->txchan); + return ret; + } + + set_bit(TX_BUSY, &fts->dma_chan_busy); + + return 0; +} + +static inline bool phytium_spi_dma_rx_busy(struct phytium_spi *fts) +{ + return !!(phytium_readl(fts, SR) & SR_RF_NOT_EMPT); +} + +static int phytium_spi_dma_wait_rx_done(struct phytium_spi *fts) +{ + int retry = SPI_WAIT_RETRIES; + unsigned long ns = 0; + u32 nents = 0; + + /* + * It's unlikely that DMA engine is still doing the data fetching, but + * if it's let's give it some reasonable time. The timeout calculation + * is based on the synchronous APB/SSI reference clock rate, on a + * number of data entries left in the Rx FIFO, times a number of clock + * periods normally needed for a single APB read/write transaction + * without PREADY signal utilized (which is true for the phytium APB SSI + * controller). + */ + nents = phytium_readl(fts, RXFLR); + ns = 4U * NSEC_PER_SEC / fts->max_freq * nents; + + while (phytium_spi_dma_rx_busy(fts) && retry--) + spi_transfer_delay_ns(ns); + + if (retry < 0) { + dev_err(&fts->master->dev, "Rx hanged up, nents = %d\n", nents); + return -EIO; + } + + return 0; +} + +/* + * fts->dma_chan_busy is set before the dma transfer starts, + * callback for rx + * channel will clear a corresponding bit. + */ +static void phytium_spi_dma_rx_done(void *arg) +{ + struct phytium_spi *fts = arg; + + clear_bit(RX_BUSY, &fts->dma_chan_busy); + if (test_bit(TX_BUSY, &fts->dma_chan_busy)) + return; + + complete(&fts->dma_completion); +} + +static int phytium_spi_dma_config_rx(struct phytium_spi *fts) +{ + struct dma_slave_config rxconf; + + memset(&rxconf, 0, sizeof(rxconf)); + rxconf.direction = DMA_DEV_TO_MEM; + rxconf.src_addr = fts->dma_addr; + rxconf.src_maxburst = fts->rxburst; + rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + rxconf.src_addr_width = phytium_spi_dma_convert_width(fts->n_bytes); + rxconf.device_fc = false; + + return dmaengine_slave_config(fts->rxchan, &rxconf); +} + +static int phytium_spi_dma_submit_rx(struct phytium_spi *fts, + struct scatterlist *sgl, unsigned int nents) +{ + struct dma_async_tx_descriptor *rxdesc; + dma_cookie_t cookie; + int ret; + + rxdesc = dmaengine_prep_slave_sg(fts->rxchan, sgl, nents, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!rxdesc) + return -ENOMEM; + + rxdesc->callback = phytium_spi_dma_rx_done; + rxdesc->callback_param = fts; + + cookie = dmaengine_submit(rxdesc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_sync(fts->rxchan); + return ret; + } + + set_bit(RX_BUSY, &fts->dma_chan_busy); + + return 0; +} + +static int phytium_spi_dma_setup(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + u16 imr, dma_ctrl; + int ret; + + if (!xfer->tx_buf) + return -EINVAL; + + /* Setup DMA channels */ + ret = phytium_spi_dma_config_tx(fts); + if (ret) + return ret; + + if (xfer->rx_buf) { + ret = phytium_spi_dma_config_rx(fts); + if (ret) + return ret; + } + + /* Set the DMA handshaking interface */ + dma_ctrl = SPI_DMA_TDMAE; + if (xfer->rx_buf) + dma_ctrl |= SPI_DMA_RDMAE; + phytium_writel(fts, DMACR, dma_ctrl); + + /* Set the interrupt mask */ + imr = INT_TXOI; + if (xfer->rx_buf) + imr |= INT_RXUI | INT_RXOI; + + spi_umask_intr(fts, imr); + + reinit_completion(&fts->dma_completion); + + fts->transfer_handler = phytium_spi_dma_transfer_handler; + + return 0; +} + +static int phytium_spi_dma_transfer_all(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + int ret; + + /* Submit the DMA Tx transfer */ + ret = phytium_spi_dma_submit_tx(fts, xfer->tx_sg.sgl, + xfer->tx_sg.nents); + if (ret) + goto err_clear_dmac; + + /* Submit the DMA Rx transfer if required */ + if (xfer->rx_buf) { + ret = phytium_spi_dma_submit_rx(fts, xfer->rx_sg.sgl, + xfer->rx_sg.nents); + if (ret) + goto err_clear_dmac; + + /* rx must be started before tx due to spi instinct */ + dma_async_issue_pending(fts->rxchan); + } + + dma_async_issue_pending(fts->txchan); + + ret = phytium_spi_dma_wait(fts, xfer->len, xfer->speed_hz); + +err_clear_dmac: + phytium_writel(fts, DMACR, 0); + + return ret; +} + +static int phytium_spi_dma_transfer_one(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + struct scatterlist *tx_sg = NULL, *rx_sg = NULL, tx_tmp, rx_tmp; + unsigned int tx_len = 0, rx_len = 0; + unsigned int base, len; + int ret; + + sg_init_table(&tx_tmp, 1); + sg_init_table(&rx_tmp, 1); + + for (base = 0, len = 0; base < xfer->len; base += len) { + /* Fetch next Tx DMA data chunk */ + if (!tx_len) { + tx_sg = !tx_sg ? &xfer->tx_sg.sgl[0] : sg_next(tx_sg); + sg_dma_address(&tx_tmp) = sg_dma_address(tx_sg); + tx_len = sg_dma_len(tx_sg); + } + + /* Fetch next Rx DMA data chunk */ + if (!rx_len) { + rx_sg = !rx_sg ? &xfer->rx_sg.sgl[0] : sg_next(rx_sg); + sg_dma_address(&rx_tmp) = sg_dma_address(rx_sg); + rx_len = sg_dma_len(rx_sg); + } + + if ((base + DMA_MAX_BUF_SIZE) > xfer->len) + len = xfer->len - base; + else + len = DMA_MAX_BUF_SIZE; + + len = min3(len, tx_len, rx_len); + + sg_dma_len(&tx_tmp) = len; + sg_dma_len(&rx_tmp) = len; + + /* Submit DMA Tx transfer */ + ret = phytium_spi_dma_submit_tx(fts, &tx_tmp, 1); + if (ret) + break; + + /* Submit DMA Rx transfer */ + ret = phytium_spi_dma_submit_rx(fts, &rx_tmp, 1); + if (ret) + break; + + /* Rx must be started before Tx due to SPI instinct */ + dma_async_issue_pending(fts->rxchan); + + dma_async_issue_pending(fts->txchan); + + /* + * Here we only need to wait for the DMA transfer to be + * finished since SPI controller is kept enabled during the + * procedure this loop implements and there is no risk to lose + * data left in the Tx/Rx FIFOs. + */ + ret = phytium_spi_dma_wait(fts, len, xfer->speed_hz); + if (ret) + break; + + reinit_completion(&fts->dma_completion); + + sg_dma_address(&tx_tmp) += len; + sg_dma_address(&rx_tmp) += len; + tx_len -= len; + rx_len -= len; + } + + phytium_writel(fts, DMACR, 0); + + return ret; +} + +static int phytium_spi_dma_transfer(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + unsigned int nents; + int ret; + + nents = max(xfer->tx_sg.nents, xfer->rx_sg.nents); + + /* + * large transfer length caused spi RX FIFO full event + * transfer 4096 bytes each time + */ + if (xfer->len <= DMA_MAX_BUF_SIZE) + ret = phytium_spi_dma_transfer_all(fts, xfer); + else + ret = phytium_spi_dma_transfer_one(fts, xfer); + if (ret) + return ret; + + if (fts->master->cur_msg->status == -EINPROGRESS) { + ret = phytium_spi_dma_wait_tx_done(fts, xfer); + if (ret) + return ret; + } + + if (xfer->rx_buf && fts->master->cur_msg->status == -EINPROGRESS) + ret = phytium_spi_dma_wait_rx_done(fts); + + return ret; +} + +static void phytium_spi_dma_stop(struct phytium_spi *fts) +{ + if (test_bit(TX_BUSY, &fts->dma_chan_busy)) { + dmaengine_terminate_sync(fts->txchan); + clear_bit(TX_BUSY, &fts->dma_chan_busy); + } + if (test_bit(RX_BUSY, &fts->dma_chan_busy)) { + dmaengine_terminate_sync(fts->rxchan); + clear_bit(RX_BUSY, &fts->dma_chan_busy); + } +} + +static const struct phytium_spi_dma_ops phytium_spi_dma_generic_ops = { + .dma_init = phytium_spi_dma_init, + .dma_exit = phytium_spi_dma_exit, + .dma_setup = phytium_spi_dma_setup, + .can_dma = phytium_spi_can_dma, + .dma_transfer = phytium_spi_dma_transfer, + .dma_stop = phytium_spi_dma_stop, +}; + +void phytium_spi_dmaops_set(struct phytium_spi *fts) +{ + fts->dma_ops = &phytium_spi_dma_generic_ops; +} +EXPORT_SYMBOL_GPL(phytium_spi_dmaops_set); + +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-pci.c b/drivers/spi/spi-phytium-pci.c new file mode 100644 index 0000000000000000000000000000000000000000..d6480320a3c3640f7c3c245771bbf66e8638c4d6 --- /dev/null +++ b/drivers/spi/spi-phytium-pci.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller PCI driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + * + * Derived from drivers/spi/spi-dw-pci.c + * Copyright (c) 2009, 2014 Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-phytium.h" + +#define DRIVER_NAME "phytium_spi_pci" + +static int phytium_spi_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct phytium_spi *fts; + int pci_bar = 0; + int ret; + + fts = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi), + GFP_KERNEL); + if (!fts) + return -ENOMEM; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + ret = pcim_iomap_regions(pdev, 1 << pci_bar, pci_name(pdev)); + if (ret) { + dev_err(&pdev->dev, "pci iomap failed?\n"); + return ret; + } + + fts->regs = pcim_iomap_table(pdev)[pci_bar]; + if (IS_ERR(fts->regs)) { + dev_err(&pdev->dev, "SPI region map failed\n"); + return PTR_ERR(fts->regs); + } + + fts->irq = pdev->irq; + if (fts->irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return fts->irq; /* -ENXIO */ + } + + fts->bus_num = -1; + + fts->max_freq = 48000000; + + fts->num_cs = 4; + + fts->global_cs = 1; + + ret = phytium_spi_add_host(&pdev->dev, fts); + if (ret) + return ret; + + pci_set_drvdata(pdev, fts); + return 0; +} + +static void phytium_spi_pci_remove(struct pci_dev *pdev) +{ + struct phytium_spi *fts = pci_get_drvdata(pdev); + + phytium_spi_remove_host(fts); +} + + +#ifdef CONFIG_PM_SLEEP +static int spi_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct phytium_spi *fts = spi_master_get_devdata(master); + + return phytium_spi_suspend_host(fts); +} + +static int spi_resume(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct phytium_spi *fts = spi_master_get_devdata(master); + + return phytium_spi_resume_host(fts); +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_spi_pm_ops, spi_suspend, spi_resume); + +static const struct pci_device_id phytium_device_pci_tbl[] = { + { PCI_VDEVICE(PHYTIUM, 0xdc2c) }, + {}, +}; + +static struct pci_driver phytium_spi_pci_driver = { + .name = DRIVER_NAME, + .id_table = phytium_device_pci_tbl, + .probe = phytium_spi_pci_probe, + .remove = phytium_spi_pci_remove, + .driver = { + .pm = &phytium_spi_pm_ops, + } +}; + +module_pci_driver(phytium_spi_pci_driver); + +MODULE_AUTHOR("Yiqun Zhang "); +MODULE_DESCRIPTION("PCI Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-plat.c b/drivers/spi/spi-phytium-plat.c new file mode 100644 index 0000000000000000000000000000000000000000..275f48c0434da8e67484ea2d7275515289add4a7 --- /dev/null +++ b/drivers/spi/spi-phytium-plat.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller platform driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + * + * Derived from drivers/spi/spi-dw-mmio.c + * Copyright (c) 2010, Octasic semiconductor. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-phytium.h" + +#define DRIVER_NAME "phytium_spi" + +struct phytium_spi_clk { + struct phytium_spi fts; + struct clk *clk; +}; + +static int phytium_spi_probe(struct platform_device *pdev) +{ + struct phytium_spi_clk *ftsc; + struct phytium_spi *fts; + struct resource *mem; + int ret; + int num_cs; + int global_cs; + + ftsc = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi_clk), + GFP_KERNEL); + if (!ftsc) + return -ENOMEM; + + fts = &ftsc->fts; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -EINVAL; + } + + fts->paddr = mem->start; + fts->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(fts->regs)) { + dev_err(&pdev->dev, "SPI region map failed\n"); + return PTR_ERR(fts->regs); + } + + fts->irq = platform_get_irq(pdev, 0); + if (fts->irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return fts->irq; /* -ENXIO */ + } + + if (pdev->dev.of_node) { + ftsc->clk = devm_clk_get(&pdev->dev, NULL); + + if (IS_ERR(ftsc->clk)) + return PTR_ERR(ftsc->clk); + ret = clk_prepare_enable(ftsc->clk); + if (ret) + return ret; + + fts->max_freq = clk_get_rate(ftsc->clk); + } else if (has_acpi_companion(&pdev->dev)) { + fts->max_freq = 48000000; + } + + fts->bus_num = pdev->id; + device_property_read_u32(&pdev->dev, "reg-io-width", &fts->reg_io_width); + + num_cs = 4; + device_property_read_u32(&pdev->dev, "num-cs", &num_cs); + fts->num_cs = num_cs; + + device_property_read_u32(&pdev->dev, "global-cs", &global_cs); + fts->global_cs = global_cs; + + /* check is use dma transfer */ + if ((device_property_read_string_array(&pdev->dev, "dma-names", + NULL, 0) > 0) && + device_property_present(&pdev->dev, "dmas")) { + fts->dma_en = true; + phytium_spi_dmaops_set(fts); + } + + ret = phytium_spi_add_host(&pdev->dev, fts); + if (ret) + goto out; + + platform_set_drvdata(pdev, ftsc); + return 0; + +out: + clk_disable_unprepare(ftsc->clk); + return ret; +} + +static int phytium_spi_remove(struct platform_device *pdev) +{ + struct phytium_spi_clk *ftsc = platform_get_drvdata(pdev); + + phytium_spi_remove_host(&ftsc->fts); + clk_disable_unprepare(ftsc->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int spi_suspend(struct device *dev) +{ + struct phytium_spi_clk *ftsc = dev_get_drvdata(dev); + + return phytium_spi_suspend_host(&ftsc->fts); +} + +static int spi_resume(struct device *dev) +{ + struct phytium_spi_clk *ftsc = dev_get_drvdata(dev); + + return phytium_spi_resume_host(&ftsc->fts); +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_spi_pm_ops, spi_suspend, spi_resume); + +static const struct of_device_id phytium_spi_of_match[] = { + { .compatible = "phytium,spi", .data = (void *)0 }, + { /* end of table */} +}; +MODULE_DEVICE_TABLE(of, phytium_spi_of_match); + +static const struct acpi_device_id phytium_spi_acpi_match[] = { + {"PHYT000E", 0}, + {} +}; +MODULE_DEVICE_TABLE(acpi, phytium_spi_acpi_match); + +static struct platform_driver phytium_spi_driver = { + .probe = phytium_spi_probe, + .remove = phytium_spi_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(phytium_spi_of_match), + .acpi_match_table = ACPI_PTR(phytium_spi_acpi_match), + .pm = &phytium_spi_pm_ops, + }, +}; +module_platform_driver(phytium_spi_driver); + +MODULE_AUTHOR("Yiqun Zhang "); +MODULE_DESCRIPTION("Platform Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c new file mode 100644 index 0000000000000000000000000000000000000000..032b5e2ec7a43da3e6be485f72ee33baca23097b --- /dev/null +++ b/drivers/spi/spi-phytium-qspi.c @@ -0,0 +1,830 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium Quad SPI controller driver. + * + * Copyright (c) 2022-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +#define QSPI_FLASH_CAP_REG 0x00 +#define QSPI_FLASH_CAP_NUM_SHIFT 3 +#define QSPI_FLASH_CAP_NUM_MASK (0x3 << QSPI_FLASH_CAP_NUM_SHIFT) +#define QSPI_FLASH_CAP_CAP_SHIFT 0 +#define QSPI_FLASH_CAP_CAP_MASK (0x7 << QSPI_FLASH_CAP_CAP_SHIFT) + +#define QSPI_RD_CFG_REG 0x04 +#define QSPI_RD_CFG_RD_CMD_SHIFT 24 +#define QSPI_RD_CFG_RD_CMD_MASK (0xff << QSPI_RD_CFG_RD_CMD_SHIFT) +#define QSPI_RD_CFG_RD_THROUGH_SHIFT 23 +#define QSPI_RD_CFG_RD_THROUGH_MASK (0x1 << QSPI_RD_CFG_RD_THROUGH_SHIFT) +#define QSPI_RD_CFG_RD_TRANSFER_SHIFT 20 +#define QSPI_RD_CFG_RD_TRANSFER_MASK (0x7 << QSPI_RD_CFG_RD_TRANSFER_SHIFT) +#define QSPI_RD_CFG_RD_ADDR_SEL_SHIFT 19 +#define QSPI_RD_CFG_RD_ADDR_SEL_MASK (0x1 << QSPI_RD_CFG_RD_ADDR_SEL_SHIFT) +#define QSPI_RD_CFG_RD_LATENCY_SHIFT 18 +#define QSPI_RD_CFG_RD_LATENCY_MASK (0x1 << QSPI_RD_CFG_RD_LATENCY_SHIFT) +#define QSPI_RD_CFG_MODE_BYTE_SHIFT 17 +#define QSPI_RD_CFG_MODE_BYTE_MASK (0x1 << QSPI_RD_CFG_MODE_BYTE_SHIFT) +#define QSPI_RD_CFG_CMD_SIGN_SHIFT 9 +#define QSPI_RD_CFG_CMD_SIGN_MASK (0xff << QSPI_RD_CFG_CMD_SIGN_SHIFT) +#define QSPI_RD_CFG_DUMMY_SHIFT 4 +#define QSPI_RD_CFG_DUMMY_MASK (0x1f << QSPI_RD_CFG_DUMMY_SHIFT) +#define QSPI_RD_CFG_D_BUFFER_SHIFT 3 +#define QSPI_RD_CFG_D_BUFFER_MASK (0x1 << QSPI_RD_CFG_D_BUFFER_SHIFT) +#define QSPI_RD_CFG_RD_SCK_SEL_SHIFT 0 +#define QSPI_RD_CFG_RD_SCK_SEL_MASK (0x7 << QSPI_RD_CFG_RD_SCK_SEL_SHIFT) + +#define QSPI_WR_CFG_REG 0x08 +#define QSPI_WR_CFG_WR_CMD_SHIFT 24 +#define QSPI_WR_CFG_WR_CMD_MASK (0xff << QSPI_WR_CFG_WR_CMD_SHIFT) +#define QSPI_WR_CFG_WR_WAIT_SHIFT 9 +#define QSPI_WR_CFG_WR_WAIT_MASK (0x01 << QSPI_WR_CFG_WR_WAIT_SHIFT) +#define QSPI_WR_CFG_WR_THROUGH_SHIFT 8 +#define QSPI_WR_CFG_WR_THROUGH_MASK (0x01 << QSPI_WR_CFG_WR_THROUGH_SHIFT) +#define QSPI_WR_CFG_WR_TRANSFER_SHIFT 5 +#define QSPI_WR_CFG_WR_TRANSFER_MASK (0X7 << QSPI_WR_CFG_WR_TRANSFER_SHIFT) +#define QSPI_WR_CFG_WR_ADDR_SEL_SHIFT 4 +#define QSPI_WR_CFG_WR_ADDR_SEL_MASK (0x1 << QSPI_WR_CFG_WR_ADDR_SEL_SHIFT) +#define QSPI_WR_CFG_WR_MODE_SHIFT 3 +#define QSPI_WR_CFG_WR_MODE_MASK (0x1 << QSPI_WR_CFG_WR_MODE_SHIFT) +#define QSPI_WR_CFG_WR_SCK_SEL_SHIFT 0 +#define QSPI_WR_CFG_WR_SCK_SEL_MASK (0x7 << QSPI_WR_CFG_WR_SCK_SEL_SHIFT) + +#define QSPI_FLUSH_REG 0x0c +#define QSPI_FLUSH_EN (0x1 << 0) + +#define QSPI_CMD_PORT_REG 0x10 +#define QSPI_CMD_PORT_CMD_SHIFT 24 +#define QSPI_CMD_PORT_CMD_MASK (0xff << QSPI_CMD_PORT_CMD_SHIFT) +#define QSPI_CMD_PORT_WAIT_SHIFT 22 +#define QSPI_CMD_PORT_WAIT_MASK (0x1 << QSPI_CMD_PORT_WAIT_SHIFT) +#define QSPI_CMD_PORT_THROUGH_SHIFT 21 +#define QSPI_CMD_PORT_THROUGH_MASK (0x1 << QSPI_CMD_PORT_THROUGH_SHIFT) +#define QSPI_CMD_PORT_CS_SHIFT 19 +#define QSPI_CMD_PORT_CS_MASK (0x3 << QSPI_CMD_PORT_CS_SHIFT) +#define QSPI_CMD_PORT_TRANSFER_SHIFT 16 +#define QSPI_CMD_PORT_TRANSFER_MASK (0x7 << QSPI_CMD_PORT_TRANSFER_SHIFT) +#define QSPI_CMD_PORT_CMD_ADDR_SHIFT 15 +#define QSPI_CMD_PORT_CMD_ADDR_MASK (0x1 << QSPI_CMD_PORT_CMD_ADDR_SHIFT) +#define QSPI_CMD_PORT_LATENCY_SHIFT 14 +#define QSPI_CMD_PORT_LATENCY_MASK (0x1 << QSPI_CMD_PORT_LATENCY_SHIFT) +#define QSPI_CMD_PORT_DATA_XFER_SHIFT 13 +#define QSPI_CMD_PORT_DATA_XFER_MASK (0x1 << QSPI_CMD_PORT_DATA_XFER_SHIFT) +#define QSPI_CMD_PORT_ADDR_SEL_SHIFT 12 +#define QSPI_CMD_PORT_ADDR_SEL_MASK (0x1 << QSPI_CMD_PORT_ADDR_SEL_SHIFT) +#define QSPI_CMD_PORT_DUMMY_SHIFT 7 +#define QSPI_CMD_PORT_DUMMY_MASK (0x1f << QSPI_CMD_PORT_DUMMY_SHIFT) +#define QSPI_CMD_PORT_P_BUFFER_SHIFT 6 +#define QSPI_CMD_PORT_P_BUFFER_MASK (0x1 << QSPI_CMD_PORT_P_BUFFER_SHIFT) +#define QSPI_CMD_PORT_RW_NUM_SHIFT 3 +#define QSPI_CMD_PORT_RW_NUM_MASK (0x7 << QSPI_CMD_PORT_RW_NUM_SHIFT) +#define QSPI_CMD_PORT_SCK_SEL_SHIFT 0 +#define QSPI_CMD_PORT_SCK_SEL_MASK (0x7 << QSPI_CMD_PORT_SCK_SEL_SHIFT) + +#define QSPI_ADDR_PORT_REG 0x14 +#define QSPI_HD_PORT_REG 0x18 +#define QSPI_LD_PORT_REG 0x1c + +#define QSPI_FUN_SET_REG 0x20 +#define QSPI_FUN_SET_HOLD_SHIFT 24 +#define QSPI_FUN_SET_HOLD_MASK (0xff << QSPI_FUN_SET_HOLD_SHIFT) +#define QSPI_FUN_SET_SETUP_SHIFT 16 +#define QSPI_FUN_SET_SETUP_MASK (0xff << QSPI_FUN_SET_SETUP_SHIFT) +#define QSPI_FUN_SET_DELAY_SHIFT 0 +#define QSPI_FUN_SET_DELAY_MASK (0xffff << QSPI_FUN_SET_DELAY_SHIFT) + +#define QSPI_WIP_REG 0x24 +#define QSPI_WIP_W_CMD_SHIFT 24 +#define QSPI_WIP_W_CMD_MASK (0xff << QSPI_WIP_W_CMD_SHIFT) +#define QSPI_WIP_W_TRANSFER_SHIFT 3 +#define QSPI_WIP_W_TRANSFER_MASK (0x3 << QSPI_WIP_W_TRANSFER_SHIFT) +#define QSPI_WIP_W_SCK_SEL_SHIFT 0 +#define QSPI_WIP_W_SCK_SEL_MASK (0x7 << QSPI_WIP_W_SCK_SEL_SHIFT) + +#define QSPI_WP_REG 0x28 +#define QSPI_WP_EN_SHIFT 17 +#define QSPI_WP_EN_MASK (0x1 << QSPI_WP_EN_SHIFT) +#define QSPI_WP_IO2_SHIFT 16 +#define QSPI_WP_IO2_MASK (0x1 << QSPI_WP_IO2_SHIFT) +#define QSPI_WP_HOLD_SHIFT 8 +#define QSPI_WP_HOLD_MASK (0xff << QSPI_WP_HOLD_SHIFT) +#define QSPI_WP_SETUP_SHIFT 0 +#define QSPI_WP_SETUP_MASK (0xff << QSPI_WP_SETUP_SHIFT) + +#define QSPI_MODE_REG 0x2c +#define QSPI_MODE_VALID_SHIFT 8 +#define QSPI_MODE_VALID_MASK (0xff << QSPI_MODE_VALID_SHIFT) +#define QSPI_MODE_SHIFT 0 +#define QSPI_MODE_MASK (0xff << QSPI_MODE_SHIFT) + +#define PHYTIUM_QSPI_MAX_NORCHIP 4 +#define PHYTIUM_QSPI_MAX_MMAP_SZ (SZ_256M * PHYTIUM_QSPI_MAX_NORCHIP) +#define PHYTIUM_QSPI_MAX_XFER_SZ 8 +#define PHYTIUM_QSPI_DEFAULT_SCK_SEL 5 + +#define XFER_PROTO_1_1_1 0x0 +#define XFER_PROTO_1_1_2 0x1 +#define XFER_PROTO_1_1_4 0x2 +#define XFER_PROTO_1_2_2 0x3 +#define XFER_PROTO_1_4_4 0x4 +#define XFER_PROTO_2_2_2 0x5 +#define XFER_PROTO_4_4_4 0x6 + +struct phytium_qspi_flash { + u32 cs; + u32 clk_div; + + void __iomem *base; + resource_size_t size; + struct spi_device *spi; +}; + +struct phytium_qspi { + struct device *dev; + struct spi_controller *ctrl; + + void __iomem *io_base; + void __iomem *mm_base; + resource_size_t mm_size; + resource_size_t used_size; + + struct clk *clk; + u32 clk_rate; + + struct phytium_qspi_flash flash[PHYTIUM_QSPI_MAX_NORCHIP]; + u8 fnum; + bool nodirmap; +}; + +static bool phytium_qspi_check_buswidth(u8 width) +{ + switch (width) { + case 1: + case 2: + case 4: + return 0; + } + + return -EOPNOTSUPP; +} + +static uint phytium_spi_nor_clac_clk_div(int div) +{ + uint clk_div = 0; + + if (div <= 2) + clk_div = 1; + else if (div <= 4) + clk_div = 2; + else if (div <= 8) + clk_div = 3; + else if (div <= 16) + clk_div = 4; + else if (div <= 32) + clk_div = 5; + else if (div <= 64) + clk_div = 6; + else if (div <= 128) + clk_div = 7; + else + clk_div = 65535; + + return clk_div; +} + +static int phytium_spi_nor_protocol_encode(const struct spi_mem_op *op, u32 *code) +{ + int ret = 0; + + if (op->cmd.buswidth == 1 && + op->addr.buswidth == 1 && + op->data.buswidth == 1) + *code = XFER_PROTO_1_1_1; + else if (op->cmd.buswidth == 1 && + op->addr.buswidth == 1 && + op->data.buswidth == 2) + *code = XFER_PROTO_1_1_2; + else if (op->cmd.buswidth == 1 && + op->addr.buswidth == 1 && + op->data.buswidth == 4) + *code = XFER_PROTO_1_1_4; + else if (op->cmd.buswidth == 1 && + op->addr.buswidth == 2 && + op->data.buswidth == 2) + *code = XFER_PROTO_1_2_2; + else if (op->cmd.buswidth == 1 && + op->addr.buswidth == 4 && + op->data.buswidth == 4) + *code = XFER_PROTO_1_4_4; + else if (op->cmd.buswidth == 2 && + op->addr.buswidth == 2 && + op->data.buswidth == 2) + *code = XFER_PROTO_2_2_2; + else if (op->cmd.buswidth == 4 && + op->addr.buswidth == 4 && + op->data.buswidth == 4) + *code = XFER_PROTO_4_4_4; + else + *code = XFER_PROTO_1_1_1; + + return ret; +} + +static int phytium_qspi_flash_capacity_encode(u32 size, u32 *cap) +{ + int ret = 0; + + switch (size) { + case SZ_4M: + *cap = 0x0; + break; + case SZ_8M: + *cap = 0x1; + break; + case SZ_16M: + *cap = 0x2; + break; + case SZ_32M: + *cap = 0x3; + break; + case SZ_64M: + *cap = 0x4; + break; + case SZ_128M: + *cap = 0x5; + break; + case SZ_256M: + *cap = 0x6; + break; + case SZ_512M: + *cap = 0x7; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int phytium_qspi_write_port(struct phytium_qspi *qspi, + const u8 *buf, const size_t len) +{ + u32 bouncebuf[2] = { 0 }; + + if (len > PHYTIUM_QSPI_MAX_XFER_SZ) { + dev_err(qspi->dev, "WRITE data exceeds 8 bytes.\n"); + return -EINVAL; + } + + memcpy(bouncebuf, buf, len); + + if (len > 4) + writel_relaxed(bouncebuf[1], qspi->io_base + QSPI_HD_PORT_REG); + writel_relaxed(bouncebuf[0], qspi->io_base + QSPI_LD_PORT_REG); + + return 0; +} + +static int phytium_qspi_read_port(struct phytium_qspi *qspi, + u8 *buf, size_t len) +{ + u32 bouncebuf[2] = { 0 }; + + if (len > PHYTIUM_QSPI_MAX_XFER_SZ) { + dev_err(qspi->dev, "READ data exceeds 8 bytes.\n"); + return -EINVAL; + } + + /* Dummy write to LD_PORT register and issue READ ops*/ + writel_relaxed(0, qspi->io_base + QSPI_LD_PORT_REG); + + /* Read data */ + bouncebuf[0] = readl_relaxed(qspi->io_base + QSPI_LD_PORT_REG); + if (len > 4) + bouncebuf[1] = readl_relaxed(qspi->io_base + QSPI_HD_PORT_REG); + + memcpy(buf, bouncebuf, len); + + return 0; +} + +static int phytium_qspi_adjust_op_size(struct spi_mem *mem, + struct spi_mem_op *op) +{ + if (op->data.nbytes > PHYTIUM_QSPI_MAX_XFER_SZ) + op->data.nbytes = PHYTIUM_QSPI_MAX_XFER_SZ; + + return 0; +} + +static bool phytium_qspi_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + int ret; + + ret = phytium_qspi_check_buswidth(op->cmd.buswidth); + + if (op->addr.nbytes) + ret |= phytium_qspi_check_buswidth(op->addr.buswidth); + + if (op->dummy.nbytes) + ret |= phytium_qspi_check_buswidth(op->dummy.buswidth); + + if (op->data.nbytes) + ret |= phytium_qspi_check_buswidth(op->data.buswidth); + + if (ret) + return false; + + /* Max 32 dummy clock cycles supported */ + if (op->dummy.nbytes && + (op->dummy.nbytes * 8 / op->dummy.buswidth > 32)) + return false; + + return spi_mem_default_supports_op(mem, op); +} + +static int phytium_qspi_exec_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + struct phytium_qspi *qspi = spi_controller_get_devdata(mem->spi->master); + struct phytium_qspi_flash *flash = &qspi->flash[mem->spi->chip_select]; + u32 cmd, transfer; + int ret; + + dev_dbg(qspi->dev, "cmd:%#x mode: %d.%d.%d.%d addr:%#llx len:%#x\n", + op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth, + op->dummy.buswidth, op->data.buswidth, op->addr.val, + op->data.nbytes); + + cmd = op->cmd.opcode << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + + ret = phytium_spi_nor_protocol_encode(op, &transfer); + if (ret) { + dev_err(qspi->dev, "Unsupported SPI NOR protocol.\n"); + goto out; + } + cmd |= transfer << QSPI_CMD_PORT_TRANSFER_SHIFT; + + if (op->addr.nbytes) { + cmd |= QSPI_CMD_PORT_CMD_ADDR_MASK; + if (op->addr.nbytes == 4) + cmd |= QSPI_CMD_PORT_ADDR_SEL_MASK; + + /* Write target address to ADDR_PORT register */ + writel_relaxed(op->addr.val, qspi->io_base + QSPI_ADDR_PORT_REG); + } + + if (op->dummy.nbytes) { + cmd |= QSPI_CMD_PORT_LATENCY_MASK; + cmd |= ((op->dummy.nbytes * 8) / op->dummy.buswidth) << + QSPI_CMD_PORT_LATENCY_SHIFT; + } + + if (op->data.nbytes) { + cmd |= QSPI_CMD_PORT_DATA_XFER_MASK; + cmd &= ~QSPI_CMD_PORT_P_BUFFER_MASK; + cmd |= (op->data.nbytes-1) << QSPI_CMD_PORT_RW_NUM_SHIFT; + } + + cmd |= flash->clk_div; + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + + if (op->data.dir == SPI_MEM_DATA_IN) { + ret = phytium_qspi_read_port(qspi, op->data.buf.in, op->data.nbytes); + if (ret) { + dev_err(qspi->dev, "Failed to read data from the port.\n"); + goto out; + } + } else if (op->data.dir == SPI_MEM_DATA_OUT) { + ret = phytium_qspi_write_port(qspi, op->data.buf.out, op->data.nbytes); + if (ret) { + dev_err(qspi->dev, "Failed to write data to the port.\n"); + goto out; + } + } else { + /* Dummy write to LD_PORT register and issue the command */ + writel_relaxed(0, qspi->io_base + QSPI_LD_PORT_REG); + } + +out: + return ret; +} + +static int phytium_qspi_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + struct spi_device *spi = desc->mem->spi; + struct phytium_qspi *qspi = spi_controller_get_devdata(spi->master); + struct phytium_qspi_flash *flash = &qspi->flash[spi->chip_select]; + struct spi_nor *nor = spi_mem_get_drvdata(desc->mem); + u32 cmd, transfer; + int ret = 0; + + if (!qspi->mm_base || !qspi->mm_size) { + ret = -EOPNOTSUPP; + goto out; + } + + if (!flash->base) { + flash->base = qspi->mm_base + qspi->used_size; + qspi->used_size += nor->mtd.size; + } + + /* Setup RD/WR_CFG register */ + if (desc->info.op_tmpl.data.dir == SPI_MEM_DATA_IN) { + cmd = desc->info.op_tmpl.cmd.opcode << QSPI_RD_CFG_RD_CMD_SHIFT; + ret = phytium_spi_nor_protocol_encode(&desc->info.op_tmpl, &transfer); + if (ret) { + dev_err(qspi->dev, "Unsupported SPI NOR protocol.\n"); + goto out; + } + cmd |= transfer << QSPI_RD_CFG_RD_TRANSFER_SHIFT; + + if (desc->info.op_tmpl.addr.nbytes == 4) + cmd |= QSPI_RD_CFG_RD_ADDR_SEL_MASK; + + if (nor->read_dummy) { + cmd |= QSPI_RD_CFG_RD_LATENCY_MASK; + cmd |= (nor->read_dummy - 1) << QSPI_RD_CFG_DUMMY_SHIFT; + } + + cmd |= QSPI_RD_CFG_D_BUFFER_MASK; + cmd |= flash->clk_div & QSPI_RD_CFG_RD_SCK_SEL_MASK; + + writel_relaxed(cmd, qspi->io_base + QSPI_RD_CFG_REG); + + dev_dbg(qspi->dev, "Create read dirmap and setup RD_CFG_REG [%#x].\n", cmd); + } else if (desc->info.op_tmpl.data.dir == SPI_MEM_DATA_OUT) { + cmd = desc->info.op_tmpl.cmd.opcode << QSPI_WR_CFG_WR_CMD_SHIFT; + ret = phytium_spi_nor_protocol_encode(&desc->info.op_tmpl, &transfer); + if (ret) { + dev_err(qspi->dev, "Unsupported SPI NOR protocol.\n"); + goto out; + } + cmd |= transfer << QSPI_WR_CFG_WR_TRANSFER_SHIFT; + + if (desc->info.op_tmpl.addr.nbytes == 4) + cmd |= QSPI_WR_CFG_WR_ADDR_SEL_MASK; + + cmd |= QSPI_WR_CFG_WR_MODE_MASK; + cmd |= flash->clk_div & QSPI_WR_CFG_WR_SCK_SEL_MASK; + + writel_relaxed(cmd, qspi->io_base + QSPI_WR_CFG_REG); + + dev_dbg(qspi->dev, "Create write dirmap and setup WR_CFG_REG [%#x].\n", cmd); + } else { + ret = -EINVAL; + } + +out: + return ret; +} + +static ssize_t phytium_qspi_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + struct spi_device *spi = desc->mem->spi; + struct phytium_qspi *qspi = spi_controller_get_devdata(spi->master); + struct phytium_qspi_flash *flash = &qspi->flash[spi->chip_select]; + + void __iomem *src = flash->base + offs; + u8 *buf_rx = buf; + + memcpy_fromio(buf_rx, src, len); + + return len; +} + +static ssize_t phytium_qspi_dirmap_write(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, const void *buf) +{ + struct spi_device *spi = desc->mem->spi; + struct phytium_qspi *qspi = spi_controller_get_devdata(spi->master); + struct phytium_qspi_flash *flash = &qspi->flash[spi->chip_select]; + + void __iomem *dst = flash->base + offs; + void __iomem *addr; + int i; + size_t mask = 0x03; + u_char tmp[4] = {0}; + + if (offs & 0x03) { + dev_err(qspi->dev, "Addr not four-byte aligned!\n"); + return -EINVAL; + } + + for (i = 0; i < len / 4; i++) + writel_relaxed(*(u32 *)(buf + 4 * i), dst + 4 * i); + + if (len & mask) { + addr = dst + (len & ~mask); + memcpy(tmp, buf + (len & ~mask), len & mask); + writel_relaxed(*(u32 *)(tmp), addr); + } + + //write cache data to flash + writel_relaxed(QSPI_FLUSH_EN, qspi->io_base + QSPI_FLUSH_REG); + + return len; +} + +static int phytium_qspi_setup(struct spi_device *spi) +{ + struct spi_controller *ctrl = spi->master; + struct phytium_qspi *qspi = spi_controller_get_devdata(ctrl); + struct phytium_qspi_flash *flash; + uint clk_div; + + if (ctrl->busy) + return -EBUSY; + + flash = &qspi->flash[spi->chip_select]; + + flash->cs = spi->chip_select; + flash->spi = spi; + if (flash->cs >= PHYTIUM_QSPI_MAX_NORCHIP) { + dev_err(qspi->dev, "Flash CS is out of range.\n"); + return -EINVAL; + } + qspi->fnum++; + + + if (spi->max_speed_hz) { + clk_div = DIV_ROUND_UP(qspi->clk_rate, spi->max_speed_hz); + flash->clk_div = phytium_spi_nor_clac_clk_div(clk_div); + if (flash->clk_div == 65535) { + dev_err(qspi->dev, "qspi maximum frequency setting is error.\n"); + return -EINVAL; + } + } else + flash->clk_div = PHYTIUM_QSPI_DEFAULT_SCK_SEL; + + return 0; +} + +static struct spi_controller_mem_ops phytium_qspi_mem_ops = { + .adjust_op_size = phytium_qspi_adjust_op_size, + .supports_op = phytium_qspi_supports_op, + .exec_op = phytium_qspi_exec_op, + .dirmap_create = phytium_qspi_dirmap_create, + .dirmap_read = phytium_qspi_dirmap_read, + .dirmap_write = phytium_qspi_dirmap_write, +}; + +/** + * Direct mapping is supported only when all flashes under the controller + * are of the same size and the mapping address is continuous. For those + * cases which flashes are of different sizes, the driver offered a non-dirmap + * mem_ops with which read/write ops is executed through command port. + */ +static struct spi_controller_mem_ops phytium_qspi_mem_ops_nodirmap = { + .adjust_op_size = phytium_qspi_adjust_op_size, + .supports_op = phytium_qspi_supports_op, + .exec_op = phytium_qspi_exec_op, +}; + +/** + * phytium_qspi_probe - Probe method for the QSPI driver + * @pdev: Pointer to the platform_device structure + * + * This function initializes the driver data structures and the hardware. + * + * Return: 0 on success and error value on failure + */ +static int phytium_qspi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_controller *ctrl; + struct resource *res; + struct phytium_qspi *qspi; + int i, ret; + u32 flash_cap; + struct spi_mem *mem; + struct spi_nor *nor; + const char **reg_name_array; + + ctrl = spi_alloc_master(dev, sizeof(*qspi)); + if (!ctrl) + return -ENOMEM; + + ctrl->mode_bits = SPI_CPOL | SPI_CPHA | + SPI_RX_DUAL | SPI_RX_QUAD | + SPI_TX_DUAL | SPI_TX_QUAD; + ctrl->setup = phytium_qspi_setup; + ctrl->num_chipselect = PHYTIUM_QSPI_MAX_NORCHIP; + if (IS_ENABLED(CONFIG_OF)) + ctrl->dev.of_node = dev->of_node; + else if (IS_ENABLED(CONFIG_ACPI) && has_acpi_companion(dev)) + ctrl->dev.fwnode = dev->fwnode; + + qspi = spi_controller_get_devdata(ctrl); + qspi->ctrl = ctrl; + + reg_name_array = kcalloc(4, sizeof(*reg_name_array), GFP_KERNEL); + if (dev->of_node) + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi"); + else if (has_acpi_companion(dev)) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + fwnode_property_read_string_array(dev->fwnode, + "reg-names", reg_name_array, 2); + res->name = reg_name_array[0]; + } + qspi->io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(qspi->io_base)) { + ret = PTR_ERR(qspi->io_base); + goto probe_master_put; + } + + if (dev->of_node) + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mm"); + else if (has_acpi_companion(dev)) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + res->name = reg_name_array[1]; + } + + qspi->mm_base = devm_ioremap_resource(dev, res); + if (IS_ERR(qspi->mm_base)) { + ret = PTR_ERR(qspi->mm_base); + goto probe_master_put; + } + + qspi->mm_size = resource_size(res); + if (qspi->mm_size > PHYTIUM_QSPI_MAX_MMAP_SZ) { + ret = -EINVAL; + goto probe_master_put; + } + qspi->used_size = 0; + + if (dev->of_node) { + qspi->clk = devm_clk_get(dev, NULL); + if (IS_ERR(qspi->clk)) { + ret = PTR_ERR(qspi->clk); + goto probe_master_put; + } + + qspi->clk_rate = clk_get_rate(qspi->clk); + if (!qspi->clk_rate) { + ret = -EINVAL; + goto probe_master_put; + } + + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + goto probe_master_put; + } + + ret = clk_prepare_enable(qspi->clk); + if (ret) { + dev_err(dev, "Failed to enable PCLK of the controller.\n"); + goto probe_clk_failed; + } + } else if (has_acpi_companion(dev)) { + qspi->clk_rate = 50000000; + } + qspi->nodirmap = device_property_present(dev, "no-direct-mapping"); + ctrl->mem_ops = qspi->nodirmap ? + &phytium_qspi_mem_ops_nodirmap : + &phytium_qspi_mem_ops; + + qspi->dev = dev; + platform_set_drvdata(pdev, qspi); + + ret = devm_spi_register_controller(dev, ctrl); + if (ret) { + dev_err(dev, "failed to register SPI controller: %d\n", ret); + goto probe_setup_failed; + } + + if (!qspi->nodirmap) { + /* + * The controller supports direct mapping access only if all + * flashes are of same size. + */ + + i = 0; + for (i = 0; qspi->fnum > i && i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (qspi->flash[i].spi) { + mem = spi_get_drvdata(qspi->flash[i].spi); + if (mem) { + nor = spi_mem_get_drvdata(mem); + if (nor) + qspi->flash[i].size = nor->mtd.size; + } + } + } + + for (i = 1; qspi->fnum > i && i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (qspi->flash[i].size != qspi->flash[0].size) { + dev_err(dev, "Flashes are of different sizes.\n"); + ret = -EINVAL; + goto probe_setup_failed; + } + } + + ret = phytium_qspi_flash_capacity_encode(qspi->flash[0].size, + &flash_cap); + if (ret) { + dev_err(dev, "Flash size is invalid.\n"); + goto probe_setup_failed; + } + + flash_cap |= qspi->fnum << QSPI_FLASH_CAP_NUM_SHIFT; + + writel_relaxed(flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + } + + return 0; + +probe_setup_failed: + clk_disable_unprepare(qspi->clk); +probe_clk_failed: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); +probe_master_put: + + return ret; +} + +/** + * phytium_qspi_remove - Remove method for the QSPI driver + * @pdev: Pointer to the platform_device structure + * + * This function is called if a device is physically removed from the system + * or if the driver module is being unloaded. It free all resources allocated + * to the device. + * + * Return: 0 on success and error value on failure + */ +static int phytium_qspi_remove(struct platform_device *pdev) +{ + struct phytium_qspi *qspi = platform_get_drvdata(pdev); + + clk_disable_unprepare(qspi->clk); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int __maybe_unused phytium_qspi_suspend(struct device *dev) +{ + return pm_runtime_force_suspend(dev); +} + +static int __maybe_unused phytium_qspi_resume(struct device *dev) +{ + return pm_runtime_force_resume(dev); +} + +static const struct dev_pm_ops phytium_qspi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(phytium_qspi_suspend, + phytium_qspi_resume) +}; + +static const struct of_device_id phytium_qspi_of_match[] = { + { .compatible = "phytium,qspi-nor" }, + { } +}; +static const struct acpi_device_id phytium_qspi_acpi_match[] = { + { "PHYT0011", 0 }, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_qspi_of_match); +MODULE_DEVICE_TABLE(acpi, phytium_qspi_acpi_match); + +static struct platform_driver phytium_qspi_driver = { + .probe = phytium_qspi_probe, + .remove = phytium_qspi_remove, + .driver = { + .name = "phytium-qspi", + .of_match_table = of_match_ptr(phytium_qspi_of_match), + .acpi_match_table = ACPI_PTR(phytium_qspi_acpi_match), + .pm = &phytium_qspi_pm_ops, + }, +}; +module_platform_driver(phytium_qspi_driver); + +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Phytium Quad SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c new file mode 100644 index 0000000000000000000000000000000000000000..8fdd3a9e47594f48aee969f7ba469b765f2d2058 --- /dev/null +++ b/drivers/spi/spi-phytium.c @@ -0,0 +1,502 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd.. + * + * Derived from drivers/spi/spi-dw.c + * Copyright (c) 2009, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-phytium.h" + +struct phytium_spi_chip { + u8 poll_mode; + u8 type; + void (*cs_control)(u32 command); +}; + +struct chip_data { + u8 cs; + u8 tmode; + u8 type; + + u8 poll_mode; + + u16 clk_div; + u32 speed_hz; + void (*cs_control)(u32 command); +}; + +static void phytium_spi_set_cs(struct spi_device *spi, bool enable) +{ + struct phytium_spi *fts = spi_master_get_devdata(spi->master); + struct chip_data *chip = spi_get_ctldata(spi); + u32 origin; + + if (chip && chip->cs_control) + chip->cs_control(!enable); + + if (!enable) { + phytium_writel(fts, SER, BIT(spi->chip_select)); + if (fts->global_cs) { + origin = phytium_readl(fts, GCSR); + phytium_writel(fts, GCSR, origin | (1 << spi->chip_select)); + } + } else { + if (fts->global_cs) { + origin = phytium_readl(fts, GCSR); + phytium_writel(fts, GCSR, origin & ~(1 << spi->chip_select)); + } + } +} + +static inline u32 tx_max(struct phytium_spi *fts) +{ + u32 tx_left, tx_room, rxtx_gap; + + tx_left = (fts->tx_end - fts->tx) / fts->n_bytes; + tx_room = fts->fifo_len - phytium_readl(fts, TXFLR); + + rxtx_gap = ((fts->rx_end - fts->rx) - (fts->tx_end - fts->tx)) + / fts->n_bytes; + + return min3(tx_left, tx_room, (u32) (fts->fifo_len - rxtx_gap)); +} + +static inline u32 rx_max(struct phytium_spi *fts) +{ + u32 rx_left = (fts->rx_end - fts->rx) / fts->n_bytes; + + return min_t(u32, rx_left, phytium_readl(fts, RXFLR)); +} + +static void phytium_writer(struct phytium_spi *fts) +{ + u32 max = tx_max(fts); + u16 txw = 0; + + while (max--) { + if (fts->tx_end - fts->len) { + if (fts->n_bytes == 1) + txw = *(u8 *)(fts->tx); + else + txw = *(u16 *)(fts->tx); + } + phytium_write_io_reg(fts, DR, txw); + fts->tx += fts->n_bytes; + } +} + +static void phytium_reader(struct phytium_spi *fts) +{ + u32 max = rx_max(fts); + u16 rxw; + + while (max--) { + rxw = phytium_read_io_reg(fts, DR); + if (fts->rx_end - fts->len) { + if (fts->n_bytes == 1) + *(u8 *)(fts->rx) = rxw; + else + *(u16 *)(fts->rx) = rxw; + } + fts->rx += fts->n_bytes; + } +} +int phytium_spi_check_status(struct phytium_spi *fts, bool raw) +{ + u32 irq_status; + int ret = 0; + + if (raw) + irq_status = phytium_readl(fts, RISR); + else + irq_status = phytium_readl(fts, ISR); + + if (irq_status & INT_RXOI) { + dev_err(&fts->master->dev, "RX FIFO overflow detected\n"); + ret = -EIO; + } + + if (irq_status & INT_RXUI) { + dev_err(&fts->master->dev, "RX FIFO underflow detected\n"); + ret = -EIO; + } + + if (irq_status & INT_TXOI) { + dev_err(&fts->master->dev, "TX FIFO overflow detected\n"); + ret = -EIO; + } + + /* Generically handle the erroneous situation */ + if (ret) { + spi_reset_chip(fts); + if (fts->master->cur_msg) + fts->master->cur_msg->status = ret; + } + return ret; +} +EXPORT_SYMBOL_GPL(phytium_spi_check_status); + +static void int_error_stop(struct phytium_spi *fts, const char *msg) +{ + spi_reset_chip(fts); + + dev_err(&fts->master->dev, "%s\n", msg); + fts->master->cur_msg->status = -EIO; + spi_finalize_current_transfer(fts->master); +} + +static irqreturn_t interrupt_transfer(struct phytium_spi *fts) +{ + u16 irq_status = phytium_readl(fts, ISR); + + if (irq_status & (INT_TXOI | INT_RXOI | INT_RXUI)) { + phytium_readl(fts, ICR); + int_error_stop(fts, "irq transfer: fifo overrun/underrun"); + return IRQ_HANDLED; + } + + phytium_reader(fts); + if (fts->rx_end == fts->rx) { + spi_mask_intr(fts, INT_TXEI); + spi_finalize_current_transfer(fts->master); + return IRQ_HANDLED; + } + if (irq_status & INT_TXEI) { + spi_mask_intr(fts, INT_TXEI); + phytium_writer(fts); + spi_umask_intr(fts, INT_TXEI); + } + + return IRQ_HANDLED; +} + +static irqreturn_t phytium_spi_irq(int irq, void *dev_id) +{ + struct spi_master *master = dev_id; + struct phytium_spi *fts = spi_master_get_devdata(master); + u16 irq_status = phytium_readl(fts, ISR) & 0x3f; + + if (!irq_status) + return IRQ_NONE; + + if (!master->cur_msg) { + spi_mask_intr(fts, INT_TXEI); + return IRQ_HANDLED; + } + + if (fts->transfer_handler) + return fts->transfer_handler(fts); + else + return IRQ_HANDLED; +} + +static int poll_transfer(struct phytium_spi *fts) +{ + do { + phytium_writer(fts); + phytium_reader(fts); + cpu_relax(); + } while (fts->rx_end > fts->rx); + + return 0; +} + +static int phytium_spi_transfer_one(struct spi_master *master, + struct spi_device *spi, struct spi_transfer *transfer) +{ + struct phytium_spi *fts = spi_master_get_devdata(master); + struct chip_data *chip = spi_get_ctldata(spi); + u8 imask = 0; + u16 txlevel = 0; + u16 clk_div; + u32 cr0; + int ret = 0; + + fts->dma_mapped = 0; + fts->tx = (void *)transfer->tx_buf; + fts->tx_end = fts->tx + transfer->len; + fts->rx = transfer->rx_buf; + fts->rx_end = fts->rx + transfer->len; + fts->len = transfer->len; + + spi_enable_chip(fts, 0); + + if (transfer->speed_hz != fts->current_freq) { + if (transfer->speed_hz != chip->speed_hz) { + clk_div = (fts->max_freq / transfer->speed_hz + 1) & + 0xfffe; + + chip->speed_hz = transfer->speed_hz; + chip->clk_div = clk_div; + } + fts->current_freq = transfer->speed_hz; + spi_set_clk(fts, chip->clk_div); + } + + if (transfer->bits_per_word == 8) + fts->n_bytes = 1; + else if (transfer->bits_per_word == 16) + fts->n_bytes = 2; + else + return -EINVAL; + + cr0 = (transfer->bits_per_word - 1) + | (chip->type << FRF_OFFSET) + | (spi->mode << MODE_OFFSET) + | (chip->tmode << TMOD_OFFSET); + + if (chip->cs_control) { + if (fts->rx && fts->tx) + chip->tmode = TMOD_TR; + else if (fts->rx) + chip->tmode = TMOD_RO; + else + chip->tmode = TMOD_TO; + + cr0 &= ~TMOD_MASK; + cr0 |= (chip->tmode << TMOD_OFFSET); + } + + phytium_writel(fts, CTRLR0, cr0); + + /* check if current transfer is a DMA transcation */ + if (master->can_dma && master->can_dma(master, spi, transfer)) + fts->dma_mapped = master->cur_msg_mapped; + + spi_mask_intr(fts, 0xff); + + /* DMA setup */ + if (fts->dma_mapped) { + ret = fts->dma_ops->dma_setup(fts, transfer); + if (ret) + return ret; + } + /* interrupt transfer mode setup */ + if (!chip->poll_mode && !fts->dma_mapped) { + txlevel = min_t(u16, fts->fifo_len / 2, fts->len / fts->n_bytes); + phytium_writel(fts, TXFLTR, txlevel); + + imask |= INT_TXEI | INT_TXOI | + INT_RXUI | INT_RXOI; + spi_umask_intr(fts, imask); + + fts->transfer_handler = interrupt_transfer; + } + + spi_enable_chip(fts, 1); + + if (fts->dma_mapped) + return fts->dma_ops->dma_transfer(fts, transfer); + + if (chip->poll_mode) + return poll_transfer(fts); + + return 1; +} + +static void phytium_spi_handle_err(struct spi_master *master, + struct spi_message *msg) +{ + struct phytium_spi *fts = spi_master_get_devdata(master); + + if (fts->dma_mapped) + fts->dma_ops->dma_stop(fts); + + spi_reset_chip(fts); +} + +static int phytium_spi_setup(struct spi_device *spi) +{ + struct phytium_spi_chip *chip_info = NULL; + struct chip_data *chip; + struct spi_master *master = spi->master; + struct phytium_spi *fts = spi_master_get_devdata(master); + u32 cr0; + + spi_enable_chip(fts, 0); + + chip = spi_get_ctldata(spi); + if (!chip) { + chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + spi_set_ctldata(spi, chip); + } + + chip_info = spi->controller_data; + + if (chip_info) { + if (chip_info->cs_control) + chip->cs_control = chip_info->cs_control; + + chip->poll_mode = chip_info->poll_mode; + chip->type = chip_info->type; + } + + chip->tmode = 0; + + cr0 = (spi->bits_per_word - 1) | (chip->type << FRF_OFFSET) | + (spi->mode << MODE_OFFSET) | (chip->tmode << TMOD_OFFSET); + + phytium_writel(fts, CTRLR0, cr0); + + spi_enable_chip(fts, 1); + + return 0; +} + +static void phytium_spi_cleanup(struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata(spi); + + kfree(chip); + spi_set_ctldata(spi, NULL); +} + +static void spi_hw_init(struct device *dev, struct phytium_spi *fts) +{ + spi_reset_chip(fts); + + if (!fts->fifo_len) { + u32 fifo; + + for (fifo = 1; fifo < 256; fifo++) { + phytium_writel(fts, TXFLTR, fifo); + if (fifo != phytium_readl(fts, TXFLTR)) + break; + } + phytium_writel(fts, TXFLTR, 0); + + fts->fifo_len = (fifo == 1) ? 0 : fifo; + dev_dbg(dev, "Detected FIFO size: %u bytes\n", fts->fifo_len); + } +} + +int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts) +{ + struct spi_master *master; + int ret; + + WARN_ON(fts == NULL); + + master = spi_alloc_master(dev, 0); + if (!master) + return -ENOMEM; + + fts->master = master; + fts->dma_addr = (dma_addr_t)(fts->paddr + DR); + snprintf(fts->name, sizeof(fts->name), "phytium_spi%d", fts->bus_num); + + ret = request_irq(fts->irq, phytium_spi_irq, IRQF_SHARED, + fts->name, master); + if (ret < 0) { + dev_err(dev, "can not get IRQ\n"); + goto err_free_master; + } + + master->use_gpio_descriptors = true; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; + master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16); + master->bus_num = fts->bus_num; + master->num_chipselect = fts->num_cs; + master->setup = phytium_spi_setup; + master->cleanup = phytium_spi_cleanup; + master->set_cs = phytium_spi_set_cs; + master->transfer_one = phytium_spi_transfer_one; + master->handle_err = phytium_spi_handle_err; + master->max_speed_hz = fts->max_freq; + master->dev.of_node = dev->of_node; + master->dev.fwnode = dev->fwnode; + master->flags = SPI_CONTROLLER_GPIO_SS; + + spi_hw_init(dev, fts); + + if (fts->dma_ops && fts->dma_ops->dma_init) { + ret = fts->dma_ops->dma_init(dev, fts); + if (ret) { + dev_warn(dev, "DMA init failed\n"); + } else { + master->can_dma = fts->dma_ops->can_dma; + master->flags |= SPI_CONTROLLER_MUST_TX; + } + } + + spi_master_set_devdata(master, fts); + ret = spi_register_controller(master); + if (ret) { + dev_err(&master->dev, "problem registering spi master\n"); + goto err_exit; + } + + return 0; + +err_exit: + if (fts->dma_ops && fts->dma_ops->dma_exit) + fts->dma_ops->dma_exit(fts); + spi_enable_chip(fts, 0); + free_irq(fts->irq, master); +err_free_master: + spi_master_put(master); + return ret; +} +EXPORT_SYMBOL_GPL(phytium_spi_add_host); + +void phytium_spi_remove_host(struct phytium_spi *fts) +{ + if (fts->dma_ops && fts->dma_ops->dma_exit) + fts->dma_ops->dma_exit(fts); + spi_shutdown_chip(fts); + + spi_unregister_controller(fts->master); + + free_irq(fts->irq, fts->master); +} +EXPORT_SYMBOL_GPL(phytium_spi_remove_host); + +int phytium_spi_suspend_host(struct phytium_spi *fts) +{ + int ret; + + ret = spi_controller_suspend(fts->master); + if (ret) + return ret; + + spi_shutdown_chip(fts); + return 0; +} +EXPORT_SYMBOL_GPL(phytium_spi_suspend_host); + +int phytium_spi_resume_host(struct phytium_spi *fts) +{ + int ret; + + spi_hw_init(&fts->master->dev, fts); + ret = spi_controller_resume(fts->master); + if (ret) + dev_err(&fts->master->dev, "fail to start queue (%d)\n", ret); + return ret; +} +EXPORT_SYMBOL_GPL(phytium_spi_resume_host); + +MODULE_AUTHOR("Zhu Mingshuai "); +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h new file mode 100644 index 0000000000000000000000000000000000000000..6906de00a5cb63df60946b9bf60013713dc6c267 --- /dev/null +++ b/drivers/spi/spi-phytium.h @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium SPI controller driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + */ +#ifndef PHYTIUM_SPI_HEADER_H +#define PHYTIUM_SPI_HEADER_H + +#include +#include +#include + +#define CTRLR0 0x00 +#define SSIENR 0x08 +#define SER 0x10 +#define BAUDR 0x14 +#define TXFLTR 0x18 +#define TXFLR 0x20 +#define RXFLR 0x24 +#define SR 0x28 +#define IMR 0x2c +#define ISR 0x30 +#define RISR 0x34 +#define ICR 0x48 +#define DMACR 0x4C +#define DMATDLR 0x50 +#define DMARDLR 0x54 +#define DR 0x60 +#define GCSR 0x100 + +#define FRF_OFFSET 4 +#define MODE_OFFSET 6 +#define TMOD_OFFSET 8 + +#define TMOD_MASK (0x3 << TMOD_OFFSET) +#define TMOD_TR 0x0 +#define TMOD_TO 0x1 +#define TMOD_RO 0x2 + +#define INT_TXEI (1 << 0) +#define INT_TXOI (1 << 1) +#define INT_RXUI (1 << 2) +#define INT_RXOI (1 << 3) +/* Bit fields in SR, 7 bits */ +#define SR_MASK 0x7f /* cover 7 bits */ +#define SR_BUSY (1 << 0) +#define SR_TF_NOT_FULL (1 << 1) +#define SR_TF_EMPT (1 << 2) +#define SR_RF_NOT_EMPT (1 << 3) +#define SR_RF_FULL (1 << 4) +#define SR_TX_ERR (1 << 5) +#define SR_DCOL (1 << 6) +/* Bit fields in DMACR */ +#define SPI_DMA_RDMAE (1 << 0) +#define SPI_DMA_TDMAE (1 << 1) +#define SPI_WAIT_RETRIES 5 +struct phytium_spi; +struct phytium_spi_dma_ops { + int (*dma_init)(struct device *dev, struct phytium_spi *fts); + void (*dma_exit)(struct phytium_spi *fts); + int (*dma_setup)(struct phytium_spi *fts, struct spi_transfer *xfer); + bool (*can_dma)(struct spi_controller *master, struct spi_device *spi, + struct spi_transfer *xfer); + int (*dma_transfer)(struct phytium_spi *fts, struct spi_transfer *xfer); + void (*dma_stop)(struct phytium_spi *fts); +}; + +struct phytium_spi { + struct spi_master *master; + char name[16]; + + void __iomem *regs; + bool global_cs; + bool dma_en; + unsigned long paddr; + int irq; + u32 fifo_len; + u32 max_freq; + + u32 reg_io_width; + u16 bus_num; + u16 num_cs; + int *cs; + + size_t len; + void *tx; + void *tx_end; + void *rx; + void *rx_end; + u8 n_bytes; + int dma_mapped; + irqreturn_t (*transfer_handler)(struct phytium_spi *fts); + /* DMA info */ + u32 current_freq; /* frequency in hz */ + struct dma_chan *txchan; + u32 txburst; + struct dma_chan *rxchan; + u32 rxburst; + u32 dma_sg_burst; + unsigned long dma_chan_busy; + dma_addr_t dma_addr; /* phy address of the Data register */ + const struct phytium_spi_dma_ops *dma_ops; + struct completion dma_completion; +}; + +static inline u32 phytium_readl(struct phytium_spi *fts, u32 offset) +{ + return __raw_readl(fts->regs + offset); +} +static inline u16 phytium_readw(struct phytium_spi *fts, u32 offset) +{ + return __raw_readw(fts->regs + offset); +} +static inline void phytium_writel(struct phytium_spi *fts, u32 offset, u32 val) +{ + __raw_writel(val, fts->regs + offset); +} +static inline void phytium_writew(struct phytium_spi *fts, u32 offset, u16 val) +{ + __raw_writew(val, fts->regs + offset); +} +static inline u32 phytium_read_io_reg(struct phytium_spi *fts, u32 offset) +{ + switch (fts->reg_io_width) { + case 2: + return phytium_readw(fts, offset); + case 4: + default: + return phytium_readl(fts, offset); + } +} +static inline void phytium_write_io_reg(struct phytium_spi *fts, + u32 offset, u32 val) +{ + switch (fts->reg_io_width) { + case 2: + phytium_writew(fts, offset, val); + break; + case 4: + default: + phytium_writel(fts, offset, val); + break; + } +} +static inline void spi_enable_chip(struct phytium_spi *fts, int enable) +{ + phytium_writel(fts, SSIENR, (enable ? 1 : 0)); +} +static inline void spi_set_clk(struct phytium_spi *fts, u16 div) +{ + phytium_writel(fts, BAUDR, div); +} +static inline void spi_mask_intr(struct phytium_spi *fts, u32 mask) +{ + u32 new_mask; + + new_mask = phytium_readl(fts, IMR) & ~mask; + phytium_writel(fts, IMR, new_mask); +} +static inline void spi_umask_intr(struct phytium_spi *fts, u32 mask) +{ + u32 new_mask; + + new_mask = phytium_readl(fts, IMR) | mask; + phytium_writel(fts, IMR, new_mask); +} +static inline void spi_global_cs(struct phytium_spi *fts) +{ + u32 global_cs_en, mask, setmask; + + mask = GENMASK(fts->num_cs-1, 0) << fts->num_cs; + setmask = ~GENMASK(fts->num_cs-1, 0); + global_cs_en = (phytium_readl(fts, GCSR) | mask) & setmask; + phytium_writel(fts, GCSR, global_cs_en); +} +static inline void spi_reset_chip(struct phytium_spi *fts) +{ + spi_enable_chip(fts, 0); + if (fts->global_cs) + spi_global_cs(fts); + spi_mask_intr(fts, 0xff); + spi_enable_chip(fts, 1); +} +static inline void spi_shutdown_chip(struct phytium_spi *fts) +{ + spi_enable_chip(fts, 0); + spi_set_clk(fts, 0); + fts->current_freq = 0; +} +extern int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts); +extern void phytium_spi_remove_host(struct phytium_spi *fts); +extern int phytium_spi_suspend_host(struct phytium_spi *fts); +extern int phytium_spi_resume_host(struct phytium_spi *fts); +extern void phytium_spi_dmaops_set(struct phytium_spi *fts); +extern int phytium_spi_check_status(struct phytium_spi *fts, bool raw); +#endif /* PHYTIUM_SPI_HEADER_H */ diff --git a/drivers/tee/optee/Kconfig b/drivers/tee/optee/Kconfig index 70898bbd58095951744b8fd0a566ebd9249b8daf..dc097c2867e59bec25340684b64177eb47959f9f 100644 --- a/drivers/tee/optee/Kconfig +++ b/drivers/tee/optee/Kconfig @@ -24,3 +24,33 @@ config OPTEE_INSECURE_LOAD_IMAGE Additional documentation on kernel security risks are at Documentation/staging/tee.rst. + +if OPTEE + +choice + prompt "Default conduit method" + default OPTEE_DEFAULT_METHOD_NONE + help + This option sets the default conduit method for OP-TEE in case + firmware misses "method" property. If in doubt, select "none" + which depends on firmware to provide the value. + +config OPTEE_DEFAULT_METHOD_NONE + bool "none" + help + There is no default conduit method used by the driver. Require + firmware to provide the method explicitly. + +config OPTEE_DEFAULT_METHOD_HVC + bool "hvc" + help + Use the "hvc" as default conduit method. + +config OPTEE_DEFAULT_METHOD_SMC + bool "smc" + help + Use the "hvc" as default conduit method. + +endchoice + +endif diff --git a/drivers/tee/optee/smc_abi.c b/drivers/tee/optee/smc_abi.c index d5b28fd35d665378591674a8baaeffac5bcd77c0..e2b16275df1d9772a4b12ccdfb9f81241afcd0cc 100644 --- a/drivers/tee/optee/smc_abi.c +++ b/drivers/tee/optee/smc_abi.c @@ -6,6 +6,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include #include #include #include @@ -1424,6 +1425,14 @@ static void optee_smccc_hvc(unsigned long a0, unsigned long a1, arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res); } +#if defined(CONFIG_OPTEE_DEFAULT_METHOD_HVC) +#define DEFAULT_CONDUIT_METHOD optee_smccc_hvc +#elif defined(CONFIG_OPTEE_DEFAULT_METHOD_SMC) +#define DEFAULT_CONDUIT_METHOD optee_smccc_smc +#else +#define DEFAULT_CONDUIT_METHOD ERR_PTR(-ENXIO) +#endif + static optee_invoke_fn *get_invoke_func(struct device *dev) { const char *method; @@ -1432,7 +1441,7 @@ static optee_invoke_fn *get_invoke_func(struct device *dev) if (device_property_read_string(dev, "method", &method)) { pr_warn("missing \"method\" property\n"); - return ERR_PTR(-ENXIO); + return DEFAULT_CONDUIT_METHOD; } if (!strcmp("hvc", method)) @@ -1820,6 +1829,14 @@ static const struct of_device_id optee_dt_match[] = { }; MODULE_DEVICE_TABLE(of, optee_dt_match); +#ifdef CONFIG_ACPI +static const struct acpi_device_id optee_acpi_match[] = { + { "PHYT8003" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, optee_acpi_match); +#endif + static struct platform_driver optee_driver = { .probe = optee_probe, .remove = optee_smc_remove, @@ -1827,6 +1844,7 @@ static struct platform_driver optee_driver = { .driver = { .name = "optee", .of_match_table = optee_dt_match, + .acpi_match_table = ACPI_PTR(optee_acpi_match), }, }; diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index 5fc8540a83e31530b1a1404e8ac849a8f60b19dc..8559ba1361c6450b09556aeaa172ab0cafc0ae7d 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -3156,6 +3156,8 @@ static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc) mutex_unlock(&gsm->mutex); /* Now wipe the queues */ tty_ldisc_flush(gsm->tty); + + guard(spinlock_irqsave)(&gsm->tx_lock); list_for_each_entry_safe(txq, ntxq, &gsm->tx_ctrl_list, list) kfree(txq); INIT_LIST_HEAD(&gsm->tx_ctrl_list); diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index bdc568a4ab66930b963dabb94bdd0daf617cc03e..6ddca3ee3dd13d48d1a36f7d1be93db3d9257190 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -85,6 +85,18 @@ config SERIAL_EARLYCON_SEMIHOST This is enabled with "earlycon=smh" on the kernel command line. The console is enabled when early_param is processed. +config SERIAL_PHYTIUM_PCI + tristate "Phytium PCI serial port support" + depends on PCI + select SERIAL_CORE + help + This driver supports the Phytium UART controller on PCI/PCIe adapters. + If you want to compile this driver into the kernel, say Y here. To + compile this driver as a module, choose M here. + + If unsure, say N. + + config SERIAL_EARLYCON_RISCV_SBI bool "Early console using RISC-V SBI" depends on RISCV_SBI_V01 diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 138abbc8973812269c27aa595df1071bf678b4ba..cd1d9c759cac022917b58b8d8296999bc1be730a 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -88,6 +88,7 @@ obj-$(CONFIG_SERIAL_MILBEAUT_USIO) += milbeaut_usio.o obj-$(CONFIG_SERIAL_SIFIVE) += sifive.o obj-$(CONFIG_SERIAL_LITEUART) += liteuart.o obj-$(CONFIG_SERIAL_SUNPLUS) += sunplus-uart.o +obj-$(CONFIG_SERIAL_PHYTIUM_PCI) += phytium-uart.o # GPIOLIB helpers for modem control lines obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o diff --git a/drivers/tty/serial/phytium-uart.c b/drivers/tty/serial/phytium-uart.c new file mode 100644 index 0000000000000000000000000000000000000000..1839203fc306a71ebee8d087803d9eea26bec65e --- /dev/null +++ b/drivers/tty/serial/phytium-uart.c @@ -0,0 +1,927 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Phytium PCI UART controller + * + * Copyright (c) 2021-2023, Phytium Techonology Co., Ltd. + * + * Derived from drivers/tty/serial/amba-pl011.c + * Copyright 1999 ARM Limited + * Copyright (C) 2000 Deep Blue Solutions Ltd. + * Copyright (C) 2010 ST-Ericsson SA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "phytium_uart" + +#define REG_DR 0x00 +#define REG_FR 0x18 +#define REG_IBRD 0x24 +#define REG_FBRD 0x28 +#define REG_LCRH_RX 0x2c +#define REG_LCRH_TX 0x2c +#define REG_CR 0x30 +#define REG_IFLS 0x34 +#define REG_IMSC 0x38 +#define REG_RIS 0x3c +#define REG_MIS 0x40 +#define REG_ICR 0x44 + +#define REG_DR_OE (1 << 11) +#define REG_DR_BE (1 << 10) +#define REG_DR_PE (1 << 9) +#define REG_DR_FE (1 << 8) + +#define REG_LCRH_SPS 0x80 +#define REG_LCRH_WLEN_8 0x60 +#define REG_LCRH_WLEN_7 0x40 +#define REG_LCRH_WLEN_6 0x20 +#define REG_LCRH_WLEN_5 0x00 +#define REG_LCRH_FEN 0x10 +#define REG_LCRH_STP2 0x08 +#define REG_LCRH_EPS 0x04 +#define REG_LCRH_PEN 0x02 +#define REG_LCRH_BRK 0x01 + +#define REG_FR_RI 0x100 +#define REG_FR_TXFE 0x080 +#define REG_FR_RXFF 0x040 +#define REG_FR_TXFF 0x020 +#define REG_FR_RXFE 0x010 +#define REG_FR_BUSY 0x008 +#define REG_FR_DCD 0x004 +#define REG_FR_DSR 0x002 +#define REG_FR_CTS 0x001 +#define REG_FR_TMSK (REG_FR_TXFF + REG_FR_BUSY) + +#define REG_CR_CTSEN 0x8000 /* CTS hardware flow control */ +#define REG_CR_RTSEN 0x4000 /* RTS hardware flow control */ +#define REG_CR_OUT2 0x2000 /* OUT2 */ +#define REG_CR_OUT1 0x1000 /* OUT1 */ +#define REG_CR_RTS 0x0800 /* RTS */ +#define REG_CR_DTR 0x0400 /* DTR */ +#define REG_CR_RXE 0x0200 /* receive enable */ +#define REG_CR_TXE 0x0100 /* transmit enable */ +#define REG_CR_LBE 0x0080 /* loopback enable */ +#define REG_CR_RTIE 0x0040 +#define REG_CR_TIE 0x0020 +#define REG_CR_RIE 0x0010 +#define REG_CR_MSIE 0x0008 +#define REG_CR_IIRLP 0x0004 /* SIR low power mode */ +#define REG_CR_SIREN 0x0002 /* SIR enable */ +#define REG_CR_UARTEN 0x0001 /* UART enable */ + +#define REG_IFLS_RX1_8 (0 << 3) +#define REG_IFLS_RX2_8 (1 << 3) +#define REG_IFLS_RX4_8 (2 << 3) +#define REG_IFLS_RX6_8 (3 << 3) +#define REG_IFLS_RX7_8 (4 << 3) +#define REG_IFLS_TX1_8 (0 << 0) +#define REG_IFLS_TX2_8 (1 << 0) +#define REG_IFLS_TX4_8 (2 << 0) +#define REG_IFLS_TX6_8 (3 << 0) + +#define REG_IMSC_OEIM (1 << 10) /* overrun error interrupt mask */ +#define REG_IMSC_BEIM (1 << 9) /* break error interrupt mask */ +#define REG_IMSC_PEIM (1 << 8) /* parity error interrupt mask */ +#define REG_IMSC_FEIM (1 << 7) /* framing error interrupt mask */ +#define REG_IMSC_RTIM (1 << 6) /* receive timeout interrupt mask */ +#define REG_IMSC_TXIM (1 << 5) /* transmit interrupt mask */ +#define REG_IMSC_RXIM (1 << 4) /* receive interrupt mask */ +#define REG_IMSC_DSRMIM (1 << 3) /* DSR interrupt mask */ +#define REG_IMSC_DCDMIM (1 << 2) /* DCD interrupt mask */ +#define REG_IMSC_CTSMIM (1 << 1) /* CTS interrupt mask */ +#define REG_IMSC_RIMIM (1 << 0) /* RI interrupt mask */ + +#define REG_ICR_OEIS (1 << 10) /* overrun error interrupt status */ +#define REG_ICR_BEIS (1 << 9) /* break error interrupt status */ +#define REG_ICR_PEIS (1 << 8) /* parity error interrupt status */ +#define REG_ICR_FEIS (1 << 7) /* framing error interrupt status */ +#define REG_ICR_RTIS (1 << 6) /* receive timeout interrupt status */ +#define REG_ICR_TXIS (1 << 5) /* transmit interrupt status */ +#define REG_ICR_RXIS (1 << 4) /* receive interrupt status */ +#define REG_ICR_DSRMIS (1 << 3) /* DSR interrupt status */ +#define REG_ICR_DCDMIS (1 << 2) /* DCD interrupt status */ +#define REG_ICR_CTSMIS (1 << 1) /* CTS interrupt status */ +#define REG_ICR_RIMIS (1 << 0) /* RI interrupt status */ + +#define UART_NR 12 + +#define UART_DR_ERROR (REG_DR_OE|REG_DR_BE|REG_DR_PE|REG_DR_FE) +#define UART_DUMMY_DR_RX (1 << 16) + +#define DEFAULT_UARTCLK 48000000 /* 48 MHz */ + +/* + * We wrap our port structure around the generic uart_port. + */ +struct phytium_uart_port { + struct uart_port port; + unsigned int im; /* interrupt mask */ + unsigned int old_status; + unsigned int old_cr; /* state during shutdown */ + char type[12]; +}; + +static unsigned int phytium_uart_read(const struct phytium_uart_port *pup, + unsigned int reg) +{ + void __iomem *addr = pup->port.membase + reg; + + return readl_relaxed(addr); +} + +static void phytium_uart_write(unsigned int val, const struct phytium_uart_port *pup, + unsigned int reg) +{ + void __iomem *addr = pup->port.membase + reg; + + writel_relaxed(val, addr); +} + +static int phytium_fifo_to_tty(struct phytium_uart_port *pup) +{ + u16 status; + unsigned int ch, flag, fifotaken; + + for (fifotaken = 0; fifotaken < 256; fifotaken++) { + status = phytium_uart_read(pup, REG_FR); + if (status & REG_FR_RXFE) + break; + + /* Take chars from the FIFO and update status */ + ch = phytium_uart_read(pup, REG_DR) | UART_DUMMY_DR_RX; + flag = TTY_NORMAL; + pup->port.icount.rx++; + + if (unlikely(ch & UART_DR_ERROR)) { + if (ch & REG_DR_BE) { + ch &= ~(REG_DR_FE | REG_DR_PE); + pup->port.icount.brk++; + if (uart_handle_break(&pup->port)) + continue; + } else if (ch & REG_DR_PE) + pup->port.icount.parity++; + else if (ch & REG_DR_FE) + pup->port.icount.frame++; + if (ch & REG_DR_OE) + pup->port.icount.overrun++; + + ch &= pup->port.read_status_mask; + + if (ch & REG_DR_BE) + flag = TTY_BREAK; + else if (ch & REG_DR_PE) + flag = TTY_PARITY; + else if (ch & REG_DR_FE) + flag = TTY_FRAME; + } + + if (uart_handle_sysrq_char(&pup->port, ch & 255)) + continue; + + uart_insert_char(&pup->port, ch, REG_DR_OE, ch, flag); + } + + return fifotaken; +} + +static void phytium_rx_chars(struct phytium_uart_port *pup) +__releases(&pup->port.lock) +__acquires(&pup->port.lock) +{ + phytium_fifo_to_tty(pup); + + spin_unlock(&pup->port.lock); + tty_flip_buffer_push(&pup->port.state->port); + spin_lock(&pup->port.lock); +} + +static void phytium_stop_tx(struct uart_port *port) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + + pup->im &= ~REG_IMSC_TXIM; + phytium_uart_write(pup->im, pup, REG_IMSC); +} + +static bool phytium_tx_char(struct phytium_uart_port *pup, unsigned char c, + bool from_irq) +{ + + if (unlikely(!from_irq) && + phytium_uart_read(pup, REG_FR) & REG_FR_TXFF) + return false; /* unable to transmit character */ + + phytium_uart_write(c, pup, REG_DR); + pup->port.icount.tx++; + + return true; +} + +static bool phytium_tx_chars(struct phytium_uart_port *pup, bool from_irq) +{ + struct circ_buf *xmit = &pup->port.state->xmit; + int count = pup->port.fifosize >> 1; + + if (pup->port.x_char) { + if (!phytium_tx_char(pup, pup->port.x_char, from_irq)) + return true; + pup->port.x_char = 0; + --count; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(&pup->port)) { + phytium_stop_tx(&pup->port); + return false; + } + + do { + if (likely(from_irq) && count-- == 0) + break; + + if (!phytium_tx_char(pup, xmit->buf[xmit->tail], from_irq)) + break; + + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + } while (!uart_circ_empty(xmit)); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&pup->port); + + if (uart_circ_empty(xmit)) { + phytium_stop_tx(&pup->port); + return false; + } + return true; +} + +static void phytium_modem_status(struct phytium_uart_port *pup) +{ + unsigned int status, delta; + + status = phytium_uart_read(pup, REG_FR) & (REG_FR_DCD|REG_FR_DSR|REG_FR_CTS); + + delta = status ^ pup->old_status; + pup->old_status = status; + + if (!delta) + return; + + if (delta & REG_FR_DCD) + uart_handle_dcd_change(&pup->port, status & REG_FR_DCD); + + if (delta & REG_FR_DSR) + pup->port.icount.dsr++; + + if (delta & REG_FR_CTS) + uart_handle_cts_change(&pup->port, status & REG_FR_CTS); + + wake_up_interruptible(&pup->port.state->port.delta_msr_wait); +} + +static irqreturn_t phytium_uart_interrupt(int irq, void *dev_id) +{ + struct phytium_uart_port *pup = dev_id; + unsigned long flags; + unsigned int status, pass_counter = 256; + int handled = 0; + + spin_lock_irqsave(&pup->port.lock, flags); + status = phytium_uart_read(pup, REG_RIS) & pup->im; + if (status) { + do { + phytium_uart_write(status & ~(REG_ICR_TXIS|REG_ICR_RTIS|REG_ICR_RXIS), + pup, REG_ICR); + + if (status & (REG_ICR_RTIS|REG_ICR_RXIS)) + phytium_rx_chars(pup); + + if (status & (REG_ICR_DSRMIS|REG_ICR_DCDMIS| + REG_ICR_CTSMIS|REG_ICR_RIMIS)) + phytium_modem_status(pup); + if (status & REG_ICR_TXIS) + phytium_tx_chars(pup, true); + + if (pass_counter-- == 0) + break; + + status = phytium_uart_read(pup, REG_RIS) & pup->im; + } while (status != 0); + handled = 1; + } + spin_unlock_irqrestore(&pup->port.lock, flags); + + return IRQ_RETVAL(handled); +} + +static unsigned int phytium_tx_empty(struct uart_port *port) +{ + unsigned int status; + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + + status = phytium_uart_read(pup, REG_FR) & (REG_FR_BUSY | REG_FR_TXFF); + + return status ? 0 : TIOCSER_TEMT; +} + +static void phytium_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + unsigned int cr; + + cr = phytium_uart_read(pup, REG_CR); + +#define TIOCMBIT(tiocmbit, uartbit) \ + do { \ + if (mctrl & tiocmbit) \ + cr |= uartbit; \ + else \ + cr &= ~uartbit; \ + } while (0) + + TIOCMBIT(TIOCM_RTS, REG_CR_RTS); + TIOCMBIT(TIOCM_DTR, REG_CR_DTR); + TIOCMBIT(TIOCM_OUT1, REG_CR_OUT1); + TIOCMBIT(TIOCM_OUT2, REG_CR_OUT2); + TIOCMBIT(TIOCM_LOOP, REG_CR_LBE); + + if (port->status & UPSTAT_AUTORTS) { + /* We need to disable auto-RTS if we want to turn RTS off */ + TIOCMBIT(TIOCM_RTS, REG_CR_RTSEN); + } +#undef TIOCMBIT + + phytium_uart_write(cr, pup, REG_CR); +} + +static unsigned int phytium_get_mctrl(struct uart_port *port) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + unsigned int cr = 0; + unsigned int status = phytium_uart_read(pup, REG_FR); + +#define TIOCMBIT(uartbit, tiocmbit) \ + do { \ + if (status & uartbit) \ + cr |= tiocmbit; \ + } while (0) + + TIOCMBIT(REG_FR_DCD, TIOCM_CAR); + TIOCMBIT(REG_FR_DSR, TIOCM_DSR); + TIOCMBIT(REG_FR_CTS, TIOCM_CTS); + TIOCMBIT(REG_FR_RI, TIOCM_RNG); +#undef TIOCMBIT + return cr; +} + +static void phytium_start_tx(struct uart_port *port) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + + if (phytium_tx_chars(pup, false)) { + pup->im |= REG_IMSC_TXIM; + phytium_uart_write(pup->im, pup, REG_IMSC); + } +} + +static void phytium_stop_rx(struct uart_port *port) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + + pup->im &= ~(REG_IMSC_RXIM|REG_IMSC_RTIM|REG_IMSC_FEIM| + REG_IMSC_PEIM|REG_IMSC_BEIM|REG_IMSC_OEIM); + phytium_uart_write(pup->im, pup, REG_IMSC); +} + +static void phytium_enable_ms(struct uart_port *port) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + + pup->im |= REG_IMSC_RIMIM|REG_IMSC_CTSMIM|REG_IMSC_DCDMIM|REG_IMSC_DSRMIM; + phytium_uart_write(pup->im, pup, REG_IMSC); +} + +static void phytium_break_ctl(struct uart_port *port, int break_state) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + unsigned long flags; + unsigned int lcr_h; + + spin_lock_irqsave(&pup->port.lock, flags); + lcr_h = phytium_uart_read(pup, REG_LCRH_TX); + if (break_state == -1) + lcr_h |= REG_LCRH_BRK; + else + lcr_h &= ~REG_LCRH_BRK; + phytium_uart_write(lcr_h, pup, REG_LCRH_TX); + spin_unlock_irqrestore(&pup->port.lock, flags); +} + +static int phytium_hwinit(struct uart_port *port) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + + /* XXX: more configurable setup method in future */ + pup->port.uartclk = DEFAULT_UARTCLK; + + /* Clear pending error and receive interrupts */ + phytium_uart_write(REG_ICR_OEIS | REG_ICR_BEIS | REG_ICR_PEIS | + REG_ICR_FEIS | REG_ICR_RTIS | REG_ICR_RXIS, + pup, REG_ICR); + + /* + * Save interrupts enable mask, and enable RX interrupts in case if + * the interrupt is used for NMI entry. + */ + pup->im = phytium_uart_read(pup, REG_IMSC); + phytium_uart_write(REG_IMSC_RTIM | REG_IMSC_RXIM, pup, REG_IMSC); + + return 0; +} + +static int phytium_uart_allocate_irq(struct phytium_uart_port *pup) +{ + phytium_uart_write(pup->im, pup, REG_IMSC); + + return request_irq(pup->port.irq, phytium_uart_interrupt, IRQF_SHARED, DRV_NAME, pup); +} + +static void phytium_enable_interrtups(struct phytium_uart_port *pup) +{ + unsigned int i; + + spin_lock_irq(&pup->port.lock); + + /* Clear out any spuriously appearing RX interrupts */ + phytium_uart_write(REG_ICR_RTIS | REG_ICR_RXIS, pup, REG_ICR); + + /* + * RXIS is asserted only when the RX FIFO transitions from below + * to above the trigger threshold. If the RX FIFO is already + * full to the threashold this can't happen and RXIS will now be + * stuck off. Drain the RX FIFO explicitly to fix this: + */ + for (i = 0; i < pup->port.fifosize * 2; i++) { + if (phytium_uart_read(pup, REG_FR) & REG_FR_RXFE) + break; + + phytium_uart_read(pup, REG_DR); + } + + pup->im = REG_IMSC_RTIM | REG_IMSC_RXIM; + phytium_uart_write(pup->im, pup, REG_IMSC); + spin_unlock_irq(&pup->port.lock); +} + +static int phytium_startup(struct uart_port *port) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + unsigned int cr; + int ret = 0; + + ret = phytium_hwinit(port); + if (ret) + goto out; + + ret = phytium_uart_allocate_irq(pup); + if (ret) + goto out; + + phytium_uart_write(REG_IFLS_RX4_8|REG_IFLS_TX4_8, pup, REG_IFLS); + + spin_lock_irq(&pup->port.lock); + + /* restore RTS and DTR */ + cr = pup->old_cr & (REG_CR_RTS | REG_CR_DTR); + cr |= REG_CR_UARTEN | REG_CR_RXE | REG_CR_TXE; + phytium_uart_write(cr, pup, REG_CR); + + spin_unlock_irq(&pup->port.lock); + + /* initialise the old status of the modem signals */ + pup->old_status = phytium_uart_read(pup, REG_FR) & (REG_FR_DCD|REG_FR_DSR|REG_FR_CTS); + + phytium_enable_interrtups(pup); + +out: + return ret; +} + +static void phytium_shutdown_channel(struct phytium_uart_port *pup, + unsigned int lcrh) +{ + unsigned long val; + + val = phytium_uart_read(pup, lcrh); + val &= ~(REG_LCRH_BRK | REG_LCRH_FEN); + phytium_uart_write(val, pup, lcrh); +} + +static void phytium_disable_uart(struct phytium_uart_port *pup) +{ + unsigned int cr; + + pup->port.status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); + spin_lock_irq(&pup->port.lock); + cr = phytium_uart_read(pup, REG_CR); + pup->old_cr = cr; + cr &= REG_CR_RTS | REG_CR_DTR; + cr |= REG_CR_UARTEN | REG_CR_TXE; + phytium_uart_write(cr, pup, REG_CR); + spin_unlock_irq(&pup->port.lock); + + /* + * disable break condition and fifos + */ + phytium_shutdown_channel(pup, REG_LCRH_RX); +} + +static void phytium_disable_interrupts(struct phytium_uart_port *pup) +{ + spin_lock_irq(&pup->port.lock); + + /* mask all interrupts and clear all pending ones */ + pup->im = 0; + phytium_uart_write(pup->im, pup, REG_IMSC); + phytium_uart_write(0xffff, pup, REG_ICR); + + spin_unlock_irq(&pup->port.lock); +} + +static void phytium_shutdown(struct uart_port *port) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + + phytium_disable_interrupts(pup); + + free_irq(pup->port.irq, pup); + + phytium_disable_uart(pup); + + if (pup->port.ops->flush_buffer) + pup->port.ops->flush_buffer(port); +} + +static void +phytium_setup_status_masks(struct uart_port *port, struct ktermios *termios) +{ + port->read_status_mask = REG_DR_OE | 255; + if (termios->c_iflag & INPCK) + port->read_status_mask |= REG_DR_FE | REG_DR_PE; + if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK)) + port->read_status_mask |= REG_DR_BE; + + /* + * Characters to ignore + */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= REG_DR_FE | REG_DR_PE; + if (termios->c_iflag & IGNBRK) { + port->ignore_status_mask |= REG_DR_BE; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= REG_DR_OE; + } + + /* + * Ignore all characters if CREAD is not set. + */ + if ((termios->c_cflag & CREAD) == 0) + port->ignore_status_mask |= UART_DUMMY_DR_RX; +} + +static void +phytium_set_termios(struct uart_port *port, struct ktermios *termios, const struct ktermios *old) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + unsigned int lcr_h, old_cr; + unsigned long flags; + unsigned int baud, quot; + + /* Ask the core to calculate the divisor for us. */ + baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16); + + if (baud > port->uartclk/16) + quot = DIV_ROUND_CLOSEST(port->uartclk * 8, baud); + else + quot = DIV_ROUND_CLOSEST(port->uartclk * 4, baud); + + switch (termios->c_cflag & CSIZE) { + case CS5: + lcr_h = REG_LCRH_WLEN_5; + break; + case CS6: + lcr_h = REG_LCRH_WLEN_6; + break; + case CS7: + lcr_h = REG_LCRH_WLEN_7; + break; + default: /* CS8 */ + lcr_h = REG_LCRH_WLEN_8; + break; + } + if (termios->c_cflag & CSTOPB) + lcr_h |= REG_LCRH_STP2; + if (termios->c_cflag & PARENB) { + lcr_h |= REG_LCRH_PEN; + if (!(termios->c_cflag & PARODD)) + lcr_h |= REG_LCRH_EPS; + if (termios->c_cflag & CMSPAR) + lcr_h |= REG_LCRH_SPS; + } + if (pup->port.fifosize > 1) + lcr_h |= REG_LCRH_FEN; + + spin_lock_irqsave(&port->lock, flags); + + /* + * Update the per-port timeout. + */ + uart_update_timeout(port, termios->c_cflag, baud); + + phytium_setup_status_masks(port, termios); + + if (UART_ENABLE_MS(port, termios->c_cflag)) + phytium_enable_ms(port); + + /* first, disable everything */ + old_cr = phytium_uart_read(pup, REG_CR); + phytium_uart_write(0, pup, REG_CR); + + if (termios->c_cflag & CRTSCTS) { + if (old_cr & REG_CR_RTS) + old_cr |= REG_CR_RTSEN; + + old_cr |= REG_CR_CTSEN; + port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; + } else { + old_cr &= ~(REG_CR_CTSEN | REG_CR_RTSEN); + port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); + } + + /* Set baud rate */ + phytium_uart_write(quot & 0x3f, pup, REG_FBRD); + phytium_uart_write(quot >> 6, pup, REG_IBRD); + + phytium_uart_write(lcr_h, pup, REG_LCRH_RX); + phytium_uart_write(old_cr, pup, REG_CR); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static const char *phytium_type(struct uart_port *port) +{ + struct phytium_uart_port *pup = + container_of(port, struct phytium_uart_port, port); + return pup->port.type == PORT_PHYTIUM ? pup->type : NULL; +} + +static void phytium_release_port(struct uart_port *port) +{ + /* Nothing to release ... */ +} + +static int phytium_request_port(struct uart_port *port) +{ + /* UARTs always present */ + return 0; +} + +static void phytium_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) { + port->type = PORT_PHYTIUM; + phytium_request_port(port); + } +} + +static int phytium_verify_port(struct uart_port *port, struct serial_struct *ser) +{ + int ret = 0; + + if (ser->type != PORT_UNKNOWN && ser->type != PORT_PHYTIUM) + ret = -EINVAL; + if (ser->irq < 0 || ser->irq >= nr_irqs) + ret = -EINVAL; + if (ser->baud_base < 9600) + ret = -EINVAL; + + return ret; +} + +static const struct uart_ops phytium_uart_ops = { + .tx_empty = phytium_tx_empty, + .set_mctrl = phytium_set_mctrl, + .get_mctrl = phytium_get_mctrl, + .stop_tx = phytium_stop_tx, + .start_tx = phytium_start_tx, + .stop_rx = phytium_stop_rx, + .enable_ms = phytium_enable_ms, + .break_ctl = phytium_break_ctl, + .startup = phytium_startup, + .shutdown = phytium_shutdown, + .set_termios = phytium_set_termios, + .type = phytium_type, + .release_port = phytium_release_port, + .request_port = phytium_request_port, + .config_port = phytium_config_port, + .verify_port = phytium_verify_port, +}; + +static struct phytium_uart_port *uart_ports[UART_NR]; + +static struct uart_driver phytium_uart = { + .owner = THIS_MODULE, + .driver_name = DRV_NAME, + .dev_name = "ttyS", + .nr = UART_NR, +}; + +void phytium_unregister_port(struct phytium_uart_port *pup) +{ + int i; + bool busy = false; + + for (i = 0; i < ARRAY_SIZE(uart_ports); i++) { + if (uart_ports[i] == pup) + uart_ports[i] = NULL; + else if (uart_ports[i]) + busy = true; + } + + if (!busy) + uart_unregister_driver(&phytium_uart); +} + +static int phytium_find_free_port(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(uart_ports); i++) + if (uart_ports[i] == NULL) + return i; + + return -EBUSY; +} + +static int phytium_register_port(struct phytium_uart_port *pup) +{ + int rc; + + /* Ensure interrupts from this UART are masked and cleared */ + phytium_uart_write(0, pup, REG_IMSC); + phytium_uart_write(0xffff, pup, REG_ICR); + + if (!phytium_uart.state) { + rc = uart_register_driver(&phytium_uart); + if (rc < 0) { + dev_err(pup->port.dev, + "Failed to register Phytium PCI UART driver\n"); + return rc; + } + } + + rc = uart_add_one_port(&phytium_uart, &pup->port); + if (rc) + phytium_unregister_port(pup); + + return rc; +} + +static int phytium_uart_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct phytium_uart_port *pup; + int portnr, rc; + + portnr = phytium_find_free_port(); + if (portnr < 0) + return portnr; + + pup = devm_kzalloc(&pdev->dev, sizeof(struct phytium_uart_port), + GFP_KERNEL); + if (!pup) + return -ENOMEM; + + rc = pcim_enable_device(pdev); + if (rc) + return rc; + + rc = pcim_iomap_regions_request_all(pdev, 0x01, pci_name(pdev)); + if (rc) + return rc; + + pup->port.iotype = UPIO_MEM32; + pup->port.irq = pdev->irq; + pup->port.mapbase = pci_resource_start(pdev, 0); + pup->port.membase = pcim_iomap_table(pdev)[0]; + pup->port.ops = &phytium_uart_ops; + pup->port.dev = &pdev->dev; + pup->port.fifosize = 32; + pup->port.flags = UPF_BOOT_AUTOCONF; + pup->port.line = portnr; + + uart_ports[portnr] = pup; + + pup->old_cr = 0; + snprintf(pup->type, sizeof(pup->type), "pci-uart"); + + pci_set_drvdata(pdev, pup); + + return phytium_register_port(pup); +} + +static void phytium_uart_remove(struct pci_dev *pdev) +{ + struct phytium_uart_port *pup = pci_get_drvdata(pdev); + + uart_remove_one_port(&phytium_uart, &pup->port); + phytium_unregister_port(pup); +} + +#ifdef CONFIG_PM_SLEEP +static int phytium_uart_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct phytium_uart_port *pup = pci_get_drvdata(pdev); + + if (pup) + uart_suspend_port(&phytium_uart, &pup->port); + + return 0; +} + +static int phytium_uart_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct phytium_uart_port *pup = pci_get_drvdata(pdev); + + if (pup) + uart_resume_port(&phytium_uart, &pup->port); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_dev_pm_ops, phytium_uart_suspend, phytium_uart_resume); + +static const struct pci_device_id pci_ids[] = { + { PCI_VDEVICE(PHYTIUM, 0xdc2e) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, pci_ids); + +static struct pci_driver phytium_uart_pci_driver = { + .name = DRV_NAME, + .probe = phytium_uart_probe, + .remove = phytium_uart_remove, + .driver = { + .pm = &phytium_dev_pm_ops, + }, + .id_table = pci_ids, +}; + +static int __init phytium_uart_init(void) +{ + pr_info("Serial: Phytium PCI UART driver\n"); + + return pci_register_driver(&phytium_uart_pci_driver); +} + +static void __exit phytium_uart_exit(void) +{ + pci_unregister_driver(&phytium_uart_pci_driver); +} + +module_init(phytium_uart_init); +module_exit(phytium_uart_exit); + +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Phytium PCI serial port driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 7f33bcc315f27bf0cde170cb8f53fbc500e86754..7eaccbc6a5b13e5d6bcbe90ba102c170b0d9c459 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -129,6 +129,8 @@ source "drivers/usb/chipidea/Kconfig" source "drivers/usb/isp1760/Kconfig" +source "drivers/usb/phytium/Kconfig" + comment "USB port drivers" if USB diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 949eca0adebea36f36099a915c6d4fb8a18a2e26..0bb5d64f16b0c2ff545737feb48af75756bf782c 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_USB_CDNS3) += cdns3/ obj-$(CONFIG_USB_CDNSP_PCI) += cdns3/ obj-$(CONFIG_USB_FOTG210) += fotg210/ +obj-$(CONFIG_USB_PHYTIUM) += phytium/ obj-$(CONFIG_USB_MON) += mon/ obj-$(CONFIG_USB_MTU3) += mtu3/ diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index ac42ea2795c51d9eece3b665754df33aac3e6448..e7d31ef2950c40b83d1e69f00b078c005d4963b5 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -62,6 +62,7 @@ #define PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_XHCI 0x1138 #define PCI_DEVICE_ID_INTEL_ALDER_LAKE_PCH_XHCI 0x51ed #define PCI_DEVICE_ID_INTEL_ALDER_LAKE_N_PCH_XHCI 0x54ed +#define PCI_DEVICE_ID_PHYTIUM_XHCI 0xdc27 #define PCI_DEVICE_ID_AMD_RENOIR_XHCI 0x1639 #define PCI_DEVICE_ID_AMD_PROMONTORYA_4 0x43b9 @@ -481,6 +482,10 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) if (pdev->vendor == PCI_VENDOR_ID_VIA) xhci->quirks |= XHCI_RESET_ON_RESUME; + if (pdev->vendor == PCI_VENDOR_ID_PHYTIUM || + pdev->device == PCI_DEVICE_ID_PHYTIUM_XHCI) + xhci->quirks |= XHCI_RESET_ON_RESUME; + /* See https://bugzilla.kernel.org/show_bug.cgi?id=79511 */ if (pdev->vendor == PCI_VENDOR_ID_VIA && pdev->device == 0x3432) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index d68e9abcdc69a6add121a6c1c7ac701fbb3d8886..f0f5d4b9b35b0d9b6e4416ca65b747fae032bb7f 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -113,6 +113,10 @@ static const struct xhci_plat_priv xhci_plat_brcm = { .quirks = XHCI_RESET_ON_RESUME | XHCI_SUSPEND_RESUME_CLKS, }; +static const struct xhci_plat_priv xhci_plat_phytium_pe220x = { + .quirks = XHCI_RESET_ON_RESUME, +}; + static const struct of_device_id usb_xhci_of_match[] = { { .compatible = "generic-xhci", @@ -133,6 +137,9 @@ static const struct of_device_id usb_xhci_of_match[] = { }, { .compatible = "brcm,bcm7445-xhci", .data = &xhci_plat_brcm, + }, { + .compatible = "phytium,pe220x-xhci", + .data = &xhci_plat_phytium_pe220x, }, {}, }; @@ -396,6 +403,8 @@ static int xhci_generic_plat_probe(struct platform_device *pdev) if (pdev->dev.of_node) priv_match = of_device_get_match_data(&pdev->dev); + else if (has_acpi_companion(&pdev->dev)) + priv_match = acpi_device_get_match_data(&pdev->dev); else priv_match = dev_get_platdata(&pdev->dev); @@ -554,6 +563,7 @@ EXPORT_SYMBOL_GPL(xhci_plat_pm_ops); static const struct acpi_device_id usb_xhci_acpi_match[] = { /* XHCI-compliant USB Controller */ { "PNP0D10", }, + { "PHYT0039", (kernel_ulong_t)&xhci_plat_phytium_pe220x }, { } }; MODULE_DEVICE_TABLE(acpi, usb_xhci_acpi_match); diff --git a/drivers/usb/phytium/Kconfig b/drivers/usb/phytium/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..797d7b4ee1034da3e3d2a88f631de11cc1a9690e --- /dev/null +++ b/drivers/usb/phytium/Kconfig @@ -0,0 +1,20 @@ +config USB_PHYTIUM + tristate "PHYTIUM USB Support" + depends on USB + depends on USB_GADGET + help + Say Y or M here if your system has a OTG USB Controller based on PHYTIUM SOC. + + If you choose to build this driver is a dynamically linked modules, the module will + be called phytium-usb.ko + +config USB_PHYTIUM_PCI + tristate "PHYTIUM PCI USB Support" + default n + depends on USB + depends on USB_GADGET + help + Say Y or M here if your system has a OTG USB Controller based on PHYTIUM SOC. + + If you choose to build this driver is a dynamically linked modules, the module will + be called phytium-usb-pci.ko diff --git a/drivers/usb/phytium/Makefile b/drivers/usb/phytium/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..e176c334cba560bf54bbbc92a89ea17faeace449 --- /dev/null +++ b/drivers/usb/phytium/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_USB_PHYTIUM) += phytium-usb.o +obj-$(CONFIG_USB_PHYTIUM_PCI) += phytium-usb-pci.o + +phytium-usb-y := core.o dma.o platform.o host.o gadget.o +phytium-usb-pci-y := core.o dma.o pci.o host.o gadget.o diff --git a/drivers/usb/phytium/core.c b/drivers/usb/phytium/core.c new file mode 100644 index 0000000000000000000000000000000000000000..c0182c0770e5ef5f5baa7d7b1709b844ca8578db --- /dev/null +++ b/drivers/usb/phytium/core.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "core.h" + +int phytium_core_reset(struct phytium_cusb *config, bool skip_wait) +{ + if (!config) + return 0; + + spin_lock_init(&config->lock); + + return 0; +} + +uint32_t phytium_read32(uint32_t *address) +{ + return readl(address); +} + +void phytium_write32(uint32_t *address, uint32_t value) +{ + writel(value, address); +} + +uint16_t phytium_read16(uint16_t *address) +{ + return readw(address); +} + +void phytium_write16(uint16_t *address, uint16_t value) +{ + writew(value, address); +} + +uint8_t phytium_read8(uint8_t *address) +{ + return readb(address); +} + +void phytium_write8(uint8_t *address, uint8_t value) +{ + writeb(value, address); +} + diff --git a/drivers/usb/phytium/core.h b/drivers/usb/phytium/core.h new file mode 100644 index 0000000000000000000000000000000000000000..f563672ccaa9963859ff3e1bf8ce62276d3c0bce --- /dev/null +++ b/drivers/usb/phytium/core.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __PHYTIUM_CORE_H__ +#define __PHYTIUM_CORE_H__ + +#include +#include +#include "host_api.h" +#include "gadget.h" + +#define MAX_EPS_CHANNELS 16 + +struct phytium_ep { + struct phytium_cusb *config; + u16 max_packet; + u8 ep_num; + struct GADGET_EP *gadget_ep; + struct list_head req_list; + struct usb_ep end_point; + char name[12]; + u8 is_tx; + const struct usb_endpoint_descriptor *desc; + u8 busy; +}; + +struct phytium_request { + struct usb_request request; + struct GADGET_REQ *gadget_request; + struct list_head list; + struct phytium_ep *ep; + struct phytium_cusb *config; + u8 is_tx; + u8 epnum; +}; + +struct phytium_cusb { + struct device *dev; + void __iomem *regs; + void __iomem *phy_regs; + int irq; + spinlock_t lock; + enum usb_dr_mode dr_mode; + + struct GADGET_OBJ *gadget_obj; + struct GADGET_CFG gadget_cfg; + struct GADGET_CALLBACKS gadget_callbacks; + struct GADGET_SYSREQ gadget_sysreq; + struct GADGET_DEV *gadget_dev; + void *gadget_priv; + + struct usb_gadget gadget; + struct usb_gadget_driver *gadget_driver; + struct phytium_ep endpoints_tx[MAX_EPS_CHANNELS]; + struct phytium_ep endpoints_rx[MAX_EPS_CHANNELS]; + u8 ep0_data_stage_is_tx; + + struct HOST_OBJ *host_obj; + struct HOST_CFG host_cfg; + struct HOST_CALLBACKS host_callbacks; + struct HOST_SYSREQ host_sysreq; + void *host_priv; + struct usb_hcd *hcd; + + struct DMA_OBJ *dma_obj; + struct DMA_CFG dma_cfg; + struct DMA_CALLBACKS dma_callbacks; + struct DMA_SYSREQ dma_sysreq; + bool isVhubHost; +}; + +int phytium_core_reset(struct phytium_cusb *config, bool skip_wait); + +int phytium_host_init(struct phytium_cusb *config); +int phytium_host_uninit(struct phytium_cusb *config); + +#ifdef CONFIG_PM +int phytium_host_resume(void *priv); +int phytium_host_suspend(void *priv); +int phytium_gadget_resume(void *priv); +int phytium_gadget_suspend(void *priv); +#endif + +int phytium_gadget_init(struct phytium_cusb *config); +int phytium_gadget_uninit(struct phytium_cusb *config); + +uint32_t phytium_read32(uint32_t *address); + +void phytium_write32(uint32_t *address, uint32_t value); + +uint16_t phytium_read16(uint16_t *address); + +void phytium_write16(uint16_t *address, uint16_t value); + +uint8_t phytium_read8(uint8_t *address); + +void phytium_write8(uint8_t *address, uint8_t value); + +#endif diff --git a/drivers/usb/phytium/dma.c b/drivers/usb/phytium/dma.c new file mode 100644 index 0000000000000000000000000000000000000000..acc0674cf5d38bda23e10b2e155723f832de582f --- /dev/null +++ b/drivers/usb/phytium/dma.c @@ -0,0 +1,784 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include "core.h" +#include "dma.h" +#include "hw-regs.h" + +#define TRB_POOL_SIZE (sizeof(struct DMA_Trb) * (NUM_OF_TRB)) +#define TRB_BURST_LENGTH (0x80 << 24) + +#define BUILD_NORMAL_TRB_NO_IOC(trb, data_ptr, data_size, stream_id) { \ + trb.dmaAddr = data_ptr; \ + trb.dmaSize = data_size; \ + trb.ctrl = (stream_id << 16) | TD_TYPE_NORMAL | TDF_CYCLE_BIT; } + +#define BUILD_NORMAL_TRB_NO_IOC_CHAIN(trb, data_ptr, data_size, stream_id) { \ + trb.dmaAddr = data_ptr; \ + trb.dmaSize = data_size; \ + trb.ctrl = (stream_id << 16) | TD_TYPE_NORMAL | TDF_CYCLE_BIT | TDF_CHAIN_BIT; } + +#define BUILD_NORMAL_TRB(trb, data_ptr, data_size, stream_id) { \ + trb.dmaAddr = data_ptr; \ + trb.dmaSize = (data_size) | TRB_BURST_LENGTH; \ + trb.ctrl = (stream_id << 16) | TD_TYPE_NORMAL | TDF_CYCLE_BIT |\ + TDF_INT_ON_COMPLECTION | TDF_INT_ON_SHORT_PACKET; } + +#define BUILD_LINK_TRB(trb, target_ptr) { \ + trb.dmaAddr = target_ptr; \ + trb.dmaSize = 0; \ + trb.ctrl = TD_TYPE_LINK | TDF_CYCLE_BIT | TDF_CHAIN_BIT; } + +#define BUILD_EMPTY_TRB(trb) { \ + trb.dmaAddr = 0; \ + trb.dmaSize = 0; \ + trb.ctrl = 0; } + + +uint32_t divRoundUp(uint32_t divident, uint32_t divisor) +{ + return divisor ? ((divident + divisor - 1) / divisor) : 0; +} + +static inline struct DMA_TrbChainDesc *GetTrbChainDescEntry(struct list_head *list) +{ + return (struct DMA_TrbChainDesc *)((uintptr_t)list - + (uintptr_t)&(((struct DMA_TrbChainDesc *)0)->chainNode)); +} + +static int32_t phytium_dma_probe(struct DMA_CFG *config, struct DMA_SYSREQ *sysReq) +{ + if (!sysReq) + return -EINVAL; + + sysReq->trbMemSize = TRB_POOL_SIZE; + sysReq->privDataSize = sizeof(struct DMA_CONTROLLER); + + return 0; +} + +static int32_t phytium_dma_init(struct DMA_CONTROLLER *priv, + const struct DMA_CFG *config, struct DMA_CALLBACKS *callbacks) +{ + if (!priv || !config || !callbacks) + return -EINVAL; + + if (!config->trbAddr || !config->trbDmaAddr) + return -EINVAL; + + memset((void *)priv, 0, sizeof(struct DMA_CONTROLLER)); + memset((void *)config->trbAddr, 0, TRB_POOL_SIZE); + + priv->trbDMAPoolAddr = config->trbDmaAddr; + priv->trbPoolAddr = config->trbAddr; + priv->regs = (struct DMARegs *)config->regBase; + priv->dmaCfg = *config; + priv->dmaDrv = DMA_GetInstance(); + priv->dmaCallbacks = *callbacks; + priv->isHostCtrlMode = 0; + return 0; +} + +static void phytium_dma_destroy(struct DMA_CONTROLLER *priv) +{ + +} + +static int32_t phytium_dma_start(struct DMA_CONTROLLER *priv) +{ + int i; + + if (!priv) + return -EINVAL; + + priv->dmaMode = DMA_MODE_CHANNEL_INDIVIDUAL; + if ((priv->dmaCfg.dmaModeRx & priv->dmaCfg.dmaModeTx) == 0xFFFF) { + priv->dmaMode = DMA_MODE_GLOBAL_DMULT; + phytium_write32(&priv->regs->conf, DMARF_DMULT); + } else if ((priv->dmaCfg.dmaModeRx | priv->dmaCfg.dmaModeTx)) { + priv->dmaMode = DMA_MODE_GLOBAL_DSING; + phytium_write32(&priv->regs->conf, DMARF_DSING); + } + + for (i = 0; i < MAX_DMA_CHANNELS; i++) { + if (priv->dmaCfg.dmaModeRx & (1 << i)) { + priv->rx[i].dmultEnabled = 1; + priv->rx[i].maxTrbLen = TD_DMULT_MAX_TRB_DATA_SIZE; + priv->rx[i].maxTdLen = TD_DMULT_MAX_TD_DATA_SIZE; + } else { + priv->rx[i].dmultEnabled = 0; + priv->rx[i].maxTrbLen = TD_SING_MAX_TRB_DATA_SIZE; + priv->rx[i].maxTdLen = TD_SING_MAX_TD_DATA_SIZE; + } + priv->rx[i].lastTransferLength = 0; + + if (priv->dmaCfg.dmaModeTx & (1 << i)) { + priv->tx[i].dmultEnabled = 1; + priv->tx[i].maxTrbLen = TD_DMULT_MAX_TRB_DATA_SIZE; + priv->tx[i].maxTdLen = TD_DMULT_MAX_TD_DATA_SIZE; + } else { + priv->tx[i].dmultEnabled = 0; + priv->tx[i].maxTrbLen = TD_SING_MAX_TRB_DATA_SIZE; + priv->tx[i].maxTdLen = TD_SING_MAX_TD_DATA_SIZE; + } + priv->tx[i].lastTransferLength = 0; + } + + return 0; +} + +static uint32_t phytium_dma_stop(struct DMA_CONTROLLER *priv) +{ + + return 0; +} + +static int32_t updateDescBuffer(struct DMA_CONTROLLER *priv, + struct DMA_TrbChainDesc *trbChainDesc, uint16_t status) +{ + uint32_t ep_sel, i; + struct DMA_Channel *channel; + + if (!priv || !trbChainDesc) + return -EINVAL; + + channel = trbChainDesc->channel; + if (!channel) + return -EINVAL; + + if (channel->isIsoc && trbChainDesc->lastTrbIsLink) { + if (channel->trbChainDescList.prev == channel->trbChainDescList.next) + return 0; + } + + for (i = 0; i < trbChainDesc->numOfTrbs; i++) { + if (trbChainDesc->framesDesc) { + if (trbChainDesc->isoError) + trbChainDesc->framesDesc[i].length = 0; + else + trbChainDesc->framesDesc[i].length = + (uint32_t)trbChainDesc->trbPool[i].dmaSize & TD_SIZE_MASK; + } + + trbChainDesc->actualLen += + (uint32_t)trbChainDesc->trbPool[i].dmaSize & TD_SIZE_MASK; + } + + if (trbChainDesc->isoError) + trbChainDesc->actualLen = 0; + + ep_sel = phytium_read32(&priv->regs->ep_sel); + channel->lastTransferLength = trbChainDesc->actualLen; + + if (!trbChainDesc->lastTrbIsLink) { + if (!list_empty(&trbChainDesc->chainNode)) { + for (i = 0; i < trbChainDesc->mapSize; i++) + priv->trbChainFreeMap[(trbChainDesc->start >> 3) + i] = 0; + + trbChainDesc->channel->numOfTrbChain--; + trbChainDesc->reserved = 0; + list_del(&trbChainDesc->chainNode); + trbChainDesc = NULL; + } + } + + if (channel->trbChainDescList.prev == &channel->trbChainDescList) + channel->status = DMA_STATUS_FREE; + + if (priv->dmaCallbacks.complete) + priv->dmaCallbacks.complete(priv->parent, channel->hwUsbEppNum, + channel->isDirTx, false); + + if (channel->trbChainDescList.next != &channel->trbChainDescList) { + trbChainDesc = GetTrbChainDescEntry(channel->trbChainDescList.next); + if (trbChainDesc && trbChainDesc->lastTrbIsLink) { + if (!list_empty(&trbChainDesc->chainNode)) { + for (i = 0; i < trbChainDesc->mapSize; i++) + priv->trbChainFreeMap[(trbChainDesc->start >> 3) + i] = 0; + + trbChainDesc->channel->numOfTrbChain--; + trbChainDesc->reserved = 0; + list_del(&trbChainDesc->chainNode); + } + } + } + phytium_write32(&priv->regs->ep_sel, ep_sel); + + return 0; +} + +static void phytium_dma_isr(struct DMA_CONTROLLER *priv) +{ + uint32_t ep_sts, ep_ists, ep_cfg; + int i; + uint8_t isDirTx, epNum; + struct DMA_Channel *channel; + struct DMA_TrbChainDesc *trbChainDesc; + + if (!priv) + return; + + ep_ists = phytium_read32(&priv->regs->ep_ists); + if (!ep_ists) { + phytium_write32(&priv->regs->ep_sts, DMARF_EP_IOC | + DMARF_EP_ISP | DMARF_EP_TRBERR); + return; + } + + for (i = 0; i < 32; i++) { + if (!(ep_ists & (1 << i))) + continue; + + isDirTx = i > 15 ? DMARD_EP_TX : 0u; + epNum = isDirTx ? i - 16 : i; + phytium_write32(&priv->regs->ep_sel, epNum | isDirTx); + + if (isDirTx) + channel = &priv->tx[epNum]; + else + channel = &priv->rx[epNum]; + + ep_sts = phytium_read32(&priv->regs->ep_sts); + + if (ep_sts & DMARF_EP_TRBERR) + phytium_write32(&priv->regs->ep_sts, DMARF_EP_TRBERR); + + if ((ep_sts & DMARF_EP_IOC) || (ep_sts & DMARF_EP_ISP) || (channel->dmultGuard + & DMARF_EP_IOC) || (channel->dmultGuard & DMARF_EP_ISP)) { +retransmit: + phytium_write32(&priv->regs->ep_sts, DMARF_EP_IOC | DMARF_EP_ISP); + trbChainDesc = GetTrbChainDescEntry(channel->trbChainDescList.next); + if (!(ep_sts & DMARF_EP_TRBERR) && channel->dmultEnabled + && !trbChainDesc->lastTrbIsLink) { + if (!priv->isHostCtrlMode && !channel->isDirTx) { + channel->dmultGuard = 0; + phytium_write32(&priv->regs->ep_sts, DMARF_EP_TRBERR); + ep_cfg = phytium_read32(&priv->regs->ep_cfg); + ep_cfg &= ~DMARV_EP_ENABLED; + phytium_write32(&priv->regs->ep_cfg, (uint32_t)ep_cfg); + updateDescBuffer(priv, trbChainDesc, 0); + break; + } + channel->dmultGuard = ep_sts; + break; + } + + channel->dmultGuard = 0; + updateDescBuffer(priv, trbChainDesc, 0); + } + + if (ep_sts & DMARF_EP_OUTSMM) + phytium_write32(&priv->regs->ep_sts, DMARF_EP_OUTSMM); + + if (ep_sts & DMARF_EP_DESCMIS) + phytium_write32(&priv->regs->ep_sts, DMARF_EP_DESCMIS); + + if (ep_sts & DMARF_EP_ISOERR) { + phytium_write32(&priv->regs->ep_sts, DMARF_EP_ISOERR); + trbChainDesc = GetTrbChainDescEntry(channel->trbChainDescList.next); + trbChainDesc->isoError = 1; + goto retransmit; + } + } +} + +static void phytium_dma_errIsr(struct DMA_CONTROLLER *priv, uint8_t irqNr, uint8_t isDirTx) +{ + struct DMA_Channel *channel; + + if (!priv) + return; + + if (isDirTx) + channel = &priv->tx[irqNr]; + else + channel = &priv->rx[irqNr]; + + if (channel->status >= DMA_STATUS_BUSY) + channel->status = DMA_STATUS_ABORT; + + if (priv->dmaCallbacks.complete) { + if (!irqNr && priv->resubmit) { + priv->dmaCallbacks.complete(priv->parent, channel->hwUsbEppNum, + channel->isDirTx, true); + priv->resubmit = false; + } else + priv->dmaCallbacks.complete(priv->parent, channel->hwUsbEppNum, + channel->isDirTx, false); + } +} + +static void *phytium_dma_channelAlloc(struct DMA_CONTROLLER *priv, + uint8_t isDirTx, uint8_t hwEpNum, uint8_t isIsoc) +{ + struct DMA_Channel *channel; + uint32_t ep_ien, ep_cfg; + uint16_t dmaMode; + + if (!priv || (hwEpNum >= MAX_DMA_CHANNELS)) + return NULL; + + if (!priv->regs) { + pr_err("dma regs is null\n"); + return NULL; + } + + ep_ien = phytium_read32(&priv->regs->ep_ien); + + if (isDirTx) { + if (priv->tx[hwEpNum].status > DMA_STATUS_FREE) + return NULL; + + channel = &priv->tx[hwEpNum]; + channel->isDirTx = 0x80; + ep_ien |= (0x01 << (hwEpNum + 16)); + dmaMode = priv->dmaCfg.dmaModeTx; + } else { + if (priv->rx[hwEpNum].status > DMA_STATUS_FREE) + return NULL; + + channel = &priv->rx[hwEpNum]; + channel->isDirTx = 0x00; + ep_ien |= (0x01 << hwEpNum); + dmaMode = priv->dmaCfg.dmaModeRx; + } + + channel->isIsoc = 0; + if (isIsoc) + channel->isIsoc = 1; + + INIT_LIST_HEAD(&channel->trbChainDescList); + channel->numOfTrbChain = 0; + channel->controller = priv; + channel->dmultGuard = 0; + channel->status = DMA_STATUS_FREE; + channel->hwUsbEppNum = hwEpNum; + + phytium_write32(&priv->regs->ep_sel, (uint32_t)hwEpNum | channel->isDirTx); + ep_cfg = phytium_read32(&priv->regs->ep_cfg); + + if (priv->dmaMode == DMA_MODE_CHANNEL_INDIVIDUAL) { + if (dmaMode & (1 << hwEpNum)) + ep_cfg |= DMARV_EP_DMULT; + else + ep_cfg |= DMARV_EP_DSING; + } + + phytium_write32(&priv->regs->ep_sts, DMARF_EP_IOC | DMARF_EP_ISP | DMARF_EP_TRBERR); + phytium_write32(&priv->regs->ep_cfg, (uint32_t)DMARV_EP_ENABLED | ep_cfg); + phytium_write32(&priv->regs->ep_ien, ep_ien); + phytium_write32(&priv->regs->ep_sts_en, DMARF_EP_TRBERR); + + return channel; +} + +static int32_t phytium_dma_channelRelease(struct DMA_CONTROLLER *priv, struct DMA_Channel *channel) +{ + uint32_t ep_ien, i; + struct DMA_TrbChainDesc *trbChainDesc; + + if (!channel || !priv) + return -EINVAL; + + ep_ien = phytium_read32(&priv->regs->ep_ien); + if (channel->isDirTx) + ep_ien &= ~(0x01 << (channel->hwUsbEppNum + 16)); + else + ep_ien &= ~(0x01 << channel->hwUsbEppNum); + + phytium_write32(&priv->regs->ep_sel, (uint32_t)channel->hwUsbEppNum | channel->isDirTx); + phytium_write32(&priv->regs->ep_cfg, (uint32_t)0); + phytium_write32(&priv->regs->ep_ien, ep_ien); + phytium_write32(&priv->regs->ep_sts_en, 0x0); + phytium_write32(&priv->regs->ep_cmd, DMARF_EP_EPRST); + phytium_write32(&priv->regs->ep_sts, DMARF_EP_IOC | DMARF_EP_ISP | + DMARF_EP_TRBERR | DMARF_EP_ISOERR); + + if (channel->status >= DMA_STATUS_BUSY) + channel->status = DMA_STATUS_ABORT; + + priv->dmaDrv->dma_channelAbort(priv, channel); + + while (channel->trbChainDescList.next != &channel->trbChainDescList) { + trbChainDesc = GetTrbChainDescEntry(channel->trbChainDescList.next); + if (trbChainDesc) { + if (!list_empty(&trbChainDesc->chainNode)) { + for (i = 0; i < trbChainDesc->mapSize; i++) + priv->trbChainFreeMap[(trbChainDesc->start >> 3) + i] = 0; + + trbChainDesc->channel->numOfTrbChain--; + trbChainDesc->reserved = 0; + list_del(&trbChainDesc->chainNode); + } + } + } + + channel->status = DMA_STATUS_UNKNOWN; + + return 0; +} + +static void ShowTrbChain(struct DMA_TrbChainDesc *trbChainDesc) +{ + int i; + + if (!trbChainDesc) + return; + pr_debug("Trb Chain %p for channel %p\n", trbChainDesc, trbChainDesc->channel); + pr_debug("idx | trb size | trb addr | FLAG: NORMAL LINK CHAIN ALL\n"); + for (i = 0; i < trbChainDesc->numOfTrbs + 2; i++) + pr_debug("%02d | %08x | %08x | %d %d %d %08x\n", + i, + trbChainDesc->trbPool[i].dmaSize & TD_SIZE_MASK, + trbChainDesc->trbPool[i].dmaAddr, + (trbChainDesc->trbPool[i].ctrl & TD_TYPE_NORMAL) ? 1 : 0, + (trbChainDesc->trbPool[i].ctrl & TD_TYPE_LINK) ? 1 : 0, + (trbChainDesc->trbPool[i].ctrl & TDF_CHAIN_BIT) ? 1 : 0, + trbChainDesc->trbPool[i].ctrl); +} + +static uint32_t phytium_dma_NewTd(struct DMA_CONTROLLER *priv, + struct DMA_TrbChainDesc *trbChainDesc) +{ + uint32_t startAddress, dataSize; + struct DMA_Channel *channel; + int i = 0; + uint32_t tmp = 0; + + if (!priv || !trbChainDesc) + return 0; + + startAddress = trbChainDesc->dwStartAddress; + channel = trbChainDesc->channel; + dataSize = channel->maxTrbLen; + + if (trbChainDesc->numOfTrbs > 1) { + for (i = 0; i < (trbChainDesc->numOfTrbs - 1); i++) { + if (channel->dmultEnabled) { + if (trbChainDesc->framesDesc && priv->isHostCtrlMode + && channel->isIsoc) { + BUILD_NORMAL_TRB_NO_IOC(trbChainDesc->trbPool[i], + trbChainDesc->dwStartAddress + + trbChainDesc->framesDesc[i].offset, + trbChainDesc->framesDesc[i].length, 0); + continue; + } else + BUILD_NORMAL_TRB_NO_IOC(trbChainDesc->trbPool[i], + startAddress, dataSize, 0); + } else + BUILD_NORMAL_TRB_NO_IOC_CHAIN(trbChainDesc->trbPool[i], + startAddress, dataSize, 0); + startAddress += dataSize; + tmp += dataSize; + } + } + + if (trbChainDesc->framesDesc && priv->isHostCtrlMode && channel->isIsoc) { + BUILD_NORMAL_TRB(trbChainDesc->trbPool[i], + trbChainDesc->dwStartAddress + trbChainDesc->framesDesc[i].offset, + trbChainDesc->framesDesc[i].length, 0); + } else + BUILD_NORMAL_TRB(trbChainDesc->trbPool[i], startAddress, + trbChainDesc->len - tmp, 0); + + trbChainDesc->lastTrbIsLink = 0; + if (channel->dmultEnabled) { + if (channel->isIsoc) { + trbChainDesc->lastTrbIsLink = 1; + BUILD_LINK_TRB(trbChainDesc->trbPool[i + 1], trbChainDesc->trbDMAAddr); + } else + BUILD_EMPTY_TRB(trbChainDesc->trbPool[i + 1]); + } + + ShowTrbChain(trbChainDesc); + + return 0; +} + +static void phytium_dma_ArmTd(struct DMA_CONTROLLER *priv, struct DMA_TrbChainDesc *trbChainDesc) +{ + uint32_t ep_sts, ep_cfg, ep_cmd; + struct DMA_TrbChainDesc *startedChain; + struct DMA_Trb *lastPrevTrb; + struct DMA_Channel *channel; + + if (!priv || !trbChainDesc) + return; + + channel = trbChainDesc->channel; + phytium_write32(&priv->regs->ep_sel, (channel->isDirTx | channel->hwUsbEppNum)); + + ep_sts = phytium_read32(&priv->regs->ep_sts); + + if (channel->status == DMA_STATUS_FREE) { + phytium_write32(&priv->regs->ep_sts, DMARF_EP_TRBERR); + phytium_write32(&priv->regs->traddr, trbChainDesc->trbDMAAddr); + phytium_write32(&priv->regs->ep_sts, DMARF_EP_TRBERR); + + ep_cfg = phytium_read32(&priv->regs->ep_cfg); + phytium_write32(&priv->regs->ep_cmd, DMARF_EP_DRDY); + if (!(ep_cfg & DMARV_EP_ENABLED)) { + ep_cfg |= DMARV_EP_ENABLED; + phytium_write32(&priv->regs->ep_cfg, ep_cfg); + } + } else { + if (channel->trbChainDescList.prev != channel->trbChainDescList.next) { + startedChain = GetTrbChainDescEntry(channel->trbChainDescList.prev->prev); + lastPrevTrb = &startedChain->trbPool[startedChain->numOfTrbs]; + startedChain->lastTrbIsLink = 1; + BUILD_LINK_TRB((*lastPrevTrb), trbChainDesc->trbDMAAddr); + ep_cmd = phytium_read32(&priv->regs->ep_cmd); + if (!(ep_cmd & DMARF_EP_DRDY)) { + phytium_write32(&priv->regs->traddr, trbChainDesc->trbDMAAddr); + phytium_write32(&priv->regs->ep_cmd, DMARF_EP_DRDY); + } + ShowTrbChain(startedChain); + } + } +} + +static int32_t phytium_dma_TrbChainAlloc(struct DMA_CONTROLLER *priv, + struct DMA_Channel *channel, uint32_t numOfTrbs, struct DMA_TrbChainDesc **chain) +{ + struct DMA_TrbChainDesc *trbChainDesc = NULL; + int i, j; + uint32_t mapcount, count = 0; + + if (!priv || !channel) + return -ENOMEM; + + for (i = 0; i < TAB_SIZE_OF_DMA_CHAIN; i++) { + if (!priv->trbChainDesc[i].reserved) { + trbChainDesc = &priv->trbChainDesc[i]; + break; + } + } + + if (!trbChainDesc) { + pr_err("No Free TRB Chain Descriptor\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&trbChainDesc->chainNode); + *chain = NULL; + mapcount = (numOfTrbs + 2 + 7) >> 3; + + for (i = 0; i < TRB_MAP_SIZE; i++) { + if (priv->trbChainFreeMap[i] == 0x0) + count++; + else + count = 0; + + if (count == mapcount) + break; + } + + if (count != mapcount) { + pr_err("No Free TRBs count:0x%x, mapcount:0x%x\n", count, mapcount); + return -ENOMEM; + } + + trbChainDesc->reserved = 1; + trbChainDesc->channel = channel; + trbChainDesc->actualLen = 0; + trbChainDesc->mapSize = mapcount; + trbChainDesc->numOfTrbs = numOfTrbs; + trbChainDesc->start = (i + 1 - mapcount) << 3; + trbChainDesc->end = trbChainDesc->start; + trbChainDesc->trbPool = (struct DMA_Trb *)priv->trbPoolAddr + trbChainDesc->start; + trbChainDesc->trbDMAAddr = (uint32_t)(uintptr_t)((struct DMA_Trb *)priv->trbDMAPoolAddr + + trbChainDesc->start); + trbChainDesc->isoError = 0; + + list_add_tail(&trbChainDesc->chainNode, &channel->trbChainDescList); + + channel->numOfTrbChain++; + + for (j = 0; j < count; j++) + priv->trbChainFreeMap[i - j] = 0xFF; + + *chain = trbChainDesc; + + return 0; +} + +static int32_t phytium_dma_channelProgram(struct DMA_CONTROLLER *priv, + struct DMA_Channel *channel, uint16_t packetSize, uintptr_t dmaAddr, + uint32_t len, void *framesDesc, uint32_t framesNumber) +{ + uint32_t numOfTrbs; + uint8_t retval; + struct DMA_TrbChainDesc *trbChainDesc; + + if (!priv || !channel) + return -EINVAL; + + //printk(KERN_ERR "%s %d\n",__func__,__LINE__); + if (framesDesc && priv->isHostCtrlMode && channel->isIsoc) + numOfTrbs = framesNumber; + else + numOfTrbs = divRoundUp(len, channel->maxTrbLen); + + retval = phytium_dma_TrbChainAlloc(priv, channel, numOfTrbs, &trbChainDesc); + if (retval) + return retval; + + trbChainDesc->dwStartAddress = dmaAddr; + trbChainDesc->len = len; + trbChainDesc->framesDesc = framesDesc; + + channel->wMaxPacketSize = packetSize; + phytium_dma_NewTd(priv, trbChainDesc); + channel->lastTransferLength = 0; + phytium_dma_ArmTd(priv, trbChainDesc); + + channel->status = DMA_STATUS_BUSY; + + return 0; +} + +static enum DMA_Status phytium_dma_getChannelStatus(struct DMA_CONTROLLER *priv, + struct DMA_Channel *channel) +{ + uint32_t ep_cmd, ep_sts; + + if (!priv || !channel) + return DMA_STATUS_UNKNOWN; + + if (channel->status >= DMA_STATUS_BUSY) { + phytium_write32(&priv->regs->ep_sel, channel->isDirTx | channel->hwUsbEppNum); + ep_cmd = phytium_read32(&priv->regs->ep_cmd); + ep_sts = phytium_read32(&priv->regs->ep_sts); + + if ((ep_cmd & DMARF_EP_DRDY) || (ep_sts & DMARF_EP_DBUSY)) + channel->status = DMA_STATUS_ARMED; + else + channel->status = DMA_STATUS_BUSY; + } + + return channel->status; +} + +static int32_t phytium_dma_channelAbort(struct DMA_CONTROLLER *priv, struct DMA_Channel *channel) +{ + struct DMA_TrbChainDesc *trbChainDesc; + uint32_t ep_cfg, i; + + if (!priv || !channel) + return -EINVAL; + + if (phytium_dma_getChannelStatus(priv, channel) >= DMA_STATUS_BUSY) + phytium_write32(&priv->regs->conf, DMARF_RESET); + + phytium_write32(&priv->regs->ep_sel, (uint32_t)(channel->isDirTx | channel->hwUsbEppNum)); + ep_cfg = phytium_read32(&priv->regs->ep_cfg); + ep_cfg &= ~DMARV_EP_ENABLED; + phytium_write32(&priv->regs->ep_cfg, ep_cfg); + phytium_write32(&priv->regs->ep_sts, 0xFFFFFFFF); + + while (channel->trbChainDescList.next != &channel->trbChainDescList) { + trbChainDesc = GetTrbChainDescEntry(channel->trbChainDescList.next); + if (trbChainDesc) { + if (!list_empty(&trbChainDesc->chainNode)) { + for (i = 0; i < trbChainDesc->mapSize; i++) + priv->trbChainFreeMap[(trbChainDesc->start >> 3) + i] = 0; + + trbChainDesc->channel->numOfTrbChain--; + trbChainDesc->reserved = 0; + list_del(&trbChainDesc->chainNode); + } + } + } + if (channel->status != DMA_STATUS_UNKNOWN) + channel->status = DMA_STATUS_FREE; + + return 0; +} + +static int32_t phytium_dma_getActualLength(struct DMA_CONTROLLER *priv, struct DMA_Channel *channel) +{ + if (!priv || !channel) + return -EINVAL; + + return channel->lastTransferLength; +} + +static int32_t phytium_dma_getMaxLength(struct DMA_CONTROLLER *priv, struct DMA_Channel *channel) +{ + if (!priv || !channel) + return -EINVAL; + + return channel->maxTdLen; +} + +static int32_t phytium_dma_setMaxLength(struct DMA_CONTROLLER *priv, + struct DMA_Channel *channel, uint32_t val) +{ + if (!priv || !channel) + return -EINVAL; + + if (channel->dmultEnabled) + channel->maxTrbLen = val; + else + channel->maxTrbLen = (val > TD_SING_MAX_TRB_DATA_SIZE) ? + TD_SING_MAX_TRB_DATA_SIZE : val; + + return 0; +} + +static void phytium_dma_setParentPriv(struct DMA_CONTROLLER *priv, void *parent) +{ + if (!priv) + return; + + priv->parent = parent; +} + +void phytium_dma_controllerReset(struct DMA_CONTROLLER *priv) +{ + uint32_t conf; + + if (!priv) + return; + + conf = phytium_read32(&priv->regs->conf); + conf |= DMARF_RESET; + phytium_write32(&priv->regs->conf, conf); + + priv->resubmit = true; +} + +void phytium_dma_setHostMode(struct DMA_CONTROLLER *priv) +{ + if (!priv) + return; + + priv->isHostCtrlMode = 1; +} + +struct DMA_OBJ phytium_dma_Drv = { + .dma_probe = phytium_dma_probe, + .dma_init = phytium_dma_init, + .dma_destroy = phytium_dma_destroy, + .dma_start = phytium_dma_start, + .dma_stop = phytium_dma_stop, + .dma_isr = phytium_dma_isr, + .dma_errIsr = phytium_dma_errIsr, + .dma_channelAlloc = phytium_dma_channelAlloc, + .dma_channelRelease = phytium_dma_channelRelease, + .dma_channelProgram = phytium_dma_channelProgram, + .dma_channelAbort = phytium_dma_channelAbort, + .dma_getChannelStatus = phytium_dma_getChannelStatus, + .dma_getActualLength = phytium_dma_getActualLength, + .dma_getMaxLength = phytium_dma_getMaxLength, + .dma_setMaxLength = phytium_dma_setMaxLength, + .dma_setParentPriv = phytium_dma_setParentPriv, + .dma_controllerReset = phytium_dma_controllerReset, + .dma_setHostMode = phytium_dma_setHostMode, +}; + +struct DMA_OBJ *DMA_GetInstance(void) +{ + return &phytium_dma_Drv; +} diff --git a/drivers/usb/phytium/dma.h b/drivers/usb/phytium/dma.h new file mode 100644 index 0000000000000000000000000000000000000000..f2c25e6006e707a2af7ed0051316dc3302c6c430 --- /dev/null +++ b/drivers/usb/phytium/dma.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __PHYTIUM_DMA_H__ +#define __PHYTIUM_DMA_H__ + +#include +//#include "list.h" + +#define NUM_OF_TRB 1024 +#define TRB_MAP_SIZE ((NUM_OF_TRB + (sizeof(uint8_t) * 8) - 1) / (sizeof(uint8_t) * 8)) +#define MAX_DMA_CHANNELS 16 +#define TAB_SIZE_OF_DMA_CHAIN (MAX_DMA_CHANNELS * 2) + +#define DMARD_EP_TX 0x80ul +#define DMARD_EP_RX 0x00ul + +#define DMARF_EP_EPRST 0x00000001ul +#define DMARF_EP_DRDY 0x00000040ul +#define DMARF_EP_DFLUSH 0x00000080ul + +#define DMARF_EP_IOC 0x4ul +#define DMARF_EP_ISP 0x8ul +#define DMARF_EP_DESCMIS 0x10ul +#define DMARF_EP_TRBERR 0x80ul +#define DMARF_EP_DBUSY 0x200ul +#define DMARF_EP_CCS 0x800ul +#define DMARF_EP_OUTSMM 0x4000ul +#define DMARF_EP_ISOERR 0x8000ul +#define DMARF_EP_DTRANS 0x80000000ul + +#define DMARV_EP_DISABLED 0ul +#define DMARV_EP_ENABLED 1ul +#define DMARV_EP_DSING 0x1000ul +#define DMARV_EP_DMULT 0x2000ul + +#define TD_SIZE_MASK 0x00001FFFF + +#define DMARF_RESET 0x00000001ul +#define DMARF_DSING 0x00000100ul +#define DMARF_DMULT 0x00000200ul + +#define TD_DMULT_MAX_TRB_DATA_SIZE 65536u +#define TD_DMULT_MAX_TD_DATA_SIZE (~1u) +#define TD_SING_MAX_TRB_DATA_SIZE 65536u +#define TD_SING_MAX_TD_DATA_SIZE 65536u + +#define TD_TYPE_NORMAL 0x400L +#define TD_TYPE_LINK 0x1800L +#define TDF_CYCLE_BIT 0x1L +#define TDF_TOGGLE_CYCLE_BIT 0x2L +#define TDF_INT_ON_SHORT_PACKET 0x4L +#define TDF_FIFO_MODE 0x8L +#define TDF_CHAIN_BIT 0x10L +#define TDF_INT_ON_COMPLECTION 0x20L +#define TDF_STREAMID_VALID 0x200L + +struct DMA_Trb { + uint32_t dmaAddr; + uint32_t dmaSize; + uint32_t ctrl; +}; + +enum DMA_Status { + DMA_STATUS_UNKNOWN, + DMA_STATUS_FREE, + DMA_STATUS_ABORT, + DMA_STATUS_BUSY, + DMA_STATUS_ARMED +}; + +struct DMA_CFG { + uintptr_t regBase; + uint16_t dmaModeTx; + uint16_t dmaModeRx; + void *trbAddr; + uintptr_t trbDmaAddr; +}; + +struct DMA_SYSREQ { + uint32_t privDataSize; + uint32_t trbMemSize; +}; + +struct DMA_CALLBACKS { + void (*complete)(void *pD, uint8_t epNum, uint8_t dir, bool resubmit); +}; + +struct DMA_CONTROLLER; + +struct DMA_Channel { + struct DMA_CONTROLLER *controller; + uint16_t wMaxPacketSize; + uint8_t hwUsbEppNum; + uint8_t isDirTx; + uint32_t maxTdLen; + uint32_t maxTrbLen; + enum DMA_Status status; + void *priv; + uint32_t dmultGuard; + uint8_t dmultEnabled; + uint8_t numOfTrbChain; + struct list_head trbChainDescList; + uint32_t lastTransferLength; + uint8_t isIsoc; +}; + +struct DMA_OBJ { + int32_t (*dma_probe)(struct DMA_CFG *config, struct DMA_SYSREQ *sysReq); + + int32_t (*dma_init)(struct DMA_CONTROLLER *priv, const struct DMA_CFG *config, + struct DMA_CALLBACKS *callbacks); + + void (*dma_destroy)(struct DMA_CONTROLLER *priv); + + int32_t (*dma_start)(struct DMA_CONTROLLER *priv); + + uint32_t (*dma_stop)(struct DMA_CONTROLLER *priv); + + void (*dma_isr)(struct DMA_CONTROLLER *priv); + + void (*dma_errIsr)(struct DMA_CONTROLLER *priv, uint8_t irqNr, uint8_t isDirTx); + + void * (*dma_channelAlloc)(struct DMA_CONTROLLER *priv, + uint8_t isDirTx, uint8_t hwEpNum, uint8_t isIso); + + int32_t (*dma_channelRelease)(struct DMA_CONTROLLER *priv, struct DMA_Channel *channel); + + int32_t (*dma_channelProgram)(struct DMA_CONTROLLER *priv, struct DMA_Channel *channel, + uint16_t packetSize, uintptr_t dmaAddr, + uint32_t len, void *framesDesc, uint32_t framesNumber); + + int32_t (*dma_channelAbort)(struct DMA_CONTROLLER *priv, struct DMA_Channel *channel); + + enum DMA_Status (*dma_getChannelStatus)(struct DMA_CONTROLLER *priv, + struct DMA_Channel *channel); + + int32_t (*dma_getActualLength)(struct DMA_CONTROLLER *priv, struct DMA_Channel *channel); + + int32_t (*dma_getMaxLength)(struct DMA_CONTROLLER *priv, struct DMA_Channel *channel); + + int32_t (*dma_setMaxLength)(struct DMA_CONTROLLER *priv, + struct DMA_Channel *channel, uint32_t val); + + void (*dma_setParentPriv)(struct DMA_CONTROLLER *priv, void *parent); + + void (*dma_controllerReset)(struct DMA_CONTROLLER *priv); + + void (*dma_setHostMode)(struct DMA_CONTROLLER *priv); +}; + +enum DMA_Mode { + DMA_MODE_GLOBAL_DMULT, + DMA_MODE_GLOBAL_DSING, + DMA_MODE_CHANNEL_INDIVIDUAL, +}; + +struct DMA_TrbFrameDesc { + uint32_t length; + uint32_t offset; +}; + +struct DMA_TrbChainDesc { + uint8_t reserved; + struct DMA_Channel *channel; + struct DMA_Trb *trbPool; + uint32_t trbDMAAddr; + uint32_t len; + uint32_t dwStartAddress; + uint32_t actualLen; + uint8_t isoError; + uint8_t lastTrbIsLink; + uint32_t mapSize; + uint32_t numOfTrbs; + uint32_t start; + uint32_t end; + struct DMA_TrbFrameDesc *framesDesc; + struct list_head chainNode; +}; + +struct DMA_CONTROLLER { + struct DMARegs *regs; + struct DMA_OBJ *dmaDrv; + struct DMA_CFG dmaCfg; + struct DMA_CALLBACKS dmaCallbacks; + struct DMA_Channel rx[MAX_DMA_CHANNELS]; + struct DMA_Channel tx[MAX_DMA_CHANNELS]; + enum DMA_Mode dmaMode; + uint8_t isHostCtrlMode; + void *parent; + void *trbPoolAddr; + uintptr_t trbDMAPoolAddr; + uint8_t trbChainFreeMap[TRB_MAP_SIZE]; + struct DMA_TrbChainDesc trbChainDesc[TAB_SIZE_OF_DMA_CHAIN]; + bool resubmit; +}; + +struct DMA_OBJ *DMA_GetInstance(void); +#endif diff --git a/drivers/usb/phytium/gadget.c b/drivers/usb/phytium/gadget.c new file mode 100644 index 0000000000000000000000000000000000000000..e5e1625d785f89f44153f12985c3e22680bef55a --- /dev/null +++ b/drivers/usb/phytium/gadget.c @@ -0,0 +1,2538 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include "gadget.h" +#include "dma.h" +#include "core.h" +#include "hw-regs.h" + +#define DRV_NAME "phytium_gadget" + +#define GADGET_PRIV_BUFFER_SIZE 64 +#define GADGET_USB_EP_NUMBER_MASK 0xf +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +#define GADGET_ESTALL 1 +#define GADGET_EUNHANDLED 2 +#define GADGET_EAUTOACK 3 +#define GADGET_ESHUTDOWN 4 +#define GADGET_ECONNRESET 5 +#define GADGET_EAGAIN 6 + +static inline struct GadgetEp *toGadgetEp(struct GADGET_EP *gadget_Ep) +{ + return (struct GadgetEp *)((uintptr_t)gadget_Ep - + ((uintptr_t)&((struct GadgetEp *)0)->gadgetEp)); +} + +static inline struct GadgetRequest *requestToGadgetRequest(struct GADGET_REQ *req) +{ + return (struct GadgetRequest *)((uintptr_t)req - + ((uintptr_t)&((struct GadgetRequest *)0)->request)); +} + +static inline struct GADGET_REQ *listToGadgetRequest(struct list_head *list) +{ + return (struct GADGET_REQ *)((uintptr_t)list - + ((uintptr_t)&((struct GADGET_REQ *)0)->list)); +} + +#define listBrowsingRequest(iterator, head, memeber) \ + for (iterator = listToGadgetRequest((head)->next); \ + &iterator->list != (head); \ + iterator = listToGadgetRequest(iterator->list.next)) + +static inline struct GADGET_REQ *gadgetGetNextReq(struct GadgetEp *gadgetEp) +{ + struct list_head *list = &gadgetEp->request; + + if (list_empty(list)) { + pr_debug("no request available for %s\n", gadgetEp->gadgetEp.name); + return NULL; + } + + return listToGadgetRequest(list->next); +} + +static inline struct GADGET_REQ *gadgetGetNextEp0Req(struct GADGET_CTRL *priv) +{ + struct list_head *queue; + + if (!priv) + return NULL; + + queue = &priv->in[0].request; + + if (list_empty(queue)) + return NULL; + + return listToGadgetRequest(queue->next); +} + +void gadget_giveback(struct phytium_ep *phy_ep, struct usb_request *usb_req, int status) +{ + struct phytium_request *phy_req; + struct phytium_cusb *config; + int busy; + + if (!phy_ep || !usb_req) + return; + + busy = phy_ep->busy; + phy_req = usb_req ? container_of(usb_req, struct phytium_request, request) : NULL; + config = phy_req->config; + + list_del(&phy_req->list); + + if (usb_req->status == -EINPROGRESS) { + if (status == GADGET_ESHUTDOWN) + usb_req->status = -ESHUTDOWN; + else if (status == GADGET_ECONNRESET) + usb_req->status = -ECONNRESET; + else + usb_req->status = -phy_req->gadget_request->status; + } + + if (usb_req->status == 0) + pr_debug("%s done request %p, %d/%d\n", phy_ep->end_point.name, + usb_req, usb_req->actual, usb_req->length); + else + pr_debug("%s request %p, %d/%d fault %d\n", phy_ep->end_point.name, + usb_req, usb_req->actual, usb_req->length, usb_req->status); + + usb_gadget_unmap_request(&config->gadget, &phy_req->request, phy_req->is_tx); + + busy = phy_ep->busy; + phy_ep->busy = 1; + + spin_unlock(&config->lock); + + if (phy_req->request.complete) + phy_req->request.complete(&phy_req->ep->end_point, usb_req); + + spin_lock(&config->lock); + + phy_ep->busy = busy; +} + +static void gadget_callback_complete(struct GADGET_EP *gadget_ep, struct GADGET_REQ *gadget_req) +{ + struct phytium_ep *phy_ep; + struct phytium_request *phy_req; + struct usb_request *usb_req; + + if (!gadget_ep || !gadget_req) + return; + + phy_req = gadget_req->context; + usb_req = &phy_req->request; + phy_ep = phy_req->ep; + usb_req->actual = gadget_req->actual; + usb_req->length = gadget_req->length; + + gadget_giveback(phy_ep, usb_req, gadget_req->status); +} + +static void gadgetDisconnect(struct GADGET_CTRL *priv) +{ + pr_info("Disconnect USB Device Driver\n"); + + if (!priv) + return; + + priv->gadgetDev.speed = USB_SPEED_UNKNOWN; + priv->gadgetDev.state = USB_STATE_NOTATTACHED; + + if (priv->eventCallback.disconnect) + priv->eventCallback.disconnect(priv); +} + +static int gadget_get_frame(struct usb_gadget *gadget) +{ + pr_info("%s %d\n", __func__, __LINE__); + + return 0; +} + +static int gadget_wakeup(struct usb_gadget *gadget) +{ + pr_info("%s %d\n", __func__, __LINE__); + + return 0; +} + +static int gadget_vbus_session(struct usb_gadget *gadget, int is_active) +{ + pr_info("%s %d\n", __func__, __LINE__); + + return 0; +} + +static int gadget_vbus_draw(struct usb_gadget *gadget, unsigned int mA) +{ + pr_info("%s %d\n", __func__, __LINE__); + + return 0; +} + +static int gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + pr_info("%s %d\n", __func__, __LINE__); + + return 0; +} + +static int gadget_udc_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver) +{ + unsigned long flags; + struct phytium_cusb *config; + struct GADGET_CTRL *priv; + uint32_t gen_cfg; + + if (!gadget || !driver) + return -EINVAL; + + if (driver->max_speed < USB_SPEED_HIGH) + return -EINVAL; + + config = container_of(gadget, struct phytium_cusb, gadget); + + pr_info("registering driver %s\n", driver->function); + + spin_lock_irqsave(&config->lock, flags); + config->gadget_driver = driver; + spin_unlock_irqrestore(&config->lock, flags); + + config->gadget_obj->gadget_start(config->gadget_priv); + + priv = (struct GADGET_CTRL *)config->gadget_priv; + if (priv->phy_regs) { + gen_cfg = phytium_read32(&priv->phy_regs->gen_cfg); + gen_cfg = gen_cfg & (~BIT(7)); + phytium_write32(&priv->phy_regs->gen_cfg, gen_cfg); + } + + return 0; +} + +static int gadget_udc_stop(struct usb_gadget *gadget) +{ + struct phytium_cusb *config; + unsigned long flags; + struct GADGET_CTRL *priv; + uint32_t gen_cfg; + + if (!gadget) + return -EINVAL; + + config = container_of(gadget, struct phytium_cusb, gadget); + + priv = (struct GADGET_CTRL *)config->gadget_priv; + if (priv->phy_regs) { + gen_cfg = phytium_read32(&priv->phy_regs->gen_cfg); + gen_cfg = gen_cfg | BIT(7); + phytium_write32(&priv->phy_regs->gen_cfg, gen_cfg); + } + spin_lock_irqsave(&config->lock, flags); + config->gadget_driver = NULL; + spin_unlock_irqrestore(&config->lock, flags); + + return 0; +} + +static struct usb_gadget_ops phytium_gadget_ops = { + .get_frame = gadget_get_frame, + .wakeup = gadget_wakeup, + .vbus_session = gadget_vbus_session, + .vbus_draw = gadget_vbus_draw, + .pullup = gadget_pullup, + .udc_start = gadget_udc_start, + .udc_stop = gadget_udc_stop, +}; + +static int gadget_ep_enable(struct usb_ep *ls_ep, const struct usb_endpoint_descriptor *desc) +{ + struct phytium_ep *phy_ep = NULL; + struct phytium_cusb *config; + unsigned long flags; + + if (!ls_ep || !desc) + return -EINVAL; + + phy_ep = ls_ep ? container_of(ls_ep, struct phytium_ep, end_point) : NULL; + config = phy_ep->config; + + if (phy_ep->desc) + return -EBUSY; + + spin_lock_irqsave(&config->lock, flags); + + phy_ep->desc = desc; + phy_ep->busy = 0; + config->gadget_obj->gadget_epEnable(config->gadget_priv, phy_ep->gadget_ep, desc); + + spin_unlock_irqrestore(&config->lock, flags); + + return 0; +} + +static int gadget_ep_disable(struct usb_ep *ls_ep) +{ + struct phytium_ep *phy_ep = NULL; + struct phytium_cusb *config; + unsigned long flags; + + if (!ls_ep) + return -EBUSY; + + pr_info("%s %d\n", __func__, __LINE__); + + phy_ep = ls_ep ? container_of(ls_ep, struct phytium_ep, end_point) : NULL; + config = phy_ep->config; + + spin_lock_irqsave(&config->lock, flags); + + phy_ep->desc = NULL; + phy_ep->busy = 0; + phy_ep->end_point.desc = NULL; + config->gadget_obj->gadget_epDisable(config->gadget_priv, phy_ep->gadget_ep); + + spin_unlock_irqrestore(&config->lock, flags); + + return 0; +} + +static struct usb_request *gadget_ep_alloc_request(struct usb_ep *ls_ep, gfp_t gfp_flags) +{ + struct phytium_ep *phy_ep; + struct phytium_cusb *config; + struct GADGET_EP *gadget_ep; + struct phytium_request *phy_request; + + if (!ls_ep) + return NULL; + + pr_info("%s %d\n", __func__, __LINE__); + phy_request = kzalloc(sizeof(*phy_request), gfp_flags); + if (!phy_request) { + pr_err("not enough momory\n"); + return NULL; + } + + phy_ep = ls_ep ? container_of(ls_ep, struct phytium_ep, end_point) : NULL; + config = phy_ep->config; + gadget_ep = phy_ep->gadget_ep; + + INIT_LIST_HEAD(&phy_request->list); + phy_request->request.dma = DMA_ADDR_INVALID; + phy_request->epnum = phy_ep->ep_num; + phy_request->ep = phy_ep; + phy_request->config = phy_ep->config; + + config->gadget_obj->gadget_reqAlloc(config->gadget_priv, gadget_ep, + &phy_request->gadget_request); + + return &phy_request->request; +} + +static void gadget_ep_free_request(struct usb_ep *ls_ep, struct usb_request *ls_req) +{ + struct phytium_ep *phy_ep; + struct phytium_cusb *config; + struct phytium_request *phy_request; + + if (!ls_ep || !ls_req) + return; + + pr_info("%s %d\n", __func__, __LINE__); + phy_request = ls_req ? container_of(ls_req, struct phytium_request, request) : NULL; + config = phy_request->config; + phy_ep = ls_ep ? container_of(ls_ep, struct phytium_ep, end_point) : NULL; + + config->gadget_obj->gadget_reqFree(config->gadget_priv, phy_ep->gadget_ep, + phy_request->gadget_request); + kfree(phy_request); +} + +static int gadget_ep_enqueue(struct usb_ep *ls_ep, struct usb_request *ls_req, gfp_t gfp_flags) +{ + struct phytium_ep *phy_ep; + struct phytium_cusb *config; + struct phytium_request *phy_request; + unsigned long flags; + int status = 0; + + if (!ls_ep || !ls_req) + return -EINVAL; + + if (!ls_req->buf) + return -ENODATA; + + phy_ep = ls_ep ? container_of(ls_ep, struct phytium_ep, end_point) : NULL; + config = phy_ep->config; + phy_request = ls_req ? container_of(ls_req, struct phytium_request, request) : NULL; + phy_request->config = config; + + if (phy_request->ep != phy_ep) + return -EINVAL; + + phy_request->request.actual = 0; + phy_request->request.status = -EINPROGRESS; + phy_request->epnum = phy_ep->ep_num; + phy_request->is_tx = phy_ep->is_tx; + + phy_request->gadget_request->length = ls_req->length; + phy_request->gadget_request->status = 0; + phy_request->gadget_request->complete = gadget_callback_complete; + phy_request->gadget_request->buf = ls_req->buf; + phy_request->gadget_request->context = ls_req; + + status = usb_gadget_map_request(&config->gadget, &phy_request->request, phy_request->is_tx); + + if (!phy_ep->desc) { + pr_debug("req %p queued to %s while ep %s\n", phy_request, ls_ep->name, "disabled"); + status = -ESHUTDOWN; + usb_gadget_unmap_request(&config->gadget, &phy_request->request, + phy_request->is_tx); + return status; + } + + spin_lock_irqsave(&config->lock, flags); + + phy_request->gadget_request->dma = phy_request->request.dma; + list_add_tail(&phy_request->list, &phy_ep->req_list); + + pr_debug("queue to %s (%s), length = %d\n", phy_ep->name, + phy_ep->is_tx ? "IN/TX" : "OUT/RX", phy_request->request.length); + + status = config->gadget_obj->gadget_reqQueue(config->gadget_priv, phy_ep->gadget_ep, + phy_request->gadget_request); + + spin_unlock_irqrestore(&config->lock, flags); + + return status; +} + +static int gadget_ep_dequeue(struct usb_ep *ls_ep, struct usb_request *ls_req) +{ + struct phytium_ep *phy_ep; + struct phytium_cusb *config; + unsigned long flags; + int status = 0; + struct phytium_request *phy_request; + struct phytium_request *phy_next_request; + + if (!ls_ep || !ls_req) + return -EINVAL; + + phy_ep = ls_ep ? container_of(ls_ep, struct phytium_ep, end_point) : NULL; + config = phy_ep->config; + phy_request = ls_req ? container_of(ls_req, struct phytium_request, request) : NULL; + + if (phy_request->ep != phy_ep) + return -EINVAL; + + spin_lock_irqsave(&config->lock, flags); + + list_for_each_entry(phy_next_request, &phy_ep->req_list, list) { + if (phy_next_request == phy_request) + break; + } + + if (phy_next_request != phy_request) { + pr_info("request %p not queued to %s\n", phy_request, ls_ep->name); + status = -EINVAL; + goto done; + } + + status = config->gadget_obj->gadget_reqDequeue(config->gadget_priv, phy_ep->gadget_ep, + phy_request->gadget_request); +done: + spin_unlock_irqrestore(&config->lock, flags); + return status; +} + +static int gadget_ep_set_halt(struct usb_ep *ls_ep, int value) +{ + struct phytium_ep *phy_ep; + struct phytium_cusb *config; + struct GADGET_EP *gadget_ep = NULL; + unsigned long flags; + int status = 0; + + if (!ls_ep) + return -EINVAL; + + phy_ep = ls_ep ? container_of(ls_ep, struct phytium_ep, end_point) : NULL; + config = phy_ep->config; + gadget_ep = phy_ep->gadget_ep; + + spin_lock_irqsave(&config->lock, flags); + + status = config->gadget_obj->gadget_epSetHalt(config->gadget_priv, + phy_ep->gadget_ep, value); + if (status > 0) { + spin_unlock_irqrestore(&config->lock, flags); + return -status; + } + + spin_unlock_irqrestore(&config->lock, flags); + + return 0; +} + +static const struct usb_ep_ops gadget_ep_ops = { + .enable = gadget_ep_enable, + .disable = gadget_ep_disable, + .alloc_request = gadget_ep_alloc_request, + .free_request = gadget_ep_free_request, + .queue = gadget_ep_enqueue, + .dequeue = gadget_ep_dequeue, + .set_halt = gadget_ep_set_halt, +}; + +static int gadget_ep0_enable(struct usb_ep *ep, const struct usb_endpoint_descriptor *desc) +{ + return -EINVAL; +} + +static int gadget_ep0_disable(struct usb_ep *ep) +{ + return -EINVAL; +} + +static int gadget_ep0_enqueue(struct usb_ep *ls_ep, struct usb_request *ls_req, gfp_t gfp_flags) +{ + struct phytium_ep *phy_ep; + struct phytium_request *phy_request; + struct phytium_cusb *config; + int status = 0; + unsigned long flags; + + if (!ls_ep || !ls_req) + return -EINVAL; + + phy_ep = ls_ep ? container_of(ls_ep, struct phytium_ep, end_point) : NULL; + config = phy_ep->config; + phy_request = ls_req ? container_of(ls_req, struct phytium_request, request) : NULL; + + spin_lock_irqsave(&config->lock, flags); + + if (!list_empty(&phy_ep->req_list)) { + status = -EBUSY; + goto cleanup; + } + + phy_request->config = config; + phy_request->request.actual = 0; + phy_request->gadget_request->actual = 0; + phy_request->is_tx = config->ep0_data_stage_is_tx; + phy_request->gadget_request->length = phy_request->request.length; + phy_request->gadget_request->status = 0; + phy_request->gadget_request->complete = gadget_callback_complete; + phy_request->gadget_request->buf = phy_request->request.buf; + phy_request->gadget_request->context = phy_request; + + status = usb_gadget_map_request(&config->gadget, &phy_request->request, phy_request->is_tx); + if (status) { + pr_info("failed to map request\n"); + status = -EINVAL; + goto cleanup; + } + + phy_request->gadget_request->dma = phy_request->request.dma; + list_add_tail(&phy_request->list, &phy_ep->req_list); + + pr_debug("queue to %s (%s), length = %d\n", phy_ep->name, + phy_ep->is_tx ? "IN/TX" : "OUT/RX", phy_request->request.length); + + status = config->gadget_obj->gadget_reqQueue(config->gadget_priv, phy_ep->gadget_ep, + phy_request->gadget_request); + if (status > 0) { + status = -status; + usb_gadget_unmap_request(&config->gadget, &phy_request->request, + phy_request->is_tx); + list_del(&phy_request->list); + goto cleanup; + } + +cleanup: + spin_unlock_irqrestore(&config->lock, flags); + return status; +} + +static int gadget_ep0_dequeue(struct usb_ep *ep, struct usb_request *ls_request) +{ + return -EOPNOTSUPP; +} + +static int gadget_ep0_set_halt(struct usb_ep *ep, int value) +{ + return -EINVAL; +} + + +static const struct usb_ep_ops gadget_ep0_ops = { + .enable = gadget_ep0_enable, + .disable = gadget_ep0_disable, + .alloc_request = gadget_ep_alloc_request, + .free_request = gadget_ep_free_request, + .queue = gadget_ep0_enqueue, + .dequeue = gadget_ep0_dequeue, + .set_halt = gadget_ep0_set_halt, +}; + +static int32_t gadgetWaitForBusyBit(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep) +{ + struct GadgetEp *gadgetEp; + uint8_t epNum; + uint8_t txcs = CS_BUSY; + uint8_t buf = 0; + uint8_t bufflag = 0; + + if (!priv || !gadget_Ep) + return -EINVAL; + + gadgetEp = toGadgetEp(gadget_Ep); + epNum = gadgetEp->hwEpNum; + + if (gadgetEp->isInEp || gadgetEp->hwEpNum == 0) + return 0; + + buf = phytium_read8(&priv->regs->ep[epNum - 1].txcon) & CON_BUF; + + while ((txcs & CS_BUSY) || (bufflag == 0)) { + txcs = phytium_read8(&priv->regs->ep[epNum - 1].txcs); + + if (((txcs & CS_NPAK) >> CS_NPAK_OFFSET) == buf || buf == 0) + bufflag = 1; + else + bufflag = 0; + } + + return 0; +} + +static inline void gadgetEpXDataReceive(struct GADGET_CTRL *priv, + struct GadgetRequest *gadgetRequest) +{ + struct GadgetEp *gadgetEp; + struct GADGET_REQ *gadgetReq; + uint8_t epType; + uint32_t requestSize, channelStatus, chMaxLen; + + if (!priv || !gadgetRequest) + return; + + gadgetEp = gadgetRequest->ep; + chMaxLen = priv->dmaDrv->dma_getMaxLength(priv->dmaController, gadgetEp->channel); + epType = gadgetEp->gadgetEp.desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + channelStatus = priv->dmaDrv->dma_getChannelStatus(priv->dmaController, gadgetEp->channel); + + gadgetReq = &gadgetRequest->request; + if (gadgetReq->actual < gadgetReq->length || gadgetRequest->zlp) { + gadgetRequest->zlp = 0; + if ((gadgetReq->length - gadgetReq->actual) < chMaxLen) + requestSize = gadgetReq->length - gadgetReq->actual; + else + requestSize = chMaxLen; + + priv->dmaDrv->dma_channelProgram(priv->dmaController, gadgetEp->channel, + gadgetEp->gadgetEp.maxPacket, + gadgetReq->dma + gadgetReq->actual, requestSize, NULL, 0); + } +} + +static inline void gadgetEpXDataSend(struct GADGET_CTRL *priv, struct GadgetRequest *gadgetRequest) +{ + struct GadgetEp *gadgetEp; + struct GADGET_REQ *gadgetReq; + uint8_t epType; + uint32_t requestSize, channelStatus, chMaxLen; + + if (!priv || !gadgetRequest) + return; + + gadgetEp = gadgetRequest->ep; + chMaxLen = priv->dmaDrv->dma_getMaxLength(priv->dmaController, gadgetEp->channel); + epType = gadgetEp->gadgetEp.desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + channelStatus = priv->dmaDrv->dma_getChannelStatus(priv->dmaController, gadgetEp->channel); + + gadgetReq = &gadgetRequest->request; + if ((gadgetReq->length - gadgetReq->actual) < chMaxLen) + requestSize = gadgetReq->length - gadgetReq->actual; + else + requestSize = chMaxLen; + pr_debug("Transmit/IN %s gadgetReq %p gadgetRequest:%p requestSize:0x%x packetSize:0x%x\n", + gadgetEp->gadgetEp.name, gadgetReq, gadgetRequest, + requestSize, gadgetEp->gadgetEp.maxPacket); + + gadgetRequest->zlp = 0; + priv->dmaDrv->dma_channelProgram(priv->dmaController, gadgetEp->channel, + gadgetEp->gadgetEp.maxPacket, + gadgetReq->dma + gadgetReq->actual, requestSize, NULL, 0); +} + +static int32_t gadgetEpXSetHalt(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep, + uint8_t value) +{ + struct GadgetEp *gadgetEp; + uint8_t epType; + struct GADGET_REQ *req = NULL; + struct GadgetRequest *gadgetRequest = NULL; + uint8_t epNum, txcon, rxcon; + uint32_t status = DMA_STATUS_ARMED; + + if (!priv || !gadget_Ep) + return -EINVAL; + + gadgetEp = toGadgetEp(gadget_Ep); + epType = gadgetEp->gadgetEp.desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + if (epType == USB_ENDPOINT_XFER_ISOC) + return -EINVAL; + + pr_debug("%s: %s stall\n", gadget_Ep->name, value ? "set" : "clear"); + req = gadgetGetNextReq(gadgetEp); + + if (!value) + gadgetEp->wedged = 0; + + if (value && gadgetEp->isInEp && req && gadgetEp->state == GADGET_EP_BUSY) { + while (status == DMA_STATUS_ARMED) + status = priv->dmaDrv->dma_getChannelStatus(priv->dmaController, + gadgetEp->channel); + + gadgetWaitForBusyBit(priv, gadget_Ep); + } + + epNum = gadgetEp->hwEpNum; + + if (gadgetEp->isInEp) { + txcon = phytium_read8(&priv->regs->ep[epNum - 1].txcon); + if (value) { + phytium_write8(&priv->regs->ep[epNum - 1].txcon, txcon | CON_STALL); + phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX | epNum); + phytium_write8(&priv->regs->endprst, + ENDPRST_IO_TX | epNum | ENDPRST_FIFORST); + + } else { + phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX | epNum); + phytium_write8(&priv->regs->endprst, + ENDPRST_IO_TX | epNum | ENDPRST_TOGRST); + phytium_write8(&priv->regs->ep[epNum - 1].txcon, txcon & (~CON_STALL)); + } + } else { + rxcon = phytium_read8(&priv->regs->ep[epNum - 1].rxcon); + if (value) { + phytium_write8(&priv->regs->ep[epNum - 1].rxcon, rxcon | CON_STALL); + phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX | epNum); + phytium_write8(&priv->regs->endprst, epNum | ENDPRST_FIFORST); + } else { + phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX | epNum); + phytium_write8(&priv->regs->endprst, + epNum | ENDPRST_TOGRST | ENDPRST_FIFORST); + phytium_write8(&priv->regs->ep[epNum - 1].rxcon, rxcon & (~CON_STALL)); + } + } + + if (gadgetEp->state != GADGET_EP_BUSY && !value && req) { + gadgetRequest = requestToGadgetRequest(req); + if (gadgetEp->isInEp) + gadgetEpXDataSend(priv, gadgetRequest); + else + gadgetEpXDataReceive(priv, gadgetRequest); + } + + return 0; +} + +static int32_t gadgetEp0SetHalt(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep, + uint8_t value) +{ + struct GadgetEp *gadgetEp; + + if (!priv || !gadget_Ep) + return -EINVAL; + + gadgetEp = toGadgetEp(gadget_Ep); + if (!list_empty(&gadgetEp->request)) + return -EBUSY; + + switch (priv->ep0State) { + case GADGET_EP0_STAGE_IN: + case GADGET_EP0_STAGE_OUT: + case GADGET_EP0_STAGE_ACK: + case GADGET_EP0_STAGE_STATUSIN: + case GADGET_EP0_STAGE_STATUSOUT: + priv->ep0State = GADGET_EP0_STAGE_SETUP; + break; + default: + return -EINVAL; + } + + return 0; +} + + +static void gadgetEp0Callback(struct GADGET_CTRL *priv, struct GadgetEp *gadgetEp, + struct GADGET_REQ *req, uint8_t status) +{ + if (!priv || !gadgetEp || !req) + return; + + priv->ep0State = GADGET_EP0_STAGE_SETUP; + list_del(&req->list); + gadgetEp->requestsInList--; + + if (req->status == EINPROGRESS) + req->status = status; + + if (req->complete) + req->complete(&gadgetEp->gadgetEp, req); +} + +static enum usb_device_speed gadgetGetActualSpeed(struct GADGET_CTRL *priv) +{ + uint8_t speedctrl; + + if (!priv) + return USB_SPEED_UNKNOWN; + + speedctrl = phytium_read8(&priv->regs->speedctrl) & (~SPEEDCTRL_HSDISABLE); + switch (speedctrl) { + case SPEEDCTRL_HS: + return USB_SPEED_HIGH; + case SPEEDCTRL_FS: + return USB_SPEED_FULL; + case SPEEDCTRL_LS: + return USB_SPEED_LOW; + default: + return USB_SPEED_UNKNOWN; + } +} + +static int32_t gadgetServiceSetFeatureReq(struct GADGET_CTRL *priv, struct usb_ctrlrequest *setup) +{ + uint8_t epNum, isIn; + struct GadgetEp *gadgetEp; + + if (!priv || !setup) + return 0; + + switch (setup->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (setup->wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + pr_info("set feature - remote wakup\n"); + priv->isRemoteWakeup = 1; + break; + case USB_DEVICE_B_HNP_ENABLE: + pr_info("set feature - B HNP Enable\n"); + pr_info("otg not implement\n"); + return -EINVAL; + case USB_DEVICE_A_HNP_SUPPORT: + pr_info("set feature - A HNP support\n"); + pr_info("otg not implete\n"); + return -EINVAL; + } + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + epNum = setup->wIndex & 0x0f; + isIn = setup->wIndex & USB_DIR_IN; + if (epNum == 0 || epNum > 15 || setup->wValue != 0) + return -EINVAL; + + gadgetEp = isIn ? &priv->in[epNum] : &priv->out[epNum]; + + gadgetEpXSetHalt(priv, &gadgetEp->gadgetEp, 1); + break; + default: + return -EINVAL; + } + return 0; +} + +static int32_t gadgetServiceClearFeatureReq(struct GADGET_CTRL *priv, struct usb_ctrlrequest *setup) +{ + uint8_t epNum, isIn; + struct GadgetEp *gadgetEp; + + if (!priv || !setup) + return -EINVAL; + + switch (setup->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + if (setup->wValue == USB_DEVICE_B_HNP_ENABLE) { + pr_err("otg not implement\n"); + return -EINVAL; + } + + if (setup->wValue != USB_DEVICE_REMOTE_WAKEUP) + return GADGET_EUNHANDLED; + + priv->isRemoteWakeup = 0; + return GADGET_EAUTOACK; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + pr_info("clear feature wIndex:0x%x wValue:0x%x\n", setup->wIndex, setup->wValue); + epNum = setup->wIndex & 0x7f; + isIn = setup->wIndex & USB_DIR_IN; + if (epNum == 0 || epNum > 15 || setup->wValue != 0) + return GADGET_EUNHANDLED; + + gadgetEp = isIn ? &priv->in[epNum] : &priv->out[epNum]; + if (!gadgetEp->gadgetEp.desc) + return -EINVAL; + + if (gadgetEp->wedged) + break; + gadgetEpXSetHalt(priv, &gadgetEp->gadgetEp, 0); + break; + default: + return GADGET_EUNHANDLED; + } + + return 0; +} + +static int32_t gadgetServiceSetupReq(struct GADGET_CTRL *priv, struct usb_ctrlrequest *setup) +{ + struct GadgetEp *gadgetEp; + uint8_t isIn, epNum; + uint8_t rxcon, txcon; + int len; + + if (!priv || !setup) + return -EINVAL; + + if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + if (setup->bRequest != USB_REQ_GET_STATUS) + return GADGET_EUNHANDLED; + } else + return GADGET_EUNHANDLED; + + priv->privBuffAddr[1] = 0; + priv->privBuffAddr[0] = 0; + + switch (setup->bRequest & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + pr_info("wIndex:0x%x isSelfPowered:%d isRomoteWakeup:%d\n", + setup->wIndex, priv->isSelfPowered, priv->isRemoteWakeup); + + if (setup->wIndex == OTG_STS_SELECTOR) + priv->privBuffAddr[0] = priv->hostRequestFlag; + else { + priv->privBuffAddr[0] = priv->isSelfPowered ? USB_DEVICE_SELF_POWERED : 0; + priv->privBuffAddr[1] = priv->isRemoteWakeup ? USB_DEVICE_REMOTE_WAKEUP : 0; + } + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + epNum = setup->wIndex & 0x0f; + if (!epNum) + break; + + isIn = setup->wIndex & USB_DIR_IN; + + gadgetEp = isIn ? &priv->in[epNum] : &priv->out[epNum]; + if (!gadgetEp->gadgetEp.desc) + return -EINVAL; + + if (isIn) { + txcon = phytium_read8(&priv->regs->ep[epNum - 1].txcon); + priv->privBuffAddr[0] = (txcon & CON_STALL) ? 1 : 0; + } else { + rxcon = phytium_read8(&priv->regs->ep[epNum - 1].rxcon); + priv->privBuffAddr[0] = (rxcon & CON_STALL) ? 1 : 0; + } + break; + default: + return GADGET_EUNHANDLED; + } + + len = setup->wLength; + if (len > 2) + len = 2; + + priv->dmaDrv->dma_channelProgram(priv->dmaController, priv->in[0].channel, + priv->in[0].gadgetEp.maxPacket, priv->privBuffDma, len, NULL, 0); + + return 0; +} + +static int32_t gadgetGetSetup(struct GADGET_CTRL *priv, struct usb_ctrlrequest *setup) +{ + int i; + uint8_t ep0cs; + struct GADGET_REQ *request = NULL; + struct GadgetRequest *gadgetRequest; + + if (!priv || !setup) + return -EINVAL; + + phytium_write8(&priv->regs->ep0cs, EP0CS_CHGSET); + + for (i = 0; i < 8; i++) + ((char *)setup)[i] = phytium_read8(&priv->regs->setupdat[i]); + + ep0cs = phytium_read8(&priv->regs->ep0cs); + if (ep0cs & EP0CS_CHGSET) { + pr_info("setup flags change: not error\n"); + return GADGET_EAUTOACK; + } + + phytium_write8(&priv->regs->usbirq, USBIR_SUDAV); + pr_debug("setup packet: req%02x.%02x v:%04x i:%04x I%d\n", setup->bRequestType, + setup->bRequest, setup->wValue, setup->wIndex, setup->wLength); + + request = gadgetGetNextEp0Req(priv); + if (request) { + gadgetRequest = requestToGadgetRequest(request); + pr_info("Previous request has not been finished but new was received\n"); + gadgetEp0Callback(priv, gadgetRequest->ep, request, 0); + } + + if (setup->wLength) { + if (setup->bRequestType & USB_DIR_IN) + priv->ep0State = GADGET_EP0_STAGE_IN; + else + priv->ep0State = GADGET_EP0_STAGE_OUT; + } else + priv->ep0State = GADGET_EP0_STAGE_ACK; + + return 0; +} + +static void gadgetEp0StageSetup(struct GADGET_CTRL *priv) +{ + struct usb_ctrlrequest setup; + int32_t retval; + uint8_t ep0cs; + + if (!priv) + return; + + retval = gadgetGetSetup(priv, &setup); + + priv->gadgetDev.speed = gadgetGetActualSpeed(priv); + + switch (priv->ep0State) { + case GADGET_EP0_STAGE_ACK: + if ((setup.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) + retval = GADGET_EUNHANDLED; + else { + switch (setup.bRequest) { + case USB_REQ_SET_ADDRESS: + priv->deviceAddress = setup.wValue & 0x7F; + priv->gadgetDev.state = USB_STATE_ADDRESS; + pr_info("set address: %d\n", priv->deviceAddress); + retval = GADGET_EAUTOACK; + break; + case USB_REQ_SET_FEATURE: + retval = gadgetServiceSetFeatureReq(priv, &setup); + break; + case USB_REQ_CLEAR_FEATURE: + retval = gadgetServiceClearFeatureReq(priv, &setup); + break; + default: + retval = GADGET_EUNHANDLED; + break; + } + } + + if (retval == GADGET_EUNHANDLED) + break; + else if (retval == 0) + phytium_write8(&priv->regs->ep0cs, EP0CS_HSNAK); + + priv->ep0State = GADGET_EP0_STAGE_SETUP; + break; + case GADGET_EP0_STAGE_IN: + pr_debug("setup data stage in\n"); + retval = gadgetServiceSetupReq(priv, &setup); + + if (retval == 0) + priv->ep0State = GADGET_EP0_STAGE_STATUSOUT; + break; + case GADGET_EP0_STAGE_OUT: + pr_debug("setup data stage out\n"); + phytium_write8(&priv->regs->ep0Rxbc, 0); + retval = GADGET_EUNHANDLED; + break; + default: + if (retval == GADGET_EAUTOACK) + return; + + pr_debug("forward request\n"); + retval = GADGET_EUNHANDLED; + break; + } + + if (retval == GADGET_EUNHANDLED) { + if (priv->eventCallback.setup) + retval = priv->eventCallback.setup(priv, &setup); + + if (retval == 0x7FFF) { + pr_debug("Respond Delayed not finished yet\n"); + return; + } + + if (retval) + retval = GADGET_ESTALL; + } + + if (retval == GADGET_EUNHANDLED || retval == GADGET_ESTALL) { + pr_debug("request not handled - send stall\n"); + ep0cs = phytium_read8(&priv->regs->ep0cs); + ep0cs |= EP0CS_STALL; + phytium_write8(&priv->regs->ep0cs, ep0cs); + priv->ep0State = GADGET_EP0_STAGE_SETUP; + } else if (priv->ep0State == GADGET_EP0_STAGE_ACK) { + priv->ep0State = GADGET_EP0_STAGE_SETUP; + phytium_write8(&priv->regs->ep0cs, EP0CS_HSNAK); + pr_debug("setup transfer completed\n"); + } +} +static void gadgetEp0DataSend(struct GADGET_CTRL *priv) +{ + struct GADGET_REQ *request; + uint32_t chMaxLen, requestSize; + + if (!priv) + return; + + request = gadgetGetNextEp0Req(priv); + if (!request) { + pr_debug("Ep0 queue is empty\n"); + return; + } + + if (priv->dmaDrv->dma_getChannelStatus(priv->dmaController, priv->in[0].channel) + >= DMA_STATUS_BUSY) { + pr_err("transfer is pending now\n"); + return; + } + + chMaxLen = priv->dmaDrv->dma_getMaxLength(priv->dmaController, priv->in[0].channel); + requestSize = (request->length < chMaxLen) ? request->length : chMaxLen; + pr_debug("usbRequest;%p requestSize:%d packetSize:%d\n", request, requestSize, + priv->in[0].gadgetEp.maxPacket); + priv->ep0State = GADGET_EP0_STAGE_STATUSOUT; + + priv->dmaDrv->dma_channelProgram(priv->dmaController, priv->in[0].channel, + priv->in[0].gadgetEp.maxPacket, request->dma, requestSize, NULL, 0); +} + +static void gadgetEp0DataReceive(struct GADGET_CTRL *priv) +{ + uint32_t chMaxLen, requestSize; + struct GADGET_REQ *request; + + if (!priv) + return; + + request = gadgetGetNextEp0Req(priv); + if (!request) { + pr_debug("Ep0 queue is empty\n"); + return; + } + + chMaxLen = priv->dmaDrv->dma_getMaxLength(priv->dmaController, priv->out[0].channel); + requestSize = (request->length < chMaxLen) ? request->length : chMaxLen; + pr_debug("usbRequest;%p requestSize:%d packetSize:%d\n", request, requestSize, + priv->out[0].gadgetEp.maxPacket); + priv->ep0State = GADGET_EP0_STAGE_STATUSIN; + + priv->dmaDrv->dma_channelProgram(priv->dmaController, priv->out[0].channel, + priv->out[0].gadgetEp.maxPacket, request->dma, requestSize, NULL, 0); +} + +static uint32_t gadgetEp0Irq(struct GADGET_CTRL *priv) +{ + uint8_t usbcs; + struct GADGET_REQ *request; + + if (!priv) + return 0; + + switch (priv->ep0State) { + case GADGET_EP0_STAGE_IN://send data + pr_debug("DATA Stage IN\n"); + gadgetEp0DataSend(priv); + break; + case GADGET_EP0_STAGE_OUT://receive data + pr_debug("DATA Stage OUT\n"); + gadgetEp0DataReceive(priv); + break; + case GADGET_EP0_STAGE_STATUSIN: + pr_debug("DATA Stage STATUS IN\n"); + request = gadgetGetNextEp0Req(priv); + if (request) + request->actual = priv->dmaDrv->dma_getActualLength(priv->dmaController, + priv->out[0].channel); + + priv->ep0State = GADGET_EP0_STAGE_SETUP; + phytium_write8(&priv->regs->ep0cs, EP0CS_HSNAK); + gadgetEp0Callback(priv, &priv->out[0], request, 0); + break; + case GADGET_EP0_STAGE_STATUSOUT: + pr_debug("DATA Stage STATUS OUT\n"); + request = gadgetGetNextEp0Req(priv); + if (request) + request->actual = priv->dmaDrv->dma_getActualLength(priv->dmaController, + priv->in[0].channel); + + phytium_write8(&priv->regs->ep0cs, EP0CS_HSNAK); + if (request) + gadgetEp0Callback(priv, &priv->in[0], request, 0); + else + priv->ep0State = GADGET_EP0_STAGE_SETUP; + break; + case GADGET_EP0_STAGE_SETUP: + pr_debug("DATA Stage SETUP\n"); + gadgetEp0StageSetup(priv); + break; + case GADGET_EP0_STAGE_ACK: + pr_debug("DATA Stage ACK\n"); + break; + default: + pr_debug("DATA Stage UNKNOWN\n"); + usbcs = phytium_read8(&priv->regs->usbcs); + usbcs |= EP0CS_STALL; + phytium_write8(&priv->regs->usbcs, usbcs); + break; + } + + return 0; +} + +static void gadgetEpXCallback(struct GADGET_CTRL *priv, struct GadgetEp *gadgetEp, + struct GADGET_REQ *req, uint32_t status) +{ + if (!gadgetEp || !req) + return; + + list_del(&req->list); + gadgetEp->requestsInList--; + req->status = status; + + if (req->complete) + req->complete(&gadgetEp->gadgetEp, req); +} + +static void gadgetEpXDataCallback(struct GADGET_CTRL *priv, uint8_t epNum, uint8_t epDir) +{ + struct GadgetEp *gadgetEp; + struct GADGET_REQ *gadgetReq; + uint32_t actual_length = 0; + uint32_t chMaxLen = 0; + uint8_t epType; + + if (!priv) + return; + + pr_debug("%s %d epNum:%d epDir:%d\n", __func__, __LINE__, epNum, epDir); + gadgetEp = epDir ? &priv->in[epNum] : &priv->out[epNum]; + + gadgetReq = gadgetGetNextReq(gadgetEp); + if (!gadgetReq) { + pr_debug("%s queue is empty\n", gadgetEp->gadgetEp.name); + return; + } + + if (gadgetEp->channel) { + actual_length = priv->dmaDrv->dma_getActualLength(priv->dmaController, + gadgetEp->channel); + chMaxLen = priv->dmaDrv->dma_getMaxLength(priv->dmaController, gadgetEp->channel); + gadgetReq->actual += actual_length; + } + + if (gadgetReq->actual == gadgetReq->length || actual_length < chMaxLen) { + gadgetEpXCallback(priv, gadgetEp, gadgetReq, 0); + + if (gadgetEp->gadgetEp.desc) { + epType = gadgetEp->gadgetEp.desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + if (epType == USB_ENDPOINT_XFER_ISOC) + return; + + gadgetReq = gadgetGetNextReq(gadgetEp); + gadgetEp->state = GADGET_EP_ALLOCATED; + if (!gadgetReq) { + pr_debug("%s queue is empty\n", gadgetEp->gadgetEp.name); + return; + } + gadgetEp->state = GADGET_EP_BUSY; + if (epDir) + gadgetEpXDataSend(priv, requestToGadgetRequest(gadgetReq)); + else + gadgetEpXDataReceive(priv, requestToGadgetRequest(gadgetReq)); + } + } else { + if (epDir) + gadgetEpXDataSend(priv, requestToGadgetRequest(gadgetReq)); + else + gadgetEpXDataReceive(priv, requestToGadgetRequest(gadgetReq)); + } +} + +void gadget_CallbackTransfer(void *priv, uint8_t epNum, uint8_t epDir, bool resubmit) +{ + if (!epNum) + gadgetEp0Irq(priv); + else + gadgetEpXDataCallback(priv, epNum, epDir); +} + +static void gadgetAbortEndpoint(struct GADGET_CTRL *priv, struct GadgetEp *gadgetEp) +{ + struct GADGET_REQ *gadgetReq; + + pr_debug("Abort Device endpoint: %s, dma channel: %p\n", gadgetEp->gadgetEp.name, + gadgetEp->channel); + + if (gadgetEp->channel && gadgetEp->hwEpNum != 0) { + priv->dmaDrv->dma_channelRelease(priv->dmaController, gadgetEp->channel); + gadgetEp->channel = NULL; + } + + if (gadgetEp->channel && gadgetEp->hwEpNum == 0) { + if (priv->releaseEp0Flag == 1) { + priv->dmaDrv->dma_channelAbort(priv->dmaController, gadgetEp->channel); + priv->dmaDrv->dma_channelAlloc(priv->dmaController, gadgetEp->isInEp, + gadgetEp->hwEpNum, 0); + } + } + + while (gadgetEp->request.next != &gadgetEp->request) { + gadgetReq = listToGadgetRequest(gadgetEp->request.next); + pr_debug("shutdown request %p form epName:%s\n", gadgetReq, + gadgetEp->gadgetEp.name); + if (!gadgetEp->gadgetEp.address) + gadgetEp0Callback(priv, gadgetEp, gadgetReq, GADGET_ESHUTDOWN); + else + gadgetEpXCallback(priv, gadgetEp, gadgetReq, GADGET_ESHUTDOWN); + } + + gadgetEp->state = GADGET_EP_FREE; +} + +static void gadgetStopActivity(struct GADGET_CTRL *priv) +{ + int i = 0; + struct GadgetEp *gadgetEp; + + pr_debug("USB Stop Activity\n"); + + if (!priv) + return; + + for (i = 0; i < 16; i++) { + gadgetEp = &priv->in[i]; + if (gadgetEp->state != GADGET_EP_NOT_IMPLEMENTED) + gadgetAbortEndpoint(priv, gadgetEp); + + gadgetEp = &priv->out[i]; + if (gadgetEp->state != GADGET_EP_NOT_IMPLEMENTED) + gadgetAbortEndpoint(priv, gadgetEp); + } +} + + +static int32_t gadgetEp0Enable(struct GADGET_CTRL *priv, struct GadgetEp *gadgetEp) +{ + uint8_t txien, rxien; + + if (!priv || !gadgetEp) + return -EINVAL; + + if (gadgetEp->state != GADGET_EP_FREE) + return -EBUSY; + + if (!gadgetEp->isInEp) { + rxien = phytium_read16(&priv->regs->rxien); + rxien &= ~(1 << gadgetEp->hwEpNum); + phytium_write16(&priv->regs->rxien, rxien); + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_IO_TX | FIFOCTRL_FIFOAUTO); + } else { + txien = phytium_read16(&priv->regs->txien); + txien &= ~(1 << gadgetEp->hwEpNum); + phytium_write16(&priv->regs->txien, txien); + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO); + } + + gadgetEp->gadgetEp.desc = NULL; + gadgetEp->state = GADGET_EP_ALLOCATED; + gadgetEp->channel = priv->dmaDrv->dma_channelAlloc(priv->dmaController, + gadgetEp->isInEp, gadgetEp->hwEpNum, 0); + phytium_write8(&priv->regs->ep0maxpack, 0x40); + + return 0; +} + +static int32_t gadgetEpXEnable(struct GADGET_CTRL *priv, struct GadgetEp *gadgetEp, + const struct usb_endpoint_descriptor *desc) +{ + uint32_t status = -EINVAL; + uint32_t payload; + uint16_t type, iso = 0; + uint8_t epNum = 0; + + if (!priv || !gadgetEp || !desc) + return -EINVAL; + + pr_debug("enable endpoint %s\n", gadgetEp->gadgetEp.name); + if (gadgetEp->state != GADGET_EP_FREE) { + status = -EBUSY; + goto fail; + } + + payload = desc->wMaxPacketSize & 0x7ff; + if (!payload) { + status = -EINVAL; + goto fail; + } + + epNum = gadgetEp->hwEpNum; + + type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + switch (type) { + case USB_ENDPOINT_XFER_ISOC: + type = CON_TYPE_ISOC; + switch (payload >> 11) { + case 0: + iso = CON_TYPE_ISOC_1_ISOD; + break; + case 1: + payload *= 2; + iso = CON_TYPE_ISOC_2_ISOD; + break; + case 2: + payload *= 3; + iso = CON_TYPE_ISOC_3_ISOD; + break; + } + break; + case USB_ENDPOINT_XFER_INT: + type = CON_TYPE_INT; + break; + case USB_ENDPOINT_XFER_BULK: + type = CON_TYPE_BULK; + break; + } + + if (desc->bEndpointAddress & USB_DIR_IN) { + if (!gadgetEp->isInEp) { + status = -ENODEV; + goto fail; + } + + if (payload > priv->gadgetCfg.epIN[epNum].maxPacketSize) { + status = -EINVAL; + goto fail; + } + + phytium_write16(&priv->regs->txmaxpack[epNum - 1], payload); + phytium_write8(&priv->regs->ep[epNum - 1].txcon, CON_VAL | type + | iso | (priv->gadgetCfg.epIN[epNum - 1].bufferingValue - 1)); + + phytium_write8(&priv->regs->fifoctrl, FIFOCTRL_FIFOAUTO | FIFOCTRL_IO_TX | epNum); + phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX | epNum); + phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX | epNum | + ENDPRST_FIFORST | ENDPRST_TOGRST); + } else { + if (gadgetEp->isInEp) { + status = -ENODEV; + goto fail; + } + + if (payload > priv->gadgetCfg.epOUT[epNum].maxPacketSize) { + status = -EINVAL; + goto fail; + } + + phytium_write16(&priv->regs->rxmaxpack[epNum - 1], payload); + phytium_write8(&priv->regs->ep[epNum - 1].rxcon, CON_VAL | type + | iso | (priv->gadgetCfg.epIN[epNum - 1].bufferingValue - 1)); + + phytium_write8(&priv->regs->fifoctrl, FIFOCTRL_FIFOAUTO | epNum); + phytium_write8(&priv->regs->endprst, epNum); + phytium_write8(&priv->regs->endprst, epNum | ENDPRST_FIFORST | ENDPRST_TOGRST); + } + + if (priv->dmaController) + gadgetEp->channel = priv->dmaDrv->dma_channelAlloc(priv->dmaController, + gadgetEp->isInEp, epNum, (type == CON_TYPE_ISOC) ? 1 : 0); + + if (type == CON_TYPE_ISOC) { + if (gadgetEp->isInEp) { + phytium_write16(&priv->regs->isoautodump, 1 << epNum); + phytium_write16(&priv->regs->isodctrl, 1 << epNum); + } + + priv->dmaDrv->dma_setMaxLength(priv->dmaController, gadgetEp->channel, payload); + } + + gadgetEp->state = GADGET_EP_ALLOCATED; + gadgetEp->gadgetEp.desc = desc; +fail: + return status; +} + +static int32_t gadgetEpEnable(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep, + const struct usb_endpoint_descriptor *desc) +{ + struct GadgetEp *gadgetEp = NULL; + + if (!priv || !gadget_Ep || !desc) + return -EINVAL; + + gadgetEp = toGadgetEp(gadget_Ep); + gadgetEp->wedged = 0; + + if (gadgetEp->hwEpNum) + return gadgetEpXEnable(priv, gadgetEp, desc); + else + return gadgetEp0Enable(priv, gadgetEp); +} + +static int32_t gadgetEpXDisable(struct GADGET_CTRL *priv, struct GadgetEp *gadgetEp) +{ + uint8_t txcon, rxcon; + + if (!priv || !gadgetEp) + return -EINVAL; + + pr_debug("disable endpoint %s\n", gadgetEp->gadgetEp.name); + if (gadgetEp->isInEp) { + txcon = phytium_read8(&priv->regs->ep[gadgetEp->hwEpNum - 1].txcon); + txcon &= ~CON_VAL; + phytium_write8(&priv->regs->ep[gadgetEp->hwEpNum - 1].txcon, txcon); + } else { + rxcon = phytium_read8(&priv->regs->ep[gadgetEp->hwEpNum - 1].rxcon); + rxcon &= ~CON_VAL; + phytium_write8(&priv->regs->ep[gadgetEp->hwEpNum - 1].rxcon, rxcon); + } + gadgetAbortEndpoint(priv, gadgetEp); + gadgetEp->gadgetEp.desc = 0; + gadgetEp->state = GADGET_EP_FREE; + return 0; +} + +static int32_t gadgetEp0Disable(struct GADGET_CTRL *priv, struct GadgetEp *gadgetEp) +{ + if (!priv || !gadgetEp) + return -EINVAL; + + pr_debug("disable endpoint %s\n", gadgetEp->gadgetEp.name); + gadgetAbortEndpoint(priv, gadgetEp); + + return 0; +} + +static int32_t gadgetEpDisable(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep) +{ + struct GadgetEp *gadgetEp = NULL; + + if (!priv || !gadget_Ep) + return -EINVAL; + + if (gadget_Ep->address == 0) + return -EINVAL; + + gadgetEp = toGadgetEp(gadget_Ep); + gadgetEp->wedged = 0; + if (gadgetEp->hwEpNum) + return gadgetEpXDisable(priv, gadgetEp); + else + return gadgetEp0Disable(priv, gadgetEp); +} + +static int32_t gadgetEpXQueue(struct GADGET_CTRL *priv, struct GadgetEp *gadgetEp, + struct GADGET_REQ *req) +{ + struct GadgetRequest *gadgetRequest; + + if (!priv || !gadgetEp || !req) + return -EINVAL; + + req->actual = 0; + req->status = EINPROGRESS; + + gadgetRequest = requestToGadgetRequest(req); + gadgetRequest->ep = gadgetEp; + + if (req->length == 0) + gadgetRequest->zlp = 1; + + if (gadgetEp->gadgetEp.desc == NULL) { + pr_info("%s is disabled - can not queue request %p\n", + gadgetEp->gadgetEp.name, req); + return -EINVAL; + } + + list_add_tail(&req->list, &gadgetEp->request); + pr_debug("queue to %s (%s), length:%d\n", gadgetEp->gadgetEp.name, + (gadgetEp->isInEp ? "IN/TX" : "OUT/RX"), req->length); + + if ((gadgetEp->state == GADGET_EP_ALLOCATED) && (&req->list == gadgetEp->request.next)) { + if (gadgetEp->isInEp) { + if (!(phytium_read8(&priv->regs->ep[gadgetEp->hwEpNum - 1].txcon) + & CON_STALL)) { + gadgetEp->state = GADGET_EP_BUSY; + gadgetEpXDataSend(priv, gadgetRequest); + } + } else { + if (!(phytium_read8(&priv->regs->ep[gadgetEp->hwEpNum - 1].rxcon) + & CON_STALL)) { + gadgetEp->state = GADGET_EP_BUSY; + gadgetEpXDataReceive(priv, gadgetRequest); + } + } + } else if (gadgetEp->state == GADGET_EP_BUSY) { + if (usb_endpoint_xfer_isoc(gadgetEp->gadgetEp.desc)) { + if (gadgetEp->isInEp) + gadgetEpXDataSend(priv, gadgetRequest); + else + gadgetEpXDataReceive(priv, gadgetRequest); + } + } + + pr_debug("endpoint %s (%s) now is busy - transfer will be waiting in Queue\n", + gadgetEp->gadgetEp.name, (gadgetEp->isInEp ? "IN/TX" : "OUT/RX")); + + return 0; +} + +static int32_t gadgetEp0Queue(struct GADGET_CTRL *priv, struct GadgetEp *gadgetEp, + struct GADGET_REQ *req) +{ + struct GadgetRequest *gadgetRequest; + + if (!priv || !gadgetEp || !req) + return -EINVAL; + + req->actual = 0; + req->status = EINPROGRESS; + + if (!list_empty(&gadgetEp->request)) + return -EBUSY; + + gadgetRequest = requestToGadgetRequest(req); + gadgetRequest->ep = gadgetEp; + + switch (priv->ep0State) { + case GADGET_EP0_STAGE_OUT: + case GADGET_EP0_STAGE_IN: + case GADGET_EP0_STAGE_ACK: + break; + default: + return -EINVAL; + } + + list_add_tail(&req->list, &gadgetEp->request); + gadgetEp->requestsInList++; + + pr_debug("queue to %s (%s), length:%d stage:%d\n", gadgetEp->gadgetEp.name, + gadgetEp->isInEp ? "IN/TX" : "OUT/RX", req->length, priv->ep0State); + + switch (priv->ep0State) { + case GADGET_EP0_STAGE_OUT: + gadgetEp0DataReceive(priv); + break; + case GADGET_EP0_STAGE_IN: + gadgetEp0DataSend(priv); + break; + case GADGET_EP0_STAGE_ACK: + if (req->length) + return -EINVAL; + phytium_write8(&priv->regs->ep0cs, EP0CS_HSNAK); + gadgetEp0Callback(priv, gadgetRequest->ep, req, 0); + pr_info("control transfer completed\n"); + break; + default: + break; + } + + return 0; +} + +static int32_t gadgetEpQueue(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep, + struct GADGET_REQ *req) +{ + struct GadgetEp *gadgetEp = NULL; + + if (!priv || !gadget_Ep || !req) + return -EINVAL; + + gadgetEp = toGadgetEp(gadget_Ep); + + if (gadget_Ep->address & GADGET_USB_EP_NUMBER_MASK) + return gadgetEpXQueue(priv, gadgetEp, req); + else + return gadgetEp0Queue(priv, gadgetEp, req); +} + +static int32_t gadgetEpXDequeue(struct GADGET_CTRL *priv, struct GadgetEp *gadgetEp, + struct GADGET_REQ *req) +{ + struct GadgetRequest *gadgetRequest; + struct GADGET_REQ *iterator; + + if (!priv || !gadgetEp || !req) + return -EINVAL; + + gadgetRequest = requestToGadgetRequest(req); + if (gadgetRequest->ep != gadgetEp) + return -EINVAL; + + pr_debug("Dequeue request %p form %s\n", req, gadgetEp->gadgetEp.name); + + listBrowsingRequest(iterator, &gadgetEp->request, list) { + if (req == iterator) + break; + } + + if (req != iterator) { + pr_info("request %p not queued to %s\n", req, gadgetEp->gadgetEp.name); + return -EINVAL; + } + + if (gadgetEp->state == GADGET_EP_BUSY) { + priv->dmaDrv->dma_channelAbort(priv->dmaController, gadgetEp->channel); + gadgetEp->state = GADGET_EP_ALLOCATED; + } + + gadgetEpXCallback(priv, gadgetEp, req, GADGET_ECONNRESET); + + return 0; +} + +static int32_t gadgetEp0Dequeue(struct GADGET_CTRL *priv, struct GadgetEp *gadgetEp, + struct GADGET_REQ *req) +{ + return 0; +} + +static int32_t gadgetEpDequeue(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep, + struct GADGET_REQ *req) +{ + struct GadgetEp *gadgetEp = NULL; + + if (!priv || !gadget_Ep || !req) + return -EINVAL; + + gadgetEp = toGadgetEp(gadget_Ep); + + if (gadget_Ep->address & GADGET_USB_EP_NUMBER_MASK) + return gadgetEpXDequeue(priv, gadgetEp, req); + else + return gadgetEp0Dequeue(priv, gadgetEp, req); +} + +static int32_t gadgetEpSetHalt(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep, uint8_t value) +{ + if (!priv || !gadget_Ep) + return -EINVAL; + + if (gadget_Ep->address & GADGET_USB_EP_NUMBER_MASK) + return gadgetEpXSetHalt(priv, gadget_Ep, value); + else + return gadgetEp0SetHalt(priv, gadget_Ep, value); +} + +static int32_t gadgetEpSetWedge(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep) +{ + struct GadgetEp *gadgetEp = NULL; + + if (!priv || !gadget_Ep) + return -EINVAL; + + gadgetEp = toGadgetEp(gadget_Ep); + gadgetEp->wedged = 1; + + return gadgetEpSetHalt(priv, gadget_Ep, 1); +} + +static int32_t gadgetEpFifoStatus(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep) +{ + if (!priv || !gadget_Ep) + return -EINVAL; + + return 0; +} + +static void gadgetEpFifoFlush(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep) +{ + if (!priv || !gadget_Ep) + return; +} + +static int32_t gadgetEpAllocRequest(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep, + struct GADGET_REQ **req) +{ + struct GadgetEp *gadgetEp = NULL; + struct GadgetRequest *gadgetRequest = NULL; + + if (!priv || !gadget_Ep || !req) + return -EINVAL; + + gadgetEp = toGadgetEp(gadget_Ep); + if (priv->eventCallback.usbRequestMemAlloc) + gadgetRequest = priv->eventCallback.usbRequestMemAlloc(priv, + sizeof(*gadgetRequest)); + if (!gadgetRequest) + return -ENOMEM; + + memset(gadgetRequest, 0, sizeof(*gadgetRequest)); + *req = &gadgetRequest->request; + INIT_LIST_HEAD(&gadgetRequest->request.list); + gadgetRequest->ep = gadgetEp; + + return 0; +} + +static void gadgetEpFreeRequest(struct GADGET_CTRL *priv, struct GADGET_EP *gadget_Ep, + struct GADGET_REQ *req) +{ + struct GadgetRequest *gadgetRequest = NULL; + + if (!priv || !gadget_Ep || !req) + return; + + gadgetRequest = requestToGadgetRequest(req); + + if (priv->eventCallback.usbRequestMemFree) + priv->eventCallback.usbRequestMemFree(priv, gadgetRequest); +} + +static struct GADGET_EP_OPS gadgetEpOps = { + .epEnable = gadgetEpEnable, + .epDisable = gadgetEpDisable, + .reqQueue = gadgetEpQueue, + .reqDequeue = gadgetEpDequeue, + .epSetHalt = gadgetEpSetHalt, + .epSetWedge = gadgetEpSetWedge, + .epFifoStatus = gadgetEpFifoStatus, + .reqAlloc = gadgetEpAllocRequest, + .reqFree = gadgetEpFreeRequest +}; + +static void gadgetInitDeviceEp(struct GADGET_CTRL *priv, uint8_t isInEp) +{ + uint8_t num; + struct GadgetEp *gadgetEp; + struct GADGET_EP_CFG epCfg; + + if (!priv) + return; + + for (num = 0; num < 16; num++) { + gadgetEp = isInEp ? &priv->in[num] : &priv->out[num]; + if (num) { + epCfg = isInEp ? priv->gadgetCfg.epIN[num] : priv->gadgetCfg.epOUT[num]; + if (!epCfg.bufferingValue) { + gadgetEp->state = GADGET_EP_NOT_IMPLEMENTED; + gadgetEp->hwEpNum = num; + continue; + } + } + INIT_LIST_HEAD(&gadgetEp->gadgetEp.epList); + INIT_LIST_HEAD(&gadgetEp->request); + gadgetEp->state = GADGET_EP_FREE; + gadgetEp->hwEpNum = num; + gadgetEp->isInEp = isInEp; + gadgetEp->requestsInList = 0; + snprintf(gadgetEp->gadgetEp.name, sizeof(gadgetEp->gadgetEp.name), + "Ep%d%s", num, isInEp ? "in" : "out"); + if (!num) { + gadgetEp->gadgetEp.maxPacket = priv->gadgetCfg.epIN[num].maxPacketSize; + if (isInEp) + priv->gadgetDev.ep0 = &gadgetEp->gadgetEp; + gadgetEp->gadgetEp.ops = &gadgetEpOps; + gadgetEp0Enable(priv, gadgetEp); + continue; + } + + if (isInEp) { + if (epCfg.startBuf) + phytium_write16(&priv->regs->txstaddr[num - 1].addr, + epCfg.startBuf); + phytium_write8(&priv->regs->ep[num - 1].txcon, 0); + gadgetEp->gadgetEp.maxPacket = epCfg.maxPacketSize; + } else { + if (epCfg.startBuf) + phytium_write16(&priv->regs->rxstaddr[num - 1].addr, + epCfg.startBuf); + phytium_write8(&priv->regs->ep[num - 1].rxcon, 0); + gadgetEp->gadgetEp.maxPacket = epCfg.maxPacketSize; + } + + gadgetEp->gadgetEp.ops = &gadgetEpOps; + gadgetEp->gadgetEp.address = isInEp ? (0x80 | num) : num; + gadgetEp->gadgetEp.maxburst = 0; + gadgetEp->gadgetEp.mult = 0; + gadgetEp->gadgetEp.maxStreams = 0; + priv->endpointInList++; + list_add_tail(&gadgetEp->gadgetEp.epList, &priv->gadgetDev.epList); + } +} + +static void gadgetInitEndpoint(struct GADGET_CTRL *priv) +{ + if (!priv) + return; + + gadgetInitDeviceEp(priv, 1); + gadgetInitDeviceEp(priv, 0); +} + +static void gadgetSetup(struct GADGET_CTRL *priv) +{ + if (!priv) + return; + + INIT_LIST_HEAD(&priv->gadgetDev.epList); + priv->gadgetDev.state = USB_STATE_NOTATTACHED; + priv->gadgetDev.maxSpeed = USB_SPEED_HIGH; + priv->gadgetDev.speed = USB_SPEED_FULL; + snprintf(priv->gadgetDev.name, sizeof(priv->gadgetDev.name), "Phytium USB SD Driver"); + + gadgetInitEndpoint(priv); + + phytium_write8(&priv->regs->ep0maxpack, 0x40); + phytium_write16(&priv->regs->rxien, 0); + phytium_write16(&priv->regs->txien, 0); + phytium_write16(&priv->regs->rxirq, 0xFFFF); + phytium_write16(&priv->regs->txirq, 0xFFFF); + phytium_write8(&priv->regs->usbirq, 0xEF); + phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX); + phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST | ENDPRST_IO_TX); + phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST); + priv->isReady = 1; +} +int32_t gadgetInit(struct GADGET_CTRL *priv, struct GADGET_CFG *config, + struct GADGET_CALLBACKS *callbacks, struct device *pdev) +{ + struct DMA_SYSREQ dmaSysReq; + uint8_t usbcs; + + if (!priv || !config || !callbacks) + return -EINVAL; + + priv->dev = pdev; + priv->eventCallback = *callbacks; + priv->gadgetCfg = *config; + priv->regs = (struct HW_REGS *)config->regBase; + priv->phy_regs = (struct VHUB_REGS *)config->phy_regBase; + priv->gadgetDrv = GADGET_GetInstance(); + priv->dmaDrv = DMA_GetInstance(); + priv->dmaController = (void *)(priv + 1); + priv->dmaCfg.dmaModeRx = 0xFFFF; + priv->dmaCfg.dmaModeTx = 0xFFFF; + priv->dmaCfg.regBase = config->regBase + 0x400; + priv->dmaCfg.trbAddr = config->trbAddr; + priv->dmaCfg.trbDmaAddr = config->trbDmaAddr; + + priv->dmaDrv->dma_probe(NULL, &dmaSysReq); + priv->privBuffAddr = (uint8_t *)((uintptr_t)config->trbAddr + dmaSysReq.trbMemSize); + priv->privBuffDma = (uintptr_t)((uintptr_t)config->trbDmaAddr + dmaSysReq.trbMemSize); + priv->dmaCallback.complete = gadget_CallbackTransfer; + priv->dmaDrv->dma_init(priv->dmaController, &priv->dmaCfg, &priv->dmaCallback); + priv->dmaDrv->dma_setParentPriv(priv->dmaController, priv); + + usbcs = phytium_read8(&priv->regs->usbcs); + usbcs |= USBCS_DISCON | USBCS_LPMNYET; + phytium_write8(&priv->regs->usbcs, usbcs); + + gadgetSetup(priv); + + usbcs = phytium_read8(&priv->regs->usbcs); + usbcs &= USBCS_DISCON; + phytium_write8(&priv->regs->usbcs, usbcs); + + return 0; +} + +static void gadgetDestroy(struct GADGET_CTRL *priv) +{ + pr_debug("Destroy Device Controller driver\n"); + + if (priv) + return; + gadgetDisconnect(priv); + + gadgetStopActivity(priv); + + phytium_write8(&priv->regs->usbcs, USBCS_DISCON); + + priv->isReady = 0; +} + +static void gadgetStart(struct GADGET_CTRL *priv) +{ + uint8_t usbien, usbcs; + + pr_debug("Usb Device Controller start\n"); + if (!priv) + return; + + usbien = phytium_read8(&priv->regs->usbien); + usbien |= USBIR_URES | USBIR_SUDAV | USBIR_LPMIR; + phytium_write8(&priv->regs->usbien, usbien); + + usbcs = phytium_read8(&priv->regs->usbcs); + usbcs &= ~USBCS_DISCON; + phytium_write8(&priv->regs->usbcs, usbcs); + + priv->dmaDrv->dma_start(priv->dmaController); +} + +static void gadgetReset(struct GADGET_CTRL *priv) +{ + int i = 0; + + pr_debug("Usb Disable Device Activity\n"); + + if (!priv) + return; + + if (priv->gadgetDev.speed != USB_SPEED_UNKNOWN) + gadgetDisconnect(priv); + + priv->gadgetDev.aHnpSupport = 0; + priv->gadgetDev.bHnpEnable = 0; + priv->gadgetDev.state = USB_STATE_DEFAULT; + priv->deviceAddress = 0; + priv->ep0State = GADGET_EP0_STAGE_SETUP; + + gadgetStopActivity(priv); + + for (i = 0; i < 1000; i++) { + priv->gadgetDev.speed = gadgetGetActualSpeed(priv); + if (priv->gadgetDev.speed == USB_SPEED_HIGH) + return; + } +} + +static void gadgetStop(struct GADGET_CTRL *priv) +{ + pr_debug("Usb Device Controller stop\n"); + + if (!priv) + return; + + if (!priv->isReady) + return; + + gadgetReset(priv); + + phytium_write8(&priv->regs->usbien, 0); + priv->dmaDrv->dma_stop(priv->dmaController); + + priv->isReady = 0; +} + +static void gadgetIsr(struct GADGET_CTRL *priv) +{ + uint8_t usbirq, usbien, usbcs; + + if (!priv) + return; + + usbirq = phytium_read8(&priv->regs->usbirq); + usbien = phytium_read8(&priv->regs->usbien); + + pr_debug("usbirq:0x%x usbien:0x%x\n", usbirq, usbien); + + usbirq = usbirq & usbien; + + if (!usbirq) + goto DMA_IRQ; + + if (usbirq & USBIR_LPMIR) { + pr_debug("USBIRQ LPM\n"); + usbcs = phytium_read8(&priv->regs->usbcs); + usbcs &= ~USBCS_LPMNYET; + phytium_write8(&priv->regs->usbcs, usbcs); + phytium_write8(&priv->regs->usbirq, USBIR_LPMIR); + } + + if (usbirq & USBIR_URES) { + pr_debug("USBIRQ RESET\n"); + phytium_write8(&priv->regs->usbirq, USBIR_URES); + priv->releaseEp0Flag = 1; + gadgetReset(priv); + priv->releaseEp0Flag = 0; + priv->gadgetDev.state = USB_STATE_DEFAULT; + if (priv->eventCallback.connect) + priv->eventCallback.connect(priv); + } + + if (usbirq & USBIR_HSPEED) { + pr_debug("USBIRQ HighSpeed\n"); + phytium_write8(&priv->regs->usbirq, USBIR_HSPEED); + priv->gadgetDev.speed = USB_SPEED_HIGH; + } + + if (usbirq & USBIR_SUDAV) { + pr_debug("USBIRQ SUDAV\n"); + priv->ep0State = GADGET_EP0_STAGE_SETUP; + gadgetEp0Irq(priv); + } + + if (usbirq & USBIR_SOF) { + pr_debug("USBIRQ SOF\n"); + phytium_write8(&priv->regs->usbirq, USBIR_SOF); + } + + if (usbirq & USBIR_SUTOK) { + pr_debug("USBIRQ SUTOK\n"); + phytium_write8(&priv->regs->usbirq, USBIR_SUTOK); + } + + if (usbirq & USBIR_SUSP) { + pr_debug("USBIRQ SUSPEND\n"); + phytium_write8(&priv->regs->usbirq, USBIR_SUSP); + } + + return; +DMA_IRQ: + priv->dmaDrv->dma_isr(priv->dmaController); +} + +static void gadgetGetDevInstance(struct GADGET_CTRL *priv, struct GADGET_DEV **dev) +{ + if (!priv || !dev) + return; + + *dev = &priv->gadgetDev; +} + +static int32_t gadgetGetFrame(struct GADGET_CTRL *priv, uint32_t *numOfFrame) +{ + if (!priv || !numOfFrame) + return -EINVAL; + + *numOfFrame = phytium_read16(&priv->regs->frmnr); + + return 0; +} + +static int32_t gadgetWakeUp(struct GADGET_CTRL *priv) +{ + if (!priv) + return -EINVAL; + + return -EOPNOTSUPP; +} + +static int32_t gadgetSetSelfPowered(struct GADGET_CTRL *priv) +{ + if (!priv) + return -EINVAL; + + priv->isSelfPowered = 1; + + return 0; +} + +static int32_t gadgetClearSelfPowered(struct GADGET_CTRL *priv) +{ + if (!priv) + return -EINVAL; + + priv->isSelfPowered = 0; + + return 0; +} + +static int32_t gadgetVbusSession(struct GADGET_CTRL *priv, uint8_t isActive) +{ + if (!priv) + return -EINVAL; + + return -EOPNOTSUPP; +} + +static int32_t gadgetVbusDraw(struct GADGET_CTRL *priv, uint8_t mA) +{ + if (!priv) + return -EINVAL; + + return -EOPNOTSUPP; +} + +static int32_t gadgetPullUp(struct GADGET_CTRL *priv, uint8_t isOn) +{ + if (!priv) + return -EINVAL; + + return -EOPNOTSUPP; +} + +struct GADGET_OBJ GadgetObj = { + .gadget_init = gadgetInit, + .gadget_destroy = gadgetDestroy, + .gadget_start = gadgetStart, + .gadget_stop = gadgetStop, + .gadget_isr = gadgetIsr, + //endpoint operation + .gadget_epEnable = gadgetEpEnable, + .gadget_epDisable = gadgetEpDisable, + .gadget_epSetHalt = gadgetEpSetHalt, + .gadget_epSetWedge = gadgetEpSetWedge, + .gadget_epFifoStatus = gadgetEpFifoStatus, + .gadget_epFifoFlush = gadgetEpFifoFlush, + .gadget_reqQueue = gadgetEpQueue, + .gadget_reqDequeue = gadgetEpDequeue, + .gadget_reqAlloc = gadgetEpAllocRequest, + .gadget_reqFree = gadgetEpFreeRequest, + + //Device operations + .gadget_getDevInstance = gadgetGetDevInstance, + .gadget_dGetFrame = gadgetGetFrame, + .gadget_dWakeUp = gadgetWakeUp, + .gadget_dSetSelfpowered = gadgetSetSelfPowered, + .gadget_dClearSelfpowered = gadgetClearSelfPowered, + .gadget_dVbusSession = gadgetVbusSession, + .gadget_dVbusDraw = gadgetVbusDraw, + .gadget_dPullUp = gadgetPullUp, +}; + +struct GADGET_OBJ *GADGET_GetInstance(void) +{ + return &GadgetObj; +} + +static int phytium_gadget_set_default_cfg(struct phytium_cusb *config) +{ + int index; + + config->gadget_cfg.regBase = (uintptr_t)config->regs; + config->gadget_cfg.phy_regBase = (uintptr_t)config->phy_regs; + config->gadget_cfg.dmaInterfaceWidth = GADGET_DMA_32_WIDTH; + + for (index = 0; index < 16; index++) { + if (index == 0) { + config->gadget_cfg.epIN[index].bufferingValue = 1; + config->gadget_cfg.epIN[index].maxPacketSize = 64; + config->gadget_cfg.epIN[index].startBuf = 0; + + config->gadget_cfg.epOUT[index].bufferingValue = 1; + config->gadget_cfg.epOUT[index].maxPacketSize = 64; + config->gadget_cfg.epOUT[index].startBuf = 0; + } else { + config->gadget_cfg.epIN[index].bufferingValue = 4; + config->gadget_cfg.epIN[index].maxPacketSize = 1024; + config->gadget_cfg.epIN[index].startBuf = 64 + 4096 * (index - 1); + + config->gadget_cfg.epOUT[index].bufferingValue = 4; + config->gadget_cfg.epOUT[index].maxPacketSize = 1024; + config->gadget_cfg.epOUT[index].startBuf = 64 + 4096 * (index - 1); + } + } + + return 0; +} + +void gadget_callback_connect(struct GADGET_CTRL *priv) +{ + if (!priv) + return; +} + +void gadget_callback_disconnect(struct GADGET_CTRL *priv) +{ + if (!priv) + return; +} + +int32_t gadget_callback_setup(struct GADGET_CTRL *priv, struct usb_ctrlrequest *ctrl) +{ + struct phytium_cusb *config; + int ret = 0; + + if (!priv || !ctrl) + return -EINVAL; + + config = dev_get_drvdata(priv->dev); + if (!config) + return -1; + + if (!config->gadget_driver) + return -EOPNOTSUPP; + + if (ctrl->bRequestType & USB_DIR_IN) + config->ep0_data_stage_is_tx = 1; + else + config->ep0_data_stage_is_tx = 0; + + spin_unlock(&config->lock); + ret = config->gadget_driver->setup(&config->gadget, ctrl); + spin_lock(&config->lock); + + if (ret == 0x7FFF) + return ret; + + if (ret < 0) + return 1; + + return 0; +} + +void *gadget_callback_usbRequestMemAlloc(struct GADGET_CTRL *priv, u32 size) +{ + struct GADGET_REQ *gadget_req = NULL; + + gadget_req = kzalloc(size, GFP_NOWAIT); + if (!gadget_req) + return NULL; + + return gadget_req; +} + +void gadget_callback_usbRequestMemFree(struct GADGET_CTRL *priv, void *usbReq) +{ + if (!usbReq) + return; + + kfree(usbReq); +} + +static void init_peripheral_ep(struct phytium_cusb *config, + struct phytium_ep *phy_ep, struct GADGET_EP *gadget_ep, int is_tx) +{ + if (!config || !phy_ep || !gadget_ep) + return; + + memset(phy_ep, 0, sizeof(*phy_ep)); + phy_ep->config = config; + phy_ep->is_tx = is_tx; + phy_ep->gadget_ep = gadget_ep; + phy_ep->ep_num = gadget_ep->address & 0xF; + phy_ep->end_point.maxpacket = gadget_ep->maxPacket; + phy_ep->end_point.maxpacket_limit = 1024; + + INIT_LIST_HEAD(&phy_ep->req_list); + sprintf(phy_ep->name, "ep%d%s", phy_ep->ep_num, is_tx ? "in" : "out"); + + switch (phy_ep->ep_num) { + case 0: + phy_ep->end_point.caps.type_control = 1; + break; + case 1: + phy_ep->end_point.caps.type_bulk = 1; + break; + case 2: + phy_ep->end_point.caps.type_int = 1; + break; + case 3: + phy_ep->end_point.caps.type_iso = 1; + break; + default: + phy_ep->end_point.caps.type_int = 1; + phy_ep->end_point.caps.type_bulk = 1; + break; + } + + if (is_tx) { + phy_ep->end_point.caps.dir_in = 1; + phy_ep->end_point.caps.dir_out = 0; + } else { + phy_ep->end_point.caps.dir_in = 0; + phy_ep->end_point.caps.dir_out = 1; + } + + phy_ep->end_point.name = phy_ep->name; + + INIT_LIST_HEAD(&phy_ep->end_point.ep_list); + + if (!phy_ep->ep_num) { + phy_ep->end_point.ops = &gadget_ep0_ops; + config->gadget.ep0 = &phy_ep->end_point; + + if (config->gadget_dev->maxSpeed > USB_SPEED_HIGH) + config->gadget.ep0->maxpacket = 9; + } else { + phy_ep->end_point.ops = &gadget_ep_ops; + list_add_tail(&phy_ep->end_point.ep_list, &config->gadget.ep_list); + } +} + +static void gadget_init_endpoint(struct phytium_cusb *config) +{ + struct list_head *list; + struct GADGET_EP *gadget_ep; + + if (!config) + return; + + INIT_LIST_HEAD(&(config->gadget.ep_list)); + + init_peripheral_ep(config, &config->endpoints_tx[0], config->gadget_dev->ep0, 1); + init_peripheral_ep(config, &config->endpoints_rx[0], config->gadget_dev->ep0, 0); + + list_for_each(list, &config->gadget_dev->epList) { + gadget_ep = (struct GADGET_EP *)list; + if (gadget_ep->address & USB_DIR_IN) + init_peripheral_ep(config, &config->endpoints_tx[gadget_ep->address & 0xf], + gadget_ep, 1); + else + init_peripheral_ep(config, &config->endpoints_rx[gadget_ep->address & 0xf], + gadget_ep, 0); + } +} + +static int gadget_setup(struct phytium_cusb *config) +{ + int ret = -1; + + config->gadget_obj->gadget_getDevInstance(config->gadget_priv, &config->gadget_dev); + config->gadget.ops = &phytium_gadget_ops; + config->gadget.max_speed = config->gadget_dev->maxSpeed; + config->gadget.speed = USB_SPEED_HIGH; + config->gadget.name = "phytium_gadget"; + config->gadget.is_otg = 0; + + gadget_init_endpoint(config); + + ret = usb_add_gadget_udc(config->dev, &config->gadget); + if (ret) + goto err; + + return 0; + +err: + config->gadget.dev.parent = NULL; + device_unregister(&config->gadget.dev); + return ret; +} + +int phytium_gadget_reinit(struct phytium_cusb *config) +{ + struct GADGET_CTRL *ctrl; + + if (!config) + return 0; + + ctrl = (struct GADGET_CTRL *)config->gadget_priv; + if (!ctrl) + return 0; + + gadgetStop(ctrl); + + config->gadget_obj->gadget_init(config->gadget_priv, &config->gadget_cfg, + &config->gadget_callbacks, config->dev); + + return 0; +} + +int phytium_gadget_init(struct phytium_cusb *config) +{ + int ret; + + if (!config) + return 0; + + phytium_gadget_set_default_cfg(config); + config->gadget_obj = &GadgetObj; + + config->dma_cfg.regBase = config->gadget_cfg.regBase + 0x400; + config->dma_obj = DMA_GetInstance(); + config->dma_obj->dma_probe(&config->dma_cfg, &config->dma_sysreq); + + config->gadget_sysreq.privDataSize = sizeof(struct GADGET_CTRL); + config->gadget_sysreq.trbMemSize = config->dma_sysreq.trbMemSize + GADGET_PRIV_BUFFER_SIZE; + config->gadget_sysreq.privDataSize += config->dma_sysreq.privDataSize; + + config->gadget_priv = devm_kzalloc(config->dev, + config->gadget_sysreq.privDataSize, GFP_KERNEL); + if (!config->gadget_priv) { + ret = -ENOMEM; + goto err_probe; + } + config->gadget_cfg.trbAddr = dma_alloc_coherent(config->dev, + config->gadget_sysreq.trbMemSize, + (dma_addr_t *)&config->gadget_cfg.trbDmaAddr, GFP_KERNEL); + if (!config->gadget_cfg.trbAddr) { + ret = -ENOMEM; + goto err_dma_coherent; + } + + config->gadget_callbacks.connect = gadget_callback_connect; + config->gadget_callbacks.disconnect = gadget_callback_disconnect; + config->gadget_callbacks.setup = gadget_callback_setup; + config->gadget_callbacks.usbRequestMemAlloc = gadget_callback_usbRequestMemAlloc; + config->gadget_callbacks.usbRequestMemFree = gadget_callback_usbRequestMemFree; + + ret = config->gadget_obj->gadget_init(config->gadget_priv, &config->gadget_cfg, + &config->gadget_callbacks, config->dev); + if (ret) { + ret = -ENODEV; + goto err_init; + } + + //dev_set_drvdata(config->dev, config); + + gadget_setup(config); + + return 0; + +err_init: + dma_free_coherent(config->dev, config->gadget_sysreq.trbMemSize, + config->gadget_cfg.trbAddr, config->gadget_cfg.trbDmaAddr); +err_dma_coherent: +err_probe: + dev_set_drvdata(config->dev, NULL); + + return ret; +} + +int phytium_gadget_uninit(struct phytium_cusb *config) +{ + if (config) + usb_del_gadget_udc(&config->gadget); + + return 0; +} + +#ifdef CONFIG_PM +int phytium_gadget_resume(void *priv) +{ + struct GADGET_CTRL *ctrl; + uint32_t gen_cfg; + unsigned long flags = 0; + struct phytium_cusb *config = (struct phytium_cusb *)priv; + + if (!config) + return 0; + + ctrl = (struct GADGET_CTRL *)config->gadget_priv; + if (!ctrl) + return 0; + + spin_lock_irqsave(&config->lock, flags); + phytium_gadget_reinit(config); + + if (config->gadget_driver) { + config->gadget_obj->gadget_start(config->gadget_priv); + if (ctrl->phy_regs) { + gen_cfg = phytium_read32(&ctrl->phy_regs->gen_cfg); + gen_cfg = gen_cfg & (~BIT(7)); + phytium_write32(&ctrl->phy_regs->gen_cfg, gen_cfg); + } + } + spin_unlock_irqrestore(&config->lock, flags); + + return 0; +} + +int phytium_gadget_suspend(void *priv) +{ + return 0; +} +#endif diff --git a/drivers/usb/phytium/gadget.h b/drivers/usb/phytium/gadget.h new file mode 100644 index 0000000000000000000000000000000000000000..d87b55ade7a70b29189402e8164d833e6340730b --- /dev/null +++ b/drivers/usb/phytium/gadget.h @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __PHYTIUM_GADGET_H_ +#define __PHYTIUM_GADGET_H_ + +#include +#include +#include "dma.h" + +struct GADGET_CTRL; +struct GADGET_EP; +struct GADGET_REQ; + +enum GADGET_EP_STATE { + GADGET_EP_FREE, + GADGET_EP_ALLOCATED, + GADGET_EP_BUSY, + GADGET_EP_NOT_IMPLEMENTED +}; + +enum GADGET_EP0_STAGE { + GADGET_EP0_STAGE_SETUP, + GADGET_EP0_STAGE_IN, + GADGET_EP0_STAGE_OUT, + GADGET_EP0_STAGE_STATUSIN, + GADGET_EP0_STAGE_STATUSOUT, + GADGET_EP0_STAGE_ACK +}; + +struct GADGET_EP_OPS { + int32_t (*epEnable)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, + const struct usb_endpoint_descriptor *desc); + + int32_t (*epDisable)(struct GADGET_CTRL *priv, struct GADGET_EP *ep); + + int32_t (*epSetHalt)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, uint8_t value); + + int32_t (*epSetWedge)(struct GADGET_CTRL *priv, struct GADGET_EP *ep); + + int32_t (*epFifoStatus)(struct GADGET_CTRL *priv, struct GADGET_EP *ep); + + int32_t (*epFifoFlush)(struct GADGET_CTRL *priv, struct GADGET_EP *ep); + + int32_t (*reqQueue)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, + struct GADGET_REQ *req); + + int32_t (*reqDequeue)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, + struct GADGET_REQ *req); + + int32_t (*reqAlloc)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, + struct GADGET_REQ **req); + + void (*reqFree)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, + struct GADGET_REQ *req); +}; + +struct GADGET_EP { + struct list_head epList; + char name[255]; + struct GADGET_EP_OPS *ops; + uint16_t maxPacket; + uint16_t maxStreams; + uint8_t mult; + uint8_t maxburst; + uint8_t address; + const struct usb_endpoint_descriptor *desc; + const struct usb_ss_ep_comp_descriptor *compDesc; +}; + +enum GADGET_DMAInterfaceWidth { + GADGET_DMA_32_WIDTH = 4, + GADGET_DMA_64_WIDTH = 8, +}; + +struct GADGET_EP_CFG { + uint8_t bufferingValue; + uint16_t startBuf; + uint16_t maxPacketSize; +}; + +struct GADGET_CFG { + uintptr_t regBase; + uintptr_t phy_regBase; + struct GADGET_EP_CFG epIN[16]; + struct GADGET_EP_CFG epOUT[16]; + enum GADGET_DMAInterfaceWidth dmaInterfaceWidth; + void *trbAddr; + uintptr_t trbDmaAddr; +}; + +struct GADGET_SYSREQ { + uint32_t privDataSize; + uint32_t trbMemSize; +}; + +struct GadgetEp { + struct GADGET_EP gadgetEp; + enum GADGET_EP_STATE state; + uint8_t hwEpNum; + uint8_t isInEp; + struct list_head request; + uint8_t iso_flag; + void *channel; + uint32_t requestsInList; + uint8_t wedged; +}; + +struct GADGET_DEV { + struct list_head epList; + struct GADGET_EP *ep0; + unsigned int speed; + unsigned int maxSpeed; + enum usb_device_state state; + uint8_t sgSupported; + uint8_t bHnpEnable; + uint8_t aHnpSupport; + char name[255]; +}; + +struct GADGET_SgList { + uintptr_t link; + uint32_t offset; + uint32_t length; + uintptr_t dmaAddress; +}; + +struct GADGET_REQ { + struct list_head list; + void *buf; + uint32_t length; + uintptr_t dma; + uint32_t numOfSgs; + uint32_t numMappedSgs; + uint16_t streamId; + uint8_t oInterrupt; + uint8_t zero; + uint8_t shortNotOk; + void *context; + uint32_t status; + uint32_t actual; + struct GADGET_SgList *sg; + void (*complete)(struct GADGET_EP *ep, struct GADGET_REQ *req); +}; + + +struct GADGET_CALLBACKS { + void (*disconnect)(struct GADGET_CTRL *priv); + + void (*connect)(struct GADGET_CTRL *priv); + + int32_t (*setup)(struct GADGET_CTRL *priv, + struct usb_ctrlrequest *ctrl); + + void *(*usbRequestMemAlloc)(struct GADGET_CTRL *priv, + uint32_t requiredSize); + + void (*usbRequestMemFree)(struct GADGET_CTRL *priv, void *usbRequest); +}; + +struct GADGET_OBJ { + int32_t (*gadget_init)(struct GADGET_CTRL *priv, struct GADGET_CFG *config, + struct GADGET_CALLBACKS *callbacks, struct device *pdev); + + void (*gadget_destroy)(struct GADGET_CTRL *priv); + + void (*gadget_start)(struct GADGET_CTRL *priv); + + void (*gadget_stop)(struct GADGET_CTRL *priv); + + void (*gadget_isr)(struct GADGET_CTRL *priv); + + int32_t (*gadget_epEnable)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, + const struct usb_endpoint_descriptor *desc); + + int32_t (*gadget_epDisable)(struct GADGET_CTRL *priv, struct GADGET_EP *ep); + + int32_t (*gadget_epSetHalt)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, uint8_t value); + + int32_t (*gadget_epSetWedge)(struct GADGET_CTRL *priv, struct GADGET_EP *ep); + + int32_t (*gadget_epFifoStatus)(struct GADGET_CTRL *priv, struct GADGET_EP *ep); + + void (*gadget_epFifoFlush)(struct GADGET_CTRL *priv, struct GADGET_EP *ep); + + int32_t (*gadget_reqQueue)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, + struct GADGET_REQ *req); + + int32_t (*gadget_reqDequeue)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, + struct GADGET_REQ *req); + + int32_t (*gadget_reqAlloc)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, + struct GADGET_REQ **req); + + void (*gadget_reqFree)(struct GADGET_CTRL *priv, struct GADGET_EP *ep, + struct GADGET_REQ *req); + + void (*gadget_getDevInstance)(struct GADGET_CTRL *priv, struct GADGET_DEV **dev); + + int32_t (*gadget_dGetFrame)(struct GADGET_CTRL *priv, uint32_t *numOfFrame); + + int32_t (*gadget_dWakeUp)(struct GADGET_CTRL *priv); + + int32_t (*gadget_dSetSelfpowered)(struct GADGET_CTRL *priv); + + int32_t (*gadget_dClearSelfpowered)(struct GADGET_CTRL *priv); + + int32_t (*gadget_dVbusSession)(struct GADGET_CTRL *priv, uint8_t isActive); + + int32_t (*gadget_dVbusDraw)(struct GADGET_CTRL *priv, uint8_t mA); + + int32_t (*gadget_dPullUp)(struct GADGET_CTRL *priv, uint8_t isOn); + + void (*gadget_dGetConfigParams)(struct GADGET_CTRL *priv, + struct usb_dcd_config_params *configParams); +}; + +struct GADGET_CTRL { + struct device *dev; + struct GADGET_DEV gadgetDev; + struct HW_REGS *regs; + struct GADGET_OBJ *gadgetDrv; + struct GADGET_CFG gadgetCfg; + struct GADGET_CALLBACKS eventCallback; + struct GadgetEp in[16]; + struct GadgetEp out[16]; + enum GADGET_EP0_STAGE ep0State; + uint8_t isRemoteWakeup; + uint8_t isSelfPowered; + uint8_t deviceAddress; + struct DMA_OBJ *dmaDrv; + void *dmaController; + struct DMA_CFG dmaCfg; + struct DMA_CALLBACKS dmaCallback; + uint8_t releaseEp0Flag; + uint8_t isReady; + uint8_t *privBuffAddr; + uintptr_t privBuffDma; + uint8_t endpointInList; + uint8_t hostRequestFlag; + struct VHUB_REGS *phy_regs; +}; + +struct GadgetRequest { + struct GADGET_REQ request; + struct GadgetEp *ep; + struct GADGET_DEV *dev; + uint8_t zlp; +}; + +struct GADGET_OBJ *GADGET_GetInstance(void); + +#endif /* __LINUX_PHYTIUM_GADGET */ + diff --git a/drivers/usb/phytium/host.c b/drivers/usb/phytium/host.c new file mode 100644 index 0000000000000000000000000000000000000000..0f86dc239966205b915768f34ddd559513bcc31f --- /dev/null +++ b/drivers/usb/phytium/host.c @@ -0,0 +1,2765 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +//#include "list.h" +#include "core.h" +#include "dma.h" +#include "hw-regs.h" + +#define DRV_NAME "phytium_usb" + +#define HOST_GENERIC_EP_CONTROL 0x00 +#define HOST_GENERIC_EP_ISOC 0x01 +#define HOST_GENERIC_EP_BULK 0x02 +#define HOST_GENERIC_EP_INT 0x03 + +#define HOST_ESTALL 1 +#define HOST_EUNHANDLED 2 +#define HOST_EAUTOACK 3 +#define HOST_ESHUTDOWN 4 + +#define HOST_EP_NUM 16 + +static int get_epnum_from_pool(struct HOST_CTRL *priv, int real_epNum, bool dirIn) +{ + int index, dir = 0; + int ret = 0; + + if (!priv) + return 0; + + if (!dirIn) + dir = 1; + + if (real_epNum <= MAX_INSTANCE_EP_NUM) { + if (!priv->ep_remap_pool[dir][real_epNum]) { + priv->ep_remap_pool[dir][real_epNum] = real_epNum; + ret = real_epNum; + goto out; + } + + if (priv->ep_remap_pool[dir][real_epNum] == real_epNum) { + ret = real_epNum; + goto out; + } + } else { + for (index = 1; index <= MAX_INSTANCE_EP_NUM; index++) { + if (priv->ep_remap_pool[dir][index] == real_epNum) { + ret = index; + goto out; + } + } + } + + for (index = 1; index <= MAX_INSTANCE_EP_NUM; index++) { + if (!priv->ep_remap_pool[dir][index]) { + priv->ep_remap_pool[dir][index] = real_epNum; + ret = index; + goto out; + } + } + +out: + return ret; +} + +static int release_epnum_from_pool(struct HOST_CTRL *priv, int real_epNum, bool dirIn) +{ + int index = 0; + int dir = 0; + + if (!priv) + return 0; + + if (!dirIn) + dir = 1; + + for (index = 1; index <= MAX_INSTANCE_EP_NUM; index++) { + if (priv->ep_remap_pool[dir][index] == real_epNum) { + priv->ep_remap_pool[dir][index] = 0; + + return 0; + } + } + + return 0; +} + +static inline struct HOST_REQ *getUsbRequestEntry(struct list_head *list) +{ + return (struct HOST_REQ *)((uintptr_t)list - (uintptr_t)&(((struct HOST_REQ *)0)->list)); +} + +static inline struct HOST_EP_PRIV *getUsbHEpPrivEntry(struct list_head *list) +{ + struct HOST_EP_PRIV *hostEpPriv; + + if (list_empty(list)) + return NULL; + + hostEpPriv = (struct HOST_EP_PRIV *)((uintptr_t)list - + (uintptr_t)&(((struct HOST_EP_PRIV *)0)->node)); + + return hostEpPriv; +} + +static struct HOST_REQ *getNextReq(struct HOST_EP *usbEp) +{ + struct list_head *queue; + + if (!usbEp) + return NULL; + + queue = &usbEp->reqList; + + if (list_empty(queue)) + return NULL; + + return getUsbRequestEntry(queue->next); +} + +static void host_SetVbus(struct HOST_CTRL *priv, uint8_t isOn) +{ + uint8_t otgctrl = phytium_read8(&priv->regs->otgctrl); + + if (isOn) { + if (!(otgctrl & OTGCTRL_BUSREQ) || (otgctrl & OTGCTRL_ABUSDROP)) { + otgctrl &= ~OTGCTRL_ABUSDROP; + otgctrl |= OTGCTRL_BUSREQ; + phytium_write8(&priv->regs->otgctrl, otgctrl); + } + priv->otgState = HOST_OTG_STATE_A_WAIT_BCON; + } else { + if ((otgctrl & OTGCTRL_BUSREQ) || (otgctrl & OTGCTRL_ABUSDROP)) { + otgctrl |= OTGCTRL_ABUSDROP; + otgctrl &= ~OTGCTRL_BUSREQ; + phytium_write8(&priv->regs->otgctrl, otgctrl); + } + priv->otgState = HOST_OTG_STATE_A_IDLE; + } +} + +static inline void disconnectHostDetect(struct HOST_CTRL *priv) +{ + uint8_t otgctrl, otgstate; + uint32_t gen_cfg; + + if (!priv) + return; + + memset(priv->ep_remap_pool, 0, sizeof(priv->ep_remap_pool)); + otgctrl = phytium_read8(&priv->regs->otgctrl); + if ((otgctrl & OTGCTRL_ASETBHNPEN) && priv->otgState == HOST_OTG_STATE_A_SUSPEND) + pr_info("Device no Response\n"); + + phytium_write8(&priv->regs->otgirq, OTGIRQ_CONIRQ); +retry: + otgstate = phytium_read8(&priv->regs->otgstate); + if ((otgstate == HOST_OTG_STATE_A_HOST || otgstate == HOST_OTG_STATE_B_HOST)) { + pr_info("IRQ OTG: DisconnIrq Babble\n"); + goto retry; + } + + phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX); + phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST | ENDPRST_IO_TX); + phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST); + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | 0 | 0x04); + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | FIFOCTRL_IO_TX | 0 | 0x04); + + priv->portStatus = USB_PORT_STAT_POWER; + priv->portStatus |= USB_PORT_STAT_C_CONNECTION << 16; + + if (priv->hostCallbacks.portStatusChange) + priv->hostCallbacks.portStatusChange(priv); + + if (priv->otgState == HOST_OTG_STATE_A_SUSPEND) + host_SetVbus(priv, 1); + + priv->otgState = HOST_OTG_STATE_A_IDLE; + if (priv->custom_regs) { + phytium_write32(&priv->custom_regs->wakeup, 1); + } else { + gen_cfg = phytium_read32(&priv->vhub_regs->gen_cfg); + gen_cfg |= BIT(1); + phytium_write32(&priv->vhub_regs->gen_cfg, gen_cfg); + } +} + +static inline void A_IdleDetect(struct HOST_CTRL *priv, uint8_t otgstate) +{ + uint8_t otgctrl; + + if (!priv) + return; + + phytium_write8(&priv->regs->otgirq, OTGIRQ_IDLEIRQ); + + if (otgstate != HOST_OTG_STATE_A_IDLE) { + pr_info("IRQ OTG: A_IDLE Babble\n"); + return; + } + + priv->portStatus = 0; + otgctrl = phytium_read8(&priv->regs->otgctrl); + otgctrl &= ~OTGCTRL_ASETBHNPEN; + phytium_write8(&priv->regs->otgctrl, otgctrl); + + host_SetVbus(priv, 1); + + priv->otgState = HOST_OTG_STATE_A_IDLE; +} + +static inline void B_IdleDetect(struct HOST_CTRL *priv, uint8_t otgstate) +{ + uint8_t otgctrl, usbcs; + + if (!priv) + return; + + phytium_write8(&priv->regs->otgirq, OTGIRQ_IDLEIRQ); + + if (otgstate != HOST_OTG_STATE_B_IDLE) { + pr_info("IRQ OTG: B_IDLE Babble\n"); + return; + } + + otgctrl = phytium_read8(&priv->regs->otgctrl); + otgctrl &= ~OTGCTRL_ASETBHNPEN; + phytium_write8(&priv->regs->otgctrl, otgctrl); + + host_SetVbus(priv, 0); + + priv->otgState = HOST_OTG_STATE_B_IDLE; + + usbcs = phytium_read8(&priv->regs->usbcs); + usbcs &= ~USBCS_DISCON; + phytium_write8(&priv->regs->usbcs, usbcs); +} + +static uint32_t waitForBusyBit(struct HOST_CTRL *priv, struct HostEp *hwEp) +{ + uint8_t *csReg; + uint8_t flag = CS_BUSY; + uint8_t buf = 0; + uint8_t val = CS_BUSY; + uint8_t otgstate; + uint8_t bufflag = 0; + + if (!priv || !hwEp) + return 0; + + if (hwEp->isInEp) + return 0; + + if (hwEp->hwEpNum == 0) { + csReg = &priv->regs->ep0cs; + flag = EP0CS_TXBUSY_MASK; + buf = 0; + } else { + csReg = &priv->regs->ep[hwEp->hwEpNum - 1].txcs; + buf = phytium_read8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon) & CON_BUF; + } + + while ((val & flag) || bufflag == 0) { + otgstate = phytium_read8(&priv->regs->otgstate); + if (otgstate != HOST_OTG_STATE_B_HOST && otgstate != HOST_OTG_STATE_A_HOST) { + priv->ep0State = HOST_EP0_STAGE_IDLE; + return HOST_ESHUTDOWN; + } + + val = phytium_read8(csReg); + if (((val & CS_NPAK) >> CS_NPAK_OFFSET) == buf || buf == 0) + bufflag = 1; + else + bufflag = 0; + } + + return 0; +} + +static inline void connectHostDetect(struct HOST_CTRL *priv, uint8_t otgState) +{ + uint32_t gen_cfg; + + if (!priv) + return; + pr_debug("otgState:0x%x pirv->otgState:0x%x\n", otgState, priv->otgState); + if (priv->custom_regs) { + phytium_write32(&priv->custom_regs->wakeup, 0); + } else { + gen_cfg = phytium_read32(&priv->vhub_regs->gen_cfg); + gen_cfg &= ~BIT(1); + phytium_write32(&priv->vhub_regs->gen_cfg, gen_cfg); + } + + phytium_write8(&priv->regs->otgirq, OTGIRQ_CONIRQ); + + if ((otgState != HOST_OTG_STATE_A_HOST) && (otgState != HOST_OTG_STATE_B_HOST)) + return; + + if ((priv->otgState == HOST_OTG_STATE_A_PERIPHERAL) + || (priv->otgState == HOST_OTG_STATE_B_PERIPHERAL)) + priv->otgState = otgState; + + priv->ep0State = HOST_EP0_STAGE_IDLE; + + priv->portStatus &= ~(USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED | + USB_PORT_STAT_ENABLE); + + priv->portStatus |= USB_PORT_STAT_C_CONNECTION | (USB_PORT_STAT_C_CONNECTION << 16); + priv->dmaDrv->dma_controllerReset(priv->dmaController); + priv->port_resetting = 1; + host_SetVbus(priv, 1); + + switch (phytium_read8(&priv->regs->speedctrl)) { + case SPEEDCTRL_HS: + priv->portStatus |= USB_PORT_STAT_HIGH_SPEED; + pr_debug("detect High speed device\n"); + break; + case SPEEDCTRL_FS: + priv->portStatus &= ~(USB_PORT_STAT_HIGH_SPEED | USB_PORT_STAT_LOW_SPEED); + pr_debug("detect Full speed device\n"); + break; + case SPEEDCTRL_LS: + priv->portStatus |= USB_PORT_STAT_LOW_SPEED; + pr_debug("detect Low speed device\n"); + break; + } + + priv->vBusErrCnt = 0; + priv->dmaDrv->dma_setHostMode(priv->dmaController); + + if (priv->hostCallbacks.portStatusChange) + priv->hostCallbacks.portStatusChange(priv); + + priv->otgState = otgState; +} + +static void hostOtgIrq(struct HOST_CTRL *priv) +{ + uint8_t otgirq, otgien; + uint8_t otgstatus, otgstate; + uint8_t otgctrl; + + if (!priv) + return; + + otgirq = phytium_read8(&priv->regs->otgirq); + otgien = phytium_read8(&priv->regs->otgien); + otgstatus = phytium_read8(&priv->regs->otgstatus); + otgstate = phytium_read8(&priv->regs->otgstate); + otgirq &= otgien; + + if (!otgirq) + return; + + if (otgirq & OTGIRQ_BSE0SRPIRQ) { + otgirq &= ~OTGIRQ_BSE0SRPIRQ; + phytium_write8(&priv->regs->otgirq, OTGIRQ_BSE0SRPIRQ); + + otgctrl = phytium_read8(&priv->regs->otgctrl); + otgctrl &= ~OTGIRQ_BSE0SRPIRQ; + phytium_write8(&priv->regs->otgctrl, otgctrl); + } + + if (otgirq & OTGIRQ_SRPDETIRQ) { + otgirq &= ~OTGIRQ_SRPDETIRQ; + phytium_write8(&priv->regs->otgirq, OTGIRQ_SRPDETIRQ); + + otgctrl = phytium_read8(&priv->regs->otgctrl); + otgctrl &= ~OTGIRQ_SRPDETIRQ; + phytium_write8(&priv->regs->otgctrl, otgctrl); + } + + if (otgirq & OTGIRQ_VBUSERRIRQ) { + otgirq &= ~OTGIRQ_VBUSERRIRQ; + phytium_write8(&priv->regs->otgirq, OTGIRQ_VBUSERRIRQ); + + if (otgstate != HOST_OTG_STATE_A_VBUS_ERR) { + pr_info("IRQ OTG: VBUS ERROR Babble\n"); + return; + } + + host_SetVbus(priv, 0); + priv->otgState = HOST_OTG_STATE_A_VBUS_ERR; + if (priv->portStatus & USB_PORT_STAT_CONNECTION) { + priv->portStatus = USB_PORT_STAT_POWER | (USB_PORT_STAT_C_CONNECTION << 16); + if (priv->hostCallbacks.portStatusChange) + priv->hostCallbacks.portStatusChange(priv); + return; + } + + if (priv->vBusErrCnt >= 3) { + priv->vBusErrCnt = 0; + pr_info("%s %d VBUS OVER CURRENT\n", __func__, __LINE__); + priv->portStatus |= USB_PORT_STAT_OVERCURRENT | + (USB_PORT_STAT_C_OVERCURRENT << 16); + + phytium_write8(&priv->regs->otgirq, OTGIRQ_IDLEIRQ); + } else { + priv->vBusErrCnt++; + host_SetVbus(priv, 1); + phytium_write8(&priv->regs->otgirq, OTGIRQ_IDLEIRQ); + } + } + + if (otgirq & OTGIRQ_CONIRQ) { + if (priv->otgState == HOST_OTG_STATE_A_HOST || + priv->otgState == HOST_OTG_STATE_B_HOST || + priv->otgState == HOST_OTG_STATE_A_SUSPEND) { + if (otgstate == HOST_OTG_STATE_A_WAIT_VFALL || + otgstate == HOST_OTG_STATE_A_WAIT_BCON || + otgstate == HOST_OTG_STATE_A_SUSPEND) + disconnectHostDetect(priv); + } else if (priv->otgState != HOST_OTG_STATE_A_HOST && + priv->otgState != HOST_OTG_STATE_B_HOST && + priv->otgState != HOST_OTG_STATE_A_SUSPEND) + connectHostDetect(priv, otgstate); + + phytium_write8(&priv->regs->otgirq, OTGIRQ_CONIRQ); + } + + if (otgirq & OTGIRQ_IDLEIRQ) { + if (!(otgstatus & OTGSTATUS_ID)) + A_IdleDetect(priv, otgstate); + else + B_IdleDetect(priv, otgstate); + } + + phytium_write8(&priv->regs->otgirq, OTGIRQ_IDCHANGEIRQ | + OTGIRQ_SRPDETIRQ); +} + +static void hostErrorIrq(struct HOST_CTRL *priv) +{ + uint16_t txerrirq, txerrien; + uint16_t rxerrirq, rxerrien; + uint16_t i, mask; + + if (!priv) + return; + + txerrirq = phytium_read16(&priv->regs->txerrirq); + txerrien = phytium_read16(&priv->regs->txerrien); + txerrirq &= txerrien; + + rxerrirq = phytium_read16(&priv->regs->rxerrirq); + rxerrien = phytium_read16(&priv->regs->rxerrien); + rxerrirq &= rxerrirq; + if (!txerrirq && !rxerrirq) + return; + + for (i = 0; i < HOST_EP_NUM; i++) { + mask = 1 << i; + if (rxerrirq & mask) { + phytium_write16(&priv->regs->rxerrirq, mask); + rxerrien &= ~mask; + phytium_write16(&priv->regs->rxerrien, rxerrien); + priv->dmaDrv->dma_errIsr(priv->dmaController, i, 0); + } + + if (txerrirq & mask) { + phytium_write16(&priv->regs->txerrirq, mask); + txerrien &= ~mask; + phytium_write16(&priv->regs->txerrien, txerrien); + priv->dmaDrv->dma_errIsr(priv->dmaController, i, 1); + } + } +} + +static uint32_t decodeErrorCode(uint8_t code) +{ + uint32_t status = 0; + + switch (code) { + case ERR_NONE: + status = 0; + break; + case ERR_CRC: + pr_info("CRC Error\n"); + status = HOST_ESHUTDOWN; + break; + case ERR_DATA_TOGGLE_MISMATCH: + pr_info("Toggle MisMatch Error\n"); + status = HOST_ESHUTDOWN; + break; + case ERR_STALL: + pr_debug("Stall Error\n"); + status = HOST_ESTALL; + break; + case ERR_TIMEOUT: + pr_debug("Timeout Error\n"); + status = HOST_ESHUTDOWN; + break; + case ERR_PID: + pr_info("PID Error\n"); + status = HOST_ESHUTDOWN; + break; + case ERR_TOO_LONG_PACKET: + pr_info("TOO_LONG_PACKET Error\n"); + status = HOST_ESHUTDOWN; + break; + case ERR_DATA_UNDERRUN: + pr_info("UNDERRUN Error\n"); + status = HOST_ESHUTDOWN; + break; + } + + return status; +} + +static struct HOST_EP_PRIV *getIntTransfer(struct list_head *head) +{ + struct list_head *listEntry = NULL; + struct HOST_EP_PRIV *usbHEpPriv = NULL; + struct HOST_EP_PRIV *usbHEpPrivActual = NULL; + + list_for_each(listEntry, head) { + usbHEpPriv = getUsbHEpPrivEntry(listEntry); + if (!usbHEpPrivActual) + usbHEpPrivActual = usbHEpPriv; + + if (usbHEpPriv->frame < usbHEpPrivActual->frame) + usbHEpPrivActual = usbHEpPriv; + } + + return usbHEpPrivActual; +} + +static void givebackRequest(struct HOST_CTRL *priv, struct HOST_REQ *usbReq, uint32_t status) +{ + if (!priv || !usbReq) + return; + + list_del(&usbReq->list); + + if (usbReq->status == EINPROGRESS) + usbReq->status = status; + + if (priv->hostCallbacks.givebackRequest) + priv->hostCallbacks.givebackRequest(priv, usbReq, status); +} + +static void hostEpProgram(struct HOST_CTRL *priv, struct HostEp *hwEp, + struct HOST_REQ *usbReq, uintptr_t dmaBuff, uint32_t length) +{ + struct HOST_EP *usbHEp; + struct HOST_EP_PRIV *usbEpPriv; + uint32_t chMaxLen; + uint8_t regCon = 0; + uint8_t ep0cs; + uint16_t txerrien = 0; + uint16_t rxerrien = 0; + uint32_t result; + uint8_t txsoftimer, rxsoftimer; + u8 retval = 0; + + if (!priv || !hwEp || !usbReq) + return; + + usbHEp = hwEp->scheduledUsbHEp; + usbEpPriv = (struct HOST_EP_PRIV *)usbHEp->hcPriv; + + if (!hwEp->channel) { + if (usbEpPriv->type == USB_ENDPOINT_XFER_ISOC) + hwEp->channel = priv->dmaDrv->dma_channelAlloc(priv->dmaController, + !hwEp->isInEp, hwEp->hwEpNum, 1); + else + hwEp->channel = priv->dmaDrv->dma_channelAlloc(priv->dmaController, + !hwEp->isInEp, hwEp->hwEpNum, 0); + } + + chMaxLen = priv->dmaDrv->dma_getMaxLength(priv->dmaController, hwEp->channel); + + pr_debug("chMaxLen:0x%x buffLength:0x%x\n", chMaxLen, usbReq->buffLength); + if (usbReq->buffLength > chMaxLen) + length = chMaxLen; + + switch (usbEpPriv->type) { + case USB_ENDPOINT_XFER_CONTROL: + regCon = CON_TYPE_CONTROL; + break; + case USB_ENDPOINT_XFER_BULK: + regCon = CON_TYPE_BULK; + break; + case USB_ENDPOINT_XFER_INT: + regCon = CON_TYPE_INT; + break; + case USB_ENDPOINT_XFER_ISOC: + if (usbEpPriv->isocEpConfigured) + goto dma_program; + + usbEpPriv->isocEpConfigured = 1; + regCon = CON_TYPE_ISOC; + switch (usbHEp->desc.wMaxPacketSize >> 11) { + case 0: + regCon |= CON_TYPE_ISOC_1_ISOD; + priv->dmaDrv->dma_setMaxLength(priv->dmaController, + hwEp->channel, usbEpPriv->maxPacketSize); + break; + case 1: + regCon |= CON_TYPE_ISOC_2_ISOD; + priv->dmaDrv->dma_setMaxLength(priv->dmaController, + hwEp->channel, 2 * 1024); + break; + case 2: + priv->dmaDrv->dma_setMaxLength(priv->dmaController, + hwEp->channel, 3 * 1024); + regCon |= CON_TYPE_ISOC_3_ISOD; + break; + } + break; + } + + if (usbEpPriv->type != USB_ENDPOINT_XFER_ISOC) { + if (!hwEp->hwEpNum) { + if (phytium_read8(&priv->regs->ep0cs) & 0x4) { + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | + 0 | 0x4); + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | + FIFOCTRL_IO_TX | 0 | 0x4); + } + } + if (waitForBusyBit(priv, hwEp) > 0) { + usbReq->status = HOST_ESHUTDOWN; + givebackRequest(priv, usbReq, HOST_ESHUTDOWN); + pr_info("something error happen\n"); + return; + } + } + + if (!hwEp->isInEp) { + if (hwEp->hwEpNum) { + regCon |= hwEp->hwBuffers - 1; + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon, regCon); + if (usbEpPriv->type != USB_ENDPOINT_XFER_ISOC) { + retval = priv->hostCallbacks.getEpToggle(priv, + usbReq->usbDev, usbHEp->device_epNum, 0); + if (retval) { + phytium_write8(&priv->regs->endprst, hwEp->hwEpNum | + ENDPRST_IO_TX); + + phytium_write8(&priv->regs->endprst, hwEp->hwEpNum | + ENDPRST_TOGSETQ | ENDPRST_IO_TX | ENDPRST_FIFORST); + } else { + phytium_write8(&priv->regs->endprst, hwEp->hwEpNum | + ENDPRST_IO_TX); + + phytium_write8(&priv->regs->endprst, hwEp->hwEpNum | + ENDPRST_TOGRST | ENDPRST_IO_TX | ENDPRST_FIFORST); + } + } + + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon, regCon | CON_VAL); + phytium_write16(&priv->regs->txmaxpack[hwEp->hwEpNum - 1], + usbEpPriv->maxPacketSize); + + phytium_write8(&priv->regs->epExt[hwEp->hwEpNum - 1].txctrl, + usbHEp->device_epNum); + + phytium_write8(&priv->regs->fnaddr, usbEpPriv->faddress); + + // if (usbEpPriv->type == USB_ENDPOINT_XFER_INT) + txsoftimer = phytium_read8(&priv->regs->txsoftimer[hwEp->hwEpNum].ctrl); + txsoftimer = txsoftimer | BIT(1); + phytium_write8(&priv->regs->txsoftimer[hwEp->hwEpNum].ctrl, txsoftimer); + + phytium_write16(&priv->regs->txsoftimer[hwEp->hwEpNum].timer, + usbEpPriv->frame); + + phytium_write8(&priv->regs->txsoftimer[hwEp->hwEpNum].ctrl, 0x83); + } else { + phytium_write8(&priv->regs->fnaddr, usbEpPriv->faddress); + phytium_write8(&priv->regs->ep0maxpack, usbEpPriv->maxPacketSize); + phytium_write8(&priv->regs->ep0ctrl, usbEpPriv->epNum); + + if (priv->ep0State == HOST_EP0_STAGE_SETUP) { + ep0cs = phytium_read8(&priv->regs->ep0cs); + ep0cs |= EP0CS_HCSET; + phytium_write8(&priv->regs->ep0cs, ep0cs); + } + } + + phytium_write16(&priv->regs->txerrirq, 1 << hwEp->hwEpNum); + txerrien = phytium_read16(&priv->regs->txerrien); + txerrien |= 1 << hwEp->hwEpNum; + phytium_write16(&priv->regs->txerrien, txerrien); + } else { + if (hwEp->hwEpNum) { + regCon |= hwEp->hwBuffers - 1; + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcon, regCon); + + if (usbEpPriv->type != USB_ENDPOINT_XFER_ISOC) { + if (priv->hostCallbacks.getEpToggle) { + retval = priv->hostCallbacks.getEpToggle(priv, + usbReq->usbDev, usbHEp->device_epNum, 1); + if (retval) { + phytium_write8(&priv->regs->endprst, hwEp->hwEpNum); + phytium_write8(&priv->regs->endprst, hwEp->hwEpNum | + ENDPRST_TOGSETQ | ENDPRST_FIFORST); + } else { + phytium_write8(&priv->regs->endprst, hwEp->hwEpNum); + phytium_write8(&priv->regs->endprst, hwEp->hwEpNum | + ENDPRST_TOGRST | ENDPRST_FIFORST); + } + } + } + + phytium_write16(&priv->regs->rxmaxpack[hwEp->hwEpNum - 1], + usbEpPriv->maxPacketSize); + + phytium_write8(&priv->regs->epExt[hwEp->hwEpNum - 1].rxctrl, + usbHEp->device_epNum); + + phytium_write8(&priv->regs->fnaddr, usbEpPriv->faddress); + + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcs, 1); + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcs, 1); + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcs, 1); + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcs, 1); + + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcon, regCon | CON_VAL); + rxsoftimer = phytium_read8(&priv->regs->rxsoftimer[hwEp->hwEpNum].ctrl); + rxsoftimer = rxsoftimer | BIT(1); + phytium_write8(&priv->regs->rxsoftimer[hwEp->hwEpNum].ctrl, rxsoftimer); + + phytium_write16(&priv->regs->rxsoftimer[hwEp->hwEpNum].timer, + usbEpPriv->frame); + + phytium_write8(&priv->regs->rxsoftimer[hwEp->hwEpNum].ctrl, 0x83); + } else { + phytium_write8(&priv->regs->fnaddr, usbEpPriv->faddress); + phytium_write8(&priv->regs->ep0maxpack, usbEpPriv->maxPacketSize); + phytium_write8(&priv->regs->ep0ctrl, usbEpPriv->epNum); + + if (priv->ep0State == HOST_EP0_STAGE_IN + || priv->ep0State == HOST_EP0_STAGE_STATUSIN) + phytium_write8(&priv->regs->ep0cs, EP0CS_HCSETTOGGLE); + } + + phytium_write16(&priv->regs->rxerrirq, 1 << hwEp->hwEpNum); + rxerrien = phytium_read16(&priv->regs->rxerrien); + rxerrien |= 1 << hwEp->hwEpNum; + phytium_write16(&priv->regs->rxerrien, rxerrien); + } +dma_program: + result = priv->dmaDrv->dma_channelProgram(priv->dmaController, hwEp->channel, + usbEpPriv->maxPacketSize, dmaBuff, length, + (void *)usbReq->isoFramesDesc, usbReq->isoFramesNumber); + if (result) { + if (!hwEp->isInEp) { + txerrien &= ~(1 << hwEp->hwEpNum); + if (hwEp->hwEpNum) + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon, 0x08); + phytium_write16(&priv->regs->txerrien, txerrien); + } else { + rxerrien &= ~(1 << hwEp->hwEpNum); + if (hwEp->hwEpNum) + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcon, 0x08); + phytium_write16(&priv->regs->rxerrien, rxerrien); + } + + priv->dmaDrv->dma_channelRelease(priv->dmaController, hwEp->channel); + hwEp->channel = NULL; + } +} + +static void hostStartReq(struct HOST_CTRL *priv, struct HOST_REQ *req) +{ + uintptr_t dmaBuff; + uint32_t length; + struct HOST_EP *hostEp; + struct HOST_EP_PRIV *hostEpPriv; + struct HOST_EP_PRIV *hostNewEpPriv; + struct HOST_REQ *usbReq = NULL; + struct HostEp *hwEp = NULL; + int num; + + if (!priv || !req) + return; + + hostEp = req->hcPriv; + if (hostEp) { + hostEpPriv = (struct HOST_EP_PRIV *)hostEp->hcPriv; + if (hostEpPriv) { + hostEpPriv->genericHwEp->state = HOST_EP_BUSY; + switch (hostEpPriv->type) { + case USB_ENDPOINT_XFER_CONTROL: + usbReq = getNextReq(hostEp); + + priv->in[HOST_GENERIC_EP_CONTROL].scheduledUsbHEp = hostEp; + priv->ep0State = HOST_EP0_STAGE_SETUP; + hostEpPriv->currentHwEp = hostEpPriv->genericHwEp; + hostEpPriv->genericHwEp->scheduledUsbHEp = hostEp; + dmaBuff = usbReq->setupDma; + length = 8; + pr_debug("packet info: %02x %02x %04x %04x %04x\n", + usbReq->setup->bRequestType, + usbReq->setup->bRequest, + usbReq->setup->wValue, + usbReq->setup->wIndex, + usbReq->setup->wLength); + hostEpProgram(priv, hostEpPriv->genericHwEp, + usbReq, dmaBuff, length); + break; + case USB_ENDPOINT_XFER_BULK: + hwEp = hostEpPriv->genericHwEp; + usbReq = getNextReq(hostEp); + hostEpPriv->currentHwEp = hwEp; + hwEp->scheduledUsbHEp = hostEp; + dmaBuff = usbReq->buffDma + usbReq->actualLength; + length = usbReq->buffLength - usbReq->actualLength; + hostEpProgram(priv, hwEp, usbReq, dmaBuff, length); + break; + case USB_ENDPOINT_XFER_INT: + if (hostEpPriv->genericHwEp->scheduledUsbHEp) + return; + + num = req->epNum - 1; + if (hostEpPriv->isIn) + hostNewEpPriv = getIntTransfer(&priv->intInHEpQueue[num]); + else + hostNewEpPriv = getIntTransfer(&priv->intOutHEpQueue[num]); + + hostNewEpPriv->currentHwEp = hostEpPriv->genericHwEp; + hostEpPriv->genericHwEp->scheduledUsbHEp = hostNewEpPriv->usbHEp; + usbReq = getNextReq(hostEp); + dmaBuff = usbReq->buffDma + usbReq->actualLength; + length = usbReq->buffLength - usbReq->actualLength; + hostEpProgram(priv, hostEpPriv->genericHwEp, + usbReq, dmaBuff, length); + break; + case USB_ENDPOINT_XFER_ISOC: + hostEpPriv->currentHwEp = hostEpPriv->genericHwEp; + hostEpPriv->genericHwEp->scheduledUsbHEp = hostEp; + dmaBuff = req->buffDma + req->actualLength; + length = req->buffLength - req->actualLength; + hostEpProgram(priv, hostEpPriv->genericHwEp, req, dmaBuff, length); + break; + } + } + } +} + +static void abortTransfer(struct HOST_CTRL *priv, + struct HOST_REQ *usbReq, struct HostEp *hwEp) +{ + struct HOST_EP *usbEp; + struct HOST_EP_PRIV *usbHEpPriv; + uint32_t status; + + if (!priv || !usbReq || !hwEp || !hwEp->scheduledUsbHEp) + return; + + usbEp = hwEp->scheduledUsbHEp; + usbHEpPriv = (struct HOST_EP_PRIV *)usbEp->hcPriv; + if (!usbHEpPriv) + return; + + status = (usbReq->status == EINPROGRESS) ? 0 : usbReq->status; + givebackRequest(priv, usbReq, status); + + if (list_empty(&usbEp->reqList)) { + usbHEpPriv->epIsReady = 0; + usbHEpPriv->currentHwEp = NULL; + hwEp->scheduledUsbHEp = NULL; + + if (hwEp->channel) { + priv->dmaDrv->dma_channelRelease(priv->dmaController, hwEp->channel); + hwEp->channel = NULL; + } + + if (usb_endpoint_xfer_int(&usbEp->desc)) + list_del(&usbHEpPriv->node); + } +} + +static void scheduleNextTransfer(struct HOST_CTRL *priv, + struct HOST_REQ *usbReq, struct HostEp *hwEp) +{ + struct HOST_EP *usbEp; + struct HOST_EP_PRIV *usbHEpPriv; + uint8_t endprst; + uint32_t status; + struct HOST_REQ *usbNextReq = NULL; + + if (!priv || !usbReq || !hwEp) + return; + + usbEp = hwEp->scheduledUsbHEp; + usbHEpPriv = (struct HOST_EP_PRIV *)usbEp->hcPriv; + if (!usbHEpPriv) + return; + + status = (usbReq->status == EINPROGRESS) ? 0 : usbReq->status; + switch (usbHEpPriv->type) { + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + if (hwEp->isInEp) { + phytium_write8(&priv->regs->endprst, hwEp->hwEpNum); + endprst = (phytium_read8(&priv->regs->endprst) & ENDPRST_TOGSETQ) ? 1 : 0; + if (priv->hostCallbacks.setEpToggle) + priv->hostCallbacks.setEpToggle(priv, usbReq->usbDev, + usbEp->device_epNum, usbHEpPriv->isIn, endprst); + } else { + if (waitForBusyBit(priv, hwEp) > 0) { + usbReq->status = HOST_ESHUTDOWN; + givebackRequest(priv, usbReq, HOST_ESHUTDOWN); + return; + } + + phytium_write8(&priv->regs->endprst, hwEp->hwEpNum | ENDPRST_IO_TX); + endprst = (phytium_read8(&priv->regs->endprst) & ENDPRST_TOGSETQ) ? 1 : 0; + if (priv->hostCallbacks.setEpToggle) + priv->hostCallbacks.setEpToggle(priv, usbReq->usbDev, + usbEp->device_epNum, usbHEpPriv->isIn, endprst); + } + break; + } + + if (usbHEpPriv->transferFinished) + givebackRequest(priv, usbReq, status); + + if (list_empty(&usbEp->reqList)) { + if (usbHEpPriv->type == USB_ENDPOINT_XFER_CONTROL) + hwEp->state = HOST_EP_ALLOCATED; + else { + if (usbHEpPriv->genericHwEp == hwEp) + hwEp->state = HOST_EP_ALLOCATED; + else + hwEp->state = HOST_EP_FREE; + } + + usbHEpPriv->epIsReady = 0; + usbHEpPriv->currentHwEp = NULL; + hwEp->scheduledUsbHEp = NULL; + + if (hwEp->channel) { + priv->dmaDrv->dma_channelRelease(priv->dmaController, hwEp->channel); + hwEp->channel = NULL; + } + + if (usb_endpoint_xfer_int(&usbEp->desc)) + list_del(&usbHEpPriv->node); + usbHEpPriv = NULL; + } else { + if (usbHEpPriv->type == USB_ENDPOINT_XFER_INT) { + usbHEpPriv->currentHwEp = NULL; + hwEp->scheduledUsbHEp = NULL; + } + + if (usbHEpPriv->type == USB_ENDPOINT_XFER_ISOC) + return; + } + + if (usbHEpPriv) { + usbNextReq = getNextReq(usbHEpPriv->usbHEp); + hostStartReq(priv, usbNextReq); + } +} + +static int32_t hostEp0Irq(struct HOST_CTRL *priv, uint8_t isIn) +{ + struct HostEp *hwEp; + struct HOST_EP *hostEp; + struct HOST_REQ *usbReq = NULL; + struct HOST_EP_PRIV *usbHEpPriv; + uint32_t status, length; + uint8_t usbError; + uint8_t nextStage = 0; + int ret = 0; + + if (!priv) + return 0; + + hwEp = isIn ? &priv->in[HOST_GENERIC_EP_CONTROL] : &priv->out[HOST_GENERIC_EP_CONTROL]; + hostEp = hwEp->scheduledUsbHEp; + usbHEpPriv = (struct HOST_EP_PRIV *)hostEp->hcPriv; + + usbReq = getNextReq(hostEp); + if (!usbReq) + return 0; + + usbError = isIn ? phytium_read8(&priv->regs->rx0err) : phytium_read8(&priv->regs->tx0err); + usbError = (usbError & ERR_TYPE) >> 2; + status = decodeErrorCode(usbError); + if (status) { + usbReq->status = status; + + if (status == HOST_ESTALL) { + ret = 1; + priv->dmaDrv->dma_controllerReset(priv->dmaController); + } + + phytium_write16(&priv->regs->rxerrirq, 1 << hwEp->hwEpNum); + phytium_write16(&priv->regs->txerrirq, 1 << hwEp->hwEpNum); + + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | 0 | 0x4); + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | + FIFOCTRL_IO_TX | 0 | 0x4); + } + + length = priv->dmaDrv->dma_getActualLength(priv->dmaController, hwEp->channel); + priv->dmaDrv->dma_channelRelease(priv->dmaController, hwEp->channel); + hwEp->channel = NULL; + + if (usbReq->status == EINPROGRESS && priv->ep0State < HOST_EP0_STAGE_STATUSIN) { + nextStage = 0; + switch (priv->ep0State) { + case HOST_EP0_STAGE_IN: + pr_debug("Ep0 Data IN\n"); + usbHEpPriv->currentHwEp = &priv->out[HOST_GENERIC_EP_CONTROL]; + usbReq->actualLength = length; + priv->ep0State = HOST_EP0_STAGE_STATUSOUT; + break; + case HOST_EP0_STAGE_OUT: + pr_debug("Ep0 Data OUT\n"); + usbHEpPriv->currentHwEp = &priv->in[HOST_GENERIC_EP_CONTROL]; + usbReq->actualLength = length; + priv->ep0State = HOST_EP0_STAGE_STATUSIN; + break; + case HOST_EP0_STAGE_SETUP: + pr_debug("Ep0 Stage Setup\n"); + if (!usbReq->setup->wLength) { + pr_debug("EP0_STAGE_STATUSIN\n"); + priv->ep0State = HOST_EP0_STAGE_STATUSIN; + usbHEpPriv->currentHwEp = &priv->in[HOST_GENERIC_EP_CONTROL]; + break; + } else if (usbReq->setup->bRequestType & USB_DIR_IN) { + pr_debug("EP0_STAGE_STAGE_IN\n"); + priv->ep0State = HOST_EP0_STAGE_IN; + usbHEpPriv->currentHwEp = &priv->in[HOST_GENERIC_EP_CONTROL]; + nextStage = 1; + break; + } + priv->ep0State = HOST_EP0_STAGE_OUT; + nextStage = 1; + break; + case HOST_EP0_STAGE_STATUSIN: + case HOST_EP0_STAGE_STATUSOUT: + case HOST_EP0_STAGE_ACK: + case HOST_EP0_STAGE_IDLE: + default: + pr_debug("EP0 STAGE is %d\n", priv->ep0State); + break; + } + + if (nextStage) { + length = usbReq->buffLength; + hostEpProgram(priv, usbHEpPriv->currentHwEp, usbReq, + usbReq->buffDma, length); + } else + hostEpProgram(priv, usbHEpPriv->currentHwEp, usbReq, usbReq->setupDma, 0); + } else + priv->ep0State = HOST_EP0_STAGE_IDLE; + + if (priv->ep0State == HOST_EP0_STAGE_IDLE || usbReq->status != EINPROGRESS) { + usbHEpPriv->transferFinished = 1; + scheduleNextTransfer(priv, usbReq, hwEp); + } + + return ret; +} + +static void updateTimeIntTransfer(struct list_head *head, struct HOST_EP_PRIV *lastFinished) +{ + struct list_head *listEntry = NULL; + struct HOST_EP_PRIV *usbHEpPriv = NULL; + uint16_t time = lastFinished->frame; + + list_for_each(listEntry, head) { + usbHEpPriv = getUsbHEpPrivEntry(listEntry); + if (usbHEpPriv == lastFinished) { + lastFinished->frame = lastFinished->interval; + continue; + } + + if (usbHEpPriv->frame < time) + usbHEpPriv->frame = 0; + else + lastFinished->interval = usbHEpPriv->frame; + } +} + +static int32_t hostEpXIrq(struct HOST_CTRL *priv, uint8_t hwEpNum, uint8_t isIn, bool resubmit) +{ + struct HostEp *hwEp; + struct HOST_EP *hostEp; + struct HOST_EP_PRIV *usbHEpPriv; + struct HOST_REQ *usbReq; + + uint8_t usbError, rxcon, txcon; + uint32_t status, length, chMaxLen; + + if (!priv) + return -EINVAL; + + hwEp = isIn ? &priv->in[hwEpNum] : &priv->out[hwEpNum]; + hostEp = hwEp->scheduledUsbHEp; + if (!hostEp) + return -EINVAL; + + usbHEpPriv = (struct HOST_EP_PRIV *)hostEp->hcPriv; + + usbReq = getNextReq(hostEp); + if (!usbReq) + return 0; + + if (isIn) + usbError = phytium_read8(&priv->regs->epExt[hwEpNum - 1].rxerr); + else + usbError = phytium_read8(&priv->regs->epExt[hwEpNum - 1].txerr); + + usbError = (usbError & ERR_TYPE) >> 2; + status = decodeErrorCode(usbError); + if (status) { + pr_debug("%s %d Aborting\n", __func__, __LINE__); + if (isIn) + phytium_write16(&priv->regs->rxerrirq, 1 << hwEpNum); + else + phytium_write16(&priv->regs->txerrirq, 1 << hwEpNum); + priv->dmaDrv->dma_channelAbort(priv->dmaController, hwEp->channel); + } + + length = priv->dmaDrv->dma_getActualLength(priv->dmaController, hwEp->channel); + chMaxLen = priv->dmaDrv->dma_getMaxLength(priv->dmaController, hwEp->channel); + + if (status != 0) + usbReq->status = status; + + usbReq->actualLength += length; + + if (!resubmit) + usbHEpPriv->transferFinished = 1; + + if (length == chMaxLen) { + if ((usbReq->buffLength - usbReq->actualLength) == 0) + usbHEpPriv->transferFinished = 1; + else + usbHEpPriv->transferFinished = 0; + } + + if (usbHEpPriv->type != USB_ENDPOINT_XFER_ISOC) { + if (!hwEp->isInEp) { + txcon = phytium_read8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon); + txcon &= ~CON_VAL; + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon, txcon); + } else { + rxcon = phytium_read8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcon); + rxcon &= ~CON_VAL; + phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcon, rxcon); + } + priv->dmaDrv->dma_channelRelease(priv->dmaController, hwEp->channel); + hwEp->channel = NULL; + } + + if (usbHEpPriv->type == USB_ENDPOINT_XFER_INT) { + if (usbHEpPriv->isIn) + updateTimeIntTransfer(&priv->intInHEpQueue[hwEp->hwEpNum - 1], usbHEpPriv); + else + updateTimeIntTransfer(&priv->intOutHEpQueue[hwEp->hwEpNum - 1], usbHEpPriv); + } + + scheduleNextTransfer(priv, usbReq, hwEp); + + return 0; +} + + +static void host_CallbackTransfer(void *priv, uint8_t epNum, uint8_t isDirTx, bool resubmit) +{ + struct HOST_CTRL *host_priv = (struct HOST_CTRL *)priv; + int i; + int ret = 0; + + if (!epNum) { + ret = hostEp0Irq(host_priv, !isDirTx); + if (resubmit | ret) { + for (i = 1; i < HOST_EP_NUM; i++) { + hostEpXIrq(host_priv, i, !isDirTx, true); + hostEpXIrq(host_priv, i, isDirTx, true); + } + } + } else + hostEpXIrq(host_priv, epNum, !isDirTx, false); +} + +static int hc_reset(struct usb_hcd *hcd) +{ + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + + return 0; +} + +static int hc_start(struct usb_hcd *hcd) +{ + hcd->state = HC_STATE_RUNNING; + return 0; +} + +static void hc_stop(struct usb_hcd *hcd) +{ + struct phytium_cusb *config = *(struct phytium_cusb **)hcd->hcd_priv; + + if (config) + config->host_obj->host_stop(config->host_priv); + + if (config->host_cfg.trbAddr) { + dma_free_coherent(config->dev, config->host_sysreq.trbMemSize, + config->host_cfg.trbAddr, config->host_cfg.trbDmaAddr); + config->host_cfg.trbAddr = NULL; + } + + if (config->host_priv) { + devm_kfree(config->dev, config->host_priv); + config->host_priv = NULL; + } + + hcd->state = HC_STATE_HALT; +} + +static void hc_shutdown(struct usb_hcd *hcd) +{ +} + +static void host_endpoint_update(struct phytium_cusb *config, + struct HOST_USB_DEVICE *udev, struct usb_host_endpoint *ep) +{ + int epnum; + struct HOST_EP *hostEp; + + if (!config || !udev || !ep) + return; + + epnum = get_epnum_from_pool(config->host_priv, usb_endpoint_num(&ep->desc), + usb_endpoint_dir_in(&ep->desc)); + + if (usb_endpoint_dir_out(&ep->desc)) { + if (udev->out_ep[epnum] == NULL) { + hostEp = kzalloc(sizeof(struct HOST_EP) + + config->host_obj->host_epGetPrivateDataSize(config->host_priv), + GFP_ATOMIC); + udev->out_ep[epnum] = hostEp; + } else + hostEp = udev->out_ep[epnum]; + } else { + if (udev->in_ep[epnum] == NULL) { + hostEp = kzalloc(sizeof(struct HOST_EP) + + config->host_obj->host_epGetPrivateDataSize(config->host_priv), + GFP_ATOMIC); + udev->in_ep[epnum] = hostEp; + } else + hostEp = udev->in_ep[epnum]; + } + + hostEp->desc = *(struct usb_endpoint_descriptor *)(&ep->desc); + hostEp->userExt = (void *)ep; + INIT_LIST_HEAD(&hostEp->reqList); + hostEp->hcPriv = &((uint8_t *)hostEp)[sizeof(struct HOST_EP)]; +} + +static int hc_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) +{ + unsigned long flags; + u32 isoFrameSize; + int retval; + int index; + struct HOST_REQ *req; + struct HOST_CTRL *priv; + struct HOST_USB_DEVICE *usbDev; + struct usb_host_endpoint *host_ep; + struct usb_endpoint_descriptor *host_ep_desc; + struct phytium_cusb *config = *(struct phytium_cusb **)hcd->hcd_priv; + + if (!config) + return -ENODEV; + + priv = config->host_priv; + if (!priv) + return -ENODEV; + + usbDev = priv->host_devices_table[urb->dev->slot_id]; + if (!usbDev) + return -ENODEV; + + host_ep = urb->ep; + host_ep_desc = &host_ep->desc; + + spin_lock_irqsave(&config->lock, flags); + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval < 0) { + spin_unlock_irqrestore(&config->lock, flags); + return retval; + } + spin_unlock_irqrestore(&config->lock, flags); + isoFrameSize = urb->number_of_packets * sizeof(struct HOST_ISOFRAMESDESC); + req = kzalloc((sizeof(struct HOST_REQ) + + isoFrameSize), mem_flags); + if (!req) { + spin_lock_irqsave(&config->lock, flags); + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&config->lock, flags); + return -ENOMEM; + } + + req->isoFramesDesc = NULL; + req->isoFramesNumber = urb->number_of_packets; + req->epNum = get_epnum_from_pool(config->host_priv, usb_endpoint_num(host_ep_desc), + usb_endpoint_dir_in(host_ep_desc)); + + if (usb_endpoint_dir_in(host_ep_desc)) { + if (!usbDev->in_ep[req->epNum]) + host_endpoint_update(config, usbDev, host_ep); + } else { + if (!usbDev->out_ep[req->epNum]) + host_endpoint_update(config, usbDev, host_ep); + } + + if (usb_endpoint_xfer_isoc(host_ep_desc)) { + req->isoFramesDesc = (struct HOST_ISOFRAMESDESC *)(&req[1]); + for (index = 0; index < urb->number_of_packets; index++) { + req->isoFramesDesc[index].length = urb->iso_frame_desc[index].length; + req->isoFramesDesc[index].offset = urb->iso_frame_desc[index].offset; + } + } + + spin_lock_irqsave(&config->lock, flags); + INIT_LIST_HEAD(&req->list); + req->userExt = (void *)urb; + req->actualLength = urb->actual_length; + req->bufAddress = urb->transfer_buffer; + req->buffDma = urb->transfer_dma; + req->buffLength = urb->transfer_buffer_length; + req->epIsIn = usb_endpoint_dir_in(host_ep_desc); + req->eptype = usb_endpoint_type(host_ep_desc); + req->faddress = usb_pipedevice(urb->pipe); + req->interval = urb->interval; + req->reqUnlinked = 0; + req->setup = (struct usb_ctrlrequest *)urb->setup_packet; + req->setupDma = urb->setup_dma; + req->status = EINPROGRESS; + req->usbDev = &usbDev->udev; + req->usbEp = req->epIsIn ? usbDev->in_ep[req->epNum] : usbDev->out_ep[req->epNum]; + req->usbEp->device_epNum = usb_endpoint_num(host_ep_desc); + if (!req->epNum) + usbDev->ep0_hep.desc.wMaxPacketSize = urb->dev->ep0.desc.wMaxPacketSize; + + req->hcPriv = (void *)req->usbEp; + urb->hcpriv = req; + host_ep->hcpriv = (void *)usbDev; + retval = config->host_obj->host_reqQueue(config->host_priv, req); + if (retval) { + usb_hcd_unlink_urb_from_ep(hcd, urb); + urb->hcpriv = NULL; + spin_unlock_irqrestore(&config->lock, flags); + kfree(req); + return -retval; + } + spin_unlock_irqrestore(&config->lock, flags); + + return 0; +} + +static int hc_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + unsigned long flags; + struct HOST_CTRL *priv; + int ret = 0; + struct phytium_cusb *config = *(struct phytium_cusb **)hcd->hcd_priv; + + if (!config) + return -ENODEV; + + priv = config->host_priv; + if (!priv->host_devices_table[urb->dev->slot_id]) + return -ENODEV; + + spin_lock_irqsave(&config->lock, flags); + if (usb_hcd_check_unlink_urb(hcd, urb, status)) + goto done; + + if (!urb->hcpriv) + goto err_giveback; + + ret = config->host_obj->host_reqDequeue(priv, urb->hcpriv, status); + + release_epnum_from_pool(config->host_priv, usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe)); + + kfree(urb->hcpriv); + urb->hcpriv = NULL; +done: + spin_unlock_irqrestore(&config->lock, flags); + return ret; + +err_giveback: + kfree(urb->hcpriv); + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&config->lock, flags); + usb_hcd_giveback_urb(hcd, urb, -ESHUTDOWN); + return ret; +} + +static void hc_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ld_ep) +{ + struct HOST_USB_DEVICE *usbDev; + int ep_num; + struct phytium_cusb *config; + + config = *(struct phytium_cusb **)hcd->hcd_priv; + if (!config) + return; + + ep_num = get_epnum_from_pool(config->host_priv, usb_endpoint_num(&ld_ep->desc), + usb_endpoint_dir_in(&ld_ep->desc)); + + usbDev = (struct HOST_USB_DEVICE *)ld_ep->hcpriv; + if (!usbDev) + return; + + if (ld_ep->desc.bEndpointAddress) { + if (usb_endpoint_dir_in(&ld_ep->desc)) { + if (usbDev->in_ep[ep_num]) { + usbDev->in_ep[ep_num]->userExt = NULL; + INIT_LIST_HEAD(&usbDev->in_ep[ep_num]->reqList); + kfree(usbDev->in_ep[ep_num]); + usbDev->in_ep[ep_num] = NULL; + } + } else { + if (usbDev->out_ep[ep_num]) { + usbDev->out_ep[ep_num]->userExt = NULL; + INIT_LIST_HEAD(&usbDev->out_ep[ep_num]->reqList); + kfree(usbDev->out_ep[ep_num]); + usbDev->out_ep[ep_num] = NULL; + } + } + } +} + +static int hc_get_frame(struct usb_hcd *hcd) +{ + return 0; +} + +static int hc_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct HOST_USB_DEVICE *usbDev; + struct phytium_cusb *config; + struct HOST_CTRL *priv; + unsigned long flags = 0; + int index; + int slot = -EINVAL; + + if (!hcd || !udev) + return -EINVAL; + + config = *(struct phytium_cusb **)hcd->hcd_priv; + if (!config) + return 0; + + spin_lock_irqsave(&config->lock, flags); + priv = config->host_priv; + for (index = 0; index < MAX_SUPPORTED_DEVICES; index++) { + if (priv->host_devices_table[index] == NULL) { + slot = index; + break; + } + } + spin_unlock_irqrestore(&config->lock, flags); + + if (slot < 0) + return -ENOMEM; + + usbDev = kzalloc((sizeof(struct HOST_USB_DEVICE) + + config->host_obj->host_epGetPrivateDataSize(priv)), GFP_KERNEL); + if (!usbDev) + return -ENOMEM; + + usbDev->ep0_hep.hcPriv = &((uint8_t *)usbDev)[sizeof(struct HOST_USB_DEVICE)]; + usbDev->udev.userExt = (void *)usbDev; + usbDev->ld_udev = udev; + usbDev->ld_udev->slot_id = slot; + usbDev->ep0_hep.desc.bLength = 7; + usbDev->ep0_hep.desc.bDescriptorType = USB_DT_ENDPOINT; + usbDev->in_ep[0] = &usbDev->ep0_hep; + usbDev->out_ep[0] = &usbDev->ep0_hep; + INIT_LIST_HEAD(&usbDev->ep0_hep.reqList); + + priv->host_devices_table[slot] = usbDev; + + return 1; +} + +static void hc_free_dev(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct HOST_CTRL *priv; + struct HOST_USB_DEVICE *usbDev; + struct phytium_cusb *config; + int i; + + if (!hcd || !udev) + return; + + config = *(struct phytium_cusb **)(hcd->hcd_priv); + if (!config) + return; + + priv = (struct HOST_CTRL *)config->host_priv; + usbDev = priv->host_devices_table[udev->slot_id]; + if (!usbDev) + return; + + usbDev->in_ep[0] = NULL; + usbDev->out_ep[0] = NULL; + for (i = 1; i < HOST_EP_NUM; i++) { + if (usbDev->in_ep[i]) + hc_endpoint_disable(hcd, + (struct usb_host_endpoint *)usbDev->in_ep[i]->userExt); + if (usbDev->out_ep[i]) + hc_endpoint_disable(hcd, + (struct usb_host_endpoint *)usbDev->out_ep[i]->userExt); + } + + priv->host_devices_table[udev->slot_id] = NULL; + usbDev->ld_udev->slot_id = 0; + usbDev->udev.userExt = (void *)NULL; + kfree(usbDev); +} + +static int hc_reset_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct HOST_CTRL *priv; + struct HOST_USB_DEVICE *usb_device; + struct phytium_cusb *config; + + if (!hcd || !udev) + return -EINVAL; + + config = *(struct phytium_cusb **)hcd->hcd_priv; + priv = (struct HOST_CTRL *)config->host_priv; + usb_device = priv->host_devices_table[udev->slot_id]; + if (!usb_device) + return -ENODEV; + + usb_device->udev.speed = usb_device->ld_udev->speed; + usb_device->udev.devnum = usb_device->ld_udev->devnum; + + if (USB_SPEED_HIGH == usb_device->udev.speed || USB_SPEED_FULL == usb_device->udev.speed) + usb_device->ep0_hep.desc.wMaxPacketSize = 64; + else + usb_device->ep0_hep.desc.wMaxPacketSize = 8; + + pr_debug("speed:%d ep0 wMaxPacketSize:%d\n", usb_device->udev.speed, + usb_device->ep0_hep.desc.wMaxPacketSize); + + return 0; +} + +static int hc_update_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct HOST_CTRL *priv; + struct HOST_USB_DEVICE *usb_device; + struct phytium_cusb *config; + + if (!hcd || !udev) + return -EINVAL; + + config = *(struct phytium_cusb **)(hcd->hcd_priv); + priv = (struct HOST_CTRL *)config->host_priv; + + usb_device = priv->host_devices_table[udev->slot_id]; + if (!usb_device) + return -ENODEV; + + usb_device->udev.devnum = udev->devnum; + + return 0; +} + +static int hc_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + return 0; +} + +static int hc_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + return 0; +} + +static int hc_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct HOST_CTRL *priv; + struct phytium_cusb *config = *(struct phytium_cusb **)hcd->hcd_priv; + + if (!config) + return 0; + + priv = (struct HOST_CTRL *)config->host_priv; + if (!priv) + return 0; + + if (priv->portStatus & 0xffff0000) { + *buf = 0x02; + return 1; + } + + return 0; +} + +#ifdef CONFIG_PM +static int hc_bus_suspend(struct usb_hcd *hcd) +{ + unsigned long flags; + struct phytium_cusb *config = *(struct phytium_cusb **)(hcd->hcd_priv); + + if (!config) + return 0; + + spin_lock_irqsave(&config->lock, flags); + hcd->state = HC_STATE_SUSPENDED; + spin_unlock_irqrestore(&config->lock, flags); + + return 0; +} + +static int hc_bus_resume(struct usb_hcd *hcd) +{ + unsigned long flags; + struct phytium_cusb *config = *(struct phytium_cusb **)(hcd->hcd_priv); + + if (!config) + return 0; + + spin_lock_irqsave(&config->lock, flags); + hcd->state = HC_STATE_RESUMING; + spin_unlock_irqrestore(&config->lock, flags); + return 0; +} +#endif + +static void host_giveback_request(struct HOST_CTRL *priv, + struct HOST_REQ *req, uint32_t status) +{ + struct urb *urb_req; + int urb_status = 0; + int i = 0; + struct phytium_cusb *config; + + if (!priv || !req) + return; + + urb_req = req->userExt; + urb_req->actual_length = req->actualLength; + + switch (status) { + case HOST_ESTALL: + urb_status = -EPIPE; + break; + case HOST_EUNHANDLED: + urb_status = -EPROTO; + break; + case HOST_ESHUTDOWN: + urb_status = -ESHUTDOWN; + break; + default: + urb_status = status; + } + pr_debug("complete %p %pS (%d) dev%d ep%d%s %d/%d\n", + urb_req, urb_req->complete, urb_status, + usb_pipedevice(urb_req->pipe), + usb_pipeendpoint(urb_req->pipe), + usb_pipein(urb_req->pipe) ? "in" : "out", + urb_req->actual_length, + urb_req->transfer_buffer_length); + + if (usb_endpoint_xfer_isoc(&urb_req->ep->desc)) { + for (i = 0; i < urb_req->number_of_packets; i++) { + urb_req->iso_frame_desc[i].status = 0; + urb_req->iso_frame_desc[i].actual_length = req->isoFramesDesc[i].length; + } + } + + config = dev_get_drvdata(priv->dev); + usb_hcd_unlink_urb_from_ep(config->hcd, urb_req); + spin_unlock(&config->lock); + usb_hcd_giveback_urb(config->hcd, urb_req, urb_status); + kfree(req); + spin_lock(&config->lock); +} + +static void host_rh_port_status_change(struct HOST_CTRL *priv) +{ + uint32_t statusHub; + char *status; + uint32_t retval; + struct usb_ctrlrequest setup; + struct phytium_cusb *config; + + if (!priv) + return; + + config = dev_get_drvdata(priv->dev); + if (!config) + return; + + status = (char *)&statusHub; + hc_hub_status_data(config->hcd, status); + + if (status) { + setup.bRequestType = USB_TYPE_CLASS | USB_RECIP_OTHER | USB_DIR_IN;//to host + setup.bRequest = USB_REQ_GET_STATUS; + setup.wValue = 0; + setup.wIndex = 1; + setup.wLength = 4; + retval = config->host_obj->host_vhubControl(priv, &setup, (uint8_t *)&statusHub); + if (retval) + return; + + if (status[1] & USB_PORT_STAT_C_CONNECTION) { + if (status[0] & USB_PORT_STAT_CONNECTION) { + if (config->hcd->status_urb) + usb_hcd_poll_rh_status(config->hcd); + else + usb_hcd_resume_root_hub(config->hcd); + } else { + usb_hcd_resume_root_hub(config->hcd); + usb_hcd_poll_rh_status(config->hcd); + } + } + } else + usb_hcd_resume_root_hub(config->hcd); +} + +static void host_set_ep_toggle(struct HOST_CTRL *priv, + struct HOST_DEVICE *udev, u8 ep_num, u8 is_in, u8 toggle) +{ + struct HOST_USB_DEVICE *device; + + if (!priv || !udev) + return; + + device = (struct HOST_USB_DEVICE *)udev->userExt; + + usb_settoggle(device->ld_udev, ep_num, !is_in, toggle); +} + +static u8 host_get_ep_toggle(struct HOST_CTRL *priv, + struct HOST_DEVICE *udev, u8 ep_num, u8 is_in) +{ + struct HOST_USB_DEVICE *device; + + if (!priv || !udev) + return 0; + + device = (struct HOST_USB_DEVICE *)udev->userExt; + + return usb_gettoggle(device->ld_udev, ep_num, !is_in); +} + +static uint32_t initEndpoints(struct HOST_CTRL *priv) +{ + int epNum; + + if (!priv) + return 0; + + priv->hwEpInCount = 0; + priv->hwEpOutCount = 0; + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | 0); + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | FIFOCTRL_IO_TX | 0); + memset(priv->ep_remap_pool, 0, sizeof(priv->ep_remap_pool)); + + for (epNum = 0; epNum < HOST_EP_NUM; epNum++) { + priv->in[epNum].isInEp = 1; + priv->in[epNum].hwEpNum = epNum; + if (priv->hostCfg.epIN[epNum].bufferingValue) { + priv->in[epNum].hwMaxPacketSize = priv->hostCfg.epIN[epNum].maxPacketSize; + priv->in[epNum].hwBuffers = priv->hostCfg.epIN[epNum].bufferingValue; + priv->in[epNum].state = HOST_EP_FREE; + priv->in[epNum].channel = NULL; + priv->hwEpInCount++; + + if (epNum) { + phytium_write16(&priv->regs->rxstaddr[epNum - 1].addr, + priv->hostCfg.epIN[epNum].startBuf); + phytium_write8(&priv->regs->ep[epNum - 1].rxcon, 0x08); + phytium_write8(&priv->regs->fifoctrl, FIFOCTRL_FIFOAUTO | epNum); + phytium_write8(&priv->regs->irqmode[epNum - 1].inirqmode, 0x80); + } + } else + priv->in[epNum].state = HOST_EP_NOT_IMPLEMENTED; + + priv->out[epNum].isInEp = 0; + priv->out[epNum].hwEpNum = epNum; + if (priv->hostCfg.epOUT[epNum].bufferingValue) { + priv->out[epNum].hwMaxPacketSize = priv->hostCfg.epOUT[epNum].maxPacketSize; + priv->out[epNum].hwBuffers = priv->hostCfg.epOUT[epNum].bufferingValue; + priv->out[epNum].state = HOST_EP_FREE; + priv->out[epNum].channel = NULL; + priv->hwEpInCount++; + + if (epNum) { + phytium_write16(&priv->regs->txstaddr[epNum - 1].addr, + priv->hostCfg.epOUT[epNum].startBuf); + phytium_write8(&priv->regs->ep[epNum - 1].txcon, 0x08); + phytium_write8(&priv->regs->fifoctrl, + FIFOCTRL_FIFOAUTO | FIFOCTRL_IO_TX | epNum); + phytium_write8(&priv->regs->irqmode[epNum - 1].outirqmode, 0x80); + } + } else + priv->out[epNum].state = HOST_EP_NOT_IMPLEMENTED; + } + + return 0; +} + +int32_t hostInit(struct HOST_CTRL *priv, struct HOST_CFG *config, + struct HOST_CALLBACKS *callbacks, struct device *pdev, bool isVhubHost) +{ + int index; + + if (!config || !priv || !callbacks || !pdev) + return -EINVAL; + + priv->dev = pdev; + priv->hostCallbacks = *callbacks; + priv->hostCfg = *config; + priv->regs = (struct HW_REGS *)config->regBase; + priv->hostDrv = HOST_GetInstance(); + + priv->dmaDrv = DMA_GetInstance(); + priv->dmaCfg.dmaModeRx = 0xFFFF; + priv->dmaCfg.dmaModeTx = 0xFFFF; + priv->dmaCfg.regBase = config->regBase + 0x400; + priv->dmaCfg.trbAddr = config->trbAddr; + priv->dmaCfg.trbDmaAddr = config->trbDmaAddr; + priv->dmaCallback.complete = host_CallbackTransfer; + + priv->dmaController = (void *)(priv + 1); + priv->dmaDrv->dma_init(priv->dmaController, &priv->dmaCfg, &priv->dmaCallback); + priv->dmaDrv->dma_setParentPriv(priv->dmaController, priv); + + INIT_LIST_HEAD(&priv->ctrlHEpQueue); + + for (index = 0; index < MAX_INSTANCE_EP_NUM; index++) { + INIT_LIST_HEAD(&priv->isoInHEpQueue[index]); + INIT_LIST_HEAD(&priv->isoOutHEpQueue[index]); + INIT_LIST_HEAD(&priv->intInHEpQueue[index]); + INIT_LIST_HEAD(&priv->intOutHEpQueue[index]); + INIT_LIST_HEAD(&priv->bulkInHEpQueue[index]); + INIT_LIST_HEAD(&priv->bulkOutHEpQueue[index]); + } + + phytium_write8(&priv->regs->cpuctrl, BIT(1)); + phytium_write8(&priv->regs->otgctrl, OTGCTRL_ABUSDROP); + phytium_write8(&priv->regs->ep0maxpack, 0x40); + + //disable interrupts + phytium_write8(&priv->regs->otgien, 0x0); + phytium_write8(&priv->regs->usbien, 0x0); + phytium_write16(&priv->regs->txerrien, 0x0); + phytium_write16(&priv->regs->rxerrien, 0x0); + + //clear all interrupt except otg idle irq + phytium_write8(&priv->regs->otgirq, 0xFE); + phytium_write8(&priv->regs->usbirq, 0xFF); + phytium_write16(&priv->regs->txerrirq, 0xFF); + phytium_write16(&priv->regs->rxerrirq, 0xFF); + + phytium_write8(&priv->regs->tawaitbcon, 0x80); + + initEndpoints(priv); + priv->otgState = HOST_OTG_STATE_B_IDLE; + + //reset all endpoint + phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX); + phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST | ENDPRST_IO_TX); + phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST); + + if (isVhubHost) + priv->vhub_regs = (struct VHUB_REGS *)(config->phy_regBase); + else + priv->custom_regs = (struct CUSTOM_REGS *)(config->phy_regBase); + + return 0; +} + +void hostDestroy(struct HOST_CTRL *priv) +{ +} + +void hostStart(struct HOST_CTRL *priv) +{ + uint8_t otgstate, usbien; + + if (!priv) + return; + + priv->dmaDrv->dma_start(priv->dmaController); + usbien = phytium_read8(&priv->regs->usbien); + usbien = usbien | USBIR_URES | USBIR_SUSP; + phytium_write8(&priv->regs->usbien, usbien); +retry: + otgstate = phytium_read8(&priv->regs->otgstate); + switch (otgstate) { + case HOST_OTG_STATE_A_IDLE: + priv->ep0State = HOST_EP0_STAGE_IDLE; + priv->otgState = HOST_OTG_STATE_A_IDLE; + phytium_write8(&priv->regs->otgirq, OTGIRQ_IDLEIRQ); + host_SetVbus(priv, 1); + break; + case HOST_OTG_STATE_B_IDLE: + host_SetVbus(priv, 1); + break; + case HOST_OTG_STATE_A_WAIT_VFALL: + goto retry; + } + + phytium_write8(&priv->regs->otgien, OTGIRQ_CONIRQ | + OTGIRQ_VBUSERRIRQ | OTGIRQ_SRPDETIRQ); +} + +void hostStop(struct HOST_CTRL *priv) +{ + if (!priv) + return; + + phytium_write8(&priv->regs->otgien, 0x0); + phytium_write8(&priv->regs->usbien, 0x0); + phytium_write16(&priv->regs->txerrien, 0x0); + phytium_write16(&priv->regs->rxerrien, 0x0); + + phytium_write8(&priv->regs->otgirq, 0xFE); + phytium_write8(&priv->regs->usbirq, 0xFF); + phytium_write16(&priv->regs->txerrirq, 0xFF); + phytium_write16(&priv->regs->rxerrirq, 0xFF); + + host_SetVbus(priv, 0); + priv->dmaDrv->dma_stop(priv->dmaController); +} + +static void handleReset(struct HOST_CTRL *priv) +{ + if (!priv) + return; + + if (priv->otgState == HOST_OTG_STATE_A_WAIT_BCON + || priv->otgState == HOST_OTG_STATE_B_WAIT_ACON) { + switch (phytium_read8(&priv->regs->speedctrl)) { + case SPEEDCTRL_HS: + priv->portStatus |= USB_PORT_STAT_HIGH_SPEED; + break; + case SPEEDCTRL_FS: + priv->portStatus &= ~(USB_PORT_STAT_HIGH_SPEED | USB_PORT_STAT_LOW_SPEED); + break; + case SPEEDCTRL_LS: + priv->portStatus |= USB_PORT_STAT_LOW_SPEED; + break; + } + + priv->dmaDrv->dma_setHostMode(priv->dmaController); + + if (priv->hostCallbacks.portStatusChange) + priv->hostCallbacks.portStatusChange(priv); + + switch (phytium_read8(&priv->regs->otgstate)) { + case HOST_OTG_STATE_B_HOST: + priv->otgState = HOST_OTG_STATE_B_HOST; + break; + case HOST_OTG_STATE_A_HOST: + break; + default: + priv->otgState = HOST_OTG_STATE_A_HOST; + break; + } + } +} + +void hostIsr(struct HOST_CTRL *priv) +{ + uint8_t usbirq, usbien; + + if (!priv) + return; + + usbirq = phytium_read8(&priv->regs->usbirq); + usbien = phytium_read8(&priv->regs->usbien); + pr_debug("raw usbirq:0x%x usbien:0x%x\n", usbirq, usbien); + usbirq = usbirq & usbien; + + hostErrorIrq(priv); + hostOtgIrq(priv); + + if (!usbirq) + goto DMA_IRQ; + + if (usbirq & USBIR_URES) { + phytium_write8(&priv->regs->usbirq, USBIR_URES); + priv->port_resetting = 0; + handleReset(priv); + } + + if (usbirq & USBIR_SOF) + phytium_write8(&priv->regs->usbirq, USBIR_SOF); + + if (usbirq & USBIR_SUSP) { + pr_debug("clear suspend irq\n"); + phytium_write8(&priv->regs->usbirq, USBIR_SUSP); + phytium_write8(&priv->regs->clkgate, 0x7); + } + + return; +DMA_IRQ: + priv->dmaDrv->dma_isr(priv->dmaController); +} + +int32_t hostEpDisable(struct HOST_CTRL *priv, struct HOST_EP *ep) +{ + return 0; +} + +unsigned int get_endpoint_interval(struct usb_endpoint_descriptor desc, + int speed) +{ + unsigned int interval = 0; + + switch (speed) { + case USB_SPEED_HIGH: + if (usb_endpoint_xfer_control(&desc) || usb_endpoint_xfer_bulk(&desc)) { + if (desc.bInterval == 0) + return interval; + interval = fls(desc.bInterval) - 1; + interval = clamp_val(interval, 0, 15); + interval = 1 << interval; + if ((1 << interval) != desc.bInterval) + pr_debug("rounding to %d microframes, desc %d microframes\n", + 1 << interval, desc.bInterval); + break; + } + + if (usb_endpoint_xfer_isoc(&desc) || usb_endpoint_xfer_int(&desc)) { + interval = clamp_val(desc.bInterval, 1, 16) - 1; + interval = 1 << interval; + if (interval != desc.bInterval - 1) + pr_debug("rounding to %d %sframes\n", 1 << interval, + speed == USB_SPEED_FULL ? "" : "micro"); + } + break; + case USB_SPEED_FULL: + if (usb_endpoint_xfer_isoc(&desc)) { + interval = clamp_val(desc.bInterval, 1, 16) - 1; + if (interval != desc.bInterval - 1) + pr_debug("rounding to %d %sframes\n", 1 << interval, + speed == USB_SPEED_FULL ? "" : "micro"); + interval += 3; + break; + } + fallthrough; + case USB_SPEED_LOW: + if (usb_endpoint_xfer_int(&desc) || usb_endpoint_xfer_isoc(&desc)) { + interval = fls(desc.bInterval * 8) - 1; + interval = clamp_val(interval, 3, 10); + if ((1 << interval) != desc.bInterval * 8) + pr_debug("rounding to %d microframes, desc %d microframes\n", + 1 << interval, desc.bInterval); + } + } + + return interval; +} + +int32_t hostReqQueue(struct HOST_CTRL *priv, struct HOST_REQ *req) +{ + struct HOST_EP_PRIV *hostEpPriv; + struct list_head *hEpQueue = NULL; + uint8_t idleQueue = 0; + + if (!priv || !req) + return -EINVAL; + + list_add_tail((struct list_head *)&req->list, (struct list_head *)&req->usbEp->reqList); + hostEpPriv = (struct HOST_EP_PRIV *)req->usbEp->hcPriv; + + if (hostEpPriv->epIsReady) { + if (usb_endpoint_xfer_isoc(&req->usbEp->desc)) { + hostEpPriv->isocEpConfigured = 1; + hostStartReq(priv, req); + } + return 0; + } + + hostEpPriv->epIsReady = 1; + hostEpPriv->maxPacketSize = req->usbEp->desc.wMaxPacketSize; + hostEpPriv->interval = req->interval; + hostEpPriv->epNum = req->usbEp->desc.bEndpointAddress & 0xf; + hostEpPriv->faddress = req->faddress; + hostEpPriv->usbHEp = req->usbEp; + hostEpPriv->isIn = req->epIsIn; + + hostEpPriv->frame = get_endpoint_interval(req->usbEp->desc, req->usbDev->speed); + hostEpPriv->interval = hostEpPriv->frame; + switch (usb_endpoint_type(&req->usbEp->desc)) { + case USB_ENDPOINT_XFER_CONTROL: + hostEpPriv->isIn = 0; + hEpQueue = &priv->ctrlHEpQueue; + hostEpPriv->type = USB_ENDPOINT_XFER_CONTROL; + break; + case USB_ENDPOINT_XFER_BULK: + hEpQueue = hostEpPriv->isIn ? &priv->bulkInHEpQueue[req->epNum - 1] : + &priv->bulkOutHEpQueue[req->epNum - 1]; + hostEpPriv->type = USB_ENDPOINT_XFER_BULK; + break; + case USB_ENDPOINT_XFER_INT: + hEpQueue = hostEpPriv->isIn ? &priv->intInHEpQueue[req->epNum - 1] : + &priv->intOutHEpQueue[req->epNum - 1]; + hostEpPriv->type = USB_ENDPOINT_XFER_INT; + break; + case USB_ENDPOINT_XFER_ISOC: + hEpQueue = hostEpPriv->isIn ? &priv->isoInHEpQueue[req->epNum - 1] : + &priv->isoOutHEpQueue[req->epNum - 1]; + hostEpPriv->type = USB_ENDPOINT_XFER_ISOC; + break; + default: + break; + } + + if (hostEpPriv->isIn) + hostEpPriv->genericHwEp = &priv->in[req->epNum]; + else + hostEpPriv->genericHwEp = &priv->out[req->epNum]; + + hostEpPriv->genericHwEp->state = HOST_EP_ALLOCATED; + hostEpPriv->genericHwEp->refCount++; + + if (list_empty(hEpQueue)) { + if (hostEpPriv->genericHwEp->state == HOST_EP_BUSY) { + pr_err("Error:Hardware endpoint %d is busy\n", + hostEpPriv->genericHwEp->hwEpNum); + return -EINVAL; + } + idleQueue = 1; + } + + if (usb_endpoint_xfer_int(&req->usbEp->desc)) + list_add_tail(&hostEpPriv->node, hEpQueue); + + if (idleQueue) + hostStartReq(priv, req); + + return 0; +} + +static int abortActuallyUsbRequest(struct HOST_CTRL *priv, + struct HOST_REQ *req, struct HOST_EP *usbEp) +{ + struct HOST_EP_PRIV *usbEpPriv; + struct HostEp *hostEp; + uint8_t rxcon, txcon; + + if (!priv || !req || !usbEp) + return -EINVAL; + + usbEpPriv = (struct HOST_EP_PRIV *)usbEp->hcPriv; + hostEp = usbEpPriv->currentHwEp; + + usbEpPriv->transferFinished = 1; + if (hostEp->isInEp) { + if (hostEp->hwEpNum) { + rxcon = phytium_read8(&priv->regs->ep[hostEp->hwEpNum - 1].rxcon); + rxcon = rxcon & (~BIT(7)); + phytium_write8(&priv->regs->ep[hostEp->hwEpNum - 1].rxcon, rxcon); + } + } else { + if (hostEp->hwEpNum) { + txcon = phytium_read8(&priv->regs->ep[hostEp->hwEpNum - 1].txcon); + txcon = txcon & (~BIT(7)); + phytium_write8(&priv->regs->ep[hostEp->hwEpNum - 1].txcon, txcon); + } + } + abortTransfer(priv, req, hostEp); + + return 0; +} + +int32_t hostReqDequeue(struct HOST_CTRL *priv, struct HOST_REQ *req, uint32_t status) +{ + struct HOST_EP *usbEp; + struct HOST_EP_PRIV *usbEpPriv; + int ret = 0; + + if (!priv || !req) + return -EINVAL; + + usbEp = req->usbEp; + usbEpPriv = (struct HOST_EP_PRIV *)usbEp->hcPriv; + + pr_debug("Dequeue usbReq:%p dev%d usbEp%d%s\n", req, req->faddress, req->epNum, + req->epIsIn ? "in" : "out"); + + if (!usbEpPriv->epIsReady) + return 0; + + if (!usbEpPriv->currentHwEp || req->list.prev != &usbEp->reqList) { + givebackRequest(priv, req, status); + if (list_empty(&usbEp->reqList)) { + usbEpPriv->epIsReady = 0; + usbEpPriv->currentHwEp = NULL; + if (usb_endpoint_xfer_int(&usbEp->desc)) + list_del(&usbEpPriv->node); + } + } else + ret = abortActuallyUsbRequest(priv, req, usbEp); + + if (usbEpPriv->isocEpConfigured) + usbEpPriv->isocEpConfigured = 0; + + return ret; +} + +int32_t hostVHubStatusData(struct HOST_CTRL *priv, uint8_t *status) +{ + return 0; +} + +int32_t hostGetDevicePD(struct HOST_CTRL *priv) +{ + return 0; +} + +int32_t hostGetPrivateDataSize(struct HOST_CTRL *priv) +{ + if (!priv) + return 0; + + return sizeof(struct HOST_EP_PRIV); +} + +static int hub_descriptor(struct usb_hub_descriptor *buf) +{ + buf->bDescLength = 9; + buf->bDescriptorType = 0x29; + buf->bNbrPorts = 1; + buf->wHubCharacteristics = 0x11; + buf->bPwrOn2PwrGood = 5; + buf->bHubContrCurrent = 0; + buf->u.hs.DeviceRemovable[0] = 0x2; + + return 0; +} + +static int HubPortSuspend(struct HOST_CTRL *priv, u16 on) +{ + uint8_t otgctrl; + + if (!priv) + return 0; + + otgctrl = phytium_read8(&priv->regs->otgctrl); + + if (on) { + otgctrl &= ~OTGCTRL_BUSREQ; + otgctrl &= ~OTGCTRL_BHNPEN; + + priv->portStatus |= USB_PORT_STAT_SUSPEND; + + switch (phytium_read8(&priv->regs->otgstate)) { + case HOST_OTG_STATE_A_HOST: + priv->otgState = HOST_OTG_STATE_A_SUSPEND; + otgctrl |= OTGCTRL_ASETBHNPEN; + break; + case HOST_OTG_STATE_B_HOST: + priv->otgState = HOST_OTG_STATE_B_HOST_2; + break; + default: + break; + } + phytium_write8(&priv->regs->otgctrl, otgctrl); + } else { + otgctrl |= OTGCTRL_BUSREQ; + otgctrl &= ~OTGCTRL_ASETBHNPEN; + phytium_write8(&priv->regs->otgctrl, otgctrl); + priv->portStatus |= USB_PORT_STAT_RESUME; + } + return 0; +} + +static void HubPortReset(struct HOST_CTRL *priv, uint8_t on) +{ + uint8_t speed; + + if (!priv) + return; + + if (on) { + phytium_write16(&priv->regs->txerrirq, 0xFFFF); + phytium_write16(&priv->regs->txirq, 0xFFFF); + phytium_write16(&priv->regs->rxerrirq, 0xFFFF); + phytium_write16(&priv->regs->rxirq, 0xFFFF); + + phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX); + phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | + ENDPRST_TOGRST | ENDPRST_IO_TX); + phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST); + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | 0 | 0x04); + phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | + FIFOCTRL_IO_TX | 0 | 0x04); + + priv->portStatus |= USB_PORT_STAT_RESET; + priv->portStatus &= ~USB_PORT_STAT_ENABLE; + priv->port_resetting = 0; + } else { + speed = phytium_read8(&priv->regs->speedctrl); + if (speed == SPEEDCTRL_HS) + priv->portStatus |= USB_PORT_STAT_HIGH_SPEED; + else if (speed == SPEEDCTRL_FS) + priv->portStatus &= ~(USB_PORT_STAT_HIGH_SPEED | USB_PORT_STAT_LOW_SPEED); + else + priv->portStatus |= USB_PORT_STAT_LOW_SPEED; + + priv->portStatus &= ~USB_PORT_STAT_RESET; + priv->portStatus |= USB_PORT_STAT_ENABLE | (USB_PORT_STAT_C_RESET << 16) + | (USB_PORT_STAT_C_ENABLE << 16); + + if (priv->hostCallbacks.portStatusChange) + priv->hostCallbacks.portStatusChange(priv); + } +} + +static int get_PortStatus(struct HOST_CTRL *priv, u16 wIndex, uint8_t *buff) +{ + uint32_t temp = 0; + + if (!priv || !buff) + return 0; + + if ((wIndex & 0xff) != 1) + return 1; + + if (priv->portStatus & USB_PORT_STAT_RESET) { + if (!priv->port_resetting) + HubPortReset(priv, 0); + } + + if (priv->portStatus & USB_PORT_STAT_RESUME) { + priv->portStatus &= ~(USB_PORT_STAT_SUSPEND | USB_PORT_STAT_RESUME); + priv->portStatus |= USB_PORT_STAT_C_SUSPEND << 16; + if (priv->hostCallbacks.portStatusChange) + priv->hostCallbacks.portStatusChange(priv); + } + + temp = priv->portStatus & (~USB_PORT_STAT_RESUME); + buff[0] = temp; + buff[1] = temp >> 8; + buff[2] = temp >> 16; + buff[3] = temp >> 24; + + return 0; +} + +static int set_PortFeature(struct HOST_CTRL *priv, u16 wValue, u16 wIndex) +{ +// struct HW_Regs *regs = priv->regs; + + if ((wIndex & 0xff) != 1) + return 1; + + switch (wValue) { + case USB_PORT_FEAT_CONNECTION: + break; + case USB_PORT_FEAT_ENABLE: + break; + case USB_PORT_FEAT_SUSPEND: + HubPortSuspend(priv, 1); + break; + case USB_PORT_FEAT_OVER_CURRENT: + break; + case USB_PORT_FEAT_RESET: + HubPortReset(priv, 1); + break; + case USB_PORT_FEAT_L1: + break; + case USB_PORT_FEAT_POWER: + hostStart(priv); + break; + case USB_PORT_FEAT_LOWSPEED: + break; + case USB_PORT_FEAT_C_CONNECTION: + break; + case USB_PORT_FEAT_C_ENABLE: + break; + case USB_PORT_FEAT_C_SUSPEND: + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + break; + case USB_PORT_FEAT_INDICATOR: + break; + case USB_PORT_FEAT_C_PORT_L1: + break; + default: + break; + } + priv->portStatus |= 1 << wValue; + + return 0; +} + +static int Clear_PortFeature(struct HOST_CTRL *priv, u16 wValue, u16 wIndex) +{ + if ((wIndex & 0xff) != 1) + return 1; + + switch (wValue) { + case USB_PORT_FEAT_CONNECTION: + break; + case USB_PORT_FEAT_ENABLE: + break; + case USB_PORT_FEAT_SUSPEND: + HubPortSuspend(priv, 0); + break; + case USB_PORT_FEAT_OVER_CURRENT: + break; + case USB_PORT_FEAT_RESET: + break; + case USB_PORT_FEAT_L1: + break; + case USB_PORT_FEAT_POWER: + break; + case USB_PORT_FEAT_LOWSPEED: + break; + case USB_PORT_FEAT_C_CONNECTION: + break; + case USB_PORT_FEAT_C_ENABLE: + break; + case USB_PORT_FEAT_C_SUSPEND: + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + break; + case USB_PORT_FEAT_INDICATOR: + break; + case USB_PORT_FEAT_C_PORT_L1: + break; + default: + break; + } + priv->portStatus &= ~(1 << wValue); + + return 0; +} + +static int hc_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + unsigned long flags = 0; + int retval = 0; + struct HOST_CTRL *priv; + struct phytium_cusb *config = *(struct phytium_cusb **)hcd->hcd_priv; + + if (!config) + return -EINVAL; + + if (!buf) + return -EINVAL; + + if (unlikely(!HCD_HW_ACCESSIBLE(hcd))) { + spin_unlock_irqrestore(&config->lock, flags); + return -ESHUTDOWN; + } + + priv = (struct HOST_CTRL *)config->host_priv; + if (!priv) + return -EINVAL; + + spin_lock_irqsave(&config->lock, flags); + switch (typeReq) { + case GetHubStatus: + break; + case GetPortStatus: + get_PortStatus(priv, wIndex, (uint8_t *)buf); + break; + case GetHubDescriptor: + hub_descriptor((struct usb_hub_descriptor *)buf); + break; + case SetPortFeature: + set_PortFeature(priv, wValue, wIndex); + break; + case ClearPortFeature: + retval = Clear_PortFeature(priv, wValue, wIndex); + break; + case SetHubFeature: + break; + case ClearHubFeature: + break; + default: + break; + } + + spin_unlock_irqrestore(&config->lock, flags); + + return retval; +} + +int32_t hostVHubControl(struct HOST_CTRL *priv, struct usb_ctrlrequest *setup, uint8_t *buff) +{ + uint16_t request; + uint32_t retval = 0; + + if (!priv || !setup || !buff) + return -EINVAL; + + request = setup->bRequestType << 0x08 | setup->bRequest; + switch (request) { + case ClearHubFeature: + break; + case SetHubFeature: + break; + case ClearPortFeature: + retval = Clear_PortFeature(priv, setup->wValue, setup->wIndex); + break; + case SetPortFeature: + retval = set_PortFeature(priv, setup->wValue, setup->wIndex); + break; + case GetHubDescriptor: + retval = hub_descriptor((struct usb_hub_descriptor *)buff); + break; + case GetHubStatus: + break; + case GetPortStatus: + retval = get_PortStatus(priv, setup->wIndex, (uint8_t *)buff); + break; + default: + retval = EOPNOTSUPP; + break; + } + + return retval; +} + +struct HOST_OBJ hostDrv = { + .host_init = hostInit, + .host_destroy = hostDestroy, + .host_start = hostStart, + .host_stop = hostStop, + .host_isr = hostIsr, + + //endpoint operation + .host_epDisable = hostEpDisable, + .host_reqQueue = hostReqQueue, + .host_reqDequeue = hostReqDequeue, + .host_vhubStatusData = hostVHubStatusData, + .host_vhubControl = hostVHubControl, + .host_getDevicePD = hostGetDevicePD, + .host_epGetPrivateDataSize = hostGetPrivateDataSize, +}; + +static struct hc_driver host_driver = { + .description = "phytium-hcd", + .product_desc = "Phytium Host USB Driver", + .hcd_priv_size = sizeof(struct phytium_cusb *), + .flags = HCD_MEMORY | HCD_USB2 | HCD_DMA, + .reset = hc_reset, + .start = hc_start, + .stop = hc_stop, + .shutdown = hc_shutdown, + .urb_enqueue = hc_urb_enqueue, + .urb_dequeue = hc_urb_dequeue, + .endpoint_disable = hc_endpoint_disable, + .get_frame_number = hc_get_frame, + .alloc_dev = hc_alloc_dev, + .free_dev = hc_free_dev, + .reset_device = hc_reset_device, + .update_device = hc_update_device, + .add_endpoint = hc_add_endpoint, + .drop_endpoint = hc_drop_endpoint, + .hub_status_data = hc_hub_status_data, + .hub_control = hc_hub_control, +#ifdef CONFIG_PM + .bus_suspend = hc_bus_suspend, + .bus_resume = hc_bus_resume, +#endif +}; + +static int phytium_host_set_default_cfg(struct phytium_cusb *config) +{ + int index; + + config->host_cfg.regBase = (uintptr_t)config->regs; + config->host_cfg.phy_regBase = (uintptr_t)config->phy_regs; + config->host_cfg.dmultEnabled = 1; + config->host_cfg.memoryAlignment = 0; + config->host_cfg.dmaSupport = 1; + config->host_cfg.isEmbeddedHost = 1; + + for (index = 0; index < HOST_EP_NUM; index++) { + if (index == 0) { + config->host_cfg.epIN[index].bufferingValue = 1; + config->host_cfg.epIN[index].maxPacketSize = 64; + config->host_cfg.epIN[index].startBuf = 0; + + config->host_cfg.epOUT[index].bufferingValue = 1; + config->host_cfg.epOUT[index].maxPacketSize = 64; + config->host_cfg.epOUT[index].startBuf = 0; + } else { + config->host_cfg.epIN[index].bufferingValue = 2; + config->host_cfg.epIN[index].maxPacketSize = 1024; + config->host_cfg.epIN[index].startBuf = 64 + 2 * 1024 * (index - 1); + + config->host_cfg.epOUT[index].bufferingValue = 2; + config->host_cfg.epOUT[index].maxPacketSize = 1024; + config->host_cfg.epOUT[index].startBuf = 64 + 2 * 1024 * (index - 1); + } + } + + return 0; +} + +static int phytium_host_reinit(struct phytium_cusb *config) +{ + struct HOST_CTRL *ctrl; + + if (!config || !config->host_priv) + return 0; + + ctrl = (struct HOST_CTRL *)config->host_priv; + + usb_root_hub_lost_power(config->hcd->self.root_hub); + hostStop(ctrl); + + ctrl->portStatus = 0; + + config->host_obj->host_init(config->host_priv, &config->host_cfg, + &config->host_callbacks, config->dev, config->isVhubHost); + + return 0; +} + +int phytium_host_init(struct phytium_cusb *config) +{ + int ret; + + if (!config) + return 0; + + phytium_host_set_default_cfg(config); + config->host_obj = HOST_GetInstance(); + config->dma_cfg.regBase = config->host_cfg.regBase + 0x400; + + config->dma_obj = DMA_GetInstance(); + config->dma_obj->dma_probe(&config->dma_cfg, &config->dma_sysreq); + + config->host_sysreq.privDataSize = sizeof(struct HOST_CTRL); + config->host_sysreq.trbMemSize = config->dma_sysreq.trbMemSize; + config->host_sysreq.privDataSize += config->dma_sysreq.privDataSize; + + config->host_priv = devm_kzalloc(config->dev, config->host_sysreq.privDataSize, GFP_KERNEL); + if (!config->host_priv) { + ret = -ENOMEM; + goto err_probe; + } + + config->host_cfg.trbAddr = dma_alloc_coherent(config->dev, config->host_sysreq.trbMemSize, + (dma_addr_t *)&config->host_cfg.trbDmaAddr, GFP_KERNEL); + if (!config->host_cfg.trbAddr) { + ret = -ENOMEM; + goto err_dma_coherent; + } + + config->host_callbacks.portStatusChange = host_rh_port_status_change; + config->host_callbacks.getEpToggle = host_get_ep_toggle; + config->host_callbacks.setEpToggle = host_set_ep_toggle; + config->host_callbacks.givebackRequest = host_giveback_request; + + config->host_obj->host_init(config->host_priv, &config->host_cfg, + &config->host_callbacks, config->dev, config->isVhubHost); + + config->hcd = usb_create_hcd(&host_driver, config->dev, dev_name(config->dev)); + if (!config->hcd) { + ret = -ENODEV; + goto err_host; + } + + *config->hcd->hcd_priv = (unsigned long)config; + config->hcd->self.uses_pio_for_control = 0; + config->hcd->uses_new_polling = 1; + config->hcd->has_tt = 1; + + dev_set_drvdata(config->dev, config); + + config->hcd->self.otg_port = 1; + config->hcd->power_budget = 500; + ret = usb_add_hcd(config->hcd, 0, 0); + if (ret < 0) + goto err_setup; + + return 0; +err_setup: + usb_put_hcd(config->hcd); +err_host: + config->host_obj->host_destroy(config->host_priv); + dma_free_coherent(config->dev, config->host_sysreq.trbMemSize, + config->host_cfg.trbAddr, config->host_cfg.trbDmaAddr); +err_dma_coherent: +err_probe: + dev_set_drvdata(config->dev, NULL); + return ret; +} + +int phytium_host_uninit(struct phytium_cusb *config) +{ + if (!config) + return 0; + + if (config->hcd) { + usb_remove_hcd(config->hcd); + usb_put_hcd(config->hcd); + config->hcd = NULL; + } + + return 0; +} + +#ifdef CONFIG_PM +int phytium_host_resume(void *priv) +{ + int otgctrl; + struct phytium_cusb *config = (struct phytium_cusb *)priv; + struct HOST_CTRL *ctrl = (struct HOST_CTRL *)config->host_priv; + + if (!ctrl) + return -EINVAL; + + otgctrl = phytium_read8(&ctrl->regs->otgctrl); + otgctrl |= 1; + phytium_write8(&ctrl->regs->otgctrl, otgctrl); + + phytium_host_reinit(config); + + return 0; +} + +int phytium_host_suspend(void *priv) +{ + int otgctrl; + struct phytium_cusb *config = (struct phytium_cusb *)priv; + struct HOST_CTRL *ctrl = (struct HOST_CTRL *)config->host_priv; + + if (!ctrl) + return -EINVAL; + + otgctrl = phytium_read8(&ctrl->regs->otgctrl); + otgctrl = otgctrl & (~1); + phytium_write8(&ctrl->regs->otgctrl, otgctrl); + + return 0; +} +#endif + +struct HOST_OBJ *HOST_GetInstance(void) +{ + return &hostDrv; +} diff --git a/drivers/usb/phytium/host_api.h b/drivers/usb/phytium/host_api.h new file mode 100644 index 0000000000000000000000000000000000000000..b99d2b4980fbe09ab31f8591b0cd528f6516d6f5 --- /dev/null +++ b/drivers/usb/phytium/host_api.h @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __PHYTIUM_HOST_API_H_ +#define __PHYTIUM_HOST_API_H_ + +#include +//#include "list.h" +#include "dma.h" + +#define MAX_SUPPORTED_DEVICES 16 +#define USB_PORT_STAT_RESUME (1 << 31) +#define MAX_INSTANCE_EP_NUM 6 +#define ENDPOINT_DIR 2 + +enum HOST_OtgState { + HOST_OTG_STATE_A_IDLE, + HOST_OTG_STATE_A_WAIT_VRISE, + HOST_OTG_STATE_A_WAIT_BCON, + HOST_OTG_STATE_A_HOST, + HOST_OTG_STATE_A_SUSPEND, + HOST_OTG_STATE_A_PERIPHERAL, + HOST_OTG_STATE_A_VBUS_ERR, + HOST_OTG_STATE_A_WAIT_VFALL, + HOST_OTG_STATE_B_IDLE = 0x10, + HOST_OTG_STATE_B_PERIPHERAL, + HOST_OTG_STATE_B_WAIT_ACON, + HOST_OTG_STATE_B_HOST, + HOST_OTG_STATE_B_HOST_2, + HOST_OTG_STATE_B_SRP_INT1, + HOST_OTG_STATE_B_SRP_INT2, + HOST_OTG_STATE_B_DISCHRG1, + HOST_OTG_STATE_B_DISCHRG2, + HOST_OTG_STATE_UNKNOWN, +}; + +enum HOST_EP0_STAGE { + HOST_EP0_STAGE_IDLE, + HOST_EP0_STAGE_SETUP, + HOST_EP0_STAGE_IN, + HOST_EP0_STAGE_OUT, + HOST_EP0_STAGE_STATUSIN, + HOST_EP0_STAGE_STATUSOUT, + HOST_EP0_STAGE_ACK, +}; + +enum HOST_EP_STATE { + HOST_EP_FREE, + HOST_EP_ALLOCATED, + HOST_EP_BUSY, + HOST_EP_NOT_IMPLEMENTED +}; + +struct HOST_DEVICE { + uint8_t devnum; + uint8_t hubPort; + unsigned int speed; + struct HOST_DEVICE *parent; + void *hcPriv; + void *userExt; +}; + +struct HOST_EP { + struct usb_endpoint_descriptor desc; + struct list_head reqList; + void *userExt; + uint8_t *hcPriv; + uint8_t device_epNum; +}; + +struct HOST_USB_DEVICE { + struct HOST_EP ep0_hep; + struct HOST_EP *in_ep[16]; + struct HOST_EP *out_ep[16]; + struct HOST_DEVICE udev; + struct usb_device *ld_udev; +}; + +struct HostEp { + uint8_t name[255]; + uint8_t hwEpNum; + uint8_t hwBuffers; + uint16_t hwMaxPacketSize; + uint8_t isInEp; + void *channel; + uint8_t usbEpNum; + uint8_t type; + uint8_t usbPacketSize; + enum HOST_EP_STATE state; + struct HOST_EP *scheduledUsbHEp; + uint8_t refCount; +}; + +struct HOST_ISOFRAMESDESC { + uint32_t length; + uint32_t offset; +}; + +struct HOST_EP_PRIV { + struct list_head node; + struct HostEp *genericHwEp; + struct HostEp *currentHwEp; + uint8_t epIsReady; + uint8_t isIn; + uint8_t type; + uint8_t interval; + uint8_t epNum; + uint8_t faddress; + uint16_t maxPacketSize; + uint32_t frame; + uint8_t hubAddress; + uint8_t portAddress; + uint8_t split; + struct HOST_EP *usbHEp; + uint8_t isocEpConfigured; + uint8_t transferFinished; +}; + +struct HOST_REQ { + struct list_head list; + struct HOST_EP *usbEp; + void *userExt; + void *hcPriv; + struct HOST_DEVICE *usbDev; + struct usb_ctrlrequest *setup; + uintptr_t setupDma; + void *bufAddress; + uintptr_t buffDma; + uint32_t buffLength; + uint32_t actualLength; + uint8_t epIsIn; + uint8_t eptype; + uint8_t epNum; + uint8_t faddress; + uint8_t interval; + uint8_t status; + uint8_t reqUnlinked; + struct HOST_ISOFRAMESDESC *isoFramesDesc; + uint32_t isoFramesNumber; +}; + +struct HOST_SYSREQ { + uint32_t privDataSize; + uint32_t trbMemSize; +}; + +struct HOST_EP_CFG { + uint8_t bufferingValue; + uint16_t startBuf; + uint16_t maxPacketSize; +}; + +struct HOST_CFG { + uintptr_t regBase; + uintptr_t phy_regBase; + struct HOST_EP_CFG epIN[16]; + struct HOST_EP_CFG epOUT[16]; + uint8_t dmultEnabled; + uint8_t memoryAlignment; + uint8_t dmaSupport; + uint8_t isEmbeddedHost; + void *trbAddr; + uintptr_t trbDmaAddr; +}; + +struct HOST_CTRL; + +struct HOST_CALLBACKS { + void (*portStatusChange)(struct HOST_CTRL *priv); + + uint8_t (*getEpToggle)(struct HOST_CTRL *priv, + struct HOST_DEVICE *usbDev, uint8_t epNum, uint8_t isIn); + + void (*setEpToggle)(struct HOST_CTRL *priv, struct HOST_DEVICE *usbDev, + uint8_t epNum, uint8_t isIn, uint8_t toggle); + + void (*givebackRequest)(struct HOST_CTRL *priv, + struct HOST_REQ *usbReq, uint32_t status); + + void (*setTimer)(struct HOST_CTRL *priv, uint32_t time, uint8_t id); +}; + +struct HOST_OBJ { + int32_t (*host_probe)(struct HOST_CFG *config, struct HOST_SYSREQ *sysReq); + + int32_t (*host_init)(struct HOST_CTRL *priv, struct HOST_CFG *config, + struct HOST_CALLBACKS *callbacks, struct device *pdev, bool isVhubHost); + + void (*host_destroy)(struct HOST_CTRL *priv); + + void (*host_start)(struct HOST_CTRL *priv); + + void (*host_stop)(struct HOST_CTRL *priv); + + void (*host_isr)(struct HOST_CTRL *priv); + + int32_t (*host_epDisable)(struct HOST_CTRL *priv, struct HOST_EP *ep); + + int32_t (*host_reqQueue)(struct HOST_CTRL *priv, struct HOST_REQ *req); + + int32_t (*host_reqDequeue)(struct HOST_CTRL *priv, + struct HOST_REQ *req, uint32_t status); + + int32_t (*host_vhubStatusData)(struct HOST_CTRL *priv, uint8_t *status); + + int32_t (*host_vhubControl)(struct HOST_CTRL *priv, + struct usb_ctrlrequest *setup, uint8_t *buff); + + int32_t (*host_getDevicePD)(struct HOST_CTRL *priv); + + int32_t (*host_epGetPrivateDataSize)(struct HOST_CTRL *priv); +}; + +struct HOST_CTRL { + struct device *dev; + struct HW_REGS *regs; + struct HOST_OBJ *hostDrv; + struct HOST_CFG hostCfg; + struct HOST_CALLBACKS hostCallbacks; + struct HostEp in[16]; + struct HostEp out[16]; + uint32_t portStatus; + struct list_head ctrlHEpQueue; + struct list_head isoInHEpQueue[MAX_INSTANCE_EP_NUM]; + struct list_head isoOutHEpQueue[MAX_INSTANCE_EP_NUM]; + struct list_head intInHEpQueue[MAX_INSTANCE_EP_NUM]; + struct list_head intOutHEpQueue[MAX_INSTANCE_EP_NUM]; + struct list_head bulkInHEpQueue[MAX_INSTANCE_EP_NUM]; + struct list_head bulkOutHEpQueue[MAX_INSTANCE_EP_NUM]; + uint8_t hwEpInCount; + uint8_t hwEpOutCount; + unsigned int speed; + enum HOST_OtgState otgState; + enum HOST_EP0_STAGE ep0State; + uint8_t vBusErrCnt; + uint8_t isRemoteWakeup; + uint8_t isSelfPowered; + uint8_t deviceAddress; + struct DMA_OBJ *dmaDrv; + void *dmaController; + struct DMA_CFG dmaCfg; + struct DMA_CALLBACKS dmaCallback; + uint8_t port_resetting; + struct HOST_USB_DEVICE *host_devices_table[MAX_SUPPORTED_DEVICES]; + struct CUSTOM_REGS *custom_regs; + struct VHUB_REGS *vhub_regs; + int ep_remap_pool[ENDPOINT_DIR][MAX_INSTANCE_EP_NUM + 1]; +}; + +struct HOST_OBJ *HOST_GetInstance(void); + +#endif diff --git a/drivers/usb/phytium/hw-regs.h b/drivers/usb/phytium/hw-regs.h new file mode 100644 index 0000000000000000000000000000000000000000..8da9f8e9b92537f20c2be1c232849f331f32baf7 --- /dev/null +++ b/drivers/usb/phytium/hw-regs.h @@ -0,0 +1,297 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_PHYTIUM_HW_REGS +#define __LINUX_PHYTIUM_HW_REGS + +#define USBIEN 0x198 +#define USBIEN_SUDAVIE BIT(0) +#define USBIEN_SOFIE BIT(1) +#define USBIEN_SUTOKIE BIT(2) +#define USBIEN_SUSPIE BIT(3) +#define USBIEN_URESIE BIT(4) +#define USBIEN_HSPEEDIE BIT(5) +#define USBIEN_LPMIE BIT(7) + +#define USBCS 0x1a3 +#define USBCS_LSMODE BIT(0) +#define USBCS_LPMNYET BIT(1) +#define USBCS_SIGSUME BIT(5) +#define USBCS_DISCON BIT(6) +#define USBCS_WAKESRC BIT(7) + +#define USBIR_SOF BIT(1) +#define USBIR_SUSP BIT(3) +#define USBIR_URES BIT(4) +#define USBIR_HSPEED BIT(5) + +#define USBIR_SUDAV BIT(0) +#define USBIR_SUTOK BIT(2) +#define USBIR_LPMIR BIT(7) + +#define OTGIRQ_IDLEIRQ BIT(0) +#define OTGIRQ_SRPDETIRQ BIT(1) +#define OTGIRQ_CONIRQ BIT(2) +#define OTGIRQ_LOCSOFIRQ BIT(2) +#define OTGIRQ_VBUSERRIRQ BIT(3) +#define OTGIRQ_PERIPHIRQ BIT(4) +#define OTGIRQ_IDCHANGEIRQ BIT(5) +#define OTGIRQ_HOSTDISCON BIT(6) +#define OTGIRQ_BSE0SRPIRQ BIT(7) + +#define OTGCTRL_BUSREQ BIT(0) +#define OTGCTRL_ABUSDROP BIT(1) +#define OTGCTRL_ASETBHNPEN BIT(2) +#define OTGCTRL_BHNPEN BIT(3) +#define OTGCTRL_SRPVBUSDETEN BIT(4) +#define OTGCTRL_SRPDATDETEN BIT(5) +#define OTGCTRL_FORCEBCONN BIT(7) + +#define OTGSTATUS_ID BIT(6) + +#define ENDPRST_EP 0x0f +#define ENDPRST_IO_TX BIT(4) +#define ENDPRST_TOGRST BIT(5) +#define ENDPRST_FIFORST BIT(6) +#define ENDPRST_TOGSETQ BIT(7) + +#define FIFOCTRL_EP 0x0f +#define FIFOCTRL_IO_TX BIT(4) +#define FIFOCTRL_FIFOAUTO BIT(5) + +#define SPEEDCTRL_LS BIT(0) +#define SPEEDCTRL_FS BIT(1) +#define SPEEDCTRL_HS BIT(2) +#define SPEEDCTRL_HSDISABLE BIT(7) + +#define CON_TYPE_CONTROL 0x00 +#define CON_TYPE_ISOC 0x04 +#define CON_TYPE_BULK 0x08 +#define CON_TYPE_INT 0x0C +#define CON_TYPE_ISOC_1_ISOD 0x00 +#define CON_TYPE_ISOC_2_ISOD 0x10 +#define CON_TYPE_ISOC_3_ISOD 0x20 +#define CON_STALL 0x40 +#define CON_VAL 0x80 + +#define ERR_TYPE 0x1c +#define ERR_COUNT 0x03 +#define ERR_RESEND BIT(6) +#define ERR_UNDERRIEN BIT(7) + +#define ERR_NONE 0 +#define ERR_CRC 1 +#define ERR_DATA_TOGGLE_MISMATCH 2 +#define ERR_STALL 3 +#define ERR_TIMEOUT 4 +#define ERR_PID 5 +#define ERR_TOO_LONG_PACKET 6 +#define ERR_DATA_UNDERRUN 7 + +#define EP0CS_HCSETTOGGLE BIT(6) +#define EP0CS_HCSET BIT(4) +#define EP0CS_RXBUSY_MASK BIT(3) +#define EP0CS_TXBUSY_MASK BIT(2) +#define EP0CS_STALL BIT(0) +#define EP0CS_HSNAK BIT(1) +#define EP0CS_DSTALL BIT(4) +#define EP0CS_CHGSET BIT(7) + +#define CS_ERR 0x01 +#define CS_BUSY 0x02 +#define CS_NPAK 0x0c +#define CS_NPAK_OFFSET 0x02 +#define CS_AUTO 0x10 + +#define CON_BUF_SINGLE 0x00 +#define CON_BUF_DOUBLE 0x01 +#define CON_BUF_TRIPLE 0x02 +#define CON_BUF_QUAD 0x03 +#define CON_BUF 0x03 + +struct HW_REGS { + uint8_t ep0Rxbc; /*address 0x00*/ + uint8_t ep0Txbc; /*address 0x01*/ + uint8_t ep0cs; /*address 0x02*/ + int8_t reserved0; /*address 0x03*/ + uint8_t lpmctrll; /*address 0x04*/ + uint8_t lpmctrlh; /*address 0x05*/ + uint8_t lpmclock; + uint8_t ep0fifoctrl; + struct ep { /*address 0x08*/ + uint16_t rxbc; //outbc (hcinbc) + uint8_t rxcon; + uint8_t rxcs; + uint16_t txbc; //inbc (hcoutbc + uint8_t txcon; + uint8_t txcs; + } ep[15]; + uint8_t reserved1[4]; + uint32_t fifodat[15]; /*address 0x84*/ + uint8_t ep0ctrl; /*address 0xC0*/ + uint8_t tx0err; /*address 0xC1*/ + uint8_t reserved2; + uint8_t rx0err; /*address 0xC3*/ + struct epExt { + uint8_t txctrl; + uint8_t txerr; + uint8_t rxctrl; + uint8_t rxerr; + } epExt[15]; + uint8_t ep0datatx[64]; /*address 0x100*/ + uint8_t ep0datarx[64]; /*address 0x140*/ + uint8_t setupdat[8]; /*address 0x180*/ + uint16_t txirq; /*address 0x188*/ + uint16_t rxirq; /*address 0x18A*/ + uint8_t usbirq; /*address 0x18C*/ + uint8_t reserved4; + uint16_t rxpngirq; /*address 0x18E*/ + uint16_t txfullirq; /*address 0x190*/ + uint16_t rxemptirq; /*address 0x192*/ + uint16_t txien; /*address 0x194*/ + uint16_t rxien; /*address 0x196*/ + uint8_t usbien; /*address 0x198*/ + uint8_t reserved6; + uint16_t rxpngien; /*address 0x19A*/ + uint16_t txfullien; /*address 0x19C*/ + uint16_t rxemptien; /*address 0x19E*/ + uint8_t usbivect; /*address 0x1A0*/ + uint8_t fifoivect; /*address 0x1A1*/ + uint8_t endprst; /*address 0x1A2*/ + uint8_t usbcs; /*address 0x1A3*/ + uint16_t frmnr; /*address 0x1A4*/ + uint8_t fnaddr; /*address 0x1A6*/ + uint8_t clkgate; /*address 0x1A7*/ + uint8_t fifoctrl; /*address 0x1A8*/ + uint8_t speedctrl; /*address 0x1A9*/ + uint8_t reserved8[1]; /*address 0x1AA*/ + uint8_t portctrl; /*address 0x1AB*/ + uint16_t hcfrmnr; /*address 0x1AC*/ + uint16_t hcfrmremain; /*address 0x1AE*/ + uint8_t reserved9[4]; /*address 0x1B0*/ + uint16_t rxerrirq; /*address 0x1B4*/ + uint16_t txerrirq; /*address 0x1B6*/ + uint16_t rxerrien; /*address 0x1B8*/ + uint16_t txerrien; /*address 0x1BA*/ + /*OTG extension*/ + uint8_t otgirq; /*address 0x1BC*/ + uint8_t otgstate; /*address 0x1BD*/ + uint8_t otgctrl; /*address 0x1BE*/ + uint8_t otgstatus; /*address 0x1BF*/ + uint8_t otgien; /*address 0x1C0*/ + uint8_t taaidlbdis; /*address 0x1C1*/ + uint8_t tawaitbcon; /*address 0x1C2*/ + uint8_t tbvbuspls; /*address 0x1C3*/ + uint8_t otg2ctrl; /*address 0x1C4*/ + uint8_t reserved10[2]; /*address 0x1C5*/ + uint8_t tbvbusdispls; /*address 0x1C7*/ + uint8_t traddr; /*address 0x1C8*/ + uint8_t trwdata; /*address 0x1C9*/ + uint8_t trrdata; /*address 0x1CA*/ + uint8_t trctrl; /*address 0x1CB*/ + uint16_t isoautoarm; /*address 0x1CC*/ + uint8_t adpbc1ien; /*address 0x1CE*/ + uint8_t adpbc2ien; /*address 0x1CF*/ + uint8_t adpbcctr0; /*address 0x1D0*/ + uint8_t adpbcctr1; /*address 0x1D1*/ + uint8_t adpbcctr2; /*address 0x1D2*/ + uint8_t adpbc1irq; /*address 0x1D3*/ + uint8_t adpbc0status; /*address 0x1D4*/ + uint8_t adpbc1status; /*address 0x1D5*/ + uint8_t adpbc2status; /*address 0x1D6*/ + uint8_t adpbc2irq; /*address 0x1D7*/ + uint16_t isodctrl; /*address 0x1D8*/ + uint8_t reserved11[2]; + uint16_t isoautodump; /*address 0x1DC*/ + uint8_t reserved12[2]; + uint8_t ep0maxpack; /*address 0x1E0*/ + uint8_t reserved13; + uint16_t rxmaxpack[15]; /*address 0x1E2*/ + struct rxsoftimer { /*address 0x200 to 0x23F*/ + uint16_t timer; + uint8_t reserved; + uint8_t ctrl; + } rxsoftimer[16]; + + struct txsoftimer { /*address 0x240 to 0x27F*/ + uint16_t timer; + uint8_t reserved; + uint8_t ctrl; + } txsoftimer[16]; + uint8_t reserved14[132]; + struct rxstaddr { /*address 0x304*/ + uint16_t addr; + uint16_t reserved; + } rxstaddr[15]; + uint8_t reserved15[4]; + struct txstaddr { /*address 0x344*/ + uint16_t addr; + uint16_t reserved; + } txstaddr[15]; + int8_t reserved16[4]; /*address 0x380*/ + struct irqmode { /*address 0x384*/ + int8_t inirqmode; + int8_t reserved21; + int8_t outirqmode; + int8_t reserved22; + } irqmode[15]; + /*The Microprocessor control*/ + uint8_t cpuctrl; /*address 0x3C0*/ + int8_t reserved17[15]; + /*The debug counters and workarounds*/ + uint8_t debug_rx_bcl; /*address 0x3D0*/ + uint8_t debug_rx_bch; /*address 0x3D1*/ + uint8_t debug_rx_status; /*address 0x3D2*/ + uint8_t debug_irq; /*address 0x3D3*/ + uint8_t debug_tx_bcl; /*address 0x3D4*/ + uint8_t debug_tx_bch; /*address 0x3D5*/ + uint8_t debug_tx_status; /*address 0x3D6*/ + uint8_t debug_ien; /*address 0x3D7*/ + uint8_t phywa_en; /*address 0x3D8*/ + /*endian*/ + uint8_t wa1_cnt; /*address 0x3D9*/ + int8_t reserved18[2]; /*address 0x3DA*/ + uint8_t endian_sfr_cs; /*address 0x3DC*/ + int8_t reserved19[2]; /*address 0x3DD*/ + uint8_t endian_sfr_s; /*address 0x3DF*/ + int8_t reserved20[2]; /*address 0x3E0*/ + uint16_t txmaxpack[15]; /*address 0x3E2*/ +}; + +struct CUSTOM_REGS { + uint32_t secure_ctrl; /*address 0x80000*/ + uint32_t secsid_atst; /*address 0x80004*/ + uint32_t nsaid_smmuid; /*address 0x80008*/ + uint32_t ace; /*address 0x8000c*/ + uint32_t wakeup; /*address 0x80010*/ + uint32_t debug; /*address 0x80014*/ +}; + +struct VHUB_REGS { + uint32_t gen_cfg; /*address 0x00*/ + uint32_t gen_st; /*address 0x04*/ + uint32_t bc_cfg; /*address 0x08*/ + uint32_t bc_st; /*address 0x0c*/ + uint32_t adp_cfg; /*address 0x10*/ + uint32_t adp_st; /*address 0x14*/ + uint32_t dbg_cfg; /*address 0x18*/ + uint32_t dbg_st; /*address 0x1c*/ + uint32_t utmip_cfg; /*address 0x20*/ + uint32_t utmip_st; /*address 0x24*/ +}; + +struct DMARegs { + uint32_t conf; /*address 0x400*/ + uint32_t sts; /*address 0x404*/ + uint32_t reserved5[5]; + uint32_t ep_sel; /*address 0x41C*/ + uint32_t traddr; /*address 0x420*/ + uint32_t ep_cfg; /*address 0x424*/ + uint32_t ep_cmd; /*address 0x428*/ + uint32_t ep_sts; /*address 0x42c*/ + uint32_t ep_sts_sid;/*address 0x430*/ + uint32_t ep_sts_en; /*address 0x434*/ + uint32_t drbl; /*address 0x438*/ + uint32_t ep_ien; /*address 0x43C*/ + uint32_t ep_ists; /*address 0x440*/ +}; + +#endif diff --git a/drivers/usb/phytium/pci.c b/drivers/usb/phytium/pci.c new file mode 100644 index 0000000000000000000000000000000000000000..b4d675effb88b56a36f41e2a94e5b78f4eff5c17 --- /dev/null +++ b/drivers/usb/phytium/pci.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include "core.h" + +#define PHYTIUM_OTG_USB_LOADED 3 + +static bool phytium_hw_is_device(struct phytium_cusb *config) +{ + pr_info("%s %d\n", __func__, __LINE__); + + return false; +} + +static bool phytium_hw_is_host(struct phytium_cusb *config) +{ + pr_info("%s %d\n", __func__, __LINE__); + + return true; +} + +static int phytium_get_dr_mode(struct phytium_cusb *config) +{ + enum usb_dr_mode mode; + + config->dr_mode = usb_get_dr_mode(config->dev); + if (config->dr_mode == USB_DR_MODE_UNKNOWN) + config->dr_mode = USB_DR_MODE_OTG; + + mode = config->dr_mode; + if (phytium_hw_is_device(config)) { + if (IS_ENABLED(CONFIG_USB_PHYTIUM_HOST)) { + dev_err(config->dev, "Controller does not support host mode.\n"); + return -EINVAL; + } + + mode = USB_DR_MODE_PERIPHERAL; + } else if (phytium_hw_is_host(config)) { + if (IS_ENABLED(CONFIG_USB_PHYTIUM_PERIPHERAL)) { + dev_err(config->dev, "Controller does not support device mode.\n"); + return -EINVAL; + } + mode = USB_DR_MODE_HOST; + } else { + if (IS_ENABLED(CONFIG_USB_PHYTIUM_HOST)) + mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_PHYTIUM_PERIPHERAL)) + mode = USB_DR_MODE_PERIPHERAL; + } + + if (mode != config->dr_mode) { + dev_warn(config->dev, "Configuration mismatch. dr_mode forced to %s\n", + mode == USB_DR_MODE_HOST ? "host" : "device"); + config->dr_mode = mode; + } + + return 0; +} + +static int phytium_pci_probe(struct pci_dev *pdev, const struct pci_device_id *pid) +{ + struct phytium_cusb *config; + int retval = 0; + + if (usb_disabled()) + return -ENODEV; + + retval = pcim_enable_device(pdev); + if (retval < 0) { + dev_err(&pdev->dev, "pcim_enable_device failed\n"); + return -ENODEV; + } + pci_set_master(pdev); + + config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL); + if (!config) + return -ENOMEM; + + spin_lock_init(&config->lock); + config->dev = &pdev->dev; + + config->irq = pdev->irq; + if (config->irq <= 0) { + dev_err(config->dev, "getting usb irq failed\n"); + return config->irq; + } + + config->regs = pci_iomap(pdev, 0, 0); + if (IS_ERR(config->regs)) { + dev_err(config->dev, "map IOMEM resource failed\n"); + return -1; + } + + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + + if (dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64))) + dev_err(&pdev->dev, "failed to set 64-bit dma\n"); + else if (dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32))) + dev_err(&pdev->dev, "failed to set 32-bit dma\n"); + + pci_enable_msi(pdev); + + phytium_get_dr_mode(config); + + phytium_core_reset(config, false); + + if (config->dr_mode == USB_DR_MODE_HOST || config->dr_mode == USB_DR_MODE_OTG) + phytium_host_init(config); + + if (config->dr_mode == USB_DR_MODE_PERIPHERAL || config->dr_mode == USB_DR_MODE_OTG) + phytium_gadget_init(config); + + dev_set_drvdata(config->dev, config); + + return 0; +} + +static void phytium_pci_remove(struct pci_dev *pdev) +{ + struct phytium_cusb *config = dev_get_drvdata(&pdev->dev); + + phytium_get_dr_mode(config); + if (config->dr_mode == USB_DR_MODE_HOST || config->dr_mode == USB_DR_MODE_OTG) + phytium_host_uninit(config); + + if (config->dr_mode == USB_DR_MODE_PERIPHERAL || config->dr_mode == USB_DR_MODE_OTG) + phytium_gadget_uninit(config); + + if (config->dr_mode == USB_DR_MODE_PERIPHERAL || config->dr_mode == USB_DR_MODE_OTG) + usb_del_gadget_udc(&config->gadget); + + dev_set_drvdata(&pdev->dev, NULL); + pr_info("%s %d\n", __func__, __LINE__); +} + +static void phytium_pci_shutdown(struct pci_dev *pdev) +{ + struct phytium_cusb *config; + + config = dev_get_drvdata(&pdev->dev); + + phytium_get_dr_mode(config); + + if (config->dr_mode == USB_DR_MODE_PERIPHERAL || config->dr_mode == USB_DR_MODE_OTG) + usb_del_gadget_udc(&config->gadget); +} + +#ifdef CONFIG_PM +static int phytium_pci_resume(struct pci_dev *pdev) +{ + unsigned long flags = 0; + struct phytium_cusb *config; + int ret = 0; + + config = dev_get_drvdata(&pdev->dev); + + spin_lock_irqsave(&config->lock, flags); + ret = phytium_host_resume(config); + spin_unlock_irqrestore(&config->lock, flags); + + return ret; +} + +static int phytium_pci_suspend(struct pci_dev *pdev, pm_message_t state) +{ + unsigned long flags = 0; + struct phytium_cusb *config; + int ret; + + config = dev_get_drvdata(&pdev->dev); + + spin_lock_irqsave(&config->lock, flags); + ret = phytium_host_suspend(config); + spin_unlock_irqrestore(&config->lock, flags); + + return 0; +} +#endif + +const struct pci_device_id phytium_pci_id_table[] = { + {0x10ee, 0x8012, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {} +}; + +static struct pci_driver phytium_otg_driver = { + .name = "phytium_usb", + .id_table = phytium_pci_id_table, + .probe = phytium_pci_probe, + .remove = phytium_pci_remove, + .shutdown = phytium_pci_shutdown, +#ifdef CONFIG_PM + .resume = phytium_pci_resume, + .suspend = phytium_pci_suspend, +#endif +}; + +module_pci_driver(phytium_otg_driver); + +MODULE_AUTHOR("Chen Zhenhua "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium usb pci wrapper"); diff --git a/drivers/usb/phytium/platform.c b/drivers/usb/phytium/platform.c new file mode 100644 index 0000000000000000000000000000000000000000..48f35b19ade72ac5b518b2d3bdc7070b6388d0a1 --- /dev/null +++ b/drivers/usb/phytium/platform.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include "core.h" +#include "hw-regs.h" + +#define PHYTIUM_OTG_USB_LOADED 3 +#define USB2_2_BASE_ADDRESS 0x31800000 + +static const struct of_device_id phytium_otg_of_match[] = { + { + .compatible = "phytium,usb2", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, phytium_otg_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_otg_acpi_match[] = { + { "PHYT0037", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, phytium_otg_acpi_match); +#endif + +static int phytium_get_dr_mode(struct phytium_cusb *config) +{ + config->dr_mode = usb_get_dr_mode(config->dev); + if (config->dr_mode == USB_DR_MODE_UNKNOWN) + config->dr_mode = USB_DR_MODE_PERIPHERAL; + + return 0; +} + +static irqreturn_t platform_usb_irq(int irq, void *dev_id) +{ + unsigned long flags; + uint8_t otgstate; + + struct phytium_cusb *config = (struct phytium_cusb *)dev_id; + struct GADGET_CTRL *gadget_ctrl = config->gadget_priv; + struct HOST_CTRL *host_ctrl = config->host_priv; + + if (gadget_ctrl || host_ctrl) { + if (host_ctrl) + otgstate = phytium_read8(&host_ctrl->regs->otgstate); + else + otgstate = phytium_read8(&gadget_ctrl->regs->otgstate); + + spin_lock_irqsave(&config->lock, flags); + if (otgstate > HOST_OTG_STATE_A_WAIT_VFALL) + config->gadget_obj->gadget_isr(config->gadget_priv); + else + config->host_obj->host_isr(config->host_priv); + spin_unlock_irqrestore(&config->lock, flags); + } + + return IRQ_HANDLED; +} + +static int phytium_driver_probe(struct platform_device *pdev) +{ + struct phytium_cusb *config; + struct resource *res, *phy_res; + int retval = 0; + + config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL); + if (!config) + return -ENOMEM; + + spin_lock_init(&config->lock); + config->dev = &pdev->dev; + config->isVhubHost = false; + + config->irq = platform_get_irq(pdev, 0); + if (config->irq <= 0) { + dev_err(config->dev, "getting usb irq failed\n"); + return config->irq; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + config->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (IS_ERR(config->regs)) { + dev_err(config->dev, "map IOMEM resource failed\n"); + return -1; + } + + phy_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + config->phy_regs = devm_ioremap(&pdev->dev, phy_res->start, resource_size(phy_res)); + if (IS_ERR(config->phy_regs)) { + dev_err(config->dev, "map IOMEM phy resource failed\n"); + return -1; + } + + phytium_get_dr_mode(config); + + phytium_core_reset(config, false); + + if (config->dr_mode == USB_DR_MODE_HOST || + config->dr_mode == USB_DR_MODE_OTG) { + if (res->start == USB2_2_BASE_ADDRESS) + config->isVhubHost = true; + + phytium_host_init(config); + } + + if (config->dr_mode == USB_DR_MODE_PERIPHERAL || + config->dr_mode == USB_DR_MODE_OTG) + phytium_gadget_init(config); + + dev_set_drvdata(&pdev->dev, config); + + if (config->irq > 0) { + retval = devm_request_irq(config->dev, config->irq, platform_usb_irq, + IRQF_SHARED, "phytium_otg", config); + if (retval != 0) { + dev_err(config->dev, "request irq %d err %d\n", config->irq, retval); + config->irq = 0; + } + } + + return retval; +} + +static int phytium_driver_remove(struct platform_device *dev) +{ + struct phytium_cusb *config = platform_get_drvdata(dev); + + if (!config) + return 0; + + phytium_get_dr_mode(config); + + if (config->dr_mode == USB_DR_MODE_HOST || + config->dr_mode == USB_DR_MODE_OTG) + phytium_host_uninit(config); + + if (config->dr_mode == USB_DR_MODE_PERIPHERAL || + config->dr_mode == USB_DR_MODE_OTG) + phytium_gadget_uninit(config); + + dev_set_drvdata(&dev->dev, NULL); + return 0; +} + +static void phytium_driver_shutdown(struct platform_device *dev) +{ + pr_info("%s %d\n", __func__, __LINE__); +} + +#ifdef CONFIG_PM +static int phytium_driver_suspend(struct device *dev) +{ + struct phytium_cusb *config; + int ret = 0; + + config = dev_get_drvdata(dev); + + if (config->dr_mode == USB_DR_MODE_HOST || + config->dr_mode == USB_DR_MODE_OTG) + ret = phytium_host_suspend(config); + + if (config->dr_mode == USB_DR_MODE_PERIPHERAL || + config->dr_mode == USB_DR_MODE_OTG) + ret = phytium_gadget_suspend(config); + + return ret; +} + +static int phytium_driver_resume(struct device *dev) +{ + struct phytium_cusb *config; + int ret = 0; + + config = dev_get_drvdata(dev); + if (config->dr_mode == USB_DR_MODE_HOST || + config->dr_mode == USB_DR_MODE_OTG) + ret = phytium_host_resume(config); + + if (config->dr_mode == USB_DR_MODE_PERIPHERAL || + config->dr_mode == USB_DR_MODE_OTG) + ret = phytium_gadget_resume(config); + + return ret; +} + +static const struct dev_pm_ops phytium_usb_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(phytium_driver_suspend, phytium_driver_resume) +}; +#endif + +static struct platform_driver phytium_otg_driver = { + .driver = { + .name = "phytium-otg", + .of_match_table = of_match_ptr(phytium_otg_of_match), + .acpi_match_table = ACPI_PTR(phytium_otg_acpi_match), +#ifdef CONFIG_PM + .pm = &phytium_usb_pm_ops, +#endif + }, + .probe = phytium_driver_probe, + .remove = phytium_driver_remove, + .shutdown = phytium_driver_shutdown, +}; + +module_platform_driver(phytium_otg_driver); + +MODULE_AUTHOR("Chen Zhenhua "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Phytium usb platform wrapper"); diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig index ad316573288a842e3480e85e76f52705e81c8f21..8f2675c38c82b6c67f21a4151e78ef99ac8d71dc 100644 --- a/drivers/w1/masters/Kconfig +++ b/drivers/w1/masters/Kconfig @@ -58,6 +58,16 @@ config HDQ_MASTER_OMAP Say Y here if you want support for the 1-wire or HDQ Interface on an OMAP processor. +config W1_MASTER_PHYTIUM + bool "Phytium 1-wire driver" + depends on ARCH_PHYTIUM || COMPILE_TEST + help + Say Y here if you want to get support for the 1-wire interface + on an Phytium SoC. + + This driver can also be built as a module. If so, the module + will be called phytium-w1. + config W1_MASTER_SGI tristate "SGI ASIC driver" help diff --git a/drivers/w1/masters/Makefile b/drivers/w1/masters/Makefile index c5d85a827e5209cebac809a2ca51ccd063cd02eb..60bf6070c5b3922f25d35d75d82ef1a2fddd8c4a 100644 --- a/drivers/w1/masters/Makefile +++ b/drivers/w1/masters/Makefile @@ -10,4 +10,5 @@ obj-$(CONFIG_W1_MASTER_MXC) += mxc_w1.o obj-$(CONFIG_W1_MASTER_GPIO) += w1-gpio.o obj-$(CONFIG_HDQ_MASTER_OMAP) += omap_hdq.o +obj-$(CONFIG_W1_MASTER_PHYTIUM) += phytium_w1.o obj-$(CONFIG_W1_MASTER_SGI) += sgi_w1.o diff --git a/drivers/w1/masters/phytium_w1.c b/drivers/w1/masters/phytium_w1.c new file mode 100644 index 0000000000000000000000000000000000000000..f06a36b0fa22cfaff062f1575057b987930d1686 --- /dev/null +++ b/drivers/w1/masters/phytium_w1.c @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium 1-wire bus master driver + * + * Copyright (C) 2021-2023, Phytium Technology, Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PHY_W1M_CTL 0x08 + +/* Simplify mode */ +#define PHY_W1M_CMD 0x04 +#define PHY_W1M_PWM0_START_B 0x30 +#define PHY_W1M_PWM0_END_B 0x34 +#define PHY_W1M_PWM1_START_B 0x38 +#define PHY_W1M_PWM1_END_B 0x3c +#define PHY_W1M_SAMPLE_B 0x40 +#define PHY_W1M_INT_EN_B 0x64 +#define PHY_W1M_INT_STATUS_B 0x74 +#define PHY_W1M_DATA_REG 0x70 + +#define PHY_W1M_CMD_ROM_SEARCH 0xF0 +#define PHY_W1M_CMD_WRITE_BYTE 0x36 +#define PHY_W1M_CMD_RESET_BUS 0x37 +#define PHY_W1M_CMD_READ_BYTE 0x3B +#define PHY_W1M_SLAVE_ROM_ID 0x160 + +#define PHY_W1M_INT_EN_TXCOMPLETE BIT(6) +#define PHY_W1M_INT_EN_RXCOMPLETE BIT(7) + +#define PHY_W1M_INT_STATUS_TXCOMPLETE BIT(6) +#define PHY_W1M_INT_STATUS_RXCOMPLETE BIT(7) + +#define W1M_MOD_W1 1 +#define W1M_MOD_PECI 0 + +#define PHY_W1M_FLAG_CLEAR 0 +#define PHY_W1M_FLAG_SET 1 +#define PHY_W1M_TIMEOUT (HZ/5) + +#define PHY_W1M_MAX_USER 4 + +static DECLARE_WAIT_QUEUE_HEAD(w1m_wait_queue); + +struct w1m_data { + struct device *dev; + void __iomem *w1m_base; + /* lock status update */ + struct mutex w1m_mutex; + int w1m_usecount; + u8 w1m_irqstatus; + /* device lock */ + spinlock_t w1m_spinlock; + /* + * Used to control the call to phytium_w1m_get and phytium_w1m_put. + * Non-w1 Protocol: Write the CMD|REG_address first, followed by + * the data wrire or read. + */ + int init_trans; +}; + +/* W1 register I/O routines */ +static inline u8 phytium_w1m_read(struct w1m_data *w1m_data, u32 offset) +{ + return readl(w1m_data->w1m_base + offset); +} + +static inline void phytium_w1m_write(struct w1m_data *w1m_data, u32 offset, u8 val) +{ + writel(val, w1m_data->w1m_base + offset); +} + +static inline u8 phytium_w1m_merge(struct w1m_data *w1m_data, u32 offset, + u8 val, u8 mask) +{ + u8 new_val = (__raw_readl(w1m_data->w1m_base + offset) & ~mask) + | (val & mask); + writel(new_val, w1m_data->w1m_base + offset); + + return new_val; +} + +static void w1m_disable_interrupt(struct w1m_data *w1m_data, u32 offset, + u32 mask) +{ + u32 ie; + + ie = readl(w1m_data->w1m_base + offset); + writel(ie & mask, w1m_data->w1m_base + offset); +} + +/* write out a byte and fill *status with W1M_INT_STATUS */ +static int phytium_write_byte(struct w1m_data *w1m_data, u8 val, u8 *status) +{ + int ret; + unsigned long irqflags; + + *status = 0; + + spin_lock_irqsave(&w1m_data->w1m_spinlock, irqflags); + /* clear interrupt flags via a dummy read */ + phytium_w1m_read(w1m_data, PHY_W1M_INT_STATUS_B); + /* ISR loads it with new INT_STATUS */ + w1m_data->w1m_irqstatus = 0; + spin_unlock_irqrestore(&w1m_data->w1m_spinlock, irqflags); + + phytium_w1m_merge(w1m_data, PHY_W1M_INT_EN_B, + PHY_W1M_INT_EN_TXCOMPLETE, + PHY_W1M_INT_EN_TXCOMPLETE); + + phytium_w1m_write(w1m_data, PHY_W1M_DATA_REG, val); + phytium_w1m_write(w1m_data, PHY_W1M_CMD, 0x36); + + /* wait for the TXCOMPLETE bit */ + ret = wait_event_timeout(w1m_wait_queue, + w1m_data->w1m_irqstatus, PHY_W1M_TIMEOUT); + if (ret == 0) { + dev_err(w1m_data->dev, "TX wait elapsed\n"); + ret = -ETIMEDOUT; + goto out; + } + + *status = w1m_data->w1m_irqstatus; + /* check irqstatus */ + if (!(*status & PHY_W1M_INT_STATUS_TXCOMPLETE)) { + dev_err(w1m_data->dev, + "timeout waiting for TXCOMPLETE/RXCOMPLETE, %x", *status); + ret = -ETIMEDOUT; + } + +out: + return ret; +} + +static irqreturn_t w1m_isr(int irq, void *_w1m) +{ + struct w1m_data *w1m_data = _w1m; + unsigned long irqflags; + + spin_lock_irqsave(&w1m_data->w1m_spinlock, irqflags); + w1m_data->w1m_irqstatus = phytium_w1m_read(w1m_data, PHY_W1M_INT_STATUS_B); + spin_unlock_irqrestore(&w1m_data->w1m_spinlock, irqflags); + + phytium_w1m_write(w1m_data, PHY_W1M_INT_STATUS_B, 0x00); + if (w1m_data->w1m_irqstatus & + (PHY_W1M_INT_STATUS_TXCOMPLETE | PHY_W1M_INT_STATUS_RXCOMPLETE)) { + /* wake up sleeping process */ + wake_up(&w1m_wait_queue); + } + + return IRQ_HANDLED; +} + +static int phytium_read_byte(struct w1m_data *w1m_data, u8 *val) +{ + int ret = 0; + u8 status; + unsigned long irqflags; + + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + ret = -EINTR; + goto rtn; + } + + if (!w1m_data->w1m_usecount) { + ret = -EINVAL; + goto out; + } + + spin_lock_irqsave(&w1m_data->w1m_spinlock, irqflags); + /* clear interrupt flags via a dummy read */ + phytium_w1m_read(w1m_data, PHY_W1M_INT_STATUS_B); + /* ISR loads it with new INT_STATUS */ + w1m_data->w1m_irqstatus = 0; + spin_unlock_irqrestore(&w1m_data->w1m_spinlock, irqflags); + + phytium_w1m_merge(w1m_data, PHY_W1M_INT_EN_B, + PHY_W1M_INT_EN_RXCOMPLETE, + PHY_W1M_INT_EN_RXCOMPLETE); + + phytium_w1m_write(w1m_data, PHY_W1M_CMD, 0x3b); + + wait_event_timeout(w1m_wait_queue, + (w1m_data->w1m_irqstatus & PHY_W1M_INT_STATUS_RXCOMPLETE), + PHY_W1M_TIMEOUT); + + status = w1m_data->w1m_irqstatus; + /* check irqstatus */ + if (!(status & PHY_W1M_INT_STATUS_RXCOMPLETE)) { + dev_err(w1m_data->dev, "timeout waiting for RXCOMPLETE, %x", status); + ret = -ETIMEDOUT; + goto out; + } + + /* the data is ready. Read it in! */ + *val = phytium_w1m_read(w1m_data, PHY_W1M_DATA_REG); +out: + mutex_unlock(&w1m_data->w1m_mutex); +rtn: + return ret; +} + +static int phytium_w1m_get(struct w1m_data *w1m_data) +{ + int ret = 0; + + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + ret = -EINTR; + goto rtn; + } + + if (w1m_data->w1m_usecount == PHY_W1M_MAX_USER) { + dev_warn(w1m_data->dev, "attempt to exceed the max use count"); + ret = -EINVAL; + goto out; + } else { + w1m_data->w1m_usecount++; + try_module_get(THIS_MODULE); + if (w1m_data->w1m_usecount == 1) + pm_runtime_get_sync(w1m_data->dev); + } + +out: + mutex_unlock(&w1m_data->w1m_mutex); +rtn: + return ret; +} + +/* Disable clocks to the module */ +static int phytium_w1m_put(struct w1m_data *w1m_data) +{ + int ret = 0; + + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) + return -EINTR; + + if (w1m_data->w1m_usecount == 0) { + dev_warn(w1m_data->dev, + "attempt to decrement use count when it is zero"); + ret = -EINVAL; + } else { + w1m_data->w1m_usecount--; + module_put(THIS_MODULE); + if (w1m_data->w1m_usecount == 0) + pm_runtime_put_sync(w1m_data->dev); + } + mutex_unlock(&w1m_data->w1m_mutex); + + return ret; +} + +/* + * W1 triplet callback function - used for searching ROM addresses. + * Registered only when controller is in 1-wire mode. + */ +static u8 phytium_w1_triplet(void *_w1m, u8 bdir) +{ + u8 id_bit, comp_bit; + int err; + u8 ret = 0x3; /* no slaves responded */ + struct w1m_data *w1m_data = _w1m; + + phytium_w1m_get(_w1m); + + err = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (err < 0) { + dev_err(w1m_data->dev, "Could not acquire mutex\n"); + goto rtn; + } + + w1m_data->w1m_irqstatus = 0; + /* read id_bit */ + phytium_w1m_merge(w1m_data, PHY_W1M_INT_EN_B, + PHY_W1M_INT_EN_RXCOMPLETE, + PHY_W1M_INT_EN_RXCOMPLETE); + phytium_w1m_write(w1m_data, PHY_W1M_CMD, 0x3a); + + err = wait_event_timeout(w1m_wait_queue, + (w1m_data->w1m_irqstatus + & PHY_W1M_INT_STATUS_RXCOMPLETE), + PHY_W1M_TIMEOUT); + if (err == 0) { + dev_err(w1m_data->dev, "RX wait elapsed\n"); + goto out; + } + id_bit = (phytium_w1m_read(_w1m, PHY_W1M_DATA_REG) & 0x01); + + w1m_data->w1m_irqstatus = 0; + /* read comp_bit */ + phytium_w1m_merge(w1m_data, PHY_W1M_INT_EN_B, + PHY_W1M_INT_EN_RXCOMPLETE, + PHY_W1M_INT_EN_RXCOMPLETE); + phytium_w1m_write(w1m_data, PHY_W1M_CMD, 0x3a); + err = wait_event_timeout(w1m_wait_queue, + (w1m_data->w1m_irqstatus + & PHY_W1M_INT_STATUS_RXCOMPLETE), + PHY_W1M_TIMEOUT); + if (err == 0) { + dev_err(w1m_data->dev, "RX wait elapsed\n"); + goto out; + } + comp_bit = (phytium_w1m_read(_w1m, PHY_W1M_DATA_REG) & 0x01); + + if (id_bit && comp_bit) { + ret = 0x03; /* no slaves responded */ + goto out; + } + if (!id_bit && !comp_bit) { + /* Both bits are valid, take the direction given */ + ret = bdir ? 0x04 : 0; + } else { + /* Only one bit is valid, take that direction */ + bdir = id_bit; + ret = id_bit ? 0x05 : 0x02; + } + + w1m_data->w1m_irqstatus = 0; + /* write bdir bit */ + phytium_w1m_merge(w1m_data, PHY_W1M_INT_EN_B, + PHY_W1M_INT_EN_TXCOMPLETE, + PHY_W1M_INT_EN_TXCOMPLETE); + + phytium_w1m_write(w1m_data, PHY_W1M_DATA_REG, bdir); + phytium_w1m_write(w1m_data, PHY_W1M_CMD, 0x35); + + err = wait_event_timeout(w1m_wait_queue, + (w1m_data->w1m_irqstatus + & PHY_W1M_INT_STATUS_TXCOMPLETE), + PHY_W1M_TIMEOUT); + if (err == 0) { + dev_err(w1m_data->dev, "TX wait elapsed\n"); + goto out; + } + +out: + mutex_unlock(&w1m_data->w1m_mutex); +rtn: + phytium_w1m_put(_w1m); + return ret; +} + +static u8 phytium_w1_touch_bit(void *_w1m, u8 bit) +{ + u8 result = 0; + int err; + struct w1m_data *w1m_data = _w1m; + + phytium_w1m_get(w1m_data); + + w1m_data->init_trans++; + + err = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (err < 0) { + dev_err(w1m_data->dev, "Could not acquired mutex\n"); + goto rtn; + } + + w1m_data->w1m_irqstatus = 0; + + phytium_w1m_merge(w1m_data, PHY_W1M_INT_EN_B, + PHY_W1M_INT_EN_RXCOMPLETE, + PHY_W1M_INT_EN_RXCOMPLETE); + phytium_w1m_write(w1m_data, PHY_W1M_CMD, 0x3a); + + err = wait_event_timeout(w1m_wait_queue, + (w1m_data->w1m_irqstatus & PHY_W1M_INT_STATUS_RXCOMPLETE), + PHY_W1M_TIMEOUT); + if (err == 0) { + dev_info(w1m_data->dev, "Rx wait elapsed\n"); + goto out; + } + + result = (phytium_w1m_read(w1m_data, PHY_W1M_DATA_REG) & 0x01); + + w1m_data->w1m_irqstatus = 0; + +out: + mutex_unlock(&w1m_data->w1m_mutex); +rtn: + phytium_w1m_put(w1m_data); + + return result; +} + +/* reset callback */ +static u8 phytium_w1_reset_bus(void *_w1m) +{ + phytium_w1m_get(_w1m); + phytium_w1m_write(_w1m, PHY_W1M_CMD, 0x37); + mdelay(1); + phytium_w1m_put(_w1m); + return 0; +} + +/* Read a byte of data from the device */ +static u8 phytium_w1_read_byte(void *_w1m) +{ + struct w1m_data *w1m_data = _w1m; + u8 val = 0; + int ret; + + /* First write to initialize the transfer */ + if (w1m_data->init_trans == 0) + phytium_w1m_get(w1m_data); + + w1m_data->init_trans++; + ret = phytium_read_byte(w1m_data, &val); + if (ret) { + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + dev_err(w1m_data->dev, "Could not acquire mutex\n"); + return -EINTR; + } + w1m_data->init_trans = 0; + mutex_unlock(&w1m_data->w1m_mutex); + phytium_w1m_put(w1m_data); + return -1; + } + + w1m_disable_interrupt(w1m_data, PHY_W1M_INT_EN_B, + ~((u32)PHY_W1M_INT_EN_RXCOMPLETE)); + + /* Write followed by a read, release the module */ + if (w1m_data->init_trans) { + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + dev_err(w1m_data->dev, "Could not acquire mutex\n"); + return -EINTR; + } + w1m_data->init_trans = 0; + mutex_unlock(&w1m_data->w1m_mutex); + phytium_w1m_put(w1m_data); + } + + return val; +} + +/* Write a byte of data to the device */ +static void phytium_w1_write_byte(void *_w1m, u8 byte) +{ + struct w1m_data *w1m_data = _w1m; + int ret; + u8 status; + + /* First write to initialize the transfer */ + if (w1m_data->init_trans == 0) + phytium_w1m_get(w1m_data); + + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + dev_err(w1m_data->dev, "Could not acquire mutex\n"); + return; + } + mutex_unlock(&w1m_data->w1m_mutex); + + ret = phytium_write_byte(w1m_data, byte, &status); + if (ret < 0) { + dev_err(w1m_data->dev, "TX failure:Ctrl status %x\n", status); + return; + } + + w1m_data->init_trans++; + w1m_disable_interrupt(w1m_data, PHY_W1M_INT_EN_B, + ~((u32)PHY_W1M_INT_EN_TXCOMPLETE)); + /* Second write, data transferred. Release the module */ + if (w1m_data->init_trans > 1) { + phytium_w1m_put(w1m_data); + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + dev_err(w1m_data->dev, "Could not acquire mutex\n"); + return; + } + w1m_data->init_trans = 0; + mutex_unlock(&w1m_data->w1m_mutex); + } +} + +static struct w1_bus_master phytium_w1_master = { + .read_byte = phytium_w1_read_byte, + .write_byte = phytium_w1_write_byte, + .reset_bus = phytium_w1_reset_bus, + .touch_bit = phytium_w1_touch_bit, +}; + +static int phytium_w1m_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct w1m_data *w1m_data; + struct resource *res; + int ret, irq; + + w1m_data = devm_kzalloc(dev, sizeof(*w1m_data), GFP_KERNEL); + if (!w1m_data) + return -ENOMEM; + + w1m_data->dev = dev; + platform_set_drvdata(pdev, w1m_data); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + w1m_data->w1m_base = devm_ioremap_resource(dev, res); + if (IS_ERR(w1m_data->w1m_base)) + return PTR_ERR(w1m_data->w1m_base); + + w1m_data->w1m_usecount = 0; + mutex_init(&w1m_data->w1m_mutex); + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "pm_runtime_get_sync failed\n"); + goto err_w1; + } + + spin_lock_init(&w1m_data->w1m_spinlock); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ: %d\n", irq); + ret = irq; + goto err_irq; + } + + ret = devm_request_irq(dev, irq, w1m_isr, 0, "phytium-w1", w1m_data); + if (ret < 0) { + dev_err(&pdev->dev, "could not request irq\n"); + goto err_irq; + } + + pm_runtime_put_sync(&pdev->dev); + + phytium_w1_master.triplet = phytium_w1_triplet; + phytium_w1m_write(w1m_data, PHY_W1M_CTL, 0x10); + phytium_w1m_write(w1m_data, PHY_W1M_INT_EN_B, 0x00); + + phytium_w1_master.data = w1m_data; + + ret = w1_add_master_device(&phytium_w1_master); + if (ret) { + dev_err(&pdev->dev, "Failure in registering w1 master\n"); + goto err_w1; + } + + return 0; + +err_irq: + pm_runtime_put_sync(&pdev->dev); +err_w1: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int phytium_w1m_remove(struct platform_device *pdev) +{ + struct w1m_data *w1m_data = platform_get_drvdata(pdev); + + mutex_lock(&w1m_data->w1m_mutex); + + if (w1m_data->w1m_usecount) { + dev_warn(&pdev->dev, "removed when use count is not zero\n"); + mutex_unlock(&w1m_data->w1m_mutex); + return -EBUSY; + } + + mutex_unlock(&w1m_data->w1m_mutex); + + /* remove module dependency */ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id phytium_w1m_dt_ids[] = { + { .compatible = "phytium,w1" }, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_w1m_dt_ids); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_w1m_acpi_ids[] = { + { "PHYT0034", 0 }, + { } +}; +#endif + +static struct platform_driver phytium_w1m_driver = { + .probe = phytium_w1m_probe, + .remove = phytium_w1m_remove, + .driver = { + .name = "phytium-w1", + .of_match_table = phytium_w1m_dt_ids, + .acpi_match_table = ACPI_PTR(phytium_w1m_acpi_ids), + }, +}; +module_platform_driver(phytium_w1m_driver); + +MODULE_AUTHOR("Zhu Mingshuai "); +MODULE_DESCRIPTION("Phytium w1 bus master driver"); +MODULE_LICENSE("GPL"); diff --git a/include/acpi/actbl2.h b/include/acpi/actbl2.h index 5c0bea9136a618d2c90c044ef06eb87000f19237..2b4dd2c3348f313c2edf23daee59a0820667008e 100644 --- a/include/acpi/actbl2.h +++ b/include/acpi/actbl2.h @@ -897,8 +897,7 @@ enum acpi_madt_type { ACPI_MADT_TYPE_APLIC = 26, ACPI_MADT_TYPE_PLIC = 27, ACPI_MADT_TYPE_RESERVED = 28, /* 28 to 0x7F are reserved */ - ACPI_MADT_TYPE_OEM_RESERVED = 0x80, /* 0x80 to 0xFF are reserved for OEM use */ - ACPI_MADT_TYPE_PHYTIUM_2500 = 128 + ACPI_MADT_TYPE_OEM_RESERVED = 0x80 /* 0x80 to 0xFF are reserved for OEM use */ }; /* diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h index 837bad9f142ef403e0ec0e26c461c8b8d002d321..58e778d6c429975817b51a41864424abd3428d8c 100644 --- a/include/linux/cpuhotplug.h +++ b/include/linux/cpuhotplug.h @@ -151,6 +151,10 @@ enum cpuhp_state { CPUHP_AP_IRQ_MIPS_GIC_STARTING, CPUHP_AP_IRQ_RISCV_STARTING, CPUHP_AP_IRQ_LOONGARCH_STARTING, +#ifdef CONFIG_LOONGARCH + CPUHP_AP_IRQ_EIOINTC_STARTING, + CPUHP_AP_IRQ_AVECINTC_STARTING, +#endif CPUHP_AP_IRQ_SIFIVE_PLIC_STARTING, CPUHP_AP_ARM_MVEBU_COHERENCY, CPUHP_AP_MICROCODE_LOADER, diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 0b52da4f23467829fefc67672441ff45e3fb827f..c55be28cf0918d210d3435915948f0125fc02c8e 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -182,6 +182,7 @@ struct i3c_dev_boardinfo { u8 static_addr; u64 pid; struct device_node *of_node; + struct fwnode_handle *fwnode; }; /** diff --git a/include/linux/irqchip/arm-gic-phytium-2500.h b/include/linux/irqchip/arm-gic-phytium-2500.h index f212a29390bf654941489686151e7b1e08ff2bf2..3688d93a5c3e2f791871afb92efd5eb3dba4b180 100644 --- a/include/linux/irqchip/arm-gic-phytium-2500.h +++ b/include/linux/irqchip/arm-gic-phytium-2500.h @@ -1,8 +1,8 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ +/* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (C) 2013, 2014 ARM Limited, All Rights Reserved. - * Author: Marc Zyngier + * Copyright (C) 2020-2023, Phytium Technology Co., Ltd */ + #ifndef __LINUX_IRQCHIP_ARM_GIC_PHYTIUM_2500_H #define __LINUX_IRQCHIP_ARM_GIC_PHYTIUM_2500_H @@ -635,7 +635,7 @@ struct rdists { struct irq_domain; struct fwnode_handle; -int __init its_lpi_memreserve_init(void); +int __init phytium_its_lpi_memreserve_init(void); int phytium_its_cpu_init(void); int phytium_its_init(struct fwnode_handle *handle, struct rdists *rdists, struct irq_domain *domain); diff --git a/include/linux/mtd/partitions.h b/include/linux/mtd/partitions.h index b74a539ec58190b5231e3dd013087cee7613bbc9..73f16747d9bcc7a5d86ecfea6997e1608019250a 100644 --- a/include/linux/mtd/partitions.h +++ b/include/linux/mtd/partitions.h @@ -51,6 +51,7 @@ struct mtd_partition { uint32_t mask_flags; /* master MTD flags to mask out for this partition */ uint32_t add_flags; /* flags to add to the partition */ struct device_node *of_node; + struct fwnode_handle *fwnode; }; #define MTDPART_OFS_RETAIN (-3) @@ -61,6 +62,8 @@ struct mtd_partition { struct mtd_info; struct device_node; +struct acpi_device; +struct hwnode_handle; /** * struct mtd_part_parser_data - used to pass data to MTD partition parsers. @@ -80,6 +83,7 @@ struct mtd_part_parser { struct module *owner; const char *name; const struct of_device_id *of_match_table; + const struct acpi_device_id *acpi_match_table; int (*parse_fn)(struct mtd_info *, const struct mtd_partition **, struct mtd_part_parser_data *); void (*cleanup)(const struct mtd_partition *pparts, int nr_parts); diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 101183b8d3bcd897191fcdae216bbc624248d488..21eb2ab900800f5a5ce55f053ffd1531e8aa505e 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -348,8 +348,9 @@ struct hdac_bus { bool corbrp_self_clear:1; /* CORBRP clears itself after reset */ bool polling_mode:1; bool needs_damn_long_delay:1; - bool not_use_interrupts:1; /* prohibiting the RIRB IRQ */ - bool access_sdnctl_in_dword:1; /* accessing the sdnctl register by dword */ + bool not_use_interrupts:1; /* prohibiting the RIRB IRQ */ + bool access_sdnctl_in_dword:1; /* accessing the sdnctl register by dword */ + bool cmd_resend; /* command resend */ bool hygon_dword_access:1; int poll_count; diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index add349889d0a391488dadcb936abf3ba5a9cee77..3db2a19dfcf9bb917c2b91779a187dcf2a9d2c89 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -245,4 +245,7 @@ /* Sunplus UART */ #define PORT_SUNPLUS 123 +/* Phytium PCI UART */ +#define PORT_PHYTIUM 124 + #endif /* _UAPILINUX_SERIAL_CORE_H */ diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c index df37a85cf27cc79d938406c2b54a778d3945b66f..3bb2969e4e33491e233ef823fe2a843dd254d8a0 100644 --- a/sound/hda/hdac_controller.c +++ b/sound/hda/hdac_controller.c @@ -146,6 +146,9 @@ int snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val) { unsigned int addr = azx_command_addr(val); unsigned int wp, rp; + unsigned long timeout; + unsigned int rirb_wp; + int i = 0; spin_lock_irq(&bus->reg_lock); @@ -172,6 +175,42 @@ int snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val) bus->corb.buf[wp] = cpu_to_le32(val); snd_hdac_chip_writew(bus, CORBWP, wp); + if (bus->cmd_resend) { + timeout = jiffies + msecs_to_jiffies(1000); + udelay(80); + rirb_wp = snd_hdac_chip_readw(bus, RIRBWP); + while (rirb_wp == bus->rirb.wp) { + udelay(80); + rirb_wp = snd_hdac_chip_readw(bus, RIRBWP); + if (rirb_wp != bus->rirb.wp) + break; + if (i > 5) + break; + if (time_after(jiffies, timeout)) + break; + + /* add command to corb */ + wp = snd_hdac_chip_readw(bus, CORBWP); + if (wp == 0xffff) { + /* something wrong, controller likely turned to D3 */ + spin_unlock_irq(&bus->reg_lock); + return -EIO; + } + wp++; + wp %= AZX_MAX_CORB_ENTRIES; + + rp = snd_hdac_chip_readw(bus, CORBRP); + if (wp == rp) { + /* oops, it's full */ + spin_unlock_irq(&bus->reg_lock); + return -EAGAIN; + } + bus->corb.buf[wp] = cpu_to_le32(val); + snd_hdac_chip_writew(bus, CORBWP, wp); + i++; + } + } + spin_unlock_irq(&bus->reg_lock); return 0; diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c index 2312266939b2121b824b6d7bd612a13c6d5e8740..03a7ee03f6a92c19685661c8c466b3037653ed0b 100644 --- a/sound/hda/hdac_stream.c +++ b/sound/hda/hdac_stream.c @@ -134,7 +134,11 @@ void snd_hdac_stream_start(struct hdac_stream *azx_dev) trace_snd_hdac_stream_start(bus, azx_dev); +#ifdef CONFIG_SND_HDA_PHYTIUM + azx_dev->start_wallclk = snd_hdac_chip_readl(bus, WALLCLK) / 15; +#else azx_dev->start_wallclk = snd_hdac_chip_readl(bus, WALLCLK); +#endif /* enable SIE */ snd_hdac_chip_updatel(bus, INTCTL, @@ -632,7 +636,11 @@ static u64 azx_cc_read(const struct cyclecounter *cc) { struct hdac_stream *azx_dev = container_of(cc, struct hdac_stream, cc); +#ifdef CONFIG_SND_HDA_PHYTIUM + return snd_hdac_chip_readl(azx_dev->bus, WALLCLK) / 25; +#else return snd_hdac_chip_readl(azx_dev->bus, WALLCLK); +#endif } static void azx_timecounter_init(struct hdac_stream *azx_dev, diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index 9698ebe3fbc2e7e3b9218e7c44e313dd99aac73d..445663c2a51ef9ebcb5cc5ed4bf77d88e20a258d 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -26,6 +26,22 @@ config SND_HDA_INTEL To compile this driver as a module, choose M here: the module will be called snd-hda-intel. +config SND_HDA_PHYTIUM + tristate "PHYTIUM HD Audio" + depends on SOUND + select SND_HDA + select SND_HDA_ALIGNED_MMIO + help + Say Y here to support the HDA controller present in PHYTIUM + SoCs + + This options enables support for the HD Audio controller + present in some PHYTIUM SoCs, used to communicate audio + to the "High Definition Audio" codec. + + To compile this driver as a module, choose M here: the module + will be called snd-hda-phytium. + config SND_HDA_TEGRA tristate "NVIDIA Tegra HD Audio" depends on ARCH_TEGRA diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index f00fc9ed6096314f90ccc45797b2078c8e0fd9ec..5671283da45d72c7d06fac5f6585adb6e97d2679 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 snd-hda-intel-objs := hda_intel.o snd-hda-tegra-objs := hda_tegra.o +snd-hda-phytium-objs := hda_phytium.o snd-hda-codec-y := hda_bind.o hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o snd-hda-codec-y += hda_controller.o @@ -70,3 +71,4 @@ obj-$(CONFIG_SND_HDA_SCODEC_TAS2781_I2C) += snd-hda-scodec-tas2781-i2c.o # when built in kernel obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o obj-$(CONFIG_SND_HDA_TEGRA) += snd-hda-tegra.o +obj-$(CONFIG_SND_HDA_PHYTIUM) += snd-hda-phytium.o diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c index 688eddb8942d91dc51f425b6d0b891f292eb7c14..1dd7504d4daf0eea57277093dde06e9d09cb7a55 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -16,6 +16,8 @@ #include #include #include +#include +#include "hda_phytium.h" #ifdef CONFIG_X86 /* for art-tsc conversion */ @@ -25,6 +27,7 @@ #include #include #include "hda_controller.h" +#include "hda_intel.h" #include "hda_local.h" #define CREATE_TRACE_POINTS @@ -80,6 +83,104 @@ static u64 azx_adjust_codec_delay(struct snd_pcm_substream *substream, return (nsec > codec_nsecs) ? nsec - codec_nsecs : 0; } +int gf_setup_bdle(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + __le32 *bdl; + unsigned int i, stream_idx; + struct gf_private *gf_chip = NULL; + + // setup BDL and BDLE + if ((chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + stream_idx = apcm->codec->addr - 1; + if ((stream_idx <= 1) && (gf_chip->diu_fb_bdl_vaddr[stream_idx])) { + if (azx_dev->core.bdl.bytes <= BDL_SIZE) { + memcpy(gf_chip->diu_fb_bdl_vaddr[stream_idx], azx_dev->core.bdl.area, azx_dev->core.bdl.bytes); + } else { + memcpy(gf_chip->diu_fb_bdl_vaddr[stream_idx], azx_dev->core.bdl.area, BDL_SIZE); + } + bdl = (__le32 *)gf_chip->diu_fb_bdl_vaddr[stream_idx]; + for (i = 0; i < azx_dev->core.frags; i++) { + if (i > 0) { + bdl[i*4] = cpu_to_le32((u32)(bdl[(i-1)*4] + bdl[(i-1)*4+2])); + bdl[i*4+1] = cpu_to_le32(upper_32_bits(gf_chip->diu_fb_stream_ofs[stream_idx])); + } + else { + bdl[i*4] = cpu_to_le32((u32)gf_chip->diu_fb_stream_ofs[stream_idx]); + bdl[i*4+1] = cpu_to_le32(upper_32_bits(gf_chip->diu_fb_stream_ofs[stream_idx])); + } + } + snd_hdac_stream_writel((azx_stream(azx_dev)), SD_BDLPL, (u32)gf_chip->diu_fb_bdl_ofs[stream_idx]); + snd_hdac_stream_writel((azx_stream(azx_dev)), SD_BDLPU, upper_32_bits(gf_chip->diu_fb_bdl_ofs[stream_idx])); + } + } + return 0; +} + +int gf_pre_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + unsigned int stream_idx; + struct gf_private *gf_chip = NULL; + + if ((substream->runtime) && (chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + stream_idx = apcm->codec->addr - 1; + if ((cmd == SNDRV_PCM_TRIGGER_START) && + (stream_idx <= 1) && (gf_chip->diu_fb_stream_vaddr[stream_idx]) && (substream->runtime->dma_area)) { + memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, substream->runtime->dma_bytes); + gf_chip->diu_fb_stream_pos[stream_idx] = 0; + } + } + return 0; +} + +int gf_update_stream(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + unsigned int stream_idx, hw_pos, appl_pos; + struct gf_private *gf_chip = NULL; + + if ((substream->runtime) && (chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + stream_idx = apcm->codec->addr - 1; + if ((stream_idx <= 1) && (gf_chip->diu_fb_stream_vaddr[stream_idx]) && (substream->runtime->dma_area) && + (substream->runtime->dma_bytes <= GF_HDA_FB_STREAM_SIZE) && snd_pcm_running(substream)) { + hw_pos = frames_to_bytes(substream->runtime, substream->runtime->status->hw_ptr % substream->runtime->buffer_size); + appl_pos = frames_to_bytes(substream->runtime, substream->runtime->control->appl_ptr % substream->runtime->buffer_size); + + if (hw_pos == appl_pos) { + memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, substream->runtime->dma_bytes); + } + else if (appl_pos > gf_chip->diu_fb_stream_pos[stream_idx]) { + memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx] + gf_chip->diu_fb_stream_pos[stream_idx], substream->runtime->dma_area + gf_chip->diu_fb_stream_pos[stream_idx], (appl_pos - gf_chip->diu_fb_stream_pos[stream_idx])); + } + else if (appl_pos < gf_chip->diu_fb_stream_pos[stream_idx]) { + if(substream->runtime->dma_bytes > gf_chip->diu_fb_stream_pos[stream_idx]) { + memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx] + gf_chip->diu_fb_stream_pos[stream_idx], substream->runtime->dma_area + gf_chip->diu_fb_stream_pos[stream_idx], (substream->runtime->dma_bytes - gf_chip->diu_fb_stream_pos[stream_idx])); + } + memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, appl_pos); + } + gf_chip->diu_fb_stream_pos[stream_idx] = appl_pos; + } + } + return 0; +} + /* * PCM ops */ @@ -156,6 +257,9 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) struct hda_spdif_out *spdif = snd_hda_spdif_out_of_nid(apcm->codec, hinfo->nid); unsigned short ctls = spdif ? spdif->ctls : 0; + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + + hda->substream = substream; trace_azx_pcm_prepare(chip, azx_dev); dsp_lock(azx_dev); @@ -184,6 +288,8 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) snd_hdac_stream_setup(azx_stream(azx_dev)); + gf_setup_bdle(substream); + stream_tag = azx_dev->core.stream_tag; /* CA-IBG chips need the playback stream starting from 1 */ if ((chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND) && @@ -238,6 +344,8 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) return -EINVAL; } + gf_pre_trigger(substream, cmd); + snd_pcm_group_for_each_entry(s, substream) { if (s->pcm->card != substream->pcm->card) continue; @@ -325,6 +433,9 @@ static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream) struct azx_pcm *apcm = snd_pcm_substream_chip(substream); struct azx *chip = apcm->chip; struct azx_dev *azx_dev = get_azx_dev(substream); + + gf_update_stream(substream); + return bytes_to_frames(substream->runtime, azx_get_position(chip, azx_dev)); } diff --git a/sound/pci/hda/hda_controller.h b/sound/pci/hda/hda_controller.h index fbab8dda329e38ae3855d4fc88e2e6b1ccee9d5a..ec57bed32283c27c5990980c48e27828f7128d6f 100644 --- a/sound/pci/hda/hda_controller.h +++ b/sound/pci/hda/hda_controller.h @@ -17,6 +17,9 @@ #define AZX_MAX_CODECS HDA_MAX_CODECS #define AZX_DEFAULT_CODECS 4 +#define GF_HDA_PATCH_VERSION 1 +#define GF_HDA_FB_STREAM_SIZE 7*1024*1024 + /* driver quirks (capabilities) */ /* bits 0-7 are used for indicating driver type */ #define AZX_DCAPS_NO_TCSEL (1 << 8) /* No Intel TCSEL bit */ diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 30499bff36216249044705811f62fd77a0d01120..0462eb22cfd96c709ba5b1900592392f32c69664 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -400,6 +400,84 @@ static void azx_free_pci_zx(struct azx *chip) iounmap(chip->remap_diu_addr); } +static int gf_init_pci(struct azx *chip) +{ + struct pci_dev *diu_pci = NULL; + unsigned long fb_size; + phys_addr_t diu_fb_base; + phys_addr_t diu_fb_stream[2]; + phys_addr_t diu_fb_bdl[2]; + struct gf_private *gf_chip = NULL; + + dev_info(chip->card->dev, "gf_hda patch version %03d \n", GF_HDA_PATCH_VERSION); + + if ((chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + gf_chip->diu_fb_stream_vaddr[0] = NULL; + gf_chip->diu_fb_stream_vaddr[1] = NULL; + gf_chip->diu_fb_bdl_vaddr[0] = NULL; + gf_chip->diu_fb_bdl_vaddr[1] = NULL; + + if (chip->pci->bus != NULL) + diu_pci = pci_get_slot(chip->pci->bus, PCI_DEVFN(PCI_SLOT(chip->pci->devfn), 0)); + + if (!diu_pci) { + dev_info(chip->card->dev, "gf_hda can't get display device\n"); + } + else { + diu_fb_base = pci_resource_start(diu_pci, 1); + fb_size = pci_resource_len(diu_pci, 1); + + diu_fb_stream[0] = diu_fb_base + fb_size - (4+16)*1024*1024; + gf_chip->diu_fb_stream_ofs[0] = diu_fb_stream[0] - diu_fb_base; // stream offset = fb_size -4M-16M + gf_chip->diu_fb_stream_vaddr[0] = ioremap_wc(diu_fb_stream[0], GF_HDA_FB_STREAM_SIZE); // size = 7M + + diu_fb_stream[1] = diu_fb_stream[0] + GF_HDA_FB_STREAM_SIZE; + gf_chip->diu_fb_stream_ofs[1] = diu_fb_stream[1] - diu_fb_base; // stream offset = fb_size -4M-16M+7M + gf_chip->diu_fb_stream_vaddr[1] = ioremap_wc(diu_fb_stream[1], GF_HDA_FB_STREAM_SIZE); // size = 7M + + diu_fb_bdl[0] = diu_fb_stream[1] + GF_HDA_FB_STREAM_SIZE; + gf_chip->diu_fb_bdl_ofs[0] = diu_fb_bdl[0] - diu_fb_base; // stream offset = fb_size -4M-16M+7M*2 + gf_chip->diu_fb_bdl_vaddr[0] = ioremap_wc(diu_fb_bdl[0], BDL_SIZE); // size = 4K + + diu_fb_bdl[1] = diu_fb_bdl[0] + BDL_SIZE; + gf_chip->diu_fb_bdl_ofs[1] = diu_fb_bdl[1] - diu_fb_base; // stream offset = fb_size -4M-16M+7M*2+4K + gf_chip->diu_fb_bdl_vaddr[1] = ioremap_wc(diu_fb_bdl[1], BDL_SIZE); // size = 4K + + dev_info(chip->card->dev, "gf_hda diu fb base=0x%llx, size=%dM.\n", diu_fb_base, (unsigned int)(fb_size >> 20)); + pci_dev_put(diu_pci); + } + } + return 0; +} + +static void gf_free_pci(struct azx *chip) +{ + struct gf_private *gf_chip = NULL; + + if ((chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return; + } + + if(gf_chip->diu_fb_stream_vaddr[0]) + iounmap(gf_chip->diu_fb_stream_vaddr[0]); + + if(gf_chip->diu_fb_stream_vaddr[1]) + iounmap(gf_chip->diu_fb_stream_vaddr[1]); + + if(gf_chip->diu_fb_bdl_vaddr[0]) + iounmap(gf_chip->diu_fb_bdl_vaddr[0]); + + if(gf_chip->diu_fb_bdl_vaddr[1]) + iounmap(gf_chip->diu_fb_bdl_vaddr[1]); + } +} + static void azx_init_pci(struct azx *chip) { int snoop_type = azx_get_snoop_type(chip); @@ -1408,6 +1486,10 @@ static void azx_free(struct azx *chip) if (bus->irq >= 0) free_irq(bus->irq, (void*)chip); + if (chip->driver_type == AZX_DRIVER_GFHDMI) { + gf_free_pci(chip); + } + azx_free_stream_pages(chip); azx_free_streams(chip); snd_hdac_bus_exit(bus); @@ -1823,7 +1905,12 @@ static int azx_create(struct snd_card *card, struct pci_dev *pci, if (err < 0) return err; - hda = devm_kzalloc(&pci->dev, sizeof(*hda), GFP_KERNEL); + if ((pci != NULL) && (pci->vendor == 0x6766) && (pci->device == 0x3d40)) { + hda = devm_kzalloc(&pci->dev, sizeof(struct gf_private), GFP_KERNEL); + } + else { + hda = devm_kzalloc(&pci->dev, sizeof(*hda), GFP_KERNEL); + } if (!hda) return -ENOMEM; @@ -1911,8 +1998,10 @@ static int azx_first_init(struct azx *chip) * Fix response write request not synced to memory when handle * hdac interrupt on Glenfly Gpus */ - if (chip->driver_type == AZX_DRIVER_GFHDMI) + if (chip->driver_type == AZX_DRIVER_GFHDMI) { bus->polling_mode = 1; + gf_init_pci(chip); + } if (chip->driver_type == AZX_DRIVER_LOONGSON) { bus->polling_mode = 1; diff --git a/sound/pci/hda/hda_intel.h b/sound/pci/hda/hda_intel.h index 0f39418f9328b08e7c34c2db540eed30f734100c..fbad365c7eab9f038993c01e674a6cc9ed647522 100644 --- a/sound/pci/hda/hda_intel.h +++ b/sound/pci/hda/hda_intel.h @@ -34,4 +34,15 @@ struct hda_intel { int probe_retry; /* being probe-retry */ }; +struct gf_private { + struct hda_intel hda; + + phys_addr_t diu_fb_stream_ofs[2]; + void __iomem *diu_fb_stream_vaddr[2]; + unsigned int diu_fb_stream_pos[2]; + + phys_addr_t diu_fb_bdl_ofs[2]; + void __iomem *diu_fb_bdl_vaddr[2]; +}; + #endif diff --git a/sound/pci/hda/hda_phytium.c b/sound/pci/hda/hda_phytium.c new file mode 100644 index 0000000000000000000000000000000000000000..652163f86ba5312b1ef25b95df563cec6e3ad6cd --- /dev/null +++ b/sound/pci/hda/hda_phytium.c @@ -0,0 +1,1086 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of primary ALSA driver code for Phytium HD Audio. + * + * Copyright(c) 2018-2022, Phytium Technology Co., Ltd. + */ + +#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 "hda_controller.h" +#include "hda_phytium.h" + +#include "hda_intel_trace.h" + +/* position fix mode */ +enum { + POS_FIX_AUTO, + POS_FIX_LPIB, + POS_FIX_POSBUF, + POS_FIX_VIACOMBO, + POS_FIX_COMBO, +}; + +/* Define IN stream 0 FIFO size offset in VIA controller */ +#define VIA_IN_STREAM0_FIFO_SIZE_OFFSET 0x90 + +/* FT have 4 playback and 4 capture */ +#define FT4C_NUM_CAPTURE 4 +#define FT4C_NUM_PLAYBACK 4 + +#define DWORD_BYTE_WIDTH 4 +#define BYTE_BIT_WIDTH 8 + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static char *model[SNDRV_CARDS]; +static int position_fix[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1}; +static int bdl_pos_adj[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; +static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; +static int probe_only[SNDRV_CARDS]; +static int jackpoll_ms[SNDRV_CARDS]; +static int single_cmd = -1; +static int enable_msi = -1; +#ifdef CONFIG_SND_HDA_INPUT_BEEP +static bool beep_mode[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = + CONFIG_SND_HDA_INPUT_BEEP_MODE}; +#endif + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Intel HD audio interface."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Intel HD audio interface."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Intel HD audio interface."); +module_param_array(model, charp, NULL, 0444); +MODULE_PARM_DESC(model, "Use the given board model."); +module_param_array(position_fix, int, NULL, 0444); +MODULE_PARM_DESC(position_fix, "DMA pointer read method. (-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO)."); +module_param_array(bdl_pos_adj, int, NULL, 0644); +MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset."); +module_param_array(probe_mask, int, NULL, 0444); +MODULE_PARM_DESC(probe_mask, "Bitmask to probe codecs (default = -1)."); +module_param_array(probe_only, int, NULL, 0444); +MODULE_PARM_DESC(probe_only, "Only probing and no codec initialization."); +module_param_array(jackpoll_ms, int, NULL, 0444); +MODULE_PARM_DESC(jackpoll_ms, "Ms between polling for jack events (default = 0, using unsol events only)"); +module_param(single_cmd, bint, 0444); +MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs (for debugging only)."); +module_param(enable_msi, bint, 0444); +MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)"); +#ifdef CONFIG_SND_HDA_INPUT_BEEP +module_param_array(beep_mode, bool, NULL, 0444); +MODULE_PARM_DESC(beep_mode, "Select HDA Beep registration mode (0=off, 1=on) (default=1)."); +#endif + +#define power_save 0 + +static int align_buffer_size = -1; +module_param(align_buffer_size, bint, 0644); +MODULE_PARM_DESC(align_buffer_size, + "Force buffer and period sizes to be multiple of 128 bytes."); + +/* driver types */ +enum { + AZX_DRIVER_ICH, + AZX_DRIVER_PCH, + AZX_DRIVER_SCH, + AZX_DRIVER_HDMI, + AZX_DRIVER_ATI, + AZX_DRIVER_ATIHDMI, + AZX_DRIVER_ATIHDMI_NS, + AZX_DRIVER_VIA, + AZX_DRIVER_SIS, + AZX_DRIVER_ULI, + AZX_DRIVER_NVIDIA, + AZX_DRIVER_TERA, + AZX_DRIVER_CTX, + AZX_DRIVER_CTHDA, + AZX_DRIVER_CMEDIA, + AZX_DRIVER_GENERIC, + AZX_DRIVER_FT, + AZX_NUM_DRIVERS, /* keep this as last entry */ +}; + +/* NOP for other archs */ +static inline void mark_pages_wc(struct azx *chip, struct snd_dma_buffer *buf, + bool on) +{ +} + +static inline void mark_runtime_wc(struct azx *chip, struct azx_dev *azx_dev, + struct snd_pcm_substream *substream, bool on) +{ +} + +static int azx_acquire_irq(struct azx *chip, int do_disconnect); + +/* calculate runtime delay from LPIB */ +static int azx_get_delay_from_lpib(struct azx *chip, struct azx_dev *azx_dev, + unsigned int pos) +{ + struct snd_pcm_substream *substream = azx_dev->core.substream; + int stream = substream->stream; + unsigned int lpib_pos = azx_get_pos_lpib(chip, azx_dev); + int delay; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = pos - lpib_pos; + else + delay = lpib_pos - pos; + if (delay < 0) { + if (delay >= azx_dev->core.delay_negative_threshold) + delay = 0; + else + delay += azx_dev->core.bufsize; + } + + if (delay >= azx_dev->core.period_bytes) { + dev_info(chip->card->dev, + "Unstable LPIB (%d >= %d); disabling LPIB delay counting\n", + delay, azx_dev->core.period_bytes); + delay = 0; + chip->driver_caps &= ~AZX_DCAPS_COUNT_LPIB_DELAY; + chip->get_delay[stream] = NULL; + } + + return bytes_to_frames(substream->runtime, delay); +} + +static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev); + +/* called from IRQ */ +static int azx_position_check(struct azx *chip, struct azx_dev *azx_dev) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + int ok; + + ok = azx_position_ok(chip, azx_dev); + if (ok == 1) { + azx_dev->irq_pending = 0; + return ok; + } else if (ok == 0) { + /* bogus IRQ, process it later */ + azx_dev->irq_pending = 1; + schedule_work(&hda->irq_pending_work); + } + return 0; +} + +static int azx_ft_link_power(struct azx *chip, bool enable) +{ + return 0; +} + +/* + * Check whether the current DMA position is acceptable for updating + * periods. Returns non-zero if it's OK. + * + * Many HD-audio controllers appear pretty inaccurate about + * the update-IRQ timing. The IRQ is issued before actually the + * data is processed. So, we need to process it afterwords in a + * workqueue. + */ +static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) +{ + struct snd_pcm_substream *substream = azx_dev->core.substream; + int stream = substream->stream; + u32 wallclk; + unsigned int pos; + + wallclk = (azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk); + + if (wallclk < (azx_dev->core.period_wallclk * 2) / 3) + return -1; /* bogus (too early) interrupt */ + + if (chip->get_position[stream]) + pos = chip->get_position[stream](chip, azx_dev); + else { /* use the position buffer as default */ + pos = azx_get_pos_posbuf(chip, azx_dev); + if (!pos || pos == (u32)-1) { + dev_info(chip->card->dev, + "Invalid position buffer, using LPIB read method instead.\n"); + chip->get_position[stream] = azx_get_pos_lpib; + if (chip->get_position[0] == azx_get_pos_lpib && + chip->get_position[1] == azx_get_pos_lpib) + azx_bus(chip)->use_posbuf = false; + pos = azx_get_pos_lpib(chip, azx_dev); + chip->get_delay[stream] = NULL; + } else { + chip->get_position[stream] = azx_get_pos_posbuf; + if (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY) + chip->get_delay[stream] = azx_get_delay_from_lpib; + } + } + + if (pos >= azx_dev->core.bufsize) + pos = 0; + + if (WARN_ONCE(!azx_dev->core.period_bytes, + "hda-ft: zero azx_dev->period_bytes")) + return -1; /* this shouldn't happen! */ + if (wallclk < (azx_dev->core.period_wallclk * 5) / 4 && + pos % azx_dev->core.period_bytes > azx_dev->core.period_bytes / 2) + /* NG - it's below the first next period boundary */ + return chip->bdl_pos_adj ? 0 : -1; + + azx_dev->core.start_wallclk += wallclk; + + return 1; /* OK, it's fine */ +} + +/* The work for pending PCM period updates. */ +static void azx_irq_pending_work(struct work_struct *work) +{ + struct hda_ft *hda = container_of(work, struct hda_ft, irq_pending_work); + struct azx *chip = &hda->chip; + struct hdac_bus *bus = azx_bus(chip); + struct hdac_stream *s; + int pending, ok; + + if (!hda->irq_pending_warned) { + dev_info(chip->card->dev, + "IRQ timing workaround is activated for card #%d. Suggest a bigger bdl_pos_adj.\n", + chip->card->number); + hda->irq_pending_warned = 1; + } + + for (;;) { + pending = 0; + spin_lock_irq(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + struct azx_dev *azx_dev = stream_to_azx_dev(s); + + if (!azx_dev->irq_pending || + !s->substream || !s->running) + continue; + ok = azx_position_ok(chip, azx_dev); + if (ok > 0) { + azx_dev->irq_pending = 0; + spin_unlock(&bus->reg_lock); + snd_pcm_period_elapsed(s->substream); + spin_lock(&bus->reg_lock); + } else if (ok < 0) { + pending = 0; /* too early */ + } else { + pending++; + } + } + spin_unlock_irq(&bus->reg_lock); + if (!pending) + return; + udelay(1000); + } +} + +/* clear irq_pending flags and assure no on-going workq */ +static void azx_clear_irq_pending(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + struct hdac_stream *s; + + spin_lock_irq(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + struct azx_dev *azx_dev = stream_to_azx_dev(s); + + azx_dev->irq_pending = 0; + } + spin_unlock_irq(&bus->reg_lock); +} + +static int azx_acquire_irq(struct azx *chip, int do_disconnect) +{ + struct hdac_bus *bus = azx_bus(chip); + + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct platform_device *pdev = to_platform_device(hda->dev); + int irq_id = platform_get_irq(pdev, 0); + int err; + + err = request_irq(irq_id, azx_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip); + if (err) { + dev_err(chip->card->dev, + "unable to request IRQ %d, disabling device\n", + irq_id); + if (do_disconnect) + snd_card_disconnect(chip->card); + return err; + } + bus->irq = irq_id; + + return 0; +} + +/* get the current DMA position with correction on VIA chips */ +static unsigned int azx_via_get_position(struct azx *chip, + struct azx_dev *azx_dev) +{ + unsigned int link_pos, mini_pos, bound_pos; + unsigned int mod_link_pos, mod_dma_pos, mod_mini_pos; + unsigned int fifo_size; + + link_pos = snd_hdac_stream_get_pos_lpib(azx_stream(azx_dev)); + if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Playback, no problem using link position */ + return link_pos; + } + + /* Capture */ + /* For new chipset, + * use mod to get the DMA position just like old chipset + */ + mod_dma_pos = le32_to_cpu(*azx_dev->core.posbuf); + mod_dma_pos %= azx_dev->core.period_bytes; + + /* azx_dev->fifo_size can't get FIFO size of in stream. + * Get from base address + offset. + */ + fifo_size = readw(azx_bus(chip)->remap_addr + + VIA_IN_STREAM0_FIFO_SIZE_OFFSET); + + if (azx_dev->insufficient) { + /* Link position never gather than FIFO size */ + if (link_pos <= fifo_size) + return 0; + + azx_dev->insufficient = 0; + } + + if (link_pos <= fifo_size) + mini_pos = azx_dev->core.bufsize + link_pos - fifo_size; + else + mini_pos = link_pos - fifo_size; + + /* Find nearest previous boudary */ + mod_mini_pos = mini_pos % azx_dev->core.period_bytes; + mod_link_pos = link_pos % azx_dev->core.period_bytes; + if (mod_link_pos >= fifo_size) + bound_pos = link_pos - mod_link_pos; + else if (mod_dma_pos >= mod_mini_pos) + bound_pos = mini_pos - mod_mini_pos; + else { + bound_pos = mini_pos - mod_mini_pos + azx_dev->core.period_bytes; + if (bound_pos >= azx_dev->core.bufsize) + bound_pos = 0; + } + + /* Calculate real DMA position we want */ + return bound_pos + mod_dma_pos; +} + +#ifdef CONFIG_PM +static DEFINE_MUTEX(card_list_lock); +static LIST_HEAD(card_list); + +static void azx_add_card_list(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + + mutex_lock(&card_list_lock); + list_add(&hda->list, &card_list); + mutex_unlock(&card_list_lock); +} + +static void azx_del_card_list(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + + mutex_lock(&card_list_lock); + list_del_init(&hda->list); + mutex_unlock(&card_list_lock); +} + +#else +#define azx_add_card_list(chip) /* NOP */ +#define azx_del_card_list(chip) /* NOP */ +#endif /* CONFIG_PM */ + +#if defined(CONFIG_PM_SLEEP) +/* power management */ +static int azx_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_ft *hda; + struct hdac_bus *bus; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + if (chip->disabled || !chip->running) + return 0; + + bus = azx_bus(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + azx_clear_irq_pending(chip); + azx_stop_chip(chip); + if (bus->irq >= 0) { + free_irq(bus->irq, (void *)chip); + bus->irq = -1; + } + + return 0; +} + +static int azx_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_ft *hda; + struct hdac_bus *bus; + int index; + struct snd_pcm_substream *substream; + struct azx_dev *azx_dev; + int err; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + bus = azx_bus(chip); + if (chip->disabled || !chip->running) + return 0; + + if (azx_acquire_irq(chip, 1) < 0) + return -EIO; + + index = chip->dev_index; + + snd_hdac_bus_exit_link_reset(bus); + usleep_range(1000, 1200); + + azx_init_chip(chip, 0); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + if (hda->substream && hda->substream->runtime) { + substream = hda->substream; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { + substream->runtime->status->state = + substream->runtime->status->suspended_state; + err = substream->ops->prepare(substream); + if (err < 0) + return err; + } + + azx_dev = get_azx_dev(substream); + hda->substream = NULL; + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM +static int azx_runtime_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_ft *hda; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + if (chip->disabled) + return 0; + + if (!azx_has_pm_runtime(chip)) + return 0; + + azx_stop_chip(chip); + azx_enter_link_reset(chip); + azx_clear_irq_pending(chip); + + return 0; +} + +static int azx_runtime_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_ft *hda; + struct hdac_bus *bus; + struct hda_codec *codec; + int status; + int index; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + bus = azx_bus(chip); + if (chip->disabled) + return 0; + + if (!azx_has_pm_runtime(chip)) + return 0; + + /* Read STATESTS before controller reset */ + status = azx_readw(chip, STATESTS); + + index = chip->dev_index; + + snd_hdac_bus_exit_link_reset(bus); + usleep_range(1000, 1200); + + azx_init_chip(chip, 0); + + if (status) { + list_for_each_codec(codec, &chip->bus) + if (status & (1 << codec->addr)) + schedule_delayed_work(&codec->jackpoll_work, + codec->jackpoll_interval); + } + + return 0; +} + +static int azx_runtime_idle(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_ft *hda; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + if (chip->disabled) + return 0; + + if (!azx_has_pm_runtime(chip) || + azx_bus(chip)->codec_powered || !chip->running) + return -EBUSY; + + return 0; +} + +static const struct dev_pm_ops azx_pm = { + SET_SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume) + SET_RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, azx_runtime_idle) +}; + +#define hda_ft_pm (&azx_pm) +#else +#define hda_ft_pm NULL +#endif /* CONFIG_PM */ + +static int azx_probe_continue(struct azx *chip); + +/* + * destructor + */ +static int azx_free(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct hdac_bus *bus = azx_bus(chip); + struct platform_device *pdev = to_platform_device(hda->dev); + struct device *hddev = hda->dev; + + if (azx_has_pm_runtime(chip) && chip->running) + pm_runtime_get_noresume(&pdev->dev); + + azx_del_card_list(chip); + + complete_all(&hda->probe_wait); + + if (bus->chip_init) { + azx_clear_irq_pending(chip); + azx_stop_all_streams(chip); + azx_stop_chip(chip); + } + + if (bus->irq >= 0) { + free_irq(bus->irq, (void *)chip); + bus->irq = -1; + } + + devm_iounmap(hddev, bus->remap_addr); + + azx_free_stream_pages(chip); + azx_free_streams(chip); + snd_hdac_bus_exit(bus); + + return 0; +} + +static int azx_dev_disconnect(struct snd_device *device) +{ + struct azx *chip = device->device_data; + + chip->bus.shutdown = 1; + return 0; +} + +static int azx_dev_free(struct snd_device *device) +{ + return azx_free(device->device_data); +} + +static int check_position_fix(struct azx *chip, int fix) +{ + switch (fix) { + case POS_FIX_AUTO: + case POS_FIX_LPIB: + case POS_FIX_POSBUF: + case POS_FIX_VIACOMBO: + case POS_FIX_COMBO: + return fix; + } + + if (chip->driver_caps & AZX_DCAPS_POSFIX_LPIB) { + dev_dbg(chip->card->dev, "Using LPIB position fix\n"); + return POS_FIX_LPIB; + } + return POS_FIX_AUTO; +} + +static void assign_position_fix(struct azx *chip, int fix) +{ + static azx_get_pos_callback_t callbacks[] = { + [POS_FIX_AUTO] = NULL, + [POS_FIX_LPIB] = azx_get_pos_lpib, + [POS_FIX_POSBUF] = azx_get_pos_posbuf, + [POS_FIX_VIACOMBO] = azx_via_get_position, + [POS_FIX_COMBO] = azx_get_pos_lpib, + }; + + chip->get_position[0] = chip->get_position[1] = callbacks[fix]; + + /* combo mode uses LPIB only for playback */ + if (fix == POS_FIX_COMBO) + chip->get_position[1] = NULL; + + if (fix == POS_FIX_POSBUF && + (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)) { + chip->get_delay[0] = chip->get_delay[1] = + azx_get_delay_from_lpib; + } + +} + +#define AZX_FORCE_CODEC_MASK 0x100 + +static void check_probe_mask(struct azx *chip, int dev) +{ + chip->codec_probe_mask = probe_mask[dev]; + + /* check forced option */ + if (chip->codec_probe_mask != -1 && + (chip->codec_probe_mask & AZX_FORCE_CODEC_MASK)) { + azx_bus(chip)->codec_mask = chip->codec_probe_mask & 0xff; + dev_info(chip->card->dev, "codec_mask forced to 0x%x\n", + (int)azx_bus(chip)->codec_mask); + } +} + +static void azx_probe_work(struct work_struct *work) +{ + struct hda_ft *hda = container_of(work, struct hda_ft, probe_work); + + azx_probe_continue(&hda->chip); +} + +/* + * constructor + */ +static const struct hda_controller_ops axi_hda_ops; + +static int hda_ft_create(struct snd_card *card, struct platform_device *pdev, + int dev, unsigned int driver_caps, + struct azx **rchip) +{ + static struct snd_device_ops ops = { + .dev_disconnect = azx_dev_disconnect, + .dev_free = azx_dev_free, + }; + struct hda_ft *hda; + struct azx *chip; + int err; + + *rchip = NULL; + + hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL); + if (!hda) + return -ENOMEM; + hda->dev = &pdev->dev; + chip = &hda->chip; + mutex_init(&chip->open_mutex); + chip->card = card; + chip->ops = &axi_hda_ops; + chip->driver_caps = driver_caps; + chip->driver_type = driver_caps & 0xff; + chip->dev_index = dev; + if (jackpoll_ms[dev] >= 50 && jackpoll_ms[dev] <= 60000) + chip->jackpoll_interval = msecs_to_jiffies(jackpoll_ms[dev]); + INIT_LIST_HEAD(&chip->pcm_list); + INIT_WORK(&hda->irq_pending_work, azx_irq_pending_work); + INIT_LIST_HEAD(&hda->list); + + init_completion(&hda->probe_wait); + assign_position_fix(chip, check_position_fix(chip, position_fix[dev])); + check_probe_mask(chip, dev); + + if (single_cmd < 0) /* allow fallback to single_cmd at errors */ + chip->fallback_to_single_cmd = 0; + else /* explicitly set to single_cmd or not */ + chip->single_cmd = single_cmd; + + if (bdl_pos_adj[dev] < 0) { + switch (chip->driver_type) { + case AZX_DRIVER_FT: + bdl_pos_adj[dev] = 32; + break; + default: + bdl_pos_adj[dev] = 32; + break; + } + } + chip->bdl_pos_adj = bdl_pos_adj[dev]; + + err = azx_bus_init(chip, model[dev]); + if (err < 0) { + return err; + } + + chip->bus.core.aligned_mmio = 1; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + dev_err(card->dev, "Error creating device [card]!\n"); + azx_free(chip); + return err; + } + + /* continue probing in work context as may trigger request module */ + INIT_WORK(&hda->probe_work, azx_probe_work); + + *rchip = chip; + + return 0; +} + +static int azx_first_init(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct platform_device *pdev = to_platform_device(hda->dev); + struct device *hddev = hda->dev; + + int dev = chip->dev_index; + bool full_reset; + + struct snd_card *card = chip->card; + struct hdac_bus *bus = azx_bus(chip); + int err; + unsigned short gcap; + unsigned int dma_bits = 64; + + struct resource *res; + const struct acpi_device_id *match; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hda->regs = devm_ioremap_resource(hddev, res); + if (IS_ERR(hda->regs)) + return PTR_ERR(hda->regs); + + bus->addr = res->start; + bus->remap_addr = hda->regs; + if (bus->remap_addr == NULL) { + dev_err(card->dev, "ioremap error\n"); + return -ENXIO; + } + + bus->cmd_resend = 1; + + if (azx_acquire_irq(chip, 0) < 0) + return -EBUSY; + + synchronize_irq(bus->irq); + + gcap = azx_readw(chip, GCAP); + dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap); + + /* disable 64bit DMA address on some devices */ + if (chip->driver_caps & AZX_DCAPS_NO_64BIT) { + dev_dbg(card->dev, "Disabling 64bit DMA\n"); + gcap &= ~AZX_GCAP_64OK; + } + + /* disable buffer size rounding to 128-byte multiples if supported */ + if (align_buffer_size >= 0) + chip->align_buffer_size = !!align_buffer_size; + else { + if (chip->driver_caps & AZX_DCAPS_NO_ALIGN_BUFSIZE) + chip->align_buffer_size = 0; + else + chip->align_buffer_size = 1; + } + + if (has_acpi_companion(hddev)) { + match = acpi_match_device(hddev->driver->acpi_match_table, hddev); + if (!match) { + dev_err(hddev, "Error ACPI match data is missing\n"); + return -ENODEV; + } + set_dma_ops(hddev, NULL); + acpi_dma_configure(hddev, DEV_DMA_NON_COHERENT); + } + + /* allow 64bit DMA address if supported by H/W */ + if (!(gcap & AZX_GCAP_64OK)) + dma_bits = 32; + if (!dma_set_mask(hddev, DMA_BIT_MASK(dma_bits))) { + dma_set_coherent_mask(hddev, DMA_BIT_MASK(dma_bits)); + } else { + dma_set_mask(hddev, DMA_BIT_MASK(32)); + dma_set_coherent_mask(hddev, DMA_BIT_MASK(32)); + } + + /* read number of streams from GCAP register instead of using + * hardcoded value + */ + chip->capture_streams = (gcap >> 8) & 0x0f; + chip->playback_streams = (gcap >> 12) & 0x0f; + if (!chip->playback_streams && !chip->capture_streams) { + /* gcap didn't give any info, switching to old method */ + chip->playback_streams = FT4C_NUM_PLAYBACK; + chip->capture_streams = FT4C_NUM_CAPTURE; + } + chip->capture_index_offset = 0; + chip->playback_index_offset = chip->capture_streams; + chip->num_streams = chip->playback_streams + chip->capture_streams; + + /* initialize streams */ + err = azx_init_streams(chip); + if (err < 0) + return err; + + err = azx_alloc_stream_pages(chip); + if (err < 0) + return err; + + full_reset = (probe_only[dev] & 2) ? false : true; + azx_init_chip(chip, full_reset); + + /* codec detection */ + if (!azx_bus(chip)->codec_mask) { + dev_err(card->dev, "no codecs found!\n"); + return -ENODEV; + } + + strscpy(card->driver, "ft-hda", sizeof(card->driver)); + strscpy(card->shortname, "ft-hda", sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx irq %i", + card->shortname, bus->addr, bus->irq); + + return 0; +} + + +static const struct hda_controller_ops axi_hda_ops = { + .position_check = azx_position_check, + .link_power = azx_ft_link_power, +}; + +static DECLARE_BITMAP(probed_devs, SNDRV_CARDS); + +static int hda_ft_probe(struct platform_device *pdev) +{ + const unsigned int driver_flags = AZX_DRIVER_FT; + struct snd_card *card; + struct hda_ft *hda; + struct azx *chip; + bool schedule_probe; + int err; + int dev; + + dev = find_first_zero_bit(probed_devs, SNDRV_CARDS); + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + set_bit(dev, probed_devs); + return -ENOENT; + } + + err = snd_card_new(&pdev->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { + dev_err(&pdev->dev, "Error creating card!\n"); + return err; + } + + err = hda_ft_create(card, pdev, dev, driver_flags, &chip); + if (err < 0) + goto out_free; + card->private_data = chip; + hda = container_of(chip, struct hda_ft, chip); + + dev_set_drvdata(&pdev->dev, card); + + schedule_probe = !chip->disabled; + + if (schedule_probe) + schedule_work(&hda->probe_work); + + set_bit(dev, probed_devs); + if (chip->disabled) + complete_all(&hda->probe_wait); + return 0; + +out_free: + snd_card_free(card); + return err; +} + +/* number of codec slots for each chipset: 0 = default slots (i.e. 4) */ +static unsigned int azx_max_codecs[AZX_NUM_DRIVERS] = { + [AZX_DRIVER_FT] = 4, +}; + +static int azx_probe_continue(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct device *hddev = hda->dev; + int dev = chip->dev_index; + int err; + struct hdac_bus *bus = azx_bus(chip); + + hda->probe_continued = 1; + + err = azx_first_init(chip); + if (err < 0) + goto out_free; + +#ifdef CONFIG_SND_HDA_INPUT_BEEP + chip->beep_mode = beep_mode[dev]; +#endif + + /* create codec instances */ + err = azx_probe_codecs(chip, azx_max_codecs[chip->driver_type]); + if (err < 0) + goto out_free; + + if ((probe_only[dev] & 1) == 0) { + err = azx_codec_configure(chip); + if (err < 0) + goto out_free; + } + + err = snd_card_register(chip->card); + if (err < 0) + goto out_free; + + chip->running = 1; + azx_add_card_list(chip); + snd_hda_set_power_save(&chip->bus, power_save * 1000); + + if (azx_has_pm_runtime(chip)) + pm_runtime_put_noidle(hddev); + return err; + +out_free: + if (bus->irq >= 0) { + free_irq(bus->irq, (void *)chip); + bus->irq = -1; + } + return err; +} + +static int hda_ft_remove(struct platform_device *pdev) +{ + struct snd_card *card = dev_get_drvdata(&pdev->dev); + struct azx *chip; + struct hda_ft *hda; + + if (card) { + /* cancel the pending probing work */ + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + cancel_work_sync(&hda->probe_work); + clear_bit(chip->dev_index, probed_devs); + + snd_card_free(card); + return 0; + } + return 0; +} + +static void hda_ft_shutdown(struct platform_device *pdev) +{ + struct snd_card *card = dev_get_drvdata(&pdev->dev); + struct azx *chip; + + if (!card) + return; + chip = card->private_data; + if (chip && chip->running) + azx_stop_chip(chip); +} + +static const struct of_device_id hda_ft_of_match[] = { + { .compatible = "phytium,hda" }, + {}, +}; +MODULE_DEVICE_TABLE(of, hda_ft_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id hda_ft_acpi_match[] = { + { .id = "PHYT0006" }, + {} +}; +MODULE_DEVICE_TABLE(acpi, hda_ft_acpi_match); +#else +#define hda_ft_acpi_match NULL +#endif + +static struct platform_driver ft_platform_hda = { + .driver = { + .name = "ft-hda", + .pm = hda_ft_pm, + .of_match_table = hda_ft_of_match, + .acpi_match_table = hda_ft_acpi_match, + }, + .probe = hda_ft_probe, + .remove = hda_ft_remove, + .shutdown = hda_ft_shutdown, +}; + +module_platform_driver(ft_platform_hda); + +MODULE_DESCRIPTION("FT HDA bus driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/hda_phytium.h b/sound/pci/hda/hda_phytium.h new file mode 100644 index 0000000000000000000000000000000000000000..a2183ef5e0d8332760434fc23d68c0bcc12556d7 --- /dev/null +++ b/sound/pci/hda/hda_phytium.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Implementation of primary ALSA driver code base for Phytium HD Audio. + * + * Copyright(c) 2018-2022, Phytium Technology Co., Ltd. + */ +#ifndef __SOUND_HDA_PHYTIUM_H__ +#define __SOUND_HDA_PHYTIUM_H__ + +#include "hda_controller.h" + +struct hda_ft { + struct azx chip; + struct snd_pcm_substream *substream; + struct device *dev; + void __iomem *regs; + + /* for pending irqs */ + struct work_struct irq_pending_work; + + /* sync probing */ + struct completion probe_wait; + struct work_struct probe_work; + + /* card list (for power_save trigger) */ + struct list_head list; + + /* extra flags */ + unsigned int irq_pending_warned:1; + unsigned int probe_continued:1; + +}; + +#endif diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 7581fffba6f99435236cd587d8bab68727de64e0..947aa8f31a78a50f868ebe00af98108a76a026ba 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -4622,6 +4622,8 @@ HDA_CODEC_ENTRY(0x10de00a6, "GPU a6 HDMI/DP", patch_nvhdmi), HDA_CODEC_ENTRY(0x10de00a7, "GPU a7 HDMI/DP", patch_nvhdmi), HDA_CODEC_ENTRY(0x10de8001, "MCP73 HDMI", patch_nvhdmi_2ch), HDA_CODEC_ENTRY(0x10de8067, "MCP67/68 HDMI", patch_nvhdmi_2ch), +HDA_CODEC_ENTRY(0x67663d80, "Arise 80 HDMI/DP", patch_gf_hdmi), +HDA_CODEC_ENTRY(0x67663d81, "Arise 81 HDMI/DP", patch_gf_hdmi), HDA_CODEC_ENTRY(0x67663d82, "Arise 82 HDMI/DP", patch_gf_hdmi), HDA_CODEC_ENTRY(0x67663d83, "Arise 83 HDMI/DP", patch_gf_hdmi), HDA_CODEC_ENTRY(0x67663d84, "Arise 84 HDMI/DP", patch_gf_hdmi), diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 439fa631c342ad76dbd5f4974bc28396e9863d57..e70e61755f038be2515843d806b248ceb874aff0 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -96,6 +96,7 @@ source "sound/soc/intel/Kconfig" source "sound/soc/mediatek/Kconfig" source "sound/soc/meson/Kconfig" source "sound/soc/mxs/Kconfig" +source "sound/soc/phytium/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/qcom/Kconfig" source "sound/soc/rockchip/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 8376fdb217ed1b5c9243c2fc82a2984f6d7aff97..9b39526bf33065d27bbcd205895cc36803e660de 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_SND_SOC) += mediatek/ obj-$(CONFIG_SND_SOC) += meson/ obj-$(CONFIG_SND_SOC) += mxs/ obj-$(CONFIG_SND_SOC) += kirkwood/ +obj-$(CONFIG_SND_SOC) += phytium/ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += qcom/ obj-$(CONFIG_SND_SOC) += rockchip/ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f1e1dbc509f6e4cd9e6ab09c2daa886d0610dba6..1de59b63abdd994dc04baa51401b3cbcdac77b86 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1071,6 +1071,15 @@ config SND_SOC_ES8328_SPI depends on SPI_MASTER select SND_SOC_ES8328 +config SND_SOC_ES8336 + tristate "Everest Semi ES8336 CODEC" + depends on I2C + select GPIO_PHYTIUM_PCI + +config SND_SOC_ES8388 + tristate "Everest Semi ES8388 CODEC" + depends on I2C + config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index a87e56938ce58887036537de1fa5329db3bd25f5..006e3aa37ec5ab72d1a19846270580c04e6a1e68 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -119,6 +119,8 @@ snd-soc-es8326-objs := es8326.o snd-soc-es8328-objs := es8328.o snd-soc-es8328-i2c-objs := es8328-i2c.o snd-soc-es8328-spi-objs := es8328-spi.o +snd-soc-es8336-objs := es8336.o +snd-soc-es8388-objs := es8388.o snd-soc-gtm601-objs := gtm601.o snd-soc-hdac-hdmi-objs := hdac_hdmi.o snd-soc-hdac-hda-objs := hdac_hda.o diff --git a/sound/soc/codecs/es8336.c b/sound/soc/codecs/es8336.c new file mode 100644 index 0000000000000000000000000000000000000000..6acb7438f3c6d44f5da8286b54ee24cc9ead2a9b --- /dev/null +++ b/sound/soc/codecs/es8336.c @@ -0,0 +1,1083 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * es8336 ALSA SoC audio driver + * + * Copyright (C) Everest Semiconductor Co.,Ltd + * Copyright (C) 2022-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "es8336.h" + +#define INVALID_GPIO -1 +#define GPIO_LOW 0 +#define GPIO_HIGH 1 +#define ES8336_MUTE (1 << 5) + +static struct snd_soc_component *es8336_component; + +static const struct reg_default es8336_reg_defaults[] = { + {0x00, 0x03}, {0x01, 0x03}, {0x02, 0x00}, {0x03, 0x20}, + {0x04, 0x11}, {0x05, 0x00}, {0x06, 0x11}, {0x07, 0x00}, + {0x08, 0x00}, {0x09, 0x01}, {0x0a, 0x00}, {0x0b, 0x00}, + {0x0c, 0xf8}, {0x0d, 0x3f}, {0x0e, 0x00}, {0x0f, 0x00}, + {0x10, 0x01}, {0x11, 0xfc}, {0x12, 0x28}, {0x13, 0x00}, + {0x14, 0x00}, {0x15, 0x33}, {0x16, 0x00}, {0x17, 0x00}, + {0x18, 0x88}, {0x19, 0x06}, {0x1a, 0x22}, {0x1b, 0x03}, + {0x1c, 0x0f}, {0x1d, 0x00}, {0x1e, 0x80}, {0x1f, 0x80}, + {0x20, 0x00}, {0x21, 0x00}, {0x22, 0xc0}, {0x23, 0x00}, + {0x24, 0x01}, {0x25, 0x08}, {0x26, 0x10}, {0x27, 0xc0}, + {0x28, 0x00}, {0x29, 0x1c}, {0x2a, 0x00}, {0x2b, 0xb0}, + {0x2c, 0x32}, {0x2d, 0x03}, {0x2e, 0x00}, {0x2f, 0x11}, + {0x30, 0x10}, {0x31, 0x00}, {0x32, 0x00}, {0x33, 0xc0}, + {0x34, 0xc0}, {0x35, 0x1f}, {0x36, 0xf7}, {0x37, 0xfd}, + {0x38, 0xff}, {0x39, 0x1f}, {0x3a, 0xf7}, {0x3b, 0xfd}, + {0x3c, 0xff}, {0x3d, 0x1f}, {0x3e, 0xf7}, {0x3f, 0xfd}, + {0x40, 0xff}, {0x41, 0x1f}, {0x42, 0xf7}, {0x43, 0xfd}, + {0x44, 0xff}, {0x45, 0x1f}, {0x46, 0xf7}, {0x47, 0xfd}, + {0x48, 0xff}, {0x49, 0x1f}, {0x4a, 0xf7}, {0x4b, 0xfd}, + {0x4c, 0xff}, {0x4d, 0x00}, {0x4e, 0x00}, {0x4f, 0xff}, + {0x50, 0x00}, {0x51, 0x00}, {0x52, 0x00}, {0x53, 0x00}, +}; + +/* codec private data */ +struct es8336_priv { + struct regmap *regmap; + unsigned int dmic_amic; + unsigned int sysclk; + struct snd_pcm_hw_constraint_list *sysclk_constraints; + struct clk *mclk; + int debounce_time; + int hp_det_invert; + struct delayed_work work; + + int spk_ctl_gpio; + int hp_det_gpio; + bool muted; + bool hp_inserted; + bool spk_active_level; + + int pwr_count; +}; + +/* + * es8336_reset + * write value 0xff to reg0x00, the chip will be in reset mode + * then, writer 0x00 to reg0x00, unreset the chip + */ +static int es8336_reset(struct snd_soc_component *component) +{ + snd_soc_component_write(component, ES8336_RESET_REG00, 0x3F); + usleep_range(5000, 5500); + return snd_soc_component_write(component, ES8336_RESET_REG00, 0x03); +} + +static void es8336_enable_spk(struct es8336_priv *es8336, bool enable) +{ + bool level; + + if (es8336->spk_ctl_gpio == INVALID_GPIO) + return; + + level = enable ? es8336->spk_active_level : !es8336->spk_active_level; + gpio_set_value(es8336->spk_ctl_gpio, level); +} + +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -9600, 50, 1); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -9600, 50, 1); +static const DECLARE_TLV_DB_SCALE(hpmixer_gain_tlv, -1200, 150, 0); +static const DECLARE_TLV_DB_SCALE(mic_bst_tlv, 0, 1200, 0); + +static unsigned int linin_pga_tlv[] = { + TLV_DB_RANGE_HEAD(9), + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(300, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(600, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(900, 0, 0), + 4, 4, TLV_DB_SCALE_ITEM(1200, 0, 0), + 5, 5, TLV_DB_SCALE_ITEM(1500, 0, 0), + 6, 6, TLV_DB_SCALE_ITEM(1800, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(2100, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(2400, 0, 0), +}; + +static unsigned int hpout_vol_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 3, TLV_DB_SCALE_ITEM(-4800, 1200, 0), +}; + +static const char *const alc_func_txt[] = { "Off", "On" }; + +static const struct soc_enum alc_func = + SOC_ENUM_SINGLE(ES8336_ADC_ALC1_REG29, 6, 2, alc_func_txt); + +static const char *const ng_type_txt[] = { + "Constant PGA Gain", "Mute ADC Output" }; + +static const struct soc_enum ng_type = + SOC_ENUM_SINGLE(ES8336_ADC_ALC6_REG2E, 6, 2, ng_type_txt); + +static const char *const adcpol_txt[] = { "Normal", "Invert" }; + +static const struct soc_enum adcpol = + SOC_ENUM_SINGLE(ES8336_ADC_MUTE_REG26, 1, 2, adcpol_txt); + +static const char *const dacpol_txt[] = { + "Normal", "R Invert", "L Invert", "L + R Invert" }; + +static const struct soc_enum dacpol = + SOC_ENUM_SINGLE(ES8336_DAC_SET1_REG30, 0, 4, dacpol_txt); + +static const struct snd_kcontrol_new es8336_snd_controls[] = { + /* HP OUT VOLUME */ + SOC_DOUBLE_TLV("HP Playback Volume", ES8336_CPHP_ICAL_VOL_REG18, + 4, 0, 4, 1, hpout_vol_tlv), + /* HPMIXER VOLUME Control */ + SOC_DOUBLE_TLV("HPMixer Gain", ES8336_HPMIX_VOL_REG16, + 0, 4, 7, 0, hpmixer_gain_tlv), + + /* DAC Digital controls */ + SOC_DOUBLE_R_TLV("DAC Playback Volume", ES8336_DAC_VOLL_REG33, + ES8336_DAC_VOLR_REG34, 0, 0xC0, 1, dac_vol_tlv), + + SOC_SINGLE("Enable DAC Soft Ramp", ES8336_DAC_SET1_REG30, 4, 1, 1), + SOC_SINGLE("DAC Soft Ramp Rate", ES8336_DAC_SET1_REG30, 2, 4, 0), + + SOC_ENUM("Playback Polarity", dacpol), + SOC_SINGLE("DAC Notch Filter", ES8336_DAC_SET2_REG31, 6, 1, 0), + SOC_SINGLE("DAC Double Fs Mode", ES8336_DAC_SET2_REG31, 7, 1, 0), + SOC_SINGLE("DAC Volume Control-LeR", ES8336_DAC_SET2_REG31, 2, 1, 0), + SOC_SINGLE("DAC Stereo Enhancement", ES8336_DAC_SET3_REG32, 0, 7, 0), + + /* +20dB D2SE PGA Control */ + SOC_SINGLE_TLV("MIC Boost", ES8336_ADC_D2SEPGA_REG24, + 0, 1, 0, mic_bst_tlv), + /* 0-+24dB Lineinput PGA Control */ + SOC_SINGLE_TLV("Input PGA", ES8336_ADC_PGAGAIN_REG23, + 4, 8, 0, linin_pga_tlv), +}; + +/* Analog Input MUX */ +static const char * const es8336_analog_in_txt[] = { + "lin1-rin1", + "lin2-rin2", + "lin1-rin1 with 20db Boost", + "lin2-rin2 with 20db Boost" +}; + +static const unsigned int es8336_analog_in_values[] = { 0, 1, 2, 3 }; + +static const struct soc_enum es8336_analog_input_enum = + SOC_VALUE_ENUM_SINGLE(ES8336_ADC_PDN_LINSEL_REG22, 4, 3, + ARRAY_SIZE(es8336_analog_in_txt), + es8336_analog_in_txt, + es8336_analog_in_values); + +static const struct snd_kcontrol_new es8336_analog_in_mux_controls = + SOC_DAPM_ENUM("Route", es8336_analog_input_enum); + +/* Dmic MUX */ +static const char * const es8336_dmic_txt[] = { + "dmic disable", + "dmic data at high level", + "dmic data at low level", +}; + +static const unsigned int es8336_dmic_values[] = { 0, 2, 3 }; + +static const struct soc_enum es8336_dmic_src_enum = + SOC_VALUE_ENUM_SINGLE(ES8336_ADC_DMIC_REG25, 0, 3, + ARRAY_SIZE(es8336_dmic_txt), + es8336_dmic_txt, + es8336_dmic_values); + +static const struct snd_kcontrol_new es8336_dmic_src_controls = + SOC_DAPM_ENUM("Route", es8336_dmic_src_enum); + +/* hp mixer mux */ +static const char *const es8336_hpmux_texts[] = { + "lin1-rin1", + "lin2-rin2", + "lin-rin with Boost", + "lin-rin with Boost and PGA" +}; + +static const unsigned int es8336_hpmux_values[] = { 0, 1, 2, 3 }; + +static const struct soc_enum es8336_left_hpmux_enum = + SOC_VALUE_ENUM_SINGLE(ES8336_HPMIX_SEL_REG13, 4, 7, + ARRAY_SIZE(es8336_hpmux_texts), + es8336_hpmux_texts, + es8336_hpmux_values); + +static const struct snd_kcontrol_new es8336_left_hpmux_controls = + SOC_DAPM_ENUM("Route", es8336_left_hpmux_enum); + +static const struct soc_enum es8336_right_hpmux_enum = + SOC_VALUE_ENUM_SINGLE(ES8336_HPMIX_SEL_REG13, 0, 7, + ARRAY_SIZE(es8336_hpmux_texts), + es8336_hpmux_texts, + es8336_hpmux_values); + +static const struct snd_kcontrol_new es8336_right_hpmux_controls = + SOC_DAPM_ENUM("Route", es8336_right_hpmux_enum); + +/* headphone Output Mixer */ +static const struct snd_kcontrol_new es8336_out_left_mix[] = { + SOC_DAPM_SINGLE("LLIN Switch", ES8336_HPMIX_SWITCH_REG14, + 6, 1, 0), + SOC_DAPM_SINGLE("Left DAC Switch", ES8336_HPMIX_SWITCH_REG14, + 7, 1, 0), +}; + +static const struct snd_kcontrol_new es8336_out_right_mix[] = { + SOC_DAPM_SINGLE("RLIN Switch", ES8336_HPMIX_SWITCH_REG14, + 2, 1, 0), + SOC_DAPM_SINGLE("Right DAC Switch", ES8336_HPMIX_SWITCH_REG14, + 3, 1, 0), +}; + +/* DAC data source mux */ +static const char * const es8336_dacsrc_texts[] = { + "LDATA TO LDAC, RDATA TO RDAC", + "LDATA TO LDAC, LDATA TO RDAC", + "RDATA TO LDAC, RDATA TO RDAC", + "RDATA TO LDAC, LDATA TO RDAC", +}; + +static const unsigned int es8336_dacsrc_values[] = { 0, 1, 2, 3 }; + +static const struct soc_enum es8336_dacsrc_mux_enum = + SOC_VALUE_ENUM_SINGLE(ES8336_DAC_SET1_REG30, 6, 4, + ARRAY_SIZE(es8336_dacsrc_texts), + es8336_dacsrc_texts, + es8336_dacsrc_values); +static const struct snd_kcontrol_new es8336_dacsrc_mux_controls = + SOC_DAPM_ENUM("Route", es8336_dacsrc_mux_enum); + +static const struct snd_soc_dapm_widget es8336_dapm_widgets[] = { + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + + SND_SOC_DAPM_MICBIAS("micbias", SND_SOC_NOPM, + 0, 0), + /* Input MUX */ + SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, + &es8336_analog_in_mux_controls), + + SND_SOC_DAPM_PGA("Line input PGA", ES8336_ADC_PDN_LINSEL_REG22, + 7, 1, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("Mono ADC", NULL, ES8336_ADC_PDN_LINSEL_REG22, 6, 1), + + /* Dmic MUX */ + SND_SOC_DAPM_MUX("Digital Mic Mux", SND_SOC_NOPM, 0, 0, + &es8336_dmic_src_controls), + + /* Digital Interface */ + SND_SOC_DAPM_AIF_OUT("I2S OUT", "I2S1 Capture", 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("I2S IN", "I2S1 Playback", 0, + SND_SOC_NOPM, 0, 0), + + /* DACs DATA SRC MUX */ + SND_SOC_DAPM_MUX("DAC SRC Mux", SND_SOC_NOPM, 0, 0, + &es8336_dacsrc_mux_controls), + /* DACs */ + SND_SOC_DAPM_DAC("Right DAC", NULL, ES8336_DAC_PDN_REG2F, 0, 1), + SND_SOC_DAPM_DAC("Left DAC", NULL, ES8336_DAC_PDN_REG2F, 4, 1), + + /* Headphone Output Side */ + /* hpmux for hp mixer */ + SND_SOC_DAPM_MUX("Left Hp mux", SND_SOC_NOPM, 0, 0, + &es8336_left_hpmux_controls), + SND_SOC_DAPM_MUX("Right Hp mux", SND_SOC_NOPM, 0, 0, + &es8336_right_hpmux_controls), + /* Output mixer */ + SND_SOC_DAPM_MIXER("Left Hp mixer", ES8336_HPMIX_PDN_REG15, + 4, 1, &es8336_out_left_mix[0], + ARRAY_SIZE(es8336_out_left_mix)), + SND_SOC_DAPM_MIXER("Right Hp mixer", ES8336_HPMIX_PDN_REG15, + 0, 1, &es8336_out_right_mix[0], + ARRAY_SIZE(es8336_out_right_mix)), + + /* Output charge pump */ + SND_SOC_DAPM_PGA("HPCP L", ES8336_CPHP_OUTEN_REG17, + 6, 0, NULL, 0), + SND_SOC_DAPM_PGA("HPCP R", ES8336_CPHP_OUTEN_REG17, + 2, 0, NULL, 0), + + /* Output Driver */ + SND_SOC_DAPM_PGA("HPVOL L", ES8336_CPHP_OUTEN_REG17, + 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("HPVOL R", ES8336_CPHP_OUTEN_REG17, + 1, 0, NULL, 0), + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), +}; + +static const struct snd_soc_dapm_route es8336_dapm_routes[] = { + /* + * record route map + */ + {"MIC1", NULL, "micbias"}, + {"MIC2", NULL, "micbias"}, + {"DMIC", NULL, "micbias"}, + + {"Differential Mux", "lin1-rin1", "MIC1"}, + {"Differential Mux", "lin2-rin2", "MIC2"}, + {"Differential Mux", "lin1-rin1 with 20db Boost", "MIC1"}, + {"Differential Mux", "lin2-rin2 with 20db Boost", "MIC2"}, + {"Line input PGA", NULL, "Differential Mux"}, + + {"Mono ADC", NULL, "Line input PGA"}, + + {"Digital Mic Mux", "dmic disable", "Mono ADC"}, + {"Digital Mic Mux", "dmic data at high level", "DMIC"}, + {"Digital Mic Mux", "dmic data at low level", "DMIC"}, + + {"I2S OUT", NULL, "Digital Mic Mux"}, + /* + * playback route map + */ + {"DAC SRC Mux", "LDATA TO LDAC, RDATA TO RDAC", "I2S IN"}, + {"DAC SRC Mux", "LDATA TO LDAC, LDATA TO RDAC", "I2S IN"}, + {"DAC SRC Mux", "RDATA TO LDAC, RDATA TO RDAC", "I2S IN"}, + {"DAC SRC Mux", "RDATA TO LDAC, LDATA TO RDAC", "I2S IN"}, + + {"Left DAC", NULL, "DAC SRC Mux"}, + {"Right DAC", NULL, "DAC SRC Mux"}, + + {"Left Hp mux", "lin1-rin1", "MIC1"}, + {"Left Hp mux", "lin2-rin2", "MIC2"}, + {"Left Hp mux", "lin-rin with Boost", "Differential Mux"}, + {"Left Hp mux", "lin-rin with Boost and PGA", "Line input PGA"}, + + {"Right Hp mux", "lin1-rin1", "MIC1"}, + {"Right Hp mux", "lin2-rin2", "MIC2"}, + {"Right Hp mux", "lin-rin with Boost", "Differential Mux"}, + {"Right Hp mux", "lin-rin with Boost and PGA", "Line input PGA"}, + + {"Left Hp mixer", "LLIN Switch", "Left Hp mux"}, + {"Left Hp mixer", "Left DAC Switch", "Left DAC"}, + + {"Right Hp mixer", "RLIN Switch", "Right Hp mux"}, + {"Right Hp mixer", "Right DAC Switch", "Right DAC"}, + + {"HPCP L", NULL, "Left Hp mixer"}, + {"HPCP R", NULL, "Right Hp mixer"}, + + {"HPVOL L", NULL, "HPCP L"}, + {"HPVOL R", NULL, "HPCP R"}, + + {"HPOL", NULL, "HPVOL L"}, + {"HPOR", NULL, "HPVOL R"}, +}; + + +/* The set of rates we can generate from the above for each SYSCLK */ + +static unsigned int rates_12288[] = { + 8000, 12000, 16000, 24000, 24000, 32000, 48000, 96000, +}; + +static struct snd_pcm_hw_constraint_list constraints_12288 = { + .count = ARRAY_SIZE(rates_12288), + .list = rates_12288, +}; + +static unsigned int rates_112896[] = { + 8000, 11025, 22050, 44100, +}; + +static struct snd_pcm_hw_constraint_list constraints_112896 = { + .count = ARRAY_SIZE(rates_112896), + .list = rates_112896, +}; + +static unsigned int rates_12[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, + 48000, 88235, 96000, +}; + +static struct snd_pcm_hw_constraint_list constraints_12 = { + .count = ARRAY_SIZE(rates_12), + .list = rates_12, +}; + +/* + * Note that this should be called from init rather than from hw_params. + */ +static int es8336_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct es8336_priv *es8336 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case 11289600: + case 18432000: + case 22579200: + case 36864000: + es8336->sysclk_constraints = &constraints_112896; + es8336->sysclk = freq; + return 0; + case 12288000: + case 19200000: + case 16934400: + case 24576000: + case 33868800: + es8336->sysclk_constraints = &constraints_12288; + es8336->sysclk = freq; + return 0; + case 12000000: + case 24000000: + es8336->sysclk_constraints = &constraints_12; + es8336->sysclk = freq; + return 0; + } + + return 0; +} + +static int es8336_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u8 iface = 0; + u8 adciface = 0; + u8 daciface = 0; + + iface = snd_soc_component_read(component, ES8336_IFACE); + adciface = snd_soc_component_read(component, ES8336_ADC_IFACE); + daciface = snd_soc_component_read(component, ES8336_DAC_IFACE); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x80; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iface &= 0x7F; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + adciface &= 0xFC; + daciface &= 0xFC; + break; + case SND_SOC_DAIFMT_RIGHT_J: + return -EINVAL; + case SND_SOC_DAIFMT_LEFT_J: + adciface &= 0xFC; + daciface &= 0xFC; + adciface |= 0x01; + daciface |= 0x01; + break; + case SND_SOC_DAIFMT_DSP_A: + adciface &= 0xDC; + daciface &= 0xDC; + adciface |= 0x03; + daciface |= 0x03; + break; + case SND_SOC_DAIFMT_DSP_B: + adciface &= 0xDC; + daciface &= 0xDC; + adciface |= 0x23; + daciface |= 0x23; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + iface &= 0xDF; + adciface &= 0xDF; + daciface &= 0xDF; + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x20; + adciface |= 0x20; + daciface |= 0x20; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x20; + adciface &= 0xDF; + daciface &= 0xDF; + break; + case SND_SOC_DAIFMT_NB_IF: + iface &= 0xDF; + adciface |= 0x20; + daciface |= 0x20; + break; + default: + return -EINVAL; + } + snd_soc_component_write(component, ES8336_IFACE, iface); + snd_soc_component_write(component, ES8336_ADC_IFACE, adciface); + snd_soc_component_write(component, ES8336_DAC_IFACE, daciface); + return 0; +} + +static int es8336_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct es8336_priv *es8336 = snd_soc_component_get_drvdata(component); + bool playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + snd_soc_component_write(component, ES8336_RESET_REG00, 0xC0); + snd_soc_component_write(component, ES8336_SYS_PDN_REG0D, 0x00); + /* es8336: both playback and capture need dac mclk */ + snd_soc_component_update_bits(component, ES8336_CLKMGR_CLKSW_REG01, + ES8336_CLKMGR_MCLK_DIV_MASK | + ES8336_CLKMGR_DAC_MCLK_MASK, + ES8336_CLKMGR_MCLK_DIV_NML | + ES8336_CLKMGR_DAC_MCLK_EN); + es8336->pwr_count++; + + if (playback) { + snd_soc_component_write(component, ES8336_SYS_LP1_REG0E, 0x3F); + snd_soc_component_write(component, ES8336_SYS_LP2_REG0F, 0x1F); + snd_soc_component_write(component, ES8336_HPMIX_SWITCH_REG14, 0x88); + snd_soc_component_write(component, ES8336_HPMIX_PDN_REG15, 0x00); + snd_soc_component_write(component, ES8336_HPMIX_VOL_REG16, 0xBB); + snd_soc_component_write(component, ES8336_CPHP_PDN2_REG1A, 0x10); + snd_soc_component_write(component, ES8336_CPHP_LDOCTL_REG1B, 0x30); + snd_soc_component_write(component, ES8336_CPHP_PDN1_REG19, 0x02); + snd_soc_component_write(component, ES8336_DAC_PDN_REG2F, 0x00); + snd_soc_component_write(component, ES8336_CPHP_OUTEN_REG17, 0x66); + snd_soc_component_update_bits(component, ES8336_CLKMGR_CLKSW_REG01, + ES8336_CLKMGR_DAC_MCLK_MASK | + ES8336_CLKMGR_DAC_ANALOG_MASK, + ES8336_CLKMGR_DAC_MCLK_EN | + ES8336_CLKMGR_DAC_ANALOG_EN); + msleep(50); + } else { + snd_soc_component_update_bits(component, ES8336_CLKMGR_CLKSW_REG01, + ES8336_CLKMGR_ADC_MCLK_MASK | + ES8336_CLKMGR_ADC_ANALOG_MASK, + ES8336_CLKMGR_ADC_MCLK_EN | + ES8336_CLKMGR_ADC_ANALOG_EN); + } + + return 0; +} + +static void es8336_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct es8336_priv *es8336 = snd_soc_component_get_drvdata(component); + bool playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + if (playback) { + snd_soc_component_write(component, ES8336_CPHP_OUTEN_REG17, 0x00); + snd_soc_component_write(component, ES8336_DAC_PDN_REG2F, 0x11); + snd_soc_component_write(component, ES8336_CPHP_LDOCTL_REG1B, 0x03); + snd_soc_component_write(component, ES8336_CPHP_PDN2_REG1A, 0x22); + snd_soc_component_write(component, ES8336_CPHP_PDN1_REG19, 0x06); + snd_soc_component_write(component, ES8336_HPMIX_SWITCH_REG14, 0x00); + snd_soc_component_write(component, ES8336_HPMIX_PDN_REG15, 0x33); + snd_soc_component_write(component, ES8336_HPMIX_VOL_REG16, 0x00); + snd_soc_component_write(component, ES8336_SYS_PDN_REG0D, 0x00); + snd_soc_component_write(component, ES8336_SYS_LP1_REG0E, 0xFF); + snd_soc_component_write(component, ES8336_SYS_LP2_REG0F, 0xFF); + snd_soc_component_update_bits(component, ES8336_CLKMGR_CLKSW_REG01, + ES8336_CLKMGR_DAC_ANALOG_MASK, + ES8336_CLKMGR_DAC_ANALOG_DIS); + } else { + snd_soc_component_update_bits(component, ES8336_CLKMGR_CLKSW_REG01, + ES8336_CLKMGR_ADC_MCLK_MASK | + ES8336_CLKMGR_ADC_ANALOG_MASK, + ES8336_CLKMGR_ADC_MCLK_DIS | + ES8336_CLKMGR_ADC_ANALOG_DIS); + } + + if (--es8336->pwr_count == 0) { + if (!es8336->hp_inserted) + snd_soc_component_write(component, ES8336_SYS_PDN_REG0D, 0x3F); + snd_soc_component_write(component, ES8336_CLKMGR_CLKSW_REG01, 0xF3); + } +} + + +static int es8336_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + int val = 0; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val = ES8336_DACWL_16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val = ES8336_DACWL_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = ES8336_DACWL_24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + val = ES8336_DACWL_32; + break; + default: + val = ES8336_DACWL_16; + break; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_component_update_bits(component, ES8336_SDP_DACFMT_REG0B, + ES8336_DACWL_MASK, val); + else + snd_soc_component_update_bits(component, ES8336_SDP_ADCFMT_REG0A, + ES8336_ADCWL_MASK, val); + + return 0; +} + +static int es8336_mute(struct snd_soc_dai *dai, int mute, int direction) +{ + struct snd_soc_component *component = dai->component; + struct es8336_priv *es8336 = snd_soc_component_get_drvdata(component); + + es8336->muted = mute; + if (!es8336->hp_inserted) + es8336_enable_spk(es8336, true); + else + es8336_enable_spk(es8336, false); + if (direction) + return snd_soc_component_update_bits(dai->component, ES8336_ADC_MUTE_REG26, + ES8336_MUTE, + mute ? ES8336_MUTE : 0); + else + return snd_soc_component_update_bits(dai->component, ES8336_DAC_SET1_REG30, + ES8336_MUTE, + mute ? ES8336_MUTE : 0); +} + +static int es8336_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct es8336_priv *es8336 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, ES8336_CPHP_OUTEN_REG17, 0x00); + snd_soc_component_write(component, ES8336_DAC_PDN_REG2F, 0x11); + snd_soc_component_write(component, ES8336_CPHP_LDOCTL_REG1B, 0x03); + snd_soc_component_write(component, ES8336_CPHP_PDN2_REG1A, 0x22); + snd_soc_component_write(component, ES8336_CPHP_PDN1_REG19, 0x06); + snd_soc_component_write(component, ES8336_HPMIX_SWITCH_REG14, 0x00); + snd_soc_component_write(component, ES8336_HPMIX_PDN_REG15, 0x33); + snd_soc_component_write(component, ES8336_HPMIX_VOL_REG16, 0x00); + if (!es8336->hp_inserted) + snd_soc_component_write(component, ES8336_SYS_PDN_REG0D, 0x3F); + snd_soc_component_write(component, ES8336_SYS_LP1_REG0E, 0x3F); + snd_soc_component_write(component, ES8336_SYS_LP2_REG0F, 0x1F); + snd_soc_component_write(component, ES8336_RESET_REG00, 0x00); + break; + } + + return 0; +} + +#define es8336_RATES SNDRV_PCM_RATE_8000_96000 + +#define es8336_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops es8336_ops = { + .startup = es8336_pcm_startup, + .hw_params = es8336_pcm_hw_params, + .set_fmt = es8336_set_dai_fmt, + .set_sysclk = es8336_set_dai_sysclk, + .mute_stream = es8336_mute, + .shutdown = es8336_pcm_shutdown, + .no_capture_mute = 1, +}; + +static struct snd_soc_dai_driver es8336_dai = { + .name = "es8336-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = es8336_RATES, + .formats = es8336_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = es8336_RATES, + .formats = es8336_FORMATS, + }, + .ops = &es8336_ops, + .symmetric_rate = 1, +}; + +static int es8336_init_regs(struct snd_soc_component *component) +{ + snd_soc_component_write(component, ES8336_RESET_REG00, 0x3f); + usleep_range(5000, 5500); + snd_soc_component_write(component, ES8336_RESET_REG00, 0x00); + snd_soc_component_write(component, ES8336_SYS_VMIDSEL_REG0C, 0xFF); + msleep(30); + snd_soc_component_write(component, ES8336_CLKMGR_CLKSEL_REG02, 0x08); + snd_soc_component_write(component, ES8336_CLKMGR_ADCOSR_REG03, 0x20); + snd_soc_component_write(component, ES8336_CLKMGR_ADCDIV1_REG04, 0x11); + snd_soc_component_write(component, ES8336_CLKMGR_ADCDIV2_REG05, 0x00); + snd_soc_component_write(component, ES8336_CLKMGR_DACDIV1_REG06, 0x11); + snd_soc_component_write(component, ES8336_CLKMGR_DACDIV2_REG07, 0x00); + snd_soc_component_write(component, ES8336_CLKMGR_CPDIV_REG08, 0x00); + snd_soc_component_write(component, ES8336_SDP_MS_BCKDIV_REG09, 0x04); + snd_soc_component_write(component, ES8336_CLKMGR_CLKSW_REG01, 0x7F); + snd_soc_component_write(component, ES8336_CAL_TYPE_REG1C, 0x0F); + snd_soc_component_write(component, ES8336_CAL_HPLIV_REG1E, 0x90); + snd_soc_component_write(component, ES8336_CAL_HPRIV_REG1F, 0x90); + snd_soc_component_write(component, ES8336_ADC_VOLUME_REG27, 0x00); + snd_soc_component_write(component, ES8336_ADC_PDN_LINSEL_REG22, 0xc0); + snd_soc_component_write(component, ES8336_ADC_D2SEPGA_REG24, 0x00); + snd_soc_component_write(component, ES8336_ADC_DMIC_REG25, 0x08); + snd_soc_component_write(component, ES8336_DAC_SET2_REG31, 0x20); + snd_soc_component_write(component, ES8336_DAC_SET3_REG32, 0x00); + snd_soc_component_write(component, ES8336_DAC_VOLL_REG33, 0x00); + snd_soc_component_write(component, ES8336_DAC_VOLR_REG34, 0x00); + snd_soc_component_write(component, ES8336_SDP_ADCFMT_REG0A, 0x00); + snd_soc_component_write(component, ES8336_SDP_DACFMT_REG0B, 0x00); + snd_soc_component_write(component, ES8336_SYS_VMIDLOW_REG10, 0x11); + snd_soc_component_write(component, ES8336_SYS_VSEL_REG11, 0xFC); + snd_soc_component_write(component, ES8336_SYS_REF_REG12, 0x28); + snd_soc_component_write(component, ES8336_SYS_LP1_REG0E, 0x04); + snd_soc_component_write(component, ES8336_SYS_LP2_REG0F, 0x0C); + snd_soc_component_write(component, ES8336_DAC_PDN_REG2F, 0x11); + snd_soc_component_write(component, ES8336_HPMIX_SEL_REG13, 0x00); + snd_soc_component_write(component, ES8336_HPMIX_SWITCH_REG14, 0x88); + snd_soc_component_write(component, ES8336_HPMIX_PDN_REG15, 0x00); + snd_soc_component_write(component, ES8336_HPMIX_VOL_REG16, 0xBB); + snd_soc_component_write(component, ES8336_CPHP_PDN2_REG1A, 0x10); + snd_soc_component_write(component, ES8336_CPHP_LDOCTL_REG1B, 0x30); + snd_soc_component_write(component, ES8336_CPHP_PDN1_REG19, 0x02); + snd_soc_component_write(component, ES8336_CPHP_ICAL_VOL_REG18, 0x00); + snd_soc_component_write(component, ES8336_GPIO_SEL_REG4D, 0x02); + snd_soc_component_write(component, ES8336_GPIO_DEBUNCE_INT_REG4E, 0x02); + snd_soc_component_write(component, ES8336_TESTMODE_REG50, 0xA0); + snd_soc_component_write(component, ES8336_TEST1_REG51, 0x00); + snd_soc_component_write(component, ES8336_TEST2_REG52, 0x00); + snd_soc_component_write(component, ES8336_SYS_PDN_REG0D, 0x00); + snd_soc_component_write(component, ES8336_RESET_REG00, 0xC0); + msleep(50); + snd_soc_component_write(component, ES8336_ADC_PGAGAIN_REG23, 0x60); + snd_soc_component_write(component, ES8336_ADC_D2SEPGA_REG24, 0x01); + /* adc ds mode, HPF enable */ + snd_soc_component_write(component, ES8336_ADC_DMIC_REG25, 0x08); + snd_soc_component_write(component, ES8336_ADC_ALC1_REG29, 0xcd); + snd_soc_component_write(component, ES8336_ADC_ALC2_REG2A, 0x08); + snd_soc_component_write(component, ES8336_ADC_ALC3_REG2B, 0xa0); + snd_soc_component_write(component, ES8336_ADC_ALC4_REG2C, 0x05); + snd_soc_component_write(component, ES8336_ADC_ALC5_REG2D, 0x06); + snd_soc_component_write(component, ES8336_ADC_ALC6_REG2E, 0x61); + return 0; +} + +static int es8336_suspend(struct snd_soc_component *component) +{ + return 0; +} + +static int es8336_resume(struct snd_soc_component *component) +{ + struct es8336_priv *es8336 = snd_soc_component_get_drvdata(component); + int ret; + + es8336_reset(component); /* UPDATED BY DAVID,15-3-5 */ + ret = snd_soc_component_read(component, ES8336_CLKMGR_ADCDIV2_REG05); + if (!ret) { + es8336_init_regs(component); + snd_soc_component_write(component, ES8336_GPIO_SEL_REG4D, 0x02); + /* max debance time, enable interrupt, low active */ + snd_soc_component_write(component, ES8336_GPIO_DEBUNCE_INT_REG4E, 0xf3); + /* es8336_set_bias_level(component, SND_SOC_BIAS_OFF); */ + snd_soc_component_write(component, ES8336_CPHP_OUTEN_REG17, 0x00); + snd_soc_component_write(component, ES8336_DAC_PDN_REG2F, 0x11); + snd_soc_component_write(component, ES8336_CPHP_LDOCTL_REG1B, 0x03); + snd_soc_component_write(component, ES8336_CPHP_PDN2_REG1A, 0x22); + snd_soc_component_write(component, ES8336_CPHP_PDN1_REG19, 0x06); + snd_soc_component_write(component, ES8336_HPMIX_SWITCH_REG14, 0x00); + snd_soc_component_write(component, ES8336_HPMIX_PDN_REG15, 0x33); + snd_soc_component_write(component, ES8336_HPMIX_VOL_REG16, 0x00); + if (!es8336->hp_inserted) + snd_soc_component_write(component, ES8336_SYS_PDN_REG0D, 0x3F); + snd_soc_component_write(component, ES8336_SYS_LP1_REG0E, 0xFF); + snd_soc_component_write(component, ES8336_SYS_LP2_REG0F, 0xFF); + snd_soc_component_write(component, ES8336_CLKMGR_CLKSW_REG01, 0xF3); + snd_soc_component_write(component, ES8336_ADC_PDN_LINSEL_REG22, 0xc0); + } + return 0; +} + +static irqreturn_t es8336_irq_handler(int irq, void *data) +{ + struct es8336_priv *es8336 = data; + + queue_delayed_work(system_power_efficient_wq, &es8336->work, + msecs_to_jiffies(es8336->debounce_time)); + + return IRQ_HANDLED; +} + +static void hp_work(struct work_struct *work) +{ + struct es8336_priv *es8336; + int enable; + + es8336 = container_of(work, struct es8336_priv, work.work); + enable = gpio_get_value(es8336->hp_det_gpio); + if (es8336->hp_det_invert) + enable = !enable; + + es8336->hp_inserted = !enable; + if (!es8336->muted) { + if (es8336->hp_inserted) + es8336_enable_spk(es8336, false); + else + es8336_enable_spk(es8336, true); + } +} + +static int es8336_probe(struct snd_soc_component *component) +{ + struct es8336_priv *es8336 = snd_soc_component_get_drvdata(component); + int ret = 0; + + es8336_component = component; + ret = snd_soc_component_read(component, ES8336_CLKMGR_ADCDIV2_REG05); + if (!ret) { + es8336_reset(component); /* UPDATED BY DAVID,15-3-5 */ + ret = snd_soc_component_read(component, ES8336_CLKMGR_ADCDIV2_REG05); + if (!ret) { + es8336_init_regs(component); + snd_soc_component_write(component, ES8336_GPIO_SEL_REG4D, 0x02); + /* max debance time, enable interrupt, low active */ + snd_soc_component_write(component, + ES8336_GPIO_DEBUNCE_INT_REG4E, 0xf3); + + /* es8336_set_bias_level(codec, SND_SOC_BIAS_OFF); */ + snd_soc_component_write(component, ES8336_CPHP_OUTEN_REG17, 0x00); + snd_soc_component_write(component, ES8336_DAC_PDN_REG2F, 0x11); + snd_soc_component_write(component, ES8336_CPHP_LDOCTL_REG1B, 0x03); + snd_soc_component_write(component, ES8336_CPHP_PDN2_REG1A, 0x22); + snd_soc_component_write(component, ES8336_CPHP_PDN1_REG19, 0x06); + snd_soc_component_write(component, ES8336_HPMIX_SWITCH_REG14, 0x00); + snd_soc_component_write(component, ES8336_HPMIX_PDN_REG15, 0x33); + snd_soc_component_write(component, ES8336_HPMIX_VOL_REG16, 0x00); + if (!es8336->hp_inserted) + snd_soc_component_write(component, ES8336_SYS_PDN_REG0D, + 0x3F); + snd_soc_component_write(component, ES8336_SYS_LP1_REG0E, 0xFF); + snd_soc_component_write(component, ES8336_SYS_LP2_REG0F, 0xFF); + snd_soc_component_write(component, ES8336_CLKMGR_CLKSW_REG01, 0xF3); + snd_soc_component_write(component, + ES8336_ADC_PDN_LINSEL_REG22, 0xc0); + } + } + + return ret; +} + +static void es8336_remove(struct snd_soc_component *component) +{ + es8336_set_bias_level(component, SND_SOC_BIAS_OFF); +} + +const struct regmap_config es8336_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ES8336_TEST3_REG53, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = es8336_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(es8336_reg_defaults), +}; + +static const struct snd_soc_component_driver soc_component_dev_es8336 = { + .probe = es8336_probe, + .remove = es8336_remove, + .suspend = es8336_suspend, + .resume = es8336_resume, + .set_bias_level = es8336_set_bias_level, + + .controls = es8336_snd_controls, + .num_controls = ARRAY_SIZE(es8336_snd_controls), + .dapm_widgets = es8336_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(es8336_dapm_widgets), + .dapm_routes = es8336_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(es8336_dapm_routes), +}; + +static int es8336_i2c_probe(struct i2c_client *i2c) +{ + struct es8336_priv *es8336; + struct gpio_desc *gpiod; + int ret = -1; + int hp_irq; + int active_level = 0; + + es8336 = devm_kzalloc(&i2c->dev, sizeof(*es8336), GFP_KERNEL); + if (!es8336) + return -ENOMEM; + + es8336->debounce_time = 200; + es8336->hp_det_invert = 0; + es8336->pwr_count = 0; + es8336->hp_inserted = false; + es8336->muted = true; + + es8336->regmap = devm_regmap_init_i2c(i2c, &es8336_regmap_config); + if (IS_ERR(es8336->regmap)) { + ret = PTR_ERR(es8336->regmap); + dev_err(&i2c->dev, "Failed to init regmap: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, es8336); + + gpiod = devm_gpiod_get_index_optional(&i2c->dev, "sel", 0, + GPIOD_OUT_HIGH); + device_property_read_u32(&i2c->dev, "spk-active-level", &active_level); + if (!gpiod) { + dev_info(&i2c->dev, "Can not get spk_ctl_gpio\n"); + es8336->spk_ctl_gpio = INVALID_GPIO; + } else { + es8336->spk_ctl_gpio = desc_to_gpio(gpiod); + es8336->spk_active_level = active_level; + es8336_enable_spk(es8336, false); + } + + gpiod = devm_gpiod_get_index_optional(&i2c->dev, "det", 0, + GPIOD_IN); + + if (!gpiod) { + dev_info(&i2c->dev, "Can not get hp_det_gpio\n"); + es8336->hp_det_gpio = INVALID_GPIO; + } else { + es8336->hp_det_gpio = desc_to_gpio(gpiod); + INIT_DELAYED_WORK(&es8336->work, hp_work); + es8336->hp_det_invert = 0; + hp_irq = gpio_to_irq(es8336->hp_det_gpio); + ret = devm_request_threaded_irq(&i2c->dev, hp_irq, NULL, + es8336_irq_handler, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "es8336_interrupt", es8336); + if (ret < 0) { + dev_err(&i2c->dev, "request_irq failed: %d\n", ret); + return ret; + } + + schedule_delayed_work(&es8336->work, + msecs_to_jiffies(es8336->debounce_time)); + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_es8336, + &es8336_dai, 1); + + return ret; +} + +static void es8336_i2c_remove(struct i2c_client *client) +{ + +} + +static void es8336_i2c_shutdown(struct i2c_client *client) +{ + struct es8336_priv *es8336 = i2c_get_clientdata(client); + + if (es8336_component != NULL) { + es8336_enable_spk(es8336, false); + msleep(20); + es8336_set_bias_level(es8336_component, SND_SOC_BIAS_OFF); + } +} + +static const struct i2c_device_id es8336_i2c_id[] = { + {"es8336", 0}, + {"10ES8336:00", 0}, + {"10ES8336", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, es8336_i2c_id); + +static const struct of_device_id es8336_of_match[] = { + { .compatible = "everest,es8336", }, + { } +}; +MODULE_DEVICE_TABLE(of, es8336_of_match); + +static const struct acpi_device_id es8336_acpi_match[] = { + { "ESSX8336", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, es8336_acpi_match); + +static struct i2c_driver es8336_i2c_driver = { + .driver = { + .name = "es8336", + .of_match_table = es8336_of_match, + .acpi_match_table = es8336_acpi_match, + }, + .probe = es8336_i2c_probe, + .remove = es8336_i2c_remove, + .shutdown = es8336_i2c_shutdown, + .id_table = es8336_i2c_id, +}; + +module_i2c_driver(es8336_i2c_driver); +MODULE_DESCRIPTION("ASoC es8336 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es8336.h b/sound/soc/codecs/es8336.h new file mode 100644 index 0000000000000000000000000000000000000000..368a0a4accdde317f7640a89f1b1074c43f2f652 --- /dev/null +++ b/sound/soc/codecs/es8336.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Everest Semiconductor Co.,Ltd + * Copyright (C) 2022-2023, Phytium Technology Co., Ltd. + */ + +#ifndef _ES8336_H +#define _ES8336_H + +/* ES8336 register space */ +/* + * RESET Control + */ +#define ES8336_RESET_REG00 0x00 +/* + * Clock Managerment + */ +#define ES8336_CLKMGR_CLKSW_REG01 0x01 +#define ES8336_CLKMGR_CLKSEL_REG02 0x02 +#define ES8336_CLKMGR_ADCOSR_REG03 0x03 +#define ES8336_CLKMGR_ADCDIV1_REG04 0x04 +#define ES8336_CLKMGR_ADCDIV2_REG05 0x05 +#define ES8336_CLKMGR_DACDIV1_REG06 0x06 +#define ES8336_CLKMGR_DACDIV2_REG07 0x07 +#define ES8336_CLKMGR_CPDIV_REG08 0x08 +/* + * SDP Control + */ +#define ES8336_SDP_MS_BCKDIV_REG09 0x09 +#define ES8336_SDP_ADCFMT_REG0A 0x0a +#define ES8336_SDP_DACFMT_REG0B 0x0b +/* + * System Control + */ +#define ES8336_SYS_VMIDSEL_REG0C 0x0c +#define ES8336_SYS_PDN_REG0D 0x0d +#define ES8336_SYS_LP1_REG0E 0x0e +#define ES8336_SYS_LP2_REG0F 0x0f +#define ES8336_SYS_VMIDLOW_REG10 0x10 +#define ES8336_SYS_VSEL_REG11 0x11 +#define ES8336_SYS_REF_REG12 0x12 +/* + * HP Mixer + */ +#define ES8336_HPMIX_SEL_REG13 0x13 +#define ES8336_HPMIX_SWITCH_REG14 0x14 +#define ES8336_HPMIX_PDN_REG15 0x15 +#define ES8336_HPMIX_VOL_REG16 0x16 +/* + * Charge Pump Headphone driver + */ +#define ES8336_CPHP_OUTEN_REG17 0x17 +#define ES8336_CPHP_ICAL_VOL_REG18 0x18 +#define ES8336_CPHP_PDN1_REG19 0x19 +#define ES8336_CPHP_PDN2_REG1A 0x1a +#define ES8336_CPHP_LDOCTL_REG1B 0x1b +/* + * Calibration + */ +#define ES8336_CAL_TYPE_REG1C 0x1c +#define ES8336_CAL_SET_REG1D 0x1d +#define ES8336_CAL_HPLIV_REG1E 0x1e +#define ES8336_CAL_HPRIV_REG1F 0x1f +#define ES8336_CAL_HPLMV_REG20 0x20 +#define ES8336_CAL_HPRMV_REG21 0x21 +/* + * ADC Control + */ +#define ES8336_ADC_PDN_LINSEL_REG22 0x22 +#define ES8336_ADC_PGAGAIN_REG23 0x23 +#define ES8336_ADC_D2SEPGA_REG24 0x24 +#define ES8336_ADC_DMIC_REG25 0x25 +#define ES8336_ADC_MUTE_REG26 0x26 +#define ES8336_ADC_VOLUME_REG27 0x27 +#define ES8336_ADC_ALC1_REG29 0x29 +#define ES8336_ADC_ALC2_REG2A 0x2a +#define ES8336_ADC_ALC3_REG2B 0x2b +#define ES8336_ADC_ALC4_REG2C 0x2c +#define ES8336_ADC_ALC5_REG2D 0x2d +#define ES8336_ADC_ALC6_REG2E 0x2e +/* + * DAC Control + */ +#define ES8336_DAC_PDN_REG2F 0x2f +#define ES8336_DAC_SET1_REG30 0x30 +#define ES8336_DAC_SET2_REG31 0x31 +#define ES8336_DAC_SET3_REG32 0x32 +#define ES8336_DAC_VOLL_REG33 0x33 +#define ES8336_DAC_VOLR_REG34 0x34 +/* + * GPIO + */ +#define ES8336_GPIO_SEL_REG4D 0x4D +#define ES8336_GPIO_DEBUNCE_INT_REG4E 0x4E +#define ES8336_GPIO_FLAG 0x4F +/* + * TEST MODE + */ +#define ES8336_TESTMODE_REG50 0x50 +#define ES8336_TEST1_REG51 0x51 +#define ES8336_TEST2_REG52 0x52 +#define ES8336_TEST3_REG53 0x53 + +#define ES8336_IFACE ES8336_SDP_MS_BCKDIV_REG09 +#define ES8336_ADC_IFACE ES8336_SDP_ADCFMT_REG0A +#define ES8336_DAC_IFACE ES8336_SDP_DACFMT_REG0B + +#define ES8336_REGNUM 84 + +/* REGISTER 0X01 CLOCK MANAGER */ +#define ES8336_CLKMGR_MCLK_DIV_MASK (0X1<<7) +#define ES8336_CLKMGR_MCLK_DIV_NML (0X0<<7) +#define ES8336_CLKMGR_MCLK_DIV_1 (0X1<<7) +#define ES8336_CLKMGR_ADC_MCLK_MASK (0X1<<3) +#define ES8336_CLKMGR_ADC_MCLK_EN (0X1<<3) +#define ES8336_CLKMGR_ADC_MCLK_DIS (0X0<<3) +#define ES8336_CLKMGR_DAC_MCLK_MASK (0X1<<2) +#define ES8336_CLKMGR_DAC_MCLK_EN (0X1<<2) +#define ES8336_CLKMGR_DAC_MCLK_DIS (0X0<<2) +#define ES8336_CLKMGR_ADC_ANALOG_MASK (0X1<<1) +#define ES8336_CLKMGR_ADC_ANALOG_EN (0X1<<1) +#define ES8336_CLKMGR_ADC_ANALOG_DIS (0X0<<1) +#define ES8336_CLKMGR_DAC_ANALOG_MASK (0X1<<0) +#define ES8336_CLKMGR_DAC_ANALOG_EN (0X1<<0) +#define ES8336_CLKMGR_DAC_ANALOG_DIS (0X0<<0) + +/* REGISTER 0X0A */ +#define ES8336_ADCWL_MASK (0x7 << 2) +#define ES8336_ADCWL_32 (0x4 << 2) +#define ES8336_ADCWL_24 (0x0 << 2) +#define ES8336_ADCWL_20 (0x1 << 2) +#define ES8336_ADCWL_18 (0x2 << 2) +#define ES8336_ADCWL_16 (0x3 << 2) +#define ES8336_ADCFMT_MASK (0x3 << 0) +#define ES8336_ADCFMT_I2S (0x0 << 0) +#define ES8336_ADCWL_LEFT (0x1 << 0) +#define ES8336_ADCWL_RIGHT (0x2 << 0) +#define ES8336_ADCWL_PCM (0x3 << 0) + +/* REGISTER 0X0B */ +#define ES8336_DACWL_MASK (0x7 << 2) +#define ES8336_DACWL_32 (0x4 << 2) +#define ES8336_DACWL_24 (0x0 << 2) +#define ES8336_DACWL_20 (0x1 << 2) +#define ES8336_DACWL_18 (0x2 << 2) +#define ES8336_DACWL_16 (0x3 << 2) +#define ES8336_DACFMT_MASK (0x3 << 0) +#define ES8336_DACFMT_I2S (0x0 << 0) +#define ES8336_DACWL_LEFT (0x1 << 0) +#define ES8336_DACWL_RIGHT (0x2 << 0) +#define ES8336_DACWL_PCM (0x3 << 0) + +#endif diff --git a/sound/soc/codecs/es8388.c b/sound/soc/codecs/es8388.c new file mode 100644 index 0000000000000000000000000000000000000000..5945ab87b46d26a944087b67aff05c572f203e66 --- /dev/null +++ b/sound/soc/codecs/es8388.c @@ -0,0 +1,819 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * es8388.c -- ES8388 ALSA SoC Audio driver + * + * Copyright 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "es8388.h" +#include +#include + +static const unsigned int rates_12288[] = { + 8000, 12000, 16000, 24000, 32000, 48000, 96000, +}; + +static const int ratios_12288[] = { + 10, 7, 6, 4, 3, 2, 0, +}; + +static const struct snd_pcm_hw_constraint_list constraints_12288 = { + .count = ARRAY_SIZE(rates_12288), + .list = rates_12288, +}; + +static const unsigned int rates_11289[] = { + 8018, 11025, 22050, 44100, 88200, +}; + +static const int ratios_11289[] = { + 9, 7, 4, 2, 0, +}; + +static const struct snd_pcm_hw_constraint_list constraints_11289 = { + .count = ARRAY_SIZE(rates_11289), + .list = rates_11289, +}; + +#define ES8388_RATES (SNDRV_PCM_RATE_192000 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_8000_48000) +#define ES8388_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct es8388_priv { + struct regmap *regmap; + struct clk *clk; + int playback_fs; + bool deemph; + int mclkdiv2; + const struct snd_pcm_hw_constraint_list *sysclk_constraints; + const int *mclk_ratios; + bool master; +}; + +/* + * ES8388 Controls + */ +static const char * const adcpol_txt[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; +static SOC_ENUM_SINGLE_DECL(adcpol, + ES8388_ADCCONTROL6, 6, adcpol_txt); + +static const DECLARE_TLV_DB_SCALE(play_tlv, -3000, 100, 0); +static const DECLARE_TLV_DB_SCALE(dac_adc_tlv, -9600, 50, 0); +static const DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 300, 0); + +static const struct { + int rate; + unsigned int val; +} deemph_settings[] = { + { 0, ES8388_DACCONTROL6_DEEMPH_OFF }, + { 32000, ES8388_DACCONTROL6_DEEMPH_32k }, + { 44100, ES8388_DACCONTROL6_DEEMPH_44_1k }, + { 48000, ES8388_DACCONTROL6_DEEMPH_48k }, +}; + +static int es8388_set_deemph(struct snd_soc_component *component) +{ + struct es8388_priv *es8388 = snd_soc_component_get_drvdata(component); + int val, i, best; + + /* + * If we're using deemphasis select the nearest available sample + * rate. + */ + if (es8388->deemph) { + best = 0; + for (i = 1; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i].rate - es8388->playback_fs) < + abs(deemph_settings[best].rate - es8388->playback_fs)) + best = i; + } + + val = deemph_settings[best].val; + } else { + val = ES8388_DACCONTROL6_DEEMPH_OFF; + } + + dev_dbg(component->dev, "Set deemphasis %d\n", val); + + return snd_soc_component_update_bits(component, ES8388_DACCONTROL6, + ES8388_DACCONTROL6_DEEMPH_MASK, val); +} + +static int es8388_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct es8388_priv *es8388 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = es8388->deemph; + return 0; +} + +static int es8388_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct es8388_priv *es8388 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; + int ret; + + if (deemph > 1) + return -EINVAL; + + ret = es8388_set_deemph(component); + if (ret < 0) + return ret; + + es8388->deemph = deemph; + + return 0; +} + +static const struct snd_kcontrol_new es8388_snd_controls[] = { + SOC_DOUBLE_R_TLV("Capture Digital Volume", + ES8388_ADCCONTROL8, ES8388_ADCCONTROL9, + 0, 0xc0, 1, dac_adc_tlv), + + SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + es8388_get_deemph, es8388_put_deemph), + + SOC_ENUM("Capture Polarity", adcpol), + + SOC_SINGLE_TLV("Left Mixer Left Bypass Volume", + ES8388_DACCONTROL17, 3, 7, 1, bypass_tlv), + SOC_SINGLE_TLV("Left Mixer Right Bypass Volume", + ES8388_DACCONTROL19, 3, 7, 1, bypass_tlv), + SOC_SINGLE_TLV("Right Mixer Left Bypass Volume", + ES8388_DACCONTROL18, 3, 7, 1, bypass_tlv), + SOC_SINGLE_TLV("Right Mixer Right Bypass Volume", + ES8388_DACCONTROL20, 3, 7, 1, bypass_tlv), + + SOC_DOUBLE_R_TLV("PCM Volume", + ES8388_LDACVOL, ES8388_RDACVOL, + 0, ES8388_DACVOL_MAX, 1, dac_adc_tlv), + + SOC_DOUBLE_R_TLV("Output 1 Playback Volume", + ES8388_LOUT1VOL, ES8388_ROUT1VOL, + 0, ES8388_OUT1VOL_MAX, 0, play_tlv), + + SOC_DOUBLE_R_TLV("Output 2 Playback Volume", + ES8388_LOUT2VOL, ES8388_ROUT2VOL, + 0, ES8388_OUT2VOL_MAX, 0, play_tlv), + + SOC_DOUBLE_TLV("Mic PGA Volume", ES8388_ADCCONTROL1, + 4, 0, 8, 0, mic_tlv), +}; + +/* + * DAPM Controls + */ +static const char * const es8388_line_texts[] = { + "Line 1", "Line 2", "PGA", "Differential"}; + +static const struct soc_enum es8388_lline_enum = + SOC_ENUM_SINGLE(ES8388_DACCONTROL16, 3, + ARRAY_SIZE(es8388_line_texts), + es8388_line_texts); +static const struct snd_kcontrol_new es8388_left_line_controls = + SOC_DAPM_ENUM("Route", es8388_lline_enum); + +static const struct soc_enum es8388_rline_enum = + SOC_ENUM_SINGLE(ES8388_DACCONTROL16, 0, + ARRAY_SIZE(es8388_line_texts), + es8388_line_texts); +static const struct snd_kcontrol_new es8388_right_line_controls = + SOC_DAPM_ENUM("Route", es8388_lline_enum); + +/* Left Mixer */ +static const struct snd_kcontrol_new es8388_left_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", ES8388_DACCONTROL17, 7, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", ES8388_DACCONTROL17, 6, 1, 0), + SOC_DAPM_SINGLE("Right Playback Switch", ES8388_DACCONTROL18, 7, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", ES8388_DACCONTROL18, 6, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new es8388_right_mixer_controls[] = { + SOC_DAPM_SINGLE("Left Playback Switch", ES8388_DACCONTROL19, 7, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", ES8388_DACCONTROL19, 6, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", ES8388_DACCONTROL20, 7, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", ES8388_DACCONTROL20, 6, 1, 0), +}; + +static const char * const es8388_pga_sel[] = { + "Line 1", "Line 2", "Line 3", "Differential"}; + +/* Left PGA Mux */ +static const struct soc_enum es8388_lpga_enum = + SOC_ENUM_SINGLE(ES8388_ADCCONTROL2, 6, + ARRAY_SIZE(es8388_pga_sel), + es8388_pga_sel); +static const struct snd_kcontrol_new es8388_left_pga_controls = + SOC_DAPM_ENUM("Route", es8388_lpga_enum); + +/* Right PGA Mux */ +static const struct soc_enum es8388_rpga_enum = + SOC_ENUM_SINGLE(ES8388_ADCCONTROL2, 4, + ARRAY_SIZE(es8388_pga_sel), + es8388_pga_sel); +static const struct snd_kcontrol_new es8388_right_pga_controls = + SOC_DAPM_ENUM("Route", es8388_rpga_enum); + +/* Differential Mux */ +static const char * const es8388_diff_sel[] = {"Line 1", "Line 2"}; +static SOC_ENUM_SINGLE_DECL(diffmux, + ES8388_ADCCONTROL3, 7, es8388_diff_sel); +static const struct snd_kcontrol_new es8388_diffmux_controls = + SOC_DAPM_ENUM("Route", diffmux); + +/* Mono ADC Mux */ +static const char * const es8388_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; +static SOC_ENUM_SINGLE_DECL(monomux, + ES8388_ADCCONTROL3, 3, es8388_mono_mux); +static const struct snd_kcontrol_new es8388_monomux_controls = + SOC_DAPM_ENUM("Route", monomux); + +static const struct snd_soc_dapm_widget es8388_dapm_widgets[] = { + SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, + &es8388_diffmux_controls), + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &es8388_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &es8388_monomux_controls), + + SND_SOC_DAPM_MUX("Left PGA Mux", ES8388_ADCPOWER, + ES8388_ADCPOWER_AINL_OFF, 1, + &es8388_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", ES8388_ADCPOWER, + ES8388_ADCPOWER_AINR_OFF, 1, + &es8388_right_pga_controls), + + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &es8388_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &es8388_right_line_controls), + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", ES8388_ADCPOWER, + ES8388_ADCPOWER_ADCR_OFF, 1), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", ES8388_ADCPOWER, + ES8388_ADCPOWER_ADCL_OFF, 1), + + SND_SOC_DAPM_SUPPLY("DAC STM", ES8388_CHIPPOWER, + ES8388_CHIPPOWER_DACSTM_RESET, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC STM", ES8388_CHIPPOWER, + ES8388_CHIPPOWER_ADCSTM_RESET, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DAC DIG", ES8388_CHIPPOWER, + ES8388_CHIPPOWER_DACDIG_OFF, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC DIG", ES8388_CHIPPOWER, + ES8388_CHIPPOWER_ADCDIG_OFF, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DAC DLL", ES8388_CHIPPOWER, + ES8388_CHIPPOWER_DACDLL_OFF, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC DLL", ES8388_CHIPPOWER, + ES8388_CHIPPOWER_ADCDLL_OFF, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("ADC Vref", ES8388_CHIPPOWER, + ES8388_CHIPPOWER_ADCVREF_OFF, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Vref", ES8388_CHIPPOWER, + ES8388_CHIPPOWER_DACVREF_OFF, 1, NULL, 0), + + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", ES8388_DACPOWER, + ES8388_DACPOWER_RDAC_OFF, 1), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", ES8388_DACPOWER, + ES8388_DACPOWER_LDAC_OFF, 1), + + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &es8388_left_mixer_controls[0], + ARRAY_SIZE(es8388_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &es8388_right_mixer_controls[0], + ARRAY_SIZE(es8388_right_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", ES8388_DACPOWER, + ES8388_DACPOWER_ROUT2_ON, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", ES8388_DACPOWER, + ES8388_DACPOWER_LOUT2_ON, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", ES8388_DACPOWER, + ES8388_DACPOWER_ROUT1_ON, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", ES8388_DACPOWER, + ES8388_DACPOWER_LOUT1_ON, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("LINPUT2"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT2"), +}; + +static const struct snd_soc_dapm_route es8388_dapm_routes[] = { + { "Left Line Mux", "Line 1", "LINPUT1" }, + { "Left Line Mux", "Line 2", "LINPUT2" }, + { "Left Line Mux", "PGA", "Left PGA Mux" }, + { "Left Line Mux", "Differential", "Differential Mux" }, + + { "Right Line Mux", "Line 1", "RINPUT1" }, + { "Right Line Mux", "Line 2", "RINPUT2" }, + { "Right Line Mux", "PGA", "Right PGA Mux" }, + { "Right Line Mux", "Differential", "Differential Mux" }, + + { "Left PGA Mux", "Line 1", "LINPUT1" }, + { "Left PGA Mux", "Line 2", "LINPUT2" }, + { "Left PGA Mux", "Differential", "Differential Mux" }, + + { "Right PGA Mux", "Line 1", "RINPUT1" }, + { "Right PGA Mux", "Line 2", "RINPUT2" }, + { "Right PGA Mux", "Differential", "Differential Mux" }, + + { "Differential Mux", "Line 1", "LINPUT1" }, + { "Differential Mux", "Line 1", "RINPUT1" }, + { "Differential Mux", "Line 2", "LINPUT2" }, + { "Differential Mux", "Line 2", "RINPUT2" }, + + { "Left ADC Mux", "Stereo", "Left PGA Mux" }, + { "Left ADC Mux", "Mono (Left)", "Left PGA Mux" }, + { "Left ADC Mux", "Digital Mono", "Left PGA Mux" }, + + { "Right ADC Mux", "Stereo", "Right PGA Mux" }, + { "Right ADC Mux", "Mono (Right)", "Right PGA Mux" }, + { "Right ADC Mux", "Digital Mono", "Right PGA Mux" }, + + { "Left ADC", NULL, "Left ADC Mux" }, + { "Right ADC", NULL, "Right ADC Mux" }, + + { "ADC DIG", NULL, "ADC STM" }, + { "ADC DIG", NULL, "ADC Vref" }, + { "ADC DIG", NULL, "ADC DLL" }, + + { "Left ADC", NULL, "ADC DIG" }, + { "Right ADC", NULL, "ADC DIG" }, + + { "Left Line Mux", "Line 1", "LINPUT1" }, + { "Left Line Mux", "Line 2", "LINPUT2" }, + { "Left Line Mux", "PGA", "Left PGA Mux" }, + { "Left Line Mux", "Differential", "Differential Mux" }, + + { "Right Line Mux", "Line 1", "RINPUT1" }, + { "Right Line Mux", "Line 2", "RINPUT2" }, + { "Right Line Mux", "PGA", "Right PGA Mux" }, + { "Right Line Mux", "Differential", "Differential Mux" }, + + { "Left Out 1", NULL, "Left DAC" }, + { "Right Out 1", NULL, "Right DAC" }, + { "Left Out 2", NULL, "Left DAC" }, + { "Right Out 2", NULL, "Right DAC" }, + + { "Left Mixer", "Playback Switch", "Left DAC" }, + { "Left Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Left Mixer", "Right Playback Switch", "Right DAC" }, + { "Left Mixer", "Right Bypass Switch", "Right Line Mux" }, + + { "Right Mixer", "Left Playback Switch", "Left DAC" }, + { "Right Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Right Mixer", "Playback Switch", "Right DAC" }, + { "Right Mixer", "Right Bypass Switch", "Right Line Mux" }, + + { "DAC DIG", NULL, "DAC STM" }, + { "DAC DIG", NULL, "DAC Vref" }, + { "DAC DIG", NULL, "DAC DLL" }, + + { "Left DAC", NULL, "DAC DIG" }, + { "Right DAC", NULL, "DAC DIG" }, + + { "Left Out 1", NULL, "Left Mixer" }, + { "LOUT1", NULL, "Left Out 1" }, + { "Right Out 1", NULL, "Right Mixer" }, + { "ROUT1", NULL, "Right Out 1" }, + + { "Left Out 2", NULL, "Left Mixer" }, + { "LOUT2", NULL, "Left Out 2" }, + { "Right Out 2", NULL, "Right Mixer" }, + { "ROUT2", NULL, "Right Out 2" }, +}; + +static int es8388_mute(struct snd_soc_dai *dai, int mute, int direction) +{ + if (direction) + return snd_soc_component_update_bits(dai->component, ES8388_ADCCONTROL7, + ES8388_ADCCONTROL7_ADC_MUTE, + mute ? ES8388_ADCCONTROL7_ADC_MUTE : 0); + else + return snd_soc_component_update_bits(dai->component, ES8388_DACCONTROL3, + ES8388_DACCONTROL3_DACMUTE, + mute ? ES8388_DACCONTROL3_DACMUTE : 0); +} + +static int es8388_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct es8388_priv *es8388 = snd_soc_component_get_drvdata(component); + + if (es8388->master && es8388->sysclk_constraints) + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + es8388->sysclk_constraints); + + return 0; +} + +static int es8388_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct es8388_priv *es8388 = snd_soc_component_get_drvdata(component); + int i; + int reg; + int wl; + int ratio; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = ES8388_DACCONTROL2; + else + reg = ES8388_ADCCONTROL5; + + if (es8388->master) { + if (!es8388->sysclk_constraints) { + dev_err(component->dev, "No MCLK configured\n"); + return -EINVAL; + } + + for (i = 0; i < es8388->sysclk_constraints->count; i++) + if (es8388->sysclk_constraints->list[i] == + params_rate(params)) + break; + + if (i == es8388->sysclk_constraints->count) { + dev_err(component->dev, + "LRCLK %d unsupported with current clock\n", + params_rate(params)); + return -EINVAL; + } + ratio = es8388->mclk_ratios[i]; + } else { + ratio = 0; + es8388->mclkdiv2 = 0; + } + + snd_soc_component_update_bits(component, ES8388_MASTERMODE, + ES8388_MASTERMODE_MCLKDIV2, + es8388->mclkdiv2 ? ES8388_MASTERMODE_MCLKDIV2 : 0); + + switch (params_width(params)) { + case 16: + wl = 3; + break; + case 18: + wl = 2; + break; + case 20: + wl = 1; + break; + case 24: + wl = 0; + break; + case 32: + wl = 4; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + snd_soc_component_update_bits(component, ES8388_DACCONTROL1, + ES8388_DACCONTROL1_DACWL_MASK, + wl << ES8388_DACCONTROL1_DACWL_SHIFT); + + es8388->playback_fs = params_rate(params); + es8388_set_deemph(component); + } else + snd_soc_component_update_bits(component, ES8388_ADCCONTROL4, + ES8388_ADCCONTROL4_ADCWL_MASK, + wl << ES8388_ADCCONTROL4_ADCWL_SHIFT); + + return snd_soc_component_update_bits(component, reg, ES8388_RATEMASK, ratio); +} + +static int es8388_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct es8388_priv *es8388 = snd_soc_component_get_drvdata(component); + int mclkdiv2 = 0; + + switch (freq) { + case 0: + es8388->sysclk_constraints = NULL; + es8388->mclk_ratios = NULL; + break; + case 22579200: + mclkdiv2 = 1; + fallthrough; + case 11289600: + es8388->sysclk_constraints = &constraints_11289; + es8388->mclk_ratios = ratios_11289; + break; + case 24576000: + mclkdiv2 = 1; + fallthrough; + case 12288000: + es8388->sysclk_constraints = &constraints_12288; + es8388->mclk_ratios = ratios_12288; + break; + default: + return -EINVAL; + } + + es8388->mclkdiv2 = mclkdiv2; + return 0; +} + +static int es8388_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct es8388_priv *es8388 = snd_soc_component_get_drvdata(component); + u8 dac_mode = 0; + u8 adc_mode = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* Master serial port mode, with BCLK generated automatically */ + snd_soc_component_update_bits(component, ES8388_MASTERMODE, + ES8388_MASTERMODE_MSC, + ES8388_MASTERMODE_MSC); + es8388->master = true; + break; + case SND_SOC_DAIFMT_CBS_CFS: + /* Slave serial port mode */ + snd_soc_component_update_bits(component, ES8388_MASTERMODE, + ES8388_MASTERMODE_MSC, 0); + es8388->master = false; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dac_mode |= ES8388_DACCONTROL1_DACFORMAT_I2S; + adc_mode |= ES8388_ADCCONTROL4_ADCFORMAT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + dac_mode |= ES8388_DACCONTROL1_DACFORMAT_RJUST; + adc_mode |= ES8388_ADCCONTROL4_ADCFORMAT_RJUST; + break; + case SND_SOC_DAIFMT_LEFT_J: + dac_mode |= ES8388_DACCONTROL1_DACFORMAT_LJUST; + adc_mode |= ES8388_ADCCONTROL4_ADCFORMAT_LJUST; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) + return -EINVAL; + + snd_soc_component_update_bits(component, ES8388_DACCONTROL1, + ES8388_DACCONTROL1_DACFORMAT_MASK, dac_mode); + snd_soc_component_update_bits(component, ES8388_ADCCONTROL4, + ES8388_ADCCONTROL4_ADCFORMAT_MASK, adc_mode); + + return 0; +} + +static int es8388_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VREF, VMID=2x50k, digital enabled */ + snd_soc_component_write(component, ES8388_CHIPPOWER, 0); + snd_soc_component_update_bits(component, ES8388_CONTROL1, + ES8388_CONTROL1_VMIDSEL_MASK | + ES8388_CONTROL1_ENREF, + ES8388_CONTROL1_VMIDSEL_50k | + ES8388_CONTROL1_ENREF); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + snd_soc_component_update_bits(component, ES8388_CONTROL1, + ES8388_CONTROL1_VMIDSEL_MASK | + ES8388_CONTROL1_ENREF, + ES8388_CONTROL1_VMIDSEL_5k | + ES8388_CONTROL1_ENREF); + + /* Charge caps */ + msleep(100); + } + + snd_soc_component_write(component, ES8388_CONTROL2, + ES8388_CONTROL2_OVERCURRENT_ON | + ES8388_CONTROL2_THERMAL_SHUTDOWN_ON); + + /* VREF, VMID=2*500k, digital stopped */ + snd_soc_component_update_bits(component, ES8388_CONTROL1, + ES8388_CONTROL1_VMIDSEL_MASK | + ES8388_CONTROL1_ENREF, + ES8388_CONTROL1_VMIDSEL_500k | + ES8388_CONTROL1_ENREF); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, ES8388_CONTROL1, + ES8388_CONTROL1_VMIDSEL_MASK | + ES8388_CONTROL1_ENREF, + 0); + break; + } + return 0; +} + +static const struct snd_soc_dai_ops es8388_dai_ops = { + .startup = es8388_startup, + .hw_params = es8388_hw_params, + .mute_stream = es8388_mute, + .set_sysclk = es8388_set_sysclk, + .set_fmt = es8388_set_dai_fmt, + .no_capture_mute = 1, +}; + +static struct snd_soc_dai_driver es8388_dai = { + .name = "es8388-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = ES8388_RATES, + .formats = ES8388_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = ES8388_RATES, + .formats = ES8388_FORMATS, + }, + .ops = &es8388_dai_ops, + .symmetric_rate = 1, +}; + +static int es8388_suspend(struct snd_soc_component *component) +{ + return 0; +} + +static int es8388_resume(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + struct es8388_priv *es8388; + int ret; + + es8388 = snd_soc_component_get_drvdata(component); + + regcache_mark_dirty(regmap); + ret = regcache_sync(regmap); + if (ret) { + dev_err(component->dev, "unable to sync regcache\n"); + return ret; + } + + return 0; +} + +static int es8388_component_probe(struct snd_soc_component *component) +{ + snd_soc_component_write(component, ES8388_ADCPOWER, 0xf0); + snd_soc_component_write(component, ES8388_CONTROL1, 0x30); + snd_soc_component_write(component, ES8388_DACCONTROL21, 0x80); + snd_soc_component_write(component, ES8388_ADCCONTROL10, 0xda); + + return 0; +} + +static void es8388_remove(struct snd_soc_component *component) +{ +} + +const struct regmap_config es8388_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ES8388_REG_MAX, + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, +}; +EXPORT_SYMBOL_GPL(es8388_regmap_config); + +static const struct snd_soc_component_driver es8388_component_driver = { + .probe = es8388_component_probe, + .remove = es8388_remove, + .suspend = es8388_suspend, + .resume = es8388_resume, + .set_bias_level = es8388_set_bias_level, + .controls = es8388_snd_controls, + .num_controls = ARRAY_SIZE(es8388_snd_controls), + .dapm_widgets = es8388_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(es8388_dapm_widgets), + .dapm_routes = es8388_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(es8388_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, +}; + +int es8388_probe(struct device *dev, struct regmap *regmap) +{ + struct es8388_priv *es8388; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + es8388 = devm_kzalloc(dev, sizeof(*es8388), GFP_KERNEL); + if (es8388 == NULL) + return -ENOMEM; + + es8388->regmap = regmap; + + dev_set_drvdata(dev, es8388); + + return devm_snd_soc_register_component(dev, + &es8388_component_driver, &es8388_dai, 1); +} +EXPORT_SYMBOL_GPL(es8388_probe); + +static const struct i2c_device_id es8388_id[] = { + { "es8388", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, es8388_id); + +static const struct of_device_id es8388_of_match[] = { + { .compatible = "everest,es8388", }, + { } +}; +MODULE_DEVICE_TABLE(of, es8388_of_match); + +static struct acpi_device_id es8388_acpi_match[] = { + {"ESSX8388", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, es8388_acpi_match); + +static int es8388_i2c_probe(struct i2c_client *i2c) +{ + return es8388_probe(&i2c->dev, + devm_regmap_init_i2c(i2c, &es8388_regmap_config)); +} + +static struct i2c_driver es8388_i2c_driver = { + .driver = { + .name = "es8388", + .of_match_table = es8388_of_match, + .acpi_match_table = es8388_acpi_match, + }, + .probe = es8388_i2c_probe, + .id_table = es8388_id, +}; + +module_i2c_driver(es8388_i2c_driver); + +MODULE_DESCRIPTION("ASoC ES8388 driver"); +MODULE_AUTHOR("Yiqun Zhang "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es8388.h b/sound/soc/codecs/es8388.h new file mode 100644 index 0000000000000000000000000000000000000000..5858a71261fba6d8258eac45edc2d0cfcbda39ef --- /dev/null +++ b/sound/soc/codecs/es8388.h @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * es8388.h -- ES8388 ALSA SoC Audio driver + */ + +#ifndef _ES8388_H +#define _ES8388_H + +#include + +struct device; + +extern const struct regmap_config es8388_regmap_config; +int es8388_probe(struct device *dev, struct regmap *regmap); + +#define ES8388_DACLVOL 46 +#define ES8388_DACRVOL 47 +#define ES8388_DACCTL 28 +#define ES8388_RATEMASK (0x1f << 0) + +#define ES8388_CONTROL1 0x00 +#define ES8388_CONTROL1_VMIDSEL_OFF (0 << 0) +#define ES8388_CONTROL1_VMIDSEL_50k (1 << 0) +#define ES8388_CONTROL1_VMIDSEL_500k (2 << 0) +#define ES8388_CONTROL1_VMIDSEL_5k (3 << 0) +#define ES8388_CONTROL1_VMIDSEL_MASK (3 << 0) +#define ES8388_CONTROL1_ENREF (1 << 2) +#define ES8388_CONTROL1_SEQEN (1 << 3) +#define ES8388_CONTROL1_SAMEFS (1 << 4) +#define ES8388_CONTROL1_DACMCLK_ADC (0 << 5) +#define ES8388_CONTROL1_DACMCLK_DAC (1 << 5) +#define ES8388_CONTROL1_LRCM (1 << 6) +#define ES8388_CONTROL1_SCP_RESET (1 << 7) + +#define ES8388_CONTROL2 0x01 +#define ES8388_CONTROL2_VREF_BUF_OFF (1 << 0) +#define ES8388_CONTROL2_VREF_LOWPOWER (1 << 1) +#define ES8388_CONTROL2_IBIASGEN_OFF (1 << 2) +#define ES8388_CONTROL2_ANALOG_OFF (1 << 3) +#define ES8388_CONTROL2_VREF_BUF_LOWPOWER (1 << 4) +#define ES8388_CONTROL2_VCM_MOD_LOWPOWER (1 << 5) +#define ES8388_CONTROL2_OVERCURRENT_ON (1 << 6) +#define ES8388_CONTROL2_THERMAL_SHUTDOWN_ON (1 << 7) + +#define ES8388_CHIPPOWER 0x02 +#define ES8388_CHIPPOWER_DACVREF_OFF 0 +#define ES8388_CHIPPOWER_ADCVREF_OFF 1 +#define ES8388_CHIPPOWER_DACDLL_OFF 2 +#define ES8388_CHIPPOWER_ADCDLL_OFF 3 +#define ES8388_CHIPPOWER_DACSTM_RESET 4 +#define ES8388_CHIPPOWER_ADCSTM_RESET 5 +#define ES8388_CHIPPOWER_DACDIG_OFF 6 +#define ES8388_CHIPPOWER_ADCDIG_OFF 7 + +#define ES8388_ADCPOWER 0x03 +#define ES8388_ADCPOWER_INT1_LOWPOWER 0 +#define ES8388_ADCPOWER_FLASH_ADC_LOWPOWER 1 +#define ES8388_ADCPOWER_ADC_BIAS_GEN_OFF 2 +#define ES8388_ADCPOWER_MIC_BIAS_OFF 3 +#define ES8388_ADCPOWER_ADCR_OFF 4 +#define ES8388_ADCPOWER_ADCL_OFF 5 +#define ES8388_ADCPOWER_AINR_OFF 6 +#define ES8388_ADCPOWER_AINL_OFF 7 + +#define ES8388_DACPOWER 0x04 +#define ES8388_DACPOWER_OUT3_ON 0 +#define ES8388_DACPOWER_MONO_ON 1 +#define ES8388_DACPOWER_ROUT2_ON 2 +#define ES8388_DACPOWER_LOUT2_ON 3 +#define ES8388_DACPOWER_ROUT1_ON 4 +#define ES8388_DACPOWER_LOUT1_ON 5 +#define ES8388_DACPOWER_RDAC_OFF 6 +#define ES8388_DACPOWER_LDAC_OFF 7 + +#define ES8388_CHIPLOPOW1 0x05 +#define ES8388_CHIPLOPOW2 0x06 +#define ES8388_ANAVOLMANAG 0x07 + +#define ES8388_MASTERMODE 0x08 +#define ES8388_MASTERMODE_BCLKDIV (0 << 0) +#define ES8388_MASTERMODE_BCLK_INV (1 << 5) +#define ES8388_MASTERMODE_MCLKDIV2 (1 << 6) +#define ES8388_MASTERMODE_MSC (1 << 7) + +#define ES8388_ADCCONTROL1 0x09 +#define ES8388_ADCCONTROL2 0x0a +#define ES8388_ADCCONTROL3 0x0b + +#define ES8388_ADCCONTROL4 0x0c +#define ES8388_ADCCONTROL4_ADCFORMAT_MASK (3 << 0) +#define ES8388_ADCCONTROL4_ADCFORMAT_I2S (0 << 0) +#define ES8388_ADCCONTROL4_ADCFORMAT_LJUST (1 << 0) +#define ES8388_ADCCONTROL4_ADCFORMAT_RJUST (2 << 0) +#define ES8388_ADCCONTROL4_ADCFORMAT_PCM (3 << 0) +#define ES8388_ADCCONTROL4_ADCWL_SHIFT 2 +#define ES8388_ADCCONTROL4_ADCWL_MASK (7 << 2) +#define ES8388_ADCCONTROL4_ADCLRP_I2S_POL_NORMAL (0 << 5) +#define ES8388_ADCCONTROL4_ADCLRP_I2S_POL_INV (1 << 5) +#define ES8388_ADCCONTROL4_ADCLRP_PCM_MSB_CLK2 (0 << 5) +#define ES8388_ADCCONTROL4_ADCLRP_PCM_MSB_CLK1 (1 << 5) + +#define ES8388_ADCCONTROL5 0x0d +#define ES8388_ADCCONTROL5_RATEMASK (0x1f << 0) + +#define ES8388_ADCCONTROL6 0x0e + +#define ES8388_ADCCONTROL7 0x0f +#define ES8388_ADCCONTROL7_ADC_MUTE (1 << 2) +#define ES8388_ADCCONTROL7_ADC_LER (1 << 3) +#define ES8388_ADCCONTROL7_ADC_ZERO_CROSS (1 << 4) +#define ES8388_ADCCONTROL7_ADC_SOFT_RAMP (1 << 5) +#define ES8388_ADCCONTROL7_ADC_RAMP_RATE_4 (0 << 6) +#define ES8388_ADCCONTROL7_ADC_RAMP_RATE_8 (1 << 6) +#define ES8388_ADCCONTROL7_ADC_RAMP_RATE_16 (2 << 6) +#define ES8388_ADCCONTROL7_ADC_RAMP_RATE_32 (3 << 6) + +#define ES8388_ADCCONTROL8 0x10 +#define ES8388_ADCCONTROL9 0x11 +#define ES8388_ADCCONTROL10 0x12 +#define ES8388_ADCCONTROL11 0x13 +#define ES8388_ADCCONTROL12 0x14 +#define ES8388_ADCCONTROL13 0x15 +#define ES8388_ADCCONTROL14 0x16 + +#define ES8388_DACCONTROL1 0x17 +#define ES8388_DACCONTROL1_DACFORMAT_MASK (3 << 1) +#define ES8388_DACCONTROL1_DACFORMAT_I2S (0 << 1) +#define ES8388_DACCONTROL1_DACFORMAT_LJUST (1 << 1) +#define ES8388_DACCONTROL1_DACFORMAT_RJUST (2 << 1) +#define ES8388_DACCONTROL1_DACFORMAT_PCM (3 << 1) +#define ES8388_DACCONTROL1_DACWL_SHIFT 3 +#define ES8388_DACCONTROL1_DACWL_MASK (7 << 3) +#define ES8388_DACCONTROL1_DACLRP_I2S_POL_NORMAL (0 << 6) +#define ES8388_DACCONTROL1_DACLRP_I2S_POL_INV (1 << 6) +#define ES8388_DACCONTROL1_DACLRP_PCM_MSB_CLK2 (0 << 6) +#define ES8388_DACCONTROL1_DACLRP_PCM_MSB_CLK1 (1 << 6) +#define ES8388_DACCONTROL1_LRSWAP (1 << 7) + +#define ES8388_DACCONTROL2 0x18 +#define ES8388_DACCONTROL2_RATEMASK (0x1f << 0) +#define ES8388_DACCONTROL2_DOUBLESPEED (1 << 5) + +#define ES8388_DACCONTROL3 0x19 +#define ES8388_DACCONTROL3_AUTOMUTE (1 << 2) +#define ES8388_DACCONTROL3_DACMUTE (1 << 2) +#define ES8388_DACCONTROL3_LEFTGAINVOL (1 << 3) +#define ES8388_DACCONTROL3_DACZEROCROSS (1 << 4) +#define ES8388_DACCONTROL3_DACSOFTRAMP (1 << 5) +#define ES8388_DACCONTROL3_DACRAMPRATE (3 << 6) + +#define ES8388_LDACVOL 0x1a +#define ES8388_LDACVOL_MASK (0 << 0) +#define ES8388_LDACVOL_MAX (0xc0) + +#define ES8388_RDACVOL 0x1b +#define ES8388_RDACVOL_MASK (0 << 0) +#define ES8388_RDACVOL_MAX (0xc0) + +#define ES8388_DACVOL_MAX (0xc0) + +#define ES8388_DACCONTROL4 0x1a +#define ES8388_DACCONTROL5 0x1b + +#define ES8388_DACCONTROL6 0x1c +#define ES8388_DACCONTROL6_CLICKFREE (1 << 3) +#define ES8388_DACCONTROL6_DAC_INVR (1 << 4) +#define ES8388_DACCONTROL6_DAC_INVL (1 << 5) +#define ES8388_DACCONTROL6_DEEMPH_MASK (3 << 6) +#define ES8388_DACCONTROL6_DEEMPH_OFF (0 << 6) +#define ES8388_DACCONTROL6_DEEMPH_32k (1 << 6) +#define ES8388_DACCONTROL6_DEEMPH_44_1k (2 << 6) +#define ES8388_DACCONTROL6_DEEMPH_48k (3 << 6) + +#define ES8388_DACCONTROL7 0x1d +#define ES8388_DACCONTROL7_VPP_SCALE_3p5 (0 << 0) +#define ES8388_DACCONTROL7_VPP_SCALE_4p0 (1 << 0) +#define ES8388_DACCONTROL7_VPP_SCALE_3p0 (2 << 0) +#define ES8388_DACCONTROL7_VPP_SCALE_2p5 (3 << 0) +#define ES8388_DACCONTROL7_SHELVING_STRENGTH (1 << 2) /* In eights */ +#define ES8388_DACCONTROL7_MONO (1 << 5) +#define ES8388_DACCONTROL7_ZEROR (1 << 6) +#define ES8388_DACCONTROL7_ZEROL (1 << 7) + +/* Shelving filter */ +#define ES8388_DACCONTROL8 0x1e +#define ES8388_DACCONTROL9 0x1f +#define ES8388_DACCONTROL10 0x20 +#define ES8388_DACCONTROL11 0x21 +#define ES8388_DACCONTROL12 0x22 +#define ES8388_DACCONTROL13 0x23 +#define ES8388_DACCONTROL14 0x24 +#define ES8388_DACCONTROL15 0x25 + +#define ES8388_DACCONTROL16 0x26 +#define ES8388_DACCONTROL16_RMIXSEL_RIN1 (0 << 0) +#define ES8388_DACCONTROL16_RMIXSEL_RIN2 (1 << 0) +#define ES8388_DACCONTROL16_RMIXSEL_RIN3 (2 << 0) +#define ES8388_DACCONTROL16_RMIXSEL_RADC (3 << 0) +#define ES8388_DACCONTROL16_LMIXSEL_LIN1 (0 << 3) +#define ES8388_DACCONTROL16_LMIXSEL_LIN2 (1 << 3) +#define ES8388_DACCONTROL16_LMIXSEL_LIN3 (2 << 3) +#define ES8388_DACCONTROL16_LMIXSEL_LADC (3 << 3) + +#define ES8388_DACCONTROL17 0x27 +#define ES8388_DACCONTROL17_LI2LOVOL (7 << 3) +#define ES8388_DACCONTROL17_LI2LO (1 << 6) +#define ES8388_DACCONTROL17_LD2LO (1 << 7) + +#define ES8388_DACCONTROL18 0x28 +#define ES8388_DACCONTROL18_RI2LOVOL (7 << 3) +#define ES8388_DACCONTROL18_RI2LO (1 << 6) +#define ES8388_DACCONTROL18_RD2LO (1 << 7) + +#define ES8388_DACCONTROL19 0x29 +#define ES8388_DACCONTROL19_LI2ROVOL (7 << 3) +#define ES8388_DACCONTROL19_LI2RO (1 << 6) +#define ES8388_DACCONTROL19_LD2RO (1 << 7) + +#define ES8388_DACCONTROL20 0x2a +#define ES8388_DACCONTROL20_RI2ROVOL (7 << 3) +#define ES8388_DACCONTROL20_RI2RO (1 << 6) +#define ES8388_DACCONTROL20_RD2RO (1 << 7) + +#define ES8388_DACCONTROL21 0x2b +#define ES8388_DACCONTROL21_LI2MOVOL (7 << 3) +#define ES8388_DACCONTROL21_LI2MO (1 << 6) +#define ES8388_DACCONTROL21_LD2MO (1 << 7) + +#define ES8388_DACCONTROL22 0x2c +#define ES8388_DACCONTROL22_RI2MOVOL (7 << 3) +#define ES8388_DACCONTROL22_RI2MO (1 << 6) +#define ES8388_DACCONTROL22_RD2MO (1 << 7) + +#define ES8388_DACCONTROL23 0x2d +#define ES8388_DACCONTROL23_MOUTINV (1 << 1) +#define ES8388_DACCONTROL23_HPSWPOL (1 << 2) +#define ES8388_DACCONTROL23_HPSWEN (1 << 3) +#define ES8388_DACCONTROL23_VROI_1p5k (0 << 4) +#define ES8388_DACCONTROL23_VROI_40k (1 << 4) +#define ES8388_DACCONTROL23_OUT3_VREF (0 << 5) +#define ES8388_DACCONTROL23_OUT3_ROUT1 (1 << 5) +#define ES8388_DACCONTROL23_OUT3_MONOOUT (2 << 5) +#define ES8388_DACCONTROL23_OUT3_RIGHT_MIXER (3 << 5) +#define ES8388_DACCONTROL23_ROUT2INV (1 << 7) + +/* LOUT1 Amplifier */ +#define ES8388_LOUT1VOL 0x2e +#define ES8388_LOUT1VOL_MASK (0 << 5) +#define ES8388_LOUT1VOL_MAX (0x24) + +/* ROUT1 Amplifier */ +#define ES8388_ROUT1VOL 0x2f +#define ES8388_ROUT1VOL_MASK (0 << 5) +#define ES8388_ROUT1VOL_MAX (0x24) + +#define ES8388_OUT1VOL_MAX (0x24) + +/* LOUT2 Amplifier */ +#define ES8388_LOUT2VOL 0x30 +#define ES8388_LOUT2VOL_MASK (0 << 5) +#define ES8388_LOUT2VOL_MAX (0x24) + +/* ROUT2 Amplifier */ +#define ES8388_ROUT2VOL 0x31 +#define ES8388_ROUT2VOL_MASK (0 << 5) +#define ES8388_ROUT2VOL_MAX (0x24) + +#define ES8388_OUT2VOL_MAX (0x24) + +/* Mono Out Amplifier */ +#define ES8388_MONOOUTVOL 0x32 +#define ES8388_MONOOUTVOL_MASK (0 << 5) +#define ES8388_MONOOUTVOL_MAX (0x24) + +#define ES8388_DACCONTROL29 0x33 +#define ES8388_DACCONTROL30 0x34 + +#define ES8388_SYSCLK 0 + +#define ES8388_REG_MAX 0x35 + +#define ES8388_1536FS 1536 +#define ES8388_1024FS 1024 +#define ES8388_768FS 768 +#define ES8388_512FS 512 +#define ES8388_384FS 384 +#define ES8388_256FS 256 +#define ES8388_128FS 128 + +#endif diff --git a/sound/soc/phytium/Kconfig b/sound/soc/phytium/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..b4fd9ae0b654bd337194dd15e02663060dfdf6a6 --- /dev/null +++ b/sound/soc/phytium/Kconfig @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0 +config SND_SOC_PHYTIUM_I2S + tristate "Phytium I2S Device Driver" + depends on ARCH_PHYTIUM + help + Say Y or M if you want to add support for I2S driver for + Phytium I2S device . The device supports 2 channels each + for play and record. + +config SND_PMDK_ES8388 + tristate "Phytium machine support with ES8388" + depends on I2C && SND_SOC_PHYTIUM_I2S + select SND_SOC_ES8388 + help + Say Y if you want to add Phytium machine support for + ES8388 codecs. + +config SND_PMDK_ES8336 + tristate "Phytium machine support with ES8336" + depends on I2C && SND_SOC_PHYTIUM_I2S + select SND_SOC_ES8336 + help + Say Y if you want to add Phytium machine support for + ES8336 codecs. + +config SND_PMDK_DP + tristate "Phytium machine support with DP" + depends on I2C && SND_SOC_PHYTIUM_I2S + select SND_SOC_HDMI_CODEC + help + Say Y if you want to add Phytium machine support for + Displayport. diff --git a/sound/soc/phytium/Makefile b/sound/soc/phytium/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..45cd2ed9d420cce7c1e2cd52a617e7cc4e6c0175 --- /dev/null +++ b/sound/soc/phytium/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# PHYTIUM Platform Support + +snd-soc-phytium-i2s-objs :=phytium_i2s.o +obj-$(CONFIG_SND_SOC_PHYTIUM_I2S) += snd-soc-phytium-i2s.o + +snd-soc-pmdk-es8388-objs :=pmdk_es8388.o +obj-$(CONFIG_SND_PMDK_ES8388) += snd-soc-pmdk-es8388.o + +snd-soc-pmdk-es8336-objs :=pmdk_es8336.o +obj-$(CONFIG_SND_PMDK_ES8336) += snd-soc-pmdk-es8336.o + +snd-soc-pmdk-dp-objs :=pmdk_dp.o +obj-$(CONFIG_SND_PMDK_DP) += snd-soc-pmdk-dp.o diff --git a/sound/soc/phytium/local.h b/sound/soc/phytium/local.h new file mode 100644 index 0000000000000000000000000000000000000000..47c3c6c8cfbb01a64ecf11385ee60dea7e3116e8 --- /dev/null +++ b/sound/soc/phytium/local.h @@ -0,0 +1,319 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020-2023, Phytium Technology Co., Ltd. + */ + +#ifndef __PHYTIUM_I2S_LOCAL_H +#define __PHYTIUM_I2S_LOCAL_H + +#include +#include +#include +#include + +/* I2S clk setting*/ +#define CLK_CFG0 0xc00 +#define CLK_CFG1 0xc04 + +/* Common register for all channel */ +#define I2S_IER 0x000 +#define IRER 0x004 +#define ITER 0x008 +#define CER 0x00C + +#define RXFFR 0x014 +#define TXFFR 0x018 + +/* Interrupt status register fields */ +#define ISR_TXFO BIT(5) +#define ISR_TXFE BIT(4) +#define ISR_RXFO BIT(1) +#define ISR_RXDA BIT(0) + +/* I2STxRxRegisters for all channels */ +#define LRBR_LTHR(x) (0x40 * x + 0x020) +#define RRBR_RTHR(x) (0x40 * x + 0x024) +#define RER(x) (0x40 * x + 0x028) + +#define RCR(x) (0x40 * x + 0x030) + +#define ISR(x) (0x40 * x + 0x038) +#define IMR(x) (0x40 * x + 0x03C) +#define ROR(x) (0x40 * x + 0x040) +#define TOR(x) (0x40 * x + 0x044) +#define RFCR(x) (0x40 * x + 0x048) +#define TFCR(x) (0x40 * x + 0x04C) +#define RFF(x) (0x40 * x + 0x050) +#define TFF(x) (0x40 * x + 0x054) + +/* Enable txd and rxd block channel0~3 */ +#define TER(x) (0x40 * x + 0x02C) +#define CCR 0x010 +#define TCR(x) (0x40 * x + 0x034) + +/* I2SCOMPRegisters */ +#define I2S_COMP_PARAM_2 0x01F0 +#define I2S_COMP_PARAM_1 0x01F4 +#define I2S_COMP_VERSION 0x01F8 +#define I2S_COMP_TYPE 0x01FC + +/* I2S AND DMA */ +#define DMA_GCAP 0x0024 + +#define DMA_CHAL_CONFG1 0x0028 + +#define DMA_CHAL_CONFG0 0x0004 +#define DMA_MASK_INT 0x000c +#define DMA_BDLPU(x) (0x40 * x + 0x0040) +#define DMA_BDLPL(x) (0x40 * x + 0x0044) +#define DMA_CHALX_DEV_ADDR(x) (0x40 * x + 0x0048) +#define DMA_CHALX_CBL(x) (0x40 * x + 0x0054) +#define DMA_CHALX_LVI(x) (0x40 * x + 0x004c) + +#define DMA_CHALX_DSIZE(x) (0x40 * x + 0x0064) +#define DMA_CHALX_DLENTH(x) (0x40 * x + 0x0068) +#define DMA_CHALX_CTL(x) (0x40 * x + 0x0058) + + +#define DMA_CTL 0x0000 + +#define DMA_LPIB(x) (0x40 * x + 0x0050) + +#define DMA_STS 0x0008 + +/* max number of fragments - we may use more if allocating more pages for BDL */ +#define BDL_SIZE 4096 +#define AZX_MAX_BDL_ENTRIES (BDL_SIZE / 16) + +/* + * Component parameter register fields - define the I2S block's + * configuration. + */ +#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25) +#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22) +#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19) +#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16) +#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9) +#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7) +#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6) +#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5) +#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4) +#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2) +#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0) + +#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10) +#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7) +#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3) +#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0) + +/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */ +#define COMP_MAX_WORDSIZE (1 << 3) +#define COMP_MAX_DATA_WIDTH (1 << 2) + +#define MAX_CHANNEL_NUM 8 +#define MIN_CHANNEL_NUM 2 + +#define azx_bus(chip) (&(chip)->bus.core) +#define bus_to_azx(_bus) container_of(_bus, struct azx, bus.core) + +#define I2S_UNSOL_QUEUE_SIZE 64 +#define I2S_MAX_CODECS 8 /* limit by controller side */ + +#define azx_stream(dev) (&(dev)->core) + +struct i2s_clk_config_data { + int chan_nr; + u32 data_width; + u32 sample_rate; +}; + +struct i2sc_bus { + struct device *dev; + const struct i2s_bus_ops *ops; + const struct i2s_io_ops *io_ops; + const struct i2s_ext_bus_ops *ext_ops; + + /* h/w resources */ + unsigned long addr; + void __iomem *remap_addr; + int irq; + + /* codec linked list */ + struct list_head codec_list; + unsigned int num_codecs; + + unsigned int unsol_rp, unsol_wp; + struct work_struct unsol_work; + + struct snd_dma_buffer bdl0; + struct snd_dma_buffer bdl1; + + /* i2s_stream linked list */ + struct list_head stream_list; + + bool reverse_assign; /* assign devices in reverse order */ + + int bdl_pos_adj; /* BDL position adjustment */ + + /* locks */ + spinlock_t reg_lock; +}; + +struct i2s_bus { + struct i2sc_bus core; + + struct snd_card *card; + + struct pci_dev *pci; + + struct mutex prepare_mutex; +}; + + +/* + * i2s stream + */ +struct i2s_stream { + struct i2sc_bus *bus; + struct snd_dma_buffer bdl; /* BDL buffer */ + __le32 *posbuf; /* position buffer pointer */ + int direction; /* playback / capture (SNDRV_PCM_STREAM_*) */ + + unsigned int bufsize; /* size of the play buffer in bytes */ + unsigned int period_bytes; /* size of the period in bytes */ + unsigned int frags; /* number for period in the play buffer */ + unsigned int fifo_size; /* FIFO size */ + + void __iomem *sd_addr; /* stream descriptor pointer */ + + u32 sd_int_sta_mask; /* stream int status mask */ + + /* pcm support */ + struct snd_pcm_substream *substream; /* assigned substream, + * set in PCM open + */ + unsigned int format_val; /* format value to be set in the + * controller and the codec + */ + unsigned char stream_tag; /* assigned stream */ + unsigned char index; /* stream index */ + int assigned_key; /* last device# key assigned to */ + + bool opened; + bool running; + bool prepared; + bool no_period_wakeup; + + int delay_negative_threshold; + + struct list_head list; + +}; + +struct azx_dev { + struct i2s_stream core; + unsigned int irq_pending:1; +}; + +/* PCM setup */ +static inline struct azx_dev *get_azx_dev(struct snd_pcm_substream *substream) +{ + return substream->runtime->private_data; +} + +#define AZX_MAX_CODECS HDA_MAX_CODECS +#define AZX_DEFAULT_CODECS 4 + +#define stream_to_azx_dev(s) container_of(s, struct azx_dev, core) + +struct azx; + +struct i2s_controller_ops { + int (*substream_alloc_pages)(struct azx *chip, + struct snd_pcm_substream *substream, + size_t size); + int (*substream_free_pages)(struct azx *chip, + struct snd_pcm_substream *substream); + int (*position_check)(struct azx *chip, struct azx_dev *azx_dev); +}; + +struct i2s_io_ops { + int (*dma_alloc_pages)(struct i2sc_bus *bus, int type, size_t size, + struct snd_dma_buffer *buf); + void (*dma_free_pages)(struct i2sc_bus *bus, + struct snd_dma_buffer *buf); +}; + +struct azx { + struct i2s_bus bus; + + struct snd_card *card; + struct pci_dev *pci; + int dev_index; + + int playback_streams; + int playback_index_offset; + int capture_streams; + int capture_index_offset; + int num_streams; + + /* Register interaction. */ + const struct i2s_controller_ops *ops; + + /* locks */ + struct mutex open_mutex; /* Prevents concurrent open/close operations */ + + /* PCM */ + struct list_head pcm_list; /* azx_pcm list */ + + /* flags */ + int bdl_pos_adj; + unsigned int running:1; + unsigned int region_requested:1; + unsigned int disabled:1; +}; + +struct i2s_phytium { + struct azx chip; + struct snd_pcm_substream *substream; + struct device *dev; + struct device *pdev; + u32 paddr; + void __iomem *regs; + void __iomem *regs_db; + int irq_id; + + /* for pending irqs */ + struct work_struct irq_pending_work; + + /* sync probing */ + struct completion probe_wait; + struct work_struct probe_work; + + /* extra flags */ + unsigned int pcie:1; + unsigned int irq_pending_warned:1; + unsigned int probe_continued:1; + unsigned int i2s_dp:1; + + unsigned int i2s_reg_comp1; + unsigned int i2s_reg_comp2; + struct clk *clk; + unsigned int capability; + unsigned int quirks; + u32 fifo_th; + int active; + u32 xfer_resolution; + u32 ccr; + u32 clk_base; + u32 cfg; + struct i2s_clk_config_data config; + + /*azx_dev*/ + struct i2s_stream core; +}; + +#define azx_alloc_stream_pages(chip) \ + snd_i2s_bus_alloc_stream_pages(azx_bus(chip)) + +#endif diff --git a/sound/soc/phytium/phytium_i2s.c b/sound/soc/phytium/phytium_i2s.c new file mode 100644 index 0000000000000000000000000000000000000000..d3966b705beb6a9aad06107c4ec3e122da0d1fd4 --- /dev/null +++ b/sound/soc/phytium/phytium_i2s.c @@ -0,0 +1,1433 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium I2S ASoC driver + * + * Copyright (C) 2020-2023, Phytium Technology Co., Ltd. + * + * Derived from sound/soc/dwc/dwc-i2s.c + * Copyright (C) 2010 ST Microelectronics + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "local.h" + +#define NUM_CAPTURE 1 +#define NUM_PLAYBACK 1 + +#define PHYTIUM_I2S_PLAY (1 << 0) +#define PHYTIUM_I2S_RECORD (1 << 1) +#define PHYTIUM_I2S_SLAVE (1 << 2) +#define PHYTIUM_I2S_MASTER (1 << 3) + +#define PHYTIUM_I2S_QUIRK_16BIT_IDX_OVERRIDE (1 << 2) + +#define TWO_CHANNEL_SUPPORT 2 /* up to 2.0 */ +#define FOUR_CHANNEL_SUPPORT 4 /* up to 3.1 */ +#define SIX_CHANNEL_SUPPORT 6 /* up to 5.1 */ +#define EIGHT_CHANNEL_SUPPORT 8 /* up to 7.1 */ + +struct pdata_px210_mfd { + struct device *dev; + char *name; + int clk_base; +}; + +static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val) +{ + writel(val, io_base + reg); +} + +static inline u32 i2s_read_reg(void __iomem *io_base, int reg) +{ + return readl(io_base + reg); +} + +static inline void i2s_disable_channels(struct i2s_phytium *dev, u32 stream) +{ + u32 i = 0; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < 4; i++) + i2s_write_reg(dev->regs, TER(i), 0); + } else { + for (i = 0; i < 4; i++) + i2s_write_reg(dev->regs, RER(i), 0); + } +} + +static int substream_free_pages(struct azx *chip, + struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static void stream_update(struct i2sc_bus *bus, struct i2s_stream *s) +{ + struct azx *chip = bus_to_azx(bus); + + struct azx_dev *azx_dev = stream_to_azx_dev(s); + + /* check whether this IRQ is really acceptable */ + if (!chip->ops->position_check || + chip->ops->position_check(chip, azx_dev)) { + spin_unlock(&bus->reg_lock); + snd_pcm_period_elapsed(azx_stream(azx_dev)->substream); + spin_lock(&bus->reg_lock); + } +} + +int snd_i2s_bus_handle_stream_irq(struct i2sc_bus *bus, unsigned int status, + void (*ack)(struct i2sc_bus *, struct i2s_stream *)) +{ + struct i2s_stream *azx_dev; + u32 sd_status, qc_sd_status; + int handled = 0; + + list_for_each_entry(azx_dev, &bus->stream_list, list) { + if (status & azx_dev->sd_int_sta_mask) { + sd_status = i2s_read_reg(azx_dev->sd_addr, DMA_STS); + i2s_write_reg(azx_dev->sd_addr, DMA_STS, azx_dev->sd_int_sta_mask); + qc_sd_status = i2s_read_reg(azx_dev->sd_addr, DMA_STS); + handled |= 1 << azx_dev->index; + azx_dev->running = 1; + if (!azx_dev->substream || !azx_dev->running || + !(sd_status & 0xffffffff)) { + continue; + } + if (ack) + ack(bus, azx_dev); + } + } + + return handled; +} + +irqreturn_t azx_i2s_interrupt(int irq, void *dev_id) +{ + struct azx *chip = dev_id; + struct i2sc_bus *bus = azx_bus(chip); + u32 status; + bool active, handled = false; + int repeat = 0; /* count for avoiding endless loop */ + + spin_lock(&bus->reg_lock); + + if (chip->disabled) + goto unlock; + + do { + status = i2s_read_reg(bus->remap_addr, DMA_STS); + + if (status == 0) + break; + + handled = true; + active = false; + if (snd_i2s_bus_handle_stream_irq(bus, status, stream_update)) + active = true; + } while (active && ++repeat < 1); + + unlock: + spin_unlock(&bus->reg_lock); + return IRQ_RETVAL(handled); +} + +static int azx_acquire_irq(struct azx *chip, int do_disconnect) +{ + struct i2sc_bus *bus = azx_bus(chip); + struct i2s_phytium *i2s = container_of(chip, struct i2s_phytium, chip); + int err; + + err = devm_request_irq(i2s->dev, i2s->irq_id, azx_i2s_interrupt, IRQF_SHARED, + "phytium i2s", chip); + + if (err < 0) { + dev_err(i2s->dev, "failed to request irq\n"); + return err; + } + + bus->irq = i2s->irq_id; + + return 0; +} + +static void i2s_start(struct i2s_phytium *dev, + struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->regs, ITER, 1); + else + i2s_write_reg(dev->regs, IRER, 1); + + /*enable the clock*/ + i2s_write_reg(dev->regs, CER, 1); + + /*enable the i2s*/ + i2s_write_reg(dev->regs, I2S_IER, 1); +} + +static void i2s_stop(struct i2s_phytium *dev, struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->regs, ITER, 0); + else + i2s_write_reg(dev->regs, IRER, 0); + + if (!dev->active) { + i2s_write_reg(dev->regs, CER, 0); + i2s_write_reg(dev->regs, I2S_IER, 0); + } +} + +static void phytium_i2s_config(struct i2s_phytium *dev, int stream) +{ + i2s_disable_channels(dev, stream); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_write_reg(dev->regs, TCR(0), dev->xfer_resolution); + i2s_write_reg(dev->regs, TER(0), 1); + } else { + i2s_write_reg(dev->regs, RCR(0), dev->xfer_resolution); + i2s_write_reg(dev->regs, RER(0), 1); + } +} + +static int phytium_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(dai); + struct i2s_clk_config_data *config = &dev->config; + u64 fix, point; + u32 cfg = 0; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + config->data_width = 16; + dev->ccr = 0x00; + dev->xfer_resolution = 0x02; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + config->data_width = 24; + dev->ccr = 0x08; + dev->xfer_resolution = 0x04; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + config->data_width = 32; + dev->ccr = 0x10; + dev->xfer_resolution = 0x05; + break; + + default: + dev_err(dev->dev, "phytium-i2s: unsupported PCM fmt"); + return -EINVAL; + } + + config->chan_nr = params_channels(params); + + switch (config->chan_nr) { + case EIGHT_CHANNEL_SUPPORT: + case SIX_CHANNEL_SUPPORT: + case FOUR_CHANNEL_SUPPORT: + case TWO_CHANNEL_SUPPORT: + break; + default: + dev_err(dev->dev, "channel not supported\n"); + return -EINVAL; + } + + phytium_i2s_config(dev, substream->stream); + + i2s_write_reg(dev->regs, CCR, dev->ccr); + + config->sample_rate = params_rate(params); + if (dev->capability & PHYTIUM_I2S_MASTER) { + fix = dev->clk_base / config->sample_rate / config->data_width / 32; + point = ((dev->clk_base / config->sample_rate) << 10) / config->data_width / 32; + point = (point - (fix << 10)) * 10; + cfg = ((u16) fix << 16) | (u16) point; + i2s_write_reg(dev->regs, CLK_CFG0, cfg); + i2s_write_reg(dev->regs, CLK_CFG1, 0xf); + } + dev->cfg = cfg; + return 0; +} + +static int phytium_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->regs, TXFFR, 1); + else + i2s_write_reg(dev->regs, RXFFR, 1); + + return 0; +} + +static int phytium_i2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dev->active++; + i2s_start(dev, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev->active--; + i2s_stop(dev, substream); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int phytium_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(cpu_dai); + int ret = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_BC_FC: + if (dev->capability & PHYTIUM_I2S_SLAVE) + ret = 0; + else + ret = -EINVAL; + break; + case SND_SOC_DAIFMT_BP_FP: + if (dev->capability & PHYTIUM_I2S_MASTER) + ret = 0; + else + ret = -EINVAL; + break; + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + ret = -EINVAL; + break; + default: + dev_dbg(dev->dev, "phytium/i2s: Invalid master/slave format\n"); + ret = -EINVAL; + break; + } + return ret; +} + +static const struct snd_soc_dai_ops phytium_i2s_dai_ops = { + .hw_params = phytium_i2s_hw_params, + .prepare = phytium_i2s_prepare, + .trigger = phytium_i2s_trigger, + .set_fmt = phytium_i2s_set_fmt, +}; + +#ifdef CONFIG_PM +static int phytium_i2s_suspend(struct snd_soc_component *component) +{ + return 0; +} + +static int phytium_i2s_resume(struct snd_soc_component *component) +{ + struct i2s_phytium *dev = snd_soc_component_get_drvdata(component); + struct snd_soc_dai *dai; + + for_each_component_dais(component, dai) { + if (snd_soc_dai_stream_active(dai, SNDRV_PCM_STREAM_PLAYBACK)) + phytium_i2s_config(dev, SNDRV_PCM_STREAM_PLAYBACK); + if (snd_soc_dai_stream_active(dai, SNDRV_PCM_STREAM_CAPTURE)) + phytium_i2s_config(dev, SNDRV_PCM_STREAM_CAPTURE); + } + i2s_write_reg(dev->regs, CLK_CFG0, dev->cfg); + i2s_write_reg(dev->regs, CLK_CFG1, 0xf); + return 0; +} +#else +#define phytium_i2s_suspend NULL +#define phytium_i2s_resume NULL +#endif + +static struct snd_soc_dai_driver phytium_i2s_dai = { + .playback = { + .stream_name = "i2s-Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "i2s-Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &phytium_i2s_dai_ops, + .symmetric_rate = 1, +}; + +static const struct snd_pcm_hardware phytium_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = (SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 4096*16, + .period_bytes_min = 1024, + .period_bytes_max = 4096*4, + .periods_min = 2, + .periods_max = 16, + .fifo_size = 16, +}; + +struct i2s_stream *snd_i2s_stream_assign(struct i2sc_bus *bus, + struct snd_pcm_substream *substream) +{ + struct i2s_stream *azx_dev; + struct i2s_stream *res = NULL; + + /* make a non-zero unique key for the substream */ + int key = (substream->pcm->device << 16) | (substream->number << 2) | + (substream->stream + 1); + + list_for_each_entry(azx_dev, &bus->stream_list, list) { + if (azx_dev->direction != substream->stream) + continue; + + azx_dev->opened = 0; + + if (azx_dev->assigned_key == key) { + res = azx_dev; + break; + } + + if (!res || bus->reverse_assign) + res = azx_dev; + } + + if (res) { + spin_lock_irq(&bus->reg_lock); + res->opened = 1; + res->running = 0; + res->assigned_key = key; + res->substream = substream; + spin_unlock_irq(&bus->reg_lock); + } + + return res; +} + +/* assign a stream for the PCM */ +static inline struct azx_dev * +azx_assign_device(struct azx *chip, struct snd_pcm_substream *substream) +{ + struct i2s_stream *s; + + s = snd_i2s_stream_assign(azx_bus(chip), substream); + if (!s) + return NULL; + return stream_to_azx_dev(s); +} + +static int phytium_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); + + struct azx *chip = &dev->chip; + struct azx_dev *azx_dev; + struct snd_pcm_runtime *runtime = substream->runtime; + + azx_dev = azx_assign_device(chip, substream); + if (azx_dev == NULL) + return -EBUSY; + + snd_soc_set_runtime_hwparams(substream, &phytium_pcm_hardware); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128); + runtime->private_data = azx_dev; + + return 0; +} + +static int phytium_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); + struct azx *chip = &dev->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + + mutex_lock(&chip->open_mutex); + azx_stream(azx_dev)->opened = 0; + azx_stream(azx_dev)->running = 0; + azx_stream(azx_dev)->substream = NULL; + + mutex_unlock(&chip->open_mutex); + return 0; +} + +static int phytium_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); + size_t size = phytium_pcm_hardware.buffer_bytes_max; + + snd_pcm_set_managed_buffer_all(rtd->pcm, + SNDRV_DMA_TYPE_DEV, + dev->pdev, size, size); + + return 0; +} + +static const struct i2s_io_ops axi_i2s_io_ops; +static const struct i2s_controller_ops axi_i2s_ops; + +static int phytium_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); + struct azx *chip = &dev->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + int ret; + + azx_dev->core.bufsize = 0; + azx_dev->core.period_bytes = 0; + azx_dev->core.format_val = 0; + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S16_LE: + azx_dev->core.format_val = 2; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + azx_dev->core.format_val = 0; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + azx_dev->core.format_val = 0; + break; + + default: + dev_err(dev->dev, "phytium-i2s: unsupported PCM fmt"); + return -EINVAL; + } + + + ret = chip->ops->substream_alloc_pages(chip, substream, + params_buffer_bytes(hw_params)); + + return ret; +} +/* + * set up a BDL entry + */ +static int setup_bdle(struct i2sc_bus *bus, + struct snd_dma_buffer *dmab, + struct i2s_stream *azx_dev, __le32 **bdlp, + int ofs, int size, int with_ioc) +{ + struct snd_pcm_substream *substream = azx_dev->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + __le32 *bdl = *bdlp; + + dmab->addr = runtime->dma_addr; + while (size > 0) { + dma_addr_t addr; + int chunk; + + if (azx_dev->frags >= AZX_MAX_BDL_ENTRIES) + return -EINVAL; + + addr = snd_sgbuf_get_addr(dmab, ofs); + + /* program the address field of the BDL entry */ + bdl[0] = cpu_to_le32((u32)addr); + + bdl[1] = cpu_to_le32(upper_32_bits(addr)); + + /* program the size field of the BDL entry */ + chunk = snd_sgbuf_get_chunk_size(dmab, ofs, size); + + bdl[2] = cpu_to_le32(chunk); + + /* program the IOC to enable interrupt + * only when the whole fragment is processed + */ + size -= chunk; + bdl[3] = (size || !with_ioc) ? 0 : cpu_to_le32(0x01); + + bdl += 4; + azx_dev->frags++; + ofs += chunk; + } + *bdlp = bdl; + return ofs; +} + +int snd_i2s_stream_setup_periods(struct i2s_stream *azx_dev) +{ + struct i2sc_bus *bus = azx_dev->bus; + struct snd_pcm_substream *substream = azx_dev->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + __le32 *bdl; + int i, ofs, periods, period_bytes; + int pos_adj, pos_align; + + period_bytes = azx_dev->period_bytes; + periods = azx_dev->bufsize / period_bytes; + + /* program the initial BDL entries */ + bdl = (__le32 *)azx_dev->bdl.area; + + ofs = 0; + azx_dev->frags = 0; + + pos_adj = bus->bdl_pos_adj; + + if (!azx_dev->no_period_wakeup && pos_adj > 0) { + + pos_align = pos_adj; + pos_adj = (pos_adj * runtime->rate + 47999) / 48000; + + if (!pos_adj) + pos_adj = pos_align; + else + pos_adj = ((pos_adj + pos_align - 1) / pos_align) * + pos_align; + + pos_adj = frames_to_bytes(runtime, pos_adj); + if (pos_adj >= period_bytes) { + dev_warn(bus->dev, "Too big adjustment %d\n", + pos_adj); + pos_adj = 0; + } else { + ofs = setup_bdle(bus, snd_pcm_get_dma_buf(substream), + azx_dev, + &bdl, ofs, pos_adj, true); + if (ofs < 0) + goto error; + } + } else { + pos_adj = 0; + } + + for (i = 0; i < periods; i++) { + if (i == periods - 1 && pos_adj) + ofs = setup_bdle(bus, snd_pcm_get_dma_buf(substream), + azx_dev, &bdl, ofs, + period_bytes - pos_adj, 0); + else + ofs = setup_bdle(bus, snd_pcm_get_dma_buf(substream), + azx_dev, &bdl, ofs, + period_bytes, + !azx_dev->no_period_wakeup); + if (ofs < 0) + goto error; + } + + return 0; + error: + dev_err(bus->dev, "Too many BDL entries: buffer=%d, period=%d\n", + azx_dev->bufsize, period_bytes); + return -EINVAL; +} + +int snd_i2s_stream_set_params(struct i2s_stream *azx_dev, + unsigned int format_val) +{ + unsigned int bufsize, period_bytes; + struct snd_pcm_substream *substream = azx_dev->substream; + struct snd_pcm_runtime *runtime; + int err; + + if (!substream) + return -EINVAL; + + runtime = substream->runtime; + bufsize = snd_pcm_lib_buffer_bytes(substream); + period_bytes = snd_pcm_lib_period_bytes(substream); + if (bufsize != azx_dev->bufsize || + period_bytes != azx_dev->period_bytes || + runtime->no_period_wakeup != azx_dev->no_period_wakeup) { + azx_dev->bufsize = bufsize; + azx_dev->period_bytes = period_bytes; + azx_dev->no_period_wakeup = runtime->no_period_wakeup; + err = snd_i2s_stream_setup_periods(azx_dev); + if (err < 0) + return err; + } + + return 0; +} + +int snd_i2s_stream_setup(struct i2s_stream *azx_dev, int pcie, u32 paddr) +{ + struct snd_pcm_runtime *runtime; + + if (azx_dev->substream) + runtime = azx_dev->substream->runtime; + else + runtime = NULL; + + i2s_write_reg(azx_dev->sd_addr, DMA_CHAL_CONFG0, 0x8081); + i2s_write_reg(azx_dev->sd_addr, DMA_MASK_INT, 0x80000003); + + if (azx_dev->direction == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_write_reg(azx_dev->sd_addr, DMA_BDLPL(1), (u32)azx_dev->bdl.addr); + i2s_write_reg(azx_dev->sd_addr, DMA_BDLPU(1), upper_32_bits(azx_dev->bdl.addr)); + if (pcie) + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DEV_ADDR(1), 0x1c8); + else + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DEV_ADDR(1), paddr + 0x1c8); + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CBL(1), azx_dev->bufsize); + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_LVI(1), azx_dev->frags - 1); + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DSIZE(1), azx_dev->format_val);//0x2 + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DLENTH(1), 0x0);//0x0 + } else { + i2s_write_reg(azx_dev->sd_addr, DMA_BDLPL(0), (u32)azx_dev->bdl.addr); + i2s_write_reg(azx_dev->sd_addr, DMA_BDLPU(0), upper_32_bits(azx_dev->bdl.addr)); + if (pcie) + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DEV_ADDR(0), 0x1c0); + else + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DEV_ADDR(0), paddr + 0x1c0); + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CBL(0), azx_dev->bufsize); + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_LVI(0), azx_dev->frags - 1); + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DSIZE(0), azx_dev->format_val << 2);//0x8 + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DLENTH(0), 0x0); + } + + if (runtime && runtime->period_size > 64) + azx_dev->delay_negative_threshold = + -frames_to_bytes(runtime, 64); + else + azx_dev->delay_negative_threshold = 0; + + return 0; +} + +static int phytium_pcm_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); + struct azx *chip = &dev->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + struct i2sc_bus *bus = azx_bus(chip); + struct i2s_stream *hstr_p; + int err; + + dev->substream = substream; + azx_dev->core.substream = substream; + azx_dev->core.sd_addr = dev->regs_db; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + azx_dev->core.bdl.area = bus->bdl0.area; + azx_dev->core.bdl.addr = bus->bdl0.addr; + } else { + azx_dev->core.bdl.area = bus->bdl1.area; + azx_dev->core.bdl.addr = bus->bdl1.addr; + } + + if (!substream) + return -EINVAL; + + hstr_p = azx_stream(azx_dev); + hstr_p->direction = substream->stream; + + err = snd_i2s_stream_set_params(azx_stream(azx_dev), 0); + if (err < 0) + goto unlock; + + snd_i2s_stream_setup(azx_stream(azx_dev), dev->pcie, dev->paddr); + + unlock: + if (!err) + azx_stream(azx_dev)->prepared = 1; + + return err; +} + +void snd_i2s_stream_clear(struct i2s_stream *azx_dev) +{ + if (azx_dev->direction == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(1), 0x0); + else + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(0), 0x0); + + azx_dev->running = false; +} + +void snd_i2s_stream_stop(struct i2s_stream *azx_dev) +{ + snd_i2s_stream_clear(azx_dev); +} + +void snd_i2s_stream_start(struct i2s_stream *azx_dev, bool fresh_start) +{ + if (azx_dev->direction == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(1), 0x1); + else + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(0), 0x5); + + azx_dev->running = true; +} + +static int phytium_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); + struct azx *chip = &dev->chip; + struct i2sc_bus *bus = azx_bus(chip); + struct azx_dev *azx_dev = get_azx_dev(substream); + struct snd_pcm_substream *s; + struct i2s_stream *hstr; + bool start; + int sbits = 0; + + hstr = azx_stream(azx_dev); + hstr->direction = substream->stream; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + start = true; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + start = false; + break; + default: + return -EINVAL; + } + + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + azx_dev = get_azx_dev(s); + sbits |= 1 << azx_dev->core.index; + snd_pcm_trigger_done(s, substream); + } + + spin_lock(&bus->reg_lock); + + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + azx_dev = get_azx_dev(s); + if (start) + snd_i2s_stream_start(azx_stream(azx_dev), true); + else + snd_i2s_stream_stop(azx_stream(azx_dev)); + } + + i2s_write_reg(dev->regs_db, DMA_CTL, 0x1); + spin_unlock(&bus->reg_lock); + + return 0; +} + +void snd_i2s_stream_cleanup(struct i2s_stream *azx_dev) +{ + int cnt = 10; + u32 mask; + + if (azx_dev->sd_addr) { + if (azx_dev->direction == SNDRV_PCM_STREAM_PLAYBACK) { + mask = i2s_read_reg(azx_dev->sd_addr, DMA_MASK_INT); + mask &= ~BIT(1); + i2s_write_reg(azx_dev->sd_addr, DMA_MASK_INT, mask); + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(1), 0); + while (cnt--) { + if (i2s_read_reg(azx_dev->sd_addr, DMA_CHALX_CTL(1)) == 0) + break; + } + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(1), 2); + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(1), 0); + i2s_write_reg(azx_dev->sd_addr, DMA_BDLPL(1), 0); + i2s_write_reg(azx_dev->sd_addr, DMA_BDLPU(1), 0); + i2s_write_reg(azx_dev->sd_addr, DMA_STS, azx_dev->sd_int_sta_mask); + } else { + mask = i2s_read_reg(azx_dev->sd_addr, DMA_MASK_INT); + mask &= ~BIT(0); + i2s_write_reg(azx_dev->sd_addr, DMA_MASK_INT, mask); + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(0), 0); + while (cnt--) { + if (i2s_read_reg(azx_dev->sd_addr, DMA_CHALX_CTL(0)) == 0) + break; + } + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(0), 2); + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(0), 0); + i2s_write_reg(azx_dev->sd_addr, DMA_BDLPL(0), 0); + i2s_write_reg(azx_dev->sd_addr, DMA_BDLPU(0), 0); + i2s_write_reg(azx_dev->sd_addr, DMA_STS, azx_dev->sd_int_sta_mask); + } + } +} + +static int phytium_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); + struct azx *chip = &dev->chip; + struct i2s_stream *hstr_p; + struct azx_dev *azx_dev = get_azx_dev(substream); + int err; + + hstr_p = azx_stream(azx_dev); + hstr_p->direction = substream->stream; + snd_i2s_stream_cleanup(azx_stream(azx_dev)); + + err = chip->ops->substream_free_pages(chip, substream); + azx_stream(azx_dev)->prepared = 0; + + return err; +} + +static snd_pcm_uframes_t phytium_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); + u32 pos = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pos = i2s_read_reg(dev->regs_db, DMA_LPIB(1)); + else + pos = i2s_read_reg(dev->regs_db, DMA_LPIB(0)); + + return bytes_to_frames(substream->runtime, pos); +} + +static const struct snd_soc_component_driver phytium_i2s_component = { + .name = "phytium-i2s", + .pcm_construct = phytium_pcm_new, + .open = phytium_pcm_open, + .close = phytium_pcm_close, + .hw_params = phytium_pcm_hw_params, + .hw_free = phytium_pcm_hw_free, + .prepare = phytium_pcm_prepare, + .trigger = phytium_pcm_trigger, + .pointer = phytium_pcm_pointer, + .suspend = phytium_i2s_suspend, + .resume = phytium_i2s_resume, + .legacy_dai_naming = 1, +}; + +/* Maximum bit resolution of a channel - not uniformly spaced */ +static const u32 fifo_width[COMP_MAX_WORDSIZE] = { + 12, 16, 20, 24, 32, 0, 0, 0 +}; + +/* Width of (DMA) bus */ +static const u32 bus_widths[COMP_MAX_DATA_WIDTH] = { + DMA_SLAVE_BUSWIDTH_1_BYTE, + DMA_SLAVE_BUSWIDTH_2_BYTES, + DMA_SLAVE_BUSWIDTH_4_BYTES, + DMA_SLAVE_BUSWIDTH_UNDEFINED +}; + +/* PCM format to support channel resolution */ +static const u32 formats[COMP_MAX_WORDSIZE] = { + SNDRV_PCM_FMTBIT_S16_LE, + SNDRV_PCM_FMTBIT_S16_LE, + SNDRV_PCM_FMTBIT_S24_LE, + SNDRV_PCM_FMTBIT_S24_LE, + SNDRV_PCM_FMTBIT_S32_LE, + 0, + 0, + 0 +}; + +static int phytium_configure_dai(struct i2s_phytium *dev) +{ + u32 comp1 = i2s_read_reg(dev->regs, dev->i2s_reg_comp1); + u32 comp2 = i2s_read_reg(dev->regs, dev->i2s_reg_comp2); + u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1)); + u32 idx; + + if (COMP1_TX_ENABLED(comp1)) { + dev_dbg(dev->dev, " phytium: play supported\n"); + idx = COMP1_TX_WORDSIZE_0(comp1); + if (WARN_ON(idx >= ARRAY_SIZE(formats))) + return -EINVAL; + } + + if (COMP1_RX_ENABLED(comp1)) { + dev_dbg(dev->dev, "phytium: record supported\n"); + idx = COMP2_RX_WORDSIZE_0(comp2); + if (WARN_ON(idx >= ARRAY_SIZE(formats))) + return -EINVAL; + if (dev->quirks & PHYTIUM_I2S_QUIRK_16BIT_IDX_OVERRIDE) + idx = 1; + } + + if (COMP1_MODE_EN(comp1)) { + dev_dbg(dev->dev, "phytium: i2s master mode supported\n"); + dev->capability |= PHYTIUM_I2S_MASTER; + } else { + dev_dbg(dev->dev, "phytium: i2s slave mode supported\n"); + dev->capability |= PHYTIUM_I2S_SLAVE; + } + + dev->fifo_th = fifo_depth / 2; + return 0; +} + +static int phytium_configure_dai_by_dt(struct i2s_phytium *dev) +{ + u32 comp1 = i2s_read_reg(dev->regs, I2S_COMP_PARAM_1); + u32 comp2 = i2s_read_reg(dev->regs, I2S_COMP_PARAM_2); + u32 idx = COMP1_APB_DATA_WIDTH(comp1); + u32 idx2; + int ret; + + if (WARN_ON(idx >= ARRAY_SIZE(bus_widths))) + return -EINVAL; + + ret = phytium_configure_dai(dev); + if (ret < 0) + return ret; + + if (COMP1_TX_ENABLED(comp1)) { + idx2 = COMP1_TX_WORDSIZE_0(comp1); + dev->capability |= PHYTIUM_I2S_PLAY; + } + if (COMP1_RX_ENABLED(comp1)) { + idx2 = COMP2_RX_WORDSIZE_0(comp2); + dev->capability |= PHYTIUM_I2S_RECORD; + } + + return 0; +} + +static int phytium_i2s_dma_alloc_pages(struct i2sc_bus *bus, int type, size_t size, + struct snd_dma_buffer *buf) +{ + int err; + + err = snd_dma_alloc_pages(type, bus->dev, size, buf); + if (err < 0) + return err; + + return 0; +} + +int snd_i2s_bus_alloc_stream_pages(struct i2sc_bus *bus) +{ + struct i2s_stream *s; + int num_streams = 0; + int err; + + list_for_each_entry(s, &bus->stream_list, list) { + /* allocate memory for the BDL for each stream */ + err = bus->io_ops->dma_alloc_pages(bus, SNDRV_DMA_TYPE_DEV, + BDL_SIZE, &s->bdl); + if (num_streams == 0) { + bus->bdl0.addr = s->bdl.addr; + bus->bdl0.area = s->bdl.area; + } else { + bus->bdl1.addr = s->bdl.addr; + bus->bdl1.area = s->bdl.area; + } + num_streams++; + if (err < 0) + return -ENOMEM; + } + + if (WARN_ON(!num_streams)) + return -EINVAL; + + return 0; +} + +static int stream_direction(struct azx *chip, unsigned char index) +{ + if (index >= chip->playback_index_offset && + index < chip->playback_index_offset + chip->playback_streams) + return SNDRV_PCM_STREAM_PLAYBACK; + return SNDRV_PCM_STREAM_CAPTURE; +} + +void snd_i2s_stream_init(struct i2sc_bus *bus, struct i2s_stream *azx_dev, + int idx, int direction, int tag) +{ + azx_dev->bus = bus; + azx_dev->sd_addr = bus->remap_addr; + + if (idx == 0) + azx_dev->sd_int_sta_mask = 1 << 8; + else + azx_dev->sd_int_sta_mask = 1; + + azx_dev->index = idx; + azx_dev->direction = direction; + azx_dev->stream_tag = tag; + + list_add_tail(&azx_dev->list, &bus->stream_list); +} + +int azx_i2s_init_streams(struct azx *chip) +{ + int i; + + for (i = 0; i < chip->num_streams; i++) { + struct azx_dev *azx_dev = kzalloc(sizeof(*azx_dev), GFP_KERNEL); + int dir, tag; + + if (!azx_dev) + return -ENOMEM; + + dir = stream_direction(chip, i); + + tag = i + 1; + + snd_i2s_stream_init(azx_bus(chip), azx_stream(azx_dev), i, dir, tag); + } + + return 0; +} + +static int azx_first_init(struct azx *chip) +{ + struct i2s_phytium *i2s = container_of(chip, struct i2s_phytium, chip); + struct platform_device *pdev = to_platform_device(i2s->dev); + struct device *i2sdev = i2s->dev; + struct i2sc_bus *bus = azx_bus(chip); + struct resource *res; + int err; + unsigned int dma_bits = 64; + + chip->region_requested = 1; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + bus->addr = res->start; + bus->remap_addr = i2s->regs_db; + bus->dev = i2s->pdev; + + if (bus->remap_addr == NULL) { + dev_err(i2sdev, "ioremap error\n"); + return -ENXIO; + } + + if (azx_acquire_irq(chip, 0) < 0) + return -EBUSY; + + synchronize_irq(bus->irq); + + spin_lock_init(&bus->reg_lock); + + if (!dma_set_mask(i2sdev, DMA_BIT_MASK(dma_bits))) { + err = dma_set_coherent_mask(i2sdev, DMA_BIT_MASK(dma_bits)); + } else { + err = dma_set_mask(i2sdev, DMA_BIT_MASK(32)); + err = dma_set_coherent_mask(i2sdev, DMA_BIT_MASK(32)); + } + + chip->playback_streams = NUM_PLAYBACK; + chip->capture_streams = NUM_CAPTURE; + + chip->playback_index_offset = 0; + chip->capture_index_offset = chip->playback_streams; + chip->num_streams = chip->playback_streams + chip->capture_streams; + + err = azx_i2s_init_streams(chip); + if (err < 0) + return err; + + err = azx_alloc_stream_pages(chip); + if (err < 0) + return err; + + return 0; +} + +static int azx_probe_continue(struct azx *chip) +{ + struct i2s_phytium *i2s = container_of(chip, struct i2s_phytium, chip); + int err; + + i2s->probe_continued = 1; + + err = azx_first_init(chip); + if (err < 0) + goto out_free; + + chip->running = 1; + +out_free: + return err; +} + +static void azx_probe_work(struct work_struct *work) +{ + struct i2s_phytium *i2s = container_of(work, struct i2s_phytium, probe_work); + + azx_probe_continue(&i2s->chip); +} + +int azx_i2s_bus_init(struct azx *chip, + const struct i2s_io_ops *io_ops) +{ + struct i2s_bus *bus = &chip->bus; + + bus->core.io_ops = io_ops; + + INIT_LIST_HEAD(&bus->core.stream_list); + bus->card = chip->card; + mutex_init(&bus->prepare_mutex); + bus->pci = chip->pci; + + bus->core.bdl_pos_adj = chip->bdl_pos_adj; + return 0; +} + +static int i2s_phytium_create(struct platform_device *pdev, + int dev, struct azx **rchip, struct i2s_phytium *i2s) +{ + struct azx *chip; + int err; + + *rchip = NULL; + + if (!i2s) + return -ENOMEM; + chip = &i2s->chip; + + mutex_init(&chip->open_mutex); + + chip->ops = &axi_i2s_ops; + chip->dev_index = dev; + + INIT_LIST_HEAD(&chip->pcm_list); + init_completion(&i2s->probe_wait); + + chip->bdl_pos_adj = 32; + err = azx_i2s_bus_init(chip, &axi_i2s_io_ops); + if (err < 0) { + kfree(i2s); + return err; + } + + INIT_WORK(&i2s->probe_work, azx_probe_work); + *rchip = chip; + return 0; +} + +static int substream_alloc_pages(struct azx *chip, + struct snd_pcm_substream *substream, + size_t size) +{ + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, size); + if (ret < 0) + return ret; + + return 0; +} + +static void phytium_i2s_dma_free_pages(struct i2sc_bus *bus, + struct snd_dma_buffer *buf) +{ + snd_dma_free_pages(buf); +} + +static const struct i2s_io_ops axi_i2s_io_ops = { + .dma_alloc_pages = phytium_i2s_dma_alloc_pages, + .dma_free_pages = phytium_i2s_dma_free_pages, +}; + +static const struct i2s_controller_ops axi_i2s_ops = { + .substream_alloc_pages = substream_alloc_pages, + .substream_free_pages = substream_free_pages, +}; + +static int phytium_i2s_probe(struct platform_device *pdev) +{ + struct i2s_phytium *i2s; + struct azx *chip; + struct resource *res; + struct pdata_px210_mfd *pdata; + struct snd_soc_dai_driver *dai_drv; + struct clk *clk; + int err, ret; + int card_num = 1; + bool schedule_probe; + struct fwnode_handle *np; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + dai_drv = devm_kzalloc(&pdev->dev, sizeof(*dai_drv), GFP_KERNEL); + if (!dai_drv) + return -ENOMEM; + memcpy(dai_drv, &phytium_i2s_dai, sizeof(phytium_i2s_dai)); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->paddr = res->start; + i2s->regs = devm_ioremap_resource(&pdev->dev, res); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + i2s->regs_db = devm_ioremap_resource(&pdev->dev, res); + + if (IS_ERR(i2s->regs)) + return PTR_ERR(i2s->regs); + + i2s->irq_id = platform_get_irq(pdev, 0); + + if (i2s->irq_id < 0) + return i2s->irq_id; + + i2s->i2s_reg_comp1 = I2S_COMP_PARAM_1; + i2s->i2s_reg_comp2 = I2S_COMP_PARAM_2; + + ret = phytium_configure_dai_by_dt(i2s); + if (ret < 0) + return ret; + + err = i2s_phytium_create(pdev, card_num, &chip, i2s); + if (err < 0) + return err; + i2s = container_of(chip, struct i2s_phytium, chip); + schedule_probe = !chip->disabled; + + dev_set_drvdata(&pdev->dev, i2s); + + pdata = dev_get_platdata(&pdev->dev); + i2s->dev = &pdev->dev; + if (pdata) { + dai_drv->name = pdata->name; + i2s->pdev = pdata->dev; + i2s->clk_base = pdata->clk_base; + i2s->pcie = 1; + } else if (pdev->dev.of_node) { + device_property_read_string(&pdev->dev, "dai-name", &dai_drv->name); + i2s->pdev = &pdev->dev; + clk = devm_clk_get(&pdev->dev, NULL); + i2s->clk_base = clk_get_rate(clk); + } else if (has_acpi_companion(&pdev->dev)) { + np = dev_fwnode(&(pdev->dev)); + ret = fwnode_property_read_string(np, "dai-name", &dai_drv->name); + if (ret < 0) { + dev_err(&pdev->dev, "missing dai-name property from acpi\n"); + goto failed_get_dai_name; + } + + i2s->pdev = &pdev->dev; + ret = fwnode_property_read_u32(np, "i2s_clk", &i2s->clk_base); + if (ret < 0) { + dev_err(&pdev->dev, "missing i2s_clk property from acpi\n"); + goto failed_get_dai_name; + } + } + + ret = devm_snd_soc_register_component(&pdev->dev, &phytium_i2s_component, + dai_drv, 1); + if (ret != 0) + dev_err(&pdev->dev, "not able to register dai\n"); + + if (schedule_probe) + schedule_work(&i2s->probe_work); + + if (chip->disabled) + complete_all(&i2s->probe_wait); + + return 0; + +failed_get_dai_name: + return ret; +} + +static int phytium_i2s_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + return 0; +} + +static const struct of_device_id phytium_i2s_of_match[] = { + { .compatible = "phytium,i2s", }, + {}, +}; +MODULE_DEVICE_TABLE(of, phytium_i2s_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_i2s_acpi_match[] = { + { "PHYT0016", 0 }, + { } +}; +#else +#define phytium_i2s_acpi_match NULL +#endif + +static struct platform_driver phytium_i2s_driver = { + .probe = phytium_i2s_probe, + .remove = phytium_i2s_remove, + .driver = { + .name = "phytium-i2s", + .of_match_table = of_match_ptr(phytium_i2s_of_match), + .acpi_match_table = phytium_i2s_acpi_match, + }, +}; + +module_platform_driver(phytium_i2s_driver); + +MODULE_DESCRIPTION("Phytium I2S Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Zhang Yiqun "); diff --git a/sound/soc/phytium/pmdk_dp.c b/sound/soc/phytium/pmdk_dp.c new file mode 100644 index 0000000000000000000000000000000000000000..74488df3d21ab71d0a0f9f85226cc0b7904a5269 --- /dev/null +++ b/sound/soc/phytium/pmdk_dp.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include + +struct pmdk_dp_private { + struct snd_soc_jack jack0; + struct snd_soc_jack jack1; + struct snd_soc_jack jack2; +}; + +/* PMDK widgets */ +static const struct snd_soc_dapm_widget pmdk_dp_dapm_widgets[] = { + SND_SOC_DAPM_LINE("DP", NULL), +}; + +/* PMDK control */ +static const struct snd_kcontrol_new pmdk_controls[] = { + SOC_DAPM_PIN_SWITCH("DP"), +}; + +/* PMDK connections */ +static const struct snd_soc_dapm_route pmdk_dp_audio_map[] = { + {"DP", NULL, "TX"}, +}; + +static struct snd_soc_jack_pin dp0_pins[] = { + { + .pin = "DP/HDMI 0", + .mask = SND_JACK_LINEOUT, + }, +}; + +static struct snd_soc_jack_pin dp1_pins[] = { + { + .pin = "DP/HDMI 1", + .mask = SND_JACK_LINEOUT, + }, +}; + +static struct snd_soc_jack_pin dp2_pins[] = { + { + .pin = "DP/HDMI 2", + .mask = SND_JACK_LINEOUT, + }, +}; + +#define SMDK_DAI_FMT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_BC_FC) + +static int pmdk_dp0_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct pmdk_dp_private *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = snd_soc_rtd_to_codec(runtime, 0)->component; + int ret; + + ret = snd_soc_card_jack_new_pins(card, "DP/HDMI 0", + SND_JACK_LINEOUT, &priv->jack0, + dp0_pins, ARRAY_SIZE(dp0_pins)); + if (ret) { + dev_err(card->dev, "Jack creation failed %d\n", ret); + return ret; + } + snd_soc_component_set_jack(component, &priv->jack0, NULL); + return ret; +} + +static int pmdk_dp1_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct pmdk_dp_private *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = snd_soc_rtd_to_codec(runtime, 0)->component; + int ret; + + ret = snd_soc_card_jack_new_pins(card, "DP/HDMI 1", + SND_JACK_LINEOUT, &priv->jack1, + dp1_pins, ARRAY_SIZE(dp1_pins)); + if (ret) { + dev_err(card->dev, "Jack creation failed %d\n", ret); + return ret; + } + snd_soc_component_set_jack(component, &priv->jack1, NULL); + return ret; +} + +static int pmdk_dp2_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct pmdk_dp_private *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = snd_soc_rtd_to_codec(runtime, 0)->component; + int ret; + + ret = snd_soc_card_jack_new_pins(card, "DP/HDMI 2", + SND_JACK_LINEOUT, &priv->jack2, + dp2_pins, ARRAY_SIZE(dp2_pins)); + if (ret) { + dev_err(card->dev, "Jack creation failed %d\n", ret); + return ret; + } + snd_soc_component_set_jack(component, &priv->jack2, NULL); + return ret; +} + +SND_SOC_DAILINK_DEFS(pmdk_dp0_dai, + DAILINK_COMP_ARRAY(COMP_CPU("phytium-i2s-dp0")), + DAILINK_COMP_ARRAY(COMP_CODEC("hdmi-audio-codec.1346918656", "i2s-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); + +SND_SOC_DAILINK_DEFS(pmdk_dp1_dai, + DAILINK_COMP_ARRAY(COMP_CPU("phytium-i2s-dp1")), + DAILINK_COMP_ARRAY(COMP_CODEC("hdmi-audio-codec.1346918657", "i2s-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); + +SND_SOC_DAILINK_DEFS(pmdk_dp2_dai, + DAILINK_COMP_ARRAY(COMP_CPU("phytium-i2s-dp2")), + DAILINK_COMP_ARRAY(COMP_CODEC("hdmi-audio-codec.1346918658", "i2s-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); + +static struct snd_soc_dai_link pmdk_dai0 = { + .name = "Phytium dp0-audio", + .stream_name = "Playback", + .dai_fmt = SMDK_DAI_FMT, + .init = pmdk_dp0_init, + SND_SOC_DAILINK_REG(pmdk_dp0_dai), +}; + +static struct snd_soc_dai_link pmdk_dai1 = { + .name = "Phytium dp1-audio", + .stream_name = "Playback", + .dai_fmt = SMDK_DAI_FMT, + .init = pmdk_dp1_init, + SND_SOC_DAILINK_REG(pmdk_dp1_dai), +}; + +static struct snd_soc_dai_link pmdk_dai2 = { + .name = "Phytium dp2-audio", + .stream_name = "Playback", + .dai_fmt = SMDK_DAI_FMT, + .init = pmdk_dp2_init, + SND_SOC_DAILINK_REG(pmdk_dp2_dai), +}; + +static struct snd_soc_card pmdk = { + .name = "PMDK-I2S", + .owner = THIS_MODULE, + + .dapm_widgets = pmdk_dp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pmdk_dp_dapm_widgets), + .controls = pmdk_controls, + .num_controls = ARRAY_SIZE(pmdk_controls), + .dapm_routes = pmdk_dp_audio_map, + .num_dapm_routes = ARRAY_SIZE(pmdk_dp_audio_map), +}; + +static int pmdk_sound_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &pmdk; + struct pmdk_dp_private *priv; + struct snd_soc_dai_link *pmdk_dai; + int num_dp = 2; + + card->dev = &pdev->dev; + device_property_read_u32(&pdev->dev, "num-dp", &num_dp); + pmdk_dai = devm_kzalloc(&pdev->dev, num_dp * sizeof(*pmdk_dai), GFP_KERNEL); + if (!pmdk_dai) + return -ENOMEM; + + switch (num_dp) { + case 1: + pmdk_dai[0] = pmdk_dai0; + break; + case 2: + pmdk_dai[0] = pmdk_dai0; + pmdk_dai[1] = pmdk_dai1; + break; + case 3: + pmdk_dai[0] = pmdk_dai0; + pmdk_dai[1] = pmdk_dai1; + pmdk_dai[2] = pmdk_dai2; + break; + default: + return -EINVAL; + } + + card->dai_link = pmdk_dai; + card->num_links = num_dp; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + snd_soc_card_set_drvdata(card, priv); + + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static const struct of_device_id pmdk_sound_of_match[] = { + { .compatible = "phytium,pmdk-dp",}, + { } +}; +MODULE_DEVICE_TABLE(of, pmdk_sound_of_match); + +static const struct acpi_device_id pmdk_sound_acpi_match[] = { + { "PHYT8006", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, pmdk_sound_acpi_match); + +static struct platform_driver pmdk_sound_driver = { + .probe = pmdk_sound_probe, + .driver = { + .name = "pmdk_dp", + .acpi_match_table = pmdk_sound_acpi_match, + .of_match_table = pmdk_sound_of_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, +}; + +module_platform_driver(pmdk_sound_driver); + +MODULE_AUTHOR("Zhang Yiqun "); +MODULE_DESCRIPTION("ALSA SoC PMDK DP"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/phytium/pmdk_es8336.c b/sound/soc/phytium/pmdk_es8336.c new file mode 100644 index 0000000000000000000000000000000000000000..0926a73a9ef85a0e6d0d80f24983155502a8e105 --- /dev/null +++ b/sound/soc/phytium/pmdk_es8336.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021-2023, Phytium Techonology Co., Ltd. + */ + +#include +#include +#include +#include + + +/* PMDK widgets */ +static const struct snd_soc_dapm_widget pmdk_es8336_dapm_widgets[] = { + SND_SOC_DAPM_HP("HP", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_MIC("Mic In", NULL), +}; + +/* PMDK control */ +static const struct snd_kcontrol_new pmdk_controls[] = { + SOC_DAPM_PIN_SWITCH("HP"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Mic In"), +}; + +/* PMDK connections */ +static const struct snd_soc_dapm_route pmdk_es8336_audio_map[] = { + {"DMIC", NULL, "Int Mic"}, + {"MIC1", NULL, "Mic In"}, + {"MIC2", NULL, "Mic In"}, + + {"HP", NULL, "HPOL"}, + {"HP", NULL, "HPOR"}, +}; + +#define PMDK_DAI_FMT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBS_CFS) + +SND_SOC_DAILINK_DEFS(pmdk_es8366, + DAILINK_COMP_ARRAY(COMP_CPU("phytium-i2s-lsd")), + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-ESSX8336:00", "es8336-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); + +static struct snd_soc_dai_link pmdk_dai[] = { + { + .name = "ES8336 HIFI", + .stream_name = "ES8336 HIFI", + .dai_fmt = PMDK_DAI_FMT, + SND_SOC_DAILINK_REG(pmdk_es8366), + }, +}; + +static struct snd_soc_card pmdk = { + .name = "PMDK-I2S", + .owner = THIS_MODULE, + .dai_link = pmdk_dai, + .num_links = ARRAY_SIZE(pmdk_dai), + + .dapm_widgets = pmdk_es8336_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pmdk_es8336_dapm_widgets), + .controls = pmdk_controls, + .num_controls = ARRAY_SIZE(pmdk_controls), + .dapm_routes = pmdk_es8336_audio_map, + .num_dapm_routes = ARRAY_SIZE(pmdk_es8336_audio_map), +}; + +static int pmdk_sound_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &pmdk; + struct device *dev = &pdev->dev; + + card->dev = dev; + + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static const struct acpi_device_id pmdk_sound_acpi_match[] = { + { "PHYT8005", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, pmdk_sound_acpi_match); + +static struct platform_driver pmdk_sound_driver = { + .probe = pmdk_sound_probe, + .driver = { + .name = "pmdk_es8336", + .acpi_match_table = pmdk_sound_acpi_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, +}; + +module_platform_driver(pmdk_sound_driver); +MODULE_AUTHOR("Zhang Yiqun "); +MODULE_DESCRIPTION("ALSA SoC PMDK ES8336"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/phytium/pmdk_es8388.c b/sound/soc/phytium/pmdk_es8388.c new file mode 100644 index 0000000000000000000000000000000000000000..181223ab342403cf097379c46eb06eb1c45bce16 --- /dev/null +++ b/sound/soc/phytium/pmdk_es8388.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021-2023, Phytium Techonology Co., Ltd. + */ + +#include +#include +#include +#include +#include + +static struct snd_soc_jack hs_jack; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "FrontIn", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "RearIn", + .mask = SND_JACK_MICROPHONE, + .invert = 1 + }, + { + .pin = "Front", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Rear", + .mask = SND_JACK_HEADPHONE, + .invert = 1 + }, +}; + +/* Headset jack detection gpios */ +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + { + .name = "det", + .report = SND_JACK_HEADSET, + .debounce_time = 200, + .invert = 1, + }, +}; + +/* PMDK widgets */ +static const struct snd_soc_dapm_widget pmdk_es8388_dapm_widgets[] = { + SND_SOC_DAPM_HP("Front", NULL), + SND_SOC_DAPM_HP("Rear", NULL), + + SND_SOC_DAPM_MIC("FrontIn", NULL), + SND_SOC_DAPM_MIC("RearIn", NULL), +}; + +/* PMDK control */ +static const struct snd_kcontrol_new pmdk_controls[] = { + SOC_DAPM_PIN_SWITCH("Front"), + SOC_DAPM_PIN_SWITCH("Rear"), + SOC_DAPM_PIN_SWITCH("FrontIn"), + SOC_DAPM_PIN_SWITCH("RearIn"), +}; + +/* PMDK connections */ +static const struct snd_soc_dapm_route pmdk_es8388_audio_map[] = { + {"LINPUT1", NULL, "FrontIn"}, + {"RINPUT1", NULL, "FrontIn"}, + + {"LINPUT2", NULL, "RearIn"}, + {"RINPUT2", NULL, "RearIn"}, + + {"Front", NULL, "LOUT1"}, + {"Front", NULL, "ROUT1"}, + + {"Rear", NULL, "LOUT2"}, + {"Rear", NULL, "ROUT2"}, +}; + +static int pmdk_es8388_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + + /* Jack detection API stuff */ + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET, &hs_jack, + hs_jack_pins, ARRAY_SIZE(hs_jack_pins)); + if (ret) + goto err; + + ret = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); + if (ret) + goto err; + + return 0; + +err: + return ret; +} + +#define PMDK_DAI_FMT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBS_CFS) + +SND_SOC_DAILINK_DEFS(pmdk_es8388, + DAILINK_COMP_ARRAY(COMP_CPU("phytium-i2s-lsd")), + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-ESSX8388:00", "es8388-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); + +static struct snd_soc_dai_link pmdk_dai[] = { + { + .name = "ES8388 HIFI", + .stream_name = "ES8388 HIFI", + .dai_fmt = PMDK_DAI_FMT, + .init = pmdk_es8388_init, + SND_SOC_DAILINK_REG(pmdk_es8388), + }, +}; + +static struct snd_soc_card pmdk = { + .name = "PMDK-I2S", + .owner = THIS_MODULE, + .dai_link = pmdk_dai, + .num_links = ARRAY_SIZE(pmdk_dai), + + .dapm_widgets = pmdk_es8388_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pmdk_es8388_dapm_widgets), + .controls = pmdk_controls, + .num_controls = ARRAY_SIZE(pmdk_controls), + .dapm_routes = pmdk_es8388_audio_map, + .num_dapm_routes = ARRAY_SIZE(pmdk_es8388_audio_map), +}; + +static int pmdk_sound_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &pmdk; + struct device *dev = &pdev->dev; + int n; + + card->dev = dev; + hs_jack_gpios[0].gpiod_dev = dev; + n = gpiod_count(dev, "det"); + + if (n < 0) + pmdk_dai[0].init = NULL; + + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static const struct acpi_device_id pmdk_sound_acpi_match[] = { + { "PHYT8004", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, pmdk_sound_acpi_match); + +static struct platform_driver pmdk_sound_driver = { + .probe = pmdk_sound_probe, + .driver = { + .name = "pmdk_es8388", + .acpi_match_table = pmdk_sound_acpi_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, +}; + +module_platform_driver(pmdk_sound_driver); + +MODULE_AUTHOR("Zhang Yiqun"); +MODULE_DESCRIPTION("ALSA SoC PMDK ES8388"); +MODULE_LICENSE("GPL");