diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 5754585d72650902ae307d1d47e0d987f7015cc9..7539e44b53e4f6b4aedfb3d30e140cd47155c414 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -3450,6 +3450,10 @@ force Enable ASPM even on devices that claim not to support it. WARNING: Forcing ASPM on may cause system lockups. + pcie_hp= [PCIE] PCI Express Hotplug driver options: + nomsi Do not use MSI for PCI Express Native Hotplug (this + makes all PCIe ports use INTx for hotplug services). + pcie_ports= [PCIE] PCIe port services handling: native Use native PCIe services (PME, AER, DPC, PCIe hotplug) even if the platform doesn't give the OS permission to diff --git a/Documentation/devicetree/bindings/gpio/gpio-phytium-sgpio.txt b/Documentation/devicetree/bindings/gpio/gpio-phytium-sgpio.txt new file mode 100644 index 0000000000000000000000000000000000000000..6db0d5306ba4d44b525d0dfd92349be5345f4ba4 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/gpio-phytium-sgpio.txt @@ -0,0 +1,32 @@ +* Phytium SGPIO controller + +This SGPIO controller is for Phytium Pe220x SoCs, which supports up to +96 (32x3) Serial GPIOs. + +Required properties: +- compatible : Should contain "phytium,gpio" +- reg : Address and length of the register set for the device. +- interrupts: Interrupt mapping for GPIO IRQ. +- gpio-controller : Marks the device node as a gpio controller. +- #gpio-cells : Should be 2. The first cell is the pin number and + the second cell is used to specify the gpio polarity: + 0 = active high + 1 = active low +- ngpios: number of GPIO lines, see gpio.txt + (should be multiple of 32, up to 96 pins) +- bus-frequency: SGPIO CLK frequency +- clocks: A phandle to the APB clock for SGPIO clock division + +Example: + + 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/gpio/gpio-phytium.txt b/Documentation/devicetree/bindings/gpio/gpio-phytium.txt new file mode 100644 index 0000000000000000000000000000000000000000..77d4c6c03d0037ab37f7783f7ee074cae1301df4 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/gpio-phytium.txt @@ -0,0 +1,47 @@ +* Phytium GPIO controller + +Required properties: +- compatible : Should contain "phytium,gpio" +- reg : Address and length of the register set for the device. +- interrupts: Interrupt mapping for GPIO IRQ. +- gpio-controller : Marks the device node as a gpio controller. +- #gpio-cells : Should be 2. The first cell is the pin number and + the second cell is used to specify the gpio polarity: + 0 = active high + 1 = active low +- #address-cells : should be 1 (for addressing port subnodes). +- #size-cells : should be 0 (port subnodes). + +The GPIO controller has two ports, each of which are represented as child +nodes with the following properties: + +Required properties: +- compatible : "phytium,gpio-port" +- reg : The integer port index of the port, a single cell. + +Optional properties: +- nr-gpios : The number of pins in the port, a single cell. + +Example: + +gpio: gpio@28004000 { + compatible = "phytium,gpio"; + reg = <0x0 0x28004000 0x0 0x1000>; + interrupts = ; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + + porta { + compatible = "phytium,gpio-port"; + reg = <0>; + nr-gpios = <8>; + }; + + portb { + compatible = "phytium,gpio-port"; + reg = <1>; + nr-gpios = <8>; + }; +}; diff --git a/Documentation/devicetree/bindings/gpu/phytium-display.txt b/Documentation/devicetree/bindings/gpu/phytium-display.txt new file mode 100644 index 0000000000000000000000000000000000000000..ff2435967e1d6d14381a6d56d27d08874a466bc3 --- /dev/null +++ b/Documentation/devicetree/bindings/gpu/phytium-display.txt @@ -0,0 +1,23 @@ +* Phytium Display Engine + +Required properties: + - compatible : value should be "phytium,dc". + - reg : first reg: Physical base address of the registers and length of memory + mapped region. + second reg (optional): Physical base address and length of video memory which is reserved system memory. + - interrupts : interrupt number. + - pipe_mask : specify which pipe is enabled, each bit corresponds to a pipe. + - edp_mask : specify which pipe is edp port, each bit corresponds to a pipe (0:dp, 1:edp). + +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.txt b/Documentation/devicetree/bindings/hwlock/phytium-hwspinlock.txt new file mode 100644 index 0000000000000000000000000000000000000000..f39c86492bc9d4464c2d10bb66606bff34ba1369 --- /dev/null +++ b/Documentation/devicetree/bindings/hwlock/phytium-hwspinlock.txt @@ -0,0 +1,21 @@ +Phytium HwSpinlock Driver +======================== + +Required properties: +- compatible: Should be "phytium,hwspinlock" +- reg: Contains the hwspinlock module register address space + (base address and length) +- #hwlock-cells: Should be 1. +- nr-locks: The number of locks in the device. + +Please look at the generic hwlock binding for usage information for consumers, +"Documentation/devicetree/bindings/hwlock/hwlock.txt" + +Example: + +hwspinlock: spinlock@40000000 { + compatible = "phytium,hwspinlock"; + reg = <0x40000000 0x1000>; + #hwlock-cells = <1>; + nr-locks = <32>; +}; diff --git a/Documentation/devicetree/bindings/hwmon/tacho-phytium.txt b/Documentation/devicetree/bindings/hwmon/tacho-phytium.txt new file mode 100644 index 0000000000000000000000000000000000000000..0d02b0684c13f1e7b11cf763a04eb7d536dda637 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/tacho-phytium.txt @@ -0,0 +1,48 @@ +Phytium Fan Tacho and capture counter controller device driver + +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. + +Required properties for tacho node: +- #address-cells : should be 1. + +- #size-cells : should be 1. + +- reg : address and length of the register set for the device. + +- compatible : should be "phytium,tacho" + +- clocks : phandle to clock provider with the clock number in the second cell + +Optional properties for tacho node: + +- tacho : set the controller work as fan tachometer, which is a default option. + +- capture : set the controller work as capture counter. + +------------------------------------------------------------------------------- + +- up : set the input edging mode as ascending, which is a default option. + +- down : set the input edging mode as descending. + +- double : set the input edging mode as doule-edging. + +------------------------------------------------------------------------------- + +- debounce-level : set the debounce-levle, which can be 0, 1, 2 and 3. + +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/i2c-phytium.txt b/Documentation/devicetree/bindings/i2c/i2c-phytium.txt new file mode 100644 index 0000000000000000000000000000000000000000..a1da97b7c60dcdec68f9ff002d62d39f2c14df9a --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/i2c-phytium.txt @@ -0,0 +1,24 @@ +* Phytium I2C/SMBus controller + +Required properties : + + - compatible : should be "phytium,i2c" + - reg : Offset and length of the register set for the device + - interrupts : where IRQ is the interrupt number. + - clock-frequency : desired I2C bus clock frequency in Hz. + +Optional properties: + + - interrupt-names: should be "smbus_alert" if SMBus alert + interrupt is supported. + +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/iio/adc/phytium-adc.txt b/Documentation/devicetree/bindings/iio/adc/phytium-adc.txt new file mode 100644 index 0000000000000000000000000000000000000000..6fc793a857648a1085bd5037c2e6e7ccf2325346 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/phytium-adc.txt @@ -0,0 +1,57 @@ +Phytium ADC + +This device is a 10-bit converter for 8 voltage channels. All inputs are +single ended. + +Required properties: +- compatible: Should be "phytium,adc" +- reg: memory window mapping address and length +- interrupts: Interrupt for the ADC control interface. +- clocks: Input clock used to derive the sample clock. +- #address-cells: Should be <1> (settings for the subnodes). +- #size-cells: Should be <0> (settings for the subnodes). + +Required subnodes: + +The ADC channels are configured as subnodes of the ADC. + +Required channel node properties: + +- reg: should contain the hardware channel number. + +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.txt b/Documentation/devicetree/bindings/input/phytium-keypad.txt new file mode 100644 index 0000000000000000000000000000000000000000..b34e6a1b367821aa575c7400090fcf0b759a50cd --- /dev/null +++ b/Documentation/devicetree/bindings/input/phytium-keypad.txt @@ -0,0 +1,41 @@ +* Phytium Keypad Port device tree bindings + +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. + +Required SoC Specific Properties: +- compatible: Should be "phytium,keypad". +- reg: Physical base address and length of memory mapped region. +- interrupts: Interrupt number to the CPU(s). + +Required Board Specific Properties: +- linux,keymap: The definition can be found at +bindings/input/matrix-keymap.txt. + +Example: + +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/ipmi/phytium,bt-bmc.txt b/Documentation/devicetree/bindings/ipmi/phytium,bt-bmc.txt new file mode 100644 index 0000000000000000000000000000000000000000..7fb1de15d19b4e58a0bbd73d1c1a9be5c7557314 --- /dev/null +++ b/Documentation/devicetree/bindings/ipmi/phytium,bt-bmc.txt @@ -0,0 +1,20 @@ +Phytium BT (Block Transfer) IPMI interface + +The Phytium E-series SOCs can be used in BMC which +have a BT interface used to perform in-band IPMI communication +with their host. + +Required properties: + +- compatible : should be one of + "phytium,bt-bmc" +- reg: physical address and size of the registers +- interrupts: interrupt generated by the BT interface. + +Example: + + bt: bt@250 { + compatible = "phytium,bt-bmc"; + reg = <0x250 0x1c>; + interrupts = ; + }; diff --git a/Documentation/devicetree/bindings/ipmi/phytium,kcs-bmc.txt b/Documentation/devicetree/bindings/ipmi/phytium,kcs-bmc.txt new file mode 100644 index 0000000000000000000000000000000000000000..85cf7114c7a97a9d77b80d475d2fbb16749ac488 --- /dev/null +++ b/Documentation/devicetree/bindings/ipmi/phytium,kcs-bmc.txt @@ -0,0 +1,23 @@ +Phytium KCS (Keyboard Controller Style) IPMI interface + +The Phytium E-series SOC can be used in BMC which +have the KCS interface to perform in-band IPMI communication +with their host. + +Required properties: +- compatible : should be one of + "phytium,kcs-bmc" +- reg : physical address and size of the registers +- interrupts : interrupt generated by the controller +- kcs_chan : The LPC channel number in the controller +- kcs_addr : The host CPU IO map address + +Example: + + 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/mailbox/phytium-mailbox.txt b/Documentation/devicetree/bindings/mailbox/phytium-mailbox.txt new file mode 100644 index 0000000000000000000000000000000000000000..4d6f5a44f6e4bb961d4df5be5befa5cabe91010d --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/phytium-mailbox.txt @@ -0,0 +1,32 @@ +Phytium Mailbox Driver +====================== + +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. + +Mailbox Device Node: +==================== + +Required properties: +-------------------- +- compatible: Shall be "phytium,mbox" +- reg: Contains the mailbox register address range (base + address and length) +- #mbox-cells Shall be 1 - the index of the channel needed. +- interrupts: Contains the interrupt information corresponding to + the link. + +Example: +-------- + +mbox: mailbox@2a000000 { + compatible = "phytium,mbox"; + reg = <0x0 0x2a000000 0x0 0x1000>; + #mbox-cells = <1>; + interrupts = <0 48 4>; + clocks = <&sycclk>; + clock-names = "apb_pclk"; +}; diff --git a/Documentation/devicetree/bindings/media/phytium-jpeg.txt b/Documentation/devicetree/bindings/media/phytium-jpeg.txt new file mode 100644 index 0000000000000000000000000000000000000000..6dc6d970e8e956d994de622839542e6c9687051b --- /dev/null +++ b/Documentation/devicetree/bindings/media/phytium-jpeg.txt @@ -0,0 +1,21 @@ +* Device tree bindings for Phytium JPEG Engine + +The JPEG Engine embedded in the Phytium SOCs can +capture and compress video data from digital or analog sources. + +Required properties: + - compatible: "phytium,jpeg" + - reg: contains the offset and length of the + JPEG Engine memory region + - interrupts: the interrupt associated with the VE on this platform + - phytium,ocm-buf-addr the physical address used to storage the inputing video data + +Example: + +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/mmc/phytium-mci.txt b/Documentation/devicetree/bindings/mmc/phytium-mci.txt new file mode 100644 index 0000000000000000000000000000000000000000..129efb1fb2f6940e5f462e5ed5189ce357a5595e --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/phytium-mci.txt @@ -0,0 +1,36 @@ +* Phytium Multimedia Card Interface controller + +The highspeed MMC host controller on Phytium SoCs provides an interface +for MMC, SD and SDIO types of memory cards. + +Required properties: +- compatible : should be "phytium,mci". +- reg: mmc controller base registers. +- clocks : phandles to input clocks. +- clock-names : should be "phytium_mci_clk". +- interrupts : mmc controller interrupt. + +Examples: + - Within .dtsi: + mmc0: mmc@28000000 { + compatible = "phytium,mci"; + reg = <0x0 0x28000000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_1200mhz>; + clock-names = "phytium_mci_clk"; + status = "disabled"; + }; + + - Within dts: + &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/net/can/phytium-can.txt b/Documentation/devicetree/bindings/net/can/phytium-can.txt new file mode 100644 index 0000000000000000000000000000000000000000..ab109e54052c8f5c356cbd09596c7b14591cce71 --- /dev/null +++ b/Documentation/devicetree/bindings/net/can/phytium-can.txt @@ -0,0 +1,29 @@ +Phytium CAN controller +------------------------ + +Required properties: +- compatible: Should be: + - "phytium,can" for Phytium CAN controllers + - "phytium,canfd" for Phytium CAN controllers with CANFD support +- reg: Should contain CANFD controller registers location and length +- interrupts: Should contain IRQ line for the CANFD controller +- clocks: CLocks used by the controller +- clock-names: Input clock names, should be "can_clk" +- tx-fifo-depth: Indicates the length of TX FIFO +- rx-fifo-depth: Indicates the length of TX FIFO + +Optional property: +- extend_brp: Indicates to apply the extend BRP parameter of bit timming for + early version of CAN controller + +Example: + + 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/macb.txt b/Documentation/devicetree/bindings/net/macb.txt index 3e17ac1d5d58caa2c89dae53648c5b1d33efe0dd..27168b116916324695cfe57a9cec224a09ff63c8 100644 --- a/Documentation/devicetree/bindings/net/macb.txt +++ b/Documentation/devicetree/bindings/net/macb.txt @@ -15,6 +15,7 @@ Required properties: Use "atmel,sama5d4-gem" for the GEM IP (10/100) available on Atmel sama5d4 SoCs. Use "cdns,zynq-gem" Xilinx Zynq-7xxx SoC. Use "cdns,zynqmp-gem" for Zynq Ultrascale+ MPSoC. + Use "cdns,phytium-gem" for Phytium SoCs. Or the generic form: "cdns,emac". - reg: Address and length of the register set for the device - interrupts: Should contain macb interrupt diff --git a/Documentation/devicetree/bindings/pci/phytium,phytium-pcie-ep.txt b/Documentation/devicetree/bindings/pci/phytium,phytium-pcie-ep.txt new file mode 100644 index 0000000000000000000000000000000000000000..16bcafb3166ef78fcbb82e732cd2fdc10efea45a --- /dev/null +++ b/Documentation/devicetree/bindings/pci/phytium,phytium-pcie-ep.txt @@ -0,0 +1,21 @@ +* Phytium PCIe endpoint controller + +Required properties: +- compatible: Should contain "phytium,phytium-pcie-ep" to identify the IP used. +- reg: Should contain the controller register base address, AXI interface + region base address and hpb register base address respectively. +- reg-names: Must be "reg", "mem" and "hpb" respectively. +- max-outbound-regions: Set to maximum number of outbound regions. +- max-functions: Maximum number of functions that can be configured (default 1). + +Example: + +ep0: ep@0x29030000 { + compatible = "phytium,dp2008-pcie-ep"; + reg = <0x0 0x29030000 0x0 0x10000>, + <0x11 0x00000000 0x1 0x00000000>, + <0x0 0x29101000 0x0 0x1000>; + reg-names = "reg", "mem", "hpb"; + max-outbound-regions = <3>; + max-functions = /bits/ 8 <1>; +}; diff --git a/Documentation/devicetree/bindings/pwm/pwm-phytium.txt b/Documentation/devicetree/bindings/pwm/pwm-phytium.txt new file mode 100644 index 0000000000000000000000000000000000000000..0285e95a3730905651c5cc564edd9b981d8d2a22 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/pwm-phytium.txt @@ -0,0 +1,23 @@ +Phytium PWM controller + +Required properties: +- compatible: should be "phytium,pwm" +- reg: physical base address and length of the controller's registers +- interrupts: interrupt for the pwm controller +- clocks : clock specifiers for both ipg and per clocks. +- phytium,db: 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. + +Example: + +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.txt b/Documentation/devicetree/bindings/rng/phytium-rng.txt new file mode 100644 index 0000000000000000000000000000000000000000..ee893f65c94ce4a011a4553c1f212764dc3a7f9b --- /dev/null +++ b/Documentation/devicetree/bindings/rng/phytium-rng.txt @@ -0,0 +1,13 @@ +Phytium Random Number Generator + +Required properties: +- compatible : Should be "phytium,rng" +- reg : Offset and length of the register set of this block + +Example: + + rng@0x31a06000 { + compatible = "phytium,rng"; + reg = <0x0 0x31a06000 0x0 0x1000>; + }; + diff --git a/Documentation/devicetree/bindings/sound/phytium-i2s.txt b/Documentation/devicetree/bindings/sound/phytium-i2s.txt new file mode 100644 index 0000000000000000000000000000000000000000..bc4acffc4d5b8e3a6f418549f96bb594bbdfc17a --- /dev/null +++ b/Documentation/devicetree/bindings/sound/phytium-i2s.txt @@ -0,0 +1,24 @@ +* Phytium I2S controller + +The I2S bus (Inter-IC sound bus) is a serial link for digital +audio data transfer between devices in the system. + +Required properties: + +- compatible: should be "phytium,i2s" +- reg: It contains two register region: + - first is for physical base address and length of I2S controller. + - second is for physical base address and length of DMA_BDL controller. +- interrupts: should contain the DMA_BDL interrupt. +- clocks: phandle to clock provider with the clock number in the second cell +- dai-name: it will set dai's name used in driver. + +Example for Pe220x I2S controller: + +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/spi-phytium.txt b/Documentation/devicetree/bindings/spi/spi-phytium.txt new file mode 100644 index 0000000000000000000000000000000000000000..a674d192132c0c5f5ddb5ea34481d19b2bfc8676 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/spi-phytium.txt @@ -0,0 +1,24 @@ +Phytium SPI controller + +Required properties: +- compatible: should be "phytium,spi" +- #address-cells: see spi-bus.txt +- #size-cells: see spi-bus.txt +- reg: address and length of the spi master registers +- interrupts: should contain one interrupt +- clocks: spi clock phandle +- num-cs: see spi-bus.txt + +Optional properties: +- cs-gpios: see spi-bus.txt + +Example: + + +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/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt index ac4cd0d6195a89f6bf5619f34cd3987c20597aac..f8618a5204679d0869e3d327eddc8894fb6ae90a 100644 --- a/Documentation/devicetree/bindings/usb/usb-xhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-xhci.txt @@ -18,6 +18,7 @@ Required properties: - "renesas,rcar-gen2-xhci" for a generic R-Car Gen2 or RZ/G1 compatible device - "renesas,rcar-gen3-xhci" for a generic R-Car Gen3 compatible device + - "phytium,pe220x-xhci" for Phytium Pe220x SoC - "xhci-platform" (deprecated) When compatible with the generic version, nodes must list the diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 2c3fc512e7466fe0d7577db7d7c74b803739585c..3334d8399a36103b270a163e77b1cad606d473ba 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -292,6 +292,7 @@ parade Parade Technologies Inc. pericom Pericom Technology Inc. pervasive Pervasive Displays, Inc. phytec PHYTEC Messtechnik GmbH +phytium Phytium Technology Co., Ltd. picochip Picochip Ltd pine64 Pine64 pixcir PIXCIR MICROELECTRONICS Co., Ltd diff --git a/Documentation/devicetree/bindings/w1/phytium-w1.txt b/Documentation/devicetree/bindings/w1/phytium-w1.txt new file mode 100644 index 0000000000000000000000000000000000000000..43784c3aa6de16524a216add43a1fc984b21fe62 --- /dev/null +++ b/Documentation/devicetree/bindings/w1/phytium-w1.txt @@ -0,0 +1,14 @@ +* Phytium 1-wire bus master controller + +Required properties: +- compatible : should be "phytium,w1" +- reg : Address and length of the register set for the device +- interrupts : interrupt line. + +Example: + + onewire0: onewire@2803f000 { + compatible = "phytium,w1"; + reg = <0x0 0x2803f000 0x0 0x1000>; + interrupts = ; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 1061db6fbc326134b0be16538d144e1a151406b8..21de8cb2b6aa57bc1d2b7836ee054e7ee434d34a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9759,6 +9759,12 @@ F: Documentation/media/v4l-drivers/meye* F: drivers/media/pci/meye/ F: include/uapi/linux/meye.h +MOTORCOMM PHY DRIVER +M: Peter Geis +L: netdev@vger.kernel.org +S: Maintained +F: drivers/net/phy/motorcomm.c + MOXA SMARTIO/INDUSTIO/INTELLIO SERIAL CARD M: Jiri Slaby S: Maintained diff --git a/arch/arm/xen/efi.c b/arch/arm/xen/efi.c index bc9a37b3cecd6247c92c98c03221240898241bc8..cf94843ddddcd7e54061476c07ad262e427ff3ce 100644 --- a/arch/arm/xen/efi.c +++ b/arch/arm/xen/efi.c @@ -22,7 +22,7 @@ /* Set XEN EFI runtime services function pointers. Other fields of struct efi, * e.g. efi.systab, will be set like normal EFI. */ -void __init xen_efi_runtime_setup(void) +void xen_efi_runtime_setup(void) { efi.get_time = xen_efi_get_time; efi.set_time = xen_efi_set_time; diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index a101f5d2fbed4a2f2443894d1838695b06cc4cc1..d5e0b8fa2742ab6b0ff72a2fa9879468feb3d033 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -75,6 +75,7 @@ config ARM64 select CLONE_BACKWARDS select COMMON_CLK select CPU_PM if (SUSPEND || CPU_IDLE) + select CRC32 select DCACHE_WORD_ACCESS select DMA_DIRECT_OPS select EDAC_SUPPORT diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 91c7ffad854134eba8ffde43cf1792122f159492..d3ba9fe74f9761085b21d11d296c904902dd4f13 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -140,6 +140,11 @@ config ARCH_MVEBU - Armada 7K SoC Family - Armada 8K SoC Family +config ARCH_PHYTIUM + bool "Phytium SoC Family" + help + This enables support for Phytium ARMv8 SoC family. + config ARCH_QCOM bool "Qualcomm Platforms" select GPIOLIB diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile index 4690364d584bf4e6a9bf43e8b8788fb8b1c5f0cf..ff8820d78db5021d3a57ceb51fb3544687803eb1 100644 --- a/arch/arm64/boot/dts/Makefile +++ b/arch/arm64/boot/dts/Makefile @@ -16,6 +16,7 @@ subdir-y += lg subdir-y += marvell subdir-y += mediatek subdir-y += nvidia +subdir-y += phytium subdir-y += qcom subdir-y += realtek subdir-y += renesas diff --git a/arch/arm64/boot/dts/phytium/Makefile b/arch/arm64/boot/dts/phytium/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..baef40bc012dcc20332bacb7cb06c27b79de35dc --- /dev/null +++ b/arch/arm64/boot/dts/phytium/Makefile @@ -0,0 +1,10 @@ +dtb-$(CONFIG_ARCH_PHYTIUM) += e2000q-demo-board.dtb +dtb-$(CONFIG_ARCH_PHYTIUM) += e2000d-demo-board.dtb +dtb-$(CONFIG_ARCH_PHYTIUM) += e2000s-demo-board.dtb +dtb-$(CONFIG_ARCH_PHYTIUM) += e2000q-miniitx-board.dtb +dtb-$(CONFIG_ARCH_PHYTIUM) += e2000d-miniitx-board.dtb +dtb-$(CONFIG_ARCH_PHYTIUM) += e2000q-vpx-board.dtb +dtb-$(CONFIG_ARCH_PHYTIUM) += e2000q-edu-board.dtb +dtb-$(CONFIG_ARCH_PHYTIUM) += e2000q-come-board.dtb +dtb-$(CONFIG_ARCH_PHYTIUM) += e2000d-power-board.dtb +dtb-$(CONFIG_ARCH_PHYTIUM) += e2000q-hanwei-board.dtb diff --git a/arch/arm64/boot/dts/phytium/e2000d-demo-board.dts b/arch/arm64/boot/dts/phytium/e2000d-demo-board.dts new file mode 100644 index 0000000000000000000000000000000000000000..cde22bf16111cd431569522b71dc609dbc78ba3a --- /dev/null +++ b/arch/arm64/boot/dts/phytium/e2000d-demo-board.dts @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DTS file for Phytium Pe2202 demo board + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + */ + +/dts-v1/; +/memreserve/ 0x80000000 0x10000; + +#include "pe2202.dtsi" +#include "dt-bindings/gpio/gpio.h" + + +/{ + model = "Pe2202 DEMO DDR4"; + compatible = "phytium,pe2202"; + + chosen { + stdout-path = "serial1:115200n8"; + }; + + memory@00{ + device_type = "memory"; + reg = <0x0 0x80000000 0x0 0x80000000>; + }; + + sound_card: sound { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "phytium,pe220x-i2s-audio"; + simple-audio-card,pin-switches = "mic-in"; + simple-audio-card,widgets = "Microphone","mic-in"; + simple-audio-card,routing = "MIC2","mic-in"; + simple-audio-card,cpu { + sound-dai = <&i2s0>; + }; + simple-audio-card,codec{ + sound-dai = <&codec0>; + }; + }; +}; + +&soc { + mio9: i2c@28026000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28026000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + /* localized demo board use this rtc device */ + rtc@32 { + compatible = "wave,sd3068"; + reg = <0x32>; + }; + + rtc@68 { + compatible = "dallas,ds1339"; + reg = <0x68>; + }; + }; + + mio14: i2c@28030000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28030000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + codec0: es8336@10 { + det-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>; + sel-gpios = <&gpio2 11 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <0>; + compatible = "everest,es8336"; + reg = <0x10>; + }; + }; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&gpio2 { + status = "okay"; +}; + +&gpio3 { + status = "okay"; +}; + +&gpio4 { + status = "okay"; +}; + +&gpio5 { + status = "okay"; +}; + +&watchdog0 { + status = "okay"; +}; + +&watchdog1 { + status = "okay"; +}; + +&pcie { + status = "okay"; +}; + +&usb3_0 { + status = "okay"; +}; + +&usb3_1 { + status = "okay"; +}; + +&usb2_0 { + dr_mode = "otg"; + status = "okay"; +}; + +&usb2_1 { + dr_mode = "peripheral"; + status = "okay"; +}; + +&usb2_2 { + dr_mode = "peripheral"; + status = "okay"; +}; + +&macb0 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +/* + * In demo board, M.2 interface may insert two types of disks: PCIE (nvme protocol) or SATA (ahci protocol), + * so here enable 'sata0' node by default to correspond to the sata drive case. Pin multiplexing exists + * in PCIE2 and SATA0, when used as a boot disk, the BIOS can automatically recognize these two conditions. + */ +&sata0 { + status = "okay"; +}; + +&sata1 { + status = "okay"; +}; + +&qspi0 { + status = "okay"; + + flash@0 { + status = "okay"; + }; +}; + +&spi2 { + global-cs = <1>; + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&uart2 { + status = "okay"; +}; + +&can0 { + status = "okay"; +}; + +&can1 { + status = "okay"; +}; + +&mmc0 { + bus-width = <0x00000008>; + max-frequency = <100000000>; + cap-mmc-hw-reset; + cap-mmc-highspeed; + mmc-hs200-1_8v; + no-sdio; + no-sd; + non-removable; + status = "okay"; +}; + +&mmc1 { + bus-width = <0x00000004>; + max-frequency = <50000000>; + cap-sdio-irq; + cap-sd-highspeed; + no-sdio; + no-mmc; + status = "okay"; +}; + +&i2s0 { + #sound-dai-cells = <0>; + status = "okay"; +}; + +&dc0 { + pipe_mask = /bits/ 8 <0x2>; + edp_mask = /bits/ 8 <0x0>; + status = "okay"; +}; + +&i2s_dp1 { + status = "okay"; +}; + +&pmdk_dp { + num-dp = <1>; + dp-mask = /bits/ 8 <0x2>; + status = "okay"; +}; + +&rng0 { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/phytium/e2000d-miniitx-board.dts b/arch/arm64/boot/dts/phytium/e2000d-miniitx-board.dts new file mode 100644 index 0000000000000000000000000000000000000000..1760a910d8dcac0e8cd0a41b15bbf295af0ae6a0 --- /dev/null +++ b/arch/arm64/boot/dts/phytium/e2000d-miniitx-board.dts @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DTS file for Phytium Pe2202 miniitx board + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + * + * Hongmin Qi + */ + +/dts-v1/; +/memreserve/ 0x80000000 0x10000; + +#include "pe2202.dtsi" + +/{ + model = "Pe2202 MINIITX Board"; + compatible = "phytium,pe2202"; + + chosen { + stdout-path = "serial1:115200n8"; + }; + + memory@00{ + device_type = "memory"; + reg = <0x0 0x80000000 0x1 0x00000000>; + }; + + sound_card: sound { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "phytium,pe220x-i2s-audio"; + simple-audio-card,cpu { + sound-dai = <&i2s0>; + }; + simple-audio-card,codec { + sound-dai = <&codec0>; + }; + }; +}; + +&soc { + mio0: uart@28014000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x28014000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "disabled"; + }; + + mio1: uart@28016000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x28016000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "disabled"; + }; + + mio9: i2c@28026000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28026000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + rtc@68 { + compatible = "dallas,ds1339"; + reg = <0x68>; + }; + }; + + mio6: uart@28020000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28020000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio7: uart@28022000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28022000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio14: i2c@28030000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28030000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + codec0: es8336@10 { + det-gpios = <&gpio2 5 0>; + sel-gpios = <&gpio2 6 0>; + #sound-dai-cells = <0>; + compatible = "everest,es8336"; + reg = <0x10>; + mic-src = [20]; + }; + }; + + mio15: uart@28032000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28032000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; +}; + +&pcie { + status = "okay"; +}; + +&usb3_0 { + status = "okay"; +}; + +&usb3_1 { + status = "okay"; +}; + +&usb2_0 { + dr_mode = "otg"; + status = "okay"; +}; + +&usb2_1 { + dr_mode = "host"; + status = "disabled"; +}; + +&usb2_2 { + dr_mode = "host"; + status = "disabled"; +}; + +&usb2_3 { + dr_mode = "host"; + status = "okay"; +}; + +&usb2_4 { + dr_mode = "host"; + status = "okay"; +}; + +&macb0 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&macb1 { + phy-mode = "sgmii"; + use-mii; + status = "disabled"; +}; + +&macb2 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&macb3 { + phy-mode = "sgmii"; + use-mii; + status = "disabled"; +}; + +&can0 { + status = "okay"; +}; + +&can1 { + status = "okay"; +}; + +&qspi0 { + status = "okay"; + + flash@0 { + status = "okay"; + }; +}; + +&spi0 { + global-cs = <1>; + status = "disabled"; + + flash: w25q128@0 { + compatible = "winbond,w25q128", "jedec,spi-nor"; + spi-tx-bus-width = <1>; + spi-rx-bus-width = <1>; + spi-max-frequency = <12000000>; + reg = <0x00>; + status = "disabled"; + }; +}; + +&spi2 { + global-cs = <1>; + status = "disabled"; +}; + +&spi3 { + global-cs = <1>; + status = "disabled"; +}; + +&mmc0 { + status = "okay"; + bus-width = <0x4>; + max-frequency = <50000000>; + cap-sdio-irq; + cap-sd-highspeed; + no-mmc; +}; + +&mmc1 { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&uart2 { + status = "okay"; +}; + +&uart3 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&gpio2 { + status = "okay"; +}; + +&gpio3 { + status = "okay"; +}; + +&gpio4 { + status = "okay"; +}; + +&sata0 { + status = "okay"; +}; + +&sata1 { + status = "okay"; +}; + +&dc0 { + status = "okay"; + pipe_mask = [02]; + edp_mask = [00]; +}; + +&i2s0 { + #sound-dai-cells = <0>; + dai-name = "phytium-i2s-lsd"; + status = "okay"; +}; + +&pwm1 { + phytium,db = <0 0 0 1000 1000 0>; + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/phytium/e2000d-power-board.dts b/arch/arm64/boot/dts/phytium/e2000d-power-board.dts new file mode 100755 index 0000000000000000000000000000000000000000..099dace5410f3d569568ad4e391765fae326afa4 --- /dev/null +++ b/arch/arm64/boot/dts/phytium/e2000d-power-board.dts @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DTS file for Phytium Pe2202 power board + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + * + * Hongmin Qi + */ + +/dts-v1/; +/memreserve/ 0x80000000 0x10000; + +#include "pe2202.dtsi" + +/{ + model = "Pe2202 power Board"; + compatible = "phytium,pe2202"; + + chosen { + stdout-path = "serial1:115200n8"; + }; + + memory@00{ + device_type = "memory"; + reg = <0x0 0x80000000 0x1 0x00000000>; + }; +}; + +&soc { + mio9: i2c@28026000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28026000 0x0 0x1000>; + interrupts = <0x0 0x65 0x4>; + clocks = <&sysclk_50mhz>; + #address-cells = <0x1>; + #size-cells = <0x0>; + status = "okay"; + + rtc@68 { + compatible = "dallas,ds1339"; + reg = <0x68>; + }; + }; + + mio6: i2c@28020000 { + status = "okay"; + compatible = "phytium,i2c"; + reg = <0x0 0x28020000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <0x1>; + #size-cells = <0x0>; + eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + pagesize = <1>; + }; + }; + + mio10: uart@28028000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28028000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio11: uart@2802a000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x2802a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio13: uart@2802e000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x2802e000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio14: uart@28030000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28030000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio15: uart@28032000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28032000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; +}; + +&pcie { + status = "okay"; +}; + +&usb3_0 { + status = "okay"; +}; + +&usb3_1 { + status = "okay"; +}; + +&usb2_0 { + dr_mode = "otg"; + status = "okay"; +}; + +&usb2_3 { + dr_mode = "host"; + status = "okay"; +}; + +&usb2_4 { + dr_mode = "host"; + status = "okay"; +}; + +&macb0 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&macb1 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&macb2 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&macb3 { + phy-mode = "sgmii"; + use-mii; + status = "disabled"; +}; + +&dc0 { + pipe_mask = [02]; + edp_mask = [00]; + status = "okay"; +}; + +&can0 { + status = "okay"; +}; + +&can1 { + status = "okay"; +}; + +&qspi0 { + status = "okay"; + + flash@0 { + status = "okay"; + }; +}; + +&spi0 { + global-cs = <1>; + status = "disabled"; +}; + +&mmc0 { + bus-width = <8>; + max-frequency = <50000000>; + cap-mmc-hw-reset; + cap-mmc-highspeed; + no-sdio; + no-sd; + non-removable; + status = "okay"; +}; + +&mmc1 { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&uart2 { + status = "okay"; +}; + +&uart3 { + status = "okay"; +}; + + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&gpio2 { + status = "okay"; +}; + +&gpio3 { + status = "okay"; +}; + +&gpio4 { + status = "okay"; +}; + +&gpio5 { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/phytium/e2000q-come-board.dts b/arch/arm64/boot/dts/phytium/e2000q-come-board.dts new file mode 100755 index 0000000000000000000000000000000000000000..3d803602f8aabcc494155032162fb0b1d0357a74 --- /dev/null +++ b/arch/arm64/boot/dts/phytium/e2000q-come-board.dts @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DTS file for Phytium Pe2204 come board + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + * + * Hongmin Qi + */ + +/dts-v1/; +/memreserve/ 0x80000000 0x10000; + +#include "pe2204.dtsi" + +/{ + model = "Pe2204 come Board"; + compatible = "phytium,pe2204"; + + chosen { + stdout-path = "serial1:115200n8"; + }; + + memory@00{ + device_type = "memory"; + reg = <0x0 0x80000000 0x2 0x00000000>; + }; +}; + +&soc { + mio3: i2c@2801a000 { + status = "okay"; + compatible = "phytium,i2c"; + reg = <0x0 0x2801a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + + eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + pagesize = <1>; + }; + }; + + mio9: i2c@28026000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28026000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + rtc@32 { + compatible = "wave,sd3068"; + reg = <0x32>; + }; + }; + + mio6: uart@28020000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28020000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio10: uart@28028000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28028000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio14: uart@28030000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28030000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio15: uart@28032000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28032000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; +}; + +&pcie { + status = "okay"; +}; + +&usb3_0 { + status = "okay"; +}; + +&usb3_1 { + status = "okay"; +}; + +&usb2_0 { + dr_mode = "otg"; + status = "okay"; +}; + +&usb2_3 { + dr_mode = "host"; + status = "okay"; +}; + +&usb2_4 { + dr_mode = "host"; + status = "okay"; +}; + +&macb0 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&macb1 { + phy-mode = "sgmii"; + use-mii; + status = "disabled"; +}; + +&macb2 { + phy-mode = "rgmii"; + use-mii; + status = "disabled"; +}; + +&macb3 { + phy-mode = "rgmii"; + use-mii; + status = "disabled"; +}; + +&can0 { + status = "okay"; +}; + +&can1 { + status = "okay"; +}; + +&qspi0 { + status = "okay"; + + flash@0 { + status = "okay"; + }; +}; + +&spi0 { + global-cs = <1>; + status = "disabled"; +}; + +&spi2 { + global-cs = <1>; + status = "okay"; + + flash: w25q128@0 { + compatible = "winbond,w25q128", "jedec,spi-nor"; + spi-tx-bus-width = <1>; + spi-rx-bus-width = <1>; + spi-max-frequency = <12000000>; + reg = <0x00>; + }; +}; + +&mmc0 { + bus-width = <4>; + max-frequency = <25000000>; + cap-mmc-hw-reset; + cap-mmc-highspeed; + no-sdio; + no-sd; + non-removable; + status = "okay"; +}; + +&mmc1 { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&uart2 { + status = "okay"; +}; + +&uart3 { + status = "okay"; +}; + +&sata0 { + status = "okay"; +}; + +&sata1 { + status = "okay"; +}; + +&hda0 { + status = "okay"; +}; + +&dc0 { + status = "okay"; + pipe_mask = [03]; + edp_mask = [00]; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&gpio2 { + status = "okay"; +}; + +&gpio3 { + status = "okay"; +}; + +&gpio4 { + status = "okay"; +}; + +&gpio5 { + status = "okay"; +}; + +&pwm1 { + phytium,db = <0 0 0 1000 1000 0>; + status = "okay"; +}; + diff --git a/arch/arm64/boot/dts/phytium/e2000q-demo-board.dts b/arch/arm64/boot/dts/phytium/e2000q-demo-board.dts new file mode 100644 index 0000000000000000000000000000000000000000..8215b57cc0f940901cd876e36bacbd475539f80a --- /dev/null +++ b/arch/arm64/boot/dts/phytium/e2000q-demo-board.dts @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DTS file for Phytium Pe2204 demo board + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + */ + +/dts-v1/; +/memreserve/ 0x80000000 0x10000; + +#include "pe2204.dtsi" +#include "dt-bindings/gpio/gpio.h" + +/{ + model = "Pe2204 DEMO DDR4"; + compatible = "phytium,pe2204"; + + chosen { + stdout-path = "serial1:115200n8"; + }; + + memory@00{ + device_type = "memory"; + reg = <0x0 0x80000000 0x0 0x80000000>; + }; + + sound_card: sound { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "phytium,pe220x-i2s-audio"; + simple-audio-card,pin-switches = "mic-in"; + simple-audio-card,widgets = "Microphone","mic-in"; + simple-audio-card,routing = "MIC2","mic-in"; + simple-audio-card,cpu { + sound-dai = <&i2s0>; + }; + simple-audio-card,codec{ + sound-dai = <&codec0>; + }; + }; +}; + +&soc { + mio9: i2c@28026000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28026000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + /* localized demo board use this rtc device */ + rtc@32 { + compatible = "wave,sd3068"; + reg = <0x32>; + }; + + rtc@68 { + compatible = "dallas,ds1339"; + reg = <0x68>; + }; + }; + + mio14: i2c@28030000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28030000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + codec0: es8336@10 { + det-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>; + sel-gpios = <&gpio2 11 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <0x0>; + compatible = "everest,es8336"; + reg = <0x10>; + }; + }; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&gpio2 { + status = "okay"; +}; + +&gpio3 { + status = "okay"; +}; + +&gpio4 { + status = "okay"; +}; + +&gpio5 { + status = "okay"; +}; + +&watchdog0 { + status = "okay"; +}; + +&watchdog1 { + status = "okay"; +}; + +&pcie { + status = "okay"; +}; + +&usb3_0 { + status = "okay"; +}; + +&usb3_1 { + status = "okay"; +}; + +&usb2_0 { + dr_mode = "otg"; + status = "okay"; +}; + +&usb2_1 { + dr_mode = "peripheral"; + status = "okay"; +}; + +&usb2_2 { + dr_mode = "peripheral"; + status = "okay"; +}; + +&macb0 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +/* + * In demo board, M.2 interface may insert two types of disks: PCIE (nvme protocol) or SATA (ahci protocol), + * so here enable 'sata0' node by default to correspond to the sata drive case. Pin multiplexing exists + * in PCIE2 and SATA0, when used as a boot disk, the BIOS can automatically recognize these two conditions. + */ +&sata0 { + status = "okay"; +}; + +&sata1 { + status = "okay"; +}; + +&qspi0 { + status = "okay"; + + flash@0 { + status = "okay"; + }; +}; + +&spi2 { + global-cs = <1>; + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&uart2 { + status = "okay"; +}; + +&can0 { + status = "okay"; +}; + +&can1 { + status = "okay"; +}; + +&mmc0 { + bus-width = <0x00000008>; + max-frequency = <100000000>; + cap-mmc-hw-reset; + cap-mmc-highspeed; + mmc-hs200-1_8v; + no-sdio; + no-sd; + non-removable; + status = "okay"; +}; + +&mmc1 { + bus-width = <0x00000004>; + max-frequency = <50000000>; + cap-sdio-irq; + cap-sd-highspeed; + no-sdio; + no-mmc; + status = "okay"; +}; + +&i2s0 { + #sound-dai-cells = <0>; + dai-name = "phytium-i2s-lsd"; + status = "okay"; +}; + +&dc0 { + pipe_mask = /bits/ 8 <0x3>; + edp_mask = /bits/ 8 <0x0>; + status = "okay"; +}; + +&i2s_dp0 { + status = "okay"; +}; + +&i2s_dp1 { + status = "okay"; +}; + +&pmdk_dp { + num-dp = <2>; + dp-mask = /bits/ 8 <0x3>; + status = "okay"; +}; + +&rng0 { + status = "okay"; +}; + diff --git a/arch/arm64/boot/dts/phytium/e2000q-edu-board.dts b/arch/arm64/boot/dts/phytium/e2000q-edu-board.dts new file mode 100755 index 0000000000000000000000000000000000000000..27039933ac1459a672f3265cfb291ae23ee597b5 --- /dev/null +++ b/arch/arm64/boot/dts/phytium/e2000q-edu-board.dts @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DTS file for Phytium Pe2204 edu board + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + * + * Hongmin Qi + */ + +/dts-v1/; +/memreserve/ 0x80000000 0x10000; + +#include "pe2204.dtsi" + +/{ + model = "Pe2204 edu Board"; + compatible = "phytium,pe2204"; + + chosen { + stdout-path = "serial1:115200n8"; + }; + + memory@00{ + device_type = "memory"; + reg = <0x0 0x80000000 0x2 0x00000000>; + }; + + sound_card: sound { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "phytium,pe220x-i2s-audio"; + simple-audio-card,cpu { + sound-dai = <&i2s0>; + }; + simple-audio-card,codec { + sound-dai = <&codec0>; + }; + }; +}; + +&soc { + mio9: i2c@28026000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28026000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <0x1>; + #size-cells = <0x0>; + status = "okay"; + + rtc@68 { + compatible = "dallas,ds1339"; + reg = <0x68>; + }; + }; + + mio8: i2c@28024000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28024000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <0x1>; + #size-cells = <0x0>; + status = "okay"; + }; + + mio14: i2c@28030000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28030000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <0x1>; + #size-cells = <0x0>; + status = "okay"; + + codec0:es8336@10 { + det-gpios = <&gpio2 5 0>; + sel-gpios = <&gpio2 6 0>; + #sound-dai-cells = <0x0>; + compatible = "everest,es8336"; + reg = <0x10>; + mic-src = [30]; + }; + }; + + mio0: uart@28014000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28014000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio1: uart@28016000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28016000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio11: uart@2802A000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x2802a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio15: uart@28032000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28032000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; +}; + +&gpio2 { + status = "okay"; +}; + +&pcie { + status = "okay"; +}; + +&usb3_0 { + status = "okay"; +}; + +&usb3_1 { + //status = "okay"; +}; + +&usb2_0 { + dr_mode = "otg"; + status = "okay"; +}; + +&usb2_1 { + dr_mode = "host"; + //status = "okay"; +}; + +&usb2_2 { + dr_mode = "host"; + //status = "okay"; +}; + +&usb2_3 { + dr_mode = "host"; + status = "okay"; +}; + +&usb2_4 { + dr_mode = "host"; + //status = "okay"; +}; + +&macb0 { + phy-mode = "sgmii"; + use-mii; + //status = "okay"; +}; + +&macb1 { + phy-mode = "sgmii"; + use-mii; + //status = "okay"; +}; + +&macb2 { + phy-mode = "sgmii"; + use-mii; + //status = "okay"; +}; + +&macb3 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&can0 { + status = "okay"; +}; + +&can1 { + status = "okay"; +}; + +&qspi0 { + status = "okay"; + + flash@0 { + status = "okay"; + }; +}; + +&spi0 { + global-cs = <1>; + status = "okay"; + + flash: w25q128@0 { + compatible = "jedec,spi-nor"; + spi-tx-bus-width = <1>; + spi-rx-bus-width = <1>; + spi-max-frequency = <12000000>; + reg = <0x00>; + status = "okay"; + }; +}; + +&mmc0 { + bus-width = <0x8>; + max-frequency = <50000000>; + cap-mmc-hw-reset; + cap-mmc-highspeed; + no-sdio; + no-sd; + non-removable; + status = "okay"; +}; + +&mmc1 { + bus-width = <0x4>; + max-frequency = <25000000>; + cap-sdio-irq; + cap-sd-highspeed; + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&uart2 { + status = "okay"; +}; + +&uart3 { + status = "okay"; +}; + +&sata0 { + //status = "okay"; +}; + +&sata1 { + status = "okay"; +}; + +&dc0 { + status = "okay"; + pipe_mask = [01]; + edp_mask = [00]; +}; + +&i2s0 { + #sound-dai-cells = <0>; + dai-name = "phytium-i2s-lsd"; + status = "okay"; +}; + +&i2s_dp0 { + status = "okay"; +}; + +&pmdk_dp { + num-dp = <1>; + dp-mask = /bits/ 8 <0x1>; + status = "okay"; +}; + +&pwm0 { + phytium,db = <0 0 0 0 0 0>; + status = "okay"; +}; + +&pwm1 { + phytium,db = <0 0 0 0 0 0>; + status = "okay"; +}; + + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&gpio2 { + status = "okay"; +}; + +&gpio3 { + status = "okay"; +}; + +&gpio4 { + status = "okay"; +}; + +&gpio5 { + status = "okay"; +}; + +&keypad { + keypad,num-rows = <4>; + keypad,num-columns = <4>; + linux,keymap = <0x00000011 + 0x00010012 + 0x00020013 + 0x00030014 + 0x01000021 /*KEY_21*/ + 0x01010022 /*KEY_22*/ + 0x01020023 /*KEY_23*/ + 0x01030024 /*KEY_24*/ + 0x02000031 /*KEY_31*/ + 0x02010032 /*KEY_32*/ + 0x02020033 /*KEY_33*/ + 0x02030034 /*KEY_34*/ + 0x03000041 /*KEY_41*/ + 0x03010042 /*KEY_42*/ + 0x03020043 /*KEY_43*/ + 0x03030044 /*KEY_44*/>; + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/phytium/e2000q-hanwei-board.dts b/arch/arm64/boot/dts/phytium/e2000q-hanwei-board.dts new file mode 100755 index 0000000000000000000000000000000000000000..e5902d612d2cfbaecc50285796c8de10b2219fa4 --- /dev/null +++ b/arch/arm64/boot/dts/phytium/e2000q-hanwei-board.dts @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DTS file for Phytium Pe2204 hanwei board + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + * + * Hongmin Qi + */ + +/dts-v1/; +/memreserve/ 0x80000000 0x10000; + +#include "pe2204.dtsi" + +/{ + model = "Pe2204 hanwei Board"; + compatible = "phytium,pe2204"; + + chosen { + stdout-path = "serial1:115200n8"; + }; + + memory@00{ + device_type = "memory"; + reg = <0x0 0x80000000 0x2 0x00000000>; + }; +}; + +&soc { + mio3: uart@2801a000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x2801a000 0x0 0x1000>; + interrupts = <0x0 0x5f 0x4>; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio8: uart@28024000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28024000 0x0 0x1000>; + interrupts = <0x0 0x64 0x4>; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio10: uart@28028000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28028000 0x0 0x1000>; + interrupts = <0x0 0x66 0x4>; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio14: uart@28030000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28030000 0x0 0x1000>; + interrupts = <0x0 0x6a 0x4>; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio15: uart@28032000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0 0x28032000 0x0 0x1000>; + interrupts = <0x0 0x6b 0x4>; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; +}; + +&pcie { + status = "okay"; +}; + +&usb3_0 { + status = "okay"; +}; + +&usb3_1 { + status = "okay"; +}; + +&usb2_0 { + dr_mode = "otg"; + status = "okay"; +}; + +&usb2_3 { + dr_mode = "host"; + status = "okay"; +}; + +&usb2_4 { + dr_mode = "host"; + status = "okay"; +}; + +&macb0 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&macb1 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&macb2 { + phy-mode = "rgmii"; + use-mii; + status = "okay"; +}; + +&macb3 { + phy-mode = "rgmii"; + use-mii; + status = "okay"; +}; + +&can0 { + status = "okay"; +}; + +&can1 { + status = "okay"; +}; + +&qspi0 { + status = "okay"; + + flash@0 { + status = "okay"; + }; +}; + +&spi0 { + global-cs = <1>; + status = "disabled"; +}; + +&mmc0 { + bus-width = <8>; + max-frequency = <25000000>; + cap-mmc-hw-reset; + cap-mmc-highspeed; + no-sdio; + no-sd; + non-removable; + status = "disabled"; +}; + +&mmc1 { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&uart2 { + status = "okay"; +}; + +&uart3 { + status = "okay"; +}; + +&sata0 { + status = "okay"; +}; + +&sata1 { + status = "okay"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&gpio2 { + status = "okay"; +}; + +&gpio3 { + status = "okay"; +}; + +&gpio4 { + status = "okay"; +}; + +&gpio5 { + status = "okay"; +}; + diff --git a/arch/arm64/boot/dts/phytium/e2000q-miniitx-board.dts b/arch/arm64/boot/dts/phytium/e2000q-miniitx-board.dts new file mode 100644 index 0000000000000000000000000000000000000000..9015eae866dce1082c91d269f83bb5bc6e27fc73 --- /dev/null +++ b/arch/arm64/boot/dts/phytium/e2000q-miniitx-board.dts @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DTS file for Phytium miniITX-Pe2204 development board. + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + * + * Shaojun Yang + */ + +/dts-v1/; +/memreserve/ 0x80000000 0x10000; + +#include "pe2204.dtsi" + +/{ + model = "miniITX-Pe2204 Board"; + compatible = "phytium,pe2204"; + + chosen { + stdout-path = "serial1:115200n8"; + }; + aliases { + serial4 = &mio0; + serial5 = &mio1; + serial6 = &mio8; + serial7 = &mio11; + serial8 = &mio15; + }; + + memory@00{ + device_type = "memory"; + reg = <0x0 0x80000000 0x2 0x00000000>; + }; + + sound_card: sound { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "phytium,pe220x-i2s-audio"; + simple-audio-card,cpu { + sound-dai = <&i2s0>; + }; + simple-audio-card,codec{ + sound-dai = <&codec0>; + }; + }; +}; + +&soc { + mio9: i2c@28026000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28026000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + rtc@68 { + compatible = "dallas,ds1339"; + reg = <0x68>; + }; + }; + + mio14: i2c@28030000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28030000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + codec0: es8336@10 { + det-gpios = <&gpio2 5 0>; + sel-gpios = <&gpio2 6 0>; + #sound-dai-cells = <0>; + compatible = "everest,es8336"; + reg = <0x10>; + mic-src = [20]; + }; + }; + + + mio0: uart@28014000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x28014000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio1: uart@28016000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x28016000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio8: uart@28024000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x28024000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio11: uart@2802A000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x2802A000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio15: uart@28032000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x28032000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&gpio2 { + status = "okay"; +}; + +&gpio3 { + status = "okay"; +}; + +&gpio4 { + status = "okay"; +}; + +&gpio5 { + status = "okay"; +}; + +&watchdog0 { + status = "okay"; +}; + +&watchdog1 { + status = "okay"; +}; + +&pcie { + status = "okay"; +}; + +&sata1 { + status = "okay"; +}; + +&usb3_0 { + status = "okay"; +}; + +&usb3_1 { + status = "okay"; +}; + +&usb2_0 { + dr_mode = "otg"; + status = "okay"; +}; + +&usb2_1 { + dr_mode = "peripheral"; + status = "disabled"; +}; + +&usb2_2 { + dr_mode = "peripheral"; + status = "disabled"; +}; + +&usb2_3 { + dr_mode = "host"; + status = "okay"; +}; + +&usb2_4 { + dr_mode = "host"; + status = "okay"; +}; + +&macb0 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&macb2 { + phy-mode = "rgmii"; + use-mii; + status = "okay"; +}; + +&dc0 { + pipe_mask = [03]; + edp_mask = [00]; + status = "okay"; +}; + +&i2s0 { + #sound-dai-cells = <0>; + dai-name = "phytium-i2s-lsd"; + status = "okay"; +}; + +&i2s_dp0 { + dai-name = "phytium-i2s-dp0"; + status = "okay"; +}; + +&qspi0 { + status = "okay"; + + flash@0 { + status = "okay"; + }; +}; + +&spi0 { + global-cs = <1>; + status = "disabled"; + + flash: w25q128@0 { + compatible = "winbond,w25q128", "jedec,spi-nor"; + spi-tx-bus-width = <1>; + spi-rx-bus-width = <1>; + spi-max-frequency = <12000000>; + reg = <0x00>; + status = "disabled"; + }; +}; + +&spi1 { + global-cs = <1>; + status = "disabled"; +}; + +&spi2 { + global-cs = <1>; + status = "disabled"; +}; + +&mmc0 { + bus-width = <0x00000004>; + max-frequency = <50000000>; + cap-sdio-irq; + cap-sd-highspeed; + no-mmc; + status = "okay"; +}; + +&mmc1 { + bus-width = <0x00000008>; + max-frequency = <50000000>; + cap-mmc-hw-reset; + cap-mmc-highspeed; + no-sdio; + no-sd; + non-removable; + status = "disabled"; +}; + +&pwm0 { + phytium,db = <0 0 0 1000 1000 0>; + status = "okay"; +}; + +&pwm1 { + phytium,db = <0 0 0 1000 1000 0>; + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&uart2 { + status = "okay"; +}; + +&uart3 { + status = "okay"; +}; + +&can0 { + status = "okay"; +}; + +&can1 { + status = "okay"; +}; + +&rng0 { + status = "okay"; +}; + diff --git a/arch/arm64/boot/dts/phytium/e2000q-vpx-board.dts b/arch/arm64/boot/dts/phytium/e2000q-vpx-board.dts new file mode 100755 index 0000000000000000000000000000000000000000..c2c0b9e711907c4609d690062013b3d70788e9fa --- /dev/null +++ b/arch/arm64/boot/dts/phytium/e2000q-vpx-board.dts @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DTS file for Phytium Pe2204 vpx board. + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + * + * Tianyu Liu + */ + +/dts-v1/; +/memreserve/ 0x80000000 0x10000; + +#include "pe2204.dtsi" + +/{ + model = "Pe2204 vpx board"; + compatible = "phytium,pe2204"; + + chosen { + stdout-path = "serial1:115200n8"; + }; + + memory@00{ + device_type = "memory"; + reg = <0x0 0x80000000 0x2 0x00000000>; + }; + aliases { + serial4 = &mio3; + serial5 = &mio8; + serial6 = &mio10; + serial7 = &mio11; + serial8 = &mio15; + }; + + sound_card: sound { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "phytium,pe220x-i2s-audio"; + simple-audio-card,cpu { + sound-dai = <&i2s0>; + }; + simple-audio-card,codec{ + sound-dai = <&codec0>; + }; + }; +}; + +&soc { + mio9: i2c@28026000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28026000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + rtc@68 { + compatible = "dallas,ds1339"; + reg = <0x68>; + }; + }; + + mio14: i2c@28030000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28030000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + codec0: es8336@10 { + det-gpios = <&gpio2 5 0>; + sel-gpios = <&gpio2 6 0>; + #sound-dai-cells = <0>; + compatible = "everest,es8336"; + reg = <0x10>; + mic-src = [30]; + }; + }; + + mio3: uart@2801A000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x2801A000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio8: uart@28024000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x28024000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio10: uart@28028000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x28028000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio11: uart@2802A000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x2802A000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio13: uart@2802E000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x2802E000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; + + mio15: uart@28032000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x28032000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz &sysclk_50mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "okay"; + }; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&gpio2 { + status = "okay"; +}; + +&gpio3 { + status = "okay"; +}; + +&gpio4 { + status = "okay"; +}; + +&gpio5 { + status = "okay"; +}; + +&pcie { + status = "okay"; +}; + +&sata0 { + status = "okay"; +}; + +&sata1 { + status = "okay"; +}; + +&usb3_0 { + status = "okay"; +}; + +&usb3_1 { + status = "okay"; +}; + +&usb2_0 { + dr_mode = "otg"; + status = "okay"; +}; + +&usb2_1 { + dr_mode = "host"; + status = "disabled"; +}; + +&usb2_2 { + dr_mode = "host"; + status = "disabled"; +}; + +&usb2_3 { + dr_mode = "host"; + status = "okay"; +}; + +&usb2_4 { + dr_mode = "host"; + status = "okay"; +}; + +&macb0 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&macb2 { + phy-mode = "rgmii"; + use-mii; + force-phy-mode; + status = "okay"; +}; + +&macb3 { + phy-mode = "rgmii"; + use-mii; + status = "okay"; +}; + +&dc0 { + pipe_mask = [03]; + edp_mask = [00]; + status = "okay"; +}; + +&i2s0 { + #sound-dai-cells = <0>; + dai-name = "phytium-i2s-lsd"; + status = "okay"; +}; + +&i2s_dp0 { + status = "okay"; +}; + +&i2s_dp1 { + status = "okay"; +}; + +&pmdk_dp { + num-dp = <2>; + dp-mask = /bits/ 8 <0x3>; + status = "okay"; +}; + +&qspi0 { + status = "okay"; + + flash@0 { + status = "okay"; + }; +}; + +&spi0 { + global-cs = <1>; + status = "disabled"; + + flash: w25q128@0 { + compatible = "winbond,w25q128", "jedec,spi-nor"; + spi-tx-bus-width = <1>; + spi-rx-bus-width = <1>; + spi-max-frequency = <12000000>; + reg = <0x00>; + status = "disabled"; + }; +}; + +&spi2 { + global-cs = <1>; + status = "disabled"; +}; + +&mmc0 { + bus-width = <0x00000008>; + max-frequency = <25000000>; + cap-mmc-hw-reset; + cap-mmc-highspeed; + no-sdio; + no-sd; + non-removable; + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&uart2 { + status = "okay"; +}; + +&uart3 { + status = "okay"; +}; + +&can0 { + status = "okay"; +}; + +&can1 { + status = "okay"; +}; + +&rng0 { + status = "okay"; +}; + diff --git a/arch/arm64/boot/dts/phytium/e2000s-demo-board.dts b/arch/arm64/boot/dts/phytium/e2000s-demo-board.dts new file mode 100644 index 0000000000000000000000000000000000000000..ebaec1a5911c20bab18b88a270adccce7545102d --- /dev/null +++ b/arch/arm64/boot/dts/phytium/e2000s-demo-board.dts @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DTS file for Phytium Pe2201 demo board + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + */ + +/dts-v1/; +/memreserve/ 0x80000000 0x10000; + +#include "pe2201.dtsi" + +/{ + model = "Pe2201 DEMO DDR4"; + compatible = "phytium,pe2201"; + + chosen { + stdout-path = "serial1:115200n8"; + }; + + memory@00{ + device_type = "memory"; + reg = <0x0 0x80000000 0x0 0x80000000>; + }; +}; + +&soc { + mio9: i2c@28026000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28026000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + }; +}; + +&pcie { + status = "okay"; +}; + +&usb2_0 { + dr_mode = "otg"; + status = "okay"; +}; + +&usb2_1 { + dr_mode = "peripheral"; + status = "okay"; +}; + +&usb2_2 { + dr_mode = "peripheral"; + status = "okay"; +}; + +&macb0 { + phy-mode = "sgmii"; + use-mii; + status = "okay"; +}; + +&qspi0 { + status = "okay"; + + flash@0 { + status = "okay"; + }; +}; + +&spi2 { + global-cs = <1>; + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&uart2 { + status = "okay"; +}; + +&mmc0 { + status = "okay"; +}; + +&mmc1 { + status = "okay"; +}; + +&rng0 { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/phytium/pe2201.dtsi b/arch/arm64/boot/dts/phytium/pe2201.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..dfee0aec8f5509527f14ab7b74a9e84584ea0e18 --- /dev/null +++ b/arch/arm64/boot/dts/phytium/pe2201.dtsi @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dts file for Phytium Pe2201 SoC + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + */ + +#include "pe220x.dtsi" + +/ { + compatible = "phytium,pe2201"; + + aliases { + ethernet0 = &macb0; + ethernet1 = &macb1; + }; +}; + +&cpu { + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "phytium,ftc310", "arm,armv8"; + reg = <0x0 0x200>; + enable-method = "psci"; + clocks = <&scmi_dvfs 2>; + + i-cache-size = <0x8000>; + i-cache-line-size = <64>; + d-cache-size = <0x8000>; + d-cache-line-size = <64>; + next-level-cache = <&l2_cache>; + }; + + l2_cache: l2-cache { + compatible = "cache"; + cache-level = <2>; + cache-unified; + cache-size = <0x40000>; + cache-line-size = <64>; + cache-sets = <16>; + }; +}; + +&soc { + i2c0: i2c@28011000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28011000 0x0 0x1000>; + interrupts = ; + interrupt-names = "smbus_alert"; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + i2c1: i2c@28012000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28012000 0x0 0x1000>; + interrupts = ; + interrupt-names = "smbus_alert"; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + i2c2: i2c@28013000 { + compatible = "phytium,i2c"; + reg = <0x0 0x28013000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + onewire0: onewire@2803f000 { + compatible = "phytium,w1"; + reg = <0x0 0x2803f000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + + pwm2: pwm@2804c000 { + compatible = "phytium,pwm"; + reg = <0x0 0x2804c000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + pwm3: pwm@2804d000 { + compatible = "phytium,pwm"; + reg = <0x0 0x2804d000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + pwm4: pwm@2804e000 { + compatible = "phytium,pwm"; + reg = <0x0 0x2804e000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + pwm5: pwm@2804f000 { + compatible = "phytium,pwm"; + reg = <0x0 0x2804f000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + pwm6: pwm@28050000 { + compatible = "phytium,pwm"; + reg = <0x0 0x28050000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + pwm7: pwm@28051000 { + compatible = "phytium,pwm"; + reg = <0x0 0x28051000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + adc0: adc@2807b000 { + compatible = "phytium,adc"; + reg = <0x0 0x2807b000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + + #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 = <6>; + }; + channel@7 { + reg = <7>; + }; + }; + + adc1: adc@2807c000 { + compatible = "phytium,adc"; + reg = <0x0 0x2807c000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + + #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 = <6>; + }; + channel@7 { + reg = <7>; + }; + }; + + sgpio: sgpio@2807d000 { + compatible = "phytium,sgpio"; + reg = <0x0 0x2807d000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + ngpios = <96>; + bus-frequency = <48000>; + gpio-controller; + #gpio-cells = <2>; + status = "disabled"; + }; + + macb0: ethernet@32010000 { + compatible = "cdns,phytium-gem"; + reg = <0x0 0x32010000 0x0 0x2000>; + interrupts = , + , + , + ; + clock-names = "pclk", "hclk", "tx_clk", "tsu_clk"; + clocks = <&sysclk_250mhz>, <&sysclk_48mhz>, <&sysclk_48mhz>, <&sysclk_250mhz>; + magic-packet; + status = "disabled"; + }; + + macb1: ethernet@32012000 { + compatible = "cdns,phytium-gem"; + reg = <0x0 0x32012000 0x0 0x2000>; + interrupts = , + , + , + ; + clock-names = "pclk", "hclk", "tx_clk", "tsu_clk"; + clocks = <&sysclk_250mhz>, <&sysclk_48mhz>, <&sysclk_48mhz>, <&sysclk_250mhz>; + magic-packet; + status = "disabled"; + }; + + jpeg0: jpeg@32b32000 { + compatible = "phytium,jpeg"; + reg = <0x0 0x32b32000 0 0x1000>, + <0x0 0x28072000 0 0x30>, + <0x0 0x28073000 0 0x30>; + interrupts = , + , + ; + phytium,ocm-buf-addr = <0x30c40000 0x30c60000>; + status = "disabled"; + }; +}; diff --git a/arch/arm64/boot/dts/phytium/pe2202.dtsi b/arch/arm64/boot/dts/phytium/pe2202.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..79ab5fc63ed09b22eaf968def32b949e6c933f8d --- /dev/null +++ b/arch/arm64/boot/dts/phytium/pe2202.dtsi @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dts file for Phytium Pe2202 SoC + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + */ + +#include "pe220x.dtsi" + +/ { + compatible = "phytium,pe2202"; + + aliases { + ethernet0 = &macb0; + ethernet1 = &macb1; + ethernet2 = &macb2; + ethernet3 = &macb3; + }; +}; + +&cpu { + cpu-map { + cluster0 { + core0 { + cpu = <&cpu_l0>; + }; + + core1 { + cpu = <&cpu_l1>; + }; + }; + }; + + cpu_l0: cpu@0 { + device_type = "cpu"; + compatible = "phytium,ftc310", "arm,armv8"; + reg = <0x0 0x200>; + enable-method = "psci"; + clocks = <&scmi_dvfs 2>; + + i-cache-size = <0x8000>; + i-cache-line-size = <64>; + d-cache-size = <0x8000>; + d-cache-line-size = <64>; + next-level-cache = <&l2_cache>; + }; + + cpu_l1: cpu@1 { + device_type = "cpu"; + compatible = "phytium,ftc310", "arm,armv8"; + reg = <0x0 0x201>; + enable-method = "psci"; + clocks = <&scmi_dvfs 2>; + + i-cache-size = <0x8000>; + i-cache-line-size = <64>; + d-cache-size = <0x8000>; + d-cache-line-size = <64>; + next-level-cache = <&l2_cache>; + }; + + l2_cache: l2-cache { + compatible = "cache"; + cache-level = <2>; + cache-unified; + cache-size = <0x40000>; + cache-line-size = <64>; + cache-sets = <16>; + }; +}; + +&soc { + hda0: hda@28006000 { + compatible = "phytium,hda"; + reg = <0x0 0x28006000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + + i2s0: i2s@28009000 { + compatible = "phytium,i2s"; + reg = <0x0 0x28009000 0x0 0x1000>, + <0x0 0x28005000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_600mhz>; + clock-names = "i2s_clk"; + status = "disabled"; + }; + + can0: can@2800a000 { + compatible = "phytium,canfd"; + reg = <0x0 0x2800a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_200mhz>; + clock-names = "can_clk"; + tx-fifo-depth = <64>; + rx-fifo-depth = <64>; + status = "disabled"; + }; + + can1: can@2800b000 { + compatible = "phytium,canfd"; + reg = <0x0 0x2800b000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_200mhz>; + clock-names = "can_clk"; + tx-fifo-depth = <64>; + rx-fifo-depth = <64>; + status = "disabled"; + }; + + keypad: keypad@2807a000 { + compatible = "phytium,keypad"; + reg = <0x0 0x2807a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + usb3_0: usb3@31a08000 { + compatible = "phytium,pe220x-xhci"; + reg = <0x0 0x31a08000 0x0 0x18000>; + interrupts = ; + status = "disabled"; + }; + + usb3_1: usb3@31a28000 { + compatible = "phytium,pe220x-xhci"; + reg = <0x0 0x31a28000 0x0 0x18000>; + interrupts = ; + status = "disabled"; + }; + + sata0: sata@31a40000 { + compatible = "generic-ahci"; + reg = <0x0 0x31a40000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + + sata1: sata@32014000 { + compatible = "generic-ahci"; + reg = <0x0 0x32014000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + + macb0: ethernet@3200c000 { + compatible = "cdns,phytium-gem"; + reg = <0x0 0x3200c000 0x0 0x2000>; + interrupts = , + , + , + , + , + , + , + ; + clock-names = "pclk", "hclk", "tx_clk", "tsu_clk"; + clocks = <&sysclk_250mhz>, <&sysclk_48mhz>, <&sysclk_48mhz>, <&sysclk_250mhz>; + magic-packet; + status = "disabled"; + }; + + macb1: ethernet@3200e000 { + compatible = "cdns,phytium-gem"; + reg = <0x0 0x3200e000 0x0 0x2000>; + interrupts = , + , + , + ; + clock-names = "pclk", "hclk", "tx_clk", "tsu_clk"; + clocks = <&sysclk_250mhz>, <&sysclk_48mhz>, <&sysclk_48mhz>, <&sysclk_250mhz>; + magic-packet; + status = "disabled"; + }; + + macb2: ethernet@32010000 { + compatible = "cdns,phytium-gem"; + reg = <0x0 0x32010000 0x0 0x2000>; + interrupts = , + , + , + ; + clock-names = "pclk", "hclk", "tx_clk", "tsu_clk"; + clocks = <&sysclk_250mhz>, <&sysclk_48mhz>, <&sysclk_48mhz>, <&sysclk_250mhz>; + magic-packet; + use-mii; + status = "disabled"; + }; + + macb3: ethernet@32012000 { + compatible = "cdns,phytium-gem"; + reg = <0x0 0x32012000 0x0 0x2000>; + interrupts = , + , + , + ; + clock-names = "pclk", "hclk", "tx_clk", "tsu_clk"; + clocks = <&sysclk_250mhz>, <&sysclk_48mhz>, <&sysclk_48mhz>, <&sysclk_250mhz>; + magic-packet; + use-mii; + status = "disabled"; + }; +}; diff --git a/arch/arm64/boot/dts/phytium/pe2204.dtsi b/arch/arm64/boot/dts/phytium/pe2204.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..6fd6cb523df78a532f5249fc72673e5a8d4f314b --- /dev/null +++ b/arch/arm64/boot/dts/phytium/pe2204.dtsi @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dts file for Phytium Pe2204 SoC + * + * Copyright (c) 2022-2023 Phytium Technology Co., Ltd. + */ + +#include "pe220x.dtsi" + +/ { + compatible = "phytium,pe2204"; + + aliases { + ethernet0 = &macb0; + ethernet1 = &macb1; + ethernet2 = &macb2; + ethernet3 = &macb3; + }; +}; + +&cpu { + cpu-map { + cluster0 { + core0 { + cpu = <&cpu_b0>; + }; + }; + + cluster1 { + core0 { + cpu = <&cpu_b1>; + }; + }; + + cluster2 { + core0 { + cpu = <&cpu_l0>; + }; + + core1 { + cpu = <&cpu_l1>; + }; + }; + }; + + cpu_l0: cpu@0 { + device_type = "cpu"; + compatible = "phytium,ftc310", "arm,armv8"; + reg = <0x0 0x200>; + enable-method = "psci"; + clocks = <&scmi_dvfs 2>; + + i-cache-size = <0x8000>; + i-cache-line-size = <64>; + d-cache-size = <0x8000>; + d-cache-line-size = <64>; + next-level-cache = <&l2_cache_l>; + }; + + cpu_l1: cpu@1 { + device_type = "cpu"; + compatible = "phytium,ftc310", "arm,armv8"; + reg = <0x0 0x201>; + enable-method = "psci"; + clocks = <&scmi_dvfs 2>; + + i-cache-size = <0x8000>; + i-cache-line-size = <64>; + d-cache-size = <0x8000>; + d-cache-line-size = <64>; + next-level-cache = <&l2_cache_l>; + }; + + l2_cache_l: l2-cache { + compatible = "cache"; + cache-level = <2>; + cache-unified; + cache-size = <0x40000>; + cache-line-size = <64>; + cache-sets = <16>; + }; + + cpu_b0: cpu@100 { + device_type = "cpu"; + compatible = "phytium,ftc664", "arm,armv8"; + reg = <0x0 0x0>; + enable-method = "psci"; + clocks = <&scmi_dvfs 0>; + + i-cache-size = <0xc000>; + i-cache-line-size = <64>; + d-cache-size = <0x8000>; + d-cache-line-size = <64>; + next-level-cache = <&l2_cache_b>; + }; + + cpu_b1: cpu@101 { + device_type = "cpu"; + compatible = "phytium,ftc664", "arm,armv8"; + reg = <0x0 0x100>; + enable-method = "psci"; + clocks = <&scmi_dvfs 1>; + + i-cache-size = <0xc000>; + i-cache-line-size = <64>; + d-cache-size = <0x8000>; + d-cache-line-size = <64>; + next-level-cache = <&l2_cache_b>; + }; + + l2_cache_b: l2-cache { + compatible = "cache"; + cache-level = <2>; + cache-unified; + cache-size = <0x100000>; + cache-line-size = <64>; + cache-sets = <16>; + }; +}; + +&soc { + hda0: hda@28006000 { + compatible = "phytium,hda"; + reg = <0x0 0x28006000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + + i2s0: i2s@28009000 { + compatible = "phytium,i2s"; + reg = <0x0 0x28009000 0x0 0x1000>, + <0x0 0x28005000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_600mhz>; + clock-names = "i2s_clk"; + status = "disabled"; + }; + + can0: can@2800a000 { + compatible = "phytium,canfd"; + reg = <0x0 0x2800a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_200mhz>; + clock-names = "can_clk"; + tx-fifo-depth = <64>; + rx-fifo-depth = <64>; + status = "disabled"; + }; + + can1: can@2800b000 { + compatible = "phytium,canfd"; + reg = <0x0 0x2800b000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_200mhz>; + clock-names = "can_clk"; + tx-fifo-depth = <64>; + rx-fifo-depth = <64>; + status = "disabled"; + }; + + keypad: keypad@2807a000 { + compatible = "phytium,keypad"; + reg = <0x0 0x2807a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + usb3_0: usb3@31a08000 { + compatible = "phytium,pe220x-xhci"; + reg = <0x0 0x31a08000 0x0 0x18000>; + interrupts = ; + status = "disabled"; + }; + + usb3_1: usb3@31a28000 { + compatible = "phytium,pe220x-xhci"; + reg = <0x0 0x31a28000 0x0 0x18000>; + interrupts = ; + status = "disabled"; + }; + + sata0: sata@31a40000 { + compatible = "generic-ahci"; + reg = <0x0 0x31a40000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + + sata1: sata@32014000 { + compatible = "generic-ahci"; + reg = <0x0 0x32014000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + + macb0: ethernet@3200c000 { + compatible = "cdns,phytium-gem"; + reg = <0x0 0x3200c000 0x0 0x2000>; + interrupts = , + , + , + , + , + , + , + ; + clock-names = "pclk", "hclk", "tx_clk", "tsu_clk"; + clocks = <&sysclk_250mhz>, <&sysclk_48mhz>, <&sysclk_48mhz>, <&sysclk_250mhz>; + magic-packet; + support-tsn; + status = "disabled"; + }; + + macb1: ethernet@3200e000 { + compatible = "cdns,phytium-gem"; + reg = <0x0 0x3200e000 0x0 0x2000>; + interrupts = , + , + , + ; + clock-names = "pclk", "hclk", "tx_clk", "tsu_clk"; + clocks = <&sysclk_250mhz>, <&sysclk_48mhz>, <&sysclk_48mhz>, <&sysclk_250mhz>; + magic-packet; + status = "disabled"; + }; + + macb2: ethernet@32010000 { + compatible = "cdns,phytium-gem"; + reg = <0x0 0x32010000 0x0 0x2000>; + interrupts = , + , + , + ; + clock-names = "pclk", "hclk", "tx_clk", "tsu_clk"; + clocks = <&sysclk_250mhz>, <&sysclk_48mhz>, <&sysclk_48mhz>, <&sysclk_250mhz>; + magic-packet; + status = "disabled"; + }; + + macb3: ethernet@32012000 { + compatible = "cdns,phytium-gem"; + reg = <0x0 0x32012000 0x0 0x2000>; + interrupts = , + , + , + ; + clock-names = "pclk", "hclk", "tx_clk", "tsu_clk"; + clocks = <&sysclk_250mhz>, <&sysclk_48mhz>, <&sysclk_48mhz>, <&sysclk_250mhz>; + magic-packet; + status = "disabled"; + }; + +}; diff --git a/arch/arm64/boot/dts/phytium/pe220x.dtsi b/arch/arm64/boot/dts/phytium/pe220x.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..37bfb66b13f40886cb06180d81e4e1d7afc02880 --- /dev/null +++ b/arch/arm64/boot/dts/phytium/pe220x.dtsi @@ -0,0 +1,975 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dts file for Phytium Pe220x SoC + * + * Copyright (c) 2021-2023 Phytium Technology Co., Ltd. + */ + +#include + +/ { + compatible = "phytium,pe220x"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + aliases { + serial0 = &uart0; + serial1 = &uart1; + serial2 = &uart2; + serial3 = &uart3; + }; + + psci { + compatible = "arm,psci-1.0"; + method = "smc"; + cpu_suspend = <0xc4000001>; + cpu_off = <0x84000002>; + cpu_on = <0xc4000003>; + sys_poweroff = <0x84000008>; + sys_reset = <0x84000009>; + }; + + firmware { + scmi: scmi { + compatible = "arm,scmi"; + mboxes = <&mbox 0 &mbox 1>; + mbox-names = "tx", "rx"; + shmem = <&cpu_scp_hpri &cpu_scp_lpri>; + #address-cells = <1>; + #size-cells = <0>; + + scmi_dvfs: protocol@13 { + reg = <0x13>; + #clock-cells = <1>; + }; + + scmi_sensors0: protocol@15 { + reg = <0x15>; + #thermal-sensor-cells = <1>; + }; + }; + optee { + compatible = "linaro,optee-tz"; + method = "smc"; + }; + }; + + thermal-zones { + sensor0 { + polling-delay-passive = <100>; + polling-delay = <1000>; + thermal-sensors = <&scmi_sensors0 0>; + }; + + sensor1 { + polling-delay-passive = <100>; + polling-delay = <1000>; + thermal-sensors = <&scmi_sensors0 1>; + }; + }; + + cpu: cpus { + #address-cells = <0x2>; + #size-cells = <0x0>; + }; + + gic: interrupt-controller@30800000 { + compatible = "arm,gic-v3"; + #interrupt-cells = <3>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + interrupt-controller; + reg = <0x0 0x30800000 0 0x20000>, /* GICD */ + <0x0 0x30880000 0 0x80000>, /* GICR */ + <0x0 0x30840000 0 0x10000>, /* GICC */ + <0x0 0x30850000 0 0x10000>, /* GICH */ + <0x0 0x30860000 0 0x10000>; /* GICV */ + interrupts = ; + + its: gic-its@30820000 { + compatible = "arm,gic-v3-its"; + msi-controller; + reg = <0x0 0x30820000 0x0 0x20000>; + }; + }; + + pmu { + compatible = "arm,armv8-pmuv3"; + interrupts = ; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = , + , + , + ; + clock-frequency = <50000000>; + }; + + clocks { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + sysclk_48mhz: clk48mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <48000000>; + }; + + sysclk_50mhz: clk50mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <50000000>; + }; + + sysclk_100mhz: clk100mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <100000000>; + }; + + sysclk_200mhz: clk200mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <200000000>; + }; + + sysclk_250mhz: clk250mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <250000000>; + }; + + sysclk_300mhz: clk300mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <300000000>; + }; + + sysclk_600mhz: clk600mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <600000000>; + }; + + sysclk_1200mhz: clk1200mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <1200000000>; + }; + }; + + smmu: iommu@30000000 { + compatible = "arm,smmu-v3"; + reg = <0x0 0x30000000 0x0 0x800000>; + interrupts = , + , + , + ; + interrupt-names = "eventq", "priq", "cmdq-sync", "gerror"; + dma-coherent; + #iommu-cells = <1>; + }; + + soc: soc { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <2>; + dma-coherent; + ranges; + + mmc0: mmc@28000000 { + compatible = "phytium,mci"; + reg = <0x0 0x28000000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_1200mhz>; + clock-names = "phytium_mci_clk"; + status = "disabled"; + }; + + mmc1: mmc@28001000 { + compatible = "phytium,mci"; + reg = <0x0 0x28001000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_1200mhz>; + clock-names = "phytium_mci_clk"; + status = "disabled"; + }; + + nand0: nand@28002000 { + compatible = "phytium,nfc"; + reg = <0x0 0x28002000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + + qspi0: spi@28008000 { + compatible = "phytium,qspi"; + reg = <0x0 0x28008000 0x0 0x1000>, <0x0 0x0 0x0 0x0fffffff>; + reg-names = "qspi", "qspi_mm"; + clocks = <&sysclk_50mhz>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + + flash@0 { /* Note: By default, chip select 0 is for BIOS, so be careful when enabling this node!!! */ + reg = <0>; + spi-rx-bus-width = <1>; + spi-max-frequency = <50000000>; + status = "disabled"; + }; + }; + + uart0: uart@2800c000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x2800c000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_100mhz &sysclk_100mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "disabled"; + }; + + uart1: uart@2800d000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x2800d000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_100mhz &sysclk_100mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "disabled"; + }; + + uart2: uart@2800e000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x2800e000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_100mhz &sysclk_100mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "disabled"; + }; + + uart3: uart@2800f000 { + compatible = "arm,pl011","arm,primecell"; + reg = <0x0 0x2800f000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_100mhz &sysclk_100mhz>; + clock-names = "uartclk", "apb_pclk"; + status = "disabled"; + }; + + lpc: lpc@28010000 { + compatible = "simple-mfd", "syscon"; + reg = <0x0 0x28010000 0x0 0x1000>; + reg-io-width = <4>; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x0 0x28010000 0x1000>; + + kcs0: kcs@24 { + compatible = "phytium,kcs-bmc"; + reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>; + interrupts = ; + status = "disabled"; + }; + + kcs1: kcs@28 { + compatible = "phytium,kcs-bmc"; + reg = <0x28 0x1>, <0x34 0x1>, <0x40 0x1>; + interrupts = ; + status = "disabled"; + }; + + kcs2: kcs@2c { + compatible = "phytium,kcs-bmc"; + reg = <0x2c 0x1>, <0x38 0x1>, <0x44 0x1>; + interrupts = ; + status = "disabled"; + }; + + kcs3: kcs@8c { + compatible = "phytium,kcs-bmc"; + reg = <0x8c 0x1>, <0x90 0x1>, <0x94 0x1>; + interrupts = ; + status = "disabled"; + }; + + bt: bt@48 { + compatible = "phytium,bt-bmc"; + reg = <0x48 0x20>; + interrupts = ; + status = "disabled"; + }; + }; + + /* MIO */ + + gpio0: gpio@28034000 { + compatible = "phytium,gpio"; + reg = <0x0 0x28034000 0x0 0x1000>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + + porta { + compatible = "phytium,gpio-port"; + reg = <0>; + nr-gpios = <16>; + }; + }; + + gpio1: gpio@28035000 { + compatible = "phytium,gpio"; + reg = <0x0 0x28035000 0x0 0x1000>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + + porta { + compatible = "phytium,gpio-port"; + reg = <0>; + nr-gpios = <16>; + }; + }; + + gpio2: gpio@28036000 { + compatible = "phytium,gpio"; + reg = <0x0 0x28036000 0x0 0x1000>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + + porta { + compatible = "phytium,gpio-port"; + reg = <0>; + nr-gpios = <16>; + }; + }; + + gpio3: gpio@28037000 { + compatible = "phytium,gpio"; + reg = <0x0 0x28037000 0x0 0x1000>; + interrupts = ; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + + porta { + compatible = "phytium,gpio-port"; + reg = <0>; + nr-gpios = <16>; + }; + }; + + gpio4: gpio@28038000 { + compatible = "phytium,gpio"; + reg = <0x0 0x28038000 0x0 0x1000>; + interrupts = ; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + + porta { + compatible = "phytium,gpio-port"; + reg = <0>; + nr-gpios = <16>; + }; + }; + + gpio5: gpio@28039000 { + compatible = "phytium,gpio"; + reg = <0x0 0x28039000 0x0 0x1000>; + interrupts = ; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + + porta { + compatible = "phytium,gpio-port"; + reg = <0>; + nr-gpios = <16>; + }; + }; + + spi0: spi@2803a000 { + compatible = "phytium,spi"; + reg = <0x0 0x2803a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + num-cs = <4>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi1: spi@2803b000 { + compatible = "phytium,spi"; + reg = <0x0 0x2803b000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + num-cs = <4>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi2: spi@2803c000 { + compatible = "phytium,spi"; + reg = <0x0 0x2803c000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + num-cs = <4>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi3: spi@2803d000 { + compatible = "phytium,spi"; + reg = <0x0 0x2803d000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + num-cs = <4>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + watchdog0: watchdog@28040000 { + compatible = "arm,sbsa-gwdt"; + reg = <0x0 0x28041000 0x0 0x1000>, + <0x0 0x28040000 0x0 0x1000>; + interrupts = ; + timeout-sec = <30>; + status = "disabled"; + }; + + watchdog1: watchdog@28042000 { + compatible = "arm,sbsa-gwdt"; + reg = <0x0 0x28043000 0x0 0x1000>, + <0x0 0x28042000 0x0 0x1000>; + interrupts = ; + timeout-sec = <30>; + status = "disabled"; + }; + + pwm0: pwm@2804a000 { + compatible = "phytium,pwm"; + reg = <0x0 0x2804a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + pwm1: pwm@2804b000 { + compatible = "phytium,pwm"; + reg = <0x0 0x2804b000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz>; + status = "disabled"; + }; + + tacho0: tacho@28054000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28054000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho1: tacho@28055000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28055000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho2: tacho@28056000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28056000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho3: tacho@28057000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28057000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho4: tacho@28058000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28058000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho5: tacho@28059000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28059000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho6: tacho@2805a000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2805a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho7: tacho@2805b000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2805b000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho8: tacho@2805c000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2805c000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho9: tacho@2805d000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2805d000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho10: tacho@2805e000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2805e000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho11: tacho@2805f000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2805f000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho12: tacho@28060000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28060000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho13: tacho@28061000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28061000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho14: tacho@28062000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28062000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho15: tacho@28063000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28063000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho16: tacho@28064000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28064000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho17: tacho@28065000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28065000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho18: tacho@28066000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28066000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho19: tacho@28067000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28067000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho20: tacho@28068000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28068000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho21: tacho@28069000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28069000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho22: tacho@2806a000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2806a000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho23: tacho@2806b000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2806b000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho24: tacho@2806c000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2806c000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho25: tacho@2806d000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2806d000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho26: tacho@2806e000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2806e000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho27: tacho@2806f000 { + compatible = "phytium,tacho"; + reg = <0x0 0x2806f000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho28: tacho@28070000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28070000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho29: tacho@28071000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28071000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho30: tacho@28072000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28072000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho31: tacho@28073000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28073000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho32: tacho@28074000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28074000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho33: tacho@28075000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28075000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho34: tacho@28076000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28076000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho35: tacho@28077000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28077000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho36: tacho@28078000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28078000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + tacho37: tacho@28079000 { + compatible = "phytium,tacho"; + reg = <0x0 0x28079000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_50mhz >; + status = "disabled"; + }; + + usb2_0: usb2@31800000 { /* usb_vhub0 USB2_P2 only otg mode */ + compatible = "phytium,usb2"; + reg = <0x0 0x31800000 0x0 0x80000>, + <0x0 0x31990000 0x0 0x10000>; + interrupts = ; + status = "disabled"; + }; + + usb2_1: usb2@31880000 { /* usb_vhub1 USB2_P2 */ + compatible = "phytium,usb2"; + reg = <0x0 0x31880000 0x0 0x80000>, + <0x0 0x319a0000 0x0 0x10000>; + interrupts = ; + status = "disabled"; + }; + + usb2_2: usb2@31900000 { /* usb_vhub2 USB2_P2 */ + compatible = "phytium,usb2"; + reg = <0x0 0x31900000 0x0 0x80000>, + <0x0 0x319b0000 0x0 0x10000>; + interrupts = ; + status = "disabled"; + }; + + usb2_3: usb2@32800000 { /* USB2_0 controller registers USB2_P3 */ + compatible = "phytium,usb2"; + reg = <0x0 0x32800000 0x0 0x40000>, + <0x0 0x32880000 0x0 0x40000>; /* USB2_0 UIB registers */ + interrupts = ; + status = "disabled"; + }; + + usb2_4: usb2@32840000 { /* USB2_1 controller registers USB2_P4 */ + compatible = "phytium,usb2"; + reg = <0x0 0x32840000 0x0 0x40000>, + <0x0 0x328c0000 0x0 0x40000>; /* USB2_1 UIB registers */ + interrupts = ; + status = "disabled"; + }; + + dc0: dc@32000000 { + compatible = "phytium,dc"; + reg = <0x0 0x32000000 0x0 0x8000>; + interrupts = ; + status = "disabled"; + }; + + i2s_dp0: i2s_dp0@32009000 { + compatible = "phytium,i2s"; + reg = <0x0 0x32009000 0x0 0x1000>, + <0x0 0x32008000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_600mhz>; + clock-names = "i2s_clk"; + dai-name = "phytium-i2s-dp0"; + status = "disabled"; + }; + + i2s_dp1: i2s_dp1@3200B000 { + compatible = "phytium,i2s"; + reg = <0x0 0x3200B000 0x0 0x1000>, + <0x0 0x3200A000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_600mhz>; + clock-names = "i2s_clk"; + dai-name = "phytium-i2s-dp1"; + status = "disabled"; + }; + + pmdk_dp: pmdk_dp { + compatible = "phytium,pmdk-dp"; + status = "disabled"; + }; + + mbox: mailbox@32a00000 { + compatible = "phytium,mbox"; + reg = <0x0 0x32a00000 0x0 0x1000>; + interrupts = ; + #mbox-cells = <1>; + }; + + rng0: rng@32a36000 { + compatible = "phytium,rng"; + reg = <0x0 0x32a36000 0x0 0x1000>; + status = "disabled"; + }; + + sram: sram@32a10000 { + compatible = "phytium,pe220x-sram-ns", "mmio-sram"; + reg = <0x0 0x32a10000 0x0 0x2000>; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x0 0x32a10000 0x2000>; + + cpu_scp_lpri: scp-shmem@0 { + compatible = "arm,scmi-shmem"; + reg = <0x1000 0x400>; + }; + + cpu_scp_hpri: scp-shmem@1 { + compatible = "arm,scmi-shmem"; + reg = <0x1400 0x400>; + }; + }; + + hwspinlock: spinlock@32b36000 { + compatible = "phytium,hwspinlock"; + reg = <0x0 0x32b36000 0x0 0x1000>; + #hwlock-cells = <1>; + nr-locks = <32>; + status = "disabled"; + }; + + pcie: pcie@40000000 { + compatible = "pci-host-ecam-generic"; + device_type = "pci"; + #address-cells = <3>; + #size-cells = <2>; + #interrupt-cells = <1>; + reg = <0x0 0x40000000 0x0 0x10000000>; + msi-parent = <&its>; + bus-range = <0x0 0xff>; + interrupt-map-mask = <0x0 0x0 0x0 0x7>; + interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0x0 GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>, + <0x0 0x0 0x0 0x2 &gic 0x0 0x0 GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>, + <0x0 0x0 0x0 0x3 &gic 0x0 0x0 GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>, + <0x0 0x0 0x0 0x4 &gic 0x0 0x0 GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>; + ranges = <0x01000000 0x00 0x00000000 0x0 0x50000000 0x0 0x00f00000>, + <0x02000000 0x00 0x58000000 0x0 0x58000000 0x0 0x28000000>, + <0x03000000 0x10 0x00000000 0x10 0x00000000 0x10 0x00000000>; + iommu-map = <0x0 &smmu 0x0 0x10000>; + status = "disabled"; + }; + + }; +}; diff --git a/arch/arm64/configs/phytium_defconfig b/arch/arm64/configs/phytium_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..acd65b98533473796913cf7346151cbeb986fdf7 --- /dev/null +++ b/arch/arm64/configs/phytium_defconfig @@ -0,0 +1,550 @@ +CONFIG_LOCALVERSION="-phytium-embeded" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_USELIB=y +CONFIG_AUDIT=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_IRQ_TIME_ACCOUNTING=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_MEMCG=y +CONFIG_MEMCG_SWAP=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_HUGETLB=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_USER_NS=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_KALLSYMS_ALL=y +# CONFIG_COMPAT_BRK is not set +CONFIG_PROFILING=y +CONFIG_ARCH_PHYTIUM=y +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_HOTPLUG_PCI_PCIE=y +CONFIG_PCI_DEBUG=y +CONFIG_PCI_REALLOC_ENABLE_AUTO=y +CONFIG_PCI_IOV=y +CONFIG_HOTPLUG_PCI=y +CONFIG_HOTPLUG_PCI_ACPI=y +CONFIG_PCI_HOST_GENERIC=y +CONFIG_PCIE_PHYTIUM_EP=y +CONFIG_PCI_ENDPOINT=y +CONFIG_ARM64_VA_BITS_48=y +CONFIG_SCHED_MC=y +CONFIG_SECCOMP=y +CONFIG_KEXEC=y +CONFIG_CRASH_DUMP=y +CONFIG_XEN=y +CONFIG_COMPAT=y +CONFIG_HIBERNATION=y +CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y +CONFIG_CPU_IDLE_GOV_LADDER=y +CONFIG_ARM_CPUIDLE=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y +CONFIG_CPUFREQ_DT=y +CONFIG_ACPI_CPPC_CPUFREQ=y +CONFIG_ARM_BIG_LITTLE_CPUFREQ=y +CONFIG_ARM_SCPI_CPUFREQ=y +CONFIG_ARM_SCMI_CPUFREQ=y +CONFIG_ARM_SCMI_PROTOCOL=y +CONFIG_ARM_SCMI_TRANSPORT_FORCE_POLLING=y +CONFIG_ARM_SCPI_PROTOCOL=y +CONFIG_DMI_SYSFS=y +CONFIG_EFI_CAPSULE_LOADER=y +CONFIG_ACPI=y +CONFIG_ACPI_APEI=y +CONFIG_ACPI_APEI_GHES=y +CONFIG_ACPI_APEI_MEMORY_FAILURE=y +CONFIG_ACPI_APEI_EINJ=y +CONFIG_VIRTUALIZATION=y +CONFIG_KVM=y +CONFIG_ARM64_CRYPTO=y +CONFIG_CRYPTO_SHA1_ARM64_CE=y +CONFIG_CRYPTO_SHA2_ARM64_CE=y +CONFIG_CRYPTO_SHA512_ARM64_CE=m +CONFIG_CRYPTO_SHA3_ARM64=m +CONFIG_CRYPTO_SM3_ARM64_CE=m +CONFIG_CRYPTO_GHASH_ARM64_CE=y +CONFIG_CRYPTO_CRCT10DIF_ARM64_CE=m +CONFIG_CRYPTO_CRC32_ARM64_CE=m +CONFIG_CRYPTO_AES_ARM64_CE_CCM=y +CONFIG_CRYPTO_AES_ARM64_CE_BLK=y +CONFIG_CRYPTO_AES_ARM64_NEON_BLK=y +CONFIG_CRYPTO_CHACHA20_NEON=m +CONFIG_CRYPTO_AES_ARM64_BS=m +CONFIG_JUMP_LABEL=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_BLK_DEV_INTEGRITY=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_KSM=y +CONFIG_MEMORY_FAILURE=y +CONFIG_TRANSPARENT_HUGEPAGE=y +CONFIG_CMA=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IPV6 is not set +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m +CONFIG_NETFILTER_XT_TARGET_LOG=m +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m +CONFIG_IP_NF_IPTABLES=m +CONFIG_IP_NF_FILTER=m +CONFIG_IP_NF_TARGET_REJECT=m +CONFIG_IP_NF_NAT=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +CONFIG_IP_NF_MANGLE=m +CONFIG_BRIDGE=m +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=m +CONFIG_VLAN_8021Q_GVRP=y +CONFIG_VLAN_8021Q_MVRP=y +CONFIG_BPF_JIT=y +CONFIG_CAN=y +CONFIG_CAN_RAW=m +CONFIG_CAN_BCM=m +CONFIG_CAN_GW=m +CONFIG_CAN_PHYTIUM=m +CONFIG_CAN_PHYTIUM_PLATFORM=m +CONFIG_CAN_PHYTIUM_PCI=m +CONFIG_BT=m +CONFIG_BT_HIDP=m +# CONFIG_BT_HS is not set +# CONFIG_BT_LE is not set +CONFIG_BT_LEDS=y +# CONFIG_BT_DEBUGFS is not set +CONFIG_BT_HCIBTUSB=m +CONFIG_BT_HCIUART=m +CONFIG_BT_HCIUART_LL=y +CONFIG_BT_HCIUART_BCM=y +CONFIG_CFG80211=m +CONFIG_MAC80211=m +CONFIG_MAC80211_LEDS=y +CONFIG_RFKILL=m +CONFIG_NET_9P=m +CONFIG_NET_9P_VIRTIO=m +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=32 +CONFIG_BRCMSTB_GISB_ARB=y +CONFIG_SIMPLE_PM_BUS=y +CONFIG_VEXPRESS_CONFIG=y +CONFIG_MTD=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_CFI=y +CONFIG_MTD_CFI_AMDSTD=y +CONFIG_MTD_PHYSMAP=y +CONFIG_MTD_PHYSMAP_OF=y +CONFIG_MTD_M25P80=y +CONFIG_MTD_NAND=y +CONFIG_MTD_NAND_PHYTIUM_PCI=y +CONFIG_MTD_NAND_PHYTIUM_PLAT=y +CONFIG_MTD_SPI_NOR=y +CONFIG_SPI_PHYTIUM_QUADSPI=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_NBD=m +CONFIG_BLK_DEV_RAM=y +CONFIG_VIRTIO_BLK=y +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_MULTIPATH=y +CONFIG_NVME_FC=y +CONFIG_NVME_TARGET=y +CONFIG_NVME_TARGET_LOOP=y +CONFIG_NVME_TARGET_FC=y +CONFIG_NVME_TARGET_FCLOOP=y +CONFIG_SRAM=y +CONFIG_EEPROM_AT25=m +# CONFIG_SCSI_PROC_FS is not set +CONFIG_BLK_DEV_SD=y +CONFIG_SCSI_SAS_LIBSAS=y +CONFIG_SCSI_SAS_ATA=y +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PLATFORM=m +CONFIG_ATA=y +CONFIG_SATA_AHCI=y +CONFIG_SATA_AHCI_PLATFORM=y +CONFIG_AHCI_CEVA=y +CONFIG_AHCI_QORIQ=y +CONFIG_SATA_SIL24=y +CONFIG_PATA_PLATFORM=y +CONFIG_PATA_OF_PLATFORM=y +CONFIG_NETDEVICES=y +CONFIG_MACVLAN=m +CONFIG_MACVTAP=m +CONFIG_TUN=y +CONFIG_VETH=m +CONFIG_VIRTIO_NET=y +CONFIG_AMD_XGBE=y +CONFIG_ATL1C=m +CONFIG_MACB=y +CONFIG_THUNDER_NIC_PF=y +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +CONFIG_E1000=m +CONFIG_E1000E=m +CONFIG_IGB=m +CONFIG_IGBVF=m +CONFIG_IXGB=m +CONFIG_IXGBE=m +CONFIG_IXGBEVF=m +CONFIG_I40E=m +CONFIG_I40EVF=m +CONFIG_MVMDIO=y +CONFIG_SKY2=y +# CONFIG_NET_VENDOR_NVIDIA is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +CONFIG_STMMAC_ETH=m +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_NET_VENDOR_TEHUTI is not set +# CONFIG_NET_VENDOR_TI is not set +CONFIG_MDIO_BITBANG=y +CONFIG_MDIO_BUS_MUX_MMIOREG=y +CONFIG_AT803X_PHY=m +CONFIG_MARVELL_PHY=m +CONFIG_MARVELL_10G_PHY=m +CONFIG_MICREL_PHY=y +CONFIG_MOTORCOMM_PHY=y +CONFIG_REALTEK_PHY=m +CONFIG_USB_PEGASUS=m +CONFIG_USB_RTL8150=m +CONFIG_USB_RTL8152=m +CONFIG_USB_LAN78XX=m +CONFIG_USB_USBNET=m +CONFIG_USB_NET_DM9601=m +CONFIG_USB_NET_SR9800=m +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=m +CONFIG_USB_NET_PLUSB=m +CONFIG_USB_NET_MCS7830=m +CONFIG_ATH10K=m +CONFIG_ATH10K_PCI=m +CONFIG_BRCMFMAC=m +CONFIG_MWIFIEX=m +CONFIG_MWIFIEX_PCIE=m +CONFIG_WL18XX=m +CONFIG_WLCORE_SDIO=m +CONFIG_INPUT_EVDEV=y +CONFIG_KEYBOARD_ADC=m +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_PHYTIUM=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ATMEL_MXT=m +CONFIG_INPUT_MISC=y +# CONFIG_SERIO_SERPORT is not set +CONFIG_SERIO_AMBAKMI=y +CONFIG_LEGACY_PTY_COUNT=16 +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_PHYTIUM_PCI=y +CONFIG_SERIAL_DEV_BUS=y +CONFIG_VIRTIO_CONSOLE=y +CONFIG_PHYTIUM_KCS_IPMI_BMC=y +CONFIG_PHYTIUM_BT_IPMI_BMC=y +CONFIG_HW_RANDOM=y +# CONFIG_HW_RANDOM_CAVIUM is not set +CONFIG_HW_RANDOM_PHYTIUM=y +CONFIG_TCG_TPM=y +CONFIG_TCG_TIS_I2C_INFINEON=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_MUX=y +CONFIG_I2C_DESIGNWARE_PLATFORM=y +CONFIG_I2C_PHYTIUM_PCI=y +CONFIG_I2C_PHYTIUM_PLATFORM=y +CONFIG_I2C_SLAVE_EEPROM=y +CONFIG_SPI=y +CONFIG_SPI_PHYTIUM_PLAT=y +CONFIG_SPI_PHYTIUM_PCI=y +CONFIG_SPI_SPIDEV=m +CONFIG_SPMI=y +CONFIG_PINCTRL=y +CONFIG_PINCTRL_SINGLE=y +CONFIG_GPIOLIB=y +CONFIG_GPIO_SYSFS=y +CONFIG_W1=m +CONFIG_W1_MASTER_PHYTIUM=m +CONFIG_W1_SLAVE_THERM=m +CONFIG_POWER_AVS=y +CONFIG_POWER_RESET_BRCMSTB=y +CONFIG_POWER_RESET_VEXPRESS=y +CONFIG_POWER_RESET_XGENE=y +CONFIG_POWER_RESET_SYSCON=y +CONFIG_SYSCON_REBOOT_MODE=y +CONFIG_BATTERY_SBS=m +CONFIG_BATTERY_BQ27XXX=y +CONFIG_SENSORS_ARM_SCMI=y +CONFIG_SENSORS_ARM_SCPI=y +CONFIG_SENSORS_PHYTIUM=y +CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y +CONFIG_CPU_THERMAL=y +CONFIG_THERMAL_EMULATION=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_SYSFS=y +CONFIG_ARM_SBSA_WATCHDOG=y +CONFIG_MFD_PHYTIUM_I2S_LSD=y +CONFIG_MFD_PHYTIUM_I2S_MMD=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_FAN53555=y +CONFIG_REGULATOR_GPIO=y +CONFIG_REGULATOR_PWM=y +CONFIG_REGULATOR_VCTRL=m +CONFIG_RC_CORE=m +CONFIG_RC_DECODERS=y +CONFIG_RC_DEVICES=y +CONFIG_MEDIA_SUPPORT=m +CONFIG_MEDIA_CAMERA_SUPPORT=y +CONFIG_MEDIA_ANALOG_TV_SUPPORT=y +CONFIG_MEDIA_DIGITAL_TV_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +# CONFIG_DVB_NET is not set +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_VIDEO_CLASS=m +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_VIDEO_PHYTIUM_JPEG=m +CONFIG_V4L_MEM2MEM_DRIVERS=y +CONFIG_DRM=y +CONFIG_DRM_RCAR_LVDS=m +CONFIG_DRM_PANEL_SIMPLE=y +CONFIG_DRM_I2C_ADV7511=m +CONFIG_DRM_PHYTIUM=y +CONFIG_FB_ARMCLCD=y +CONFIG_BACKLIGHT_GENERIC=m +CONFIG_BACKLIGHT_PWM=m +CONFIG_BACKLIGHT_LP855X=m +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_HDA_PHYTIUM=m +CONFIG_SND_HDA_CODEC_REALTEK=m +CONFIG_SND_SOC=y +CONFIG_SND_SOC_PHYTIUM_I2S=m +CONFIG_SND_PMDK_ES8388=m +CONFIG_SND_PMDK_ES8336=m +CONFIG_SND_PMDK_DP=m +CONFIG_SND_SOC_ES8336=y +CONFIG_SND_SOC_ES8388=y +CONFIG_SND_SIMPLE_CARD=m +CONFIG_SND_AUDIO_GRAPH_CARD=m +CONFIG_I2C_HID=m +CONFIG_USB=y +CONFIG_USB_OTG=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_OHCI_HCD_PLATFORM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_MUSB_HDRC=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC2=y +CONFIG_USB_CHIPIDEA=y +CONFIG_USB_CHIPIDEA_UDC=y +CONFIG_USB_CHIPIDEA_HOST=y +CONFIG_USB_ISP1760=y +CONFIG_USB_PHYTIUM=y +CONFIG_USB_HSIC_USB3503=y +CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_ULPI=y +CONFIG_USB_GADGET=y +CONFIG_USB_SNP_UDC_PLAT=y +CONFIG_USB_BDC_UDC=y +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_OBEX=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_ECM_SUBSET=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_EEM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_UAC1=y +CONFIG_USB_CONFIGFS_F_UAC1_LEGACY=y +CONFIG_USB_CONFIGFS_F_UAC2=y +CONFIG_USB_CONFIGFS_F_MIDI=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_UVC=y +CONFIG_USB_CONFIGFS_F_PRINTER=y +CONFIG_USB_ROLE_SWITCH=m +CONFIG_MMC=y +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_ARMMMCI=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_ACPI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SPI=y +CONFIG_MMC_CQHCI=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_PWM=y +CONFIG_LEDS_SYSCON=y +CONFIG_LEDS_TRIGGER_DISK=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_PANIC=y +CONFIG_EDAC=y +CONFIG_EDAC_GHES=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_DS1307=y +CONFIG_RTC_DRV_SD3068=m +CONFIG_RTC_DRV_DS3232=y +CONFIG_RTC_DRV_EFI=y +CONFIG_RTC_DRV_PL031=y +CONFIG_DMADEVICES=y +CONFIG_MV_XOR_V2=y +CONFIG_PL330_DMA=y +CONFIG_UIO=m +CONFIG_UIO_CIF=m +CONFIG_UIO_PDRV_GENIRQ=m +CONFIG_UIO_DMEM_GENIRQ=m +CONFIG_UIO_AEC=m +CONFIG_UIO_SERCOS3=m +CONFIG_UIO_PCI_GENERIC=m +CONFIG_UIO_NETX=m +CONFIG_UIO_PRUSS=m +CONFIG_UIO_MF624=m +CONFIG_VFIO=y +CONFIG_VFIO_PCI=y +CONFIG_VIRTIO_PCI=y +CONFIG_VIRTIO_BALLOON=y +CONFIG_VIRTIO_MMIO=y +CONFIG_XEN_GNTDEV=y +CONFIG_XEN_GRANT_DEV_ALLOC=y +CONFIG_STAGING=y +CONFIG_COMMON_CLK_VERSATILE=y +CONFIG_CLK_SP810=y +CONFIG_CLK_VEXPRESS_OSC=y +CONFIG_COMMON_CLK_SCPI=y +CONFIG_COMMON_CLK_CS2000_CP=y +CONFIG_CLK_QORIQ=y +CONFIG_COMMON_CLK_PWM=y +CONFIG_HWSPINLOCK=y +CONFIG_HWSPINLOCK_PHYTIUM=y +CONFIG_ARM_TIMER_SP804=y +CONFIG_ARM_MHU=y +CONFIG_PHYTIUM_MBOX=y +CONFIG_PLATFORM_MHU=y +CONFIG_ARM_SMMU=y +CONFIG_ARM_SMMU_V3=y +CONFIG_REMOTEPROC=y +CONFIG_RPMSG_CHAR=y +CONFIG_RPMSG_VIRTIO=y +CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y +CONFIG_EXTCON_USB_GPIO=y +CONFIG_MEMORY=y +CONFIG_IIO=y +CONFIG_PHYTIUM_ADC=y +CONFIG_PWM=y +CONFIG_PWM_PHYTIUM=y +CONFIG_GENERIC_PHY=y +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT2_FS_POSIX_ACL=y +CONFIG_EXT2_FS_SECURITY=y +CONFIG_EXT3_FS=y +CONFIG_EXT3_FS_POSIX_ACL=y +CONFIG_EXT3_FS_SECURITY=y +CONFIG_EXT4_ENCRYPTION=y +CONFIG_EXT4_DEBUG=y +CONFIG_XFS_FS=m +CONFIG_XFS_QUOTA=y +CONFIG_XFS_POSIX_ACL=y +CONFIG_XFS_RT=y +CONFIG_XFS_ONLINE_SCRUB=y +CONFIG_XFS_ONLINE_REPAIR=y +CONFIG_XFS_DEBUG=y +CONFIG_FANOTIFY=y +CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y +CONFIG_QUOTA=y +CONFIG_AUTOFS4_FS=y +CONFIG_FUSE_FS=m +CONFIG_CUSE=m +CONFIG_OVERLAY_FS=m +CONFIG_OVERLAY_FS_INDEX=y +CONFIG_OVERLAY_FS_XINO_AUTO=y +CONFIG_OVERLAY_FS_METACOPY=y +CONFIG_ISO9660_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_HUGETLBFS=y +CONFIG_EFIVAR_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_LZ4=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_ZSTD=y +CONFIG_NFS_FS=y +CONFIG_NFS_V4=y +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFSD=y +CONFIG_NFSD_V3_ACL=y +CONFIG_NFSD_V4=y +CONFIG_NFSD_BLOCKLAYOUT=y +CONFIG_NFSD_SCSILAYOUT=y +CONFIG_NFSD_FLEXFILELAYOUT=y +CONFIG_NFSD_V4_SECURITY_LABEL=y +CONFIG_NFSD_FAULT_INJECTION=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y +CONFIG_SECURITY=y +CONFIG_CRYPTO_USER=y +CONFIG_CRYPTO_AUTHENC=m +CONFIG_CRYPTO_ECHAINIV=y +CONFIG_CRYPTO_ANSI_CPRNG=y +CONFIG_CRYPTO_DRBG_HASH=y +CONFIG_CRYPTO_USER_API_HASH=y +CONFIG_CRYPTO_USER_API_SKCIPHER=y +CONFIG_CRYPTO_USER_API_RNG=y +CONFIG_CRYPTO_USER_API_AEAD=y +CONFIG_INDIRECT_PIO=y +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_FS=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_DEBUG_KERNEL=y +# CONFIG_SCHED_DEBUG is not set +# CONFIG_DEBUG_PREEMPT is not set +# CONFIG_FTRACE is not set +CONFIG_MEMTEST=y diff --git a/arch/arm64/configs/phytium_optee.config b/arch/arm64/configs/phytium_optee.config new file mode 100644 index 0000000000000000000000000000000000000000..07554cf843d34ef484dd90155765a91519476980 --- /dev/null +++ b/arch/arm64/configs/phytium_optee.config @@ -0,0 +1,2 @@ +CONFIG_TEE=y +CONFIG_OPTEE=y diff --git a/arch/arm64/include/asm/cpucaps.h b/arch/arm64/include/asm/cpucaps.h index 64ae14371cae90582156e938f2d0d3404f6d9c79..dc862e00198658e0a7410605b93409da7a8328dd 100644 --- a/arch/arm64/include/asm/cpucaps.h +++ b/arch/arm64/include/asm/cpucaps.h @@ -55,7 +55,8 @@ #define ARM64_SSBS 34 #define ARM64_WORKAROUND_1542419 35 #define ARM64_SPECTRE_BHB 36 +#define ARM64_HAS_CRC32 37 -#define ARM64_NCAPS 37 +#define ARM64_NCAPS 38 #endif /* __ASM_CPUCAPS_H */ diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 03b0fdccaf0529ae883ddc0730b6880d4f52ad2f..05fb061c5700428dd6de570634b4a6ff5a3bcc0e 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -1352,6 +1352,15 @@ static const struct arm64_cpu_capabilities arm64_features[] = { .cpu_enable = cpu_enable_ssbs, }, #endif + { + .desc = "CRC32 instructions", + .capability = ARM64_HAS_CRC32, + .type = ARM64_CPUCAP_SYSTEM_FEATURE, + .matches = has_cpuid_feature, + .sys_reg = SYS_ID_AA64ISAR0_EL1, + .field_pos = ID_AA64ISAR0_CRC32_SHIFT, + .min_field_value = 1, + }, {}, }; diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index b3354ff94e7984641dd5a0c076d63f67db5f62e9..c41ccf4055e305cb1df7fc1ee72900f484223324 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -278,6 +278,7 @@ static int __init reserve_memblock_reserved_regions(void) arch_initcall(reserve_memblock_reserved_regions); u64 __cpu_logical_map[NR_CPUS] = { [0 ... NR_CPUS-1] = INVALID_HWID }; +EXPORT_SYMBOL(__cpu_logical_map); void __init setup_arch(char **cmdline_p) { diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index 716810a08f24250f602e679c3fc78bf5346fdf21..d7d0d03f9262d9802110433c48186c7394dc92e7 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -59,6 +59,7 @@ #include #include #include +#include #define CREATE_TRACE_POINTS #include @@ -82,7 +83,8 @@ enum ipi_msg_type { IPI_CPU_CRASH_STOP, IPI_TIMER, IPI_IRQ_WORK, - IPI_WAKEUP + IPI_WAKEUP, + IPI_RPROC = 9, }; #ifdef CONFIG_HOTPLUG_CPU @@ -914,6 +916,11 @@ void handle_IPI(int ipinr, struct pt_regs *regs) break; #endif + case IPI_RPROC: + irq_enter(); + rproc_handle_ipi(ipinr); + irq_exit(); + break; default: pr_crit("CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr); break; diff --git a/arch/arm64/lib/Makefile b/arch/arm64/lib/Makefile index 5df2d611b77d9c2befc0c92bd540d358a75daa3d..69ff9887f724d930a09457dc9cc08936c94bf452 100644 --- a/arch/arm64/lib/Makefile +++ b/arch/arm64/lib/Makefile @@ -25,3 +25,5 @@ KCOV_INSTRUMENT_atomic_ll_sc.o := n UBSAN_SANITIZE_atomic_ll_sc.o := n lib-$(CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE) += uaccess_flushcache.o + +obj-$(CONFIG_CRC32) += crc32.o diff --git a/arch/arm64/lib/crc32.S b/arch/arm64/lib/crc32.S new file mode 100644 index 0000000000000000000000000000000000000000..5bc1e85b4e1c283a93a2490f9fded58d210c797c --- /dev/null +++ b/arch/arm64/lib/crc32.S @@ -0,0 +1,60 @@ +/* + * Accelerated CRC32(C) using AArch64 CRC instructions + * + * Copyright (C) 2016 - 2018 Linaro Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + + .cpu generic+crc + + .macro __crc32, c +0: subs x2, x2, #16 + b.mi 8f + ldp x3, x4, [x1], #16 +CPU_BE( rev x3, x3 ) +CPU_BE( rev x4, x4 ) + crc32\c\()x w0, w0, x3 + crc32\c\()x w0, w0, x4 + b.ne 0b + ret + +8: tbz x2, #3, 4f + ldr x3, [x1], #8 +CPU_BE( rev x3, x3 ) + crc32\c\()x w0, w0, x3 +4: tbz x2, #2, 2f + ldr w3, [x1], #4 +CPU_BE( rev w3, w3 ) + crc32\c\()w w0, w0, w3 +2: tbz x2, #1, 1f + ldrh w3, [x1], #2 +CPU_BE( rev16 w3, w3 ) + crc32\c\()h w0, w0, w3 +1: tbz x2, #0, 0f + ldrb w3, [x1] + crc32\c\()b w0, w0, w3 +0: ret + .endm + + .align 5 +ENTRY(crc32_le) +alternative_if_not ARM64_HAS_CRC32 + b crc32_le_base +alternative_else_nop_endif + __crc32 +ENDPROC(crc32_le) + + .align 5 +ENTRY(__crc32c_le) +alternative_if_not ARM64_HAS_CRC32 + b __crc32c_le_base +alternative_else_nop_endif + __crc32 c +ENDPROC(__crc32c_le) diff --git a/arch/arm64/mm/flush.c b/arch/arm64/mm/flush.c index 5c9073bace83a656e0c2b6cb64d3a5bc62b1462a..7e856c9fb3ed784c3d069ec6df180141c355112d 100644 --- a/arch/arm64/mm/flush.c +++ b/arch/arm64/mm/flush.c @@ -88,6 +88,8 @@ EXPORT_SYMBOL(flush_dcache_page); * Additional functions defined in assembly. */ EXPORT_SYMBOL(__flush_icache_range); +EXPORT_SYMBOL(__flush_dcache_area); +EXPORT_SYMBOL(__inval_dcache_area); #ifdef CONFIG_ARCH_HAS_PMEM_API void arch_wb_cache_pmem(void *addr, size_t size) diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index dd1eea90f67f1c9c998fd00941b4cb6c9b2e7fef..fb91fe3d7a52d32570ca00f5045686016e1fd60d 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -338,7 +338,7 @@ config ACPI_CUSTOM_DSDT_FILE See Documentation/acpi/dsdt-override.txt Enter the full path name to the file which includes the AmlCode - declaration. + or dsdt_aml_code declaration. If unsure, don't enter a file name. diff --git a/drivers/acpi/acpi_apd.c b/drivers/acpi/acpi_apd.c index 2664452fa112630d254106099bc8e3026a2ecb7b..b0ad5151f55ab6565bcaa6be81d5d1dfabed153a 100644 --- a/drivers/acpi/acpi_apd.c +++ b/drivers/acpi/acpi_apd.c @@ -162,6 +162,17 @@ static const struct apd_device_desc hip08_i2c_desc = { .setup = acpi_apd_setup, .fixed_clk_rate = 250000000, }; + +static const struct apd_device_desc phytium_i2c_desc = { + .setup = acpi_apd_setup, + .fixed_clk_rate = 200000000, +}; + +static const struct apd_device_desc phytium_pe220x_i2c_desc = { + .setup = acpi_apd_setup, + .fixed_clk_rate = 50000000, +}; + static const struct apd_device_desc thunderx2_i2c_desc = { .setup = acpi_apd_setup, .fixed_clk_rate = 125000000, @@ -234,6 +245,8 @@ static const struct acpi_device_id acpi_apd_device_ids[] = { { "CAV9007", APD_ADDR(thunderx2_i2c_desc) }, { "HISI02A1", APD_ADDR(hip07_i2c_desc) }, { "HISI02A2", APD_ADDR(hip08_i2c_desc) }, + { "PHYT0003", APD_ADDR(phytium_i2c_desc) }, + { "PHYT0038", APD_ADDR(phytium_pe220x_i2c_desc) }, #endif { } }; diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 913613cf5c53f19a3ee2e8c1ffbf8190a9fe2d60..d0368bd63734eeb3ea2678593c1bc40b8c1f0160 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -91,6 +91,18 @@ bool acpi_scan_is_offline(struct acpi_device *adev, bool uevent); acpi_status acpi_sysfs_table_handler(u32 event, void *table, void *context); void acpi_scan_table_handler(u32 event, void *table, void *context); +#ifdef CONFIG_ACPI_GENERIC_GSI +int acpi_register_irq(struct device *dev, u32 hwirq, int trigger, + int polarity, struct fwnode_handle *fwnode); +#else +static inline +int acpi_register_irq(struct device *dev, u32 hwirq, int trigger, + int polarity, struct fwnode_handle *fwnode) +{ + return acpi_register_gsi(dev, hwirq, trigger, polarity); +} +#endif + /* -------------------------------------------------------------------------- Device Node Initialization / Removal -------------------------------------------------------------------------- */ diff --git a/drivers/acpi/irq.c b/drivers/acpi/irq.c index 7c352cba052893bb59f135b597f797a345974cd4..fc9b52ea4ad531bbe64e588b458fbf468259c585 100644 --- a/drivers/acpi/irq.c +++ b/drivers/acpi/irq.c @@ -13,6 +13,8 @@ #include #include +#include "internal.h" + enum acpi_irq_model_id acpi_irq_model; static struct fwnode_handle *acpi_gsi_domain_id; @@ -41,6 +43,24 @@ int acpi_gsi_to_irq(u32 gsi, unsigned int *irq) } EXPORT_SYMBOL_GPL(acpi_gsi_to_irq); +int acpi_register_irq(struct device *dev, u32 hwirq, int trigger, + int polarity, struct fwnode_handle *fwnode) +{ + struct irq_fwspec fwspec; + + if (!fwnode) { + dev_warn(dev, "No registered irqchip for hwirq %d\n", hwirq); + return -EINVAL; + } + + fwspec.fwnode = fwnode; + fwspec.param[0] = hwirq; + fwspec.param[1] = acpi_dev_get_irq_type(trigger, polarity); + fwspec.param_count = 2; + + return irq_create_fwspec_mapping(&fwspec); +} + /** * acpi_register_gsi() - Map a GSI to a linux IRQ number * @dev: device for which IRQ has to be mapped @@ -54,19 +74,7 @@ EXPORT_SYMBOL_GPL(acpi_gsi_to_irq); int acpi_register_gsi(struct device *dev, u32 gsi, int trigger, int polarity) { - struct irq_fwspec fwspec; - - if (WARN_ON(!acpi_gsi_domain_id)) { - pr_warn("GSI: No registered irqchip, giving up\n"); - return -EINVAL; - } - - fwspec.fwnode = acpi_gsi_domain_id; - fwspec.param[0] = gsi; - fwspec.param[1] = acpi_dev_get_irq_type(trigger, polarity); - fwspec.param_count = 2; - - return irq_create_fwspec_mapping(&fwspec); + return acpi_register_irq(dev, gsi, trigger, polarity, acpi_gsi_domain_id); } EXPORT_SYMBOL_GPL(acpi_register_gsi); @@ -95,7 +103,7 @@ EXPORT_SYMBOL_GPL(acpi_unregister_gsi); * Return: * The referenced device fwhandle or NULL on failure */ -static struct fwnode_handle * +struct fwnode_handle * acpi_get_irq_source_fwhandle(const struct acpi_resource_source *source) { struct fwnode_handle *result; @@ -295,3 +303,29 @@ void __init acpi_set_irq_model(enum acpi_irq_model_id model, acpi_irq_model = model; acpi_gsi_domain_id = fwnode; } + +/** + * acpi_irq_create_hierarchy - Create a hierarchical IRQ domain with the default + * GSI domain as its parent. + * @flags: Irq domain flags associated with the domain + * @size: Size of the domain. + * @fwnode: Optional fwnode of the interrupt controller + * @ops: Pointer to the interrupt domain callbacks + * @host_data: Controller private data pointer + */ +struct irq_domain *acpi_irq_create_hierarchy(unsigned int flags, + unsigned int size, + struct fwnode_handle *fwnode, + const struct irq_domain_ops *ops, + void *host_data) +{ + struct irq_domain *d = irq_find_matching_fwnode(acpi_gsi_domain_id, + DOMAIN_BUS_ANY); + + if (!d) + return NULL; + + return irq_domain_create_hierarchy(d, flags, size, fwnode, ops, + host_data); +} +EXPORT_SYMBOL_GPL(acpi_irq_create_hierarchy); diff --git a/drivers/acpi/pci_irq.c b/drivers/acpi/pci_irq.c index 94ded9513c73b0bede043e85448251813b32806c..e926cddd9eba7eea10485a25a229cd4633818789 100644 --- a/drivers/acpi/pci_irq.c +++ b/drivers/acpi/pci_irq.c @@ -35,6 +35,8 @@ #include #include +#include "internal.h" + #define PREFIX "ACPI: " #define _COMPONENT ACPI_PCI_COMPONENT @@ -423,6 +425,7 @@ int acpi_pci_irq_enable(struct pci_dev *dev) char *link = NULL; char link_desc[16]; int rc; + struct fwnode_handle *rs_fwnode; pin = dev->pin; if (!pin) { @@ -451,7 +454,8 @@ int acpi_pci_irq_enable(struct pci_dev *dev) gsi = acpi_pci_link_allocate_irq(entry->link, entry->index, &triggering, &polarity, - &link); + &link, + &rs_fwnode); else gsi = entry->index; } else @@ -475,7 +479,7 @@ int acpi_pci_irq_enable(struct pci_dev *dev) return 0; } - rc = acpi_register_gsi(&dev->dev, gsi, triggering, polarity); + rc = acpi_register_irq(&dev->dev, gsi, triggering, polarity, rs_fwnode); if (rc < 0) { dev_warn(&dev->dev, "PCI INT %c: failed to register GSI\n", pin_name(pin)); diff --git a/drivers/acpi/pci_link.c b/drivers/acpi/pci_link.c index d5eec352a6e1293c13c70f421e4379e529793920..14010783eece54a8868ac085e407a06665575d3d 100644 --- a/drivers/acpi/pci_link.c +++ b/drivers/acpi/pci_link.c @@ -74,6 +74,7 @@ struct acpi_pci_link_irq { u8 resource_type; u8 possible_count; u32 possible[ACPI_PCI_LINK_MAX_POSSIBLE]; + struct acpi_resource_source resource_source; u8 initialized:1; u8 reserved:7; }; @@ -135,6 +136,8 @@ static acpi_status acpi_pci_link_check_possible(struct acpi_resource *resource, { struct acpi_resource_extended_irq *p = &resource->data.extended_irq; + struct acpi_resource_source *rs = + &link->irq.resource_source; if (!p || !p->interrupt_count) { printk(KERN_WARNING PREFIX "Blank _PRS EXT IRQ resource\n"); @@ -155,6 +158,12 @@ static acpi_status acpi_pci_link_check_possible(struct acpi_resource *resource, link->irq.triggering = p->triggering; link->irq.polarity = p->polarity; link->irq.resource_type = ACPI_RESOURCE_TYPE_EXTENDED_IRQ; + if (p->resource_source.string_length) { + rs->index = p->resource_source.index; + rs->string_length = p->resource_source.string_length; + rs->string_ptr = kstrdup(p->resource_source.string_ptr, + GFP_KERNEL); + } break; } default: @@ -341,7 +350,8 @@ static int acpi_pci_link_set(struct acpi_pci_link *link, int irq) resource->res.data.irq.sharable = ACPI_SHARED; resource->res.data.extended_irq.interrupt_count = 1; resource->res.data.extended_irq.interrupts[0] = irq; - /* ignore resource_source, it's optional */ + resource->res.data.extended_irq.resource_source = + link->irq.resource_source; break; default: printk(KERN_ERR PREFIX "Invalid Resource_type %d\n", link->irq.resource_type); @@ -627,7 +637,7 @@ static int acpi_pci_link_allocate(struct acpi_pci_link *link) * failure: return -1 */ int acpi_pci_link_allocate_irq(acpi_handle handle, int index, int *triggering, - int *polarity, char **name) + int *polarity, char **name, struct fwnode_handle **rs_fwnode) { int result; struct acpi_device *device; @@ -671,6 +681,9 @@ int acpi_pci_link_allocate_irq(acpi_handle handle, int index, int *triggering, *polarity = link->irq.polarity; if (name) *name = acpi_device_bid(link->device); + if (rs_fwnode) + *rs_fwnode = acpi_get_irq_source_fwhandle(&link->irq.resource_source); + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Link %s is referenced\n", acpi_device_bid(link->device))); diff --git a/drivers/acpi/tables.c b/drivers/acpi/tables.c index 041ee23819276e52ad1b169f9a8560f3b10e5baf..11caaa455f3e6bb6a746fdea37c25c5428994c3f 100644 --- a/drivers/acpi/tables.c +++ b/drivers/acpi/tables.c @@ -713,6 +713,11 @@ acpi_os_physical_table_override(struct acpi_table_header *existing_table, table_length); } +#ifdef CONFIG_ACPI_CUSTOM_DSDT +static void *amlcode __attribute__ ((weakref("AmlCode"))); +static void *dsdt_amlcode __attribute__ ((weakref("dsdt_aml_code"))); +#endif + acpi_status acpi_os_table_override(struct acpi_table_header *existing_table, struct acpi_table_header **new_table) @@ -723,8 +728,11 @@ acpi_os_table_override(struct acpi_table_header *existing_table, *new_table = NULL; #ifdef CONFIG_ACPI_CUSTOM_DSDT - if (strncmp(existing_table->signature, "DSDT", 4) == 0) - *new_table = (struct acpi_table_header *)AmlCode; + if (!strncmp(existing_table->signature, "DSDT", 4)) { + *new_table = (struct acpi_table_header *)&amlcode; + if (!(*new_table)) + *new_table = (struct acpi_table_header *)&dsdt_amlcode; + } #endif if (*new_table != NULL) acpi_table_taint(existing_table); diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index ca300b1914ce24170a4862ea422b526acfeafd4d..dbb675e2dd3f9d732dbb3965491550a861d9d179 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -32,3 +32,4 @@ obj-$(CONFIG_UNIPHIER_SYSTEM_BUS) += uniphier-system-bus.o obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o obj-$(CONFIG_DA8XX_MSTPRI) += da8xx-mstpri.o + diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index dac895dc01b956882486b90e0ea75813b93ea1c7..f11db8b7e51ad671e5837bc7a428c5c38994138e 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -424,6 +424,19 @@ config HW_RANDOM_EXYNOS will be called exynos-trng. If unsure, say Y. + +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 e35ec3ce3a20426afb7d464784242a7a22faebb0..d2797a27d6eba4a5282713c56506e8f902b9e573 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -38,3 +38,4 @@ obj-$(CONFIG_HW_RANDOM_CAVIUM) += cavium-rng.o cavium-rng-vf.o obj-$(CONFIG_HW_RANDOM_MTK) += mtk-rng.o obj-$(CONFIG_HW_RANDOM_S390) += s390-trng.o obj-$(CONFIG_HW_RANDOM_KEYSTONE) += ks-sa-rng.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..5b5e896516b934288ac9f69605069112f3a37fc4 --- /dev/null +++ b/drivers/char/hw_random/phytium-rng.c @@ -0,0 +1,155 @@ +// 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 > 32 ? 32 : 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 phytium_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; + priv->rng.quality = 1000; + + 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 = phytium_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 c108441882ccde34311729361afc7f2876e5f182..9f9910799499f112cbfa75e23eb8f3d9304321da 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig @@ -103,6 +103,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_NPCM7XX || COMPILE_TEST select IPMI_KCS_BMC diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile index 7a3baf301a8f7a15ff442fe84ba045dd209c0d84..e94565313102241a85730b6edc31358fb8afbf74 100644 --- a/drivers/char/ipmi/Makefile +++ b/drivers/char/ipmi/Makefile @@ -25,3 +25,5 @@ obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o obj-$(CONFIG_ASPEED_BT_IPMI_BMC) += bt-bmc.o obj-$(CONFIG_ASPEED_KCS_IPMI_BMC) += kcs_bmc_aspeed.o obj-$(CONFIG_NPCM7XX_KCS_IPMI_BMC) += kcs_bmc_npcm7xx.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..a4d009ddf4a22e73637ef5d2b0f302df2469391b --- /dev/null +++ b/drivers/char/ipmi/bt_bmc_phytium.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2021-2022 phytium + */ + +#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 "kcs_bmc.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 + +struct phytium_kcs_bmc { + struct regmap *map; +}; + +static u8 phytium_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg) +{ + struct phytium_kcs_bmc *priv = kcs_bmc_priv(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 *kcs_bmc, u32 reg, u8 data) +{ + struct phytium_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc); + int rc; + + rc = regmap_write(priv->map, reg, data); + WARN(rc != 0, "regmap_write() 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 void phytium_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr) +{ + struct phytium_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc); + + switch (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; + } +} + +static void phytium_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable) +{ + struct phytium_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc); + + switch (kcs_bmc->channel) { + case 1: + if (enable) { + regmap_update_bits(priv->map, LPC_HICR2, + LPC_HICR2_IBFIF1, LPC_HICR2_IBFIF1); + 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); + regmap_update_bits(priv->map, LPC_HICR2, + LPC_HICR2_IBFIF1, 0); + } + break; + case 2: + if (enable) { + regmap_update_bits(priv->map, LPC_HICR2, + LPC_HICR2_IBFIF2, LPC_HICR2_IBFIF2); + 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); + regmap_update_bits(priv->map, LPC_HICR2, + LPC_HICR2_IBFIF2, 0); + } + break; + case 3: + if (enable) { + regmap_update_bits(priv->map, LPC_HICR2, + LPC_HICR2_IBFIF3, LPC_HICR2_IBFIF3); + 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); + regmap_update_bits(priv->map, LPC_HICR2, + LPC_HICR2_IBFIF3, 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: + break; + } +} + +static irqreturn_t phytium_kcs_irq(int irq, void *arg) +{ + struct kcs_bmc *kcs_bmc = arg; + + if (!kcs_bmc_handle_event(kcs_bmc)) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static int phytium_kcs_config_irq(struct kcs_bmc *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 *kcs_bmc; + 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; + } + + kcs_bmc = kcs_bmc_alloc(dev, sizeof(*priv), chan); + if (!kcs_bmc) + return -ENOMEM; + + priv = kcs_bmc_priv(kcs_bmc); + priv->map = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(priv->map)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + kcs_bmc->ioreg = phytium_kcs_bmc_ioregs[chan - 1]; + kcs_bmc->io_inputb = phytium_kcs_inb; + kcs_bmc->io_outputb = phytium_kcs_outb; + + dev_set_drvdata(dev, kcs_bmc); + + phytium_kcs_set_address(kcs_bmc, addr); + phytium_kcs_enable_channel(kcs_bmc, true); + rc = phytium_kcs_config_irq(kcs_bmc, pdev); + if (rc) + return rc; + + rc = misc_register(&kcs_bmc->miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); + return rc; + } + + pr_info("channel=%u addr=0x%x idr=0x%x odr=0x%x str=0x%x\n", + chan, addr, kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr, kcs_bmc->ioreg.str); + + return 0; +} + +static int phytium_kcs_remove(struct platform_device *pdev) +{ + struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev); + + misc_deregister(&kcs_bmc->miscdev); + + 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 v2"); +MODULE_AUTHOR("Cheng Quan "); +MODULE_DESCRIPTION("Phytium device interface to the KCS BMC device"); diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index e5f31af65aabf3037202389d5f5e1834b888ccc4..510a48184f12b54151ea2804223240720d5dcef2 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -620,7 +620,6 @@ config ZX_DMA help Support the DMA engine for ZTE ZX family platform devices. - # driver files source "drivers/dma/bestcomm/Kconfig" diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 1c419e4cea8397521b3d8cdc0c946b58caf9ce66..4f019be9de0cd43df351162e65c3f6375cb8d346 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -40,6 +40,18 @@ config ARM_SCMI_PROTOCOL This protocol library provides interface for all the client drivers making use of the features offered by the SCMI. +if ARM_SCMI_PROTOCOL +config ARM_SCMI_TRANSPORT_FORCE_POLLING + bool "Support force polling mode for SCMI Mailbox" + help + Support force polling mode for SCMI Mailbox transports. + + If you want to configure SCMI Mailbox transport to use polling mode + on the TX path and do not use any completion IRQ facility even when + available through kernel parameter, answer Y. If unsure, say N. + +endif #ARM_SCMI_PROTOCOL + config ARM_SCMI_POWER_DOMAIN tristate "SCMI power domain driver" depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index e8cd66705ad7a76933804152e83ce740e7cbdde2..9bcb60d1855381742e652255604af9a612694a7d 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -84,6 +84,10 @@ struct scmi_desc { int max_msg_size; }; +#ifdef CONFIG_ARM_SCMI_TRANSPORT_FORCE_POLLING +static bool scmi_force_polling; +#endif + /** * struct scmi_chan_info - Structure representing a SCMI channel informfation * @@ -272,6 +276,15 @@ static void scmi_tx_prepare(struct mbox_client *cl, void *m) struct scmi_chan_info *cinfo = client_to_scmi_chan_info(cl); struct scmi_shared_mem __iomem *mem = cinfo->payload; +#ifdef CONFIG_ARCH_PHYTIUM + /* callee not set cahnnel free when init, caller set it */ + static int is_init = 0; + if(unlikely(is_init == 0)) { + iowrite32(0x1, &mem->channel_status); + is_init = 1; + } +#endif + /* * Ideally channel must be free by now unless OS timeout last * request and platform continued to process the same, wait @@ -380,6 +393,14 @@ static bool scmi_xfer_done_no_timeout(const struct scmi_chan_info *cinfo, return scmi_xfer_poll_done(cinfo, xfer) || ktime_after(__cur, stop); } +#ifdef CONFIG_ARM_SCMI_TRANSPORT_FORCE_POLLING +static int __init scmi_set_force_polling(char *str) +{ + return kstrtobool(str, &scmi_force_polling); +} +early_param("scmi.force_polling", scmi_set_force_polling); +#endif + /** * scmi_do_xfer() - Do one transfer * @@ -402,6 +423,11 @@ int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer) if (unlikely(!cinfo)) return -EINVAL; +#ifdef CONFIG_ARM_SCMI_TRANSPORT_FORCE_POLLING + if (scmi_force_polling) + xfer->hdr.poll_completion = true; +#endif + ret = mbox_send_message(cinfo->chan, xfer); if (ret < 0) { dev_dbg(dev, "mbox send fail %d\n", ret); diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 7098744f9276aa7b97fc65b1975098af31861058..85a93498a3a1143b1c33d9e08d10fcce9bcc6d18 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -52,7 +52,8 @@ struct efi __read_mostly efi = { .properties_table = EFI_INVALID_TABLE_ADDR, .mem_attr_table = EFI_INVALID_TABLE_ADDR, .rng_seed = EFI_INVALID_TABLE_ADDR, - .tpm_log = EFI_INVALID_TABLE_ADDR + .tpm_log = EFI_INVALID_TABLE_ADDR, + .mem_reserve = EFI_INVALID_TABLE_ADDR, }; EXPORT_SYMBOL(efi); @@ -489,6 +490,7 @@ static __initdata efi_config_table_type_t common_tables[] = { {EFI_MEMORY_ATTRIBUTES_TABLE_GUID, "MEMATTR", &efi.mem_attr_table}, {LINUX_EFI_RANDOM_SEED_TABLE_GUID, "RNG", &efi.rng_seed}, {LINUX_EFI_TPM_EVENT_LOG_GUID, "TPMEventLog", &efi.tpm_log}, + {LINUX_EFI_MEMRESERVE_TABLE_GUID, "MEMRESERVE", &efi.mem_reserve}, {NULL_GUID, NULL, NULL}, }; @@ -596,6 +598,41 @@ int __init efi_config_parse_tables(void *config_tables, int count, int sz, early_memunmap(tbl, sizeof(*tbl)); } + if (efi.mem_reserve != EFI_INVALID_TABLE_ADDR) { + unsigned long prsv = efi.mem_reserve; + + while (prsv) { + struct linux_efi_memreserve *rsv; + u8 *p; + int i; + + /* + * Just map a full page: that is what we will get + * anyway, and it permits us to map the entire entry + * before knowing its size. + */ + p = early_memremap(ALIGN_DOWN(prsv, PAGE_SIZE), + PAGE_SIZE); + if (p == NULL) { + pr_err("Could not map UEFI memreserve entry!\n"); + return -ENOMEM; + } + + rsv = (void *)(p + prsv % PAGE_SIZE); + + /* reserve the entry itself */ + memblock_reserve(prsv, EFI_MEMRESERVE_SIZE(rsv->size)); + + for (i = 0; i < atomic_read(&rsv->count); i++) { + memblock_reserve(rsv->entry[i].base, + rsv->entry[i].size); + } + + prsv = rsv->next; + early_memunmap(p, PAGE_SIZE); + } + } + return 0; } @@ -942,6 +979,109 @@ bool efi_is_table_address(unsigned long phys_addr) return false; } +static DEFINE_SPINLOCK(efi_mem_reserve_persistent_lock); +static struct linux_efi_memreserve *efi_memreserve_root __ro_after_init; + +static int __init efi_memreserve_map_root(void) +{ + if (efi.mem_reserve == EFI_INVALID_TABLE_ADDR) + return -ENODEV; + + efi_memreserve_root = memremap(efi.mem_reserve, + sizeof(*efi_memreserve_root), + MEMREMAP_WB); + if (WARN_ON_ONCE(!efi_memreserve_root)) + return -ENOMEM; + return 0; +} + +static int efi_mem_reserve_iomem(phys_addr_t addr, u64 size) +{ + struct resource *res, *parent; + + res = kzalloc(sizeof(struct resource), GFP_ATOMIC); + if (!res) + return -ENOMEM; + + res->name = "reserved"; + res->flags = IORESOURCE_MEM; + res->start = addr; + res->end = addr + size - 1; + + /* we expect a conflict with a 'System RAM' region */ + parent = request_resource_conflict(&iomem_resource, res); + return parent ? request_resource(parent, res) : 0; +} + +int __ref efi_mem_reserve_persistent(phys_addr_t addr, u64 size) +{ + struct linux_efi_memreserve *rsv; + unsigned long prsv; + int rc, index; + + if (efi_memreserve_root == (void *)ULONG_MAX) + return -ENODEV; + + if (!efi_memreserve_root) { + rc = efi_memreserve_map_root(); + if (rc) + return rc; + } + + /* first try to find a slot in an existing linked list entry */ + for (prsv = efi_memreserve_root->next; prsv; prsv = rsv->next) { + rsv = memremap(prsv, sizeof(*rsv), MEMREMAP_WB); + index = atomic_fetch_add_unless(&rsv->count, 1, rsv->size); + if (index < rsv->size) { + rsv->entry[index].base = addr; + rsv->entry[index].size = size; + + memunmap(rsv); + return efi_mem_reserve_iomem(addr, size); + } + memunmap(rsv); + } + + /* no slot found - allocate a new linked list entry */ + rsv = (struct linux_efi_memreserve *)__get_free_page(GFP_ATOMIC); + if (!rsv) + return -ENOMEM; + + rc = efi_mem_reserve_iomem(__pa(rsv), SZ_4K); + if (rc) { + free_page((unsigned long)rsv); + return rc; + } + + /* + * The memremap() call above assumes that a linux_efi_memreserve entry + * never crosses a page boundary, so let's ensure that this remains true + * even when kexec'ing a 4k pages kernel from a >4k pages kernel, by + * using SZ_4K explicitly in the size calculation below. + */ + rsv->size = EFI_MEMRESERVE_COUNT(SZ_4K); + atomic_set(&rsv->count, 1); + rsv->entry[0].base = addr; + rsv->entry[0].size = size; + + spin_lock(&efi_mem_reserve_persistent_lock); + rsv->next = efi_memreserve_root->next; + efi_memreserve_root->next = __pa(rsv); + spin_unlock(&efi_mem_reserve_persistent_lock); + + return efi_mem_reserve_iomem(addr, size); +} + +static int __init efi_memreserve_root_init(void) +{ + if (efi_memreserve_root) + return 0; + if (efi_memreserve_map_root()) + efi_memreserve_root = (void *)ULONG_MAX; + return 0; +} +early_initcall(efi_memreserve_root_init); + #ifdef CONFIG_KEXEC static int update_efi_random_seed(struct notifier_block *nb, unsigned long code, void *unused) diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c index 6c09644d620e052d01f640c8df84e8d16bb05440..296b3211f689dd46598d8335d1b6bcb85a56bf81 100644 --- a/drivers/firmware/efi/libstub/arm-stub.c +++ b/drivers/firmware/efi/libstub/arm-stub.c @@ -69,6 +69,31 @@ static struct screen_info *setup_graphics(efi_system_table_t *sys_table_arg) return si; } +void install_memreserve_table(efi_system_table_t *sys_table_arg) +{ + struct linux_efi_memreserve *rsv; + efi_guid_t memreserve_table_guid = LINUX_EFI_MEMRESERVE_TABLE_GUID; + efi_status_t status; + + status = efi_call_early(allocate_pool, EFI_LOADER_DATA, sizeof(*rsv), + (void **)&rsv); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, "Failed to allocate memreserve entry!\n"); + return; + } + + rsv->next = 0; + rsv->size = 0; + atomic_set(&rsv->count, 0); + + status = efi_call_early(install_configuration_table, + &memreserve_table_guid, + rsv); + if (status != EFI_SUCCESS) + pr_efi_err(sys_table_arg, "Failed to install memreserve config table!\n"); +} + + /* * This function handles the architcture specific differences between arm and * arm64 regarding where the kernel image must be loaded and any memory that @@ -235,6 +260,8 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table, } } + install_memreserve_table(sys_table); + new_fdt_addr = fdt_addr; status = allocate_new_fdt_and_exit_boot(sys_table, handle, &new_fdt_addr, efi_get_max_fdt_addr(dram_base), diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c index d855c20de663cfbd38f0af1cbb8217d9a37f4206..2d573acb53f8f94a9fb0e4a7f046263f8904e08c 100644 --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -63,6 +63,7 @@ struct psci_operations psci_ops = { .conduit = PSCI_CONDUIT_NONE, .smccc_version = SMCCC_VERSION_1_0, }; +EXPORT_SYMBOL(psci_ops); enum arm_smccc_conduit arm_smccc_1_1_get_conduit(void) { diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 2c34e9537f9e4b4f5f1a72c716f2292241d3652b..c32ae93eaa31e7d3b5b898e41428abc44c5420cb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -79,6 +79,10 @@ config GPIO_GENERIC # 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 @@ -404,6 +408,26 @@ 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 enable GPIO support for Phytium SoCs. + +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 bool "PrimeCell PL061 GPIO support" depends on ARM_AMBA @@ -1308,6 +1332,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 c256aff66a6567e22ff8e80cfc73fffe224ac1d1..ae1730e175a48436e64585aa363fadb156e47eee 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -95,6 +95,10 @@ obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o obj-$(CONFIG_GPIO_OMAP) += gpio-omap.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_PCA953X) += gpio-pca953x.o obj-$(CONFIG_GPIO_PCF857X) += gpio-pcf857x.o obj-$(CONFIG_GPIO_PCH) += gpio-pch.o diff --git a/drivers/gpio/gpio-phytium-core.c b/drivers/gpio/gpio-phytium-core.c new file mode 100644 index 0000000000000000000000000000000000000000..ea0bfb2961556676ae64d837efaa7632bfda6803 --- /dev/null +++ b/drivers/gpio/gpio-phytium-core.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019-2023 Phytium Technology Co., Ltd. + */ + +#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); +} +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; + + 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; + + 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_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_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); + +#if CONFIG_SMP +int +phytium_gpio_irq_set_affinity(struct irq_data *d, const struct cpumask *mask_val, bool force) +{ + struct gpio_chip *chip_data = irq_data_get_irq_chip_data(d); + struct irq_chip *chip = irq_get_chip(chip_data->irq.parent_irq); + struct irq_data *data = irq_get_irq_data(chip_data->irq.parent_irq); + + 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); +#endif + +MODULE_LICENSE("GPL v2"); +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..a308a8aedba34c705c05ff701f991b321eacd10b --- /dev/null +++ b/drivers/gpio/gpio-phytium-core.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019-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; + struct irq_chip irq_chip; + 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_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..838d4f33f8763d768e9b4f2ce2cff958628560d8 --- /dev/null +++ b/drivers/gpio/gpio-phytium-pci.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019-2023 Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "gpio-phytium-core.h" + +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; + + gpio->irq[0] = pdev->irq; + if (gpio->irq < 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 */ + gpio->irq_chip.name = dev_name(dev); + gpio->irq_chip.irq_ack = phytium_gpio_irq_ack; + gpio->irq_chip.irq_mask = phytium_gpio_irq_mask; + gpio->irq_chip.irq_unmask = phytium_gpio_irq_unmask; + gpio->irq_chip.irq_set_type = phytium_gpio_irq_set_type; + gpio->irq_chip.irq_enable = phytium_gpio_irq_enable; + gpio->irq_chip.irq_disable = phytium_gpio_irq_disable; + + 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; + + girq->chip = &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 v2"); +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..eebed6019185b7a5e817fd9ae0a93d4077ee5387 --- /dev/null +++ b/drivers/gpio/gpio-phytium-platform.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 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, "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 */ + gpio->irq_chip.name = dev_name(dev); + gpio->irq_chip.irq_ack = phytium_gpio_irq_ack; + gpio->irq_chip.irq_mask = phytium_gpio_irq_mask; + gpio->irq_chip.irq_unmask = phytium_gpio_irq_unmask; + gpio->irq_chip.irq_set_type = phytium_gpio_irq_set_type; + gpio->irq_chip.irq_enable = phytium_gpio_irq_enable; + gpio->irq_chip.irq_disable = phytium_gpio_irq_disable; +#ifdef CONFIG_SMP + gpio->irq_chip.irq_set_affinity = phytium_gpio_irq_set_affinity; +#endif + 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 < gpio->ngpio[0]; 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; + + girq->chip = &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 v2"); +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..e7d7bb1c83761c5ca4c31d0413ccedd4220f8124 --- /dev/null +++ b/drivers/gpio/gpio-phytium-sgpio.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SGPIO Support + * + * Copyright (c) 2021-2023 Phytium Technology Co., Ltd. + */ + +#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) == 2 ? 3 : (x))* 4) // 0x14, 0x18, 0x20 +#define SGPIO_RDATA0_REG 0x24 +#define SGPIO_RDATA_REG(x) (SGPIO_RDATA0_REG + (x) * 4) + +#define DEFAULT_L3_L0 0 + +#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. + */ + /* cached data from wdata? */ + reg = readl(gpio->regs + SGPIO_WDATA_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; + } + + 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 struct platform_driver phytium_sgpio_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = of_match_ptr(phytium_sgpio_of_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 a050a9aa9a5e5c3d44379b99e15644dc58e11506..b269688581903f1a637944f12c74cc4c51d36939 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -336,6 +336,8 @@ source "drivers/gpu/drm/tve200/Kconfig" source "drivers/gpu/drm/xen/Kconfig" +source "drivers/gpu/drm/phytium/Kconfig" + # Keep legacy drivers last menuconfig DRM_LEGACY diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index a6771cef85e25d74b73f1a2ff29bd34110605488..003ad888722984908b95c658e215a0ab322f2b56 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -106,4 +106,5 @@ obj-$(CONFIG_DRM_MXSFB) += mxsfb/ obj-$(CONFIG_DRM_TINYDRM) += tinydrm/ obj-$(CONFIG_DRM_PL111) += pl111/ obj-$(CONFIG_DRM_TVE200) += tve200/ +obj-$(CONFIG_DRM_PHYTIUM) += phytium/ obj-$(CONFIG_DRM_XEN) += xen/ diff --git a/drivers/gpu/drm/phytium/Kconfig b/drivers/gpu/drm/phytium/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..e3024feb69d0e02d3f6c3f2a5257d21f367aeaaa --- /dev/null +++ b/drivers/gpu/drm/phytium/Kconfig @@ -0,0 +1,7 @@ +config DRM_PHYTIUM + tristate "DRM Support for Phytium Graphics Card" + depends on DRM + select DRM_KMS_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..2dc7ea1118cd4cbf141215a7f377e258ae2593ba --- /dev/null +++ b/drivers/gpu/drm/phytium/Makefile @@ -0,0 +1,18 @@ +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..7fa323d65c853a69b9621e8669372e0d4976312c --- /dev/null +++ b/drivers/gpu/drm/phytium/pe220x_dc.c @@ -0,0 +1,250 @@ +// 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 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 = NULL; + *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..744ab74e4cc461f384f03053fe86e3ec895514c4 --- /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 0x3f + +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..19f38fc01106cc3ae33f42f9ee37407df13b3c4f --- /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..a79bf5b5e3ab8d70ccd1d2f683e916a96fee2ae7 --- /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..7ec9620f5cbff628ee985faf34af682737e3a7b5 --- /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..21ffb6587dac99cc2e51b5b5c3fe2a87cd98279b --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_crtc.c @@ -0,0 +1,735 @@ +// 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 uint32_t dc_scaling_get_factor(uint32_t src_size, uint32_t dst_size) +{ + uint32_t factor = 0; + + factor = ((src_size - 1) << SCALE_FACTOR_SRC_OFFSET) / (dst_size - 1); + + return factor; +} + +static float dc_sint(float x) +{ + const float B = 1.2732395477; + const float C = -0.4052847346; + const float P = 0.2310792853; + float y; + + if (x < 0) + y = B*x - C*x*x; + else + y = B*x + C*x*x; + if (y < 0) + y = P * (y * (0 - y) - y) + y; + else + y = P * (y * y - y) + y; + return y; +} + +static float dc_sinc_filter(float x, int radius) +{ + float pit, pitd, f1, f2, result; + float f_radius = MATH_I2Float(radius); + + if (x == 0.0f) { + result = 1.0f; + } else if ((x < -f_radius) || (x > f_radius)) { + result = 0.0f; + } else { + pit = MATH_Multiply(PHYPI, x); + pitd = MATH_Divide(pit, f_radius); + f1 = MATH_Divide(dc_sint(pit), pit); + f2 = MATH_Divide(dc_sint(pitd), pitd); + result = MATH_Multiply(f1, f2); + } + + return result; +} + +static int dc_calculate_sync_table( + uint8_t kernel_size, + uint32_t src_size, + uint32_t dst_size, + struct filter_blit_array *kernel_info) +{ + uint32_t scale_factor; + float f_scale; + int kernel_half; + float f_subpixel_step; + float f_subpixel_offset; + uint32_t subpixel_pos; + int kernel_pos; + int padding; + uint16_t *kernel_array; + int range = 0; + + do { + /* Compute the scale factor. */ + scale_factor = dc_scaling_get_factor(src_size, dst_size); + + /* Same kernel size and ratio as before? */ + if ((kernel_info->kernelSize == kernel_size) && + (kernel_info->scaleFactor == kernel_size)) { + break; + } + + /* check the array */ + if (kernel_info->kernelStates == NULL) + break; + + /* Store new parameters. */ + kernel_info->kernelSize = kernel_size; + kernel_info->scaleFactor = scale_factor; + + /* Compute the scale factor. */ + f_scale = MATH_DivideFromUInteger(dst_size, src_size); + + /* Adjust the factor for magnification. */ + if (f_scale > 1.0f) + f_scale = 1.0f; + + /* Calculate the kernel half. */ + kernel_half = (int) (kernel_info->kernelSize >> 1); + + /* Calculate the subpixel step. */ + f_subpixel_step = MATH_Divide(1.0f, MATH_I2Float(SUBPIXELCOUNT)); + + /* Init the subpixel offset. */ + f_subpixel_offset = 0.5f; + + /* Determine kernel padding size. */ + padding = (MAXKERNELSIZE - kernel_info->kernelSize) / 2; + + /* Set initial kernel array pointer. */ + kernel_array = (uint16_t *) (kernel_info->kernelStates + 1); + + /* Loop through each subpixel. */ + for (subpixel_pos = 0; subpixel_pos < SUBPIXELLOADCOUNT; subpixel_pos++) { + /* Define a temporary set of weights. */ + float fSubpixelSet[MAXKERNELSIZE]; + + /* Init the sum of all weights for the current subpixel. */ + float fWeightSum = 0.0f; + uint16_t weightSum = 0; + short int adjustCount, adjustFrom; + short int adjustment; + + /* Compute weights. */ + for (kernel_pos = 0; kernel_pos < MAXKERNELSIZE; kernel_pos++) { + /* Determine the current index. */ + int index = kernel_pos - padding; + + /* Pad with zeros. */ + if ((index < 0) || (index >= kernel_info->kernelSize)) { + fSubpixelSet[kernel_pos] = 0.0f; + } else { + if (kernel_info->kernelSize == 1) { + fSubpixelSet[kernel_pos] = 1.0f; + } else { + /* Compute the x position for filter function. */ + float fX = MATH_Add( + MATH_I2Float(index - kernel_half), + f_subpixel_offset); + fX = MATH_Multiply(fX, f_scale); + + /* Compute the weight. */ + fSubpixelSet[kernel_pos] = dc_sinc_filter(fX, + kernel_half); + } + + /* Update the sum of weights. */ + fWeightSum = MATH_Add(fWeightSum, + fSubpixelSet[kernel_pos]); + } + } + + /* Adjust weights so that the sum will be 1.0. */ + for (kernel_pos = 0; kernel_pos < MAXKERNELSIZE; kernel_pos++) { + /* Normalize the current weight. */ + float fWeight = MATH_Divide(fSubpixelSet[kernel_pos], + fWeightSum); + + /* Convert the weight to fixed point and store in the table. */ + if (fWeight == 0.0f) + kernel_array[kernel_pos] = 0x0000; + else if (fWeight >= 1.0f) + kernel_array[kernel_pos] = 0x4000; + else if (fWeight <= -1.0f) + kernel_array[kernel_pos] = 0xC000; + else + kernel_array[kernel_pos] = + (int16_t) MATH_Multiply(fWeight, 16384.0f); + weightSum += kernel_array[kernel_pos]; + } + + /* Adjust the fixed point coefficients. */ + adjustCount = 0x4000 - weightSum; + if (adjustCount < 0) { + adjustCount = -adjustCount; + adjustment = -1; + } else { + adjustment = 1; + } + + adjustFrom = (MAXKERNELSIZE - adjustCount) / 2; + for (kernel_pos = 0; kernel_pos < adjustCount; kernel_pos++) { + range = (MAXKERNELSIZE*subpixel_pos + adjustFrom + kernel_pos) * + sizeof(uint16_t); + if ((range >= 0) && (range < KERNELTABLESIZE)) + kernel_array[adjustFrom + kernel_pos] += adjustment; + else + DRM_ERROR("%s failed\n", __func__); + } + + kernel_array += MAXKERNELSIZE; + + /* Advance to the next subpixel. */ + f_subpixel_offset = MATH_Add(f_subpixel_offset, -f_subpixel_step); + } + } while (0); + + return 0; +} + +static void phytium_dc_scaling_config(struct drm_crtc *crtc, + struct drm_crtc_state *old_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 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 scale_factor_x, scale_factor_y, i; + uint32_t kernelStates[128]; + struct filter_blit_array kernel_info_width; + void *tmp = NULL; + + if (mode->hdisplay != mode->crtc_hdisplay || mode->vdisplay != mode->crtc_vdisplay) { + phytium_crtc->src_width = mode->hdisplay; + phytium_crtc->src_height = mode->vdisplay; + phytium_crtc->dst_width = mode->crtc_hdisplay; + phytium_crtc->dst_height = mode->crtc_vdisplay; + + phytium_crtc->dst_x = (mode->crtc_hdisplay - phytium_crtc->dst_width) / 2; + phytium_crtc->dst_y = (mode->crtc_vdisplay - phytium_crtc->dst_height) / 2; + + scale_factor_x = dc_scaling_get_factor(phytium_crtc->src_width, + phytium_crtc->dst_width); + scale_factor_y = dc_scaling_get_factor(phytium_crtc->src_height, + phytium_crtc->dst_height); + if (scale_factor_y > (SCALE_FACTOR_Y_MAX << SCALE_FACTOR_SRC_OFFSET)) + scale_factor_y = (SCALE_FACTOR_Y_MAX << SCALE_FACTOR_SRC_OFFSET); + + phytium_writel_reg(priv, scale_factor_x & SCALE_FACTOR_X_MASK, + group_offset, PHYTIUM_DC_FRAMEBUFFER_SCALE_FACTOR_X); + phytium_writel_reg(priv, scale_factor_y & SCALE_FACTOR_Y_MASK, + group_offset, PHYTIUM_DC_FRAMEBUFFER_SCALE_FACTOR_Y); + phytium_writel_reg(priv, FRAMEBUFFER_TAP, + group_offset, PHYTIUM_DC_FRAMEBUFFER_SCALECONFIG); + + tmp = kmalloc(KERNELSTATES, GFP_KERNEL); + if (!tmp) { + DRM_ERROR("malloc %ld failed\n", KERNELSTATES); + return; + } + + memset(&kernel_info_width, 0, sizeof(struct filter_blit_array)); + kernel_info_width.kernelStates = tmp; + memset(kernel_info_width.kernelStates, 0, KERNELSTATES); + kernel_neon_begin(); + dc_calculate_sync_table(FRAMEBUFFER_HORIZONTAL_FILTER_TAP, + phytium_crtc->src_width, + phytium_crtc->dst_width, + &kernel_info_width); + memset(kernelStates, 0, sizeof(kernelStates)); + memcpy(kernelStates, kernel_info_width.kernelStates + 1, KERNELSTATES - 4); + kernel_neon_end(); + phytium_writel_reg(priv, HORI_FILTER_INDEX, + group_offset, PHYTIUM_DC_FRAMEBUFFER_HORI_FILTER_INDEX); + for (i = 0; i < 128; i++) { + phytium_writel_reg(priv, kernelStates[i], + group_offset, PHYTIUM_DC_FRAMEBUFFER_HORI_FILTER); + } + + memset(&kernel_info_width, 0, sizeof(struct filter_blit_array)); + kernel_info_width.kernelStates = tmp; + memset(kernel_info_width.kernelStates, 0, KERNELSTATES); + kernel_neon_begin(); + dc_calculate_sync_table(FRAMEBUFFER_FILTER_TAP, phytium_crtc->src_height, + phytium_crtc->dst_height, &kernel_info_width); + memset(kernelStates, 0, sizeof(kernelStates)); + memcpy(kernelStates, kernel_info_width.kernelStates + 1, KERNELSTATES - 4); + kernel_neon_end(); + phytium_writel_reg(priv, VERT_FILTER_INDEX, + group_offset, PHYTIUM_DC_FRAMEBUFFER_VERT_FILTER_INDEX); + for (i = 0; i < 128; i++) + phytium_writel_reg(priv, kernelStates[i], + group_offset, PHYTIUM_DC_FRAMEBUFFER_VERT_FILTER); + phytium_writel_reg(priv, INITIALOFFSET, + group_offset, PHYTIUM_DC_FRAMEBUFFER_INITIALOFFSET); + kfree(tmp); + phytium_crtc->scale_enable = true; + } else { + phytium_crtc->scale_enable = false; + } +} + +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 const struct drm_crtc_funcs phytium_crtc_funcs = { + .gamma_set = drm_atomic_helper_legacy_gamma_set, + .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, +}; + +static void +phytium_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_crtc_state *old_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_atomic_state *state = old_state->state; + 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); + + phytium_dc_scaling_config(crtc, old_state); + 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); + + config |= FRAMEBUFFER_GAMMA_ENABLE; + + 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_crtc_state *old_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_crtc_state *crtc_state) +{ + struct drm_plane_state *new_plane_state = NULL; + int ret = 0; + struct drm_atomic_state *state = crtc_state->state; + 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_crtc_state *old_crtc_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_crtc_state *old_crtc_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..86f894ba5d7068277f11630ae1922f2be1a6d6a4 --- /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..dc0d42f001361e7a4d6bf180bef2b33612dcfdeb --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_debugfs.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (c) 2021-2023 Phytium Technology Co., Ltd. + */ + +#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..aa8e2922ec257ec465d02657c0c81d40405f2177 --- /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..22e94fa874891fcce7a52dd6ceaa6c691d550f7b --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_display_drv.c @@ -0,0 +1,477 @@ +// 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 +#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 int phytium_enable_vblank(struct drm_device *dev, unsigned int virt_pipe) +{ + struct phytium_display_private *priv = dev->dev_private; + int phys_pipe; + + phys_pipe = phytium_get_phys_pipe(priv, virt_pipe); + if (phys_pipe < 0) + return 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_device *dev, unsigned int virt_pipe) +{ + struct phytium_display_private *priv = dev->dev_private; + int phys_pipe; + + phys_pipe = phytium_get_phys_pipe(priv, virt_pipe); + if (phys_pipe >= 0) + phytium_writel_reg(priv, INT_DISABLE, priv->dc_reg_base[phys_pipe], + PHYTIUM_DC_INT_ENABLE); +} + +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.allow_fb_modifiers = true; + + 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); + + ret = drm_irq_install(dev, priv->irq); + 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) +{ + phytium_drm_fbdev_fini(dev); + drm_irq_uninstall(dev); + drm_mode_config_cleanup(dev); +} + +static const struct vm_operations_struct phytium_vm_ops = { + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +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_IRQ_SHARED | + DRIVER_PRIME | + DRIVER_MODESET | + DRIVER_ATOMIC | + DRIVER_GEM, + .load = phytium_display_load, + .unload = phytium_display_unload, + .lastclose = drm_fb_helper_lastclose, + .irq_handler = phytium_display_irq_handler, + .irq_preinstall = phytium_irq_preinstall, + .irq_uninstall = phytium_irq_uninstall, + .enable_vblank = phytium_enable_vblank, + .disable_vblank = phytium_disable_vblank, + .gem_free_object = phytium_gem_free_object, + .gem_vm_ops = &phytium_vm_ops, + .gem_prime_get_sg_table = phytium_gem_prime_get_sg_table, + .gem_prime_vmap = phytium_gem_prime_vmap, + .gem_prime_vunmap = phytium_gem_prime_vunmap, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_import_sg_table = phytium_gem_prime_import_sg_table, + .gem_prime_mmap = phytium_gem_prime_mmap, + .dumb_create = phytium_gem_dumb_create, + .dumb_destroy = phytium_gem_dumb_destroy, + .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..df24138ed57819b28db9ccfd8f29748ed27e7067 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_display_drv.h @@ -0,0 +1,175 @@ +/* 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 +#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..fd31f9a908915894251c0a84bf1e1cd8d5d66d6f --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_dp.c @@ -0,0 +1,2590 @@ +// 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 "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_panel_poweroff(struct phytium_dp_device *phytium_dp); + +static int phytium_rate[] = {162000, 270000, 540000, 810000}; + +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; + strncpy(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; + + 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; + } + + kfree(edid); + + return ret; +} + +static int +phytium_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); + 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 ((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 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, + .mode_valid = phytium_connector_mode_valid, + .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->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->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_TEST_PHY_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_panel_poweroff(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); + } +} + +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, +}; + +static const struct drm_encoder_funcs phytium_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +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_digital_mute(struct device *dev, void *data, bool enable) +{ + 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, + .digital_mute = phytium_dp_audio_digital_mute, + .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, int port) +{ + 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, + port, + &codec_data, sizeof(codec_data)); + + return PTR_ERR_OR_ZERO(phytium_dp->audio_pdev); +} + +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) + 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; +} + +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, port); + 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..3747ba6237aadbc93ff7ed5826b669eaed8401b5 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_dp.h @@ -0,0 +1,153 @@ +/* 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 + +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]; + 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 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..bae872465065a4b0d3c65b457990387aa5adf601 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_fb.c @@ -0,0 +1,130 @@ +// 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_unreference_unlocked(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]; + + 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_unreference_unlocked(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_unreference_unlocked(&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..c5c9a860789fc75fe7878f437c0e093c942ba3a4 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_fb.h @@ -0,0 +1,24 @@ +/* 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__ + +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..22933029733f734b021d9e5e8039247a4c9a8e03 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_fbdev.c @@ -0,0 +1,156 @@ +// 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 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 struct fb_ops phytium_fbdev_ops = { + .owner = THIS_MODULE, + DRM_FB_HELPER_DEFAULT_OPS, + .fb_mmap = phytium_fbdev_mmap, + .fb_fillrect = drm_fb_helper_cfb_fillrect, + .fb_copyarea = drm_fb_helper_cfb_copyarea, + .fb_imageblit = drm_fb_helper_cfb_imageblit, +}; + +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_fbi(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->flags = FBINFO_FLAG_DEFAULT; + fbi->fbops = &phytium_fbdev_ops; + + fb = helper->fb; + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->format->depth); + drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); + + offset = fbi->var.xoffset * bytes_per_pixel; + offset += fbi->var.yoffset * fb->pitches[0]; + dev->mode_config.fb_base = 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, &phytium_drm_fb_helper_funcs); + + ret = drm_fb_helper_init(dev, helper, PHYTIUM_MAX_CONNECTOR); + if (ret < 0) { + DRM_DEV_ERROR(dev->dev, "Failed to initialize drm fb helper -ret %d\n", ret); + return ret; + } + + ret = drm_fb_helper_single_add_all_connectors(helper); + if (ret < 0) { + DRM_DEV_ERROR(dev->dev, "Failed to add connectors - %d/\n", ret); + goto err_drm_fb_helper_fini; + } + ret = drm_fb_helper_initial_config(helper, 32); + return 0; + +err_drm_fb_helper_fini: + drm_fb_helper_fini(helper); + return ret; +} + +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_fbi(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..81070502c8e07a210f50bf4f7b0a967afd388633 --- /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..a386e28d8820460308c8741faa8607ba0183e490 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_gem.c @@ -0,0 +1,507 @@ +// 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_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); +} + +void *phytium_gem_prime_vmap(struct drm_gem_object *obj) +{ + struct phytium_gem_object *phytium_obj = to_phytium_gem_obj(obj); + + return phytium_obj->vaddr; +} + +void phytium_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr) +{ +} + +int phytium_gem_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma) +{ + int ret = 0; + + ret = drm_gem_mmap_obj(obj, obj->size, vma); + if (ret < 0) + return ret; + + return phytium_gem_mmap_obj(obj, vma); +} + +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. + */ + vma->vm_flags &= ~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); +} + +int phytium_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, uint32_t handle) +{ + return drm_gem_dumb_destroy(file, dev, handle); +} + + +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->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_unreference_unlocked(&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_unreference_unlocked(&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..cd34f4a4539f97ef698b3a293863be6df23740fb --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_gem.h @@ -0,0 +1,45 @@ +/* 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); +void *phytium_gem_prime_vmap(struct drm_gem_object *obj); +void phytium_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr); +int phytium_gem_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma); +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..16783b24a4d374bdfaac23275ce1a36269544058 --- /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..91760e26dcf18636b5ddea494b178daf7e383d35 --- /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..bc2f8a8cbf9801f6fdc4a4af039dd35231f99799 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_pci.c @@ -0,0 +1,388 @@ +// 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); + } + dev->pdev = pdev; + pci_set_drvdata(pdev, dev); + pci_set_master(pdev); + ret = pci_enable_device(pdev); + if (ret) { + DRM_ERROR("pci enbale device fail\n"); + goto failed_enable_device; + } + + if (dc_msi_enable) { + ret = pci_enable_msi(pdev); + if (ret) + DRM_ERROR("pci enbale 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..ad116dfcb5e97921f06c2c5cf157f547106b4993 --- /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..5871275a867aa24cf41d465e0d98de4c16b710de --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_plane.c @@ -0,0 +1,640 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Phytium display drm driver + * + * Copyright (c) 2021-2023 Phytium Technology Co., Ltd. + */ + +#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_prepare_fb(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct dma_buf *dma_buf; + struct dma_fence *fence; + + if (!state->fb) + return 0; + dma_buf = to_phytium_framebuffer(state->fb)->phytium_gem_obj[0]->base.dma_buf; + if (dma_buf) { + fence = reservation_object_get_excl_rcu(dma_buf->resv); + drm_atomic_set_fence_for_plane(state, fence); + } + + return 0; +} + +static int +phytium_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) +{ + 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); + struct drm_framebuffer *fb = plane->state->fb; + int phys_pipe = phytium_plane->phys_pipe; + int config; + unsigned long iova; + + phytium_plane->enable = 1; + phytium_plane->cursor_hot_x = fb->hot_x; + phytium_plane->cursor_hot_y = fb->hot_y; + phytium_plane->cursor_x = plane->state->crtc_x + fb->hot_x; + phytium_plane->cursor_y = plane->state->crtc_y + fb->hot_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_plane_state *old_state) +{ + 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_plane_state *old_state) +{ + 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 = phytium_plane_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..2d46164861ce4135d3ba93dcfee35e9bc1be27b5 --- /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..99d7cb5dc3e0658820d9c85a708295d73b3d48a5 --- /dev/null +++ b/drivers/gpu/drm/phytium/phytium_platform.c @@ -0,0 +1,309 @@ +// 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, + }, + { } +}; + +MODULE_DEVICE_TABLE(of, display_of_match); + +#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..e752f79130db5e4c4a1d525c79992b12aecff580 --- /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..98351e75db493789e654c1fe8809dffadeeff1cd --- /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..4e737236d66833bfcd3faa83a4e7dfff46531c22 --- /dev/null +++ b/drivers/gpu/drm/phytium/px210_dc.c @@ -0,0 +1,321 @@ +// 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 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 = NULL; + *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..24d1adfae2a32f94f736ba87c692c7322191e004 --- /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 0x3f + +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..d7bd04eac2a84714ece2694d94c04c37e3e39667 --- /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..4ad65397f1aa23a40e5591981563dcfd7baae527 --- /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..5556b3ee5c1db894844687520371979380c20e70 --- /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 d150d0cab1b6f9e1daedd63d6a77c762b5db35ab..4c90cb1d016023e612461c88080e64d3a983f702 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -360,6 +360,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 93f7f41ea4ad514f88075ccd631358ae6b17ac93..2160f637ebe907864425517bfc55b28b5a88d1d7 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o +obj-$(CONFIG_SENSORS_PHYTIUM) += tacho-phytium.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o diff --git a/drivers/hwmon/tacho-phytium.c b/drivers/hwmon/tacho-phytium.c new file mode 100644 index 0000000000000000000000000000000000000000..cbfbe0b8250f548463c1ffa159621fb58f8b74d2 --- /dev/null +++ b/drivers/hwmon/tacho-phytium.c @@ -0,0 +1,391 @@ +// 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 fwnode_handle *nc = dev_fwnode(tacho->dev); + + if (fwnode_property_read_bool(nc, "tacho")) + return tacho_mode; + if (fwnode_property_read_bool(nc, "capture")) + return capture_mode; + return tacho_mode; +} + +static int phytium_tacho_get_edge_mode(struct phytium_tacho *tacho) +{ + struct fwnode_handle *nc = dev_fwnode(tacho->dev); + + if (fwnode_property_read_bool(nc, "up")) + return rising_edge; + if (fwnode_property_read_bool(nc, "down")) + return falling_edge; + if (fwnode_property_read_bool(nc, "double")) + return double_edge; + return rising_edge; +} + +static int phytium_tacho_get_debounce(struct phytium_tacho *tacho) +{ + u32 value; + struct fwnode_handle *nc = dev_fwnode(tacho->dev); + + if (!fwnode_property_read_u32(nc, "debounce-level", &value)) + return value; + else + 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 (dev->of_node) { + 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 if (has_acpi_companion(dev)){ + if(fwnode_property_read_u32(dev_fwnode(dev),"clock-frequency", (u32 *)&(tacho->freq) ) <0) + tacho->freq = 50000000; + } + + 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); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_tacho_acpi_ids[] = { + { "PHYT0033", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(acpi, phytium_tacho_acpi_ids); +#endif + +static const struct of_device_id tacho_of_match[] = { + { .compatible = "phytium,tacho", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tacho_of_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(phytium_tacho_acpi_ids), + }, +}; + +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 e895d29500eec03d10f5067c55a14602cc60de02..ffc6ceacb39c975f5a3e008e0bc2eaeddd170394 100644 --- a/drivers/hwspinlock/Kconfig +++ b/drivers/hwspinlock/Kconfig @@ -16,6 +16,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 HWSPINLOCK diff --git a/drivers/hwspinlock/Makefile b/drivers/hwspinlock/Makefile index b87c01a506a494c81a627062dff25ed1eae4ae4e..f6dcd79360d7223632f88f46e554639645320313 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_SIRF) += sirf_hwspinlock.o obj-$(CONFIG_HWSPINLOCK_SPRD) += sprd_hwspinlock.o diff --git a/drivers/hwspinlock/phytium_hwspinlock.c b/drivers/hwspinlock/phytium_hwspinlock.c new file mode 100644 index 0000000000000000000000000000000000000000..ec057388726a8f43a3b92920486012403f08186a --- /dev/null +++ b/drivers/hwspinlock/phytium_hwspinlock.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium hardware spinlock driver + * + * Copyright (c) 2021-2023 Phytium Technology Co., Ltd. + */ + +#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; +} +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_hwspinlock_acpi_ids[] = { + { "PHYT0027", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(acpi, phytium_hwspinlock_acpi_ids); +#endif + + +static const struct of_device_id phytium_hwspinlock_of_match[] = { + { .compatible = "phytium,hwspinlock", }, + { /* end */ }, +}; +MODULE_DEVICE_TABLE(of, phytium_hwspinlock_of_match); + +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_ids), + }, +}; + +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 v2"); +MODULE_DESCRIPTION("Hardware spinlock driver for Phytium"); +MODULE_AUTHOR("Chen Baozi "); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 165c112bc5b9a97df58e8e65f644ce8081c65254..3c315d72449149caec60e92941d72bc0d47f758e 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -531,6 +531,34 @@ config I2C_DESIGNWARE_BAYTRAIL the platform firmware controlling it. You should say Y if running on a BayTrail system using the AXP288. +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_DIGICOLOR tristate "Conexant Digicolor I2C driver" depends on ARCH_DIGICOLOR diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 18b26af82b1c5425a9dcec9c61cca3cdff694d60..0aea0dd31c9188dad8ec55f385d09e399e07c542 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -53,6 +53,10 @@ i2c-designware-platform-$(CONFIG_I2C_DESIGNWARE_BAYTRAIL) += i2c-designware-bayt obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o i2c-designware-pci-objs := i2c-designware-pcidrv.o obj-$(CONFIG_I2C_DIGICOLOR) += i2c-digicolor.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_EFM32) += i2c-efm32.o obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o obj-$(CONFIG_I2C_EMEV2) += i2c-emev2.o diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index 69ec4a791f23e78e1462c002eaca62c9e400da5a..fa3a1f7fd472684584ed0553904cb7cda331e148 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -108,9 +108,10 @@ int i2c_dw_set_reg_access(struct dw_i2c_dev *dev) /* Configure register access mode 16bit */ dev->flags |= ACCESS_16BIT; } else if (reg != DW_IC_COMP_TYPE_VALUE) { - dev_err(dev->dev, - "Unknown Synopsys component type: 0x%08x\n", reg); - return -ENODEV; + //dev_err(dev->dev, + //"Unknown Synopsys component type: 0x%08x\n", reg); + //return -ENODEV; + return 0; } return 0; diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 18cc324f3ca94541d830f825693ee42b6cf60df9..eeb38fe8a03c631ed798278f9f783390b9e0ae59 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -66,8 +66,6 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) 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 @@ -103,8 +101,6 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) 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); /* Check is high speed possible and fall back to fast mode if not */ if ((dev->master_cfg & DW_IC_CON_SPEED_MASK) == diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index b5750fd851251e74b0558576774da4a82d81c757..8c61595ed5277def57bb718fde9fbe711586d673 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -151,6 +151,7 @@ static const struct acpi_device_id dw_i2c_acpi_match[] = { { "APMC0D0F", 0 }, { "HISI02A1", 0 }, { "HISI02A2", 0 }, + { "PHYT0003", 0 }, { } }; MODULE_DEVICE_TABLE(acpi, dw_i2c_acpi_match); @@ -203,8 +204,8 @@ static void dw_i2c_set_fifo_size(struct dw_i2c_dev *dev, int id) * the depth could be from 2 to 256 from HW spec. */ param = i2c_dw_read_comp_param(dev); - tx_fifo_depth = ((param >> 16) & 0xff) + 1; - rx_fifo_depth = ((param >> 8) & 0xff) + 1; + tx_fifo_depth = 8;//((param >> 16) & 0xff) + 1; + rx_fifo_depth = 8;//((param >> 8) & 0xff) + 1; if (!dev->tx_fifo_depth) { dev->tx_fifo_depth = tx_fifo_depth; dev->rx_fifo_depth = rx_fifo_depth; @@ -289,7 +290,7 @@ static int dw_i2c_plat_probe(struct platform_device *pdev) 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; + t->bus_freq_hz = 100000; if (has_acpi_companion(&pdev->dev)) dw_i2c_acpi_configure(pdev); @@ -322,7 +323,6 @@ static int dw_i2c_plat_probe(struct platform_device *pdev) dev->get_clk_rate_khz = i2c_dw_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); diff --git a/drivers/i2c/busses/i2c-phytium-common.c b/drivers/i2c/busses/i2c-phytium-common.c new file mode 100644 index 0000000000000000000000000000000000000000..2af8689d861e62c3dcaaac39bd975a0bbda23672 --- /dev/null +++ b/drivers/i2c/busses/i2c-phytium-common.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Phytium I2C adapter driver. + * + * Based on the TI DAVINCI I2C adapter driver. + * + * 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 acknowledgement 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 interupts */ + 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..7cf42787389ee2ed2337ae7421086d44c2ed2175 --- /dev/null +++ b/drivers/i2c/busses/i2c-phytium-core.h @@ -0,0 +1,254 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium I2C adapter driver. + * + * 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..b525b8e153b86d140e5247d42a5ff4aa17ba1a37 --- /dev/null +++ b/drivers/i2c/busses/i2c-phytium-master.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium I2C adapter driver. + * + * 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; + } else { + 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; + } else + 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..0e0a72468bb55b121d5070d2991a8dfe23d4160d --- /dev/null +++ b/drivers/i2c/busses/i2c-phytium-pci.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PCI driver for Phytium I2C adapter. + * + * 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); + + if (pdev->device == 0xdc32) { + /* + * Since we have already register the adapter, the dev->irq + * must be valid. + */ + i2c->alert_data.irq = i2c->irq; + + i2c->ara = i2c_setup_smbus_alert(&i2c->adapter, &i2c->alert_data); + if (!i2c->ara) + return -ENODEV; + } + + 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..66709bb91d69823ce19bb0cfaeed73ca72674c33 --- /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. + * + * 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; + acpi_handle handle = ACPI_HANDLE(&pdev->dev); + const struct acpi_device_id *id; + struct acpi_device *adev; + + 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; + + if (acpi_bus_get_device(handle, &adev)) + return -ENODEV; + + 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_LEAVE_SUSPENDED); + + /* 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..a9409f55c6522e15b1127cf793e89dd5d97b2c3f --- /dev/null +++ b/drivers/i2c/busses/i2c-phytium-slave.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium I2C adapter driver (slave only). + * + * 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), + "Phytium 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/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 1dabd366ec0bcc23050c30f3bb8d25f0031d4506..86579c9e56bf06a3c337a3f68a72fb4665f4834f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -943,4 +943,16 @@ config XILINX_XADC The driver can also be build as a module. If so, the module will be called xilinx-xadc. +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. + endmenu diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 03db7b578f9c077c4a5790f79f8ba48beb8d56dd..7d58314fb0cfbff7309d5030d51b88d081011146 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -86,3 +86,4 @@ obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o xilinx-xadc-y := xilinx-xadc-core.o xilinx-xadc-events.o obj-$(CONFIG_XILINX_XADC) += xilinx-xadc.o obj-$(CONFIG_SD_ADC_MODULATOR) += sd_adc_modulator.o +obj-$(CONFIG_PHYTIUM_ADC) += phytium-adc.o diff --git a/drivers/iio/adc/phytium-adc.c b/drivers/iio/adc/phytium-adc.c new file mode 100755 index 0000000000000000000000000000000000000000..e8dfa4546326f4da11f6bcc2426d0343ba97d48a --- /dev/null +++ b/drivers/iio/adc/phytium-adc.c @@ -0,0 +1,689 @@ +// 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 phytium_adc *adc = data; + struct iio_dev *indio_dev = iio_priv_to_dev(adc); + 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); + + iio_triggered_buffer_postenable(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, + .predisable = &iio_triggered_buffer_predisable, + .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; + platform_set_drvdata(pdev, indio_dev); + + 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; + + ret = devm_request_threaded_irq(adc->dev, platform_get_irq(pdev, 0), + NULL, phytium_adc_threaded_irq, IRQF_ONESHOT, + dev_name(dev), adc); + 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 devm_iio_device_register(dev, 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 v2"); diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 4713957b0cbba11e942a01c2b2138854d648637f..d134c5fd5767f52bbc16d9367a2c9288244fcf04 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -756,4 +756,15 @@ config KEYBOARD_MTK_PMIC To compile this driver as a module, choose M here: the module will be called pmic-keys. +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 182e92985dbf69f18c783db7b04523fbc5ae56a3..e410f6e9c97c2416f783bd27e9ab7775ea776c02 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -67,3 +67,4 @@ obj-$(CONFIG_KEYBOARD_TM2_TOUCHKEY) += tm2-touchkey.o obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o obj-$(CONFIG_KEYBOARD_W90P910) += w90p910_keypad.o +obj-$(CONFIG_KEYBOARD_PHYTIUM) += phytium-keypad.o diff --git a/drivers/input/keyboard/phytium-keypad.c b/drivers/input/keyboard/phytium-keypad.c new file mode 100644 index 0000000000000000000000000000000000000000..37750d78bc2d682bba3b4a0f9f0bc2cb5dea8cd2 --- /dev/null +++ b/drivers/input/keyboard/phytium-keypad.c @@ -0,0 +1,585 @@ +// 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 +/* + * 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); + + dev_dbg(&dev->dev, ">%s\n", __func__); + + /* 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); + + dev_dbg(&dev->dev, ">%s\n", __func__); + + /* 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_ACPI +static const struct acpi_device_id phytium_keypad_acpi_ids[] = { + { "PHYT0028", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(acpi, phytium_keypad_acpi_ids); +#endif + +#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 + +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_ids), + }, + .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 v2"); +MODULE_ALIAS("platform:phytium-keypad"); diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig index d90d9f1098ff8ccba4c35d032812f6a62019baa3..958312b2169c871476d8b6f28939f82169a4a5ff 100644 --- a/drivers/input/serio/Kconfig +++ b/drivers/input/serio/Kconfig @@ -39,6 +39,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 67950a5ccb3f18484b6457f895049412415dbf49..c180361bdd12aea7325d711c9beb93cea26cd154 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/phytium-ps2.c b/drivers/input/serio/phytium-ps2.c new file mode 100644 index 0000000000000000000000000000000000000000..173866f396664f13630ee29e71e6194cea2dc91f --- /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; + strlcpy(serio->name, pci_name(pdev), sizeof(serio->name)); + strlcpy(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-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c index 9f16f47e70219614ede7422d483d05a26fd65bd0..75f33dbb7fd65798a93ed6554a1323b08b0aff55 100644 --- a/drivers/iommu/arm-smmu-v3.c +++ b/drivers/iommu/arm-smmu-v3.c @@ -201,6 +201,13 @@ #define Q_BASE_ADDR_MASK GENMASK_ULL(51, 5) #define Q_BASE_LOG2SIZE GENMASK(4, 0) +/* Ensure DMA allocations are naturally aligned */ +#ifdef CONFIG_CMA_ALIGNMENT +#define Q_MAX_SZ_SHIFT (PAGE_SHIFT + CONFIG_CMA_ALIGNMENT) +#else +#define Q_MAX_SZ_SHIFT (PAGE_SHIFT + MAX_ORDER - 1) +#endif + /* * Stream table. * @@ -298,8 +305,9 @@ FIELD_GET(ARM64_TCR_##fld, tcr)) /* Command queue */ -#define CMDQ_ENT_DWORDS 2 -#define CMDQ_MAX_SZ_SHIFT 8 +#define CMDQ_ENT_SZ_SHIFT 4 +#define CMDQ_ENT_DWORDS ((1 << CMDQ_ENT_SZ_SHIFT) >> 3) +#define CMDQ_MAX_SZ_SHIFT (Q_MAX_SZ_SHIFT - CMDQ_ENT_SZ_SHIFT) #define CMDQ_CONS_ERR GENMASK(30, 24) #define CMDQ_ERR_CERROR_NONE_IDX 0 @@ -338,14 +346,16 @@ #define CMDQ_SYNC_1_MSIADDR_MASK GENMASK_ULL(51, 2) /* Event queue */ -#define EVTQ_ENT_DWORDS 4 -#define EVTQ_MAX_SZ_SHIFT 7 +#define EVTQ_ENT_SZ_SHIFT 5 +#define EVTQ_ENT_DWORDS ((1 << EVTQ_ENT_SZ_SHIFT) >> 3) +#define EVTQ_MAX_SZ_SHIFT (Q_MAX_SZ_SHIFT - EVTQ_ENT_SZ_SHIFT) #define EVTQ_0_ID GENMASK_ULL(7, 0) /* PRI queue */ -#define PRIQ_ENT_DWORDS 2 -#define PRIQ_MAX_SZ_SHIFT 8 +#define PRIQ_ENT_SZ_SHIFT 4 +#define PRIQ_ENT_DWORDS ((1 << PRIQ_ENT_SZ_SHIFT) >> 3) +#define PRIQ_MAX_SZ_SHIFT (Q_MAX_SZ_SHIFT - PRIQ_ENT_SZ_SHIFT) #define PRIQ_0_SID GENMASK_ULL(31, 0) #define PRIQ_0_SSID GENMASK_ULL(51, 32) @@ -568,6 +578,7 @@ struct arm_smmu_device { int gerr_irq; int combined_irq; u32 sync_nr; + u8 prev_cmd_opcode; unsigned long ias; /* IPA */ unsigned long oas; /* PA */ @@ -594,6 +605,7 @@ struct arm_smmu_device { /* IOMMU core code handle */ struct iommu_device iommu; + bool bypass; }; /* SMMU private data for each master */ @@ -789,7 +801,7 @@ static int queue_remove_raw(struct arm_smmu_queue *q, u64 *ent) /* High-level queue accessors */ static int arm_smmu_cmdq_build_cmd(u64 *cmd, struct arm_smmu_cmdq_ent *ent) { - memset(cmd, 0, CMDQ_ENT_DWORDS << 3); + memset(cmd, 0, 1 << CMDQ_ENT_SZ_SHIFT); cmd[0] |= FIELD_PREP(CMDQ_0_OP, ent->opcode); switch (ent->opcode) { @@ -918,6 +930,8 @@ static void arm_smmu_cmdq_insert_cmd(struct arm_smmu_device *smmu, u64 *cmd) struct arm_smmu_queue *q = &smmu->cmdq.q; bool wfe = !!(smmu->features & ARM_SMMU_FEAT_SEV); + smmu->prev_cmd_opcode = FIELD_GET(CMDQ_0_OP, cmd[0]); + while (queue_insert_raw(q, cmd) == -ENOSPC) { if (queue_poll_cons(q, false, wfe)) dev_err_ratelimited(smmu->dev, "CMDQ timeout\n"); @@ -970,9 +984,16 @@ static int __arm_smmu_cmdq_issue_sync_msi(struct arm_smmu_device *smmu) }; spin_lock_irqsave(&smmu->cmdq.lock, flags); - ent.sync.msidata = ++smmu->sync_nr; - arm_smmu_cmdq_build_cmd(cmd, &ent); - arm_smmu_cmdq_insert_cmd(smmu, cmd); + + /* Piggy-back on the previous command if it's a SYNC */ + if (smmu->prev_cmd_opcode == CMDQ_OP_CMD_SYNC) { + ent.sync.msidata = smmu->sync_nr; + } else { + ent.sync.msidata = ++smmu->sync_nr; + arm_smmu_cmdq_build_cmd(cmd, &ent); + arm_smmu_cmdq_insert_cmd(smmu, cmd); + } + spin_unlock_irqrestore(&smmu->cmdq.lock, flags); return __arm_smmu_sync_poll_msi(smmu, ent.sync.msidata); @@ -2036,17 +2057,32 @@ static int arm_smmu_init_one_queue(struct arm_smmu_device *smmu, struct arm_smmu_queue *q, unsigned long prod_off, unsigned long cons_off, - size_t dwords) + size_t dwords, const char *name) { - size_t qsz = ((1 << q->max_n_shift) * dwords) << 3; + size_t qsz; + + do { + qsz = ((1 << q->max_n_shift) * dwords) << 3; + q->base = dmam_alloc_coherent(smmu->dev, qsz, &q->base_dma, + GFP_KERNEL); + if (q->base || qsz < PAGE_SIZE) + break; + + q->max_n_shift--; + } while (1); - q->base = dmam_alloc_coherent(smmu->dev, qsz, &q->base_dma, GFP_KERNEL); if (!q->base) { - dev_err(smmu->dev, "failed to allocate queue (0x%zx bytes)\n", - qsz); + dev_err(smmu->dev, + "failed to allocate queue (0x%zx bytes) for %s\n", + qsz, name); return -ENOMEM; } + if (!WARN_ON(q->base_dma & (qsz - 1))) { + dev_info(smmu->dev, "allocated %u entries for %s\n", + 1 << q->max_n_shift, name); + } + q->prod_reg = arm_smmu_page1_fixup(prod_off, smmu); q->cons_reg = arm_smmu_page1_fixup(cons_off, smmu); q->ent_dwords = dwords; @@ -2066,13 +2102,15 @@ static int arm_smmu_init_queues(struct arm_smmu_device *smmu) /* cmdq */ spin_lock_init(&smmu->cmdq.lock); ret = arm_smmu_init_one_queue(smmu, &smmu->cmdq.q, ARM_SMMU_CMDQ_PROD, - ARM_SMMU_CMDQ_CONS, CMDQ_ENT_DWORDS); + ARM_SMMU_CMDQ_CONS, CMDQ_ENT_DWORDS, + "cmdq"); if (ret) return ret; /* evtq */ ret = arm_smmu_init_one_queue(smmu, &smmu->evtq.q, ARM_SMMU_EVTQ_PROD, - ARM_SMMU_EVTQ_CONS, EVTQ_ENT_DWORDS); + ARM_SMMU_EVTQ_CONS, EVTQ_ENT_DWORDS, + "evtq"); if (ret) return ret; @@ -2081,7 +2119,8 @@ static int arm_smmu_init_queues(struct arm_smmu_device *smmu) return 0; return arm_smmu_init_one_queue(smmu, &smmu->priq.q, ARM_SMMU_PRIQ_PROD, - ARM_SMMU_PRIQ_CONS, PRIQ_ENT_DWORDS); + ARM_SMMU_PRIQ_CONS, PRIQ_ENT_DWORDS, + "priq"); } static int arm_smmu_init_l1_strtab(struct arm_smmu_device *smmu) @@ -2253,6 +2292,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]); @@ -2308,11 +2354,51 @@ static void arm_smmu_setup_msis(struct arm_smmu_device *smmu) devm_add_action(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; + + for_each_msi_entry(desc, dev) { + switch (desc->platform.msi_index) { + case EVTQ_MSI_INDEX: + case GERROR_MSI_INDEX: + case PRIQ_MSI_INDEX: { + phys_addr_t *cfg = arm_smmu_msi_cfg[desc->platform.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: + continue; + + } + } +} +#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; @@ -2354,7 +2440,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; @@ -2381,7 +2467,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; @@ -2406,7 +2492,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; @@ -2504,7 +2590,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; @@ -2514,7 +2600,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); @@ -2635,7 +2721,7 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu) return -ENXIO; } - /* Queue sizes, capped at 4k */ + /* Queue sizes, capped to ensure natural alignment */ smmu->cmdq.q.max_n_shift = min_t(u32, CMDQ_MAX_SZ_SHIFT, FIELD_GET(IDR1_CMDQS, reg)); if (!smmu->cmdq.q.max_n_shift) { @@ -2796,6 +2882,26 @@ static unsigned long arm_smmu_resource_size(struct arm_smmu_device *smmu) return SZ_128K; } +#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; @@ -2803,7 +2909,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) { @@ -2821,7 +2926,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); @@ -2867,7 +2972,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev) platform_set_drvdata(pdev, smmu); /* Reset the device */ - ret = arm_smmu_device_reset(smmu, bypass); + ret = arm_smmu_device_reset(smmu, false); if (ret) return ret; @@ -2929,10 +3034,21 @@ static const struct of_device_id arm_smmu_of_match[] = { }; MODULE_DEVICE_TABLE(of, arm_smmu_of_match); +#ifdef CONFIG_PM_SLEEP +static const struct dev_pm_ops arm_smmu_pm_ops = { + SET_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 = of_match_ptr(arm_smmu_of_match), + .pm = ARM_SMMU_PM_OPS, }, .probe = arm_smmu_device_probe, .remove = arm_smmu_device_remove, diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 86334aef4bd056f4443b7a2c3e0d79638eb51f81..e5d3e4b12b22a53c14cd9c8b67325e22f8637baa 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -19,13 +19,16 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -51,6 +54,7 @@ #define ITS_FLAGS_WORKAROUND_CAVIUM_23144 (1ULL << 2) #define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING (1 << 0) +#define RDIST_FLAGS_RD_TABLES_PREALLOCATED (1 << 1) static u32 lpi_id_bits; @@ -166,6 +170,13 @@ static struct { int next_victim; } vpe_proxy; +struct cpu_lpi_count { + atomic_t managed; + atomic_t unmanaged; +}; + +static DEFINE_PER_CPU(struct cpu_lpi_count, cpu_lpi_count); + static LIST_HEAD(its_nodes); static DEFINE_RAW_SPINLOCK(its_lock); static struct rdists *gic_rdists; @@ -178,6 +189,7 @@ static DEFINE_RAW_SPINLOCK(vmovp_lock); static DEFINE_IDA(its_vpeid_ida); #define gic_data_rdist() (raw_cpu_ptr(gic_rdists->rdist)) +#define gic_data_rdist_cpu(cpu) (per_cpu_ptr(gic_rdists->rdist, cpu)) #define gic_data_rdist_rd_base() (gic_data_rdist()->rd_base) #define gic_data_rdist_vlpi_base() (gic_data_rdist_rd_base() + SZ_128K) @@ -1061,7 +1073,7 @@ static inline u32 its_get_event_id(struct irq_data *d) static void lpi_write_config(struct irq_data *d, u8 clr, u8 set) { irq_hw_number_t hwirq; - struct page *prop_page; + void *va; u8 *cfg; if (irqd_is_forwarded_to_vcpu(d)) { @@ -1069,7 +1081,7 @@ static void lpi_write_config(struct irq_data *d, u8 clr, u8 set) u32 event = its_get_event_id(d); struct its_vlpi_map *map; - prop_page = its_dev->event_map.vm->vprop_page; + va = page_address(its_dev->event_map.vm->vprop_page); map = &its_dev->event_map.vlpi_maps[event]; hwirq = map->vintid; @@ -1077,11 +1089,11 @@ static void lpi_write_config(struct irq_data *d, u8 clr, u8 set) map->properties &= ~clr; map->properties |= set | LPI_PROP_GROUP1; } else { - prop_page = gic_rdists->prop_page; + va = gic_rdists->prop_table_va; hwirq = d->hwirq; } - cfg = page_address(prop_page) + hwirq - 8192; + cfg = va + hwirq - 8192; *cfg &= ~clr; *cfg |= set | LPI_PROP_GROUP1; @@ -1143,42 +1155,159 @@ static void its_unmask_irq(struct irq_data *d) lpi_update_config(d, 0, LPI_PROP_ENABLED); } +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, cpu)->managed); + + 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, cpu)->managed); + else + 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, cpu)->managed); + else + atomic_dec(&per_cpu_ptr(&cpu_lpi_count, cpu)->unmanaged); +} + +static unsigned int cpumask_pick_least_loaded(struct irq_data *d, + const struct cpumask *cpu_mask) +{ + unsigned int cpu = nr_cpu_ids, tmp; + int count = S32_MAX; + + for_each_cpu(tmp, cpu_mask) { + int this_count = its_read_lpi_count(d, tmp); + if (this_count < count) { + cpu = tmp; + count = this_count; + } + } + + return cpu; +} + +/* + * As suggested by Thomas Gleixner in: + * https://lore.kernel.org/r/87h80q2aoc.fsf@nanos.tec.linutronix.de + */ +static int its_select_cpu(struct irq_data *d, + const struct cpumask *aff_mask) +{ + struct its_device *its_dev = irq_data_get_irq_chip_data(d); + cpumask_var_t tmpmask; + int cpu, node; + + if (!alloc_cpumask_var(&tmpmask, GFP_ATOMIC)) + return -ENOMEM; + + node = its_dev->its->numa_node; + + if (!irqd_affinity_is_managed(d)) { + /* First try the NUMA node */ + if (node != NUMA_NO_NODE) { + /* + * Try the intersection of the affinity mask and the + * node mask (and the online mask, just to be safe). + */ + cpumask_and(tmpmask, cpumask_of_node(node), aff_mask); + cpumask_and(tmpmask, tmpmask, cpu_online_mask); + + /* + * Ideally, we would check if the mask is empty, and + * try again on the full node here. + * + * But it turns out that the way ACPI describes the + * affinity for ITSs only deals about memory, and + * not target CPUs, so it cannot describe a single + * ITS placed next to two NUMA nodes. + * + * Instead, just fallback on the online mask. This + * diverges from Thomas' suggestion above. + */ + cpu = cpumask_pick_least_loaded(d, tmpmask); + if (cpu < nr_cpu_ids) + goto out; + + /* If we can't cross sockets, give up */ + if ((its_dev->its->flags & ITS_FLAGS_WORKAROUND_CAVIUM_23144)) + goto out; + + /* If the above failed, expand the search */ + } + + /* Try the intersection of the affinity and online masks */ + cpumask_and(tmpmask, aff_mask, cpu_online_mask); + + /* If that doesn't fly, the online mask is the last resort */ + if (cpumask_empty(tmpmask)) + cpumask_copy(tmpmask, cpu_online_mask); + + cpu = cpumask_pick_least_loaded(d, tmpmask); + } else { + cpumask_and(tmpmask, irq_data_get_affinity_mask(d), cpu_online_mask); + + /* If we cannot cross sockets, limit the search to that node */ + if ((its_dev->its->flags & ITS_FLAGS_WORKAROUND_CAVIUM_23144) && + node != NUMA_NO_NODE) + cpumask_and(tmpmask, tmpmask, cpumask_of_node(node)); + + cpu = cpumask_pick_least_loaded(d, tmpmask); + } +out: + free_cpumask_var(tmpmask); + + pr_debug("IRQ%d -> %*pbl CPU%d\n", d->irq, cpumask_pr_args(aff_mask), cpu); + return cpu; +} + static int its_set_affinity(struct irq_data *d, const struct cpumask *mask_val, bool force) { - unsigned int cpu; - const struct cpumask *cpu_mask = cpu_online_mask; struct its_device *its_dev = irq_data_get_irq_chip_data(d); struct its_collection *target_col; u32 id = its_get_event_id(d); + int cpu, prev_cpu; /* A forwarded interrupt should use irq_set_vcpu_affinity */ if (irqd_is_forwarded_to_vcpu(d)) return -EINVAL; - /* lpi cannot be routed to a redistributor that is on a foreign node */ - if (its_dev->its->flags & ITS_FLAGS_WORKAROUND_CAVIUM_23144) { - if (its_dev->its->numa_node >= 0) { - cpu_mask = cpumask_of_node(its_dev->its->numa_node); - if (!cpumask_intersects(mask_val, cpu_mask)) - return -EINVAL; - } - } + prev_cpu = its_dev->event_map.col_map[id]; + its_dec_lpi_count(d, prev_cpu); - cpu = cpumask_any_and(mask_val, cpu_mask); + if (!force) + cpu = its_select_cpu(d, mask_val); + else + cpu = cpumask_pick_least_loaded(d, mask_val); - if (cpu >= nr_cpu_ids) - return -EINVAL; + if (cpu < 0 || cpu >= nr_cpu_ids) + goto err; /* don't set the affinity when the target cpu is same as current one */ - if (cpu != its_dev->event_map.col_map[id]) { + if (cpu != prev_cpu) { target_col = &its_dev->its->collections[cpu]; its_send_movi(its_dev, target_col, id); its_dev->event_map.col_map[id] = cpu; irq_data_update_effective_affinity(d, cpumask_of(cpu)); } + its_inc_lpi_count(d, cpu); + return IRQ_SET_MASK_OK_DONE; + +err: + its_inc_lpi_count(d, prev_cpu); + return -EINVAL; } static u64 its_irq_get_msi_base(struct its_device *its_dev) @@ -1445,6 +1574,11 @@ static int its_irq_set_vcpu_affinity(struct irq_data *d, void *vcpu_info) } } +static int its_irq_retrigger(struct irq_data *d) +{ + return !its_irq_set_irqchip_state(d, IRQCHIP_STATE_PENDING, true); +} + static struct irq_chip its_irq_chip = { .name = "ITS", .irq_mask = its_mask_irq, @@ -1453,6 +1587,7 @@ static struct irq_chip its_irq_chip = { .irq_set_affinity = its_set_affinity, .irq_compose_msi_msg = its_irq_compose_msi_msg, .irq_set_irqchip_state = its_irq_set_irqchip_state, + .irq_retrigger = its_irq_retrigger, .irq_set_vcpu_affinity = its_irq_set_vcpu_affinity, }; @@ -1633,6 +1768,15 @@ static void its_lpi_free(unsigned long *bitmap, u32 base, u32 nr_ids) kfree(bitmap); } +static void gic_reset_prop_table(void *va) +{ + /* Priority 0xa0, Group-1, disabled */ + memset(va, LPI_PROP_DEFAULT_PRIO | LPI_PROP_GROUP1, LPI_PROPBASE_SZ); + + /* Make sure the GIC will observe the written configuration */ + gic_flush_dcache_to_poc(va, LPI_PROPBASE_SZ); +} + static struct page *its_allocate_prop_table(gfp_t gfp_flags) { struct page *prop_page; @@ -1641,13 +1785,7 @@ static struct page *its_allocate_prop_table(gfp_t gfp_flags) if (!prop_page) return NULL; - /* Priority 0xa0, Group-1, disabled */ - memset(page_address(prop_page), - LPI_PROP_DEFAULT_PRIO | LPI_PROP_GROUP1, - LPI_PROPBASE_SZ); - - /* Make sure the GIC will observe the written configuration */ - gic_flush_dcache_to_poc(page_address(prop_page), LPI_PROPBASE_SZ); + gic_reset_prop_table(page_address(prop_page)); return prop_page; } @@ -1658,20 +1796,74 @@ static void its_free_prop_table(struct page *prop_page) get_order(LPI_PROPBASE_SZ)); } -static int __init its_alloc_lpi_tables(void) +static bool gic_check_reserved_range(phys_addr_t addr, unsigned long size) { - phys_addr_t paddr; + phys_addr_t start, end, addr_end; + u64 i; - lpi_id_bits = min_t(u32, GICD_TYPER_ID_BITS(gic_rdists->gicd_typer), - ITS_MAX_LPI_NRBITS); - gic_rdists->prop_page = its_allocate_prop_table(GFP_NOWAIT); - if (!gic_rdists->prop_page) { - pr_err("Failed to allocate PROPBASE\n"); - return -ENOMEM; + /* + * We don't bother checking for a kdump kernel as by + * construction, the LPI tables are out of this kernel's + * memory map. + */ + if (is_kdump_kernel()) + return true; + + addr_end = addr + size - 1; + + for_each_reserved_mem_region(i, &start, &end) { + if (addr >= start && addr_end <= end) + return true; } - paddr = page_to_phys(gic_rdists->prop_page); - pr_info("GIC: using LPI property table @%pa\n", &paddr); + /* Not found, not a good sign... */ + pr_warn("GICv3: Expected reserved range [%pa:%pa], not found\n", + &addr, &addr_end); + add_taint(TAINT_CRAP, LOCKDEP_STILL_OK); + return false; +} + +static int gic_reserve_range(phys_addr_t addr, unsigned long size) +{ + if (efi_enabled(EFI_CONFIG_TABLES)) + return efi_mem_reserve_persistent(addr, size); + + return 0; +} + +static int __init its_setup_lpi_prop_table(void) +{ + if (gic_rdists->flags & RDIST_FLAGS_RD_TABLES_PREALLOCATED) { + u64 val; + + val = gicr_read_propbaser(gic_data_rdist_rd_base() + GICR_PROPBASER); + lpi_id_bits = (val & GICR_PROPBASER_IDBITS_MASK) + 1; + + gic_rdists->prop_table_pa = val & GENMASK_ULL(51, 12); + gic_rdists->prop_table_va = memremap(gic_rdists->prop_table_pa, + LPI_PROPBASE_SZ, + MEMREMAP_WB); + gic_reset_prop_table(gic_rdists->prop_table_va); + } else { + struct page *page; + + lpi_id_bits = min_t(u32, + GICD_TYPER_ID_BITS(gic_rdists->gicd_typer), + ITS_MAX_LPI_NRBITS); + page = its_allocate_prop_table(GFP_NOWAIT); + if (!page) { + pr_err("Failed to allocate PROPBASE\n"); + return -ENOMEM; + } + + gic_rdists->prop_table_pa = page_to_phys(page); + gic_rdists->prop_table_va = page_address(page); + WARN_ON(gic_reserve_range(gic_rdists->prop_table_pa, + LPI_PROPBASE_SZ)); + } + + pr_info("GICv3: using LPI property table @%pa\n", + &gic_rdists->prop_table_pa); return its_lpi_init(lpi_id_bits); } @@ -1962,12 +2154,9 @@ static int its_alloc_collections(struct its_node *its) static struct page *its_allocate_pending_table(gfp_t gfp_flags) { struct page *pend_page; - /* - * The pending pages have to be at least 64kB aligned, - * hence the 'max(LPI_PENDBASE_SZ, SZ_64K)' below. - */ + pend_page = alloc_pages(gfp_flags | __GFP_ZERO, - get_order(max_t(u32, LPI_PENDBASE_SZ, SZ_64K))); + get_order(LPI_PENDBASE_SZ)); if (!pend_page) return NULL; @@ -1979,8 +2168,63 @@ static struct page *its_allocate_pending_table(gfp_t gfp_flags) static void its_free_pending_table(struct page *pt) { - free_pages((unsigned long)page_address(pt), - get_order(max_t(u32, LPI_PENDBASE_SZ, SZ_64K))); + free_pages((unsigned long)page_address(pt), get_order(LPI_PENDBASE_SZ)); +} + +/* + * Booting with kdump and LPIs enabled is generally fine. Any other + * case is wrong in the absence of firmware/EFI support. + */ +static bool enabled_lpis_allowed(void) +{ + phys_addr_t addr; + u64 val; + + /* Check whether the property table is in a reserved region */ + val = gicr_read_propbaser(gic_data_rdist_rd_base() + GICR_PROPBASER); + addr = val & GENMASK_ULL(51, 12); + + return gic_check_reserved_range(addr, LPI_PROPBASE_SZ); +} + +static int __init allocate_lpi_tables(void) +{ + u64 val; + int err, cpu; + + /* + * If LPIs are enabled while we run this from the boot CPU, + * flag the RD tables as pre-allocated if the stars do align. + */ + val = readl_relaxed(gic_data_rdist_rd_base() + GICR_CTLR); + if ((val & GICR_CTLR_ENABLE_LPIS) && enabled_lpis_allowed()) { + gic_rdists->flags |= (RDIST_FLAGS_RD_TABLES_PREALLOCATED | + RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING); + pr_info("GICv3: Using preallocated redistributor tables\n"); + } + + err = its_setup_lpi_prop_table(); + if (err) + return err; + + /* + * We allocate all the pending tables anyway, as we may have a + * mix of RDs that have had LPIs enabled, and some that + * don't. We'll free the unused ones as each CPU comes online. + */ + for_each_possible_cpu(cpu) { + struct page *pend_page; + + pend_page = its_allocate_pending_table(GFP_NOWAIT); + if (!pend_page) { + pr_err("Failed to allocate PENDBASE for CPU%d\n", cpu); + return -ENOMEM; + } + + gic_data_rdist_cpu(cpu)->pend_page = pend_page; + } + + return 0; } static u64 its_clear_vpend_valid(void __iomem *vlpi_base) @@ -2010,28 +2254,40 @@ static void its_cpu_init_lpis(void) { void __iomem *rbase = gic_data_rdist_rd_base(); struct page *pend_page; + phys_addr_t paddr; u64 val, tmp; - /* If we didn't allocate the pending table yet, do it now */ - pend_page = gic_data_rdist()->pend_page; - if (!pend_page) { - phys_addr_t paddr; + if (gic_data_rdist()->lpi_enabled) + return; - pend_page = its_allocate_pending_table(GFP_NOWAIT); - if (!pend_page) { - pr_err("Failed to allocate PENDBASE for CPU%d\n", - smp_processor_id()); - return; - } + val = readl_relaxed(rbase + GICR_CTLR); + if ((gic_rdists->flags & RDIST_FLAGS_RD_TABLES_PREALLOCATED) && + (val & GICR_CTLR_ENABLE_LPIS)) { + /* + * Check that we get the same property table on all + * RDs. If we don't, this is hopeless. + */ + paddr = gicr_read_propbaser(rbase + GICR_PROPBASER); + paddr &= GENMASK_ULL(51, 12); + if (WARN_ON(gic_rdists->prop_table_pa != paddr)) + add_taint(TAINT_CRAP, LOCKDEP_STILL_OK); + + paddr = gicr_read_pendbaser(rbase + GICR_PENDBASER); + paddr &= GENMASK_ULL(51, 16); - paddr = page_to_phys(pend_page); - pr_info("CPU%d: using LPI pending table @%pa\n", - smp_processor_id(), &paddr); - gic_data_rdist()->pend_page = pend_page; + WARN_ON(!gic_check_reserved_range(paddr, LPI_PENDBASE_SZ)); + its_free_pending_table(gic_data_rdist()->pend_page); + gic_data_rdist()->pend_page = NULL; + + goto out; } + pend_page = gic_data_rdist()->pend_page; + paddr = page_to_phys(pend_page); + WARN_ON(gic_reserve_range(paddr, LPI_PENDBASE_SZ)); + /* set PROPBASE */ - val = (page_to_phys(gic_rdists->prop_page) | + val = (gic_rdists->prop_table_pa | GICR_PROPBASER_InnerShareable | GICR_PROPBASER_RaWaWb | ((LPI_NRBITS - 1) & GICR_PROPBASER_IDBITS_MASK)); @@ -2105,6 +2361,12 @@ static void its_cpu_init_lpis(void) /* Make sure the GIC has seen the above */ dsb(sy); +out: + gic_data_rdist()->lpi_enabled = true; + pr_info("GICv3: CPU%d: using %s LPI pending table @%pa\n", + smp_processor_id(), + gic_data_rdist()->pend_page ? "allocated" : "reserved", + &paddr); } static void its_cpu_init_collection(struct its_node *its) @@ -2489,22 +2751,13 @@ static int its_irq_domain_activate(struct irq_domain *domain, { struct its_device *its_dev = irq_data_get_irq_chip_data(d); u32 event = its_get_event_id(d); - const struct cpumask *cpu_mask = cpu_online_mask; int cpu; - /* get the cpu_mask of local node */ - if (its_dev->its->numa_node >= 0) - cpu_mask = cpumask_of_node(its_dev->its->numa_node); - - /* Bind the LPI to the first possible CPU */ - cpu = cpumask_first_and(cpu_mask, cpu_online_mask); - if (cpu >= nr_cpu_ids) { - if (its_dev->its->flags & ITS_FLAGS_WORKAROUND_CAVIUM_23144) - return -EINVAL; - - cpu = cpumask_first(cpu_online_mask); - } + cpu = its_select_cpu(d, cpu_online_mask); + if (cpu < 0 || cpu >= nr_cpu_ids) + return -EINVAL; + its_inc_lpi_count(d, cpu); its_dev->event_map.col_map[event] = cpu; irq_data_update_effective_affinity(d, cpumask_of(cpu)); @@ -2519,6 +2772,7 @@ static void its_irq_domain_deactivate(struct irq_domain *domain, struct its_device *its_dev = irq_data_get_irq_chip_data(d); u32 event = its_get_event_id(d); + its_dec_lpi_count(d, its_dev->event_map.col_map[event]); /* Stop the delivery of interrupts */ its_send_discard(its_dev, event); } @@ -3270,6 +3524,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) { @@ -3323,6 +3578,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); } @@ -3584,16 +3856,6 @@ static int redist_disable_lpis(void) u64 timeout = USEC_PER_SEC; u64 val; - /* - * If coming via a CPU hotplug event, we don't need to disable - * LPIs before trying to re-enable them. They are already - * configured and all is well in the world. Detect this case - * by checking the allocation of the pending table for the - * current CPU. - */ - if (gic_data_rdist()->pend_page) - return 0; - if (!gic_rdists_supports_plpis()) { pr_info("CPU%d: LPIs not supported\n", smp_processor_id()); return -ENXIO; @@ -3603,7 +3865,21 @@ static int redist_disable_lpis(void) if (!(val & GICR_CTLR_ENABLE_LPIS)) return 0; - pr_warn("CPU%d: Booted with LPIs enabled, memory probably corrupted\n", + /* + * If coming via a CPU hotplug event, we don't need to disable + * LPIs before trying to re-enable them. They are already + * configured and all is well in the world. + * + * If running with preallocated tables, there is nothing to do. + */ + if (gic_data_rdist()->lpi_enabled || + (gic_rdists->flags & RDIST_FLAGS_RD_TABLES_PREALLOCATED)) + return 0; + + /* + * From that point on, we only try to do some damage control. + */ + pr_warn("GICv3: CPU%d: Booted with LPIs enabled, memory probably corrupted\n", smp_processor_id()); add_taint(TAINT_CRAP, LOCKDEP_STILL_OK); @@ -3859,7 +4135,8 @@ int __init its_init(struct fwnode_handle *handle, struct rdists *rdists, } gic_rdists = rdists; - err = its_alloc_lpi_tables(); + + err = allocate_lpi_tables(); if (err) return err; diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 05b9a4cdc8fd59254497145bf365e1096e4f505c..0d4519a8d889cffc03819bad40f052f9bdd760b1 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -653,7 +653,9 @@ early_param("irqchip.gicv3_nolpi", gicv3_nolpi_cfg); static int gic_dist_supports_lpis(void) { - return !!(readl_relaxed(gic_data.dist_base + GICD_TYPER) & GICD_TYPER_LPIS) && !gicv3_nolpi; + return (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && + !!(readl_relaxed(gic_data.dist_base + GICD_TYPER) & GICD_TYPER_LPIS) && + !gicv3_nolpi); } static void gic_cpu_init(void) @@ -673,10 +675,6 @@ static void gic_cpu_init(void) gic_cpu_config(rbase, gic_redist_wait_for_rwp); - /* Give LPIs a spin */ - if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis()) - its_cpu_init(); - /* initialise system registers */ gic_cpu_sys_reg_init(); } @@ -689,6 +687,10 @@ static void gic_cpu_init(void) static int gic_starting_cpu(unsigned int cpu) { gic_cpu_init(); + + if (gic_dist_supports_lpis()) + its_cpu_init(); + return 0; } @@ -818,6 +820,11 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val, #define gic_smp_init() do { } while(0) #endif +static int gic_retrigger(struct irq_data *data) +{ + return !gic_irq_set_irqchip_state(data, IRQCHIP_STATE_PENDING, true); +} + #ifdef CONFIG_CPU_PM /* Check whether it's single security state view */ static bool gic_dist_security_disabled(void) @@ -859,6 +866,7 @@ static struct irq_chip gic_chip = { .irq_eoi = gic_eoi_irq, .irq_set_type = gic_set_type, .irq_set_affinity = gic_set_affinity, + .irq_retrigger = gic_retrigger, .irq_get_irqchip_state = gic_irq_get_irqchip_state, .irq_set_irqchip_state = gic_irq_set_irqchip_state, .flags = IRQCHIP_SET_TYPE_MASKED | @@ -873,6 +881,7 @@ static struct irq_chip gic_eoimode1_chip = { .irq_eoi = gic_eoimode1_eoi_irq, .irq_set_type = gic_set_type, .irq_set_affinity = gic_set_affinity, + .irq_retrigger = gic_retrigger, .irq_get_irqchip_state = gic_irq_get_irqchip_state, .irq_set_irqchip_state = gic_irq_set_irqchip_state, .irq_set_vcpu_affinity = gic_irq_set_vcpu_affinity, @@ -887,6 +896,7 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) { struct irq_chip *chip = &gic_chip; + struct irq_data *irqd = irq_desc_get_irq_data(irq_to_desc(irq)); if (static_branch_likely(&supports_deactivate_key)) chip = &gic_eoimode1_chip; @@ -913,7 +923,7 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_domain_set_info(d, irq, hw, chip, d->host_data, handle_fasteoi_irq, NULL, NULL); irq_set_probe(irq); - irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq))); + irqd_set_single_target(irqd); } /* LPIs */ if (hw >= 8192 && hw < GIC_ID_NR) { @@ -923,6 +933,8 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, handle_fasteoi_irq, NULL, NULL); } + /* Prevents SW retriggers which mess up the ACK/EOI ordering */ + irqd_set_handle_enforce_irqctx(irqd); return 0; } @@ -1127,14 +1139,16 @@ static int __init gic_init_bases(void __iomem *dist_base, gic_update_vlpi_properties(); - if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis()) - its_init(handle, &gic_data.rdists, gic_data.domain); - gic_smp_init(); gic_dist_init(); gic_cpu_init(); gic_cpu_pm_init(); + if (gic_dist_supports_lpis()) { + its_init(handle, &gic_data.rdists, gic_data.domain); + its_cpu_init(); + } + return 0; out_free: diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index b5417012afc8c302faf3eea0b27d311f68fb455f..0f4ec68e3c6a4a35517058dac0c7fd35253bd3b2 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -342,6 +342,11 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val, } #endif +static int gic_retrigger(struct irq_data *data) +{ + return !gic_irq_set_irqchip_state(data, IRQCHIP_STATE_PENDING, true); +} + static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) { u32 irqstat, irqnr; @@ -412,6 +417,7 @@ static const struct irq_chip gic_chip = { .irq_unmask = gic_unmask_irq, .irq_eoi = gic_eoi_irq, .irq_set_type = gic_set_type, + .irq_retrigger = gic_retrigger, .irq_get_irqchip_state = gic_irq_get_irqchip_state, .irq_set_irqchip_state = gic_irq_set_irqchip_state, .flags = IRQCHIP_SET_TYPE_MASKED | @@ -964,6 +970,7 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) { struct gic_chip_data *gic = d->host_data; + struct irq_data *irqd = irq_desc_get_irq_data(irq_to_desc(irq)); if (hw < 32) { irq_set_percpu_devid(irq); @@ -974,8 +981,11 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data, handle_fasteoi_irq, NULL, NULL); irq_set_probe(irq); - irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq))); + irqd_set_single_target(irqd); } + + /* Prevents SW retriggers which mess up the ACK/EOI ordering */ + irqd_set_handle_enforce_irqctx(irqd); return 0; } diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index 841c005d8ebb2f720047a984ff25c0d974a37dff..461e2397dc88387b8c1b0e5aa36835ed7f1d96d8 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -21,6 +21,12 @@ 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 + This driver provides the support for the Phytium mailbox controller. + config PLATFORM_MHU tristate "Platform MHU Mailbox" depends on OF diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index c818b5d011aef28ae2e4632c3e93ff05f446590d..de3cbe3ffa44e7969a82815273fff688ab147c3b 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -9,6 +9,8 @@ obj-$(CONFIG_ARM_MHU) += arm_mhu.o obj-$(CONFIG_IMX_MBOX) += imx-mailbox.o +obj-$(CONFIG_PHYTIUM_MBOX) += phytium_mailbox.o + obj-$(CONFIG_PLATFORM_MHU) += platform_mhu.o obj-$(CONFIG_PL320_MBOX) += pl320-ipc.o diff --git a/drivers/mailbox/phytium_mailbox.c b/drivers/mailbox/phytium_mailbox.c new file mode 100644 index 0000000000000000000000000000000000000000..e33eb82efa44184e331e898a11356d5d3387b1c4 --- /dev/null +++ b/drivers/mailbox/phytium_mailbox.c @@ -0,0 +1,216 @@ +// 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 + +static spinlock_t lock; + +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; + unsigned long flags = 0; + + spin_lock_irqsave(&lock, flags); + val = readl_relaxed(mlink->rx_reg + INTR_STAT); + spin_unlock_irqrestore(&lock, flags); + if (!val) + return IRQ_NONE; + + mbox_chan_received_data(chan, (void *)&val); + + spin_lock_irqsave(&lock, flags); + writel_relaxed(val, mlink->rx_reg + INTR_CLR); + spin_unlock_irqrestore(&lock, flags); + + 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; + unsigned long flags = 0; + + spin_lock_irqsave(&lock, flags); + writel_relaxed(*arg, mlink->tx_reg + INTR_SET); + spin_unlock_irqrestore(&lock, flags); + + return 0; +} + +static int phytium_mbox_startup(struct mbox_chan *chan) +{ + struct phytium_mbox_link *mlink = chan->con_priv; + u32 val; + int ret; + unsigned long flags = 0; + + spin_lock_irqsave(&lock, flags); + val = readl_relaxed(mlink->tx_reg + INTR_STAT); + writel_relaxed(val, mlink->tx_reg + INTR_CLR); + spin_unlock_irqrestore(&lock, flags); + + 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) +{ + unsigned long flags; + struct phytium_mbox_link *mlink = chan->con_priv; + u32 val; + + spin_lock_irqsave(&lock, flags); + val = readl_relaxed(mlink->tx_reg + INTR_STAT); + spin_unlock_irqrestore(&lock, flags); + + 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; + + spin_lock_init(&lock); + + /* 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 v2"); +MODULE_DESCRIPTION("Phytium SoC Mailbox Driver"); +MODULE_AUTHOR("Chen Baozi "); diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 54fe90acb5b2962432140949a17e1630361e08ca..6c224071bc83fcad826f24ad583b8399a0bb837f 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -151,6 +151,16 @@ config VIDEO_TI_CAL In TI Technical Reference Manual this module is referred as Camera Interface Subsystem (CAMSS). +config VIDEO_PHYTIUM_JPEG + tristate "Phytium JPEG Encoder driver" + depends on VIDEO_V4L2 + 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. + endif # V4L_PLATFORM_DRIVERS menuconfig V4L_MEM2MEM_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 41322ab658027f1d7aa0c571d01ea34444319570..f89634e3239ad5a995a909c33d88a4fe3d714d50 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -93,6 +93,8 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss/ obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/ +obj-$(CONFIG_VIDEO_PHYTIUM_JPEG) += phytium-jpeg/ + obj-y += meson/ obj-y += cros-ec-cec/ diff --git a/drivers/media/platform/phytium-jpeg/Makefile b/drivers/media/platform/phytium-jpeg/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d9f50a1aaba3546017f4f69f80be616ee21b4aff --- /dev/null +++ b/drivers/media/platform/phytium-jpeg/Makefile @@ -0,0 +1,3 @@ +# 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-jpeg/phytium_jpeg_core.c b/drivers/media/platform/phytium-jpeg/phytium_jpeg_core.c new file mode 100644 index 0000000000000000000000000000000000000000..37a5674a0dd1d64849a8358241618b96bfca88ae --- /dev/null +++ b/drivers/media/platform/phytium-jpeg/phytium_jpeg_core.c @@ -0,0 +1,1381 @@ +// 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"); + set_bit(VIDEO_RES_CHANGE, &jpeg_dev->status); + + 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 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_GRABBER; /* The newest kernel using 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_GRABBER, 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_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_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 v2"); diff --git a/drivers/media/platform/phytium-jpeg/phytium_jpeg_core.h b/drivers/media/platform/phytium-jpeg/phytium_jpeg_core.h new file mode 100644 index 0000000000000000000000000000000000000000..e398d974d928dd2addede501b933aaa872e64244 --- /dev/null +++ b/drivers/media/platform/phytium-jpeg/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(50) +#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-jpeg/phytium_jpeg_reg.h b/drivers/media/platform/phytium-jpeg/phytium_jpeg_reg.h new file mode 100644 index 0000000000000000000000000000000000000000..3567badf9eb50139e995beaa52ea9f540db782bb --- /dev/null +++ b/drivers/media/platform/phytium-jpeg/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 dd938a5d04094e5e2476e33972fa8cafc8078309..acf86c746b3d5cbce2a0e3dea93732a442e46a41 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -915,6 +915,19 @@ config UCB1400_CORE To compile this driver as a module, choose M here: the module will be called ucb1400_core. +config MFD_PHYTIUM_I2S_LSD + tristate "PHYTIUM Px210 I2S LSD MFD driver" + depends on (PCI && ARCH_PHYTIUM) + help + This enables support for the Phytium Px210 LSD I2S controller. + +config MFD_PHYTIUM_I2S_MMD + tristate "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 5856a9489cbd83157025004c05fae2b341e2b51f..561d67112e8933d69d5d0933c53446b1f84a8ec3 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -241,3 +241,5 @@ obj-$(CONFIG_MFD_SC27XX_PMIC) += sprd-sc27xx-spi.o obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.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..bfdbc9a4ebcd3b268539830eb7b98a21dd47522d --- /dev/null +++ b/drivers/mfd/phytium_px210_i2s_lsd.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium I2S LSD MFD driver over PCI bus + * + * Copyright (c) 2020-2023 Phytium Technology Co., Ltd. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + * + */ + +#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("Yiqun Zhang "); +MODULE_DESCRIPTION("Phytium Px210 MFD PCI driver for I2S-LSD"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/phytium_px210_i2s_mmd.c b/drivers/mfd/phytium_px210_i2s_mmd.c new file mode 100644 index 0000000000000000000000000000000000000000..4020686d4ce2e847988246bb3adb3a27dfe5b2ab --- /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("Yiqun Zhang "); +MODULE_DESCRIPTION("Phytium Px210 MFD PCI driver for I2S-DP"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 2c11944686cf9ec977f19c4526f0c614af59f600..51fbbe77c0e58eb938f9ba949c5af9dae38f2fce 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -945,3 +945,25 @@ config MMC_SDHCI_OMAP 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 SoCs. + 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 ce8398e6f2c0e49395c4a818c95ecb1e69d6b87e..a3aa80aaf7df7c1895e041563a710c337960e985 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -69,6 +69,8 @@ obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o obj-$(CONFIG_MMC_USDHI6ROL0) += usdhi6rol0.o obj-$(CONFIG_MMC_TOSHIBA_PCI) += toshsd.o obj-$(CONFIG_MMC_BCM2835) += bcm2835.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..7e4ba4ac0a20dd40046c2169c376ffdbd548e979 --- /dev/null +++ b/drivers/mmc/host/phytium-mci-pci.c @@ -0,0 +1,179 @@ +// 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_ERASE | + MMC_CAP_CMD23 | MMC_CAP_UHS_SDR25 | MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR50| + 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_ERASE | 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[] = { + { + .vendor = 0x1DB7, + .device = 0xDC28, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .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 v2"); +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..966dbe3d96344d13ff9943c23fb029b264a1975c --- /dev/null +++ b/drivers/mmc/host/phytium-mci-plat.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Phytium Multimedia Card Interface platform 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_ERASE | 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 (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); + del_timer_sync(&host->timeout_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 v2"); +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..91e844874b81a2db3e1531aaf56170a6fc94b2b7 --- /dev/null +++ b/drivers/mmc/host/phytium-mci.c @@ -0,0 +1,1549 @@ +// 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 (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); + } + + 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); + + mod_timer(&host->timeout_timer, + jiffies + msecs_to_jiffies(MMC_REQ_TIMEOUT_MS)); +} + +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); + del_timer(&host->timeout_timer); + 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 { + /* + * Sometimes get ACMD41 cmd done irq but the respose index still the APP_CMD, + * so polling the mci status entill the respose index change. + */ + if (cmd->opcode == SD_APP_OP_COND) { + int polling_cnt = 20; + while (MMC_APP_CMD == MCI_STATUS_RESPOSE_INDEX(readl(host->base + MCI_STATUS))) { + udelay(100); + polling_cnt --; + if (polling_cnt == 0) { + dev_info(host->dev, "hw respose index not equal cmd opcode, respose value may error\n"); + break; + } + } + } + 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); + + mod_timer(&host->timeout_timer, + jiffies + msecs_to_jiffies(MMC_REQ_TIMEOUT_MS)); +} + +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 void phytium_mci_request_timeout(struct timer_list *t) +{ + struct phytium_mci_host *host = from_timer(host, t, timeout_timer); + + dev_err(host->dev, "request timeout\n"); + if (host->mrq) { + dev_err(host->dev, "aborting mrq=%p cmd=%d\n", + host->mrq, host->mrq->cmd->opcode); + if (host->cmd) { + dev_err(host->dev, "aborting cmd=%d\n", host->cmd->opcode); + phytium_mci_cmd_done(host, MCI_RAW_INTS_RTO, host->mrq, host->cmd); + } else if (host->data) { + dev_err(host->dev, "abort data: cmd%d; %d blocks\n", + host->mrq->cmd->opcode, host->data->blocks); + phytium_mci_data_xfer_done(host, MCI_RAW_INTS_RTO, host->mrq, + host->data); + } + } +} + +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) && + ((events == 0x10001) || (events == 0x10000) || (events == 0x10040))) { + writel(events, host->base + MCI_RAW_INTS); + __phytium_mci_enable_sdio_irq(host, 0); + sdio_signal_irq(host->mmc); + spin_unlock_irqrestore(&host->lock, flags); + goto irq_out; + } + + writel(events, host->base + MCI_RAW_INTS); + writel(dmac_events, host->base + MCI_DMAC_STATUS); + spin_unlock_irqrestore(&host->lock, flags); + + 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 ((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 (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, + .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); + timer_setup(&host->timeout_timer, phytium_mci_request_timeout, 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->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_zalloc_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 v2"); +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..5423597ecb97b13bc85d0486ee2323debbb66363 --- /dev/null +++ b/drivers/mmc/host/phytium-mci.h @@ -0,0 +1,357 @@ +/* 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_RESPOSE_INDEX_OFFSET (11) +#define MCI_STATUS_RESPOSE_INDEX_MASK (0x3f << MCI_STATUS_RESPOSE_INDEX_OFFSET) /* RO */ +#define MCI_STATUS_RESPOSE_INDEX(reg) (((reg) & MCI_STATUS_RESPOSE_INDEX_MASK) >> MCI_STATUS_RESPOSE_INDEX_OFFSET) +#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 + +/* mmc request timeout 5000ms */ +#define MMC_REQ_TIMEOUT_MS 5000 + +/* 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 timer_list timeout_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 + +}; + +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/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 5fc9a1bde4ac2e6d6e8475ab92c81342395d03d7..7ad102b71fc653e8d7932f6665f0842b38a88284 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -285,6 +285,25 @@ config MTD_NAND_ATMEL Enables support for NAND Flash / Smart Media Card interface on Atmel AT91 processors. +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 "NAND controller support on Marvell boards" depends on PXA3xx || ARCH_MMP || PLAT_ORION || ARCH_MVEBU || \ diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile index d5a5f9832b8879e4cbf16f5dd7f902edcf52c239..7a0f420d24120ab2d4053954c6e1fe9267d8b2a4 100644 --- a/drivers/mtd/nand/raw/Makefile +++ b/drivers/mtd/nand/raw/Makefile @@ -58,6 +58,10 @@ obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o obj-$(CONFIG_MTD_NAND_MTK) += mtk_ecc.o mtk_nand.o obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.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_bbt.o nand_timings.o nand_ids.o nand-objs += nand_amd.o nand-objs += nand_hynix.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..f94ccd41221bd83136d8af1e68542d8ecd85be2b --- /dev/null +++ b/drivers/mtd/nand/raw/phytium_nand.c @@ -0,0 +1,2171 @@ +// 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); + +#ifdef DEBUG +static int phytium_nfc_cmd_dump(struct phytium_nfc *nfc, + struct phytium_nfc_op *nfc_op, u8 *buf) +{ + u8 *p; + u8 str[1024] = {0}; + int i; + + sprintf(str, "Phytium NFC cmd dump:\n"); + sprintf(str, "%s cmd0:%x, cmd1:%x, ctrl:%x, page_cnt:%d\n", + str, nfc_op->cmd[0], nfc_op->cmd[1], nfc_op->cmd_ctrl.ctrl, nfc_op->page_cnt); + + p = &nfc_op->addr[0]; + sprintf(str, "%s addr:%02x %02x %02x %02x %02x\n", + str, p[0], p[1], p[2], p[3], p[4]); + + p = &nfc_op->mem_addr_first[0]; + sprintf(str, "%s mem_addr_first:%02x %02x %02x %02x %02x\n", + str, p[0], p[1], p[2], p[3], p[4]); + + for (i = 0; i < PHYTIUM_NFC_DSP_SIZE; i++) + sprintf(str, "%s %02x", str, buf[i]); + + dev_dbg(nfc->dev, "%s\n", str); + + return 0; +} + +int phytium_nfc_data_dump(struct phytium_nfc *nfc, u8 *buf, u32 len) +{ + u8 str[1024] = {0}; + int i; + + len = len > 512 ? 512 : len; + + sprintf(str, "Phytium NFC data dump: %d\n", len); + for (i = 0; i < len; i++) { + if (i && (i%128 == 0)) { + dev_info(nfc->dev, "next:\n%s\n", str); + memset(str, 0, 1024); + } + + if (i && (i%16 == 0)) + sprintf(str, "%s\n", str); + sprintf(str, "%s %02x", str, buf[i]); + } + + dev_dbg(nfc->dev, "%s\n", str); + + return 0; +} +EXPORT_SYMBOL_GPL(phytium_nfc_data_dump); +#else +#define phytium_nfc_cmd_dump(...) +#define phytium_nfc_data_dump(...) +#endif + +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); + + phytium_nfc_cmd_dump(nfc, nfc_op, (u8 *)nfc->dsp_addr); + + 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); + phytium_nfc_cmd_dump(nfc, nfc_op, (u8 *)nfc->dsp_addr + i*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 < 5) + 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; + + phytium_nfc_cmd_dump(nfc, &nfc_op, (u8 *)nfc->dsp_addr); + + memcpy(in, nfc->dma_buf + nfc->dma_offset, len); + nfc->dma_offset += len; + phytium_nfc_data_dump(nfc, in, 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 mtd_info *mtd, int die_nr) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + 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(&phytium_nand->chip.data_interface); + + 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(&phytium_nand->chip.data_interface); + + 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(&phytium_nand->chip.data_interface); + + 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); + + 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(&phytium_nand->chip.data_interface); + + 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(&phytium_nand->chip.data_interface); + + 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(&phytium_nand->chip.data_interface); + ecc_offset = phytium_nand->ecc.offset; + + 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_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); +out: + kfree(nfc_op); + return ret; +} + +static int phytium_nfc_hw_ecc_bch_read_page_raw(struct mtd_info *mtd, + struct nand_chip *chip, + u8 *buf, int oob_required, + int page) +{ + 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); + + phytium_nfc_data_dump(to_phytium_nfc(chip->controller), buf, mtd->writesize); + + return ret; +} + +static int phytium_nfc_hw_ecc_bch_read_oob_raw(struct mtd_info *mtd, + struct nand_chip *chip, int page) +{ + int ret; + + /* Invalidate page cache */ + chip->pagebuf = -1; + memset(chip->oob_poi, 0xFF, mtd->oobsize); + + ret = phytium_nand_oob_read(mtd, chip, NULL, chip->oob_poi, + mtd->oobsize, page, true); + + phytium_nfc_data_dump(to_phytium_nfc(chip->controller), chip->oob_poi, mtd->oobsize); + + return ret; +} + +static int phytium_nfc_hw_ecc_bch_read_page(struct mtd_info *mtd, + struct nand_chip *chip, + u8 *buf, int oob_required, + int page) +{ + int ret; + 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); + 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); + } + + phytium_nfc_data_dump(to_phytium_nfc(chip->controller), buf, mtd->writesize); + + return ret; +} + +static int phytium_nfc_hw_ecc_bch_read_oob(struct mtd_info *mtd, + struct nand_chip *chip, + int page) +{ + u32 oob_len = mtd->oobsize; + int ret; + + ret = phytium_nand_oob_read(mtd, chip, NULL, chip->oob_poi, + oob_len, page, true); + + phytium_nfc_data_dump(to_phytium_nfc(chip->controller), chip->oob_poi, oob_len); + + return ret; +} + +static int phytium_nfc_hw_ecc_bch_write_page_raw(struct mtd_info *mtd, + struct nand_chip *chip, + const u8 *buf, + int oob_required, int page) +{ + 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 mtd_info *mtd, + struct nand_chip *chip, + const u8 *buf, + int oob_required, int page) +{ + int ret; + 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); + 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 mtd_info *mtd, + struct nand_chip *chip, int page) +{ + 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 mtd_info *mtd, + struct nand_chip *chip, int page) +{ + 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 -ENOTSUPP; + + chip->ecc.algo = NAND_ECC_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) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + int ret = 0; + + if (ecc->mode != NAND_ECC_NONE && (!ecc->size || !ecc->strength)) { + if (chip->ecc_step_ds && chip->ecc_strength_ds) { + ecc->size = chip->ecc_step_ds; + ecc->strength = chip->ecc_strength_ds; + } else { + ecc->size = 512; + ecc->strength = 1; + } + } + + mtd_set_ooblayout(mtd, &phytium_nand_ooblayout_ops); + + switch (ecc->mode) { + case NAND_ECC_HW: + ret = phytium_nand_hw_ecc_ctrl_init(mtd, ecc); + break; + case NAND_ECC_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_SOFT: + case NAND_ECC_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.mode == NAND_ECC_HW) { + 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.mode = NAND_ECC_HW; + } + + 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.mode == NAND_ECC_HW) + 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 const struct nand_controller_ops phytium_nand_controller_ops = { + .attach_chip = phytium_nand_attach_chip, +}; + +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) { + nand_release(&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_data_interface(struct mtd_info *mtd, int chipnr, + const struct nand_data_interface + *conf) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + 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->exec_op = phytium_nfc_exec_op; + chip->select_chip = phytium_nfc_select_chip; + chip->setup_data_interface = phytium_nfc_setup_data_interface; + 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.mode = NAND_ECC_HW; + + 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); + nand_release(chip); + 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; +} + +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 v2"); +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..963d555a2aca009c7904b1d362d7b833483b507f --- /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; +} __attribute__ ((__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..6ce76dd15161a183bbad882ff7a1c333ecdb5095 --- /dev/null +++ b/drivers/mtd/nand/raw/phytium_nand_pci.c @@ -0,0 +1,158 @@ +// 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); + + ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + 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 v2"); +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..b3fde07b4aeb18b16db35fcd061716ff6c8c8d9f --- /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 v2"); +MODULE_DESCRIPTION("Phytium NAND controller Platform driver"); +MODULE_AUTHOR("Zhu Mingshuai "); diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index 37775fc09e09515efda6c41d7167b8a4decadc09..ff2af58a9a4a9cdeb4af430cefd38cf7b91c911c 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -7,6 +7,15 @@ menuconfig MTD_SPI_NOR if MTD_SPI_NOR +config SPI_PHYTIUM_QUADSPI + tristate "Phytium Quad SPI Controller" + depends on ARCH_PHYTIUM || ARM + depends on OF && HAS_IOMEM + help + This enables support for the Quad SPI controller in master mode. + This driver does not support generic SPI. The implementation only + supports SPI NOR. + config MTD_MT81xx_NOR tristate "Mediatek MT81xx SPI NOR flash controller" depends on HAS_IOMEM diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index f4c61d282abd5c29538d3e208615f799fcaad251..ebc8ce095bd08493749591bfeb70bb7a36d68eb6 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o +obj-$(CONFIG_SPI_PHYTIUM_QUADSPI) += phytium-quadspi.o obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o diff --git a/drivers/mtd/spi-nor/phytium-quadspi.c b/drivers/mtd/spi-nor/phytium-quadspi.c new file mode 100644 index 0000000000000000000000000000000000000000..ec317602f3a56e6d0c2e7a37cf772571b5e8049b --- /dev/null +++ b/drivers/mtd/spi-nor/phytium-quadspi.c @@ -0,0 +1,1028 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium QuadSPI 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 + +#define QSPI_FLASH_CAP_REG 0x000 +#define QSPI_RD_CFG_REG 0x004 +#define QSPI_WR_CFG_REG 0x008 +#define QSPI_FLUSH_REG 0x00C +#define QSPI_CMD_PORT_REG 0x010 +#define QSPI_ADDR_PORT_REG 0x014 +#define QSPI_HD_PORT_REG 0x018 +#define QSPI_LD_PORT_REG 0x01C +#define QSPI_FUN_SET_REG 0x020 +#define QSPI_WIP_REG 0x024 +#define QSPI_WP_REG 0x028 +#define QSPI_MODE_REG 0x02C + +#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_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 (0x01 << QSPI_RD_CFG_RD_THROUGH_SHIFT) +#define QSPI_RD_CFG_RD_TRANSFER_SHIFT 20 +#define QSPI_RD_CFG_RD_TRANSFER_MASK (0x07 << 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 (0x3 << QSPI_RD_CFG_RD_SCK_SEL_SHIFT) + +#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_MAS (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 (0x01 << 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_EN (0x1 << 0) + +#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_TRANSFER_SHIFT 13 +#define QSPI_CMD_PORT_DATA_TRANSFER_MASK (0x1 << 13) +#define QSPI_CMD_PORT_SEL_SHIFT 12 +#define QSPI_CMD_PORT_SEL_MASK (0x1 << QSPI_CMD_PORT_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_DUMMY(x) (((x) << QSPI_CMD_PORT_DUMMY_SHIFT) & QSPI_CMD_PORT_DUMMY_MASK) +#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_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_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_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_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 FSIZE_VAL(size) (__fls(size) - 1) + +#define PHYTIUM_MAX_MMAP_S SZ_512M +#define PHYTIUM_MAX_NORCHIP 4 + +#define PHYTIUM_QSPI_FIFO_SZ 32 +#define PHYTIUM_QSPI_FIFO_TIMEOUT_US 50000 +#define PHYTIUM_QSPI_BUSY_TIMEOUT_US 100000 + +#define PHYTIUM_SCK_SEL 0x05 +#define PHYTIUM_CMD_SCK_SEL 0x07 + +#define PHYTIUM_FMODE_MM 0x01 +#define PHYTIUM_FMODE_IN 0x02 + +/* + * the codes of the different commands + */ +#define CMD_WRDI 0x04 +#define CMD_RDID 0x9F +#define CMD_RDSR 0x05 +#define CMD_WREN 0x06 +#define CMD_RDAR 0x65 +#define CMD_P4E 0x20 +#define CMD_4P4E 0x21 +#define CMD_BE 0x60 +#define CMD_4BE 0xC7 +#define CMD_READ 0x03 +#define CMD_FAST_READ 0x0B +#define CMD_QOR 0x6B +#define CMD_QIOR 0xEB +#define CMD_DDRFR 0x0D +#define CMD_DDRQIOQ 0xED +#define CMD_PP 0x02 +#define CMD_QPP 0x32 +#define CMD_SE 0xD8 +#define CMD_4FAST_READ 0x0C +#define CMD_4READ 0x13 +#define CMD_4QOR 0x6C +#define CMD_4QIOR 0xEC +#define CMD_4DDRFR 0x0E +#define CMD_4DDRQIOR 0xEE +#define CMD_4PP 0x12 +#define CMD_4QPP 0x34 +#define CMD_4SE 0xDC + +#define PHYTIUM_QSPI_1_1_1 0 +#define PHYTIUM_QSPI_1_1_2 1 +#define PHYTIUM_QSPI_1_1_4 2 +#define PHYTIUM_QSPI_1_2_2 3 +#define PHYTIUM_QSPI_1_4_4 4 +#define PHYTIUM_QSPI_2_2_2 5 +#define PHYTIUM_QSPI_4_4_4 6 + +struct phytium_qspi_flash { + struct spi_nor nor; + struct phytium_qspi *qspi; + u32 cs; + u32 fsize; + u32 presc; + u32 clk_div; + u32 read_mode; + bool registered; + u32 prefetch_limit; + u32 addr_width; + u32 read_cmd; +}; + +struct phytium_qspi { + struct device *dev; + void __iomem *io_base; + void __iomem *mm_base; + resource_size_t mm_size; + u32 nor_num; + struct clk *clk; + u32 clk_rate; + struct phytium_qspi_flash flash[PHYTIUM_MAX_NORCHIP]; + + spinlock_t spinlock; + + /* + * to protect device configuration, could be different between + * 2 flash access (bk1, bk2) + */ + struct mutex lock; +}; + +/* Need to enable p_buffer */ +static int memcpy_from_ftreg(struct phytium_qspi *qspi, u_char *buf, size_t len) +{ + int i; + u32 val = 0; + + if (!qspi || !buf) + return -EINVAL; + + for (i = 0; i < len; i++) { + if (0 == i % 4) + val = readl_relaxed(qspi->io_base + QSPI_LD_PORT_REG); + + buf[i] = (u_char) (val >> (i % 4) * 8) & 0xFF; + } + + return 0; +} + +/* Not to enable p_buffer */ +static int memcpy_to_ftreg(struct phytium_qspi *qspi, u_char *buf, size_t len) +{ + u32 val = 0; + + if (!qspi || !buf || (len >= 8)) + return -EINVAL; + + if (1 == len) { + val = buf[0]; + } else if (2 == len) { + val = buf[1]; + val = (val << 8) + buf[0]; + } else if (3 == len) { + val = buf[2]; + val = (val << 8) + buf[1]; + val = (val << 8) + buf[0]; + } else if (4 == len) { + val = buf[3]; + val = (val << 8) + buf[2]; + val = (val << 8) + buf[1]; + val = (val << 8) + buf[0]; + } + + writel_relaxed(val, qspi->io_base + QSPI_LD_PORT_REG); + + return 0; +} + +static int phytium_qspi_wait_cmd(struct phytium_qspi *qspi, + struct phytium_qspi_flash *flash) +{ + u32 cmd = 0; + u32 cnt = 0; + + cmd |= CMD_RDSR << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= BIT(QSPI_CMD_PORT_DATA_TRANSFER_SHIFT); + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + + cnt = PHYTIUM_QSPI_BUSY_TIMEOUT_US / 10; + while (readl_relaxed(qspi->io_base + QSPI_LD_PORT_REG) & 0x01) { + udelay(10); + cnt--; + if (!cnt) { + dev_err(qspi->dev, "wait command process timeout\n"); + break; + } + } + + return !cnt; +} + +static int phytium_qspi_cmd_enable(struct phytium_qspi *qspi) +{ + u32 val = 0; + + writel_relaxed(val, qspi->io_base + QSPI_LD_PORT_REG); + + return 0; +} + +static int phytium_qspi_write_enable(struct phytium_qspi *qspi, + struct phytium_qspi_flash *flash) +{ + u32 cmd = 0; + + cmd = CMD_WREN << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= PHYTIUM_SCK_SEL << QSPI_CMD_PORT_SCK_SEL_SHIFT; + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + phytium_qspi_cmd_enable(qspi); + + return 0; +} + +static int phytium_qspi_write_disable(struct phytium_qspi *qspi, + struct phytium_qspi_flash *flash) +{ + u32 cmd = 0; + + cmd = CMD_WRDI << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= PHYTIUM_SCK_SEL << QSPI_CMD_PORT_SCK_SEL_SHIFT; + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + phytium_qspi_cmd_enable(qspi); + + return 0; +} + +static int phytium_qspi_read_flash_id(struct phytium_qspi *qspi, + struct phytium_qspi_flash *flash, u8 opcode, u8 *buf, int len) +{ + u32 cmd = 0; + unsigned long iflags; + + cmd = opcode << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= BIT(QSPI_CMD_PORT_DATA_TRANSFER_SHIFT); + cmd |= BIT(QSPI_CMD_PORT_P_BUFFER_SHIFT); + cmd |= PHYTIUM_CMD_SCK_SEL << QSPI_CMD_PORT_SCK_SEL_SHIFT; + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + phytium_qspi_cmd_enable(qspi); + + spin_lock_irqsave(&qspi->spinlock, iflags); + memcpy_from_ftreg(qspi, buf, len); + spin_unlock_irqrestore(&qspi->spinlock, iflags); + + dev_dbg(qspi->dev, "read flash id:%x\n", *(u32 *)buf); + return 0; +} + +static int phytium_qspi_read_flash_sfdp(struct phytium_qspi *qspi, + struct phytium_qspi_flash *flash, struct spi_nor *nor, loff_t from, u8 *buf, int len) +{ + unsigned long iflags; + u32 cmd = 0; + u8 opcode = nor->read_opcode; + + cmd = opcode << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= BIT(QSPI_CMD_PORT_DATA_TRANSFER_SHIFT); + cmd |= BIT(QSPI_CMD_PORT_P_BUFFER_SHIFT); + cmd |= BIT(QSPI_CMD_PORT_CMD_ADDR_SHIFT); + cmd |= PHYTIUM_SCK_SEL << QSPI_CMD_PORT_SCK_SEL_SHIFT; + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + cmd |= BIT(QSPI_CMD_PORT_LATENCY_SHIFT); + cmd |= QSPI_CMD_PORT_DUMMY(nor->read_dummy - 1); + + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + writel_relaxed(from, qspi->io_base + QSPI_ADDR_PORT_REG); + phytium_qspi_cmd_enable(qspi); + + spin_lock_irqsave(&qspi->spinlock, iflags); + memcpy_from_ftreg(qspi, buf, len); + spin_unlock_irqrestore(&qspi->spinlock, iflags); + + dev_dbg(qspi->dev, "read flash sfdp:0x%llx 0x%llx\n", + *(u64 *)buf, *(u64 *)(buf + 8)); + return len; +} + +static int phytium_qspi_read_flash_sr1(struct phytium_qspi *qspi, + struct phytium_qspi_flash *flash, u8 opcode, u8 *buf, int len) +{ + u32 cmd = 0; + u32 val; + + cmd = opcode << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= BIT(QSPI_CMD_PORT_DATA_TRANSFER_SHIFT); + cmd |= (len << QSPI_CMD_PORT_RW_NUM_SHIFT) & QSPI_CMD_PORT_RW_NUM_MASK; + cmd |= PHYTIUM_CMD_SCK_SEL << QSPI_CMD_PORT_SCK_SEL_SHIFT; + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + phytium_qspi_cmd_enable(qspi); + + val = readl_relaxed(qspi->io_base + QSPI_LD_PORT_REG); + buf[0] = (u8)val; + + return 0; +} + +static int phytium_qspi_read_reg(struct spi_nor *nor, + u8 opcode, u8 *buf, int len) +{ + struct phytium_qspi_flash *flash = nor->priv; + struct device *dev = flash->qspi->dev; + struct phytium_qspi *qspi = flash->qspi; + unsigned long iflags; + u32 cmd = 0; + + dev_dbg(dev, "read_reg: cmd:%#.2x buf:%pK len:%#x\n", opcode, buf, len); + + switch (opcode) { + case CMD_RDID: + phytium_qspi_read_flash_id(qspi, flash, opcode, buf, len); + return 0; + case CMD_RDSR: + phytium_qspi_read_flash_sr1(qspi, flash, opcode, buf, len); + return 0; + default: + break; + } + + cmd = opcode << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= BIT(QSPI_CMD_PORT_DATA_TRANSFER_SHIFT); + cmd |= BIT(QSPI_CMD_PORT_P_BUFFER_SHIFT); + cmd |= PHYTIUM_CMD_SCK_SEL << QSPI_CMD_PORT_SCK_SEL_SHIFT; + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + phytium_qspi_cmd_enable(qspi); + + spin_lock_irqsave(&qspi->spinlock, iflags); + memcpy_from_ftreg(qspi, buf, len); + spin_unlock_irqrestore(&qspi->spinlock, iflags); + + return 0; +} + +static int phytium_qspi_write_reg(struct spi_nor *nor, u8 opcode, + u8 *buf, int len) +{ + struct phytium_qspi_flash *flash = nor->priv; + struct device *dev = flash->qspi->dev; + struct phytium_qspi *qspi = flash->qspi; + u32 cmd = 0; + + dev_dbg(dev, "write_reg: cmd:%#.2x buf:%pK len:%#x\n", + opcode, buf, len); + + switch(opcode){ + case CMD_WREN: + phytium_qspi_write_enable(qspi, flash); + return 0; + case CMD_WRDI: + phytium_qspi_write_disable(qspi, flash); + return 0; + default: + break; + } + + cmd = opcode << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= PHYTIUM_CMD_SCK_SEL << QSPI_CMD_PORT_SCK_SEL_SHIFT; + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + + if ((len > 8) || (NULL == buf)) { + dev_err(dev, "data length exceed. commad %x, len:%d \n", opcode, len); + return -EINVAL; + } + else if(len > 0){ + cmd |= ((len - 1) << QSPI_CMD_PORT_RW_NUM_SHIFT) & QSPI_CMD_PORT_RW_NUM_MASK; + cmd |= BIT(QSPI_CMD_PORT_DATA_TRANSFER_SHIFT); + } + + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + memcpy_to_ftreg(qspi, buf, len); + + return 0; +} + +static ssize_t phytium_qspi_read_tmp(struct phytium_qspi *qspi, u32 read_cmd, + loff_t from, size_t len, u_char *buf) +{ + u32 addr = (u32)from; + u64 val = 0; + + if (!qspi) + return -1; + + dev_dbg(qspi->dev, "read cmd:%x, addr:%x len:%zx\n", read_cmd, addr, len); + writel_relaxed(read_cmd, qspi->io_base + QSPI_RD_CFG_REG); + + memcpy_fromio(buf, qspi->mm_base + addr, len); + + val = *(u64 *)(buf); + dev_dbg(qspi->dev, "read val:%llx\n", val); + + return len; +} + +static ssize_t phytium_qspi_read(struct spi_nor *nor, loff_t from, size_t len, + u_char *buf) +{ + struct phytium_qspi_flash *flash = nor->priv; + struct phytium_qspi *qspi = flash->qspi; + u32 cmd = nor->read_opcode; + u32 addr = (u32)from; + + addr = addr + flash->cs * flash->fsize; + dev_dbg(qspi->dev, "read(%#.2x): buf:%pK from:%#.8x len:%#zx\n", + nor->read_opcode, buf, addr, len); + + cmd = cmd << QSPI_RD_CFG_RD_CMD_SHIFT; + cmd |= BIT(QSPI_RD_CFG_D_BUFFER_SHIFT); + cmd |= flash->clk_div << QSPI_CMD_PORT_SCK_SEL_SHIFT; + + cmd &= ~QSPI_RD_CFG_RD_TRANSFER_MASK; + cmd |= (flash->addr_width << QSPI_RD_CFG_RD_TRANSFER_SHIFT); + + switch (nor->read_opcode) { + case CMD_READ: + case CMD_FAST_READ: + case CMD_QIOR: + case CMD_QOR: + cmd &= ~QSPI_RD_CFG_RD_ADDR_SEL_MASK; + break; + case CMD_4READ: + case CMD_4FAST_READ: + case CMD_4QOR: + case CMD_4QIOR: + cmd |= BIT(QSPI_RD_CFG_RD_ADDR_SEL_SHIFT); + break; + case 0x5A: + cmd &= ~QSPI_RD_CFG_RD_ADDR_SEL_MASK; + return phytium_qspi_read_flash_sfdp(qspi, flash, nor, from, buf, len); + break; + default: + break; + } + + if((PHYTIUM_QSPI_1_1_4 == flash->addr_width) || + (PHYTIUM_QSPI_1_4_4 == flash->addr_width)) { + cmd |= BIT(QSPI_RD_CFG_RD_LATENCY_SHIFT); + + cmd &= ~QSPI_RD_CFG_DUMMY_MASK; + cmd |= (0x07 << QSPI_RD_CFG_DUMMY_SHIFT); + } + + dev_dbg(qspi->dev, "read(%#.2x): cmd:%#x\n", nor->read_opcode, cmd); + if (cmd != flash->read_cmd) + flash->read_cmd = cmd; + + writel_relaxed(cmd, qspi->io_base + QSPI_RD_CFG_REG); + + memcpy_fromio(buf, qspi->mm_base + addr, len); + + return len; +} + +static ssize_t phytium_qspi_write(struct spi_nor *nor, loff_t to, size_t len, + const u_char *buf) +{ + struct phytium_qspi_flash *flash = nor->priv; + struct device *dev = flash->qspi->dev; + struct phytium_qspi *qspi = flash->qspi; + u32 cmd = nor->program_opcode; + u32 addr = (u32)to; + int i; + u_char tmp[8] = {0}; + size_t mask = 0x03; + + addr = addr + flash->cs * flash->fsize; + dev_dbg(dev, "write(%#.2x): buf:%p to:%#.8x len:%#zx\n", + nor->program_opcode, buf, addr, len); + + if (addr & 0x03) { + dev_err(dev, "Addr not four-byte aligned!\n"); + return -EINVAL; + } + + cmd = cmd << QSPI_WR_CFG_WR_CMD_SHIFT; + cmd |= BIT(QSPI_WR_CFG_WR_MODE_SHIFT); + cmd |= PHYTIUM_CMD_SCK_SEL << QSPI_CMD_PORT_SCK_SEL_SHIFT; + + switch (nor->program_opcode) { + case CMD_PP: + case CMD_QPP: + cmd &= ~QSPI_WR_CFG_WR_ADDR_SEL_MASK; + break; + case CMD_4PP: + case CMD_4QPP: + cmd |= BIT(QSPI_WR_CFG_WR_ADDR_SEL_SHIFT); + break; + default: + dev_err(qspi->dev, "Not support program command:%#x\n", + nor->erase_opcode); + return -EINVAL; + } + + dev_dbg(qspi->dev, "write cmd:%x\n", cmd); + writel_relaxed(cmd, qspi->io_base + QSPI_WR_CFG_REG); + + for (i = 0; i < len/4; i++) { + writel_relaxed(*(u32 *)(buf + 4*i), qspi->mm_base + addr + 4*i); + } + + if (len & mask) { + addr = addr + (len & ~mask); + phytium_qspi_read_tmp(qspi, flash->read_cmd, addr, 4, &tmp[0]); + memcpy(tmp, buf + (len & ~mask), len & mask); + writel_relaxed(*(u32 *)(tmp), qspi->mm_base + addr); + } + + writel_relaxed(QSPI_FLUSH_EN, qspi->io_base + QSPI_FLUSH_REG); + + phytium_qspi_wait_cmd(qspi, flash); + + return len; +} + +static int phytium_qspi_erase(struct spi_nor *nor, loff_t offs) +{ + struct phytium_qspi_flash *flash = nor->priv; + struct device *dev = flash->qspi->dev; + struct phytium_qspi *qspi = flash->qspi; + u32 cmd = nor->erase_opcode; + u32 addr = (u32)offs; + + dev_dbg(dev, "erase(%#.2x):offs:%#x\n", nor->erase_opcode, (u32)offs); + + phytium_qspi_write_enable(qspi, flash); + cmd = cmd << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= PHYTIUM_SCK_SEL << QSPI_CMD_PORT_SCK_SEL_SHIFT; + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + + /* s25fl256s1 not supoort D8, DC, 20, 21 */ + switch (nor->erase_opcode) { + case CMD_SE: + cmd &= ~QSPI_CMD_PORT_SEL_MASK; + cmd |= BIT(QSPI_CMD_PORT_CMD_ADDR_SHIFT); + writel_relaxed(addr, qspi->io_base + QSPI_ADDR_PORT_REG); + break; + case CMD_4SE: + cmd |= BIT(QSPI_CMD_PORT_SEL_SHIFT); + cmd |= BIT(QSPI_CMD_PORT_CMD_ADDR_SHIFT); + writel_relaxed(addr, qspi->io_base + QSPI_ADDR_PORT_REG); + break; + case CMD_P4E: + cmd &= ~QSPI_CMD_PORT_SEL_MASK; + cmd |= BIT(QSPI_CMD_PORT_CMD_ADDR_SHIFT); + writel_relaxed(addr, qspi->io_base + QSPI_ADDR_PORT_REG); + break; + case CMD_4P4E: + cmd |= BIT(QSPI_CMD_PORT_SEL_SHIFT); + cmd |= BIT(QSPI_CMD_PORT_CMD_ADDR_SHIFT); + writel_relaxed(addr, qspi->io_base + QSPI_ADDR_PORT_REG); + break; + case CMD_BE: + cmd &= ~QSPI_CMD_PORT_SEL_MASK; + break; + case CMD_4BE: + cmd |= BIT(QSPI_CMD_PORT_SEL_SHIFT); + break; + default: + dev_err(qspi->dev, "Not support erase command:%#x\n", + nor->erase_opcode); + return -EINVAL; + } + + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + phytium_qspi_cmd_enable(qspi); + phytium_qspi_wait_cmd(qspi, flash); + + return 0; +} + +static int phytium_qspi_prep(struct spi_nor *nor, enum spi_nor_ops ops) +{ + struct phytium_qspi_flash *flash = nor->priv; + struct phytium_qspi *qspi = flash->qspi; + + mutex_lock(&qspi->lock); + return 0; +} + +static void phytium_qspi_unprep(struct spi_nor *nor, enum spi_nor_ops ops) +{ + struct phytium_qspi_flash *flash = nor->priv; + struct phytium_qspi *qspi = flash->qspi; + + mutex_unlock(&qspi->lock); +} + +static int phytium_qspi_get_flash_size(struct phytium_qspi *qspi, u32 size) +{ + int ret = 0; + u32 value; + + switch (size) { + case SZ_4M: + value = 0; + break; + case SZ_8M: + value = 1; + break; + case SZ_16M: + value = 2; + break; + case SZ_32M: + value = 3; + break; + case SZ_64M: + value = 4; + break; + case SZ_128M: + value = 5; + break; + case SZ_256M: + value = 6; + break; + case SZ_512M: + value = 7; + break; + default: + value = 0; + + ret = -EINVAL; + return ret; + } + + return value; +} +static int phytium_qspi_flash_setup(struct phytium_qspi *qspi, + struct fwnode_handle *np) +{ + struct spi_nor_hwcaps hwcaps = { + .mask = SNOR_HWCAPS_READ | + SNOR_HWCAPS_READ_FAST | + SNOR_HWCAPS_PP, + }; + u32 width, presc; + u32 cs_num = 0; + u32 max_rate = 0; + u32 clk_div = 0; + u32 flash_cap = 0; + u32 addr_width = PHYTIUM_QSPI_1_1_1; + struct phytium_qspi_flash *flash; + struct mtd_info *mtd; + int ret; + + fwnode_property_read_u32(np, "reg", &cs_num); + if (cs_num >= PHYTIUM_MAX_NORCHIP) + return -EINVAL; + + fwnode_property_read_u32(np, "spi-max-frequency", &max_rate); + if (!max_rate) + return -EINVAL; + + fwnode_property_read_u32(np, "spi-clk-div", &clk_div); + if (!clk_div) + clk_div = PHYTIUM_SCK_SEL; + + if (clk_div < 4) + return -EINVAL; + + presc = DIV_ROUND_UP(qspi->clk_rate, max_rate) - 1; + + fwnode_property_read_u32(np, "spi-rx-bus-width", &width); + if (!width) + width = 1; + + if (width == 4) { + hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4; + addr_width = PHYTIUM_QSPI_1_1_4; + } else if (width == 2) { + hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2; + addr_width = PHYTIUM_QSPI_1_1_2; + } else if (width != 1) + return -EINVAL; + + flash = &qspi->flash[cs_num]; + flash->qspi = qspi; + flash->cs = cs_num; + flash->presc = presc; + flash->clk_div = clk_div; + flash->addr_width = addr_width; + + flash->nor.dev = qspi->dev; + if (qspi->dev->of_node) + spi_nor_set_flash_node(&flash->nor, qspi->dev->of_node); + flash->nor.priv = flash; + mtd = &flash->nor.mtd; + + flash->nor.read = phytium_qspi_read; + flash->nor.write = phytium_qspi_write; + flash->nor.erase = phytium_qspi_erase; + flash->nor.read_reg = phytium_qspi_read_reg; + flash->nor.write_reg = phytium_qspi_write_reg; + flash->nor.prepare = phytium_qspi_prep; + flash->nor.unprepare = phytium_qspi_unprep; + + ret = spi_nor_scan(&flash->nor, NULL, &hwcaps); + if (ret) { + dev_err(qspi->dev, "device scan failed\n"); + return ret; + } + + flash->fsize = mtd->size; + flash->prefetch_limit = mtd->size - PHYTIUM_QSPI_FIFO_SZ; + + ret = phytium_qspi_get_flash_size(flash->qspi, mtd->size); + if (ret < 0) { + dev_err(qspi->dev, "flash size invalid\n"); + return ret; + } + + flash_cap = cs_num << QSPI_FLASH_CAP_NUM_SHIFT; + flash_cap |= ret; + writel_relaxed(flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + + flash->read_mode = PHYTIUM_FMODE_MM; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(qspi->dev, "mtd device parse failed\n"); + return ret; + } + + flash->registered = true; + + dev_dbg(qspi->dev, "read mm:%s %px cs:%d bus:%d clk-div:%d\n", + flash->read_mode == PHYTIUM_FMODE_MM ? "yes" : "no", + qspi->mm_base, cs_num, width, clk_div); + + dev_dbg(qspi->dev, "mtd->size:%llx, mtd->erasesize:%x, fsize:%x\n", + mtd->size, mtd->erasesize, flash->fsize); + + return 0; +} + +static void phytium_qspi_mtd_free(struct phytium_qspi *qspi) +{ + int i; + + for (i = 0; i < PHYTIUM_MAX_NORCHIP; i++) + if (qspi->flash[i].registered) + mtd_device_unregister(&qspi->flash[i].nor.mtd); +} + +static ssize_t clk_div_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct phytium_qspi *qspi = dev_get_drvdata(dev); + struct phytium_qspi_flash *flash = &qspi->flash[0]; + + return sprintf(buf, "Flash 0 clk-div: %d\n", flash->clk_div); +} + +static ssize_t clk_div_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct phytium_qspi *qspi = dev_get_drvdata(dev); + struct phytium_qspi_flash *flash = &qspi->flash[0]; + long value; + char *token; + ssize_t status; + + token = strsep ((char **)&buf, " "); + if (!token) + return -EINVAL; + + status = kstrtol(token, 0, &value); + if (status) + return status; + + flash->clk_div = (u8)value; + + return size; +} +static DEVICE_ATTR_RW(clk_div); + +static struct attribute *phytium_qspi_attrs[] = { + &dev_attr_clk_div.attr, + NULL, +}; + +static struct attribute_group phytium_qspi_attr_group = { + .attrs = phytium_qspi_attrs, +}; + +static int phytium_qspi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fwnode_handle *flash_np; + struct phytium_qspi *qspi; + struct resource *res; + int ret; + + qspi = devm_kzalloc(dev, sizeof(*qspi), GFP_KERNEL); + if (!qspi) + return -ENOMEM; + + qspi->nor_num = device_get_child_node_count(dev); + if (!qspi->nor_num || qspi->nor_num > PHYTIUM_MAX_NORCHIP) + return -ENODEV; + + 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); + } + qspi->io_base = devm_ioremap_resource(dev, res); + + if (IS_ERR(qspi->io_base)) + return PTR_ERR(qspi->io_base); + + + 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); + + qspi->mm_base = devm_ioremap_resource(dev, res); + if (IS_ERR(qspi->mm_base)) + return PTR_ERR(qspi->mm_base); + + + qspi->mm_size = resource_size(res); + + if (dev->of_node) { + qspi->clk = devm_clk_get(dev, NULL); + if (IS_ERR(qspi->clk)) + return PTR_ERR(qspi->clk); + + qspi->clk_rate = clk_get_rate(qspi->clk); + if (!qspi->clk_rate) + return -EINVAL; + + ret = clk_prepare_enable(qspi->clk); + if (ret) { + dev_err(dev, "can not enable the clock\n"); + return ret; + } + } + else if (has_acpi_companion(dev)) { /* ACPI table not pass clk rate */ + qspi->clk_rate = 50000000; + } + + qspi->dev = dev; + platform_set_drvdata(pdev, qspi); + mutex_init(&qspi->lock); + spin_lock_init(&qspi->spinlock); + + fwnode_for_each_available_child_node(dev_fwnode(dev), flash_np) { + ret = phytium_qspi_flash_setup(qspi, flash_np); + if (ret) { + dev_err(dev, "unable to setup flash chip\n"); + goto err_flash; + } + } + + ret = sysfs_create_group(&qspi->dev->kobj, &phytium_qspi_attr_group); + if (ret) { + dev_err(dev, "unable to create sysfs\n"); + goto err_flash; + } + + return 0; + +err_flash: + mutex_destroy(&qspi->lock); + phytium_qspi_mtd_free(qspi); + + clk_disable_unprepare(qspi->clk); + return ret; +} + +static int phytium_qspi_remove(struct platform_device *pdev) +{ + struct phytium_qspi *qspi = platform_get_drvdata(pdev); + + sysfs_remove_group(&qspi->dev->kobj, &phytium_qspi_attr_group); + + phytium_qspi_mtd_free(qspi); + mutex_destroy(&qspi->lock); + + clk_disable_unprepare(qspi->clk); + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_qspi_acpi_ids[] = { + { "PHYT0011", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(acpi, phytium_qspi_acpi_ids); +#endif + +static const struct of_device_id phytium_qspi_match[] = { + {.compatible = "phytium,qspi"}, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_qspi_match); + +static struct platform_driver phytium_qspi_driver = { + .probe = phytium_qspi_probe, + .remove = phytium_qspi_remove, + .driver = { + .name = "phytium-quadspi", + .of_match_table = phytium_qspi_match, + .acpi_match_table = ACPI_PTR(phytium_qspi_acpi_ids), + }, +}; + +module_platform_driver(phytium_qspi_driver); + +MODULE_AUTHOR("Mingshuai Zhu "); +MODULE_DESCRIPTION("Phytium QuadSPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index ff641c06003a8fb81f8ee6264a8c9c97a24c868d..b6bb1f9350cd574af88fca6fb0bd4018d58f2b6c 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -988,6 +988,10 @@ static const struct flash_info spi_nor_ids[] = { { "at45db081d", INFO(0x1f2500, 0, 64 * 1024, 16, SECT_4K) }, + /* boya */ + { "by25q64as", INFO(0x684017, 0, 64 * 1024, 128, SECT_4K) }, + { "by25q128as", INFO(0x684018, 0, 64 * 1024, 256, SECT_4K) }, + /* EON -- en25xxx */ { "en25f32", INFO(0x1c3116, 0, 64 * 1024, 64, SECT_4K) }, { "en25p32", INFO(0x1c2016, 0, 64 * 1024, 64, 0) }, @@ -1044,6 +1048,11 @@ static const struct flash_info spi_nor_ids[] = { SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) }, + { + "gd25q128e", INFO(0xc86018, 0, 64 * 1024, 256, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, { "gd25q256", INFO(0xc84019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | @@ -1263,6 +1272,7 @@ static const struct flash_info spi_nor_ids[] = { /* XMC (Wuhan Xinxin Semiconductor Manufacturing Corp.) */ { "XM25QH64A", INFO(0x207017, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "XM25QH128A", INFO(0x207018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "XM25QH128B", INFO(0x205018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { }, }; diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index 7cdd0cead693dc5d78565f5fd988c6c25c7a2740..ee84f63e6e3138531940c24431dd2eb4fbab97ac 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -158,6 +158,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 44922bf29b6a0653ea1eda6ad96c30b3a645fbaa..be9c70e77df317e95a7d5cc622ddcd4bb0b0f1e9 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -33,5 +33,5 @@ obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o obj-$(CONFIG_CAN_XILINXCAN) += xilinx_can.o obj-$(CONFIG_PCH_CAN) += pch_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..e03e0c0d41d8a73b0fba7a925d3ea27e97809130 --- /dev/null +++ b/drivers/net/can/phytium/phytium_can.c @@ -0,0 +1,1144 @@ +// 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_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_RFIE | 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_ERR_CNT) & ERR_CNT_REC; + bec->txerr = (phytium_can_read(cdev, CAN_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_dlc2len((dlc & CANFD_ID2_DLC_MASK) >> CANFD_ID2_DLC_OFF); + } else { + /* CAN extended frame */ + cf->len = get_can_dlc((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_dlc2len((id & CANFD_ID1_DLC_MASK) >> CANFD_ID1_DLC_OFF); + } else { + /* CAN extended frame */ + cf->len = get_can_dlc((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; + + 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)) { + pkts += phytium_can_read_fifo(dev); + quota--; + rxfs = phytium_can_read(cdev, CAN_FIFO_CNT) & FIFO_CNT_RFN; + netdev_dbg(dev, "Next received %d frame again.\n", rxfs); + } + + if (pkts) + can_led_event(dev, CAN_LED_EVENT_RX); + + return pkts; +} + +static int phytium_can_rx_handler(struct net_device *dev, int quota) +{ + int work_done = 0; + int rx_work_or_err; + + /* Handle RX IRQ */ + 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; + + 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; + + 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); + phytium_can_enable_all_interrupts(cdev); + } + + 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; + + data_len = can_len2dlc(cf->len); + cdev->tx_skb = NULL; + + phytium_can_clr_reg_bits(cdev, CAN_CTRL, CTRL_XFER); + + /* 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; + can_put_echo_skb(skb, dev, cdev->tx_head % cdev->tx_max); + cdev->tx_head++; + + netif_stop_queue(dev); + /* triggers tranmission */ + phytium_can_set_reg_bits(cdev, CAN_CTRL, CTRL_TXREQ | CTRL_XFER); + + netdev_dbg(dev, "Trigger send message!\n"); + + return; +} + +static netdev_tx_t phytium_can_tx_handler(struct phytium_can_dev *cdev) +{ + struct net_device *dev = cdev->net; + u32 tx_fifo_used; + + /* Check if the TX buffer is full */ + tx_fifo_used = (phytium_can_read(cdev, CAN_FIFO_CNT) & FIFO_CNT_TFN) >> 16; + if (tx_fifo_used == cdev->tx_max) { + netif_stop_queue(dev); + netdev_err(dev, "BUG!, TX FIFO full when queue awake!\n"); + return NETDEV_TX_BUSY; + } + + if (cdev->tx_head == cdev->tx_tail) { + cdev->tx_head = 0; + cdev->tx_tail = 0; + } + + phytium_can_write_frame(cdev); + + 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; + + while ((cdev->tx_head - cdev->tx_tail > 0) && (isr & INTR_TEIS)) { + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_TEIC | INTR_REIC); + can_get_echo_skb(ndev, cdev->tx_tail % cdev->tx_max); + cdev->tx_tail++; + stats->tx_packets++; + isr = (phytium_can_read(cdev, CAN_INTR) & INTR_STATUS_MASK); + } + + netdev_dbg(ndev, "Finish transform packets %lu\n", stats->tx_packets); + netdev_dbg(ndev, "\n-------------------\n"); + can_led_event(ndev, CAN_LED_EVENT_TX); + netif_wake_queue(ndev); +} + +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_ERR_CNT) & ERR_CNT_REC; + txerr = ((phytium_can_read(cdev, CAN_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; + + /* Check for FIFO full interrupt and alarm */ + if ((isr & INTR_RFIS)) { + netdev_dbg(dev, "rx_fifo is full!.\n"); + 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 the type of error interrupt and Processing it */ + if (isr & (INTR_EIS | INTR_RFIS | INTR_BOIS)) { + phytium_can_clr_reg_bits(cdev, CAN_INTR, (INTR_EIE + | INTR_RFIE | INTR_BOIE)); + phytium_can_err_interrupt(dev, isr); + phytium_can_set_reg_bits(cdev, CAN_INTR, (INTR_EIC + | INTR_RFIC | INTR_BOIC)); + phytium_can_set_reg_bits(cdev, CAN_INTR, INTR_EIE | INTR_BOIE); + return IRQ_HANDLED; + } + + /* Check for Tx interrupt and Processing it */ + if (isr & INTR_TEIS) { + phytium_can_tx_interrupt(dev, isr); + } + + /* Check for the type of receive interrupt and Processing it */ + if (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); + } + + 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); + + /* 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); + 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; + + /* Start clock */ + ret = pm_runtime_resume(cdev->dev); + if (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); + + can_led_event(dev, CAN_LED_EVENT_OPEN); + napi_enable(&cdev->napi); + netif_start_queue(dev); + + return 0; + +fail: + 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); + can_led_event(dev, CAN_LED_EVENT_STOP); + + 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, 64); + + 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; + } + + 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 = pm_runtime_resume(cdev->dev); + if (ret) + return 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; + } + + devm_can_led_init(cdev->net); + + 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 v2"); +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..3f125548c4a6f1dc20d90076102b8a49f1b1d96e --- /dev/null +++ b/drivers/net/can/phytium/phytium_can.h @@ -0,0 +1,66 @@ +/* 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 + +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; + unsigned int tx_head; + unsigned int tx_tail; + unsigned int tx_max; + 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; + + int fdmode; + u32 isr; + u32 tx_fifo_depth; + + void __iomem *base; +}; + +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..401ec7e444a035a717dc2f65c39d87947fce4d63 --- /dev/null +++ b/drivers/net/can/phytium/phytium_can_pci.c @@ -0,0 +1,135 @@ +// 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 = 600000000, + .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); + + pm_runtime_enable(cdev->dev); + ret = phytium_can_register(cdev); + if (ret) + goto err; + + return 0; +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); + + 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 *mode; + + 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; + } + ret = fwnode_property_read_string(dev_fwnode(&pdev->dev), "mode-select", &mode); + if (ret < 0) { + dev_info(&pdev->dev, "get mode-select ret: %d\n", ret); + } + else { + if (strncmp("canfd", mode, strlen("canfd")) == 0) { + dev_info(&pdev->dev, "use mode-select: canfd\n"); + devtype = &phytium_canfd_data; + } + } + } + + cdev->tx_fifo_depth = tx_fifo_depth; + cdev->tx_head = 0; + cdev->tx_tail = 0; + cdev->tx_max = 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 v2"); +MODULE_DESCRIPTION("Phytium CAN driver for IO Mapped controllers"); diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index efb44d5ab02156282f477fde76e8ca3bf4675f1b..1968936e2268771c502eb11cbd908c7e740355a7 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -83,6 +83,7 @@ #define GEM_USRIO 0x000c /* User IO */ #define GEM_DMACFG 0x0010 /* DMA Configuration */ #define GEM_JML 0x0048 /* Jumbo Max Length */ +#define GEM_HS_MAC_CONFIG 0x0050 /* GEM high speed config */ #define GEM_HRB 0x0080 /* Hash Bottom */ #define GEM_HRT 0x0084 /* Hash Top */ #define GEM_SA1B 0x0088 /* Specific1 Bottom */ @@ -167,6 +168,8 @@ #define GEM_DCFG7 0x0298 /* Design Config 7 */ #define GEM_DCFG8 0x029C /* Design Config 8 */ #define GEM_DCFG10 0x02A4 /* Design Config 10 */ +#define GEM_USX_CONTROL 0x0A80 /* High speed PCS control register */ +#define GEM_USX_STATUS 0x0A88 /* High speed PCS status register */ #define GEM_TXBDCTRL 0x04cc /* TX Buffer Descriptor control register */ #define GEM_RXBDCTRL 0x04d0 /* RX Buffer Descriptor control register */ @@ -202,6 +205,31 @@ #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 + /* Bitfields in NCR */ #define MACB_LB_OFFSET 0 /* reserved */ #define MACB_LB_SIZE 1 @@ -458,6 +486,10 @@ #define MACB_REV_OFFSET 0 #define MACB_REV_SIZE 16 +/* Bitfield in HS_MAC_CONFIG */ +#define GEM_HS_MAC_SPEED_OFFSET 0 +#define GEM_HS_MAC_SPEED_SIZE 3 + /* Bitfields in DCFG1. */ #define GEM_IRQCOR_OFFSET 23 #define GEM_IRQCOR_SIZE 1 @@ -497,13 +529,27 @@ #define GEM_RXBD_RDBUFF_OFFSET 8 #define GEM_RXBD_RDBUFF_SIZE 4 +/* Bitfields in USX_CONTROL. */ +#define GEM_USX_CTRL_SPEED_OFFSET 14 +#define GEM_USX_CTRL_SPEED_SIZE 3 +#define GEM_SERDES_RATE_OFFSET 12 +#define GEM_SERDES_RATE_SIZE 2 +#define GEM_RX_SCR_BYPASS_OFFSET 9 +#define GEM_RX_SCR_BYPASS_SIZE 1 +#define GEM_TX_SCR_BYPASS_OFFSET 8 +#define GEM_TX_SCR_BYPASS_SIZE 1 +#define GEM_TX_EN_OFFSET 1 +#define GEM_TX_EN_SIZE 1 +#define GEM_SIGNAL_OK_OFFSET 0 +#define GEM_SIGNAL_OK_SIZE 1 + /* Bitfields in TISUBN */ #define GEM_SUBNSINCR_OFFSET 0 #define GEM_SUBNSINCRL_OFFSET 24 -#define GEM_SUBNSINCRL_SIZE 8 +#define GEM_SUBNSINCRL_SIZE 8 #define GEM_SUBNSINCRH_OFFSET 0 -#define GEM_SUBNSINCRH_SIZE 16 -#define GEM_SUBNSINCR_SIZE 24 +#define GEM_SUBNSINCRH_SIZE 16 +#define GEM_SUBNSINCR_SIZE 24 /* Bitfields in TI */ #define GEM_NSINCR_OFFSET 0 @@ -631,6 +677,8 @@ #define GEM_CLK_DIV48 3 #define GEM_CLK_DIV64 4 #define GEM_CLK_DIV96 5 +#define GEM_CLK_DIV128 6 +#define GEM_CLK_DIV224 7 /* Constants for MAN register */ #define MACB_MAN_SOF 1 @@ -652,6 +700,7 @@ #define MACB_CAPS_GIGABIT_MODE_AVAILABLE 0x20000000 #define MACB_CAPS_SG_DISABLED 0x40000000 #define MACB_CAPS_MACB_IS_GEM 0x80000000 +#define MACB_CAPS_SEL_CLK_HW 0x00001000 /* LSO settings */ #define MACB_LSO_UFO_ENABLE 0x01 @@ -1087,7 +1136,7 @@ struct macb_config { unsigned int dma_burst_length; int (*clk_init)(struct platform_device *pdev, struct clk **pclk, struct clk **hclk, struct clk **tx_clk, - struct clk **rx_clk); + struct clk **rx_clk, struct clk **tsu_clk); int (*init)(struct platform_device *pdev); int jumbo_max_len; }; @@ -1167,7 +1216,9 @@ struct macb { struct clk *hclk; struct clk *tx_clk; 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; @@ -1180,6 +1231,8 @@ struct macb { int link; int speed; int duplex; + int use_ncsi; + int force_phy_mode; u32 caps; unsigned int dma_burst_length; diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 50331b202f73ba794e1427657eb0d4c21304f916..b074e175650447aee519a5d171323b12f03ff0e4 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include "macb.h" #define MACB_RX_BUFFER_SIZE 128 @@ -75,6 +77,13 @@ #define GEM_MTU_MIN_SIZE ETH_MIN_MTU #define MACB_NETIF_LSO NETIF_F_TSO +#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_10G 1 + #define MACB_WOL_HAS_MAGIC_PACKET (0x1 << 0) #define MACB_WOL_ENABLED (0x1 << 1) @@ -405,12 +414,127 @@ static void macb_set_tx_clk(struct clk *clk, int speed, struct net_device *dev) netdev_err(dev, "adjusting tx_clk failed.\n"); } +static int phytium_gem_sel_clk(struct macb *bp) +{ + int speed = 0; + + if (bp->phy_interface == PHY_INTERFACE_MODE_USXGMII) { + if (bp->speed == SPEED_10000) { + gem_writel(bp, SRC_SEL_LN, 0x1); /*0x1c04*/ + 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*/ + speed = HS_SPEED_10000M; + } + } else if (bp->phy_interface == PHY_INTERFACE_MODE_SGMII) { + if (bp->speed == SPEED_2500) { + 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*/ + speed = HS_SPEED_2500M; + } else if (bp->speed == SPEED_1000) { + 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*/ + speed = HS_SPEED_1000M; + } else if (bp->speed == SPEED_100 || bp->speed == SPEED_10) { + 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*/ + speed = HS_SPEED_100M; + } + } else if (bp->phy_interface == PHY_INTERFACE_MODE_RGMII) { + if (bp->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*/ + speed = HS_SPEED_1000M; + } else if (bp->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*/ + speed = HS_SPEED_100M; + } 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*/ + speed = HS_SPEED_100M; + } + } else if (bp->phy_interface == PHY_INTERFACE_MODE_RMII) { + speed = HS_SPEED_100M; + gem_writel(bp, RX_CLK_SEL5, 0x1); /*0x1c48*/ + } + + /*HS_MAC_CONFIG(0x0050) provide rate to the external*/ + gem_writel(bp, HS_MAC_CONFIG, GEM_BFINS(HS_MAC_SPEED, speed, + gem_readl(bp, HS_MAC_CONFIG))); + + return 0; +} + static void macb_handle_link_change(struct net_device *dev) { struct macb *bp = netdev_priv(dev); struct phy_device *phydev = dev->phydev; unsigned long flags; - int status_change = 0; + int err, status_change = 0; spin_lock_irqsave(&bp->lock, flags); @@ -459,12 +583,25 @@ static void macb_handle_link_change(struct net_device *dev) */ macb_set_tx_clk(bp->tx_clk, phydev->speed, dev); + /* phytium need hwclock */ + if (bp->caps & MACB_CAPS_SEL_CLK_HW) + phytium_gem_sel_clk(bp); + netif_carrier_on(dev); netdev_info(dev, "link up (%d/%s)\n", phydev->speed, phydev->duplex == DUPLEX_FULL ? "Full" : "Half"); + + if (bp->use_ncsi) { + err = ncsi_start_dev(bp->ndev); + if (err) + netdev_err(dev, "Ncsi start dev failed (error %d)\n", err); + } } else { + if (bp->use_ncsi) + ncsi_stop_dev(bp->ndev); + netif_carrier_off(dev); netdev_info(dev, "link down\n"); } @@ -1775,12 +1912,8 @@ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *ndev) *skb = nskb; } - if (padlen) { - if (padlen >= ETH_FCS_LEN) - skb_put_zero(*skb, padlen - ETH_FCS_LEN); - else - skb_trim(*skb, ETH_FCS_LEN - padlen); - } + if (padlen > ETH_FCS_LEN) + skb_put_zero(*skb, padlen - ETH_FCS_LEN); add_fcs: /* set FCS to packet */ @@ -2137,6 +2270,9 @@ static u32 gem_mdc_clk_div(struct macb *bp) u32 config; unsigned long pclk_hz = clk_get_rate(bp->pclk); + if (!pclk_hz) + pclk_hz = 250000000; + if (pclk_hz <= 20000000) config = GEM_BF(CLK, GEM_CLK_DIV8); else if (pclk_hz <= 40000000) @@ -2247,6 +2383,31 @@ static void macb_configure_dma(struct macb *bp) } } +static int macb_usx_pcs_config(struct macb *bp) +{ + gem_writel(bp, USX_CONTROL, gem_readl(bp, USX_CONTROL) | + GEM_BIT(SIGNAL_OK)); + + return 0; +} + +static void macb_usx_pcs_link_up(struct macb *bp) +{ + u32 config; + + config = gem_readl(bp, USX_CONTROL); + if (bp->speed == SPEED_10000) { + gem_writel(bp, HS_MAC_CONFIG, GEM_BFINS(HS_MAC_SPEED, HS_SPEED_10000M, + gem_readl(bp, HS_MAC_CONFIG))); + config = GEM_BFINS(SERDES_RATE, MACB_SERDES_RATE_10G, config); + config = GEM_BFINS(USX_CTRL_SPEED, HS_SPEED_10000M, config); + } + + config &= ~(GEM_BIT(TX_SCR_BYPASS) | GEM_BIT(RX_SCR_BYPASS)); + config |= GEM_BIT(TX_EN); + gem_writel(bp, USX_CONTROL, config); +} + static void macb_init_hw(struct macb *bp) { struct macb_queue *queue; @@ -2277,8 +2438,16 @@ static void macb_init_hw(struct macb *bp) macb_writel(bp, NCFGR, config); if ((bp->caps & MACB_CAPS_JUMBO) && bp->jumbo_max_len) gem_writel(bp, JML, bp->jumbo_max_len); - bp->speed = SPEED_10; - bp->duplex = DUPLEX_HALF; + + if (bp->phy_interface == PHY_INTERFACE_MODE_USXGMII) { + macb_usx_pcs_config(bp); + if (bp->link) + macb_usx_pcs_link_up(bp); + } else { + bp->speed = SPEED_10; + bp->duplex = DUPLEX_HALF; + } + bp->rx_frm_len_mask = MACB_RX_FRMLEN_MASK; if (bp->caps & MACB_CAPS_JUMBO) bp->rx_frm_len_mask = MACB_RX_JFRMLEN_MASK; @@ -2431,7 +2600,10 @@ static void macb_set_rx_mode(struct net_device *dev) static int macb_open(struct net_device *dev) { struct macb *bp = netdev_priv(dev); - size_t bufsz = dev->mtu + ETH_HLEN + ETH_FCS_LEN + NET_IP_ALIGN; + /* adjust bufsz to be at least the size of a standard frame, + * to fix rx error when set small size mtu. + */ + size_t bufsz = (dev->mtu < ETH_DATA_LEN ? ETH_DATA_LEN : dev->mtu) + ETH_HLEN + ETH_FCS_LEN + NET_IP_ALIGN; struct macb_queue *queue; unsigned int q; int err; @@ -2441,10 +2613,6 @@ static int macb_open(struct net_device *dev) /* carrier starts down */ netif_carrier_off(dev); - /* if the phy is not yet register, retry later*/ - if (!dev->phydev) - return -EAGAIN; - /* RX buffers initialization */ macb_init_rx_buffer_size(bp, bufsz); @@ -2461,8 +2629,12 @@ static int macb_open(struct net_device *dev) bp->macbgem_ops.mog_init_rings(bp); macb_init_hw(bp); - /* schedule a link state check */ - phy_start(dev->phydev); + if (dev->phydev) + /* schedule a link state check */ + phy_start(dev->phydev); + + if (bp->link) + netif_carrier_on(dev); netif_tx_start_all_queues(dev); @@ -2803,14 +2975,17 @@ static unsigned int gem_get_tsu_rate(struct macb *bp) unsigned int tsu_rate; tsu_clk = devm_clk_get(&bp->pdev->dev, "tsu_clk"); - if (!IS_ERR(tsu_clk)) + if (!IS_ERR_OR_NULL(tsu_clk)) { tsu_rate = clk_get_rate(tsu_clk); /* try pclk instead */ - else if (!IS_ERR(bp->pclk)) { + } else if (!IS_ERR_OR_NULL(bp->pclk)) { tsu_clk = bp->pclk; tsu_rate = clk_get_rate(tsu_clk); - } else + } else if (has_acpi_companion(&bp->pdev->dev)) { + tsu_rate = 250000000; + } else { return -ENOTSUPP; + } return tsu_rate; } @@ -3280,6 +3455,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 @@ -3348,11 +3525,20 @@ static void macb_probe_queues(void __iomem *mem, static int macb_clk_init(struct platform_device *pdev, struct clk **pclk, struct clk **hclk, struct clk **tx_clk, - struct clk **rx_clk) + struct clk **rx_clk, struct clk **tsu_clk) { struct macb_platform_data *pdata; int err; - + /* clk get not support ACPI */ + if (has_acpi_companion(&pdev->dev)) { + dev_info(&pdev->dev, "ACPI skip get macb clk\n"); + *pclk = NULL; + *hclk = NULL; + *tx_clk = NULL; + *rx_clk = NULL; + *tsu_clk = NULL; + return 0; + } pdata = dev_get_platdata(&pdev->dev); if (pdata) { *pclk = pdata->pclk; @@ -3388,6 +3574,10 @@ static int macb_clk_init(struct platform_device *pdev, struct clk **pclk, if (IS_ERR(*rx_clk)) *rx_clk = NULL; + *tsu_clk = devm_clk_get(&pdev->dev, "tsu_clk"); + if (IS_ERR(*tsu_clk)) + *tsu_clk = NULL; + err = clk_prepare_enable(*pclk); if (err) { dev_err(&pdev->dev, "failed to enable pclk (%d)\n", err); @@ -3412,8 +3602,17 @@ static int macb_clk_init(struct platform_device *pdev, struct clk **pclk, goto err_disable_txclk; } + err = clk_prepare_enable(*tsu_clk); + if (err) { + dev_err(&pdev->dev, "failed to enable tsu_clk (%u)\n", err); + goto err_disable_rxclk; + } + return 0; +err_disable_rxclk: + clk_disable_unprepare(*rx_clk); + err_disable_txclk: clk_disable_unprepare(*tx_clk); @@ -3527,6 +3726,12 @@ static int macb_init(struct platform_device *pdev) /* Checksum offload is only available on gem with packet buffer */ if (macb_is_gem(bp) && !(bp->caps & MACB_CAPS_FIFO_MODE)) dev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; + + if (bp->use_ncsi) { + dev->hw_features &= ~(NETIF_F_HW_CSUM | NETIF_F_RXCSUM); + dev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER; + } + if (bp->caps & MACB_CAPS_SG_DISABLED) dev->hw_features &= ~NETIF_F_SG; dev->features = dev->hw_features; @@ -3864,13 +4069,14 @@ static const struct net_device_ops at91ether_netdev_ops = { static int at91ether_clk_init(struct platform_device *pdev, struct clk **pclk, struct clk **hclk, struct clk **tx_clk, - struct clk **rx_clk) + struct clk **rx_clk, struct clk **tsu_clk) { int err; *hclk = NULL; *tx_clk = NULL; *rx_clk = NULL; + *tsu_clk = NULL; *pclk = devm_clk_get(&pdev->dev, "ether_clk"); if (IS_ERR(*pclk)) @@ -3986,6 +4192,18 @@ static const struct macb_config zynq_config = { .init = macb_init, }; +static const struct macb_config phytium_config = { + .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | + MACB_CAPS_JUMBO | + MACB_CAPS_GEM_HAS_PTP | + MACB_CAPS_BD_RD_PREFETCH | + MACB_CAPS_SEL_CLK_HW, + .dma_burst_length = 16, + .clk_init = macb_clk_init, + .init = macb_init, + .jumbo_max_len = 16360, +}; + static const struct of_device_id macb_dt_ids[] = { { .compatible = "cdns,at32ap7000-macb" }, { .compatible = "cdns,at91sam9260-macb", .data = &at91sam9260_config }, @@ -4001,11 +4219,23 @@ static const struct of_device_id macb_dt_ids[] = { { .compatible = "cdns,emac", .data = &emac_config }, { .compatible = "cdns,zynqmp-gem", .data = &zynqmp_config}, { .compatible = "cdns,zynq-gem", .data = &zynq_config }, + { .compatible = "cdns,phytium-gem", .data = &phytium_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_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 | @@ -4016,19 +4246,46 @@ 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; int (*clk_init)(struct platform_device *, struct clk **, - struct clk **, struct clk **, struct clk **) - = macb_config->clk_init; + struct clk **, struct clk **, struct clk **, + struct clk **) = macb_config->clk_init; int (*init)(struct platform_device *) = macb_config->init; struct device_node *np = pdev->dev.of_node; struct clk *pclk, *hclk = NULL, *tx_clk = NULL, *rx_clk = NULL; + struct clk *tsu_clk = NULL; unsigned int queue_mask, num_queues; struct macb_platform_data *pdata; bool native_io; - struct phy_device *phydev; + struct phy_device *phydev = NULL; struct net_device *dev; struct resource *regs; void __iomem *mem; @@ -4050,9 +4307,18 @@ 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); + err = clk_init(pdev, &pclk, &hclk, &tx_clk, &rx_clk, &tsu_clk); if (err) return err; @@ -4089,11 +4355,12 @@ static int macb_probe(struct platform_device *pdev) bp->hclk = hclk; bp->tx_clk = tx_clk; bp->rx_clk = rx_clk; + bp->tsu_clk = tsu_clk; if (macb_config) bp->jumbo_max_len = macb_config->jumbo_max_len; bp->wol = 0; - if (of_get_property(np, "magic-packet", NULL)) + 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); @@ -4151,7 +4418,7 @@ static int macb_probe(struct platform_device *pdev) } } - err = of_get_phy_mode(np); + err = macb_get_phy_mode(pdev); if (err < 0) { pdata = dev_get_platdata(&pdev->dev); if (pdata && pdata->is_rmii) @@ -4162,16 +4429,40 @@ static int macb_probe(struct platform_device *pdev) bp->phy_interface = err; } + if (bp->phy_interface == PHY_INTERFACE_MODE_USXGMII) { + bp->link = 1; + bp->duplex = 1; + bp->speed = SPEED_10000; + } + /* IP specific init */ err = init(pdev); if (err) goto err_out_free_netdev; - err = macb_mii_init(bp); - if (err) - goto err_out_free_netdev; + if (device_property_read_bool(&pdev->dev, "use-mii")) { + if (device_property_read_bool(&pdev->dev, "force-phy-mode")) { + bp->force_phy_mode = 1; + } + err = macb_mii_init(bp); + if (err) + goto err_out_free_netdev; + phydev = dev->phydev; + } - phydev = dev->phydev; + 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); @@ -4184,7 +4475,8 @@ static int macb_probe(struct platform_device *pdev) tasklet_init(&bp->hresp_err_tasklet, macb_hresp_error_task, (unsigned long)bp); - phy_attached_info(phydev); + if (phydev) + phy_attached_info(phydev); netdev_info(dev, "Cadence %s rev 0x%08x at 0x%08lx irq %d (%pM)\n", macb_is_gem(bp) ? "GEM" : "MACB", macb_readl(bp, MID), @@ -4208,6 +4500,7 @@ static int macb_probe(struct platform_device *pdev) clk_disable_unprepare(hclk); clk_disable_unprepare(pclk); clk_disable_unprepare(rx_clk); + clk_disable_unprepare(tsu_clk); return err; } @@ -4224,11 +4517,15 @@ static int macb_remove(struct platform_device *pdev) bp = netdev_priv(dev); if (dev->phydev) phy_disconnect(dev->phydev); - mdiobus_unregister(bp->mii_bus); + if (device_property_read_bool(&pdev->dev, "use-mii")) + mdiobus_unregister(bp->mii_bus); + if (bp->ndev) + ncsi_unregister_dev(bp->ndev); if (np && of_phy_is_fixed_link(np)) of_phy_deregister_fixed_link(np); dev->phydev = NULL; - mdiobus_free(bp->mii_bus); + if (bp->mii_bus) + mdiobus_free(bp->mii_bus); unregister_netdev(dev); tasklet_kill(&bp->hresp_err_tasklet); @@ -4236,6 +4533,7 @@ static int macb_remove(struct platform_device *pdev) clk_disable_unprepare(bp->hclk); clk_disable_unprepare(bp->pclk); clk_disable_unprepare(bp->rx_clk); + clk_disable_unprepare(bp->tsu_clk); of_node_put(bp->phy_node); free_netdev(dev); } @@ -4261,6 +4559,7 @@ static int __maybe_unused macb_suspend(struct device *dev) clk_disable_unprepare(bp->hclk); clk_disable_unprepare(bp->pclk); clk_disable_unprepare(bp->rx_clk); + clk_disable_unprepare(bp->tsu_clk); } return 0; @@ -4282,6 +4581,7 @@ static int __maybe_unused macb_resume(struct device *dev) clk_prepare_enable(bp->tx_clk); clk_prepare_enable(bp->rx_clk); } + clk_prepare_enable(bp->tsu_clk); netif_device_attach(netdev); @@ -4296,6 +4596,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/stmicro/stmmac/dwmac-generic.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-generic.c index b3365b34cac7c8ae52e3b72ac6bc20f224896b68..99d821c727f1666c53e217ade893202d8701a522 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-generic.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-generic.c @@ -9,6 +9,7 @@ * warranty of any kind, whether express or implied. */ +#include #include #include #include @@ -32,6 +33,12 @@ static int dwmac_generic_probe(struct platform_device *pdev) dev_err(&pdev->dev, "dt configuration failed\n"); return PTR_ERR(plat_dat); } + } else if (has_acpi_companion(&pdev->dev)) { + plat_dat = stmmac_probe_config_acpi(pdev, &stmmac_res.mac); + if (!plat_dat) { + dev_err(&pdev->dev, "acpi configuration failed\n"); + return -EINVAL; + } } else { plat_dat = dev_get_platdata(&pdev->dev); if (!plat_dat) { @@ -85,6 +92,17 @@ static const struct of_device_id dwmac_generic_match[] = { }; MODULE_DEVICE_TABLE(of, dwmac_generic_match); +#ifdef CONFIG_ACPI +static const struct acpi_device_id dwmac_acpi_ids[] = { + { .id = "PHYT0004" }, + {}, +}; + +MODULE_DEVICE_TABLE(acpi, dwmac_acpi_ids); +#else +#define dwmac_acpi_ids NULL +#endif + static struct platform_driver dwmac_generic_driver = { .probe = dwmac_generic_probe, .remove = stmmac_pltfr_remove, @@ -92,6 +110,7 @@ static struct platform_driver dwmac_generic_driver = { .name = STMMAC_RESOURCE_NAME, .pm = &stmmac_pltfr_pm_ops, .of_match_table = of_match_ptr(dwmac_generic_match), + .acpi_match_table = ACPI_PTR(dwmac_acpi_ids), }, }; module_platform_driver(dwmac_generic_driver); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 0a4d093adfc9394826ebb0790dc6613a91cc2292..6493192b6bfd603982cc3e973b5deb8b3454e64a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -24,6 +24,7 @@ https://bugzilla.stlinux.com/ *******************************************************************************/ +#include #include #include #include diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index 9762e687fc73ae18acccb97d6864e31f971bff14..c4a1715a5406fb18662ff1da634249cbb53bcc8b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -18,6 +18,9 @@ Author: Giuseppe Cavallaro *******************************************************************************/ +#include +#include +#include #include #include #include @@ -614,6 +617,248 @@ void stmmac_remove_config_dt(struct platform_device *pdev, EXPORT_SYMBOL_GPL(stmmac_probe_config_dt); EXPORT_SYMBOL_GPL(stmmac_remove_config_dt); +#ifdef CONFIG_ACPI +/* + * Parse ACPI _DSD to setup AXI register + */ +static struct stmmac_axi * stmmac_axi_setup_acpi(struct platform_device *pdev) +{ + struct fwnode_handle *np = dev_fwnode(&(pdev->dev)); + struct stmmac_axi * axi; + + axi = devm_kzalloc(&pdev->dev, sizeof(*axi), GFP_KERNEL); + if (!axi) + return ERR_PTR(-ENOMEM); + + axi->axi_lpi_en = fwnode_property_read_bool(np, "snps,lpi_en"); + axi->axi_xit_frm = fwnode_property_read_bool(np, "snps,xit_frm"); + axi->axi_kbbe = fwnode_property_read_bool(np, "snps,axi_kbbe"); + axi->axi_fb = fwnode_property_read_bool(np, "snps,axi_fb"); + axi->axi_mb = fwnode_property_read_bool(np, "snps,axi_mb"); + axi->axi_rb = fwnode_property_read_bool(np, "snps,axi_rb"); + + if (fwnode_property_read_u32(np, "snps,wr_osr_lmt", &axi->axi_wr_osr_lmt)) + axi->axi_wr_osr_lmt = 1; + if (fwnode_property_read_u32(np, "snps,rd_osr_lmt", &axi->axi_rd_osr_lmt)) + axi->axi_rd_osr_lmt = 1; + fwnode_property_read_u32_array(np, "snps,blen", axi->axi_blen, AXI_BLEN); + + return axi; +} + +/** + * Parse ACPI _DSD parameters for multiple queues configuration + */ +static void stmmac_mtl_setup_acpi(struct platform_device *pdev, + struct plat_stmmacenet_data *plat) +{ + plat->rx_queues_to_use = 1; + plat->tx_queues_to_use = 1; + + /** + * First Queue must always be in DCB mode. As MTL_QUEUE_DCB=1 we need + * to always set this, otherwise Queue will be classified as AVB + * (because MTL_QUEUE_AVB = 0). + */ + plat->rx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB; + plat->tx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB; + + plat->rx_queues_cfg[0].use_prio = true; + + plat->rx_queues_cfg[0].pkt_route = 0x0; + + plat->rx_sched_algorithm = MTL_RX_ALGORITHM_SP; + plat->tx_sched_algorithm = MTL_TX_ALGORITHM_SP; + + plat->tx_queues_cfg[0].use_prio = true; +} + +static int stmmac_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; +} + +int fw_get_phy_mode(struct fwnode_handle *np) +{ + const char *pm; + int err, i; + + err = fwnode_property_read_string(np, "phy-mode", &pm); + if (err < 0) + err = fwnode_property_read_string(np, "phy-connection-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; +} + +int stmmac_acpi_clock_setup(struct plat_stmmacenet_data *plat, + struct platform_device *pdev) +{ + struct fwnode_handle *np = dev_fwnode(&(pdev->dev)); + struct device * dev = &pdev->dev; + struct clk *clk = ERR_PTR(-ENODEV); + u64 clk_freq = 0; + int err; + + err = fwnode_property_read_u64(np, "clock-frequency", &clk_freq); + if (err < 0) + clk_freq = 125000000; /* default to 125MHz */ + + plat->stmmac_clk = devm_clk_get(dev, dev_name(dev)); + if (IS_ERR(plat->stmmac_clk)) { + clk = clk_register_fixed_rate(dev, dev_name(dev), NULL, 0, clk_freq); + if (IS_ERR(clk)) + return -1; + if (clk_register_clkdev(clk, dev_name(dev), dev_name(dev))) + return -1; + plat->stmmac_clk = clk; + } + clk_prepare_enable(plat->stmmac_clk); + + plat->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(plat->pclk)) + plat->pclk = NULL; + clk_prepare_enable(plat->pclk); + + plat->clk_ptp_ref = devm_clk_get(dev, "ptp_ref"); + if (IS_ERR(plat->clk_ptp_ref)) { + plat->clk_ptp_rate = clk_get_rate(plat->stmmac_clk); + plat->clk_ptp_ref = NULL; + } + + plat->stmmac_rst = devm_reset_control_get(dev,STMMAC_RESOURCE_NAME); + if (IS_ERR(plat->stmmac_rst)) { + dev_info(dev, "no reset control found\n"); + plat->stmmac_rst = NULL; + } + + return 0; +} + +/** + * Parse ACPI driver parameters + */ +struct plat_stmmacenet_data * +stmmac_probe_config_acpi(struct platform_device *pdev, const char **mac) +{ + struct fwnode_handle *np; + struct plat_stmmacenet_data *plat; + struct stmmac_dma_cfg *dma_cfg; + + plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL); + if (!plat) + return ERR_PTR(-ENOMEM); + + np = dev_fwnode(&(pdev->dev)); + + plat->interface = fw_get_phy_mode(np); + + /* Get max speed of operation from device tree */ + if (fwnode_property_read_u32(np, "max-speed", &plat->max_speed)) + plat->max_speed = -1; + + if (fwnode_property_read_u32(np, "bus_id", &plat->bus_id)) + plat->bus_id = 2; + + /* Default to PHY auto-detection */ + plat->phy_addr = -1; + + /* "snps,phy-addr" is not a standard property. Mark it as deprecated + * and warn of its use. Remove this when PHY node support is added. + */ + if (fwnode_property_read_u32(np, "snps,phy-addr", &plat->phy_addr) == 0) + dev_warn(&pdev->dev, "snps,phy-addr property is deprecated\n"); + + if (stmmac_acpi_phy(plat, np, &pdev->dev)) + return ERR_PTR(-ENODEV); + + fwnode_property_read_u32(np, "tx-fifo-depth", &plat->tx_fifo_size); + fwnode_property_read_u32(np, "rx-fifo-depth", &plat->rx_fifo_size); + if (plat->tx_fifo_size == 0) + plat->tx_fifo_size = 0x10000; + if (plat->rx_fifo_size == 0) + plat->rx_fifo_size = 0x10000; + + plat->force_sf_dma_mode = + fwnode_property_read_bool(np, "snps,force_sf_dma_mode"); + plat->en_tx_lpi_clockgating = + fwnode_property_read_bool(np, "snps,en-tx-lpi-clockgating"); + + /* Set the maxmtu to a default of JUMBO_LEN in case the + * parameter is not present. + */ + plat->maxmtu = JUMBO_LEN; + + /* Set default value for multicast hash bins */ + plat->multicast_filter_bins = HASH_TABLE_SIZE; + + /* Set default value for unicast filter entries */ + plat->unicast_filter_entries = 1; + + /* Only to "snps,dwmac" */ + fwnode_property_read_u32(np, "max-frame-size", &plat->maxmtu); + fwnode_property_read_u32(np, "snps,multicast-filter-bins", + &plat->multicast_filter_bins); + fwnode_property_read_u32(np, "snps,perfect-filter-entries", + &plat->unicast_filter_entries); + plat->unicast_filter_entries = dwmac1000_validate_ucast_entries( + plat->unicast_filter_entries); + plat->multicast_filter_bins = dwmac1000_validate_mcast_bins( + plat->multicast_filter_bins); + plat->has_gmac = 1; + plat->pmt = 1; + + dma_cfg = devm_kzalloc(&pdev->dev, sizeof(*dma_cfg), GFP_KERNEL); + if (!dma_cfg) + return ERR_PTR(-ENOMEM); + plat->dma_cfg = dma_cfg; + + fwnode_property_read_u32(np, "snps,pbl", &dma_cfg->pbl); + if (!dma_cfg->pbl) + dma_cfg->pbl = DEFAULT_DMA_PBL; + + fwnode_property_read_u32(np, "snps,txpbl", &dma_cfg->txpbl); + fwnode_property_read_u32(np, "snps,rxpbl", &dma_cfg->rxpbl); + dma_cfg->pblx8 = !fwnode_property_read_bool(np, "snps,no-pbl-x8"); + + dma_cfg->aal = fwnode_property_read_bool(np, "snps,aal"); + dma_cfg->fixed_burst = fwnode_property_read_bool(np, "snps,fixed-burst"); + dma_cfg->mixed_burst = fwnode_property_read_bool(np, "snps,mixed-burst"); + + plat->force_thresh_dma_mode = fwnode_property_read_bool(np, "snps,force_thresh_dma_mode"); + if (plat->force_thresh_dma_mode) + plat->force_sf_dma_mode = 0; + + fwnode_property_read_u32(np, "snps,ps-speed", &plat->mac_port_sel_speed); + + plat->axi = stmmac_axi_setup_acpi(pdev); + + stmmac_mtl_setup_acpi(pdev, plat); + + stmmac_acpi_clock_setup(plat,pdev); + + return plat; +} +#else +struct plat_stmmacenet_data * +stmmac_probe_config_acpi(struct platform_device *pdev, const char **mac) +{ + return ERR_PTR(-EINVAL); +} +#endif /* CONFIG_ACPI */ +EXPORT_SYMBOL_GPL(stmmac_probe_config_acpi); + int stmmac_get_platform_resources(struct platform_device *pdev, struct stmmac_resources *stmmac_res) { @@ -624,33 +869,43 @@ int stmmac_get_platform_resources(struct platform_device *pdev, /* Get IRQ information early to have an ability to ask for deferred * probe if needed before we went too far with resource allocation. */ - stmmac_res->irq = platform_get_irq_byname(pdev, "macirq"); - if (stmmac_res->irq < 0) { - if (stmmac_res->irq != -EPROBE_DEFER) { - dev_err(&pdev->dev, - "MAC IRQ configuration information not found\n"); + if (pdev->dev.of_node) { + stmmac_res->irq = platform_get_irq_byname(pdev, "macirq"); + if (stmmac_res->irq < 0) { + if (stmmac_res->irq != -EPROBE_DEFER) { + dev_err(&pdev->dev, + "MAC IRQ configuration information not found\n"); + } + return stmmac_res->irq; } - return stmmac_res->irq; - } - /* On some platforms e.g. SPEAr the wake up irq differs from the mac irq - * The external wake up irq can be passed through the platform code - * named as "eth_wake_irq" - * - * In case the wake up interrupt is not passed from the platform - * so the driver will continue to use the mac irq (ndev->irq) - */ - stmmac_res->wol_irq = platform_get_irq_byname(pdev, "eth_wake_irq"); - if (stmmac_res->wol_irq < 0) { - if (stmmac_res->wol_irq == -EPROBE_DEFER) + /* On some platforms e.g. SPEAr the wake up irq differs from the mac irq + * The external wake up irq can be passed through the platform code + * named as "eth_wake_irq" + * + * In case the wake up interrupt is not passed from the platform + * so the driver will continue to use the mac irq (ndev->irq) + */ + stmmac_res->wol_irq = platform_get_irq_byname(pdev, "eth_wake_irq"); + if (stmmac_res->wol_irq < 0) { + if (stmmac_res->wol_irq == -EPROBE_DEFER) + return -EPROBE_DEFER; + stmmac_res->wol_irq = stmmac_res->irq; + } + + stmmac_res->lpi_irq = platform_get_irq_byname(pdev, "eth_lpi"); + if (stmmac_res->lpi_irq == -EPROBE_DEFER) return -EPROBE_DEFER; + } else if (has_acpi_companion(&pdev->dev)) { + stmmac_res->irq = platform_get_irq(pdev, 0); + if (stmmac_res->irq < 0) + dev_err(&pdev->dev, + "MAC IRQ configuration information not found\n"); + stmmac_res->wol_irq = stmmac_res->irq; + stmmac_res->lpi_irq = -1; } - stmmac_res->lpi_irq = platform_get_irq_byname(pdev, "eth_lpi"); - if (stmmac_res->lpi_irq == -EPROBE_DEFER) - return -EPROBE_DEFER; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); stmmac_res->addr = devm_ioremap_resource(&pdev->dev, res); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.h index b72eb0de57b70199c25213ee1b93da856cac22c2..8e117ad0e42a8360a786293c0874f199bc4a0553 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.h @@ -23,6 +23,8 @@ struct plat_stmmacenet_data * stmmac_probe_config_dt(struct platform_device *pdev, const char **mac); +struct plat_stmmacenet_data * +stmmac_probe_config_acpi(struct platform_device *pdev, const char **mac); void stmmac_remove_config_dt(struct platform_device *pdev, struct plat_stmmacenet_data *plat); diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 2386871e129494fc533a1aaeb176011941f329a1..fc3129f6989432463cfa4bafaa5e375a70a27e6b 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -385,6 +385,12 @@ config MICROSEMI_PHY ---help--- Currently supports VSC8530, VSC8531, VSC8540 and VSC8541 PHYs +config MOTORCOMM_PHY + tristate "Motorcomm PHYs" + help + Enables support for Motorcomm network PHYs. + Currently supports the YT8511 gigabit PHY. + config NATIONAL_PHY tristate "National Semiconductor PHYs" ---help--- diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index f21cda9d865edee42ed74f1e1afb05e423b224e5..c71e5d34be55e4e42cfc8ef007e5169b1dad3bf7 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_MICREL_PHY) += micrel.o obj-$(CONFIG_MICROCHIP_PHY) += microchip.o obj-$(CONFIG_MICROCHIP_T1_PHY) += microchip_t1.o obj-$(CONFIG_MICROSEMI_PHY) += mscc.o +obj-$(CONFIG_MOTORCOMM_PHY) += motorcomm.o obj-$(CONFIG_NATIONAL_PHY) += national.o obj-$(CONFIG_QSEMI_PHY) += qsemi.o obj-$(CONFIG_REALTEK_PHY) += realtek.o diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c new file mode 100755 index 0000000000000000000000000000000000000000..9d6d19f99bc8d688e078b7a4c361aa820d2a334f --- /dev/null +++ b/drivers/net/phy/motorcomm.c @@ -0,0 +1,2390 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * drivers/net/phy/motorcomm.c + * + * Driver for Motorcomm PHYs + * + * Author: yinghong.zhang + * + * Copyright (c) 2019 Motorcomm, Inc. + * + * 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. + * + * Support : Motorcomm Phys: + * Giga phys: yt8511, yt8521, yt8531, yt8614, yt8618 + * 100/10 Phys : yt8512, yt8512b, yt8510 + * Automotive 100Mb Phys : yt8010 + * Automotive 100/10 hyper range Phys: yt8510 + */ + +#include +#include +#include +#include +#include +#ifndef LINUX_VERSION_CODE +#include +#else +#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c)) +#endif + +/* for wol feature, 20210604 */ +#include + +#define YT_LINUX_MAJOR 2 +#define YT_LINUX_MINOR 2 +#define YT_LINUX_SUBVERSION 8661 +#define YT_LINUX_VERSIONID "2.2.8661" + +/******************************************** + **** configuration section begin ***********/ + +/* if system depends on ethernet packet to restore from sleep, + * please define this macro to 1 otherwise, define it to 0. + */ +#define SYS_WAKEUP_BASED_ON_ETH_PKT 1 + +/* to enable system WOL feature of phy, please define this macro to 1 + * otherwise, define it to 0. + */ +#define YTPHY_WOL_FEATURE_ENABLE 0 + +/* some GMAC need clock input from PHY, for eg., 125M, + * please enable this macro + * by degault, it is set to 0 + * NOTE: this macro will need macro SYS_WAKEUP_BASED_ON_ETH_PKT to set to 1 + */ +#define GMAC_CLOCK_INPUT_NEEDED 0 + +/* the max number of yt8521 chip on pcb board + * the most case is only 1 chip per board, but + * by default, we support up to 8. + */ +#define YTPHY_BOARD_MAX_NUM_OF_CHIP_8521 8 +#define YTPHY_BOARD_MAX_NUM_OF_CHIP_8614 4 + +/* for YT8531 package A xtal init config */ +#define YTPHY8531A_XTAL_INIT (0) + +/**** configuration section end *********** + ******************************************/ + +/* no need to change below */ +#define MOTORCOMM_PHY_ID_MASK 0x00000fff +#define MOTORCOMM_PHY_ID_8531_MASK 0xffffffff +#define MOTORCOMM_MPHY_ID_MASK 0x0000ffff +#define MOTORCOMM_MPHY_ID_MASK_8614 0xffffffff +#define MOTORCOMM_PHY_ID_MASK_8821 0xffffffff + +#define PHY_ID_YT8010 0x00000309 +#define PHY_ID_YT8010AS 0x4f51eb19 +#define PHY_ID_YT8510 0x00000109 +#define PHY_ID_YT8511 0x0000010a +#define PHY_ID_YT8512 0x00000118 +#define PHY_ID_YT8512B 0x00000128 +#define PHY_ID_YT8521 0x0000011a +#define PHY_ID_YT8531S 0x4f51e91a +#define PHY_ID_YT8531 0x4f51e91b +#define PHY_ID_YT8614 0x4F51E899 +#define PHY_ID_YT8618 0x0000e889 +#define PHY_ID_YT8821 0x4f51ea10 + +#define REG_PHY_SPEC_STATUS 0x11 +#define REG_DEBUG_ADDR_OFFSET 0x1e +#define REG_DEBUG_DATA 0x1f + +#define YT8512_EXTREG_LED0 0x40c0 +#define YT8512_EXTREG_LED1 0x40c3 + +#define YT8512_EXTREG_SLEEP_CONTROL1 0x2027 + +#define YT_SOFTWARE_RESET 0x8000 + +#define YT8512_LED0_ACT_BLK_IND 0x1000 +#define YT8512_LED0_DIS_LED_AN_TRY 0x0001 +#define YT8512_LED0_BT_BLK_EN 0x0002 +#define YT8512_LED0_HT_BLK_EN 0x0004 +#define YT8512_LED0_COL_BLK_EN 0x0008 +#define YT8512_LED0_BT_ON_EN 0x0010 +#define YT8512_LED1_BT_ON_EN 0x0010 +#define YT8512_LED1_TXACT_BLK_EN 0x0100 +#define YT8512_LED1_RXACT_BLK_EN 0x0200 +#define YT8512_SPEED_MODE 0xc000 +#define YT8512_DUPLEX 0x2000 + +#define YT8512_SPEED_MODE_BIT 14 +#define YT8512_DUPLEX_BIT 13 +#define YT8512_EN_SLEEP_SW_BIT 15 + +#define YT8521_EXTREG_SLEEP_CONTROL1 0x27 +#define YT8521_EN_SLEEP_SW_BIT 15 + +#define YT8521_SPEED_MODE 0xc000 +#define YT8521_DUPLEX 0x2000 +#define YT8521_SPEED_MODE_BIT 14 +#define YT8521_DUPLEX_BIT 13 +#define YT8521_LINK_STATUS_BIT 10 + +/* based on yt8521 wol feature config register */ +#define YTPHY_UTP_INTR_REG 0x12 +/* WOL Feature Event Interrupt Enable */ +#define YTPHY_WOL_FEATURE_INTR BIT(6) + +/* Magic Packet MAC address registers */ +#define YTPHY_WOL_FEATURE_MACADDR2_4_MAGIC_PACKET 0xa007 +#define YTPHY_WOL_FEATURE_MACADDR1_4_MAGIC_PACKET 0xa008 +#define YTPHY_WOL_FEATURE_MACADDR0_4_MAGIC_PACKET 0xa009 + +#define YTPHY_WOL_FEATURE_REG_CFG 0xa00a +#define YTPHY_WOL_FEATURE_TYPE_CFG BIT(0) /* WOL TYPE Config */ +#define YTPHY_WOL_FEATURE_ENABLE_CFG BIT(3) /* WOL Enable Config */ +#define YTPHY_WOL_FEATURE_INTR_SEL_CFG BIT(6) /* WOL Event Interrupt Enable Config */ +#define YTPHY_WOL_FEATURE_WIDTH1_CFG BIT(1) /* WOL Pulse Width Config */ +#define YTPHY_WOL_FEATURE_WIDTH2_CFG BIT(2) /* WOL Pulse Width Config */ + +#define YTPHY_REG_SPACE_UTP 0 +#define YTPHY_REG_SPACE_FIBER 2 + +enum ytphy_wol_feature_trigger_type_e { + YTPHY_WOL_FEATURE_PULSE_TRIGGER, + YTPHY_WOL_FEATURE_LEVEL_TRIGGER, + YTPHY_WOL_FEATURE_TRIGGER_TYPE_MAX +}; + +enum ytphy_wol_feature_pulse_width_e { + YTPHY_WOL_FEATURE_672MS_PULSE_WIDTH, + YTPHY_WOL_FEATURE_336MS_PULSE_WIDTH, + YTPHY_WOL_FEATURE_168MS_PULSE_WIDTH, + YTPHY_WOL_FEATURE_84MS_PULSE_WIDTH, + YTPHY_WOL_FEATURE_PULSE_WIDTH_MAX +}; + +struct ytphy_wol_feature_cfg { + bool enable; + int type; + int width; +}; + +#if (YTPHY_WOL_FEATURE_ENABLE) +#undef SYS_WAKEUP_BASED_ON_ETH_PKT +#define SYS_WAKEUP_BASED_ON_ETH_PKT 1 +#endif + +/* YT8521 polling mode */ +#define YT8521_PHY_MODE_FIBER 1 //fiber mode only +#define YT8521_PHY_MODE_UTP 2 //utp mode only +#define YT8521_PHY_MODE_POLL 3 //fiber and utp, poll mode + +/* below are for bitmap */ +#define YT_PHY_MODE_FIBER 1 //fiber/sgmii mode only +#define YT_PHY_MODE_UTP 2 //utp mode only +#define YT_PHY_MODE_QSGMII 4 //qsgmii mode only +#define YT_PHY_MODE_POLL (YT_PHY_MODE_FIBER | YT_PHY_MODE_UTP | YT_PHY_MODE_QSGMII) //qsgmii, fiber/sgmii and utp, poll mode + +/* support automatically check polling mode for yt8521 + * for Fiber only system, please define YT8521_PHY_MODE_CURR 1 + * for UTP only system, please define YT8521_PHY_MODE_CURR 2 + * for combo system, please define YT8521_PHY_MODE_CURR 3 + */ +#define YTPHY_861X_ABC_VER 0 +#if (YTPHY_861X_ABC_VER) +static int yt8614_get_port_from_phydev(struct phy_device *phydev); +#endif +static int yt8521_hw_strap_polling(struct phy_device *phydev); +static int yt8614_hw_strap_polling(struct phy_device *phydev); +#define YT8521_PHY_MODE_CURR yt8521_hw_strap_polling(phydev) +#define YT8614_PHY_MODE_CURR yt8614_hw_strap_polling(phydev) + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE) +static int ytphy_config_init(struct phy_device *phydev) +{ + int val; + + val = phy_read(phydev, 3); + + return 0; +} +#endif + + +#if (KERNEL_VERSION(5, 5, 0) > LINUX_VERSION_CODE) +static inline void phy_lock_mdio_bus(struct phy_device *phydev) +{ +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + mutex_lock(&phydev->bus->mdio_lock); +#else + mutex_lock(&phydev->mdio.bus->mdio_lock); +#endif +} + +static inline void phy_unlock_mdio_bus(struct phy_device *phydev) +{ +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + mutex_unlock(&phydev->bus->mdio_lock); +#else + mutex_unlock(&phydev->mdio.bus->mdio_lock); +#endif +} +#endif + +#if (KERNEL_VERSION(4, 16, 0) > LINUX_VERSION_CODE) +static inline int __phy_read(struct phy_device *phydev, u32 regnum) +{ +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + struct mii_bus *bus = phydev->bus; + int addr = phydev->addr; + return bus->read(bus, phydev->addr, regnum); +#else + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; +#endif + return bus->read(bus, addr, regnum); +} + +static inline int __phy_write(struct phy_device *phydev, u32 regnum, u16 val) +{ +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + struct mii_bus *bus = phydev->bus; + int addr = phydev->addr; +#else + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; +#endif + return bus->write(bus, addr, regnum, val); +} +#endif + +static int ytphy_read_ext(struct phy_device *phydev, u32 regnum) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret = __phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum); + if (ret < 0) + goto err_handle; + + ret = __phy_read(phydev, REG_DEBUG_DATA); + if (ret < 0) + goto err_handle; + +err_handle: + phy_unlock_mdio_bus(phydev); + return ret; +} + +static int ytphy_write_ext(struct phy_device *phydev, u32 regnum, u16 val) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret = __phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum); + if (ret < 0) + goto err_handle; + + ret = __phy_write(phydev, REG_DEBUG_DATA, val); + if (ret < 0) + goto err_handle; + +err_handle: + phy_unlock_mdio_bus(phydev); + return ret; +} + +static int ytphy_soft_reset(struct phy_device *phydev) +{ + int ret = 0, val = 0; + + val = phy_read(phydev, MII_BMCR); + if (val < 0) + return val; + + ret = phy_write(phydev, MII_BMCR, val | BMCR_RESET); + if (ret < 0) + return ret; + + return ret; +} + + +#if (YTPHY8531A_XTAL_INIT) +static int yt8531a_xtal_init(struct phy_device *phydev) +{ + int ret = 0; + int val = 0; + bool state = false; + + msleep(50); + + do { + ret = ytphy_write_ext(phydev, 0xa012, 0x88); + if (ret < 0) + return ret; + + msleep(100); + + val = ytphy_read_ext(phydev, 0xa012); + if (val < 0) + return val; + + usleep_range(10000, 20000); + } while (val != 0x88); + + ret = ytphy_write_ext(phydev, 0xa012, 0xc8); + if (ret < 0) + return ret; + + return ret; +} +#endif + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +#else +int yt8010_soft_reset(struct phy_device *phydev) +{ + ytphy_soft_reset(phydev); + + return 0; +} + +int yt8010AS_soft_reset(struct phy_device *phydev) +{ + int ret = 0; + + /* sgmii */ + ytphy_write_ext(phydev, 0xe, 1); + ret = ytphy_soft_reset(phydev); + if (ret < 0) { + ytphy_write_ext(phydev, 0xe, 0); + return ret; + } + + /* utp */ + ytphy_write_ext(phydev, 0xe, 0); + ret = ytphy_soft_reset(phydev); + if (ret < 0) + return ret; + + return 0; +} +#endif + +#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE) +int yt8010_aneg_done(struct phy_device *phydev) +{ + int val = 0; + + val = phy_read(phydev, 0x1); + val = phy_read(phydev, 0x1); + + return (val < 0) ? val : (val & BMSR_LSTATUS); +} +#endif + +static int yt8010_config_aneg(struct phy_device *phydev) +{ + phydev->speed = SPEED_100; + return 0; +} + +static int yt8010_read_status(struct phy_device *phydev) +{ + int ret = 0; + + ret = genphy_update_link(phydev); + if (ret) + return ret; + + /* for 8010, no definition mii reg 0x04, 0x11, here force 100/full */ + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + + return 0; +} + +static int yt8010AS_config_init(struct phy_device *phydev) +{ + phydev->autoneg = AUTONEG_DISABLE; + + return 0; +} + +static int yt8512_led_init(struct phy_device *phydev) +{ + int ret; + int val; + int mask; + + val = ytphy_read_ext(phydev, YT8512_EXTREG_LED0); + if (val < 0) + return val; + + val |= YT8512_LED0_ACT_BLK_IND; + + mask = YT8512_LED0_DIS_LED_AN_TRY | YT8512_LED0_BT_BLK_EN | + YT8512_LED0_HT_BLK_EN | YT8512_LED0_COL_BLK_EN | + YT8512_LED0_BT_ON_EN; + val &= ~mask; + + ret = ytphy_write_ext(phydev, YT8512_EXTREG_LED0, val); + if (ret < 0) + return ret; + + val = ytphy_read_ext(phydev, YT8512_EXTREG_LED1); + if (val < 0) + return val; + + val |= YT8512_LED1_BT_ON_EN; + + mask = YT8512_LED1_TXACT_BLK_EN | YT8512_LED1_RXACT_BLK_EN; + val &= ~mask; + + ret = ytphy_write_ext(phydev, YT8512_EXTREG_LED1, val); + + return ret; +} + +static int yt8512_config_init(struct phy_device *phydev) +{ + int ret; + int val; + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE) + ret = ytphy_config_init(phydev); +#else + ret = genphy_config_init(phydev); +#endif + if (ret < 0) + return ret; + + ret = yt8512_led_init(phydev); + + /* disable auto sleep */ + val = ytphy_read_ext(phydev, YT8512_EXTREG_SLEEP_CONTROL1); + if (val < 0) + return val; + + val &= (~BIT(YT8512_EN_SLEEP_SW_BIT)); + + ret = ytphy_write_ext(phydev, YT8512_EXTREG_SLEEP_CONTROL1, val); + if (ret < 0) + return ret; + + return ret; +} + +static int yt8512_read_status(struct phy_device *phydev) +{ + int ret; + int val; + int speed, speed_mode, duplex; + + ret = genphy_update_link(phydev); + if (ret) + return ret; + + val = phy_read(phydev, REG_PHY_SPEC_STATUS); + if (val < 0) + return val; + + duplex = (val & YT8512_DUPLEX) >> YT8512_DUPLEX_BIT; + speed_mode = (val & YT8512_SPEED_MODE) >> YT8512_SPEED_MODE_BIT; + switch (speed_mode) { + case 0: + speed = SPEED_10; + break; + case 1: + speed = SPEED_100; + break; + case 2: + case 3: + default: +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + speed = -1; +#else + speed = SPEED_UNKNOWN; +#endif + break; + } + + phydev->speed = speed; + phydev->duplex = duplex; + + return 0; +} + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +#else +int yt8521_soft_reset(struct phy_device *phydev) +{ + int ret, val; + + if (YT8521_PHY_MODE_CURR == YT8521_PHY_MODE_UTP) { + ytphy_write_ext(phydev, 0xa000, 0); + ytphy_soft_reset(phydev); + } + + if (YT8521_PHY_MODE_CURR == YT8521_PHY_MODE_FIBER) { + ytphy_write_ext(phydev, 0xa000, 2); + ytphy_soft_reset(phydev); + + ytphy_write_ext(phydev, 0xa000, 0); + } + + if (YT8521_PHY_MODE_CURR == YT8521_PHY_MODE_POLL) { + val = ytphy_read_ext(phydev, 0xa001); + ytphy_write_ext(phydev, 0xa001, (val & ~0x8000)); + + ytphy_write_ext(phydev, 0xa000, 0); + ret = ytphy_soft_reset(phydev); + } + + return 0; +} +#endif + +#if GMAC_CLOCK_INPUT_NEEDED +static int ytphy_mii_rd_ext(struct mii_bus *bus, int phy_id, u32 regnum) +{ + int ret; + int val; + + ret = bus->write(bus, phy_id, REG_DEBUG_ADDR_OFFSET, regnum); + if (ret < 0) + return ret; + + val = bus->read(bus, phy_id, REG_DEBUG_DATA); + + return val; +} + +static int ytphy_mii_wr_ext(struct mii_bus *bus + int phy_id, + u32 regnum, + u16 val) +{ + int ret; + + ret = bus->write(bus, phy_id, REG_DEBUG_ADDR_OFFSET, regnum); + if (ret < 0) + return ret; + + ret = bus->write(bus, phy_id, REG_DEBUG_DATA, val); + + return ret; +} + +int yt8511_config_dis_txdelay(struct mii_bus *bus, int phy_id) +{ + int ret; + int val; + + /* disable auto sleep */ + val = ytphy_mii_rd_ext(bus, phy_id, 0x27); + if (val < 0) + return val; + + val &= (~BIT(15)); + + ret = ytphy_mii_wr_ext(bus, phy_id, 0x27, val); + if (ret < 0) + return ret; + + /* enable RXC clock when no wire plug */ + val = ytphy_mii_rd_ext(bus, phy_id, 0xc); + if (val < 0) + return val; + + /* ext reg 0xc b[7:4] + * Tx Delay time = 150ps * N - 250ps + */ + val &= ~(0xf << 4); + ret = ytphy_mii_wr_ext(bus, phy_id, 0xc, val); + + return ret; +} + +int yt8511_config_out_125m(struct mii_bus *bus, int phy_id) +{ + int ret; + int val; + + /* disable auto sleep */ + val = ytphy_mii_rd_ext(bus, phy_id, 0x27); + if (val < 0) + return val; + + val &= (~BIT(15)); + + ret = ytphy_mii_wr_ext(bus, phy_id, 0x27, val); + if (ret < 0) + return ret; + + /* enable RXC clock when no wire plug */ + val = ytphy_mii_rd_ext(bus, phy_id, 0xc); + if (val < 0) + return val; + + /* ext reg 0xc.b[2:1] + * 00-----25M from pll; + * 01---- 25M from xtl;(default) + * 10-----62.5M from pll; + * 11----125M from pll(here set to this value) + */ + val |= (3 << 1); + ret = ytphy_mii_wr_ext(bus, phy_id, 0xc, val); + +#ifdef YT_8511_INIT_TO_MASTER + /* for customer, please enable it based on demand. + * configure to master + */ + + /* master/slave config reg*/ + val = bus->read(bus, phy_id, 0x9); + /* to be manual config and force to be master */ + val |= (0x3<<11); + /* take effect until phy soft reset */ + ret = bus->write(bus, phy_id, 0x9, val); + if (ret < 0) + return ret; +#endif + + return ret; +} + +static int yt8511_config_init(struct phy_device *phydev) +{ + int ret; + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE) + ret = ytphy_config_init(phydev); +#else + ret = genphy_config_init(phydev); +#endif + +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->addr); +#else + netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->mdio.addr); +#endif + + return ret; +} +#endif /* GMAC_CLOCK_INPUT_NEEDED */ + +#if (YTPHY_WOL_FEATURE_ENABLE) +static int ytphy_switch_reg_space(struct phy_device *phydev, int space) +{ + int ret; + + if (space == YTPHY_REG_SPACE_UTP) + ret = ytphy_write_ext(phydev, 0xa000, 0); + else + ret = ytphy_write_ext(phydev, 0xa000, 2); + + return ret; +} + +static int ytphy_wol_feature_enable_cfg(struct phy_device *phydev, + struct ytphy_wol_feature_cfg wol_cfg) +{ + int ret = 0; + int val = 0; + + val = ytphy_read_ext(phydev, YTPHY_WOL_FEATURE_REG_CFG); + if (val < 0) + return val; + + if (wol_cfg.enable) { + val |= YTPHY_WOL_FEATURE_ENABLE_CFG; + + if (wol_cfg.type == YTPHY_WOL_FEATURE_LEVEL_TRIGGER) { + val &= ~YTPHY_WOL_FEATURE_TYPE_CFG; + val &= ~YTPHY_WOL_FEATURE_INTR_SEL_CFG; + } else if (wol_cfg.type == YTPHY_WOL_FEATURE_PULSE_TRIGGER) { + val |= YTPHY_WOL_FEATURE_TYPE_CFG; + val |= YTPHY_WOL_FEATURE_INTR_SEL_CFG; + + if (wol_cfg.width == YTPHY_WOL_FEATURE_84MS_PULSE_WIDTH) { + val &= ~YTPHY_WOL_FEATURE_WIDTH1_CFG; + val &= ~YTPHY_WOL_FEATURE_WIDTH2_CFG; + } else if (wol_cfg.width == YTPHY_WOL_FEATURE_168MS_PULSE_WIDTH) { + val |= YTPHY_WOL_FEATURE_WIDTH1_CFG; + val &= ~YTPHY_WOL_FEATURE_WIDTH2_CFG; + } else if (wol_cfg.width == YTPHY_WOL_FEATURE_336MS_PULSE_WIDTH) { + val &= ~YTPHY_WOL_FEATURE_WIDTH1_CFG; + val |= YTPHY_WOL_FEATURE_WIDTH2_CFG; + } else if (wol_cfg.width == YTPHY_WOL_FEATURE_672MS_PULSE_WIDTH) { + val |= YTPHY_WOL_FEATURE_WIDTH1_CFG; + val |= YTPHY_WOL_FEATURE_WIDTH2_CFG; + } + } + } else { + val &= ~YTPHY_WOL_FEATURE_ENABLE_CFG; + val &= ~YTPHY_WOL_FEATURE_INTR_SEL_CFG; + } + + ret = ytphy_write_ext(phydev, YTPHY_WOL_FEATURE_REG_CFG, val); + if (ret < 0) + return ret; + + return 0; +} + +static void ytphy_wol_feature_get(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + int val = 0; + + wol->supported = WAKE_MAGIC; + wol->wolopts = 0; + + val = ytphy_read_ext(phydev, YTPHY_WOL_FEATURE_REG_CFG); + if (val < 0) + return; + + if (val & YTPHY_WOL_FEATURE_ENABLE_CFG) + wol->wolopts |= WAKE_MAGIC; + + //return; +} + +static int ytphy_wol_feature_set(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + int ret, curr_reg_space, val; + struct ytphy_wol_feature_cfg wol_cfg; + struct net_device *p_attached_dev = phydev->attached_dev; + + memset(&wol_cfg, 0, sizeof(struct ytphy_wol_feature_cfg)); + curr_reg_space = ytphy_read_ext(phydev, 0xa000); + if (curr_reg_space < 0) + return curr_reg_space; + + /* Switch to phy UTP page */ + ret = ytphy_switch_reg_space(phydev, YTPHY_REG_SPACE_UTP); + if (ret < 0) + return ret; + + if (wol->wolopts & WAKE_MAGIC) { + /* Enable the WOL feature interrupt */ + val = phy_read(phydev, YTPHY_UTP_INTR_REG); + val |= YTPHY_WOL_FEATURE_INTR; + ret = phy_write(phydev, YTPHY_UTP_INTR_REG, val); + if (ret < 0) + return ret; + + /* Set the WOL feature config */ + wol_cfg.enable = true; + wol_cfg.type = YTPHY_WOL_FEATURE_PULSE_TRIGGER; + wol_cfg.width = YTPHY_WOL_FEATURE_672MS_PULSE_WIDTH; + ret = ytphy_wol_feature_enable_cfg(phydev, wol_cfg); + if (ret < 0) + return ret; + + /* Store the device address for the magic packet */ + ret = ytphy_write_ext(phydev, YTPHY_WOL_FEATURE_MACADDR2_4_MAGIC_PACKET, + ((p_attached_dev->dev_addr[0] << 8) | + p_attached_dev->dev_addr[1])); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, YTPHY_WOL_FEATURE_MACADDR1_4_MAGIC_PACKET, + ((p_attached_dev->dev_addr[2] << 8) | + p_attached_dev->dev_addr[3])); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, YTPHY_WOL_FEATURE_MACADDR0_4_MAGIC_PACKET, + ((p_attached_dev->dev_addr[4] << 8) | + p_attached_dev->dev_addr[5])); + if (ret < 0) + return ret; + } else { + wol_cfg.enable = false; + wol_cfg.type = YTPHY_WOL_FEATURE_TRIGGER_TYPE_MAX; + wol_cfg.width = YTPHY_WOL_FEATURE_PULSE_WIDTH_MAX; + ret = ytphy_wol_feature_enable_cfg(phydev, wol_cfg); + if (ret < 0) + return ret; + } + + /* Recover to previous register space page */ + ret = ytphy_switch_reg_space(phydev, curr_reg_space); + if (ret < 0) + return ret; + + return 0; +} +#endif /*(YTPHY_WOL_FEATURE_ENABLE)*/ + +static int yt8521_hw_strap_polling(struct phy_device *phydev) +{ + int val = 0; + + val = ytphy_read_ext(phydev, 0xa001) & 0x7; + switch (val) { + case 1: + case 4: + case 5: + return YT8521_PHY_MODE_FIBER; + case 2: + case 6: + case 7: + return YT8521_PHY_MODE_POLL; + case 3: + case 0: + default: + return YT8521_PHY_MODE_UTP; + } +} + + +static int yt8521_config_init(struct phy_device *phydev) +{ + int ret; + int val, hw_strap_mode; + +#if (YTPHY_WOL_FEATURE_ENABLE) + struct ethtool_wolinfo wol; + + /* set phy wol enable */ + memset(&wol, 0x0, sizeof(struct ethtool_wolinfo)); + wol.wolopts |= WAKE_MAGIC; + ytphy_wol_feature_set(phydev, &wol); +#endif + if (phydev->force_mode) { + hw_strap_mode = ytphy_read_ext(phydev, 0xa001) & 0x7; + hw_strap_mode = hw_strap_mode & 0x7ff8; + hw_strap_mode = hw_strap_mode |0x140; + ytphy_write_ext(phydev, 0xa001, hw_strap_mode); + } + + phydev->irq = PHY_POLL; + /* NOTE: this function should not be called more than one for each chip. */ + hw_strap_mode = ytphy_read_ext(phydev, 0xa001) & 0x7; + + ytphy_write_ext(phydev, 0xa000, 0); +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE) + ret = ytphy_config_init(phydev); +#else + ret = genphy_config_init(phydev); +#endif + if (ret < 0) + return ret; + + /* disable auto sleep */ + val = ytphy_read_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1); + if (val < 0) + return val; + + val &= (~BIT(YT8521_EN_SLEEP_SW_BIT)); + + ret = ytphy_write_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1, val); + if (ret < 0) + return ret; + + /* enable RXC clock when no wire plug */ + val = ytphy_read_ext(phydev, 0xc); + if (val < 0) + return val; + val &= ~(1 << 12); + ret = ytphy_write_ext(phydev, 0xc, val); + if (ret < 0) + return ret; + +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s done, phy addr: %d, strap mode = %d, polling mode = %d\n", + __func__, phydev->addr, hw_strap_mode, yt8521_hw_strap_polling(phydev)); +#else + netdev_info(phydev->attached_dev, "%s done, phy addr: %d, strap mode = %d, polling mode = %d\n", + __func__, phydev->mdio.addr, hw_strap_mode, yt8521_hw_strap_polling(phydev)); +#endif + return ret; +} + +/* for fiber mode, there is no 10M speed mode and + * this function is for this purpose. + */ +static int yt8521_adjust_status(struct phy_device *phydev, int val, int is_utp) +{ + int speed_mode, duplex; +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + int speed = -1; +#else + int speed = SPEED_UNKNOWN; +#endif + + if (is_utp) + duplex = (val & YT8512_DUPLEX) >> YT8521_DUPLEX_BIT; + else + duplex = 1; + speed_mode = (val & YT8521_SPEED_MODE) >> YT8521_SPEED_MODE_BIT; + switch (speed_mode) { + case 0: + if (is_utp) + speed = SPEED_10; + break; + case 1: + speed = SPEED_100; + break; + case 2: + speed = SPEED_1000; + break; + case 3: + break; + default: +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + speed = -1; +#else + speed = SPEED_UNKNOWN; +#endif + break; + } + + phydev->speed = speed; + phydev->duplex = duplex; + + return 0; +} + +/* for fiber mode, when speed is 100M, there is no definition for + * autonegotiation, and this function handles this case and return + * 1 per linux kernel's polling. + */ +int yt8521_aneg_done(struct phy_device *phydev) +{ + int link_fiber = 0, link_utp = 0; + + /* reading Fiber */ + ytphy_write_ext(phydev, 0xa000, 2); + link_fiber = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YT8521_LINK_STATUS_BIT))); + + /* reading UTP */ + ytphy_write_ext(phydev, 0xa000, 0); + if (!link_fiber) + link_utp = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YT8521_LINK_STATUS_BIT))); + +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_fiber: %d, link_utp: %d\n", + __func__, phydev->addr, link_fiber, link_utp); +#else + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_fiber: %d, link_utp: %d\n", + __func__, phydev->mdio.addr, link_fiber, link_utp); +#endif + return !!(link_fiber | link_utp); +} + +static int yt8521_read_status(struct phy_device *phydev) +{ + int ret; + int val; + int yt8521_fiber_latch_val; + int yt8521_fiber_curr_val; + int link; + int link_utp = 0, link_fiber = 0; + + if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) { + /* reading UTP */ + ret = ytphy_write_ext(phydev, 0xa000, 0); + if (ret < 0) + return ret; + + val = phy_read(phydev, REG_PHY_SPEC_STATUS); + if (val < 0) + return val; + + link = val & (BIT(YT8521_LINK_STATUS_BIT)); + if (link) { + link_utp = 1; + yt8521_adjust_status(phydev, val, 1); + } else { + link_utp = 0; + } + } //(YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) + + if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_UTP) { + /* reading Fiber */ + ret = ytphy_write_ext(phydev, 0xa000, 2); + if (ret < 0) + return ret; + + val = phy_read(phydev, REG_PHY_SPEC_STATUS); + if (val < 0) + return val; + + //note: below debug information is used to check multiple PHy ports. + + /* for fiber, from 1000m to 100m, there is not link down from 0x11, + * and check reg 1 to identify such case this is important for Linux + * kernel for that, missing linkdown event will cause problem. + */ + yt8521_fiber_latch_val = phy_read(phydev, MII_BMSR); + yt8521_fiber_curr_val = phy_read(phydev, MII_BMSR); + link = val & (BIT(YT8521_LINK_STATUS_BIT)); + if (link && yt8521_fiber_latch_val != yt8521_fiber_curr_val) { + link = 0; +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n", + __func__, phydev->addr, yt8521_fiber_latch_val, yt8521_fiber_curr_val); +#else + netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n", + __func__, phydev->mdio.addr, yt8521_fiber_latch_val, yt8521_fiber_curr_val); +#endif + } + + if (link) { + link_fiber = 1; + yt8521_adjust_status(phydev, val, 0); + } else { + link_fiber = 0; + } + } //(YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_UTP) + + if (link_utp || link_fiber) { + if (phydev->link == 0) +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: %s, mii reg 0x11 = 0x%x\n", + __func__, phydev->addr, (link_utp && link_fiber) ? "UNKNOWN MEDIA" : (link_utp ? "UTP" : "Fiber"), (unsigned int)val); +#else + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: %s, mii reg 0x11 = 0x%x\n", + __func__, phydev->mdio.addr, (link_utp && link_fiber) ? "UNKNOWN MEDIA" : (link_utp ? "UTP" : "Fiber"), (unsigned int)val); +#endif + phydev->link = 1; + } else { + if (phydev->link == 1) +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr); +#else + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr); +#endif + phydev->link = 0; + } + + if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) { //utp or combo + if (link_fiber) + ytphy_write_ext(phydev, 0xa000, 2); + if (link_utp) + ytphy_write_ext(phydev, 0xa000, 0); + } + return 0; +} + +int yt8521_suspend(struct phy_device *phydev) +{ +#if !(SYS_WAKEUP_BASED_ON_ETH_PKT) + int value; + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_lock(&phydev->lock); +#else + /* no need lock in 4.19 */ +#endif + + ytphy_write_ext(phydev, 0xa000, 0); + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value | BMCR_PDOWN); + + ytphy_write_ext(phydev, 0xa000, 2); + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value | BMCR_PDOWN); + + ytphy_write_ext(phydev, 0xa000, 0); + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_unlock(&phydev->lock); +#else + /* no need lock/unlock in 4.19 */ +#endif +#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/ + + return 0; +} + +int yt8521_resume(struct phy_device *phydev) +{ + int value, ret; + + /* disable auto sleep */ + value = ytphy_read_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1); + if (value < 0) + return value; + + value &= (~BIT(YT8521_EN_SLEEP_SW_BIT)); + + ret = ytphy_write_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1, value); + if (ret < 0) + return ret; + +#if !(SYS_WAKEUP_BASED_ON_ETH_PKT) +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_lock(&phydev->lock); +#else + /* no need lock/unlock in 4.19 */ +#endif + + if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) { + ytphy_write_ext(phydev, 0xa000, 0); + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN); + } + + if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_UTP) { + ytphy_write_ext(phydev, 0xa000, 2); + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN); + + ytphy_write_ext(phydev, 0xa000, 0); + } + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_unlock(&phydev->lock); +#else + /* no need lock/unlock in 4.19 */ +#endif +#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/ + + return 0; +} + +static int yt8531S_config_init(struct phy_device *phydev) +{ + int ret = 0; + +#if (YTPHY8531A_XTAL_INIT) + ret = yt8531a_xtal_init(phydev); + if (ret < 0) + return ret; +#endif + + ret = yt8521_config_init(phydev); + + return ret; +} + +static int yt8531_config_init(struct phy_device *phydev) +{ + int ret = 0; + +#if (YTPHY8531A_XTAL_INIT) + ret = yt8531a_xtal_init(phydev); + if (ret < 0) + return ret; +#endif + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE) + ret = ytphy_config_init(phydev); +#else + ret = genphy_config_init(phydev); +#endif + if (ret < 0) + return ret; + + return 0; +} + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +#else +int yt8618_soft_reset(struct phy_device *phydev) +{ + int ret; + + ytphy_write_ext(phydev, 0xa000, 0); + ret = ytphy_soft_reset(phydev); + if (ret < 0) + return ret; + + return 0; +} + +int yt8614_soft_reset(struct phy_device *phydev) +{ + int ret; + + /* qsgmii */ + ytphy_write_ext(phydev, 0xa000, 2); + ret = ytphy_soft_reset(phydev); + if (ret < 0) { + ytphy_write_ext(phydev, 0xa000, 0); + return ret; + } + + /* sgmii */ + ytphy_write_ext(phydev, 0xa000, 3); + ret = ytphy_soft_reset(phydev); + if (ret < 0) { + ytphy_write_ext(phydev, 0xa000, 0); + return ret; + } + + /* utp */ + ytphy_write_ext(phydev, 0xa000, 0); + ret = ytphy_soft_reset(phydev); + if (ret < 0) + return ret; + + return 0; +} +#endif + +static int yt8618_config_init(struct phy_device *phydev) +{ + int ret; + int val; + unsigned int retries = 12; +#if (YTPHY_861X_ABC_VER) + int port = 0; +#endif + + phydev->irq = PHY_POLL; + +#if (YTPHY_861X_ABC_VER) + port = yt8614_get_port_from_phydev(phydev); +#endif + + ytphy_write_ext(phydev, 0xa000, 0); +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE) + ret = ytphy_config_init(phydev); +#else + ret = genphy_config_init(phydev); +#endif + if (ret < 0) + return ret; + + /* for utp to optimize signal */ + ret = ytphy_write_ext(phydev, 0x41, 0x33); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x42, 0x66); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x43, 0xaa); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x44, 0xd0d); + if (ret < 0) + return ret; + +#if (YTPHY_861X_ABC_VER) + if ((port == 2) || (port == 5)) { + ret = ytphy_write_ext(phydev, 0x57, 0x2929); + if (ret < 0) + return ret; + } +#endif + + val = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, val | BMCR_RESET); + do { + msleep(50); + ret = phy_read(phydev, MII_BMCR); + if (ret < 0) + return ret; + } while ((ret & BMCR_RESET) && --retries); + if (ret & BMCR_RESET) + return -ETIMEDOUT; + + /* for QSGMII optimization */ + ytphy_write_ext(phydev, 0xa000, 0x02); + + ret = ytphy_write_ext(phydev, 0x3, 0x4F80); + if (ret < 0) { + ytphy_write_ext(phydev, 0xa000, 0); + return ret; + } + ret = ytphy_write_ext(phydev, 0xe, 0x4F80); + if (ret < 0) { + ytphy_write_ext(phydev, 0xa000, 0); + return ret; + } + + ytphy_write_ext(phydev, 0xa000, 0); + +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->addr); +#else + netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->mdio.addr); +#endif + return ret; +} + +static int yt8614_hw_strap_polling(struct phy_device *phydev) +{ + int val = 0; + + val = ytphy_read_ext(phydev, 0xa007) & 0xf; + switch (val) { + case 8: //4'b1000, Fiber x4 + Copper x4 + case 12: //4'b1100, QSGMII x1 + Combo x4 mode; + case 13: //4'b1101, QSGMII x1 + Combo x4 mode; + return (YT_PHY_MODE_FIBER | YT_PHY_MODE_UTP); + case 14: //4'b1110, QSGMII x1 + SGMII(MAC) x4 mode; + case 11: //4'b1011, QSGMII x1 + Fiber x4 mode; + return YT_PHY_MODE_FIBER; + case 9: //4'b1001, Reserved. + case 10: //4'b1010, QSGMII x1 + Copper x4 mode + case 15: //4'b1111, SGMII(PHY) x4 + Copper x4 mode + default: + return YT_PHY_MODE_UTP; + } +} + +#if (YTPHY_861X_ABC_VER) +static int yt8614_get_port_from_phydev(struct phy_device *phydev) +{ + int tmp = ytphy_read_ext(phydev, 0xa0ff); + int phy_addr = 0; + +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + phy_addr = (unsigned int)phydev->addr; +#else + phy_addr = (unsigned int)phydev->mdio.addr; +#endif + + if ((phy_addr - tmp) < 0) { + ytphy_write_ext(phydev, 0xa0ff, phy_addr); + tmp = phy_addr; + } + + return (phy_addr - tmp); +} +#endif + +static int yt8614_config_init(struct phy_device *phydev) +{ + int ret = 0; + int val, hw_strap_mode; + unsigned int retries = 12; +#if (YTPHY_861X_ABC_VER) + int port = 0; +#endif + phydev->irq = PHY_POLL; + + /* NOTE: this function should not be called more than one for each chip. */ + hw_strap_mode = ytphy_read_ext(phydev, 0xa007) & 0xf; + +#if (YTPHY_861X_ABC_VER) + port = yt8614_get_port_from_phydev(phydev); +#endif + + ytphy_write_ext(phydev, 0xa000, 0); + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE) + ret = ytphy_config_init(phydev); +#else + ret = genphy_config_init(phydev); +#endif + if (ret < 0) + return ret; + + /* for utp to optimize signal */ + ret = ytphy_write_ext(phydev, 0x41, 0x33); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x42, 0x66); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x43, 0xaa); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x44, 0xd0d); + if (ret < 0) + return ret; + +#if (YTPHY_861X_ABC_VER) + if (port == 2) { + ret = ytphy_write_ext(phydev, 0x57, 0x2929); + if (ret < 0) + return ret; + } +#endif + + /* soft reset to take config effect */ + val = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, val | BMCR_RESET); + do { + msleep(50); + ret = phy_read(phydev, MII_BMCR); + if (ret < 0) + return ret; + } while ((ret & BMCR_RESET) && --retries); + if (ret & BMCR_RESET) + return -ETIMEDOUT; + + /* for QSGMII optimization */ + ytphy_write_ext(phydev, 0xa000, 0x02); + ret = ytphy_write_ext(phydev, 0x3, 0x4F80); + if (ret < 0) { + ytphy_write_ext(phydev, 0xa000, 0); + return ret; + } + ret = ytphy_write_ext(phydev, 0xe, 0x4F80); + if (ret < 0) { + ytphy_write_ext(phydev, 0xa000, 0); + return ret; + } + + /* for SGMII optimization */ + ytphy_write_ext(phydev, 0xa000, 0x03); + ret = ytphy_write_ext(phydev, 0x3, 0x2420); + if (ret < 0) { + ytphy_write_ext(phydev, 0xa000, 0); + return ret; + } + ret = ytphy_write_ext(phydev, 0xe, 0x24a0); + if (ret < 0) { + ytphy_write_ext(phydev, 0xa000, 0); + return ret; + } + + /* back up to utp*/ + ytphy_write_ext(phydev, 0xa000, 0); + +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode: %d\n", __func__, phydev->addr, hw_strap_mode); +#else + netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode: %d\n", __func__, phydev->mdio.addr, hw_strap_mode); +#endif + return ret; +} + +int yt8618_aneg_done(struct phy_device *phydev) +{ +#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE) + return genphy_aneg_done(phydev); +#else + return 1; +#endif +} + +int yt8614_aneg_done(struct phy_device *phydev) +{ + int link_fiber = 0, link_utp = 0; + + if (YT8614_PHY_MODE_CURR & YT_PHY_MODE_FIBER) { + /* reading Fiber */ + ytphy_write_ext(phydev, 0xa000, 3); + link_fiber = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YT8521_LINK_STATUS_BIT))); + } + + if (YT8614_PHY_MODE_CURR & YT_PHY_MODE_UTP) { + /* reading UTP */ + ytphy_write_ext(phydev, 0xa000, 0); + link_utp = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YT8521_LINK_STATUS_BIT))); + } + + return !!(link_fiber | link_utp); +} + +static int yt8614_read_status(struct phy_device *phydev) +{ + int ret; + int val, yt8614_fiber_latch_val, yt8614_fiber_curr_val; + int link; + int link_utp = 0, link_fiber = 0; + + if (YT8614_PHY_MODE_CURR & YT_PHY_MODE_UTP) { + /* switch to utp and reading regs */ + ret = ytphy_write_ext(phydev, 0xa000, 0); + if (ret < 0) + return ret; + + val = phy_read(phydev, REG_PHY_SPEC_STATUS); + if (val < 0) + return val; + + link = val & (BIT(YT8521_LINK_STATUS_BIT)); + if (link) { + link_utp = 1; + // here is same as 8521 and re-use the function; + yt8521_adjust_status(phydev, val, 1); + } else { + link_utp = 0; + } + } + + if (YT8614_PHY_MODE_CURR & YT_PHY_MODE_FIBER) { + /* reading Fiber/sgmii */ + ret = ytphy_write_ext(phydev, 0xa000, 3); + if (ret < 0) + return ret; + + val = phy_read(phydev, REG_PHY_SPEC_STATUS); + if (val < 0) + return val; + + /* for fiber, from 1000m to 100m, there is not link down from 0x11, + * and check reg 1 to identify such case + */ + yt8614_fiber_latch_val = phy_read(phydev, MII_BMSR); + yt8614_fiber_curr_val = phy_read(phydev, MII_BMSR); + link = val & (BIT(YT8521_LINK_STATUS_BIT)); + if (link && yt8614_fiber_latch_val != yt8614_fiber_curr_val) { + link = 0; +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n", + __func__, phydev->addr, yt8614_fiber_latch_val, yt8614_fiber_curr_val); +#else + netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n", + __func__, phydev->mdio.addr, yt8614_fiber_latch_val, yt8614_fiber_curr_val); +#endif + } + + if (link) { + link_fiber = 1; + yt8521_adjust_status(phydev, val, 0); + } else { + link_fiber = 0; + } + } + + if (link_utp || link_fiber) { + if (phydev->link == 0) +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media %s\n", + __func__, phydev->addr, (link_utp && link_fiber) ? "both UTP and Fiber" : (link_utp ? "UTP" : "Fiber")); +#else + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media %s\n", + __func__, phydev->mdio.addr, (link_utp && link_fiber) ? "both UTP and Fiber" : (link_utp ? "UTP" : "Fiber")); +#endif + phydev->link = 1; + } else { + if (phydev->link == 1) +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr); +#else + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr); +#endif + phydev->link = 0; + } + + if (YT8614_PHY_MODE_CURR & YT_PHY_MODE_UTP) { + if (link_utp) + ytphy_write_ext(phydev, 0xa000, 0); + } + return 0; +} + +static int yt8618_read_status(struct phy_device *phydev) +{ + int ret; + /* maybe for 8614 yt8521_fiber_latch_val, yt8521_fiber_curr_val; */ + int val; + int link; + int link_utp = 0, link_fiber = 0; + + /* switch to utp and reading regs */ + ret = ytphy_write_ext(phydev, 0xa000, 0); + if (ret < 0) + return ret; + + val = phy_read(phydev, REG_PHY_SPEC_STATUS); + if (val < 0) + return val; + + link = val & (BIT(YT8521_LINK_STATUS_BIT)); + if (link) { + link_utp = 1; + yt8521_adjust_status(phydev, val, 1); + } else { + link_utp = 0; + } + + if (link_utp || link_fiber) + phydev->link = 1; + else + phydev->link = 0; + + return 0; +} + +int yt8618_suspend(struct phy_device *phydev) +{ +#if !(SYS_WAKEUP_BASED_ON_ETH_PKT) + int value; + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_lock(&phydev->lock); +#else + /* no need lock in 4.19 */ +#endif + + ytphy_write_ext(phydev, 0xa000, 0); + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value | BMCR_PDOWN); + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_unlock(&phydev->lock); +#else + /* no need lock/unlock in 4.19 */ +#endif +#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/ + + return 0; +} + +int yt8618_resume(struct phy_device *phydev) +{ +#if !(SYS_WAKEUP_BASED_ON_ETH_PKT) + int value; + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_lock(&phydev->lock); +#else + /* no need lock/unlock in 4.19 */ +#endif + + ytphy_write_ext(phydev, 0xa000, 0); + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN); + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_unlock(&phydev->lock); +#else + /* no need lock/unlock in 4.19 */ +#endif +#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/ + + return 0; +} + +int yt8614_suspend(struct phy_device *phydev) +{ +#if !(SYS_WAKEUP_BASED_ON_ETH_PKT) + int value; + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_lock(&phydev->lock); +#else + /* no need lock in 4.19 */ +#endif + + ytphy_write_ext(phydev, 0xa000, 0); + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value | BMCR_PDOWN); + + ytphy_write_ext(phydev, 0xa000, 3); + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value | BMCR_PDOWN); + + ytphy_write_ext(phydev, 0xa000, 0); + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_unlock(&phydev->lock); +#else + /* no need lock/unlock in 4.19 */ +#endif +#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/ + + return 0; +} + +int yt8614_resume(struct phy_device *phydev) +{ +#if !(SYS_WAKEUP_BASED_ON_ETH_PKT) + int value; + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_lock(&phydev->lock); +#else + /* no need lock/unlock in 4.19 */ +#endif + + ytphy_write_ext(phydev, 0xa000, 0); + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN); + + ytphy_write_ext(phydev, 0xa000, 3); + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN); + + ytphy_write_ext(phydev, 0xa000, 0); + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + mutex_unlock(&phydev->lock); +#else + /* no need lock/unlock in 4.19 */ +#endif +#endif /* !(SYS_WAKEUP_BASED_ON_ETH_PKT) */ + + return 0; +} + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +#else +int yt8821_soft_reset(struct phy_device *phydev) +{ + int ret, val; + + val = ytphy_read_ext(phydev, 0xa001); + ytphy_write_ext(phydev, 0xa001, (val & ~0x8000)); + + ytphy_write_ext(phydev, 0xa000, 0); + ret = ytphy_soft_reset(phydev); + + return ret; +} +#endif + +static int yt8821_init(struct phy_device *phydev) +{ + int ret = 0; + int val = 0; + + /* sds pll cfg */ + ret = ytphy_write_ext(phydev, 0xa050, 0x1000); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0xa000, 0x2); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x23, 0x47a1); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0xbd, 0x3547); + if (ret < 0) + return ret; + + /* wait 1s */ + msleep(1000); + + /* calibration dcc */ + ret = ytphy_write_ext(phydev, 0xbd, 0xa547); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x29, 0x3003); + if (ret < 0) + return ret; + + /* sds driver swing */ + ret = ytphy_write_ext(phydev, 0x25, 0x788); + if (ret < 0) + return ret; + + /* phy cfg */ + ret = ytphy_write_ext(phydev, 0xa000, 0x0); + if (ret < 0) + return ret; + + /* phy template cfg */ + ret = ytphy_write_ext(phydev, 0x471, 0x4545); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x476, 0x4848); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x477, 0x4848); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x478, 0x4848); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x479, 0x4848); + if (ret < 0) + return ret; + + /* calibrate phy lc pll */ + ret = ytphy_write_ext(phydev, 0x600, 0x2300); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x8, 0x8041); + if (ret < 0) + return ret; + + /* prm_small_lng/med */ + ret = ytphy_write_ext(phydev, 0x388, 0x90); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x387, 0x90); + if (ret < 0) + return ret; + + /* echo_delay_cfg */ + ret = ytphy_write_ext(phydev, 0x3, 0xa026); + if (ret < 0) + return ret; + + /* pbo setting */ + ret = ytphy_write_ext(phydev, 0x47e, 0x3535); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x47f, 0x3535); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x480, 0x3535); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x481, 0x3535); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x483, 0x2a2a); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x484, 0x2a2a); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x485, 0x2a2a); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x486, 0x2a2a); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x488, 0x2121); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x489, 0x2121); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x48a, 0x2121); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x48b, 0x2121); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x48d, 0x1a1a); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x48e, 0x1a1a); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x48f, 0x1a1a); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x490, 0x1a1a); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x492, 0x1515); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x493, 0x1515); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x494, 0x1515); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x495, 0x1515); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x497, 0x1111); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x498, 0x1111); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x499, 0x1111); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x49a, 0x1111); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x49c, 0x0d0d); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x49d, 0x0d0d); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x49e, 0x0d0d); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0x49f, 0x0d0d); + if (ret < 0) + return ret; + ret = ytphy_write_ext(phydev, 0xa052, 0x7); + if (ret < 0) + return ret; + + /* fast link down cfg */ + ret = ytphy_write_ext(phydev, 0x355, 0x7d07); + if (ret < 0) + return ret; + + /* soft reset */ + val = phy_read(phydev, MII_BMCR); + if (val < 0) + return val; + ret = phy_write(phydev, MII_BMCR, val | BMCR_RESET); + + return ret; +} + +static int yt8821_config_init(struct phy_device *phydev) +{ + int ret; + int val, hw_strap_mode; + + phydev->irq = PHY_POLL; + + /* NOTE: this function should not be called more than one for each chip. */ + hw_strap_mode = ytphy_read_ext(phydev, 0xa001) & 0x7; + + ytphy_write_ext(phydev, 0xa000, 0); +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE) + ret = ytphy_config_init(phydev); +#else + ret = genphy_config_init(phydev); +#endif + if (ret < 0) + return ret; + + ret = yt8821_init(phydev); + if (ret < 0) + return ret; + + /* disable auto sleep */ + val = ytphy_read_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1); + if (val < 0) + return val; + + val &= (~BIT(YT8521_EN_SLEEP_SW_BIT)); + + ret = ytphy_write_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1, val); + if (ret < 0) + return ret; + +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s done, phy addr: %d, strap mode = %d\n", __func__, phydev->addr, hw_strap_mode); +#else + netdev_info(phydev->attached_dev, "%s done, phy addr: %d, strap mode = %d\n", __func__, phydev->mdio.addr, hw_strap_mode); +#endif + + return ret; +} + +/* for fiber mode, there is no 10M speed mode and + * this function is for this purpose. + */ +static int yt8821_adjust_status(struct phy_device *phydev, int val, int is_utp) +{ + int speed_mode, duplex; + int speed_mode_bit15_14, speed_mode_bit9; +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + int speed = -1; +#else + int speed = SPEED_UNKNOWN; +#endif + + if (is_utp) + duplex = (val & YT8512_DUPLEX) >> YT8521_DUPLEX_BIT; + else + duplex = 1; + + /* Bit9-Bit15-Bit14 speed mode 100---2.5G; 010---1000M; 001---100M; 000---10M */ + speed_mode_bit15_14 = (val & YT8521_SPEED_MODE) >> YT8521_SPEED_MODE_BIT; + speed_mode_bit9 = (val & BIT(9)) >> 9; + speed_mode = (speed_mode_bit9 << 2) | speed_mode_bit15_14; + switch (speed_mode) { + case 0: + if (is_utp) + speed = SPEED_10; + break; + case 1: + speed = SPEED_100; + break; + case 2: + speed = SPEED_1000; + break; + case 4: + speed = SPEED_2500; + break; + default: +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) + speed = -1; +#else + speed = SPEED_UNKNOWN; +#endif + break; + } + + phydev->speed = speed; + phydev->duplex = duplex; + + return 0; +} + +static int yt8821_read_status(struct phy_device *phydev) +{ + int ret; + int val; + int yt8521_fiber_latch_val; + int yt8521_fiber_curr_val; + int link; + int link_utp = 0, link_fiber = 0; + + if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) { + /* reading UTP */ + ret = ytphy_write_ext(phydev, 0xa000, 0); + if (ret < 0) + return ret; + + val = phy_read(phydev, REG_PHY_SPEC_STATUS); + if (val < 0) + return val; + + link = val & (BIT(YT8521_LINK_STATUS_BIT)); + if (link) { + link_utp = 1; + yt8821_adjust_status(phydev, val, 1); /* speed(2500), duplex */ + } else { + link_utp = 0; + } + } //(YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) + + if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_UTP) { + /* reading Fiber */ + ret = ytphy_write_ext(phydev, 0xa000, 2); + if (ret < 0) + return ret; + + val = phy_read(phydev, REG_PHY_SPEC_STATUS); + if (val < 0) + return val; + + //note: below debug information is used to check multiple PHy ports. + + /* for fiber, from 1000m to 100m, there is not link down from 0x11, + * and check reg 1 to identify such case this is important for Linux + * kernel for that, missing linkdown event will cause problem. + */ + yt8521_fiber_latch_val = phy_read(phydev, MII_BMSR); + yt8521_fiber_curr_val = phy_read(phydev, MII_BMSR); + link = val & (BIT(YT8521_LINK_STATUS_BIT)); + if (link && yt8521_fiber_latch_val != yt8521_fiber_curr_val) { + link = 0; +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n", + __func__, phydev->addr, yt8521_fiber_latch_val, yt8521_fiber_curr_val); +#else + netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n", + __func__, phydev->mdio.addr, yt8521_fiber_latch_val, yt8521_fiber_curr_val); +#endif + } + + if (link) { + link_fiber = 1; + yt8821_adjust_status(phydev, val, 0); + } else { + link_fiber = 0; + } + } //(YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_UTP) + + if (link_utp || link_fiber) { + if (phydev->link == 0) +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: %s, mii reg 0x11 = 0x%x\n", + __func__, phydev->addr, (link_utp && link_fiber) ? "UNKNOWN MEDIA" : (link_utp ? "UTP" : "Fiber"), (unsigned int)val); +#else + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: %s, mii reg 0x11 = 0x%x\n", + __func__, phydev->mdio.addr, (link_utp && link_fiber) ? "UNKNOWN MEDIA" : (link_utp ? "UTP" : "Fiber"), (unsigned int)val); +#endif + phydev->link = 1; + } else { + if (phydev->link == 1) +#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr); +#else + netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr); +#endif + + phydev->link = 0; + } + + if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) { + if (link_fiber) + ytphy_write_ext(phydev, 0xa000, 2); + if (link_utp) + ytphy_write_ext(phydev, 0xa000, 0); + } + return 0; +} + +#if (KERNEL_VERSION(5, 1, 21) < LINUX_VERSION_CODE) +static int yt8821_get_features(struct phy_device *phydev) +{ + linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported, 1); + return genphy_read_abilities(phydev); +} +#endif + +static struct phy_driver ytphy_drvs[] = { + { + .phy_id = PHY_ID_YT8010, + .name = "YT8010 Automotive Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_BASIC_FEATURES, + .flags = PHY_POLL, +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +#else + .soft_reset = yt8010_soft_reset, +#endif + .config_aneg = yt8010_config_aneg, +#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE) + .aneg_done = yt8010_aneg_done, +#endif +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE) + .config_init = ytphy_config_init, +#else + .config_init = genphy_config_init, +#endif + .read_status = yt8010_read_status, + }, { + .phy_id = PHY_ID_YT8010AS, + .name = "YT8010AS Automotive Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_BASIC_FEATURES, + .flags = PHY_POLL, +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +#else + .soft_reset = yt8010AS_soft_reset, +#endif +#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE) + .aneg_done = yt8010_aneg_done, +#endif + .config_init = yt8010AS_config_init, + .read_status = yt8010_read_status, + }, { + .phy_id = PHY_ID_YT8510, + .name = "YT8510 100/10Mb Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_BASIC_FEATURES, + .flags = PHY_POLL, + .config_aneg = genphy_config_aneg, +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE) + .config_init = ytphy_config_init, +#else + .config_init = genphy_config_init, +#endif + .read_status = genphy_read_status, + }, { + .phy_id = PHY_ID_YT8511, + .name = "YT8511 Gigabit Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_GBIT_FEATURES, + .flags = PHY_POLL, + .config_aneg = genphy_config_aneg, +#if GMAC_CLOCK_INPUT_NEEDED + .config_init = yt8511_config_init, +#else +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE) + .config_init = ytphy_config_init, +#else + .config_init = genphy_config_init, +#endif +#endif + .read_status = genphy_read_status, + .suspend = genphy_suspend, + .resume = genphy_resume, + }, { + .phy_id = PHY_ID_YT8512, + .name = "YT8512 Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_BASIC_FEATURES, + .flags = PHY_POLL, + .config_aneg = genphy_config_aneg, + .config_init = yt8512_config_init, + .read_status = yt8512_read_status, + .suspend = genphy_suspend, + .resume = genphy_resume, + }, { + .phy_id = PHY_ID_YT8512B, + .name = "YT8512B Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_BASIC_FEATURES, + .flags = PHY_POLL, + .config_aneg = genphy_config_aneg, + .config_init = yt8512_config_init, + .read_status = yt8512_read_status, + .suspend = genphy_suspend, + .resume = genphy_resume, + }, { + .phy_id = PHY_ID_YT8521, + .name = "YT8521 Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_GBIT_FEATURES, + .flags = PHY_POLL, +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +#else + .soft_reset = yt8521_soft_reset, +#endif + .config_aneg = genphy_config_aneg, +#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE) + .aneg_done = yt8521_aneg_done, +#endif + .config_init = yt8521_config_init, + .read_status = yt8521_read_status, + .suspend = yt8521_suspend, + .resume = yt8521_resume, +#if (YTPHY_WOL_FEATURE_ENABLE) + .get_wol = &ytphy_wol_feature_get, + .set_wol = &ytphy_wol_feature_set, +#endif + }, { + /* same as 8521 */ + .phy_id = PHY_ID_YT8531S, + .name = "YT8531S Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_GBIT_FEATURES, + .flags = PHY_POLL, +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +#else + .soft_reset = yt8521_soft_reset, +#endif + .config_aneg = genphy_config_aneg, +#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE) + .aneg_done = yt8521_aneg_done, +#endif + .config_init = yt8531S_config_init, + .read_status = yt8521_read_status, + .suspend = yt8521_suspend, + .resume = yt8521_resume, +#if (YTPHY_WOL_FEATURE_ENABLE) + .get_wol = &ytphy_wol_feature_get, + .set_wol = &ytphy_wol_feature_set, +#endif + }, { + /* same as 8511 */ + .phy_id = PHY_ID_YT8531, + .name = "YT8531 Gigabit Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_GBIT_FEATURES, + .flags = PHY_POLL, + .config_aneg = genphy_config_aneg, + + .config_init = yt8531_config_init, + .read_status = genphy_read_status, + .suspend = genphy_suspend, + .resume = genphy_resume, +#if (YTPHY_WOL_FEATURE_ENABLE) + .get_wol = &ytphy_wol_feature_get, + .set_wol = &ytphy_wol_feature_set, +#endif + }, { + .phy_id = PHY_ID_YT8618, + .name = "YT8618 Ethernet", + .phy_id_mask = MOTORCOMM_MPHY_ID_MASK, + .features = PHY_GBIT_FEATURES, + .flags = PHY_POLL, +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +#else + .soft_reset = yt8618_soft_reset, +#endif + .config_aneg = genphy_config_aneg, +#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE) + .aneg_done = yt8618_aneg_done, +#endif + .config_init = yt8618_config_init, + .read_status = yt8618_read_status, + .suspend = yt8618_suspend, + .resume = yt8618_resume, + }, + { + .phy_id = PHY_ID_YT8614, + .name = "YT8614 Ethernet", + .phy_id_mask = MOTORCOMM_MPHY_ID_MASK_8614, + .features = PHY_GBIT_FEATURES, + .flags = PHY_POLL, +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +#else + .soft_reset = yt8614_soft_reset, +#endif + .config_aneg = genphy_config_aneg, +#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE) + .aneg_done = yt8614_aneg_done, +#endif + .config_init = yt8614_config_init, + .read_status = yt8614_read_status, + .suspend = yt8614_suspend, + .resume = yt8614_resume, + }, + { + .phy_id = PHY_ID_YT8821, + .name = "YT8821 2.5Gb Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK_8821, +#if (KERNEL_VERSION(5, 2, 0) > LINUX_VERSION_CODE) + .features = PHY_GBIT_FEATURES, +#endif + .flags = PHY_POLL, +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +#else + .soft_reset = yt8821_soft_reset, +#endif + .config_aneg = genphy_config_aneg, +#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE) + .aneg_done = yt8521_aneg_done, +#endif +#if (KERNEL_VERSION(5, 1, 21) < LINUX_VERSION_CODE) + .get_features = yt8821_get_features, +#endif + .config_init = yt8821_config_init, + .read_status = yt8821_read_status, + .suspend = yt8521_suspend, + .resume = yt8521_resume, + }, +}; + +#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) +static int ytphy_drivers_register(struct phy_driver *phy_drvs, int size) +{ + int i, j; + int ret; + + for (i = 0; i < size; i++) { + ret = phy_driver_register(&phy_drvs[i]); + if (ret) + goto err; + } + + return 0; + +err: + for (j = 0; j < i; j++) + phy_driver_unregister(&phy_drvs[j]); + + return ret; +} + +static void ytphy_drivers_unregister(struct phy_driver *phy_drvs, int size) +{ + int i; + + for (i = 0; i < size; i++) + phy_driver_unregister(&phy_drvs[i]); +} + +static int __init ytphy_init(void) +{ + return ytphy_drivers_register(ytphy_drvs, ARRAY_SIZE(ytphy_drvs)); +} + +static void __exit ytphy_exit(void) +{ + ytphy_drivers_unregister(ytphy_drvs, ARRAY_SIZE(ytphy_drvs)); +} + +module_init(ytphy_init); +module_exit(ytphy_exit); +#else +/* for linux 4.x */ +module_phy_driver(ytphy_drvs); +#endif + +MODULE_DESCRIPTION("Motorcomm PHY driver"); +MODULE_AUTHOR("Leilei Zhao"); +MODULE_LICENSE("GPL"); + +static struct mdio_device_id __maybe_unused motorcomm_tbl[] = { + { PHY_ID_YT8010, MOTORCOMM_PHY_ID_MASK }, + { PHY_ID_YT8510, MOTORCOMM_PHY_ID_MASK }, + { PHY_ID_YT8511, MOTORCOMM_PHY_ID_MASK }, + { PHY_ID_YT8512, MOTORCOMM_PHY_ID_MASK }, + { PHY_ID_YT8512B, MOTORCOMM_PHY_ID_MASK }, + { PHY_ID_YT8521, MOTORCOMM_PHY_ID_MASK }, + { PHY_ID_YT8531S, MOTORCOMM_PHY_ID_8531_MASK }, + { PHY_ID_YT8531, MOTORCOMM_PHY_ID_8531_MASK }, + { PHY_ID_YT8618, MOTORCOMM_MPHY_ID_MASK }, + { PHY_ID_YT8614, MOTORCOMM_MPHY_ID_MASK_8614 }, + { PHY_ID_YT8821, MOTORCOMM_PHY_ID_MASK_8821 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, motorcomm_tbl); + diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index 028b287466fbf9bf69130282c24e7dbe26e12a92..a1a024fe78e36ea9a2cf9c86f86d85629396235a 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -278,5 +278,15 @@ config VMD To compile this driver as a module, choose M here: the module will be called vmd. +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. + source "drivers/pci/controller/dwc/Kconfig" endmenu diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile index d56a507495c5edd902a6012420924b9b837203a5..45fbf1ff335477bdeb527b9afd40a3563b8735d3 100644 --- a/drivers/pci/controller/Makefile +++ b/drivers/pci/controller/Makefile @@ -29,6 +29,8 @@ obj-$(CONFIG_PCIE_MEDIATEK) += pcie-mediatek.o obj-$(CONFIG_PCIE_MOBIVEIL) += pcie-mobiveil.o obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o obj-$(CONFIG_VMD) += vmd.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..7be67f0b258240c1497b585b50861dfd50ae4d51 --- /dev/null +++ b/drivers/pci/controller/pcie-phytium-ep.c @@ -0,0 +1,473 @@ +// 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, + 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, + 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->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, + 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, + 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->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, + 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 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) +{ + 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, + 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); + + list_for_each_entry(epf, &epc->pci_epf, list) + pci_epf_linkup(epf); + + 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; + + dev_dbg(dev, "enter %s\n", __func__); + 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)); + 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..bfdc062b165fad15d44bbdf40d1fb1ce6f63cc52 --- /dev/null +++ b/drivers/pci/controller/pcie-phytium-ep.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium pcie 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..b6dfb3fab45b0d63f01eb51bf89d290bfe0b0b57 --- /dev/null +++ b/drivers/pci/controller/pcie-phytium-register.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium pcie endpoint 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/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h index ef60718070728c227ffd954f2d3777292e5ffd53..6a77382672303338dc99126444901373e55e59fe 100644 --- a/drivers/pci/hotplug/pciehp.h +++ b/drivers/pci/hotplug/pciehp.h @@ -129,6 +129,10 @@ struct controller { unsigned int ist_running; int request_result; wait_queue_head_t requester; +#ifdef CONFIG_ARCH_PHYTIUM + u32 buses; + u16 slot_ctrl_t; +#endif }; /** diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index c71964e29b0135537ad6815aa8bc2cfc49cac004..7917d2dae15849fb0dd9d7a7ac6d4da6697be21c 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -226,6 +226,10 @@ void pciehp_handle_disable_request(struct slot *slot) void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) { struct controller *ctrl = slot->ctrl; +#ifdef CONFIG_ARCH_PHYTIUM + struct pci_dev *pdev = ctrl->pcie->port; + u16 slot_ctrl_val; +#endif bool link_active; u8 present; @@ -248,6 +252,20 @@ void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) ctrl_info(ctrl, "Slot(%s): Card not present\n", slot_name(slot)); pciehp_disable_slot(slot); +#ifdef CONFIG_ARCH_PHYTIUM + if ((ctrl->buses > 0) && (ctrl->slot_ctrl > 0)) { + pci_write_config_dword(pdev, PCI_PRIMARY_BUS, ctrl->buses); + slot_ctrl_val = ctrl->slot_ctrl_t | PCI_EXP_SLTCTL_ABPE | + PCI_EXP_SLTCTL_PFDE | PCI_EXP_SLTCTL_MRLSCE | + PCI_EXP_SLTCTL_PDCE | PCI_EXP_SLTCTL_CCIE | + PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_ATTN_IND_BLINK | + PCI_EXP_SLTCTL_PWR_IND_ON | PCI_EXP_SLTCTL_PWR_OFF | + PCI_EXP_SLTCTL_DLLSCE; + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl_val); + ctrl_info(ctrl, "Ctrl buses=0x%x, slot_ctrl=0x%x\n", + ctrl->buses, slot_ctrl_val); + } +#endif break; default: mutex_unlock(&slot->lock); diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index 2795445233b3b9117d44f35a12cea41628f3fc64..5d9feaa9c8b2243ec204ba63865674461825ac62 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -636,7 +636,13 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) struct pci_dev *pdev = ctrl_dev(ctrl); struct slot *slot = ctrl->slot; irqreturn_t ret; +#ifdef CONFIG_ARCH_PHYTIUM + u32 events, buses; + u16 slot_ctrl; + bool link_active; +#else u32 events; +#endif ctrl->ist_running = true; pci_config_pm_runtime_get(pdev); @@ -656,6 +662,23 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) goto out; } +#ifdef CONFIG_ARCH_PHYTIUM + if(slot->state == ON_STATE) { + pci_read_config_dword(pdev, PCI_PRIMARY_BUS, &buses); + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + ctrl->buses = buses; + ctrl->slot_ctrl_t = slot_ctrl; + ctrl_dbg(ctrl, "Ctrl buses=0x%x, slot_ctrl=0x%x\n", + ctrl->buses, ctrl->slot_ctrl_t); + } + + mdelay(1000); + + link_active = pciehp_check_link_active(ctrl); + if((slot->state == ON_STATE) && (link_active == false)) + events |= PCI_EXP_SLTSTA_DLLSC; +#endif + /* Check Attention Button Pressed */ if (events & PCI_EXP_SLTSTA_ABP) { ctrl_info(ctrl, "Slot(%s): Attention button pressed\n", diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h index 2498b2d340095e53cf2191bdf992ab866139614a..b1ac9eca836409e3c08261eab416f22d2bbf547a 100644 --- a/drivers/pci/pcie/portdrv.h +++ b/drivers/pci/pcie/portdrv.h @@ -116,6 +116,18 @@ void pcie_port_bus_unregister(void); struct pci_dev; +#ifdef CONFIG_HOTPLUG_PCI_PCIE +extern bool pciehp_msi_disabled; + +static inline bool pciehp_no_msi(void) +{ + return pciehp_msi_disabled; +} + +#else /* !CONFIG_HOTPLUG_PCI_PCIE */ +static inline bool pciehp_no_msi(void) { return false; } +#endif /* !CONFIG_HOTPLUG_PCI_PCIE */ + #ifdef CONFIG_PCIE_PME extern bool pcie_pme_msi_disabled; diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 7c37d815229e9ee20e7732695dda58344fc77a20..f8d5a93ba21e5cc17b3d0fced4aeb2fb2fbb01fb 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -25,6 +25,17 @@ struct portdrv_service_data { u32 service; }; +bool pciehp_msi_disabled; + +static int __init pciehp_setup(char *str) +{ + if (!strncmp(str, "nomsi", 5)) + pciehp_msi_disabled = true; + + return 1; +} +__setup("pcie_hp=", pciehp_setup); + /** * release_pcie_device - free PCI Express port service device structure * @dev: Port service device to release @@ -166,13 +177,16 @@ static int pcie_init_service_irqs(struct pci_dev *dev, int *irqs, int mask) irqs[i] = -1; /* - * If we support PME but can't use MSI/MSI-X for it, we have to - * fall back to INTx or other interrupts, e.g., a system shared - * interrupt. + * If we support PME or hotplug, but we can't use MSI/MSI-X for + * them, we have to fall back to INTx or other interrupts, e.g., a + * system shared interrupt. */ if ((mask & PCIE_PORT_SERVICE_PME) && pcie_pme_no_msi()) goto legacy_irq; + if ((mask & PCIE_PORT_SERVICE_HP) && pciehp_no_msi()) + goto legacy_irq; + /* Try to use MSI-X or MSI if supported */ if (pcie_port_enable_irq_vec(dev, irqs, mask) == 0) return 0; diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 504d252716f2e10db4914c8de651dff05106b82c..498081d89ae4c16c596a32eac9b2adc7b5c7102b 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -322,6 +322,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_PUV3 tristate "PKUnity NetBook-0916 PWM support" depends on ARCH_PUV3 diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 9c676a0dadf55e2b06fe81a6d620806393c98d48..7f5d9d6da1f43f909d7bffbd74ecb1a3936399b2 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.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_PUV3) += pwm-puv3.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.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..76ddf5d1816131d90b7742e2603b5a1ea96eb576 --- /dev/null +++ b/drivers/pwm/pwm-phytium.c @@ -0,0 +1,572 @@ +// 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; + + unsigned long clk_rate; + 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; + + cycles = our_chip->clk_rate; + 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; + + cycles = our_chip->clk_rate; + 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); + cycles = our_chip->clk_rate; + 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, + 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 fwnode_handle *np) +{ + int nb, ret, array_size; + unsigned int i; + + array_size = fwnode_property_read_u32_array(np, "phytium,db", NULL, 0); + nb = array_size / (sizeof(struct phytium_pwm_param) / sizeof(u32)); + if (nb <= 0 || nb > MAX_PARAMETER) + return -EINVAL; + + priv->num_parameters = nb; + ret = fwnode_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 fwnode_handle *np = dev_fwnode(dev); + 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 (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 (IS_ERR(chip->base)) { + dev_err(dev, "failed to get base_addr\n"); + return PTR_ERR(chip->base); + } + + if (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; + } + chip->clk_rate = clk_get_rate(chip->base_clk); + + } else if (has_acpi_companion(dev)){ + if(fwnode_property_read_u32(dev_fwnode(dev),"clock-frequency", (u32 *)&(chip->clk_rate) ) <0) + chip->clk_rate = 50000000; + } + + 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); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_pwm_acpi_ids[] = { + { "PHYT0029", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(acpi, phytium_pwm_acpi_ids); +#endif + +static const struct of_device_id phytium_pwm_matches[] = { + { .compatible = "phytium,pwm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, phytium_pwm_matches); + +static struct platform_driver pwm_phytium_driver = { + .driver = { + .name = "phytium-pwm", + .pm = &phytium_pwm_dev_pm_ops, + .of_match_table = phytium_pwm_matches, + .acpi_match_table = ACPI_PTR(phytium_pwm_acpi_ids), + }, + .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 v2"); diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c index 72bdda4ccebfd27d27a07205c6d4aa03299a567b..cc96cbca268a63bb2f42866ee575da5eb6d6db9b 100644 --- a/drivers/pwm/sysfs.c +++ b/drivers/pwm/sysfs.c @@ -267,7 +267,7 @@ 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); + dev_set_name(&export->child, "pwm%u", pwm->pwm); ret = device_register(&export->child); if (ret) { diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index e48069db17033bb79ca2ad68d6e3fd5ef6ca2633..3ead97058d07a6e8cb20bd9ff0aa76e0cea48e08 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -42,6 +42,7 @@ #include #include #include +#include #include "remoteproc_internal.h" @@ -257,6 +258,9 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i) rsc = (void *)rproc->table_ptr + rvdev->rsc_offset; rsc->vring[i].da = dma; rsc->vring[i].notifyid = notifyid; + + __flush_dcache_area(rproc->table_ptr, rproc->table_sz); + return 0; } @@ -1778,6 +1782,27 @@ void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type) } EXPORT_SYMBOL(rproc_report_crash); +void (*rproc_handle_arch_irq)(void); + +int rproc_set_handle_irq(void (handle_irq)(void)) +{ + if (rproc_handle_arch_irq) + return -EBUSY; + + rproc_handle_arch_irq = handle_irq; + return 0; +} +EXPORT_SYMBOL(rproc_set_handle_irq); + +void rproc_handle_ipi(int ipinr) +{ + if (rproc_handle_arch_irq) + rproc_handle_arch_irq(); + + return ; +} +EXPORT_SYMBOL(rproc_handle_ipi); + static int __init remoteproc_init(void) { rproc_init_sysfs(); diff --git a/drivers/remoteproc/remoteproc_virtio.c b/drivers/remoteproc/remoteproc_virtio.c index bbecd44df7e8de3b28ee10b7066c42913aec540a..8126935d0ac97e724db9d491789a82668c2f8941 100644 --- a/drivers/remoteproc/remoteproc_virtio.c +++ b/drivers/remoteproc/remoteproc_virtio.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "remoteproc_internal.h" @@ -177,6 +178,7 @@ static void rproc_virtio_set_status(struct virtio_device *vdev, u8 status) rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset; rsc->status = status; + __flush_dcache_area(rvdev->rproc->table_ptr, rvdev->rproc->table_sz); dev_dbg(&vdev->dev, "status: %d\n", status); } diff --git a/drivers/rpmsg/rpmsg_char.c b/drivers/rpmsg/rpmsg_char.c index d153fb1bf65f157a229b28d2a4cf00728b3a884f..8f0c4b0a3e73a7a4f48d9ff5c35fc295ec83b3b5 100644 --- a/drivers/rpmsg/rpmsg_char.c +++ b/drivers/rpmsg/rpmsg_char.c @@ -518,12 +518,18 @@ static void rpmsg_chrdev_remove(struct rpmsg_device *rpdev) put_device(&ctrldev->dev); } +struct rpmsg_device_id rpmsg_char_id_table[] = { + {.name = "rpmsg-openamp-demo-channel"}, + {} +}; + static struct rpmsg_driver rpmsg_chrdev_driver = { .probe = rpmsg_chrdev_probe, .remove = rpmsg_chrdev_remove, .drv = { .name = "rpmsg_chrdev", }, + .id_table = rpmsg_char_id_table, }; static int rpmsg_char_init(void) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig old mode 100644 new mode 100755 index b5845f16a3a2651108b97a057cffe8752064555d..f9efd766451f1389498e7bfe0c5061023eff3ec8 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -647,6 +647,26 @@ config RTC_DRV_S5M This driver can also be built as a module. If so, the module will be called rtc-s5m. +config RTC_DRV_SD3068 + tristate "ZXW Shenzhen whwave SD3068" + select REGMAP_I2C + help + If you say yes here you get support for the ZXW Shenzhen whwave + SD3068 RTC chips. + + This driver can also be built as a module. If so, the module + will be called rtc-sd3068 + +config RTC_DRV_SD3078 + tristate "ZXW Shenzhen whwave SD3078" + select REGMAP_I2C + help + If you say yes here you get support for the ZXW Shenzhen whwave + SD3078 RTC chips. + + This driver can also be built as a module. If so, the module + will be called rtc-sd3078 + endif # I2C comment "SPI RTC drivers" diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile old mode 100644 new mode 100755 index 5ff2fc0c361a84bcabe19642c9232b42cf0c25dd..e3172c48bd459f3de418e92cf544a522ba7a38d4 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -176,3 +176,5 @@ obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o obj-$(CONFIG_RTC_DRV_XGENE) += rtc-xgene.o obj-$(CONFIG_RTC_DRV_ZYNQMP) += rtc-zynqmp.o obj-$(CONFIG_RTC_DRV_GOLDFISH) += rtc-goldfish.o +obj-$(CONFIG_RTC_DRV_SD3068) += rtc-sd3068.o +obj-$(CONFIG_RTC_DRV_SD3078) += rtc-sd3078.o diff --git a/drivers/rtc/rtc-sd3068.c b/drivers/rtc/rtc-sd3068.c new file mode 100755 index 0000000000000000000000000000000000000000..6abd0cd852f3aa70cfb1bd594236baf423f9fbd0 --- /dev/null +++ b/drivers/rtc/rtc-sd3068.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * rtc-sd3068.c - RTC driver for some mostly-compatible I2C chips. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SD3068_REG_SC 0x00 +#define SD3068_REG_MN 0x01 +#define SD3068_REG_HR 0x02 +#define SD3068_REG_DW 0x03 +#define SD3068_REG_DM 0x04 +#define SD3068_REG_MO 0x05 +#define SD3068_REG_YR 0x06 + +#define SD3068_REG_CTRL1 0x0f +#define SD3068_REG_CTRL2 0x10 +#define SD3068_REG_CTRL3 0x11 + +#define KEY_WRITE1 0x80 +#define KEY_WRITE2 0x04 +#define KEY_WRITE3 0x80 + +#define NUM_TIME_REGS (SD3068_REG_YR - SD3068_REG_SC + 1) + +/* + * The sd3068 has write protection + * and we can choose whether or not to use it. + * Write protection is turned off by default. + */ +#define WRITE_PROTECT_EN 1 + +struct sd3068 { + struct rtc_device *rtc; + struct regmap *regmap; +}; + +/* + * In order to prevent arbitrary modification of the time register, + * when modification of the register, + * the "write" bit needs to be written in a certain order. + * 1. set WRITE1 bit + * 2. set WRITE2 bit + * 3. set WRITE3 bit + */ +static void sd3068_enable_reg_write(struct sd3068 *sd3068) +{ + regmap_update_bits(sd3068->regmap, SD3068_REG_CTRL2, + KEY_WRITE1, KEY_WRITE1); + regmap_update_bits(sd3068->regmap, SD3068_REG_CTRL1, + KEY_WRITE2, KEY_WRITE2); + regmap_update_bits(sd3068->regmap, SD3068_REG_CTRL1, + KEY_WRITE3, KEY_WRITE3); +} + +#if WRITE_PROTECT_EN +/* + * In order to prevent arbitrary modification of the time register, + * we should disable the write function. + * when disable write, + * the "write" bit needs to be clear in a certain order. + * 1. clear WRITE2 bit + * 2. clear WRITE3 bit + * 3. clear WRITE1 bit + */ +static void sd3068_disable_reg_write(struct sd3068 *sd3068) +{ + regmap_update_bits(sd3068->regmap, SD3068_REG_CTRL1, + KEY_WRITE2, 0); + regmap_update_bits(sd3068->regmap, SD3068_REG_CTRL1, + KEY_WRITE3, 0); + regmap_update_bits(sd3068->regmap, SD3068_REG_CTRL2, + KEY_WRITE1, 0); +} +#endif + +static int sd3068_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + unsigned char hour; + unsigned char rtc_data[NUM_TIME_REGS] = {0}; + struct i2c_client *client = to_i2c_client(dev); + struct sd3068 *sd3068 = i2c_get_clientdata(client); + int ret; + pr_debug("sd3068 read\n"); + + ret = regmap_bulk_read(sd3068->regmap, SD3068_REG_SC, rtc_data, + NUM_TIME_REGS); + if (ret < 0) { + dev_err(dev, "reading from RTC failed with err:%d\n", ret); + return ret; + } + + tm->tm_sec = bcd2bin(rtc_data[SD3068_REG_SC] & 0x7F); + tm->tm_min = bcd2bin(rtc_data[SD3068_REG_MN] & 0x7F); + + /* + * The sd3068 supports 12/24 hour mode. + * When getting time, + * we need to convert the 12 hour mode to the 24 hour mode. + */ + hour = rtc_data[SD3068_REG_HR]; + if (hour & 0x80) /* 24H MODE */ + tm->tm_hour = bcd2bin(rtc_data[SD3068_REG_HR] & 0x3F); + else if (hour & 0x20) /* 12H MODE PM */ + tm->tm_hour = bcd2bin(rtc_data[SD3068_REG_HR] & 0x1F) + 12; + else /* 12H MODE AM */ + tm->tm_hour = bcd2bin(rtc_data[SD3068_REG_HR] & 0x1F); + + tm->tm_mday = bcd2bin(rtc_data[SD3068_REG_DM] & 0x3F); + tm->tm_wday = rtc_data[SD3068_REG_DW] & 0x07; + tm->tm_mon = bcd2bin(rtc_data[SD3068_REG_MO] & 0x1F) - 1; + tm->tm_year = bcd2bin(rtc_data[SD3068_REG_YR]) + 100; + + return 0; +} + +static int sd3068_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + unsigned char rtc_data[NUM_TIME_REGS]; + struct i2c_client *client = to_i2c_client(dev); + struct sd3068 *sd3068 = i2c_get_clientdata(client); + int ret; + pr_debug("sd3068 set\n"); + + rtc_data[SD3068_REG_SC] = bin2bcd(tm->tm_sec); + rtc_data[SD3068_REG_MN] = bin2bcd(tm->tm_min); + rtc_data[SD3068_REG_HR] = bin2bcd(tm->tm_hour) | 0x80; + rtc_data[SD3068_REG_DM] = bin2bcd(tm->tm_mday); + rtc_data[SD3068_REG_DW] = tm->tm_wday & 0x07; + rtc_data[SD3068_REG_MO] = bin2bcd(tm->tm_mon) + 1; + rtc_data[SD3068_REG_YR] = bin2bcd(tm->tm_year - 100); + +#if WRITE_PROTECT_EN + sd3068_enable_reg_write(sd3068); +#endif + + ret = regmap_bulk_write(sd3068->regmap, SD3068_REG_SC, rtc_data, + NUM_TIME_REGS); + if (ret < 0) { + dev_err(dev, "writing to RTC failed with err:%d\n", ret); + return ret; + } + +#if WRITE_PROTECT_EN + sd3068_disable_reg_write(sd3068); +#endif + + return 0; +} + +static const struct rtc_class_ops sd3068_rtc_ops = { + .read_time = sd3068_rtc_read_time, + .set_time = sd3068_rtc_set_time, +}; + +static const struct regmap_config regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x11, +}; + +static int sd3068_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct sd3068 *sd3068; + unsigned char rtc_data[NUM_TIME_REGS] = {0}; + pr_debug("probed\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + sd3068 = devm_kzalloc(&client->dev, sizeof(*sd3068), GFP_KERNEL); + if (!sd3068) + return -ENOMEM; + + sd3068->regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(sd3068->regmap)) { + dev_err(&client->dev, "regmap allocation failed\n"); + return PTR_ERR(sd3068->regmap); + } + + i2c_set_clientdata(client, sd3068); + + sd3068->rtc = devm_rtc_allocate_device(&client->dev); + if (IS_ERR(sd3068->rtc)) + return PTR_ERR(sd3068->rtc); + + sd3068->rtc->ops = &sd3068_rtc_ops; + sd3068->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; + sd3068->rtc->range_max = RTC_TIMESTAMP_END_2099; + + ret = regmap_bulk_read(sd3068->regmap, SD3068_REG_SC, rtc_data, + NUM_TIME_REGS); + if (ret < 0) { + dev_info(&client->dev, "can not read time data when probe\n"); + return ret; + } + + ret = rtc_register_device(sd3068->rtc); + if (ret) + return ret; + + sd3068_enable_reg_write(sd3068); + + return 0; +} + + +static const struct acpi_device_id ds1307_acpi_ids[] = { + { .id = "DS1339", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, ds1307_acpi_ids); + +static const struct i2c_device_id sd3068_id[] = { + { "sd3068", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c,sd3068_id); + +static const struct of_device_id sd3068_of_match[] = { + { .compatible = "wave,sd3068" }, + { } +}; + +static struct i2c_driver sd3068_driver = { + .driver = { + .name = "rtc-sd3068", + .of_match_table = of_match_ptr(sd3068_of_match), + .acpi_match_table = ACPI_PTR(ds1307_acpi_ids), + }, + .probe = sd3068_probe, + .id_table = sd3068_id, +}; + +module_i2c_driver(sd3068_driver); +MODULE_DEVICE_TABLE(of, sd3068_of_match); + +MODULE_DESCRIPTION("RTC driver for SD3068 and similar chips"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-sd3078.c b/drivers/rtc/rtc-sd3078.c new file mode 100644 index 0000000000000000000000000000000000000000..dd315f023963b50f52cde656a9bc6fc34a9c064e --- /dev/null +++ b/drivers/rtc/rtc-sd3078.c @@ -0,0 +1,1000 @@ +/* + * drivers sd3078 + * + * Copyright (C) 2022 shenzhen wave + * + * 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. + * + * Driver for sd3078 RTC + * + * Converted to the generic RTC susbsystem by zlh (2022) + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "rtc-sd3078.h" +/*time reg*/ +#define SD3078_REG_SEC 0x00 +#define SD3078_REG_MIN 0x01 +#define SD3078_REG_HOUR 0x02 +#define SD3078_REG_WDAY 0x03 +#define SD3078_REG_MDAY 0x04 +#define SD3078_REG_MON 0x05 +#define SD3078_REG_YEAR 0x06 +/*alarm reg*/ +#define SD3078_REG_ALARM_SEC 0x07 +#define SD3078_REG_ALARM_MIN 0x08 +#define SD3078_REG_ALARM_HOUR 0x09 +#define SD3078_REG_ALARM_WEEK 0x0A +#define SD3078_REG_ALARM_DAY 0x0B +#define SD3078_REG_ALARM_MONTH 0x0C +#define SD3078_REG_ALARM_YEAR 0x0D +#define SD3078_REG_ALARM_OE 0x0E +/* control reg*/ +#define SD3078_REG_CTR1 0x0F +#define SD3078_REG_CTR2 0x10 +#define SD3078_REG_CTR3 0x11 +#define SD3078_REG_TTF 0x12 +#define SD3078_REG_TD0 0x13 +#define SD3078_REG_TD1 0x14 +#define SD3078_REG_TD2 0x15 +/*temperature reg*/ +#define SD3078_REG_TEMP 0x16 +/*i2c control reg*/ +#define SD3078_REG_I2C_CTRL 0x17 +/*charge reg*/ +#define SD3078_REG_CHARGE 0x18 +/*extend control reg*/ +#define SD3078_REG_CTR4 0x19 +#define SD3078_REG_CTR5 0x1A +/*battery voltage reg*/ +#define SD3078_REG_BAT_VAL 0x1B +/*temperature low/hign alarm reg*/ +#define SD3078_REG_TEMP_AL 0x1C +#define SD3078_REG_TEMP_AH 0x1D +/*history max/min temperature reg*/ +#define SD3078_REG_HIS_L 0x1E +#define SD3078_REG_HIS_H 0x1F +/*history temperature lowest time ram*/ +#define SD3078_REG_HIS_L_MIN 0x20 +#define SD3078_REG_HIS_L_HOUR 0x21 +#define SD3078_REG_HIS_L_WEEK 0x22 +#define SD3078_REG_HIS_L_DAY 0x23 +#define SD3078_REG_HIS_L_MON 0x24 +#define SD3078_REG_HIS_L_YEAR 0x25 +/*history temperature highest time ram*/ +#define SD3078_REG_HIS_H_MIN 0x26 +#define SD3078_REG_HIS_H_HOUR 0x27 +#define SD3078_REG_HIS_H_WEEK 0x28 +#define SD3078_REG_HIS_H_DAY 0x29 +#define SD3078_REG_HIS_H_MON 0x2A +#define SD3078_REG_HIS_H_YEAR 0x2B +/*user ram reg 2cH -71H*/ +#define SD3078_REG_USER_RAM_START 0x2C +#define SD3078_REG_USER_RAM_END 0x71 +/*device id 72H-79H*/ +#define SD3078_REG_DEVICE_ID 0x72 + +/*reg bit define*/ +#define SD3078_WRTC1 BIT(7) +#define SD3078_WRTC2 BIT(2) +#define SD3078_WRTC3 BIT(7) + +#define SD3078_TEMP_AL_OE BIT(2) /*temperature lowest alarm enable*/ +#define SD3078_TEMP_AH_OE BIT(3) /*temperature lowest alarm enable*/ +#define SD3078_IM BIT(6) +#define SD3078_INTS1 BIT(5) +#define SD3078_INTS0 BIT(4) +#define SD3078_ALARM_EN BIT(7) +#define SD3078_INT_AF BIT(5) +#define SD3078_INTFE BIT(0) +#define SD3078_INTAE BIT(1) +#define SD3078_INTDE BIT(2) +#define SD3078_TDS1 BIT(5) +#define SD3078_TDS0 BIT(4) +struct sd3078_data { + struct i2c_client *client; + struct rtc_device *rtc; +}; + +static int sd3078_set_alarm_ext(struct i2c_client *client, struct sd3078_alarm_ext *alarm_ext); + +/** + * @brief sd3078 read protect disable + * + */ + static int sd3078_read_block_data(struct i2c_client *client, unsigned char reg, + unsigned char length, unsigned char *buf) +{ + int ret; + + struct i2c_msg msgs[] = { + {/* setup read ptr */ + .addr = client->addr, + .len = 1, + .buf = ®, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = length, + .buf = buf + }, + }; + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret != 2) { + dev_err(&client->dev, "%s: read error=%d\n", __func__, ret); + return -EIO; + } + + return 0; +} + +/** + * @brief sd3078 write block + * + */ +static int sd3078_write_block_data(struct i2c_client *client, + unsigned char reg, unsigned char length, + unsigned char *buf) +{ + unsigned char temp[0x80]; + int ret; + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .len = length+1, + .buf = temp, + } + }; + if(length >=0x80) + { + return -EIO; + } + temp[0] = reg; + memcpy(&temp[1], buf, length); + ret = i2c_transfer(client->adapter, msgs, 1); + if (ret != 1) { + dev_err(&client->dev, "%s: write error\n", __func__); + return -EIO; + } + return 0; +} + +/** + * @brief sd3078 write protect enable + * + */ +static int sd3078_write_enable(struct i2c_client *client) +{ + unsigned char buf[2]; + int ret; + + ret = sd3078_read_block_data(client, SD3078_REG_CTR1, 2, buf); + if (ret != 0) + return ret < 0 ? ret : -EIO; + + buf[1] |= SD3078_WRTC1; + ret = sd3078_write_block_data(client, SD3078_REG_CTR2,1, &buf[1]); + if (ret != 0) + return ret < 0 ? ret : -EIO; + buf[0] |= SD3078_WRTC2; + buf[0] |= SD3078_WRTC3; + ret = sd3078_write_block_data(client, SD3078_REG_CTR1, 1, &buf[0]); + if (ret != 0) + return ret < 0 ? ret : -EIO; + return 0; +} + +/** + * @brief sd3078 write protect disable + * + */ +static int sd3078_write_disable(struct i2c_client *client) +{ + unsigned char buf[2]; + int ret; + + ret = sd3078_read_block_data(client, SD3078_REG_CTR1, 2, buf); + if (ret != 0) + return ret < 0 ? ret : -EIO; + + buf[0] &= ~SD3078_WRTC2; + buf[0] &= ~SD3078_WRTC3; + buf[1] &= ~SD3078_WRTC1; + + ret = sd3078_write_block_data(client, SD3078_REG_CTR1, 2, buf); + if (ret != 0) + return ret < 0 ? ret : -EIO; + return 0; +} + +/** + * @brief sd3078 get time + * + */ +static int sd3078_get_time(struct device *dev, struct rtc_time *dt) +{ + struct sd3078_data *sd3078 = dev_get_drvdata(dev); + int ret; + unsigned char date[7]; + + ret = sd3078_read_block_data(sd3078->client, SD3078_REG_SEC,7, date); + if (ret != 0) + { + return ret < 0 ? ret : -EIO; + } + + dt->tm_sec = bcd2bin(date[SD3078_REG_SEC - SD3078_REG_SEC] & 0x7f); + dt->tm_min = bcd2bin(date[SD3078_REG_MIN - SD3078_REG_SEC] & 0x7f); + dt->tm_hour = bcd2bin(date[SD3078_REG_HOUR - SD3078_REG_SEC] & 0x3f); + dt->tm_wday = bcd2bin(date[SD3078_REG_WDAY - SD3078_REG_SEC] & 0x03); + dt->tm_mday = bcd2bin(date[SD3078_REG_MDAY - SD3078_REG_SEC] & 0x3f); + dt->tm_mon = bcd2bin(date[SD3078_REG_MON - SD3078_REG_SEC] & 0x1f); + dt->tm_year = bcd2bin(date[SD3078_REG_YEAR - SD3078_REG_SEC]) + 100; + + return 0; +} + +/** + * @brief sd3078 set time + * + */ +static int sd3078_set_time(struct device *dev, struct rtc_time *dt) +{ + struct sd3078_data *sd3078 = dev_get_drvdata(dev); + unsigned char date[7]; + int ret; + + if ((dt->tm_year < 100) || (dt->tm_year > 199)) + return -EINVAL; + date[SD3078_REG_SEC - SD3078_REG_SEC] = bin2bcd(dt->tm_sec); + date[SD3078_REG_MIN - SD3078_REG_SEC] = bin2bcd(dt->tm_min); + date[SD3078_REG_HOUR - SD3078_REG_SEC] = bin2bcd(dt->tm_hour); + date[SD3078_REG_WDAY - SD3078_REG_SEC] = bin2bcd( dt->tm_wday); + date[SD3078_REG_MDAY - SD3078_REG_SEC] = bin2bcd(dt->tm_mday); + date[SD3078_REG_MON - SD3078_REG_SEC] = bin2bcd(dt->tm_mon); + date[SD3078_REG_YEAR - SD3078_REG_SEC] = bin2bcd(dt->tm_year - 100); + + sd3078_write_enable(sd3078->client); + ret = sd3078_write_block_data(sd3078->client, SD3078_REG_SEC, 7, date); + sd3078_write_disable(sd3078->client); + if (ret != 0) + return ret < 0 ? ret : -EIO; + + return 0; +} + +static int sd3078_read_alarm(struct device *dev, struct rtc_wkalrm *tm) +{ + unsigned char buf[8]; + int ret; + struct sd3078_data *sd3078 = dev_get_drvdata(dev); + + ret = sd3078_read_block_data(sd3078->client, SD3078_REG_ALARM_SEC,8, buf); + if (ret != 0) + return ret < 0 ? ret : -EIO; + + tm->time.tm_sec = buf[0]; + tm->time.tm_min = buf[1]; + tm->time.tm_hour = buf[2]; + tm->time.tm_wday = buf[3]; + tm->time.tm_yday = buf[4]; + tm->time.tm_mon = buf[5]; + tm->time.tm_year = buf[6]; + + if((buf[7] & 0x7f) != 0) + { + tm->enabled = 1; + } + ret = sd3078_read_block_data(sd3078->client, SD3078_REG_CTR1,1, &buf[0]); + if (ret != 0) + return ret < 0 ? ret : -EIO; + if((buf[0] & SD3078_INT_AF) != 0) + { + tm->pending = 1; + } + else + { + tm->pending = 0; + } + return 0; +} + +static int sd3078_set_alarm(struct device *dev, struct rtc_wkalrm *tm) +{ + int ret; + struct sd3078_data *sd3078 = dev_get_drvdata(dev); + struct sd3078_alarm_ext alarm_ext; + + alarm_ext.sec_a = tm->time.tm_sec ; + alarm_ext.min_a = tm->time.tm_min ; + alarm_ext.hour_a = tm->time.tm_hour; + alarm_ext.week_a = tm->time.tm_wday; + alarm_ext.day_a = tm->time.tm_yday; + alarm_ext.mon_a = tm->time.tm_mon; + alarm_ext.year_a = tm->time.tm_year; + if(tm->enabled == 1) + { + alarm_ext.oe_a = 0x7f; + } + else + { + alarm_ext.oe_a = 0; + } + alarm_ext.ie_a = 1; + alarm_ext.int_period = 0; + ret = sd3078_set_alarm_ext(sd3078->client, &alarm_ext); + return ret; +} + +/** + * @brief get sd3078 chip temperature + * + */ +static int sd3078_get_temperature( struct i2c_client *client, int *temp, unsigned int cmd) +{ + int ret; + unsigned reg; + unsigned char buf; + if(cmd == RTC_SD3078_RD_TEMP) + reg = SD3078_REG_TEMP; + else if(cmd == RTC_SD3078_TEMP_HIS_L) + reg = SD3078_REG_HIS_L; + else if(cmd == RTC_SD3078_TEMP_HIS_H) + reg = SD3078_REG_HIS_H; + else + return -EIO; + + ret = sd3078_read_block_data(client, reg,1, &buf); + if (ret != 0) + return ret < 0 ? ret : -EIO; + *temp = (signed char)buf; + return 0; +} + +/** + * @brief sd3078 set when power supply by battery i2c work state, + * en:0,battery supply i2c is not work,1, battery supply i2c is work + * + */ +static int sd3078_battery_i2c_ctrl(struct i2c_client *client, int en) +{ + int ret; + unsigned char buf; + + if(en !=0 &&en !=1) + return -EIO; + if(en) + buf = 0x80; + else + buf =0x00; + sd3078_write_enable(client); + ret = sd3078_write_block_data(client, SD3078_REG_I2C_CTRL, 1, &buf); + sd3078_write_disable(client); + if (ret != 0) + return ret < 0 ? ret : -EIO; + return 0; +} + +/** + * @brief sd3078 read battery voltage + * + */ +static int sd3078_read_battery_voltage( struct i2c_client *client, unsigned int *vol) +{ + int ret; + unsigned char buf[2]; + + ret = sd3078_read_block_data(client, SD3078_REG_CTR5, 2, buf); + if (ret != 0) + return ret < 0 ? ret : -EIO; + *vol = (buf[0] >>7) | buf[1] ; + return 0; +} + +/** + * @brief sd3078 read battery charge control + * + */ +static int sd3078_battery_charge_ctrl(struct i2c_client *client, struct sd3078_charge *ctrl) +{ + unsigned char buf; + int ret; + + if (ctrl->chage_en > 1) + return -EIO; + if (ctrl->resistance > 3) + return -EIO; + buf = (ctrl->chage_en <<7) | ctrl->resistance; + sd3078_write_enable(client); + ret = sd3078_write_block_data(client, SD3078_REG_CHARGE, 1, &buf); + sd3078_write_disable(client); + if (ret != 0) + return ret < 0 ? ret : -EIO; + return 0; +} + +/** + * @brief sd3078 set temperature alarm value; + * cmd:0:set lowest temperature alarm;1:set highest temperature alarm + * + */ +static int sd3078_set_alarm_temp(struct i2c_client *client, struct sd3078_temp_alarm *temp, unsigned int cmd) +{ + unsigned char buf; + int ret; + unsigned char reg; + unsigned char oe_bit; + if(cmd == RTC_SD3078_TEMP_AL) + { + reg = SD3078_REG_TEMP_AL; + oe_bit = SD3078_TEMP_AL_OE; + } + + else if(cmd == RTC_SD3078_TEMP_AH) + { + reg = SD3078_REG_TEMP_AH; + oe_bit = SD3078_TEMP_AH_OE; + } + else + return -EIO; + + + sd3078_write_enable(client); + buf = (unsigned char)temp->temp; + sd3078_write_block_data(client, reg, 1, &buf); + + ret = sd3078_read_block_data(client, SD3078_REG_CTR4, 1, &buf); + if (ret != 0) + return ret < 0 ? ret : -EIO; + /*enable temperature alarm*/ + if (temp->oe == 1) + { + buf |= oe_bit; + } + else + { + buf &= (~oe_bit); + } + ret = sd3078_write_block_data(client, SD3078_REG_CTR4, 1, &buf); + sd3078_write_disable(client); + if (ret != 0) + return ret < 0 ? ret : -EIO; + return 0; +} + +/** + * @brief sd3078 historical minimum / maximum temperature occurrence time + * + */ +static int sd3078_get_his_temp_time(struct i2c_client *client, struct rtc_time *dt, unsigned int cmd) +{ + int ret; + unsigned char date[6]; + unsigned char reg; + + if(cmd == RTC_SD3078_TEMP_HIS_L_T) + reg = SD3078_REG_HIS_L_MIN; + else if(cmd == RTC_SD3078_TEMP_HIS_H_T) + reg = SD3078_REG_HIS_H_MIN; + else + return -EIO; + ret = sd3078_read_block_data(client, reg, 6, date); + if (ret != 0) + return ret < 0 ? ret : -EIO; + dt->tm_sec = 0; + dt->tm_min = bcd2bin(date[SD3078_REG_MIN - SD3078_REG_MIN] & 0x7f); + dt->tm_hour = bcd2bin(date[SD3078_REG_HOUR - SD3078_REG_MIN] & 0x1f); + dt->tm_wday = bcd2bin(date[SD3078_REG_WDAY - SD3078_REG_MIN] & 0x03); + dt->tm_mday = bcd2bin(date[SD3078_REG_MDAY - SD3078_REG_MIN] & 0x3f); + dt->tm_mon = bcd2bin(date[SD3078_REG_MON - SD3078_REG_MIN] & 0x1f) ; + dt->tm_year = bcd2bin(date[SD3078_REG_YEAR - SD3078_REG_MIN]) + 100; + return 0; +} + +/** + * @brief sd3078 read user ram; + * + */ +static int sd3078_read_ram(struct i2c_client *client, unsigned char *buf, struct sd3078_ram *ram) +{ + int ret; + + if(ram->st_addr < SD3078_REG_USER_RAM_START || + ram->end_addr > SD3078_REG_USER_RAM_END || + ram->end_addr < ram->st_addr) + return -EIO; + + ret = sd3078_read_block_data(client, ram->st_addr, ram->end_addr - ram->st_addr + 1, buf); + if (ret != 0) + { + return ret ; + } + return 0; +} + +/** + * @brief sd3078 write user ram; + * + */ +static int sd3078_write_ram(struct i2c_client *client, unsigned char *buf, struct sd3078_ram *ram) +{ + int ret; + + if(ram->st_addr < SD3078_REG_USER_RAM_START || + ram->end_addr > SD3078_REG_USER_RAM_END || + ram->end_addr < ram->st_addr) + return -EIO; + sd3078_write_enable(client); + ret = sd3078_write_block_data(client, ram->st_addr, ram->end_addr - ram->st_addr + 1, buf); + sd3078_write_disable(client); + if (ret != 0) + return ret < 0 ? ret : -EIO; + return 0; +} + +/** + * @brief sd3078 read device id; + * + */ +static int sd3078_read_device_id(struct i2c_client *client, unsigned char *buf) +{ + int ret; + +ret = i2c_smbus_read_i2c_block_data(client, SD3078_REG_DEVICE_ID, + 8, buf); + if (ret != 8) + return ret < 0 ? ret : -EIO; + /*ret = sd3078_read_block_data(client, SD3078_REG_DEVICE_ID, 8, buf); + if (ret != 0) + return ret < 0 ? ret : -EIO;*/ + return 0; +} + +/** + * @brief sd3078 set alarm extend; + * + */ +static int sd3078_set_alarm_ext(struct i2c_client *client, struct sd3078_alarm_ext *alarm_ext) +{ + int ret; + unsigned char buf[8]; + + buf[0] = bin2bcd(alarm_ext->sec_a); + buf[1] = bin2bcd(alarm_ext->min_a); + buf[2] = bin2bcd(alarm_ext->hour_a); + buf[3] = bin2bcd(alarm_ext->week_a); + buf[4] = bin2bcd(alarm_ext->day_a); + buf[5] = bin2bcd(alarm_ext->mon_a); + buf[6] = bin2bcd(alarm_ext->year_a); + buf[7] = alarm_ext->oe_a; + ret = sd3078_write_enable(client); + if (ret != 0) + { + return ret < 0 ? ret : -EIO; + } + ret = sd3078_write_block_data(client, SD3078_REG_ALARM_SEC, 8, buf); + if (ret != 0) + { + sd3078_write_disable(client); + return ret < 0 ? ret : -EIO; + } + + /*if enable interrupt out?*/ + ret = sd3078_read_block_data(client, SD3078_REG_CTR2,1, &buf[0]); + if (ret != 0) + { + sd3078_write_disable(client); + return ret < 0 ? ret : -EIO; + } + buf[0] = buf[0]; + buf[0] &= ~SD3078_INTS1; + buf[0] |= SD3078_INTS0; + if(alarm_ext->ie_a == 1) + { + buf[0] |= SD3078_INTAE; + } + else + { + buf[0] &= ~SD3078_INTAE; + } + if(alarm_ext->int_period == 1) + { + buf[0] |= SD3078_IM; + } + else + { + buf[0] &= ~SD3078_IM; + } + ret = sd3078_write_block_data(client, SD3078_REG_CTR2, 1, &buf[0]); + if (ret != 0) + { + sd3078_write_disable(client); + return ret < 0 ? ret : -EIO; + } + sd3078_write_disable(client); + return 0; +} + +/** + * @brief sd3078 clock out + * + */ +static int sd3078_clk_out(struct i2c_client *client, struct sd3078_clk_out *clk) +{ + int ret; + unsigned char buf[2]; + + if(clk->freq > RTC_3078_CLK_OUT_1_SEC) + return -EIO; + + ret = sd3078_write_enable(client); + if (ret != 0) + { + return ret < 0 ? ret : -EIO; + } + ret = sd3078_read_block_data(client, SD3078_REG_CTR2, 2, buf); + if (ret != 0) + { + sd3078_write_disable(client); + return ret < 0 ? ret : -EIO; + } + + buf[1] &= 0xf0; + buf[1] |= clk->freq; + if(clk->oe == 1) + { + buf[0] |= SD3078_INTFE; + } + else + { + buf[0] &= ~SD3078_INTFE; + } + buf[0] |= SD3078_INTS1; + buf[0] &= ~SD3078_INTS0; + + ret = sd3078_write_block_data(client, SD3078_REG_CTR2, 2, buf); + sd3078_write_disable(client); + if (ret != 0) + { + return ret < 0 ? ret : -EIO; + } + return 0; +} + +/** + * @brief sd3078 count down + * + */ +static int sd3078_count_down(struct i2c_client *client, struct sd3078_count_down *count_down) +{ + int ret; + unsigned char buf[3]; + + if(count_down->count > 0xffffff) + return -EIO; + if(count_down->source > RTC_3078_COUNT_DOWN_1_60) + return -EIO; + + + ret = sd3078_write_enable(client); + if (ret != 0) + { + return ret < 0 ? ret : -EIO; + } + ret = sd3078_read_block_data(client, SD3078_REG_CTR2, 2, buf); + if (ret != 0) + { + sd3078_write_disable(client); + return ret < 0 ? ret : -EIO; + } + + /*set INTDF =0*/ + buf[0] &= ~SD3078_INTDE; + ret = sd3078_write_block_data(client, SD3078_REG_CTR2, 1, &buf[0]); + if (ret != 0) + { + sd3078_write_disable(client); + return ret < 0 ? ret : -EIO; + } + + if (count_down->ie == 1) + { + buf[0] |= SD3078_INTDE; + buf[0] |= SD3078_INTS1; + buf[0] |= SD3078_INTS0; + } + + if (count_down->int_period == 1) + { + buf[0] |= SD3078_IM; + } + else + { + buf[0] &= ~SD3078_IM; + } + buf[1] &= ~(0x03 << 4); + buf[1] |= (count_down->source << 4); + ret = sd3078_write_block_data(client, SD3078_REG_CTR2, 2, buf); + if (ret != 0) + { + sd3078_write_disable(client); + return ret < 0 ? ret : -EIO; + } + buf[0] = count_down->count & 0xff; + buf[1] = (count_down->count >> 8 )& 0xff; + buf[2] = (count_down->count >> 16 )& 0xff; + ret = sd3078_write_block_data(client, SD3078_REG_TD0, 3, buf); + sd3078_write_disable(client); + if (ret != 0) + { + return ret < 0 ? ret : -EIO; + } + return 0; +} +/** + * @brief sd3078 io ctrl; + * + */ +static int sd3078_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) +{ + struct sd3078_data *sd3078 = dev_get_drvdata(dev); + int ret; + + //printk(KERN_EMERG"sd3078_ioctl=%d,arg=%ld,%d\n",cmd,arg, RTC_SD3078_TEMP_AL); + switch (cmd) { + case RTC_SD3078_RD_TEMP: //read chip temperature + case RTC_SD3078_TEMP_HIS_L: //history lowest temperature value + case RTC_SD3078_TEMP_HIS_H : //history highest temperature value + { + int temp; + + ret = sd3078_get_temperature(sd3078->client, &temp, cmd); + if (ret != 0) + return ret; + if (copy_to_user((void __user *)arg, &temp, sizeof(int))) + return -EFAULT; + } + break; + case RTC_SD3078_BAT_I2C: //battery i2c ctrl + { + ret = sd3078_battery_i2c_ctrl(sd3078->client, arg); + if (ret != 0) + { + return ret; + } + } + break; + case RTC_SD3078_BAT_VOL: //battery voltage read + { + unsigned int vol; + + if(sd3078_read_battery_voltage(sd3078->client, &vol) == 0) + { + if (copy_to_user((void __user *)arg, &vol, sizeof(vol))) + return -EFAULT; + } + else + { + return -EFAULT; + } + } + break; + case RTC_SD3078_EN_CHARGE: //battery charge control + { + struct sd3078_charge ctrl; + + if (copy_from_user(&ctrl, (void __user *)arg, sizeof(ctrl ))) + return -EFAULT; + if (sd3078_battery_charge_ctrl(sd3078->client, &ctrl) != 0) + return -EFAULT; + } + break; + case RTC_SD3078_TEMP_AL : //lowest temperature alarm + case RTC_SD3078_TEMP_AH : //highest temperature alarm + { + struct sd3078_temp_alarm alarm; + + if (copy_from_user(&alarm, (void __user *)arg, sizeof(alarm))) + return -EFAULT; + if (sd3078_set_alarm_temp(sd3078->client, &alarm, cmd) != 0) + return -EFAULT; + } + break; + case RTC_SD3078_TEMP_HIS_L_T : //the occurrence time of the lowest temperature in history + case RTC_SD3078_TEMP_HIS_H_T : //the occurrence time of the highest temperature in history + { + struct rtc_time dt; + + if(sd3078_get_his_temp_time(sd3078->client, &dt, cmd) == 0) + { + if (copy_to_user((void __user *)arg, &dt, sizeof(dt))) + return -EFAULT; + } + else + return -EFAULT; + + } + break; + case RTC_SD3078_RAM_RD: //user ram read + { + struct sd3078_ram ram_r; + unsigned char buf_r[SD3078_REG_USER_RAM_END - SD3078_REG_USER_RAM_START + 1]; + + if (copy_from_user(&ram_r, (void __user *)arg, sizeof(ram_r ))) + return -EFAULT; + if (sd3078_read_ram(sd3078->client, buf_r, &ram_r) == 0) + { + if (copy_to_user((void __user *)ram_r.buf, buf_r, ram_r.end_addr - ram_r.st_addr +1)) + return -EFAULT; + } + else + { + return -EFAULT; + } + } + break; + case RTC_SD3078_RAM_WR: //user ram write + { + struct sd3078_ram ram_w; + unsigned char buf_w[SD3078_REG_USER_RAM_END - SD3078_REG_USER_RAM_START + 1]; + + if (copy_from_user(&ram_w, (void __user *)arg, sizeof(ram_w ))) + return -EFAULT; + if (copy_from_user(buf_w, ram_w.buf, ram_w.end_addr - ram_w.st_addr +1)) + return -EFAULT; + if(sd3078_write_ram(sd3078->client, buf_w, &ram_w) != 0) + return -EFAULT; + } + break; + case RTC_SD3078_DEVICE_ID: //read device id + { + unsigned char device_id[8]; + + if (sd3078_read_device_id(sd3078->client, device_id) == 0) + { + if (copy_to_user((void __user *)arg, device_id, sizeof(device_id))) + return -EFAULT; + } + else + { + return -EFAULT; + } + + } + break; + case RTC_SD3078_ALARM_EXT_SET: /*alarm extend set*/ + { + struct sd3078_alarm_ext alarm_ext; + + if (copy_from_user(&alarm_ext, (void __user *)arg, sizeof(alarm_ext ))) + return -EFAULT; + if (sd3078_set_alarm_ext(sd3078->client, &alarm_ext) != 0) + return -EFAULT; + } + break; + case RTC_SD3078_CLK_OUT: /*freq output ctrl*/ + { + struct sd3078_clk_out clk; + + if (copy_from_user(&clk, (void __user *)arg, sizeof(clk ))) + return -EFAULT; + if (sd3078_clk_out(sd3078->client, &clk) != 0) + return -EFAULT; + } + break; + case RTC_SD3078_COUNT_DOWN_SET: + { + struct sd3078_count_down count_down; + + if (copy_from_user(&count_down, (void __user *)arg, sizeof(count_down ))) + return -EFAULT; + if (sd3078_count_down(sd3078->client, &count_down) != 0) + return -EFAULT; + } + break; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static struct rtc_class_ops sd3078_rtc_ops = { + .read_time = sd3078_get_time, + .set_time = sd3078_set_time, + .set_alarm = sd3078_set_alarm, + .read_alarm = sd3078_read_alarm, + .ioctl = sd3078_ioctl, +}; + +static irqreturn_t sd3078_irq_1_handler(int irq, void *dev_id) +{ + struct i2c_client *client = dev_id; + struct sd3078_data *sd3078 = i2c_get_clientdata(client); + + mutex_lock(&sd3078->rtc->ops_lock); + /*user code*/ + mutex_unlock(&sd3078->rtc->ops_lock); + return IRQ_HANDLED; +} + +static int sd3078_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct sd3078_data *sd3078; + int err = 0; + + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_I2C_BLOCK)) { + dev_err(&adapter->dev, "doesn't support required functionality\n"); + return -EIO; + } + + sd3078 = devm_kzalloc(&client->dev, sizeof(struct sd3078_data), + GFP_KERNEL); + if (!sd3078) + return -ENOMEM; + + sd3078->client = client; + i2c_set_clientdata(client, sd3078); + sd3078->rtc = devm_rtc_device_register(&client->dev, client->name, + &sd3078_rtc_ops, THIS_MODULE); + if (IS_ERR(sd3078->rtc)) { + dev_err(&client->dev, "unable to register the class device\n"); + return PTR_ERR(sd3078->rtc); + } + if (client->irq > 0) { + dev_info(&client->dev, "IRQ %d supplied\n", client->irq); + err = devm_request_threaded_irq(&client->dev, client->irq, NULL, + sd3078_irq_1_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "sd3078", client); + + if (err) { + dev_err(&client->dev, "unable to request IRQ\n"); + client->irq = 0; + } + } + sd3078->rtc->max_user_freq = 1; + sd3078->rtc->uie_unsupported = 1; + printk(KERN_EMERG "sd3078 probe!,version=V1.0.0\n"); + return err; +} +static const struct i2c_device_id sd3078_id[] = { + { "sd3078", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c,sd3078_id); + +static const struct of_device_id sd3078_of_match[] = { + { .compatible = "wave,sd3078" }, + { } +}; + +static struct i2c_driver sd3078_driver = { + .driver = { + .name = "rtc-sd3078", + .of_match_table = of_match_ptr(sd3078_of_match), + }, + .probe = sd3078_probe, + .id_table = sd3078_id, +}; + +MODULE_DEVICE_TABLE(of, sd3078_of_match); + +module_i2c_driver(sd3078_driver); + +MODULE_AUTHOR("support@whwave.com.cn"); +MODULE_DESCRIPTION("wave sd3078 rtc driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rtc/rtc-sd3078.h b/drivers/rtc/rtc-sd3078.h new file mode 100644 index 0000000000000000000000000000000000000000..0c51a6ad07981605002971031ee84af5b9d202ca --- /dev/null +++ b/drivers/rtc/rtc-sd3078.h @@ -0,0 +1,95 @@ +#ifndef __RTC_SD3078__H +#define __RTC_SD3078__H +#include +/*clk output frequency define*/ +#define RTC_3078_CLK_OUT_4096 0x02 +#define RTC_3078_CLK_OUT_1024 0x03 +#define RTC_3078_CLK_OUT_64 0x04 +#define RTC_3078_CLK_OUT_32 0x05 +#define RTC_3078_CLK_OUT_16 0x06 +#define RTC_3078_CLK_OUT_8 0x07 +#define RTC_3078_CLK_OUT_4 0x08 +#define RTC_3078_CLK_OUT_2 0x09 +#define RTC_3078_CLK_OUT_1 0x0a +#define RTC_3078_CLK_OUT_1_2 0x0b +#define RTC_3078_CLK_OUT_1_4 0x0c +#define RTC_3078_CLK_OUT_1_8 0x0d +#define RTC_3078_CLK_OUT_1_16 0x0e +#define RTC_3078_CLK_OUT_1_SEC 0x0f + +/*conut down frequency source define*/ +#define RTC_3078_COUNT_DOWN_4096 0x00 +#define RTC_3078_COUNT_DOWN_1024 0x01 +#define RTC_3078_COUNT_DOWN_1 0x02 +#define RTC_3078_COUNT_DOWN_1_60 0x03 +/*charge control struct*/ + struct sd3078_charge +{ + unsigned char chage_en; /*0:disable,1:enable*/ + unsigned char resistance; /*00:10k;01:5k;10:2k;11:open*/ +}; + +/*user ram write/read struct*/ + struct sd3078_ram + { + unsigned char st_addr; + unsigned char end_addr; + unsigned char *buf; + }; + + /*temperature lowest/highest alarm set*/ + struct sd3078_temp_alarm + { + unsigned char type;/*0:lowest alarm,1:highest alarm*/ + unsigned char oe;/*temperature alarm set,0:disable,1:enable*/ + unsigned char ie;/*interrupt enable,when ie=1 and alarm happen,int pin output 0*/ + int temp; + }; +/*time alarm extend struct*/ + struct sd3078_alarm_ext + { + unsigned char sec_a; + unsigned char min_a; + unsigned char hour_a; + unsigned char week_a; + unsigned char day_a; + unsigned char mon_a; + unsigned char year_a; + + unsigned char ie_a;/*interrupt enable ,0:disable,1:enable*/ + unsigned char int_period;/*0:int outpute 0,1:int output pulse*/ + unsigned char oe_a;/*alarm enable;bit[6]:year,bit[5]:month,bit[4]:day,bit[3]:week,bit[2]:hour,bit[1]:minute,bit[0]:second*/ + }; + + /*frequency output struct*/ + struct sd3078_clk_out + { + unsigned char freq; + unsigned char oe;/*= clk out enable, set,0:disable,1:enable*/ + }; + /*count down struct*/ + struct sd3078_count_down + { + unsigned int count; + unsigned char source; + unsigned char ie;/*interrupt enable ,0:disable,1:enable*/ + unsigned char int_period;/*0:int outpute 0,1:int output pulse*/ + }; +/*SD3078 io ctrl cmd,start with rtc.h ioctrl */ +#define RTC_SD3078_RD_TEMP _IOR('p', 0x15, int) /*read chip temperature*/ +#define RTC_SD3078_BAT_I2C _IOW('p', 0x16, unsigned char) /*battery i2c ctrl*/ +#define RTC_SD3078_BAT_VOL _IOR('p', 0x17, unsigned int) /*battery voltage read*/ +#define RTC_SD3078_EN_CHARGE _IOW('p', 0x18, struct sd3078_charge) /*battery charge control*/ +#define RTC_SD3078_TEMP_AL _IOW('p', 0x19, struct sd3078_temp_alarm) /*set lowest temperature alarm value*/ +#define RTC_SD3078_TEMP_AH _IOW('p', 0x1a, struct sd3078_temp_alarm) /*set highest temperature alarm value*/ +#define RTC_SD3078_TEMP_HIS_L _IOR('p', 0x1b, signed char) /*history lowest temperature value*/ +#define RTC_SD3078_TEMP_HIS_H _IOR('p', 0x1c, signed char) /*history highest temperature value*/ +#define RTC_SD3078_TEMP_HIS_L_T _IOR('p', 0x1d, struct rtc_time) /*the occurrence time of the lowest temperature in history*/ +#define RTC_SD3078_TEMP_HIS_H_T _IOR('p', 0x1e, struct rtc_time) /*the occurrence time of the highest temperature in history*/ +#define RTC_SD3078_RAM_RD _IOW('p', 0x1f, struct sd3078_ram) /*user ram read*/ +#define RTC_SD3078_RAM_WR _IOW('p', 0x20, struct sd3078_ram) /*user ram write*/ +#define RTC_SD3078_DEVICE_ID _IOR('p', 0x21, int) /*read device id*/ +#define RTC_SD3078_ALARM_EXT_SET _IOW('p', 0x22, struct sd3078_alarm_ext) /*alarm extend set*/ +#define RTC_SD3078_CLK_OUT _IOW('p', 0x23, signed char) /*freq output ctrl*/ +#define RTC_SD3078_COUNT_DOWN_SET _IOW('p', 0x24, signed char) /*count down set*/ +#endif diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 0a7fd56c1ed9d15480d9c23a57a5fdea5657853b..c9d9f49526c1218e1970b367cc7d5848e2f18227 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -454,6 +454,31 @@ config SPI_ORION This enables using the SPI master controller on the Orion and MVEBU chips. +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 + This selects a PCI driver for Phytium SPI controller. + + If you say yes to this option, support will be included for + Phytium PCIe chipset of SPI controller. + + If unsure, say N. + config SPI_PIC32 tristate "Microchip PIC32 series SPI" depends on MACH_PIC32 || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index a90d55970036536d4bedeccfe533864ddb02622e..01d74f2a1afefaaced47cf3a1585adeecf2c5922 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -35,6 +35,9 @@ obj-$(CONFIG_SPI_DESIGNWARE) += spi-dw.o obj-$(CONFIG_SPI_DW_MMIO) += spi-dw-mmio.o obj-$(CONFIG_SPI_DW_PCI) += spi-dw-midpci.o spi-dw-midpci-objs := spi-dw-pci.o spi-dw-mid.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_EFM32) += spi-efm32.o obj-$(CONFIG_SPI_EP93XX) += spi-ep93xx.o obj-$(CONFIG_SPI_FALCON) += spi-falcon.o diff --git a/drivers/spi/spi-phytium-pci.c b/drivers/spi/spi-phytium-pci.c new file mode 100644 index 0000000000000000000000000000000000000000..a6ed0bb4492cad93733f3b9278197b51bc197c7b --- /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 v2"); diff --git a/drivers/spi/spi-phytium-plat.c b/drivers/spi/spi-phytium-plat.c new file mode 100644 index 0000000000000000000000000000000000000000..3acd3c657781a4612aeb4f237a6143cd6f233601 --- /dev/null +++ b/drivers/spi/spi-phytium-plat.c @@ -0,0 +1,201 @@ +// 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-pci.c + * Copyright (c) 2009, 2014 Intel Corporation. + */ + +#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" + +static int phytium_spi_probe(struct platform_device *pdev) +{ + struct phytium_spi *fts; + struct resource *mem; + int ret; + int num_cs; + int cs_gpio; + int global_cs; + int i; + + fts = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi), + GFP_KERNEL); + if (!fts) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -EINVAL; + } + + 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) { + fts->clk = devm_clk_get(&pdev->dev, NULL); + + if (IS_ERR(fts->clk)) + return PTR_ERR(fts->clk); + ret = clk_prepare_enable(fts->clk); + if (ret) + return ret; + + fts->max_freq = clk_get_rate(fts->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; + + if (pdev->dev.of_node) { + int i; + + for (i = 0; i < fts->num_cs; i++) { + cs_gpio = of_get_named_gpio(pdev->dev.of_node, + "cs-gpios", i); + + if (cs_gpio == -EPROBE_DEFER) { + ret = cs_gpio; + goto out; + } + + if (gpio_is_valid(cs_gpio)) { + ret = devm_gpio_request(&pdev->dev, cs_gpio, + dev_name(&pdev->dev)); + if (ret) + goto out; + } + } + } else if(has_acpi_companion(&pdev->dev)) { + int n; + int *cs; + struct gpio_desc *gpiod; + + n = gpiod_count(&pdev->dev, "cs"); + + cs = devm_kcalloc(&pdev->dev, n, sizeof(int), GFP_KERNEL); + fts->cs = cs; + + for (i = 0; i < n; i++) { + gpiod = devm_gpiod_get_index_optional(&pdev->dev, "cs", i, + GPIOD_OUT_LOW); + + if (IS_ERR(gpiod)) { + ret = PTR_ERR(gpiod); + goto out; + } + + cs_gpio = desc_to_gpio(gpiod); + cs[i] = cs_gpio; + } + } + + device_property_read_u32(&pdev->dev, "global-cs", &global_cs); + fts->global_cs = global_cs; + + ret = phytium_spi_add_host(&pdev->dev, fts); + if (ret) + goto out; + + platform_set_drvdata(pdev, fts); + return 0; + +out: + clk_disable_unprepare(fts->clk); + return ret; +} + +static int phytium_spi_remove(struct platform_device *pdev) +{ + struct phytium_spi *fts = platform_get_drvdata(pdev); + + phytium_spi_remove_host(fts); + clk_disable_unprepare(fts->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int spi_suspend(struct device *dev) +{ + struct phytium_spi *fts = dev_get_drvdata(dev); + + return phytium_spi_suspend_host(fts); +} + +static int spi_resume(struct device *dev) +{ + struct phytium_spi *fts = dev_get_drvdata(dev); + + return phytium_spi_resume_host(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 v2"); diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c new file mode 100644 index 0000000000000000000000000000000000000000..6e29cf8f0bad7f2899665d1da98636a8c6eafa57 --- /dev/null +++ b/drivers/spi/spi-phytium.c @@ -0,0 +1,534 @@ +/* + * Phytium SPI core controller 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 +#include +#include +#include "spi-phytium.h" + +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); +} + +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; + } +} + +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, "interrupt_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; + + 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 != chip->speed_hz) { + clk_div = (fts->max_freq / transfer->speed_hz + 1) & 0xfffe; + + chip->speed_hz = transfer->speed_hz; + chip->clk_div = clk_div; + + 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, CTRL0, cr0); + + spi_mask_intr(fts, 0xff); + + if (!chip->poll_mode) { + 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 (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); + + 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); + int ret; + 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, CTRL0, cr0); + + if (gpio_is_valid(spi->cs_gpio)) { + ret = gpio_direction_output(spi->cs_gpio, + !(spi->mode & SPI_CS_HIGH)); + if (ret) + return ret; + } + + 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; + + BUG_ON(fts == NULL); + + master = spi_alloc_master(dev, 0); + if (!master) + return -ENOMEM; + + fts->master = master; + 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->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_MASTER_GPIO_SS; + master->cs_gpios = fts->cs; + + spi_hw_init(dev, fts); + + spi_master_set_devdata(master, fts); + ret = devm_spi_register_master(dev, master); + if (ret) { + dev_err(&master->dev, "problem registering spi master\n"); + goto err_exit; + } + + return 0; + +err_exit: + 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) +{ + spi_shutdown_chip(fts); + + 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 v2"); diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h new file mode 100644 index 0000000000000000000000000000000000000000..d75e8ba7daa40ad772027f53564b6578a337a081 --- /dev/null +++ b/drivers/spi/spi-phytium.h @@ -0,0 +1,72 @@ +/* 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 CTRL0 0x00 +#define SSIENR 0x08 +#define SER 0x10 +#define BAUDR 0x14 +#define TXFLTR 0x18 +#define TXFLR 0x20 +#define RXFLR 0x24 +#define IMR 0x2c +#define ISR 0x30 +#define ICR 0x48 +#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) + +struct phytium_spi { + struct spi_master *master; + char name[16]; + + void __iomem *regs; + bool global_cs; + 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; + struct clk *clk; + irqreturn_t (*transfer_handler)(struct phytium_spi *fts); +}; + +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); + +#endif /* PHYTIUM_SPI_HEADER_H */ diff --git a/drivers/tee/Kconfig b/drivers/tee/Kconfig index a6df12d88f90cd097ab88df8d151970243aea26b..f4cf1ba4a6c4e254e1f26369ba9eb047f6d2e243 100644 --- a/drivers/tee/Kconfig +++ b/drivers/tee/Kconfig @@ -2,6 +2,7 @@ config TEE tristate "Trusted Execution Environment support" depends on HAVE_ARM_SMCCC || COMPILE_TEST + select CRYPTO_SHA1 select DMA_SHARED_BUFFER select GENERIC_ALLOCATOR help diff --git a/drivers/tee/optee/Kconfig b/drivers/tee/optee/Kconfig index 3c1ec4e9ed29aeeb692bb3eb184a2cdf75dd5742..f3be66ef85b79d654dd5cbc8c65af89eafd6efa7 100644 --- a/drivers/tee/optee/Kconfig +++ b/drivers/tee/optee/Kconfig @@ -14,3 +14,33 @@ config OPTEE_SHM_NUM_PRIV_PAGES help This sets the number of private shared memory pages to be used by OP-TEE TEE driver. + +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 + firwmare 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/call.c b/drivers/tee/optee/call.c index 4f4a7e2b122bfd46a15ac31f553f1b615d1686ae..37d021fe99ebe3d8f3ace671b28353de64472db9 100644 --- a/drivers/tee/optee/call.c +++ b/drivers/tee/optee/call.c @@ -241,9 +241,13 @@ int optee_open_session(struct tee_context *ctx, msg_arg->params[1].attr = OPTEE_MSG_ATTR_TYPE_VALUE_INPUT | OPTEE_MSG_ATTR_META; memcpy(&msg_arg->params[0].u.value, arg->uuid, sizeof(arg->uuid)); - memcpy(&msg_arg->params[1].u.value, arg->uuid, sizeof(arg->clnt_uuid)); msg_arg->params[1].u.value.c = arg->clnt_login; + rc = tee_session_calc_client_uuid((uuid_t *)&msg_arg->params[1].u.value, + arg->clnt_login, arg->clnt_uuid); + if (rc) + goto out; + rc = optee_to_msg_param(msg_arg->params + 2, arg->num_params, param); if (rc) goto out; diff --git a/drivers/tee/optee/core.c b/drivers/tee/optee/core.c index 473981e3ad70b257c7c94859087d26a5fb46b6db..dc47c5493ae280e12662db9294873b089cdba88f 100644 --- a/drivers/tee/optee/core.c +++ b/drivers/tee/optee/core.c @@ -14,6 +14,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include #include #include #include @@ -519,15 +520,23 @@ static void optee_smccc_hvc(unsigned long a0, unsigned long a1, arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res); } -static optee_invoke_fn *get_invoke_func(struct device_node *np) +#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_hvc +#else +#define DEFAULT_CONDUIT_METHOD ERR_PTR(-ENXIO) +#endif + +static optee_invoke_fn *get_invoke_func(struct device *dev) { const char *method; - pr_info("probing for conduit method from DT.\n"); + pr_info("probing for conduit method.\n"); - if (of_property_read_string(np, "method", &method)) { + 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)) @@ -539,7 +548,37 @@ static optee_invoke_fn *get_invoke_func(struct device_node *np) return ERR_PTR(-EINVAL); } -static struct optee *optee_probe(struct device_node *np) +static int optee_remove(struct platform_device *pdev) +{ + struct optee *optee = platform_get_drvdata(pdev); + + /* + * Ask OP-TEE to free all cached shared memory objects to decrease + * reference counters and also avoid wild pointers in secure world + * into the old shared memory range. + */ + optee_disable_shm_cache(optee); + + /* + * The two devices have to be unregistered before we can free the + * other resources. + */ + tee_device_unregister(optee->supp_teedev); + tee_device_unregister(optee->teedev); + + tee_shm_pool_free(optee->pool); + if (optee->memremaped_shm) + memunmap(optee->memremaped_shm); + optee_wait_queue_exit(&optee->wait_queue); + optee_supp_uninit(&optee->supp); + mutex_destroy(&optee->call_queue.mutex); + + kfree(optee); + + return 0; +} + +static int optee_probe(struct platform_device *pdev) { optee_invoke_fn *invoke_fn; struct tee_shm_pool *pool; @@ -549,25 +588,25 @@ static struct optee *optee_probe(struct device_node *np) u32 sec_caps; int rc; - invoke_fn = get_invoke_func(np); + invoke_fn = get_invoke_func(&pdev->dev); if (IS_ERR(invoke_fn)) - return (void *)invoke_fn; + return PTR_ERR(invoke_fn); if (!optee_msg_api_uid_is_optee_api(invoke_fn)) { pr_warn("api uid mismatch\n"); - return ERR_PTR(-EINVAL); + return -EINVAL; } optee_msg_get_os_revision(invoke_fn); if (!optee_msg_api_revision_is_compatible(invoke_fn)) { pr_warn("api revision mismatch\n"); - return ERR_PTR(-EINVAL); + return -EINVAL; } if (!optee_msg_exchange_capabilities(invoke_fn, &sec_caps)) { pr_warn("capabilities mismatch\n"); - return ERR_PTR(-EINVAL); + return -EINVAL; } /* @@ -575,11 +614,11 @@ static struct optee *optee_probe(struct device_node *np) * doesn't have any reserved memory we can use we can't continue. */ if (!(sec_caps & OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM)) - return ERR_PTR(-EINVAL); + return -EINVAL; pool = optee_config_shm_memremap(invoke_fn, &memremaped_shm, sec_caps); if (IS_ERR(pool)) - return (void *)pool; + return PTR_ERR(pool); optee = kzalloc(sizeof(*optee), GFP_KERNEL); if (!optee) { @@ -630,8 +669,10 @@ static struct optee *optee_probe(struct device_node *np) optee_enable_shm_cache(optee); + platform_set_drvdata(pdev, optee); + pr_info("initialized driver\n"); - return optee; + return 0; err: if (optee) { /* @@ -647,83 +688,37 @@ static struct optee *optee_probe(struct device_node *np) tee_shm_pool_free(pool); if (memremaped_shm) memunmap(memremaped_shm); - return ERR_PTR(rc); -} - -static void optee_remove(struct optee *optee) -{ - /* - * Ask OP-TEE to free all cached shared memory objects to decrease - * reference counters and also avoid wild pointers in secure world - * into the old shared memory range. - */ - optee_disable_shm_cache(optee); - - /* - * The two devices has to be unregistered before we can free the - * other resources. - */ - tee_device_unregister(optee->supp_teedev); - tee_device_unregister(optee->teedev); - - tee_shm_pool_free(optee->pool); - if (optee->memremaped_shm) - memunmap(optee->memremaped_shm); - optee_wait_queue_exit(&optee->wait_queue); - optee_supp_uninit(&optee->supp); - mutex_destroy(&optee->call_queue.mutex); - - kfree(optee); + return rc; } -static const struct of_device_id optee_match[] = { +static const struct of_device_id optee_dt_match[] = { { .compatible = "linaro,optee-tz" }, {}, }; +MODULE_DEVICE_TABLE(of, optee_dt_match); -static struct optee *optee_svc; - -static int __init optee_driver_init(void) -{ - struct device_node *fw_np; - struct device_node *np; - struct optee *optee; - - /* Node is supposed to be below /firmware */ - fw_np = of_find_node_by_name(NULL, "firmware"); - if (!fw_np) - return -ENODEV; - - np = of_find_matching_node(fw_np, optee_match); - if (!np || !of_device_is_available(np)) { - of_node_put(np); - return -ENODEV; - } - - optee = optee_probe(np); - of_node_put(np); - - if (IS_ERR(optee)) - return PTR_ERR(optee); - - optee_svc = optee; - - return 0; -} -module_init(optee_driver_init); - -static void __exit optee_driver_exit(void) -{ - struct optee *optee = optee_svc; - - optee_svc = NULL; - if (optee) - optee_remove(optee); -} -module_exit(optee_driver_exit); +#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_remove, + .driver = { + .name = "optee", + .of_match_table = optee_dt_match, + .acpi_match_table = ACPI_PTR(optee_acpi_match), + }, +}; +module_platform_driver(optee_driver); MODULE_AUTHOR("Linaro"); MODULE_DESCRIPTION("OP-TEE driver"); MODULE_SUPPORTED_DEVICE(""); MODULE_VERSION("1.0"); MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:optee"); diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index d42fc2ae8592ef62a32c036534b5da7621524583..03e3b2ea8c1a61abc9e8fb0c1bfc8b91306f37cc 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -15,6 +15,7 @@ #define pr_fmt(fmt) "%s: " fmt, __func__ #include +#include #include #include #include @@ -22,12 +23,27 @@ #include #include #include +#include +#include #include "tee_private.h" #define TEE_NUM_DEVICES 32 #define TEE_IOCTL_PARAM_SIZE(x) (sizeof(struct tee_param) * (x)) +#define TEE_UUID_NS_NAME_SIZE 128 + +/* + * TEE Client UUID name space identifier (UUIDv4) + * + * Value here is random UUID that is allocated as name space identifier for + * forming Client UUID's for TEE environment using UUIDv5 scheme. + */ +static const uuid_t tee_client_uuid_ns = UUID_INIT(0x58ac9ca0, 0x2086, 0x4683, + 0xa1, 0xb8, 0xec, 0x4b, + 0xc0, 0x8e, 0x01, 0xb6); + + /* * Unprivileged devices in the lower half range and privileged devices in * the upper half range. @@ -108,6 +124,143 @@ static int tee_release(struct inode *inode, struct file *filp) return 0; } +/** + * uuid_v5() - Calculate UUIDv5 + * @uuid: Resulting UUID + * @ns: Name space ID for UUIDv5 function + * @name: Name for UUIDv5 function + * @size: Size of name + * + * UUIDv5 is specific in RFC 4122. + * + * This implements section (for SHA-1): + * 4.3. Algorithm for Creating a Name-Based UUID + */ +static int uuid_v5(uuid_t *uuid, const uuid_t *ns, const void *name, + size_t size) +{ + unsigned char hash[SHA1_DIGEST_SIZE]; + struct crypto_shash *shash = NULL; + struct shash_desc *desc = NULL; + int rc; + + shash = crypto_alloc_shash("sha1", 0, 0); + if (IS_ERR(shash)) { + rc = PTR_ERR(shash); + pr_err("shash(sha1) allocation failed\n"); + return rc; + } + + desc = kzalloc(sizeof(*desc) + crypto_shash_descsize(shash), + GFP_KERNEL); + if (!desc) { + rc = -ENOMEM; + goto out_free_shash; + } + + desc->tfm = shash; + + rc = crypto_shash_init(desc); + if (rc < 0) + goto out_free_desc; + + rc = crypto_shash_update(desc, (const u8 *)ns, sizeof(*ns)); + if (rc < 0) + goto out_free_desc; + + rc = crypto_shash_update(desc, (const u8 *)name, size); + if (rc < 0) + goto out_free_desc; + + rc = crypto_shash_final(desc, hash); + if (rc < 0) + goto out_free_desc; + + memcpy(uuid->b, hash, UUID_SIZE); + + /* Tag for version 5 */ + uuid->b[6] = (hash[6] & 0x0F) | 0x50; + uuid->b[8] = (hash[8] & 0x3F) | 0x80; + +out_free_desc: + kfree(desc); + +out_free_shash: + crypto_free_shash(shash); + return rc; +} + +int tee_session_calc_client_uuid(uuid_t *uuid, u32 connection_method, + const u8 connection_data[TEE_IOCTL_UUID_LEN]) +{ + gid_t ns_grp = (gid_t)-1; + kgid_t grp = INVALID_GID; + char *name = NULL; + int name_len; + int rc; + + if (connection_method == TEE_IOCTL_LOGIN_PUBLIC) { + /* Nil UUID to be passed to TEE environment */ + uuid_copy(uuid, &uuid_null); + return 0; + } + + /* + * In Linux environment client UUID is based on UUIDv5. + * + * Determine client UUID with following semantics for 'name': + * + * For TEEC_LOGIN_USER: + * uid= + * + * For TEEC_LOGIN_GROUP: + * gid= + * + */ + + name = kzalloc(TEE_UUID_NS_NAME_SIZE, GFP_KERNEL); + if (!name) + return -ENOMEM; + + switch (connection_method) { + case TEE_IOCTL_LOGIN_USER: + name_len = snprintf(name, TEE_UUID_NS_NAME_SIZE, "uid=%x", + current_euid().val); + if (name_len >= TEE_UUID_NS_NAME_SIZE) { + rc = -E2BIG; + goto out_free_name; + } + break; + + case TEE_IOCTL_LOGIN_GROUP: + memcpy(&ns_grp, connection_data, sizeof(gid_t)); + grp = make_kgid(current_user_ns(), ns_grp); + if (!gid_valid(grp) || !in_egroup_p(grp)) { + rc = -EPERM; + goto out_free_name; + } + + name_len = snprintf(name, TEE_UUID_NS_NAME_SIZE, "gid=%x", + grp.val); + if (name_len >= TEE_UUID_NS_NAME_SIZE) { + rc = -E2BIG; + goto out_free_name; + } + break; + + default: + rc = -EINVAL; + goto out_free_name; + } + + rc = uuid_v5(uuid, &tee_client_uuid_ns, name, name_len); +out_free_name: + kfree(name); + + return rc; +} +EXPORT_SYMBOL_GPL(tee_session_calc_client_uuid); + static int tee_ioctl_version(struct tee_context *ctx, struct tee_ioctl_version_data __user *uvers) { diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index cd13065095bc32fac477b0047286c755238eef7d..a11e6c87fe32b63f8573e9832b2142acca6212d7 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -74,6 +74,17 @@ config SERIAL_AMBA_PL011_CONSOLE your boot loader (lilo or loadlin) about how to pass options to the kernel at boot time.) +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_ARM_SEMIHOST bool "Early console using ARM semihosting" depends on ARM64 || ARM diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index daac675612dff4804869460e7946210dd1eb7466..6d4cf6bc06ca20b50411ddc573ff169cebce8fdc 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_SERIAL_8250) += 8250/ obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o +obj-$(CONFIG_SERIAL_PHYTIUM_PCI) += phytium-uart.o obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o obj-$(CONFIG_SERIAL_PXA_NON8250) += pxa.o obj-$(CONFIG_SERIAL_PNX8XXX) += pnx8xxx_uart.o diff --git a/drivers/tty/serial/phytium-uart.c b/drivers/tty/serial/phytium-uart.c new file mode 100644 index 0000000000000000000000000000000000000000..a076668a32b9dde06c1c4683873cfb53d826fb27 --- /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 Technology 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, 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 a9f12a52f72651617c8257985652e10cebacb9a4..5df4d07679cb92b8afd19890b0a7e59833e09070 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -124,6 +124,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 7d1b8c82b208917e83ada929ef6902fed878e12e..22d79202ac93623e416bb0442c78aa89ff017a2a 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -12,6 +12,8 @@ obj-$(CONFIG_USB_DWC3) += dwc3/ obj-$(CONFIG_USB_DWC2) += dwc2/ obj-$(CONFIG_USB_ISP1760) += isp1760/ +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 e6bbb919555465f1db997be58c514d143f8ae17f..a4119fa3f8bcb3c1c6979e07ecc836af198ea9e5 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -45,6 +45,7 @@ #define PCI_DEVICE_ID_INTEL_APL_XHCI 0x5aa8 #define PCI_DEVICE_ID_INTEL_DNV_XHCI 0x19d0 #define PCI_DEVICE_ID_INTEL_CML_XHCI 0xa3af +#define PCI_DEVICE_ID_PHYTIUM_XHCI 0xdc27 #define PCI_DEVICE_ID_AMD_PROMONTORYA_4 0x43b9 #define PCI_DEVICE_ID_AMD_PROMONTORYA_3 0x43ba @@ -236,6 +237,9 @@ 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 adc437ca83b8804dcc88bb835c5e6935b9d70e90..ccb1b1dc92f464b1c7c96525202327096b000d39 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -65,12 +65,14 @@ static int xhci_priv_resume_quirk(struct usb_hcd *hcd) static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci) { + struct xhci_plat_priv *priv = xhci_to_priv(xhci); + /* * As of now platform drivers don't provide MSI support so we ensure * here that the generic code does not try to make a pci_dev from our * dev struct in order to setup MSI */ - xhci->quirks |= XHCI_PLAT; + xhci->quirks |= XHCI_PLAT | priv->quirks; } /* called during probe() after chip reset completes */ @@ -111,6 +113,10 @@ static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = { .resume_quirk = xhci_rcar_resume_quirk, }; +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", @@ -143,6 +149,9 @@ static const struct of_device_id usb_xhci_of_match[] = { }, { .compatible = "renesas,rcar-gen3-xhci", .data = &xhci_plat_renesas_rcar_gen3, + }, { + .compatible = "phytium,pe220x-xhci", + .data = &xhci_plat_phytium_pe220x, }, {}, }; @@ -252,7 +261,10 @@ static int xhci_plat_probe(struct platform_device *pdev) } xhci = hcd_to_xhci(hcd); - priv_match = of_device_get_match_data(&pdev->dev); + if (has_acpi_companion(&pdev->dev)) + priv_match = acpi_device_get_match_data(&pdev->dev); + else + priv_match = of_device_get_match_data(&pdev->dev); if (priv_match) { struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); @@ -438,6 +450,7 @@ static const struct dev_pm_ops 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/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h index ae29f22ff5bd7f38453b229985fea0d6db48e7b0..5681723fc9cd73b8c6bf40ef0d764df5e4fde511 100644 --- a/drivers/usb/host/xhci-plat.h +++ b/drivers/usb/host/xhci-plat.h @@ -12,10 +12,12 @@ struct xhci_plat_priv { const char *firmware_name; + unsigned long long quirks; void (*plat_start)(struct usb_hcd *); int (*init_quirk)(struct usb_hcd *); int (*resume_quirk)(struct usb_hcd *); }; #define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv) +#define xhci_to_priv(x) ((struct xhci_plat_priv *)(x)->priv) #endif /* _XHCI_PLAT_H */ diff --git a/drivers/usb/phytium/Kconfig b/drivers/usb/phytium/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..5d889c26a7c31c86c5cde712245823b5fdb29634 --- /dev/null +++ b/drivers/usb/phytium/Kconfig @@ -0,0 +1,8 @@ +config USB_PHYTIUM + tristate "PHYTIUM USB Support" + depends on USB + 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 diff --git a/drivers/usb/phytium/Makefile b/drivers/usb/phytium/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..05d422d4a59bc5dddc5f5483e9eeaf792330ede5 --- /dev/null +++ b/drivers/usb/phytium/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_USB_PHYTIUM) += phytium-usb.o + +phytium-usb-y := core.o dma.o platform.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..2e0d6df3eb344e386c649f2fc2dab7e774969137 --- /dev/null +++ b/drivers/usb/phytium/dma.c @@ -0,0 +1,786 @@ +// 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)) +/* burst length 'x' should be multiples of 2 */ +#define TRB_BURST_LENGTH(x) (((x) & 0xFF) << 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(0x80); \ + 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_UNKNOW; + + 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_UNKNOW; + + 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_UNKNOW) + 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..073a078c1289625ce4bec236498aa49e651c7375 --- /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; /* 0:16 transfer length; 24:31 burst length */ + uint32_t ctrl; +}; + +enum DMA_Status { + DMA_STATUS_UNKNOW, + 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..afcb846c8db342444c4bb853bd92bfd7559c5630 --- /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 -ENOTSUPP; +} + +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 -ENOTSUPP; +} + +static int32_t gadgetVbusDraw(struct GADGET_CTRL *priv, uint8_t mA) +{ + if (!priv) + return -EINVAL; + + return -ENOTSUPP; +} + +static int32_t gadgetPullUp(struct GADGET_CTRL *priv, uint8_t isOn) +{ + if (!priv) + return -EINVAL; + + return -ENOTSUPP; +} + +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..75bcaa2a71477f2fc727769b95b897ea6808fa81 --- /dev/null +++ b/drivers/usb/phytium/host.c @@ -0,0 +1,2647 @@ +// 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_CONTROLL 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 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; + + 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, usbEpPriv->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, + usbEpPriv->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, usbEpPriv->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, + usbEpPriv->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_CONTROLL].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 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, + usbHEpPriv->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, + usbHEpPriv->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 -EINVAL; + + hwEp = isIn ? &priv->in[HOST_GENERIC_EP_CONTROLL] : &priv->out[HOST_GENERIC_EP_CONTROLL]; + 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) { + priv->dmaDrv->dma_controllerReset(priv->dmaController); + ret = 1; + } + + 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_CONTROLL]; + 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_CONTROLL]; + 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_CONTROLL]; + 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_CONTROLL]; + 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; + + 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 = usb_endpoint_num(&ep->desc); + if (epnum > MAX_INSTANCE_EP_NUM) + epnum = MAX_INSTANCE_EP_NUM; + + 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 = usb_endpoint_num(host_ep_desc); + if (req->epNum > MAX_INSTANCE_EP_NUM) + req->epNum = MAX_INSTANCE_EP_NUM; + + 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]; + 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); + 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 = usb_endpoint_num(&ld_ep->desc); + + if (ep_num > MAX_INSTANCE_EP_NUM) + ep_num = MAX_INSTANCE_EP_NUM; + + 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); + + 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; +} + +int32_t hostReqQueue(struct HOST_CTRL *priv, struct HOST_REQ *req) +{ + struct HOST_EP_PRIV *hostEpPriv; + struct list_head *hEpQueue = NULL; + uint32_t interval = 0; + 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; + + 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: + if (req->usbDev->speed < USB_SPEED_FULL) + interval = (req->usbEp->desc.bInterval < 10) ? 10 : req->usbEp->desc.bInterval; + else if (req->usbDev->speed == USB_SPEED_FULL) + interval = (req->usbEp->desc.bInterval < 1) ? + 1 : req->usbEp->desc.bInterval; + else { + if (req->usbEp->desc.bInterval < 1) + interval = 1; + else if (req->usbEp->desc.bInterval > 16) + interval = 16; + else + interval = req->usbEp->desc.bInterval; + + interval = 1 << (interval - 1); + } + hEpQueue = hostEpPriv->isIn ? &priv->intInHEpQueue[req->epNum - 1] : + &priv->intOutHEpQueue[req->epNum - 1]; + hostEpPriv->type = USB_ENDPOINT_XFER_INT; + hostEpPriv->frame = interval; + hostEpPriv->interval = interval; + break; + case USB_ENDPOINT_XFER_ISOC: + if (req->usbEp->desc.bInterval < 1) + interval = 1; + else if (req->usbEp->desc.bInterval > 16) + interval = 16; + else + interval = req->usbEp->desc.bInterval; + + interval = 1 << (interval - 1); + hEpQueue = hostEpPriv->isIn ? &priv->isoInHEpQueue[req->epNum - 1] : + &priv->isoOutHEpQueue[req->epNum - 1]; + hostEpPriv->type = USB_ENDPOINT_XFER_ISOC; + hostEpPriv->frame = interval; + hostEpPriv->interval = interval; + 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; + uint16_t rxerrien = 0; + uint16_t txerrien = 0; + 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); + } + rxerrien = phytium_read16(&priv->regs->rxerrien); + rxerrien &= ~(1 << hostEp->hwEpNum); + phytium_write16(&priv->regs->rxerrien, rxerrien); + phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | + ENDPRST_IO_TX | hostEp->hwEpNum); + } 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); + } + txerrien = phytium_read16(&priv->regs->txerrien); + txerrien &= ~(1 << hostEp->hwEpNum); + phytium_write16(&priv->regs->txerrien, txerrien); + phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | hostEp->hwEpNum); + } + + scheduleNextTransfer(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, + .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..3d45258278c475fdd4ef15810499e41c73d27948 --- /dev/null +++ b/drivers/usb/phytium/host_api.h @@ -0,0 +1,248 @@ +/* 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 + +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; +}; + +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; +}; + +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..964fd67bfc5321a2f5a3f0dfa47dbf5e1ff820a7 --- /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 v2"); +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..7d39e4b6034d191d0b306b7b15ceadfb6887b79e --- /dev/null +++ b/drivers/usb/phytium/platform.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-3.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", + }, + {}, +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_otg_acpi_match[] = { + { "PHYT0037", 0 }, + { } +}; +#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 v2"); +MODULE_DESCRIPTION("Phytium usb platform wrapper"); diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig index 00827d2897b534bcbefef21999bae9989aca5dd3..63be9ed503f915111d5e6686821e49e6cf500528 100644 --- a/drivers/w1/masters/Kconfig +++ b/drivers/w1/masters/Kconfig @@ -64,5 +64,15 @@ 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 + tristate "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. + endmenu diff --git a/drivers/w1/masters/Makefile b/drivers/w1/masters/Makefile index 18954cae42567283206b7eea52e7f07174a83e8f..e27abaa8f2a2fff673c51e04443da3735d96d92a 100644 --- a/drivers/w1/masters/Makefile +++ b/drivers/w1/masters/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_W1_MASTER_MXC) += mxc_w1.o obj-$(CONFIG_W1_MASTER_DS1WM) += ds1wm.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 diff --git a/drivers/w1/masters/phytium_w1.c b/drivers/w1/masters/phytium_w1.c new file mode 100644 index 0000000000000000000000000000000000000000..9a84802ac2fb7045d6defe935f23eb178d393edc --- /dev/null +++ b/drivers/w1/masters/phytium_w1.c @@ -0,0 +1,579 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/w1/masters/phytium_w1m.c + * + * 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; +} + +/* 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, +}; + +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; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id phytium_onewire_acpi_ids[] = { + { "PHYT0034", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(acpi, phytium_onewire_acpi_ids); +#endif + +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/acpi_drivers.h b/include/acpi/acpi_drivers.h index 14499757338f65416835330254b8c90a06918d64..c524216e036393cc9df66201552e9bcca5f0f15a 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -81,7 +81,7 @@ int acpi_irq_penalty_init(void); int acpi_pci_link_allocate_irq(acpi_handle handle, int index, int *triggering, - int *polarity, char **name); + int *polarity, char **name, struct fwnode_handle **rs_fwnode); int acpi_pci_link_free_irq(acpi_handle handle); /* ACPI PCI Device Binding (pci_bind.c) */ diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 1a37748766b70660faea7792294931de78398ce5..b5795704051814d667a31e6a5e71af78777bb4da 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -23,6 +23,7 @@ #include #include /* for struct resource */ +#include #include #include #include @@ -327,6 +328,22 @@ int acpi_isa_irq_to_gsi (unsigned isa_irq, u32 *gsi); void acpi_set_irq_model(enum acpi_irq_model_id model, struct fwnode_handle *fwnode); +struct irq_domain *acpi_irq_create_hierarchy(unsigned int flags, + unsigned int size, + struct fwnode_handle *fwnode, + const struct irq_domain_ops *ops, + void *host_data); + +#ifdef CONFIG_ACPI_GENERIC_GSI +struct fwnode_handle *acpi_get_irq_source_fwhandle(const struct acpi_resource_source *source); +#else +static inline +struct fwnode_handle *acpi_get_irq_source_fwhandle(const struct acpi_resource_source *source) +{ + return NULL; +} +#endif + #ifdef CONFIG_X86_IO_APIC extern int acpi_get_override_irq(u32 gsi, int *trigger, int *polarity); #else diff --git a/include/linux/efi.h b/include/linux/efi.h index 9a5d4b499271662b99041fcaf965e86b771bf5f1..3ec7b52f8994424c20c2807abdb69783c2edc575 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -566,7 +566,7 @@ typedef efi_status_t efi_get_variable_t (efi_char16_t *name, efi_guid_t *vendor, unsigned long *data_size, void *data); typedef efi_status_t efi_get_next_variable_t (unsigned long *name_size, efi_char16_t *name, efi_guid_t *vendor); -typedef efi_status_t efi_set_variable_t (efi_char16_t *name, efi_guid_t *vendor, +typedef efi_status_t efi_set_variable_t (efi_char16_t *name, efi_guid_t *vendor, u32 attr, unsigned long data_size, void *data); typedef efi_status_t efi_get_next_high_mono_count_t (u32 *count); @@ -672,6 +672,7 @@ void efi_native_runtime_setup(void); #define LINUX_EFI_LOADER_ENTRY_GUID EFI_GUID(0x4a67b082, 0x0a4c, 0x41cf, 0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f) #define LINUX_EFI_RANDOM_SEED_TABLE_GUID EFI_GUID(0x1ce1e5bc, 0x7ceb, 0x42f2, 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b) #define LINUX_EFI_TPM_EVENT_LOG_GUID EFI_GUID(0xb7799cb0, 0xeca2, 0x4943, 0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa) +#define LINUX_EFI_MEMRESERVE_TABLE_GUID EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5, 0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2) typedef struct { efi_guid_t guid; @@ -957,6 +958,7 @@ extern struct efi { unsigned long mem_attr_table; /* memory attributes table */ unsigned long rng_seed; /* UEFI firmware random seed */ unsigned long tpm_log; /* TPM2 Event Log table */ + unsigned long mem_reserve; /* Linux EFI memreserve table */ efi_get_time_t *get_time; efi_set_time_t *set_time; efi_get_wakeup_time_t *get_wakeup_time; @@ -1045,6 +1047,7 @@ extern int __init efi_uart_console_only (void); extern u64 efi_mem_desc_end(efi_memory_desc_t *md); extern int efi_mem_desc_lookup(u64 phys_addr, efi_memory_desc_t *out_md); extern void efi_mem_reserve(phys_addr_t addr, u64 size); +extern int efi_mem_reserve_persistent(phys_addr_t addr, u64 size); extern void efi_initialize_iomem_resources(struct resource *code_resource, struct resource *data_resource, struct resource *bss_resource); extern void efi_reserve_boot_services(void); @@ -1707,4 +1710,20 @@ extern struct efi_runtime_work efi_rts_work; /* Workqueue to queue EFI Runtime Services */ extern struct workqueue_struct *efi_rts_wq; +struct linux_efi_memreserve { + int size; // allocated size of the array + atomic_t count; // number of entries used + phys_addr_t next; // pa of next struct instance + struct { + phys_addr_t base; + phys_addr_t size; + } entry[0]; +}; + +#define EFI_MEMRESERVE_SIZE(count) (sizeof(struct linux_efi_memreserve) + \ + (count) * sizeof(((struct linux_efi_memreserve *)0)->entry[0])) + +#define EFI_MEMRESERVE_COUNT(size) (((size) - sizeof(struct linux_efi_memreserve)) \ + / sizeof(((struct linux_efi_memreserve *)0)->entry[0])) + #endif /* _LINUX_EFI_H */ diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h new file mode 100644 index 0000000000000000000000000000000000000000..73b0982cc5193c2926fc733dd588195dfec57d9f --- /dev/null +++ b/include/linux/i3c/ccc.h @@ -0,0 +1,385 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Cadence Design Systems Inc. + * + * Author: Boris Brezillon + */ + +#ifndef I3C_CCC_H +#define I3C_CCC_H + +#include +#include + +/* I3C CCC (Common Command Codes) related definitions */ +#define I3C_CCC_DIRECT BIT(7) + +#define I3C_CCC_ID(id, broadcast) \ + ((id) | ((broadcast) ? 0 : I3C_CCC_DIRECT)) + +/* Commands valid in both broadcast and unicast modes */ +#define I3C_CCC_ENEC(broadcast) I3C_CCC_ID(0x0, broadcast) +#define I3C_CCC_DISEC(broadcast) I3C_CCC_ID(0x1, broadcast) +#define I3C_CCC_ENTAS(as, broadcast) I3C_CCC_ID(0x2 + (as), broadcast) +#define I3C_CCC_RSTDAA(broadcast) I3C_CCC_ID(0x6, broadcast) +#define I3C_CCC_SETMWL(broadcast) I3C_CCC_ID(0x9, broadcast) +#define I3C_CCC_SETMRL(broadcast) I3C_CCC_ID(0xa, broadcast) +#define I3C_CCC_SETXTIME(broadcast) ((broadcast) ? 0x28 : 0x98) +#define I3C_CCC_VENDOR(id, broadcast) ((id) + ((broadcast) ? 0x61 : 0xe0)) + +/* Broadcast-only commands */ +#define I3C_CCC_ENTDAA I3C_CCC_ID(0x7, true) +#define I3C_CCC_DEFSLVS I3C_CCC_ID(0x8, true) +#define I3C_CCC_ENTTM I3C_CCC_ID(0xb, true) +#define I3C_CCC_ENTHDR(x) I3C_CCC_ID(0x20 + (x), true) + +/* Unicast-only commands */ +#define I3C_CCC_SETDASA I3C_CCC_ID(0x7, false) +#define I3C_CCC_SETNEWDA I3C_CCC_ID(0x8, false) +#define I3C_CCC_GETMWL I3C_CCC_ID(0xb, false) +#define I3C_CCC_GETMRL I3C_CCC_ID(0xc, false) +#define I3C_CCC_GETPID I3C_CCC_ID(0xd, false) +#define I3C_CCC_GETBCR I3C_CCC_ID(0xe, false) +#define I3C_CCC_GETDCR I3C_CCC_ID(0xf, false) +#define I3C_CCC_GETSTATUS I3C_CCC_ID(0x10, false) +#define I3C_CCC_GETACCMST I3C_CCC_ID(0x11, false) +#define I3C_CCC_SETBRGTGT I3C_CCC_ID(0x13, false) +#define I3C_CCC_GETMXDS I3C_CCC_ID(0x14, false) +#define I3C_CCC_GETHDRCAP I3C_CCC_ID(0x15, false) +#define I3C_CCC_GETXTIME I3C_CCC_ID(0x19, false) + +#define I3C_CCC_EVENT_SIR BIT(0) +#define I3C_CCC_EVENT_MR BIT(1) +#define I3C_CCC_EVENT_HJ BIT(3) + +/** + * struct i3c_ccc_events - payload passed to ENEC/DISEC CCC + * + * @events: bitmask of I3C_CCC_EVENT_xxx events. + * + * Depending on the CCC command, the specific events coming from all devices + * (broadcast version) or a specific device (unicast version) will be + * enabled (ENEC) or disabled (DISEC). + */ +struct i3c_ccc_events { + u8 events; +}; + +/** + * struct i3c_ccc_mwl - payload passed to SETMWL/GETMWL CCC + * + * @len: maximum write length in bytes + * + * The maximum write length is only applicable to SDR private messages or + * extended Write CCCs (like SETXTIME). + */ +struct i3c_ccc_mwl { + __be16 len; +}; + +/** + * struct i3c_ccc_mrl - payload passed to SETMRL/GETMRL CCC + * + * @len: maximum read length in bytes + * @ibi_len: maximum IBI payload length + * + * The maximum read length is only applicable to SDR private messages or + * extended Read CCCs (like GETXTIME). + * The IBI length is only valid if the I3C slave is IBI capable + * (%I3C_BCR_IBI_REQ_CAP is set). + */ +struct i3c_ccc_mrl { + __be16 read_len; + u8 ibi_len; +} __packed; + +/** + * struct i3c_ccc_dev_desc - I3C/I2C device descriptor used for DEFSLVS + * + * @dyn_addr: dynamic address assigned to the I3C slave or 0 if the entry is + * describing an I2C slave. + * @dcr: DCR value (not applicable to entries describing I2C devices) + * @lvr: LVR value (not applicable to entries describing I3C devices) + * @bcr: BCR value or 0 if this entry is describing an I2C slave + * @static_addr: static address or 0 if the device does not have a static + * address + * + * The DEFSLVS command should be passed an array of i3c_ccc_dev_desc + * descriptors (one entry per I3C/I2C dev controlled by the master). + */ +struct i3c_ccc_dev_desc { + u8 dyn_addr; + union { + u8 dcr; + u8 lvr; + }; + u8 bcr; + u8 static_addr; +}; + +/** + * struct i3c_ccc_defslvs - payload passed to DEFSLVS CCC + * + * @count: number of dev descriptors + * @master: descriptor describing the current master + * @slaves: array of descriptors describing slaves controlled by the + * current master + * + * Information passed to the broadcast DEFSLVS to propagate device + * information to all masters currently acting as slaves on the bus. + * This is only meaningful if you have more than one master. + */ +struct i3c_ccc_defslvs { + u8 count; + struct i3c_ccc_dev_desc master; + struct i3c_ccc_dev_desc slaves[0]; +} __packed; + +/** + * enum i3c_ccc_test_mode - enum listing all available test modes + * + * @I3C_CCC_EXIT_TEST_MODE: exit test mode + * @I3C_CCC_VENDOR_TEST_MODE: enter vendor test mode + */ +enum i3c_ccc_test_mode { + I3C_CCC_EXIT_TEST_MODE, + I3C_CCC_VENDOR_TEST_MODE, +}; + +/** + * struct i3c_ccc_enttm - payload passed to ENTTM CCC + * + * @mode: one of the &enum i3c_ccc_test_mode modes + * + * Information passed to the ENTTM CCC to instruct an I3C device to enter a + * specific test mode. + */ +struct i3c_ccc_enttm { + u8 mode; +}; + +/** + * struct i3c_ccc_setda - payload passed to SETNEWDA and SETDASA CCCs + * + * @addr: dynamic address to assign to an I3C device + * + * Information passed to the SETNEWDA and SETDASA CCCs to assign/change the + * dynamic address of an I3C device. + */ +struct i3c_ccc_setda { + u8 addr; +}; + +/** + * struct i3c_ccc_getpid - payload passed to GETPID CCC + * + * @pid: 48 bits PID in big endian + */ +struct i3c_ccc_getpid { + u8 pid[6]; +}; + +/** + * struct i3c_ccc_getbcr - payload passed to GETBCR CCC + * + * @bcr: BCR (Bus Characteristic Register) value + */ +struct i3c_ccc_getbcr { + u8 bcr; +}; + +/** + * struct i3c_ccc_getdcr - payload passed to GETDCR CCC + * + * @dcr: DCR (Device Characteristic Register) value + */ +struct i3c_ccc_getdcr { + u8 dcr; +}; + +#define I3C_CCC_STATUS_PENDING_INT(status) ((status) & GENMASK(3, 0)) +#define I3C_CCC_STATUS_PROTOCOL_ERROR BIT(5) +#define I3C_CCC_STATUS_ACTIVITY_MODE(status) \ + (((status) & GENMASK(7, 6)) >> 6) + +/** + * struct i3c_ccc_getstatus - payload passed to GETSTATUS CCC + * + * @status: status of the I3C slave (see I3C_CCC_STATUS_xxx macros for more + * information). + */ +struct i3c_ccc_getstatus { + __be16 status; +}; + +/** + * struct i3c_ccc_getaccmst - payload passed to GETACCMST CCC + * + * @newmaster: address of the master taking bus ownership + */ +struct i3c_ccc_getaccmst { + u8 newmaster; +}; + +/** + * struct i3c_ccc_bridged_slave_desc - bridged slave descriptor + * + * @addr: dynamic address of the bridged device + * @id: ID of the slave device behind the bridge + */ +struct i3c_ccc_bridged_slave_desc { + u8 addr; + __be16 id; +} __packed; + +/** + * struct i3c_ccc_setbrgtgt - payload passed to SETBRGTGT CCC + * + * @count: number of bridged slaves + * @bslaves: bridged slave descriptors + */ +struct i3c_ccc_setbrgtgt { + u8 count; + struct i3c_ccc_bridged_slave_desc bslaves[0]; +} __packed; + +/** + * enum i3c_sdr_max_data_rate - max data rate values for private SDR transfers + */ +enum i3c_sdr_max_data_rate { + I3C_SDR0_FSCL_MAX, + I3C_SDR1_FSCL_8MHZ, + I3C_SDR2_FSCL_6MHZ, + I3C_SDR3_FSCL_4MHZ, + I3C_SDR4_FSCL_2MHZ, +}; + +/** + * enum i3c_tsco - clock to data turn-around + */ +enum i3c_tsco { + I3C_TSCO_8NS, + I3C_TSCO_9NS, + I3C_TSCO_10NS, + I3C_TSCO_11NS, + I3C_TSCO_12NS, +}; + +#define I3C_CCC_MAX_SDR_FSCL_MASK GENMASK(2, 0) +#define I3C_CCC_MAX_SDR_FSCL(x) ((x) & I3C_CCC_MAX_SDR_FSCL_MASK) + +/** + * struct i3c_ccc_getmxds - payload passed to GETMXDS CCC + * + * @maxwr: write limitations + * @maxrd: read limitations + * @maxrdturn: maximum read turn-around expressed micro-seconds and + * little-endian formatted + */ +struct i3c_ccc_getmxds { + u8 maxwr; + u8 maxrd; + u8 maxrdturn[3]; +} __packed; + +#define I3C_CCC_HDR_MODE(mode) BIT(mode) + +/** + * struct i3c_ccc_gethdrcap - payload passed to GETHDRCAP CCC + * + * @modes: bitmap of supported HDR modes + */ +struct i3c_ccc_gethdrcap { + u8 modes; +} __packed; + +/** + * enum i3c_ccc_setxtime_subcmd - SETXTIME sub-commands + */ +enum i3c_ccc_setxtime_subcmd { + I3C_CCC_SETXTIME_ST = 0x7f, + I3C_CCC_SETXTIME_DT = 0xbf, + I3C_CCC_SETXTIME_ENTER_ASYNC_MODE0 = 0xdf, + I3C_CCC_SETXTIME_ENTER_ASYNC_MODE1 = 0xef, + I3C_CCC_SETXTIME_ENTER_ASYNC_MODE2 = 0xf7, + I3C_CCC_SETXTIME_ENTER_ASYNC_MODE3 = 0xfb, + I3C_CCC_SETXTIME_ASYNC_TRIGGER = 0xfd, + I3C_CCC_SETXTIME_TPH = 0x3f, + I3C_CCC_SETXTIME_TU = 0x9f, + I3C_CCC_SETXTIME_ODR = 0x8f, +}; + +/** + * struct i3c_ccc_setxtime - payload passed to SETXTIME CCC + * + * @subcmd: one of the sub-commands ddefined in &enum i3c_ccc_setxtime_subcmd + * @data: sub-command payload. Amount of data is determined by + * &i3c_ccc_setxtime->subcmd + */ +struct i3c_ccc_setxtime { + u8 subcmd; + u8 data[0]; +} __packed; + +#define I3C_CCC_GETXTIME_SYNC_MODE BIT(0) +#define I3C_CCC_GETXTIME_ASYNC_MODE(x) BIT((x) + 1) +#define I3C_CCC_GETXTIME_OVERFLOW BIT(7) + +/** + * struct i3c_ccc_getxtime - payload retrieved from GETXTIME CCC + * + * @supported_modes: bitmap describing supported XTIME modes + * @state: current status (enabled mode and overflow status) + * @frequency: slave's internal oscillator frequency in 500KHz steps + * @inaccuracy: slave's internal oscillator inaccuracy in 0.1% steps + */ +struct i3c_ccc_getxtime { + u8 supported_modes; + u8 state; + u8 frequency; + u8 inaccuracy; +} __packed; + +/** + * struct i3c_ccc_cmd_payload - CCC payload + * + * @len: payload length + * @data: payload data. This buffer must be DMA-able + */ +struct i3c_ccc_cmd_payload { + u16 len; + void *data; +}; + +/** + * struct i3c_ccc_cmd_dest - CCC command destination + * + * @addr: can be an I3C device address or the broadcast address if this is a + * broadcast CCC + * @payload: payload to be sent to this device or broadcasted + */ +struct i3c_ccc_cmd_dest { + u8 addr; + struct i3c_ccc_cmd_payload payload; +}; + +/** + * struct i3c_ccc_cmd - CCC command + * + * @rnw: true if the CCC should retrieve data from the device. Only valid for + * unicast commands + * @id: CCC command id + * @ndests: number of destinations. Should always be one for broadcast commands + * @dests: array of destinations and associated payload for this CCC. Most of + * the time, only one destination is provided + * @err: I3C error code + */ +struct i3c_ccc_cmd { + u8 rnw; + u8 id; + unsigned int ndests; + struct i3c_ccc_cmd_dest *dests; + enum i3c_error_code err; +}; + +#endif /* I3C_CCC_H */ diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h new file mode 100644 index 0000000000000000000000000000000000000000..de102e4418ab4118f00cf5fd5b6adda4c67eb73e --- /dev/null +++ b/include/linux/i3c/device.h @@ -0,0 +1,335 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Cadence Design Systems Inc. + * + * Author: Boris Brezillon + */ + +#ifndef I3C_DEV_H +#define I3C_DEV_H + +#include +#include +#include +#include +#include +#include + +/** + * enum i3c_error_code - I3C error codes + * + * These are the standard error codes as defined by the I3C specification. + * When -EIO is returned by the i3c_device_do_priv_xfers() or + * i3c_device_send_hdr_cmds() one can check the error code in + * &struct_i3c_priv_xfer.err or &struct i3c_hdr_cmd.err to get a better idea of + * what went wrong. + * + * @I3C_ERROR_UNKNOWN: unknown error, usually means the error is not I3C + * related + * @I3C_ERROR_M0: M0 error + * @I3C_ERROR_M1: M1 error + * @I3C_ERROR_M2: M2 error + */ +enum i3c_error_code { + I3C_ERROR_UNKNOWN = 0, + I3C_ERROR_M0 = 1, + I3C_ERROR_M1, + I3C_ERROR_M2, +}; + +/** + * enum i3c_hdr_mode - HDR mode ids + * @I3C_HDR_DDR: DDR mode + * @I3C_HDR_TSP: TSP mode + * @I3C_HDR_TSL: TSL mode + */ +enum i3c_hdr_mode { + I3C_HDR_DDR, + I3C_HDR_TSP, + I3C_HDR_TSL, +}; + +/** + * struct i3c_priv_xfer - I3C SDR private transfer + * @rnw: encodes the transfer direction. true for a read, false for a write + * @len: transfer length in bytes of the transfer + * @data: input/output buffer + * @data.in: input buffer. Must point to a DMA-able buffer + * @data.out: output buffer. Must point to a DMA-able buffer + * @err: I3C error code + */ +struct i3c_priv_xfer { + u8 rnw; + u16 len; + union { + void *in; + const void *out; + } data; + enum i3c_error_code err; +}; + +/** + * enum i3c_dcr - I3C DCR values + * @I3C_DCR_GENERIC_DEVICE: generic I3C device + */ +enum i3c_dcr { + I3C_DCR_GENERIC_DEVICE = 0, +}; + +#define I3C_PID_MANUF_ID(pid) (((pid) & GENMASK_ULL(47, 33)) >> 33) +#define I3C_PID_RND_LOWER_32BITS(pid) (!!((pid) & BIT_ULL(32))) +#define I3C_PID_RND_VAL(pid) ((pid) & GENMASK_ULL(31, 0)) +#define I3C_PID_PART_ID(pid) (((pid) & GENMASK_ULL(31, 16)) >> 16) +#define I3C_PID_INSTANCE_ID(pid) (((pid) & GENMASK_ULL(15, 12)) >> 12) +#define I3C_PID_EXTRA_INFO(pid) ((pid) & GENMASK_ULL(11, 0)) + +#define I3C_BCR_DEVICE_ROLE(bcr) ((bcr) & GENMASK(7, 6)) +#define I3C_BCR_I3C_SLAVE (0 << 6) +#define I3C_BCR_I3C_MASTER (1 << 6) +#define I3C_BCR_HDR_CAP BIT(5) +#define I3C_BCR_BRIDGE BIT(4) +#define I3C_BCR_OFFLINE_CAP BIT(3) +#define I3C_BCR_IBI_PAYLOAD BIT(2) +#define I3C_BCR_IBI_REQ_CAP BIT(1) +#define I3C_BCR_MAX_DATA_SPEED_LIM BIT(0) + +/** + * struct i3c_device_info - I3C device information + * @pid: Provisional ID + * @bcr: Bus Characteristic Register + * @dcr: Device Characteristic Register + * @static_addr: static/I2C address + * @dyn_addr: dynamic address + * @hdr_cap: supported HDR modes + * @max_read_ds: max read speed information + * @max_write_ds: max write speed information + * @max_ibi_len: max IBI payload length + * @max_read_turnaround: max read turn-around time in micro-seconds + * @max_read_len: max private SDR read length in bytes + * @max_write_len: max private SDR write length in bytes + * + * These are all basic information that should be advertised by an I3C device. + * Some of them are optional depending on the device type and device + * capabilities. + * For each I3C slave attached to a master with + * i3c_master_add_i3c_dev_locked(), the core will send the relevant CCC command + * to retrieve these data. + */ +struct i3c_device_info { + u64 pid; + u8 bcr; + u8 dcr; + u8 static_addr; + u8 dyn_addr; + u8 hdr_cap; + u8 max_read_ds; + u8 max_write_ds; + u8 max_ibi_len; + u32 max_read_turnaround; + u16 max_read_len; + u16 max_write_len; +}; + +/* + * I3C device internals are kept hidden from I3C device users. It's just + * simpler to refactor things when everything goes through getter/setters, and + * I3C device drivers should not have to worry about internal representation + * anyway. + */ +struct i3c_device; + +/* These macros should be used to i3c_device_id entries. */ +#define I3C_MATCH_MANUF_AND_PART (I3C_MATCH_MANUF | I3C_MATCH_PART) + +#define I3C_DEVICE(_manufid, _partid, _drvdata) \ + { \ + .match_flags = I3C_MATCH_MANUF_AND_PART, \ + .manuf_id = _manufid, \ + .part_id = _partid, \ + .data = _drvdata, \ + } + +#define I3C_DEVICE_EXTRA_INFO(_manufid, _partid, _info, _drvdata) \ + { \ + .match_flags = I3C_MATCH_MANUF_AND_PART | \ + I3C_MATCH_EXTRA_INFO, \ + .manuf_id = _manufid, \ + .part_id = _partid, \ + .extra_info = _info, \ + .data = _drvdata, \ + } + +#define I3C_CLASS(_dcr, _drvdata) \ + { \ + .match_flags = I3C_MATCH_DCR, \ + .dcr = _dcr, \ + } + +/** + * struct i3c_driver - I3C device driver + * @driver: inherit from device_driver + * @probe: I3C device probe method + * @remove: I3C device remove method + * @id_table: I3C device match table. Will be used by the framework to decide + * which device to bind to this driver + */ +struct i3c_driver { + struct device_driver driver; + int (*probe)(struct i3c_device *dev); + int (*remove)(struct i3c_device *dev); + const struct i3c_device_id *id_table; +}; + +static inline struct i3c_driver *drv_to_i3cdrv(struct device_driver *drv) +{ + return container_of(drv, struct i3c_driver, driver); +} + +struct device *i3cdev_to_dev(struct i3c_device *i3cdev); +struct i3c_device *dev_to_i3cdev(struct device *dev); + +const struct i3c_device_id * +i3c_device_match_id(struct i3c_device *i3cdev, + const struct i3c_device_id *id_table); + +static inline void i3cdev_set_drvdata(struct i3c_device *i3cdev, + void *data) +{ + struct device *dev = i3cdev_to_dev(i3cdev); + + dev_set_drvdata(dev, data); +} + +static inline void *i3cdev_get_drvdata(struct i3c_device *i3cdev) +{ + struct device *dev = i3cdev_to_dev(i3cdev); + + return dev_get_drvdata(dev); +} + +int i3c_driver_register_with_owner(struct i3c_driver *drv, + struct module *owner); +void i3c_driver_unregister(struct i3c_driver *drv); + +#define i3c_driver_register(__drv) \ + i3c_driver_register_with_owner(__drv, THIS_MODULE) + +/** + * module_i3c_driver() - Register a module providing an I3C driver + * @__drv: the I3C driver to register + * + * Provide generic init/exit functions that simply register/unregister an I3C + * driver. + * Should be used by any driver that does not require extra init/cleanup steps. + */ +#define module_i3c_driver(__drv) \ + module_driver(__drv, i3c_driver_register, i3c_driver_unregister) + +/** + * i3c_i2c_driver_register() - Register an i2c and an i3c driver + * @i3cdrv: the I3C driver to register + * @i2cdrv: the I2C driver to register + * + * This function registers both @i2cdev and @i3cdev, and fails if one of these + * registrations fails. This is mainly useful for devices that support both I2C + * and I3C modes. + * Note that when CONFIG_I3C is not enabled, this function only registers the + * I2C driver. + * + * Return: 0 if both registrations succeeds, a negative error code otherwise. + */ +static inline int i3c_i2c_driver_register(struct i3c_driver *i3cdrv, + struct i2c_driver *i2cdrv) +{ + int ret; + + ret = i2c_add_driver(i2cdrv); + if (ret || !IS_ENABLED(CONFIG_I3C)) + return ret; + + ret = i3c_driver_register(i3cdrv); + if (ret) + i2c_del_driver(i2cdrv); + + return ret; +} + +/** + * i3c_i2c_driver_unregister() - Unregister an i2c and an i3c driver + * @i3cdrv: the I3C driver to register + * @i2cdrv: the I2C driver to register + * + * This function unregisters both @i3cdrv and @i2cdrv. + * Note that when CONFIG_I3C is not enabled, this function only unregisters the + * @i2cdrv. + */ +static inline void i3c_i2c_driver_unregister(struct i3c_driver *i3cdrv, + struct i2c_driver *i2cdrv) +{ + if (IS_ENABLED(CONFIG_I3C)) + i3c_driver_unregister(i3cdrv); + + i2c_del_driver(i2cdrv); +} + +/** + * module_i3c_i2c_driver() - Register a module providing an I3C and an I2C + * driver + * @__i3cdrv: the I3C driver to register + * @__i2cdrv: the I3C driver to register + * + * Provide generic init/exit functions that simply register/unregister an I3C + * and an I2C driver. + * This macro can be used even if CONFIG_I3C is disabled, in this case, only + * the I2C driver will be registered. + * Should be used by any driver that does not require extra init/cleanup steps. + */ +#define module_i3c_i2c_driver(__i3cdrv, __i2cdrv) \ + module_driver(__i3cdrv, \ + i3c_i2c_driver_register, \ + i3c_i2c_driver_unregister) + +int i3c_device_do_priv_xfers(struct i3c_device *dev, + struct i3c_priv_xfer *xfers, + int nxfers); + +void i3c_device_get_info(struct i3c_device *dev, struct i3c_device_info *info); + +struct i3c_ibi_payload { + unsigned int len; + const void *data; +}; + +/** + * struct i3c_ibi_setup - IBI setup object + * @max_payload_len: maximum length of the payload associated to an IBI. If one + * IBI appears to have a payload that is bigger than this + * number, the IBI will be rejected. + * @num_slots: number of pre-allocated IBI slots. This should be chosen so that + * the system never runs out of IBI slots, otherwise you'll lose + * IBIs. + * @handler: IBI handler, every time an IBI is received. This handler is called + * in a workqueue context. It is allowed to sleep and send new + * messages on the bus, though it's recommended to keep the + * processing done there as fast as possible to avoid delaying + * processing of other queued on the same workqueue. + * + * Temporary structure used to pass information to i3c_device_request_ibi(). + * This object can be allocated on the stack since i3c_device_request_ibi() + * copies every bit of information and do not use it after + * i3c_device_request_ibi() has returned. + */ +struct i3c_ibi_setup { + unsigned int max_payload_len; + unsigned int num_slots; + void (*handler)(struct i3c_device *dev, + const struct i3c_ibi_payload *payload); +}; + +int i3c_device_request_ibi(struct i3c_device *dev, + const struct i3c_ibi_setup *setup); +void i3c_device_free_ibi(struct i3c_device *dev); +int i3c_device_enable_ibi(struct i3c_device *dev); +int i3c_device_disable_ibi(struct i3c_device *dev); + +#endif /* I3C_DEV_H */ diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h new file mode 100644 index 0000000000000000000000000000000000000000..9cb39d901cd5f95070adc82609f50134ff9d8937 --- /dev/null +++ b/include/linux/i3c/master.h @@ -0,0 +1,655 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Cadence Design Systems Inc. + * + * Author: Boris Brezillon + */ + +#ifndef I3C_MASTER_H +#define I3C_MASTER_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define I3C_HOT_JOIN_ADDR 0x2 +#define I3C_BROADCAST_ADDR 0x7e +#define I3C_MAX_ADDR GENMASK(6, 0) + +struct i3c_master_controller; +struct i3c_bus; +struct i2c_device; +struct i3c_device; + +/** + * struct i3c_i2c_dev_desc - Common part of the I3C/I2C device descriptor + * @node: node element used to insert the slot into the I2C or I3C device + * list + * @master: I3C master that instantiated this device. Will be used to do + * I2C/I3C transfers + * @master_priv: master private data assigned to the device. Can be used to + * add master specific information + * + * This structure is describing common I3C/I2C dev information. + */ +struct i3c_i2c_dev_desc { + struct list_head node; + struct i3c_master_controller *master; + void *master_priv; +}; + +#define I3C_LVR_I2C_INDEX_MASK GENMASK(7, 5) +#define I3C_LVR_I2C_INDEX(x) ((x) << 5) +#define I3C_LVR_I2C_FM_MODE BIT(4) + +#define I2C_MAX_ADDR GENMASK(6, 0) + +/** + * struct i2c_dev_boardinfo - I2C device board information + * @node: used to insert the boardinfo object in the I2C boardinfo list + * @base: regular I2C board information + * @lvr: LVR (Legacy Virtual Register) needed by the I3C core to know about + * the I2C device limitations + * + * This structure is used to attach board-level information to an I2C device. + * Each I2C device connected on the I3C bus should have one. + */ +struct i2c_dev_boardinfo { + struct list_head node; + struct i2c_board_info base; + u8 lvr; +}; + +/** + * struct i2c_dev_desc - I2C device descriptor + * @common: common part of the I2C device descriptor + * @boardinfo: pointer to the boardinfo attached to this I2C device + * @dev: I2C device object registered to the I2C framework + * @addr: I2C device address + * @lvr: LVR (Legacy Virtual Register) needed by the I3C core to know about + * the I2C device limitations + * + * Each I2C device connected on the bus will have an i2c_dev_desc. + * This object is created by the core and later attached to the controller + * using &struct_i3c_master_controller->ops->attach_i2c_dev(). + * + * &struct_i2c_dev_desc is the internal representation of an I2C device + * connected on an I3C bus. This object is also passed to all + * &struct_i3c_master_controller_ops hooks. + */ +struct i2c_dev_desc { + struct i3c_i2c_dev_desc common; + const struct i2c_dev_boardinfo *boardinfo; + struct i2c_client *dev; + u16 addr; + u8 lvr; +}; + +/** + * struct i3c_ibi_slot - I3C IBI (In-Band Interrupt) slot + * @work: work associated to this slot. The IBI handler will be called from + * there + * @dev: the I3C device that has generated this IBI + * @len: length of the payload associated to this IBI + * @data: payload buffer + * + * An IBI slot is an object pre-allocated by the controller and used when an + * IBI comes in. + * Every time an IBI comes in, the I3C master driver should find a free IBI + * slot in its IBI slot pool, retrieve the IBI payload and queue the IBI using + * i3c_master_queue_ibi(). + * + * How IBI slots are allocated is left to the I3C master driver, though, for + * simple kmalloc-based allocation, the generic IBI slot pool can be used. + */ +struct i3c_ibi_slot { + struct work_struct work; + struct i3c_dev_desc *dev; + unsigned int len; + void *data; +}; + +/** + * struct i3c_device_ibi_info - IBI information attached to a specific device + * @all_ibis_handled: used to be informed when no more IBIs are waiting to be + * processed. Used by i3c_device_disable_ibi() to wait for + * all IBIs to be dequeued + * @pending_ibis: count the number of pending IBIs. Each pending IBI has its + * work element queued to the controller workqueue + * @max_payload_len: maximum payload length for an IBI coming from this device. + * this value is specified when calling + * i3c_device_request_ibi() and should not change at run + * time. All messages IBIs exceeding this limit should be + * rejected by the master + * @num_slots: number of IBI slots reserved for this device + * @enabled: reflect the IBI status + * @handler: IBI handler specified at i3c_device_request_ibi() call time. This + * handler will be called from the controller workqueue, and as such + * is allowed to sleep (though it is recommended to process the IBI + * as fast as possible to not stall processing of other IBIs queued + * on the same workqueue). + * New I3C messages can be sent from the IBI handler + * + * The &struct_i3c_device_ibi_info object is allocated when + * i3c_device_request_ibi() is called and attached to a specific device. This + * object is here to manage IBIs coming from a specific I3C device. + * + * Note that this structure is the generic view of the IBI management + * infrastructure. I3C master drivers may have their own internal + * representation which they can associate to the device using + * controller-private data. + */ +struct i3c_device_ibi_info { + struct completion all_ibis_handled; + atomic_t pending_ibis; + unsigned int max_payload_len; + unsigned int num_slots; + unsigned int enabled; + void (*handler)(struct i3c_device *dev, + const struct i3c_ibi_payload *payload); +}; + +/** + * struct i3c_dev_boardinfo - I3C device board information + * @node: used to insert the boardinfo object in the I3C boardinfo list + * @init_dyn_addr: initial dynamic address requested by the FW. We provide no + * guarantee that the device will end up using this address, + * but try our best to assign this specific address to the + * device + * @static_addr: static address the I3C device listen on before it's been + * assigned a dynamic address by the master. Will be used during + * bus initialization to assign it a specific dynamic address + * before starting DAA (Dynamic Address Assignment) + * @pid: I3C Provisional ID exposed by the device. This is a unique identifier + * that may be used to attach boardinfo to i3c_dev_desc when the device + * does not have a static address + * @of_node: optional DT node in case the device has been described in the DT + * + * This structure is used to attach board-level information to an I3C device. + * Not all I3C devices connected on the bus will have a boardinfo. It's only + * needed if you want to attach extra resources to a device or assign it a + * specific dynamic address. + */ +struct i3c_dev_boardinfo { + struct list_head node; + u8 init_dyn_addr; + u8 static_addr; + u64 pid; + struct device_node *of_node; +}; + +/** + * struct i3c_dev_desc - I3C device descriptor + * @common: common part of the I3C device descriptor + * @info: I3C device information. Will be automatically filled when you create + * your device with i3c_master_add_i3c_dev_locked() + * @ibi_lock: lock used to protect the &struct_i3c_device->ibi + * @ibi: IBI info attached to a device. Should be NULL until + * i3c_device_request_ibi() is called + * @dev: pointer to the I3C device object exposed to I3C device drivers. This + * should never be accessed from I3C master controller drivers. Only core + * code should manipulate it in when updating the dev <-> desc link or + * when propagating IBI events to the driver + * @boardinfo: pointer to the boardinfo attached to this I3C device + * + * Internal representation of an I3C device. This object is only used by the + * core and passed to I3C master controller drivers when they're requested to + * do some operations on the device. + * The core maintains the link between the internal I3C dev descriptor and the + * object exposed to the I3C device drivers (&struct_i3c_device). + */ +struct i3c_dev_desc { + struct i3c_i2c_dev_desc common; + struct i3c_device_info info; + struct mutex ibi_lock; + struct i3c_device_ibi_info *ibi; + struct i3c_device *dev; + const struct i3c_dev_boardinfo *boardinfo; +}; + +/** + * struct i3c_device - I3C device object + * @dev: device object to register the I3C dev to the device model + * @desc: pointer to an i3c device descriptor object. This link is updated + * every time the I3C device is rediscovered with a different dynamic + * address assigned + * @bus: I3C bus this device is attached to + * + * I3C device object exposed to I3C device drivers. The takes care of linking + * this object to the relevant &struct_i3c_dev_desc one. + * All I3C devs on the I3C bus are represented, including I3C masters. For each + * of them, we have an instance of &struct i3c_device. + */ +struct i3c_device { + struct device dev; + struct i3c_dev_desc *desc; + struct i3c_bus *bus; +}; + +/* + * The I3C specification says the maximum number of devices connected on the + * bus is 11, but this number depends on external parameters like trace length, + * capacitive load per Device, and the types of Devices present on the Bus. + * I3C master can also have limitations, so this number is just here as a + * reference and should be adjusted on a per-controller/per-board basis. + */ +#define I3C_BUS_MAX_DEVS 11 + +#define I3C_BUS_MAX_I3C_SCL_RATE 12900000 +#define I3C_BUS_TYP_I3C_SCL_RATE 12500000 +#define I3C_BUS_I2C_FM_PLUS_SCL_RATE 1000000 +#define I3C_BUS_I2C_FM_SCL_RATE 400000 +#define I3C_BUS_TLOW_OD_MIN_NS 200 + +/** + * enum i3c_bus_mode - I3C bus mode + * @I3C_BUS_MODE_PURE: only I3C devices are connected to the bus. No limitation + * expected + * @I3C_BUS_MODE_MIXED_FAST: I2C devices with 50ns spike filter are present on + * the bus. The only impact in this mode is that the + * high SCL pulse has to stay below 50ns to trick I2C + * devices when transmitting I3C frames + * @I3C_BUS_MODE_MIXED_LIMITED: I2C devices without 50ns spike filter are + * present on the bus. However they allow + * compliance up to the maximum SDR SCL clock + * frequency. + * @I3C_BUS_MODE_MIXED_SLOW: I2C devices without 50ns spike filter are present + * on the bus + */ +enum i3c_bus_mode { + I3C_BUS_MODE_PURE, + I3C_BUS_MODE_MIXED_FAST, + I3C_BUS_MODE_MIXED_LIMITED, + I3C_BUS_MODE_MIXED_SLOW, +}; + +/** + * enum i3c_addr_slot_status - I3C address slot status + * @I3C_ADDR_SLOT_FREE: address is free + * @I3C_ADDR_SLOT_RSVD: address is reserved + * @I3C_ADDR_SLOT_I2C_DEV: address is assigned to an I2C device + * @I3C_ADDR_SLOT_I3C_DEV: address is assigned to an I3C device + * @I3C_ADDR_SLOT_STATUS_MASK: address slot mask + * + * On an I3C bus, addresses are assigned dynamically, and we need to know which + * addresses are free to use and which ones are already assigned. + * + * Addresses marked as reserved are those reserved by the I3C protocol + * (broadcast address, ...). + */ +enum i3c_addr_slot_status { + I3C_ADDR_SLOT_FREE, + I3C_ADDR_SLOT_RSVD, + I3C_ADDR_SLOT_I2C_DEV, + I3C_ADDR_SLOT_I3C_DEV, + I3C_ADDR_SLOT_STATUS_MASK = 3, +}; + +/** + * struct i3c_bus - I3C bus object + * @cur_master: I3C master currently driving the bus. Since I3C is multi-master + * this can change over the time. Will be used to let a master + * know whether it needs to request bus ownership before sending + * a frame or not + * @id: bus ID. Assigned by the framework when register the bus + * @addrslots: a bitmap with 2-bits per-slot to encode the address status and + * ease the DAA (Dynamic Address Assignment) procedure (see + * &enum i3c_addr_slot_status) + * @mode: bus mode (see &enum i3c_bus_mode) + * @scl_rate.i3c: maximum rate for the clock signal when doing I3C SDR/priv + * transfers + * @scl_rate.i2c: maximum rate for the clock signal when doing I2C transfers + * @scl_rate: SCL signal rate for I3C and I2C mode + * @devs.i3c: contains a list of I3C device descriptors representing I3C + * devices connected on the bus and successfully attached to the + * I3C master + * @devs.i2c: contains a list of I2C device descriptors representing I2C + * devices connected on the bus and successfully attached to the + * I3C master + * @devs: 2 lists containing all I3C/I2C devices connected to the bus + * @lock: read/write lock on the bus. This is needed to protect against + * operations that have an impact on the whole bus and the devices + * connected to it. For example, when asking slaves to drop their + * dynamic address (RSTDAA CCC), we need to make sure no one is trying + * to send I3C frames to these devices. + * Note that this lock does not protect against concurrency between + * devices: several drivers can send different I3C/I2C frames through + * the same master in parallel. This is the responsibility of the + * master to guarantee that frames are actually sent sequentially and + * not interlaced + * + * The I3C bus is represented with its own object and not implicitly described + * by the I3C master to cope with the multi-master functionality, where one bus + * can be shared amongst several masters, each of them requesting bus ownership + * when they need to. + */ +struct i3c_bus { + struct i3c_dev_desc *cur_master; + int id; + unsigned long addrslots[((I2C_MAX_ADDR + 1) * 2) / BITS_PER_LONG]; + enum i3c_bus_mode mode; + struct { + unsigned long i3c; + unsigned long i2c; + } scl_rate; + struct { + struct list_head i3c; + struct list_head i2c; + } devs; + struct rw_semaphore lock; +}; + +/** + * struct i3c_master_controller_ops - I3C master methods + * @bus_init: hook responsible for the I3C bus initialization. You should at + * least call master_set_info() from there and set the bus mode. + * You can also put controller specific initialization in there. + * This method is mandatory. + * @bus_cleanup: cleanup everything done in + * &i3c_master_controller_ops->bus_init(). + * This method is optional. + * @attach_i3c_dev: called every time an I3C device is attached to the bus. It + * can be after a DAA or when a device is statically declared + * by the FW, in which case it will only have a static address + * and the dynamic address will be 0. + * When this function is called, device information have not + * been retrieved yet. + * This is a good place to attach master controller specific + * data to I3C devices. + * This method is optional. + * @reattach_i3c_dev: called every time an I3C device has its addressed + * changed. It can be because the device has been powered + * down and has lost its address, or it can happen when a + * device had a static address and has been assigned a + * dynamic address with SETDASA. + * This method is optional. + * @detach_i3c_dev: called when an I3C device is detached from the bus. Usually + * happens when the master device is unregistered. + * This method is optional. + * @do_daa: do a DAA (Dynamic Address Assignment) procedure. This is procedure + * should send an ENTDAA CCC command and then add all devices + * discovered sure the DAA using i3c_master_add_i3c_dev_locked(). + * Add devices added with i3c_master_add_i3c_dev_locked() will then be + * attached or re-attached to the controller. + * This method is mandatory. + * @supports_ccc_cmd: should return true if the CCC command is supported, false + * otherwise. + * This method is optional, if not provided the core assumes + * all CCC commands are supported. + * @send_ccc_cmd: send a CCC command + * This method is mandatory. + * @priv_xfers: do one or several private I3C SDR transfers + * This method is mandatory. + * @attach_i2c_dev: called every time an I2C device is attached to the bus. + * This is a good place to attach master controller specific + * data to I2C devices. + * This method is optional. + * @detach_i2c_dev: called when an I2C device is detached from the bus. Usually + * happens when the master device is unregistered. + * This method is optional. + * @i2c_xfers: do one or several I2C transfers. Note that, unlike i3c + * transfers, the core does not guarantee that buffers attached to + * the transfers are DMA-safe. If drivers want to have DMA-safe + * buffers, they should use the i2c_get_dma_safe_msg_buf() + * and i2c_put_dma_safe_msg_buf() helpers provided by the I2C + * framework. + * This method is mandatory. + * @request_ibi: attach an IBI handler to an I3C device. This implies defining + * an IBI handler and the constraints of the IBI (maximum payload + * length and number of pre-allocated slots). + * Some controllers support less IBI-capable devices than regular + * devices, so this method might return -%EBUSY if there's no + * more space for an extra IBI registration + * This method is optional. + * @free_ibi: free an IBI previously requested with ->request_ibi(). The IBI + * should have been disabled with ->disable_irq() prior to that + * This method is mandatory only if ->request_ibi is not NULL. + * @enable_ibi: enable the IBI. Only valid if ->request_ibi() has been called + * prior to ->enable_ibi(). The controller should first enable + * the IBI on the controller end (for example, unmask the hardware + * IRQ) and then send the ENEC CCC command (with the IBI flag set) + * to the I3C device. + * This method is mandatory only if ->request_ibi is not NULL. + * @disable_ibi: disable an IBI. First send the DISEC CCC command with the IBI + * flag set and then deactivate the hardware IRQ on the + * controller end. + * This method is mandatory only if ->request_ibi is not NULL. + * @recycle_ibi_slot: recycle an IBI slot. Called every time an IBI has been + * processed by its handler. The IBI slot should be put back + * in the IBI slot pool so that the controller can re-use it + * for a future IBI + * This method is mandatory only if ->request_ibi is not + * NULL. + */ +struct i3c_master_controller_ops { + int (*bus_init)(struct i3c_master_controller *master); + void (*bus_cleanup)(struct i3c_master_controller *master); + int (*attach_i3c_dev)(struct i3c_dev_desc *dev); + int (*reattach_i3c_dev)(struct i3c_dev_desc *dev, u8 old_dyn_addr); + void (*detach_i3c_dev)(struct i3c_dev_desc *dev); + int (*do_daa)(struct i3c_master_controller *master); + bool (*supports_ccc_cmd)(struct i3c_master_controller *master, + const struct i3c_ccc_cmd *cmd); + int (*send_ccc_cmd)(struct i3c_master_controller *master, + struct i3c_ccc_cmd *cmd); + int (*priv_xfers)(struct i3c_dev_desc *dev, + struct i3c_priv_xfer *xfers, + int nxfers); + int (*attach_i2c_dev)(struct i2c_dev_desc *dev); + void (*detach_i2c_dev)(struct i2c_dev_desc *dev); + int (*i2c_xfers)(struct i2c_dev_desc *dev, + const struct i2c_msg *xfers, int nxfers); + int (*request_ibi)(struct i3c_dev_desc *dev, + const struct i3c_ibi_setup *req); + void (*free_ibi)(struct i3c_dev_desc *dev); + int (*enable_ibi)(struct i3c_dev_desc *dev); + int (*disable_ibi)(struct i3c_dev_desc *dev); + void (*recycle_ibi_slot)(struct i3c_dev_desc *dev, + struct i3c_ibi_slot *slot); +}; + +/** + * struct i3c_master_controller - I3C master controller object + * @dev: device to be registered to the device-model + * @this: an I3C device object representing this master. This device will be + * added to the list of I3C devs available on the bus + * @i2c: I2C adapter used for backward compatibility. This adapter is + * registered to the I2C subsystem to be as transparent as possible to + * existing I2C drivers + * @ops: master operations. See &struct i3c_master_controller_ops + * @secondary: true if the master is a secondary master + * @init_done: true when the bus initialization is done + * @boardinfo.i3c: list of I3C boardinfo objects + * @boardinfo.i2c: list of I2C boardinfo objects + * @boardinfo: board-level information attached to devices connected on the bus + * @bus: I3C bus exposed by this master + * @wq: workqueue used to execute IBI handlers. Can also be used by master + * drivers if they need to postpone operations that need to take place + * in a thread context. Typical examples are Hot Join processing which + * requires taking the bus lock in maintenance, which in turn, can only + * be done from a sleep-able context + * + * A &struct i3c_master_controller has to be registered to the I3C subsystem + * through i3c_master_register(). None of &struct i3c_master_controller fields + * should be set manually, just pass appropriate values to + * i3c_master_register(). + */ +struct i3c_master_controller { + struct device dev; + struct i3c_dev_desc *this; + struct i2c_adapter i2c; + const struct i3c_master_controller_ops *ops; + unsigned int secondary : 1; + unsigned int init_done : 1; + struct { + struct list_head i3c; + struct list_head i2c; + } boardinfo; + struct i3c_bus bus; + struct workqueue_struct *wq; +}; + +/** + * i3c_bus_for_each_i2cdev() - iterate over all I2C devices present on the bus + * @bus: the I3C bus + * @dev: an I2C device descriptor pointer updated to point to the current slot + * at each iteration of the loop + * + * Iterate over all I2C devs present on the bus. + */ +#define i3c_bus_for_each_i2cdev(bus, dev) \ + list_for_each_entry(dev, &(bus)->devs.i2c, common.node) + +/** + * i3c_bus_for_each_i3cdev() - iterate over all I3C devices present on the bus + * @bus: the I3C bus + * @dev: and I3C device descriptor pointer updated to point to the current slot + * at each iteration of the loop + * + * Iterate over all I3C devs present on the bus. + */ +#define i3c_bus_for_each_i3cdev(bus, dev) \ + list_for_each_entry(dev, &(bus)->devs.i3c, common.node) + +int i3c_master_do_i2c_xfers(struct i3c_master_controller *master, + const struct i2c_msg *xfers, + int nxfers); + +int i3c_master_disec_locked(struct i3c_master_controller *master, u8 addr, + u8 evts); +int i3c_master_enec_locked(struct i3c_master_controller *master, u8 addr, + u8 evts); +int i3c_master_entdaa_locked(struct i3c_master_controller *master); +int i3c_master_defslvs_locked(struct i3c_master_controller *master); + +int i3c_master_get_free_addr(struct i3c_master_controller *master, + u8 start_addr); + +int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master, + u8 addr); +int i3c_master_do_daa(struct i3c_master_controller *master); + +int i3c_master_set_info(struct i3c_master_controller *master, + const struct i3c_device_info *info); + +int i3c_master_register(struct i3c_master_controller *master, + struct device *parent, + const struct i3c_master_controller_ops *ops, + bool secondary); +int i3c_master_unregister(struct i3c_master_controller *master); + +/** + * i3c_dev_get_master_data() - get master private data attached to an I3C + * device descriptor + * @dev: the I3C device descriptor to get private data from + * + * Return: the private data previously attached with i3c_dev_set_master_data() + * or NULL if no data has been attached to the device. + */ +static inline void *i3c_dev_get_master_data(const struct i3c_dev_desc *dev) +{ + return dev->common.master_priv; +} + +/** + * i3c_dev_set_master_data() - attach master private data to an I3C device + * descriptor + * @dev: the I3C device descriptor to attach private data to + * @data: private data + * + * This functions allows a master controller to attach per-device private data + * which can then be retrieved with i3c_dev_get_master_data(). + */ +static inline void i3c_dev_set_master_data(struct i3c_dev_desc *dev, + void *data) +{ + dev->common.master_priv = data; +} + +/** + * i2c_dev_get_master_data() - get master private data attached to an I2C + * device descriptor + * @dev: the I2C device descriptor to get private data from + * + * Return: the private data previously attached with i2c_dev_set_master_data() + * or NULL if no data has been attached to the device. + */ +static inline void *i2c_dev_get_master_data(const struct i2c_dev_desc *dev) +{ + return dev->common.master_priv; +} + +/** + * i2c_dev_set_master_data() - attach master private data to an I2C device + * descriptor + * @dev: the I2C device descriptor to attach private data to + * @data: private data + * + * This functions allows a master controller to attach per-device private data + * which can then be retrieved with i2c_device_get_master_data(). + */ +static inline void i2c_dev_set_master_data(struct i2c_dev_desc *dev, + void *data) +{ + dev->common.master_priv = data; +} + +/** + * i3c_dev_get_master() - get master used to communicate with a device + * @dev: I3C dev + * + * Return: the master controller driving @dev + */ +static inline struct i3c_master_controller * +i3c_dev_get_master(struct i3c_dev_desc *dev) +{ + return dev->common.master; +} + +/** + * i2c_dev_get_master() - get master used to communicate with a device + * @dev: I2C dev + * + * Return: the master controller driving @dev + */ +static inline struct i3c_master_controller * +i2c_dev_get_master(struct i2c_dev_desc *dev) +{ + return dev->common.master; +} + +/** + * i3c_master_get_bus() - get the bus attached to a master + * @master: master object + * + * Return: the I3C bus @master is connected to + */ +static inline struct i3c_bus * +i3c_master_get_bus(struct i3c_master_controller *master) +{ + return &master->bus; +} + +struct i3c_generic_ibi_pool; + +struct i3c_generic_ibi_pool * +i3c_generic_ibi_alloc_pool(struct i3c_dev_desc *dev, + const struct i3c_ibi_setup *req); +void i3c_generic_ibi_free_pool(struct i3c_generic_ibi_pool *pool); + +struct i3c_ibi_slot * +i3c_generic_ibi_get_free_slot(struct i3c_generic_ibi_pool *pool); +void i3c_generic_ibi_recycle_slot(struct i3c_generic_ibi_pool *pool, + struct i3c_ibi_slot *slot); + +void i3c_master_queue_ibi(struct i3c_dev_desc *dev, struct i3c_ibi_slot *slot); + +struct i3c_ibi_slot *i3c_master_get_free_ibi_slot(struct i3c_dev_desc *dev); + +#endif /* I3C_MASTER_H */ diff --git a/include/linux/irq.h b/include/linux/irq.h index 9504267414a452f6a542c115cdb7ae2a23d720f4..3a1b02271c391a3773342c8071c16212caa3cef3 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -212,6 +212,8 @@ struct irq_data { * required * IRQD_AFFINITY_ON_ACTIVATE - Affinity is set on activation. Don't call * irq_chip::irq_set_affinity() when deactivated. + * IRQD_HANDLE_ENFORCE_IRQCTX - Enforce that handle_irq_*() is only invoked + * from actual interrupt context. */ enum { IRQD_TRIGGER_MASK = 0xf, @@ -235,6 +237,7 @@ enum { IRQD_DEFAULT_TRIGGER_SET = (1 << 25), IRQD_CAN_RESERVE = (1 << 26), IRQD_MSI_NOMASK_QUIRK = (1 << 27), + IRQD_HANDLE_ENFORCE_IRQCTX = (1 << 28), IRQD_AFFINITY_ON_ACTIVATE = (1 << 29), }; @@ -305,6 +308,16 @@ static inline bool irqd_is_single_target(struct irq_data *d) return __irqd_to_state(d) & IRQD_SINGLE_TARGET; } +static inline void irqd_set_handle_enforce_irqctx(struct irq_data *d) +{ + __irqd_to_state(d) |= IRQD_HANDLE_ENFORCE_IRQCTX; +} + +static inline bool irqd_is_handle_enforce_irqctx(struct irq_data *d) +{ + return __irqd_to_state(d) & IRQD_HANDLE_ENFORCE_IRQCTX; +} + static inline bool irqd_is_wakeup_set(struct irq_data *d) { return __irqd_to_state(d) & IRQD_WAKEUP_STATE; diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h index 1d21e98d68549440aaaaff08a0e5919eee9232c1..1f33daa5c674e7bfa81a8288d2bc2da1285d7c3e 100644 --- a/include/linux/irqchip/arm-gic-v3.h +++ b/include/linux/irqchip/arm-gic-v3.h @@ -585,8 +585,10 @@ struct rdists { void __iomem *rd_base; struct page *pend_page; phys_addr_t phys_base; + bool lpi_enabled; } __percpu *rdist; - struct page *prop_page; + phys_addr_t prop_table_pa; + void *prop_table_va; u64 flags; u32 gicd_typer; bool has_vlpis; diff --git a/include/linux/memblock.h b/include/linux/memblock.h index 2acdd046df2d5dda3edd9b30e9e4bd7700f42dca..1b06db7a6096516414ea170450b853226eecc59e 100644 --- a/include/linux/memblock.h +++ b/include/linux/memblock.h @@ -301,6 +301,9 @@ static inline int memblock_get_region_node(const struct memblock_region *r) } #endif /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */ +void *memblock_alloc_exact_nid_raw(phys_addr_t size, phys_addr_t align, + phys_addr_t min_addr, phys_addr_t max_addr, + int nid); phys_addr_t memblock_alloc_nid(phys_addr_t size, phys_addr_t align, int nid); phys_addr_t memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align, int nid); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 610cdf8082f2e60cc507cd1510bdc3a2f2691d2e..7ec902aefb2735dfa4b0ceea2068f9f1e238eaef 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -448,6 +448,23 @@ struct pci_epf_device_id { kernel_ulong_t driver_data; }; +/* i3c */ + +#define I3C_MATCH_DCR 0x1 +#define I3C_MATCH_MANUF 0x2 +#define I3C_MATCH_PART 0x4 +#define I3C_MATCH_EXTRA_INFO 0x8 + +struct i3c_device_id { + __u8 match_flags; + __u8 dcr; + __u16 manuf_id; + __u16 part_id; + __u16 extra_info; + + const void *data; +}; + /* spi */ #define SPI_NAME_SIZE 32 diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index c0dd2f749d3f89d8f0bb257c20291c53b2e0f72f..2019eca4cbdc16ceaf18b98f8a83e41493d6c66b 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -3118,4 +3118,6 @@ #define PCI_VENDOR_ID_NCUBE 0x10ff +#define PCI_VENDOR_ID_PHYTIUM 0x1db7 + #endif /* _LINUX_PCI_IDS_H */ diff --git a/include/linux/phy.h b/include/linux/phy.h old mode 100644 new mode 100755 index 42766e7179d383dfcb6f618760cdb1171c5a36af..34e6d4bbc988206a16bec4d38287f2d12bda71cf --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -88,6 +88,7 @@ typedef enum { PHY_INTERFACE_MODE_XAUI, /* 10GBASE-KR, XFI, SFI - single lane 10G Serdes */ PHY_INTERFACE_MODE_10GKR, + PHY_INTERFACE_MODE_USXGMII, PHY_INTERFACE_MODE_MAX, } phy_interface_t; @@ -160,6 +161,8 @@ static inline const char *phy_modes(phy_interface_t interface) return "xaui"; case PHY_INTERFACE_MODE_10GKR: return "10gbase-kr"; + case PHY_INTERFACE_MODE_USXGMII: + return "usxgmii"; default: return "unknown"; } @@ -405,7 +408,7 @@ struct phy_device { struct phy_driver *drv; u32 phy_id; - + u32 force_mode; struct phy_c45_device_ids c45_ids; unsigned is_c45:1; unsigned is_internal:1; @@ -667,6 +670,10 @@ struct phy_driver { #define PHY_ANY_ID "MATCH ANY PHY" #define PHY_ANY_UID 0xffffffff +#define PHY_ID_MATCH_EXACT(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 0) +#define PHY_ID_MATCH_MODEL(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 4) +#define PHY_ID_MATCH_VENDOR(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 10) + /* A Structure for boards to register fixups with the PHY Lib */ struct phy_fixup { struct list_head list; diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index e3c5d856b6da52f367873271db0e436d5b371284..e1266615b9e9d4b118dd54f7e269d9139830eff2 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -558,6 +558,9 @@ void rproc_shutdown(struct rproc *rproc); void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type); int rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size); +void rproc_handle_ipi(int ipinr); +int rproc_set_handle_irq(void (handle_irq)(void)); + static inline struct rproc_vdev *vdev_to_rvdev(struct virtio_device *vdev) { return container_of(vdev, struct rproc_vdev, vdev); diff --git a/include/linux/tee_drv.h b/include/linux/tee_drv.h index 535f2ecf3c160797bae668dcba59db1a79e54a9c..4a0eecb09cb2d84f289524e08934c2832ad17312 100644 --- a/include/linux/tee_drv.h +++ b/include/linux/tee_drv.h @@ -15,6 +15,7 @@ #ifndef __TEE_DRV_H #define __TEE_DRV_H +#include #include #include #include @@ -166,6 +167,23 @@ int tee_device_register(struct tee_device *teedev); */ void tee_device_unregister(struct tee_device *teedev); +/** + * tee_session_calc_client_uuid() - Calculates client UUID for session + * @uuid: Resulting UUID + * @connection_method: Connection method for session (TEE_IOCTL_LOGIN_*) + * @connectuon_data: Connection data for opening session + * + * Based on connection method calculates UUIDv5 based client UUID. + * + * For group based logins verifies that calling process has specified + * credentials. + * + * @return < 0 on failure + */ +int tee_session_calc_client_uuid(uuid_t *uuid, u32 connection_method, + const u8 connection_data[TEE_IOCTL_UUID_LEN]); + + /** * struct tee_shm - shared memory object * @teedev: device used to allocate the object diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index cd1773d0e08f07247a42788a035a0355da113493..935f53b9b77178f3c04976af9545ae950c2fb2d1 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -354,6 +354,7 @@ struct hdac_bus { bool align_bdle_4k:1; /* BDLE align 4K boundary */ bool reverse_assign:1; /* assign devices in reverse order */ bool corbrp_self_clear:1; /* CORBRP clears itself after reset */ + bool cmd_resend:1; /* command resend */ int bdl_pos_adj; /* BDL position adjustment */ diff --git a/include/sound/hdmi-codec.h b/include/sound/hdmi-codec.h index 9483c55f871b992b78e8059f70017848c3d2ad09..1e3f2d0fe5473c5de5e3a1b48c1004208bdbda08 100644 --- a/include/sound/hdmi-codec.h +++ b/include/sound/hdmi-codec.h @@ -55,6 +55,9 @@ struct hdmi_codec_params { int channels; }; +typedef void (*hdmi_codec_plugged_cb)(struct device *dev, + bool plugged); + struct hdmi_codec_pdata; struct hdmi_codec_ops { /* @@ -96,6 +99,14 @@ struct hdmi_codec_ops { */ int (*get_dai_id)(struct snd_soc_component *comment, struct device_node *endpoint); + + /* + * Hook callback function to handle connector plug event. + * Optional + */ + int (*hook_plugged_cb)(struct device *dev, void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev); }; /* HDMI codec initalization data */ @@ -107,6 +118,12 @@ struct hdmi_codec_pdata { void *data; }; +struct snd_soc_component; +struct snd_soc_jack; + +int hdmi_codec_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack); + #define HDMI_CODEC_DRV_NAME "hdmi-audio-codec" #endif /* __HDMI_CODEC_H__ */ diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index dce5f9dae1210455734e555044749f94d399594e..998ca4e67c0e25c994e0ffdbb18d52cb467df743 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -281,4 +281,7 @@ /* MediaTek BTIF */ #define PORT_MTK_BTIF 117 +/* Phytium PCI UART */ +#define PORT_PHYTIUM 118 + #endif /* _UAPILINUX_SERIAL_CORE_H */ diff --git a/kernel/irq/debugfs.c b/kernel/irq/debugfs.c index b3f55dd581b0a56f86f6056c6d8df7e6b3fe7dcb..9473b1d47199f62ca95a674dcf4660618ee712f9 100644 --- a/kernel/irq/debugfs.c +++ b/kernel/irq/debugfs.c @@ -209,8 +209,7 @@ static ssize_t irq_debug_write(struct file *file, const char __user *user_buf, err = -EINVAL; } else { desc->istate |= IRQS_PENDING; - check_irq_resend(desc); - err = 0; + err = check_irq_resend(desc); } raw_spin_unlock_irqrestore(&desc->lock, flags); diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index 10eccbc84686156dd3ec659015d37768c62f809c..2fed5ab70ba262483e4394ac2b58e3d3dc76858a 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -108,7 +108,7 @@ irqreturn_t handle_irq_event_percpu(struct irq_desc *desc); irqreturn_t handle_irq_event(struct irq_desc *desc); /* Resending of interrupts :*/ -void check_irq_resend(struct irq_desc *desc); +int check_irq_resend(struct irq_desc *desc); bool irq_wait_for_poll(struct irq_desc *desc); void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action); @@ -422,6 +422,10 @@ static inline struct cpumask *irq_desc_get_pending_mask(struct irq_desc *desc) { return desc->pending_mask; } +static inline bool handle_enforce_irqctx(struct irq_data *data) +{ + return irqd_is_handle_enforce_irqctx(data); +} bool irq_fixup_move_pending(struct irq_desc *desc, bool force_clear); #else /* CONFIG_GENERIC_PENDING_IRQ */ static inline bool irq_can_move_pcntxt(struct irq_data *data) @@ -448,6 +452,10 @@ static inline bool irq_fixup_move_pending(struct irq_desc *desc, bool fclear) { return false; } +static inline bool handle_enforce_irqctx(struct irq_data *data) +{ + return false; +} #endif /* !CONFIG_GENERIC_PENDING_IRQ */ #if !defined(CONFIG_IRQ_DOMAIN) || !defined(CONFIG_IRQ_DOMAIN_HIERARCHY) diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c index 3633540b0f16076a03b965b73411b2dc0363c781..e9049903ac114ae38d82bcf6178a3286cea23871 100644 --- a/kernel/irq/irqdesc.c +++ b/kernel/irq/irqdesc.c @@ -635,9 +635,15 @@ void irq_init_desc(unsigned int irq) int generic_handle_irq(unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); + struct irq_data *data; if (!desc) return -EINVAL; + + data = irq_desc_get_irq_data(desc); + if (WARN_ON_ONCE(!in_irq() && handle_enforce_irqctx(data))) + return -EPERM; + generic_handle_irq_desc(desc); return 0; } diff --git a/kernel/irq/resend.c b/kernel/irq/resend.c index 98c04ca5fa43d6a4515fff52a2d956c250777083..7de48bc06c754b960d91c4934d2bd17ac56acca5 100644 --- a/kernel/irq/resend.c +++ b/kernel/irq/resend.c @@ -47,56 +47,88 @@ static void resend_irqs(unsigned long arg) /* Tasklet to handle resend: */ static DECLARE_TASKLET(resend_tasklet, resend_irqs, 0); +static int irq_sw_resend(struct irq_desc *desc) +{ + unsigned int irq = irq_desc_get_irq(desc); + + /* + * Validate whether this interrupt can be safely injected from + * non interrupt context + */ + if (handle_enforce_irqctx(&desc->irq_data)) + return -EINVAL; + + /* + * If the interrupt is running in the thread context of the parent + * irq we need to be careful, because we cannot trigger it + * directly. + */ + if (irq_settings_is_nested_thread(desc)) { + /* + * If the parent_irq is valid, we retrigger the parent, + * otherwise we do nothing. + */ + if (!desc->parent_irq) + return -EINVAL; + irq = desc->parent_irq; + } + + /* Set it pending and activate the softirq: */ + set_bit(irq, irqs_resend); + tasklet_schedule(&resend_tasklet); + return 0; +} + +#else +static int irq_sw_resend(struct irq_desc *desc) +{ + return -EINVAL; +} +#endif + +static int try_retrigger(struct irq_desc *desc) +{ + if (desc->irq_data.chip->irq_retrigger) + return desc->irq_data.chip->irq_retrigger(&desc->irq_data); + +#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY + return irq_chip_retrigger_hierarchy(&desc->irq_data); +#else + return 0; #endif +} /* * IRQ resend * * Is called with interrupts disabled and desc->lock held. */ -void check_irq_resend(struct irq_desc *desc) +int check_irq_resend(struct irq_desc *desc) { + int err = 0; + /* - * We do not resend level type interrupts. Level type - * interrupts are resent by hardware when they are still - * active. Clear the pending bit so suspend/resume does not - * get confused. + * We do not resend level type interrupts. Level type interrupts + * are resent by hardware when they are still active. Clear the + * pending bit so suspend/resume does not get confused. */ if (irq_settings_is_level(desc)) { desc->istate &= ~IRQS_PENDING; - return; + return -EINVAL; } if (desc->istate & IRQS_REPLAY) - return; - if (desc->istate & IRQS_PENDING) { - desc->istate &= ~IRQS_PENDING; - desc->istate |= IRQS_REPLAY; + return -EBUSY; - if (!desc->irq_data.chip->irq_retrigger || - !desc->irq_data.chip->irq_retrigger(&desc->irq_data)) { -#ifdef CONFIG_HARDIRQS_SW_RESEND - unsigned int irq = irq_desc_get_irq(desc); - - /* - * If the interrupt is running in the thread - * context of the parent irq we need to be - * careful, because we cannot trigger it - * directly. - */ - if (irq_settings_is_nested_thread(desc)) { - /* - * If the parent_irq is valid, we - * retrigger the parent, otherwise we - * do nothing. - */ - if (!desc->parent_irq) - return; - irq = desc->parent_irq; - } - /* Set it pending and activate the softirq: */ - set_bit(irq, irqs_resend); - tasklet_schedule(&resend_tasklet); -#endif - } - } + if (!(desc->istate & IRQS_PENDING)) + return 0; + + desc->istate &= ~IRQS_PENDING; + + if (!try_retrigger(desc)) + err = irq_sw_resend(desc); + + /* If the retrigger was successfull, mark it with the REPLAY bit */ + if (!err) + desc->istate |= IRQS_REPLAY; + return err; } diff --git a/lib/crc32.c b/lib/crc32.c index 1a5d08470044ea53f5469a0d4011e1f8a69b43ea..bf60ef26a45c241f6ff33e35e0281651e11bfc3d 100644 --- a/lib/crc32.c +++ b/lib/crc32.c @@ -183,21 +183,21 @@ static inline u32 __pure crc32_le_generic(u32 crc, unsigned char const *p, } #if CRC_LE_BITS == 1 -u32 __pure crc32_le(u32 crc, unsigned char const *p, size_t len) +u32 __pure __weak crc32_le(u32 crc, unsigned char const *p, size_t len) { return crc32_le_generic(crc, p, len, NULL, CRC32_POLY_LE); } -u32 __pure __crc32c_le(u32 crc, unsigned char const *p, size_t len) +u32 __pure __weak __crc32c_le(u32 crc, unsigned char const *p, size_t len) { return crc32_le_generic(crc, p, len, NULL, CRC32C_POLY_LE); } #else -u32 __pure crc32_le(u32 crc, unsigned char const *p, size_t len) +u32 __pure __weak crc32_le(u32 crc, unsigned char const *p, size_t len) { return crc32_le_generic(crc, p, len, (const u32 (*)[256])crc32table_le, CRC32_POLY_LE); } -u32 __pure __crc32c_le(u32 crc, unsigned char const *p, size_t len) +u32 __pure __weak __crc32c_le(u32 crc, unsigned char const *p, size_t len) { return crc32_le_generic(crc, p, len, (const u32 (*)[256])crc32ctable_le, CRC32C_POLY_LE); @@ -206,6 +206,9 @@ u32 __pure __crc32c_le(u32 crc, unsigned char const *p, size_t len) EXPORT_SYMBOL(crc32_le); EXPORT_SYMBOL(__crc32c_le); +u32 __pure crc32_le_base(u32, unsigned char const *, size_t) __alias(crc32_le); +u32 __pure __crc32c_le_base(u32, unsigned char const *, size_t) __alias(__crc32c_le); + /* * This multiplies the polynomials x and y modulo the given modulus. * This follows the "little-endian" CRC convention that the lsbit diff --git a/mm/memblock.c b/mm/memblock.c index 4d471da3cc4794a06024330848fafc0b1d5b36f8..6e568b32638fc7e1a2220c79393ccd58246a646b 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -1318,7 +1318,7 @@ phys_addr_t __init memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align, i static void * __init memblock_virt_alloc_internal( phys_addr_t size, phys_addr_t align, phys_addr_t min_addr, phys_addr_t max_addr, - int nid) + int nid, bool exact_nid) { phys_addr_t alloc; void *ptr; @@ -1346,7 +1346,7 @@ static void * __init memblock_virt_alloc_internal( if (alloc && !memblock_reserve(alloc, size)) goto done; - if (nid != NUMA_NO_NODE) { + if (nid != NUMA_NO_NODE && !exact_nid) { alloc = memblock_find_in_range_node(size, align, min_addr, max_addr, NUMA_NO_NODE, flags); @@ -1412,7 +1412,7 @@ void * __init memblock_virt_alloc_try_nid_raw( &max_addr, (void *)_RET_IP_); ptr = memblock_virt_alloc_internal(size, align, - min_addr, max_addr, nid); + min_addr, max_addr, nid, false); #ifdef CONFIG_DEBUG_VM if (ptr && size > 0) memset(ptr, PAGE_POISON_PATTERN, size); @@ -1420,6 +1420,43 @@ void * __init memblock_virt_alloc_try_nid_raw( return ptr; } +/** + * memblock_alloc_exact_nid_raw - allocate boot memory block on the exact node + * without zeroing memory + * @size: size of memory block to be allocated in bytes + * @align: alignment of the region and block's size + * @min_addr: the lower bound of the memory region from where the allocation + * is preferred (phys address) + * @max_addr: the upper bound of the memory region from where the allocation + * is preferred (phys address), or %MEMBLOCK_ALLOC_ACCESSIBLE to + * allocate only from memory limited by memblock.current_limit value + * @nid: nid of the free area to find, %NUMA_NO_NODE for any node + * + * Public function, provides additional debug information (including caller + * info), if enabled. Does not zero allocated memory. + * + * Return: + * Virtual address of allocated memory block on success, NULL on failure. + */ +void * __init memblock_alloc_exact_nid_raw( + phys_addr_t size, phys_addr_t align, + phys_addr_t min_addr, phys_addr_t max_addr, + int nid) +{ + void *ptr; + + memblock_dbg("%s: %llu bytes align=0x%llx nid=%d from=%pa max_addr=%pa %pS\n", + __func__, (u64)size, (u64)align, nid, &min_addr, + &max_addr, (void *)_RET_IP_); + + ptr = memblock_virt_alloc_internal(size, align, + min_addr, max_addr, nid, true); + if (ptr && size > 0) + memset(ptr, PAGE_POISON_PATTERN, size); + + return ptr; +} + /** * memblock_virt_alloc_try_nid_nopanic - allocate boot memory block * @size: size of memory block to be allocated in bytes @@ -1449,7 +1486,7 @@ void * __init memblock_virt_alloc_try_nid_nopanic( &max_addr, (void *)_RET_IP_); ptr = memblock_virt_alloc_internal(size, align, - min_addr, max_addr, nid); + min_addr, max_addr, nid, false); if (ptr) memset(ptr, 0, size); return ptr; @@ -1484,7 +1521,7 @@ void * __init memblock_virt_alloc_try_nid( __func__, (u64)size, (u64)align, nid, &min_addr, &max_addr, (void *)_RET_IP_); ptr = memblock_virt_alloc_internal(size, align, - min_addr, max_addr, nid); + min_addr, max_addr, nid, false); if (ptr) { memset(ptr, 0, size); return ptr; diff --git a/mm/sparse.c b/mm/sparse.c index ed60f0a375fec9f00d325344df173e67cceacd75..a0e950e1d584839f277b97739c466c9ee045e5ad 100644 --- a/mm/sparse.c +++ b/mm/sparse.c @@ -17,6 +17,8 @@ #include #include +#include + /* * Permanent SPARSEMEM data: * @@ -405,7 +407,7 @@ static void __init sparse_buffer_init(unsigned long size, int nid) { WARN_ON(sparsemap_buf); /* forgot to call sparse_buffer_fini()? */ sparsemap_buf = - memblock_virt_alloc_try_nid_raw(size, PAGE_SIZE, + memblock_alloc_exact_nid_raw(size, section_map_size(), __pa(MAX_DMA_ADDRESS), BOOTMEM_ALLOC_ACCESSIBLE, nid); sparsemap_buf_end = sparsemap_buf + size; diff --git a/scripts/dtc/checks.c b/scripts/dtc/checks.c index a2cc1036c9155e86d65d9a8b8de41b00fb061c13..a771a645f449a22f68598e5a3350e12ed1644ce5 100644 --- a/scripts/dtc/checks.c +++ b/scripts/dtc/checks.c @@ -792,7 +792,7 @@ static void check_pci_bridge(struct check *c, struct dt_info *dti, struct node * if (!strprefixeq(node->name, node->basenamelen, "pci") && !strprefixeq(node->name, node->basenamelen, "pcie")) - FAIL(c, dti, node, "node name is not \"pci\" or \"pcie\""); + ;//FAIL(c, dti, node, "node name is not \"pci\" or \"pcie\""); prop = get_property(node, "ranges"); if (!prop) diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c index a65e8c0c630d286e1e8ca6cdb639a52131163685..1cea7ba309544c36328223a92374401d4387bbca 100644 --- a/sound/hda/hdac_controller.c +++ b/sound/hda/hdac_controller.c @@ -139,6 +139,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); @@ -165,6 +168,41 @@ 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 eee422390d8e260fae1e784262598133254f023c..38586457ee09ba6c0b78c05ac68af1f78f050dde 100644 --- a/sound/hda/hdac_stream.c +++ b/sound/hda/hdac_stream.c @@ -51,7 +51,11 @@ void snd_hdac_stream_start(struct hdac_stream *azx_dev, bool fresh_start) 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 if (!fresh_start) azx_dev->start_wallclk -= azx_dev->period_wallclk; @@ -469,7 +473,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 4235907b785891326a479023f3433d2afd9b231e..df95dad1233fa0a4a42febd7d8d035b12abfc4f4 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -21,6 +21,21 @@ 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 + 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 b57432f000568eb1b40f5131464218ba1f07bcff..90e32c8ce07be3cc287fa6ea306a9eaeed8e615c 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 snd-hda-intel-objs := hda_intel.o +snd-hda-phytium-objs := hda_phytium.o snd-hda-tegra-objs := hda_tegra.o snd-hda-codec-y := hda_bind.o hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o @@ -48,3 +49,4 @@ obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.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 0c5d41e5d14684e4c8da10f078a5e04a3a895297..03ab50fd3482470b0eadd5c1e5be583e288c4abe 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -28,6 +28,8 @@ #include #include +#include "hda_phytium.h" + #ifdef CONFIG_X86 /* for art-tsc conversion */ #include @@ -171,6 +173,10 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) snd_hda_spdif_out_of_nid(apcm->codec, hinfo->nid); unsigned short ctls = spdif ? spdif->ctls : 0; + struct hda_ft *hda; + hda = container_of(chip, struct hda_ft, chip); + hda->substream = substream; + trace_azx_pcm_prepare(chip, azx_dev); dsp_lock(azx_dev); if (dsp_is_locked(azx_dev)) { diff --git a/sound/pci/hda/hda_phytium.c b/sound/pci/hda/hda_phytium.c new file mode 100644 index 0000000000000000000000000000000000000000..5d3fa387b1f5946e7ddc18a520b0f5d090ff15fb --- /dev/null +++ b/sound/pci/hda/hda_phytium.c @@ -0,0 +1,1200 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Implementation of primary ALSA driver code base for Phytium HD Audio. + * + * Copyright (c) 2018-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 "hda_codec.h" + +#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; + msleep(1); + } +} + +/* 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; + struct resource *res; + resource_size_t size; + + 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); + + if (chip->region_requested){ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + size = resource_size(res); + devm_release_mem_region(hddev, res->start, size); + } + + 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 hdac_io_ops axi_hda_io_ops; +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; + chip->jackpoll_ms = jackpoll_ms; + 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], &axi_hda_io_ops); + if (err < 0) { + return err; + } + + 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; + + 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); + chip->region_requested = 1; + + 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; + } + + /* 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; + } + + strcpy(card->driver, "ft-hda"); + strcpy(card->shortname, "ft-hda"); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx irq %i", + card->shortname, bus->addr, bus->irq); + + return 0; +} + +/* + * HDA controller ops. + */ + +/* APB register access. */ +static void axi_azx_writel(u32 value, u32 __iomem *addr) +{ + writel(value, addr); +} + +static u32 axi_azx_readl(u32 __iomem *addr) +{ + return readl(addr); +} + +static void axi_azx_writew(u16 value, u16 __iomem *addr) +{ + u32 data; + u32 offset; + + offset = (u64)addr & 0x03; + addr = (u16 __iomem *)((u64)addr & 0xFFFFFFFFFFFFFFFC); + data = readl(addr); + data &= ~(0xFFFF << offset * BYTE_BIT_WIDTH); + data |= (value << offset * BYTE_BIT_WIDTH); + writel(data, addr); +} + +static u16 axi_azx_readw(u16 __iomem *addr) +{ + return readw(addr); +} + +static void axi_azx_writeb(u8 value, u8 __iomem *addr) +{ + u32 data; + u32 offset; + + offset = (u64)addr & 0x03; + addr = (u8 __iomem *)((u64)addr & 0xFFFFFFFFFFFFFFFC); + data = readl(addr); + data &= ~(0xFF << offset * BYTE_BIT_WIDTH); + data |= (value << offset * BYTE_BIT_WIDTH); + writel(data, addr); +} + +static u8 axi_azx_readb(u8 __iomem *addr) +{ + return readb(addr); +} + +/* DMA page allocation helpers. */ +static int dma_alloc_pages(struct hdac_bus *bus, + int type, + size_t size, + struct snd_dma_buffer *buf) +{ + struct azx *chip = bus_to_azx(bus); + int err; + + err = snd_dma_alloc_pages(type, + bus->dev, + size, buf); + if (err < 0) + return err; + mark_pages_wc(chip, buf, true); + return 0; +} + +static void dma_free_pages(struct hdac_bus *bus, struct snd_dma_buffer *buf) +{ + struct azx *chip = bus_to_azx(bus); + + mark_pages_wc(chip, buf, false); + snd_dma_free_pages(buf); +} + +static int substream_alloc_pages(struct azx *chip, + struct snd_pcm_substream *substream, + size_t size) +{ + struct azx_dev *azx_dev = get_azx_dev(substream); + int ret; + + mark_runtime_wc(chip, azx_dev, substream, false); + ret = snd_pcm_lib_malloc_pages(substream, size); + if (ret < 0) + return ret; + + mark_runtime_wc(chip, azx_dev, substream, true); + return 0; +} + +static int substream_free_pages(struct azx *chip, + struct snd_pcm_substream *substream) +{ + struct azx_dev *azx_dev = get_azx_dev(substream); + mark_runtime_wc(chip, azx_dev, substream, false); + return snd_pcm_lib_free_pages(substream); +} + +static void pcm_mmap_prepare(struct snd_pcm_substream *substream, + struct vm_area_struct *area) +{ + +} + +static const struct hdac_io_ops axi_hda_io_ops = { + .reg_writel = axi_azx_writel, + .reg_readl = axi_azx_readl, + .reg_writew = axi_azx_writew, + .reg_readw = axi_azx_readw, + .reg_writeb = axi_azx_writeb, + .reg_readb = axi_azx_readb, + .dma_alloc_pages = dma_alloc_pages, + .dma_free_pages = dma_free_pages, +}; + +static const struct hda_controller_ops axi_hda_ops = { + .substream_alloc_pages = substream_alloc_pages, + .substream_free_pages = substream_free_pages, + .pcm_mmap_prepare = pcm_mmap_prepare, + .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_DCAPS_SYNC_WRITE | 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 v2"); diff --git a/sound/pci/hda/hda_phytium.h b/sound/pci/hda/hda_phytium.h new file mode 100644 index 0000000000000000000000000000000000000000..edca12ec6fa7e36baed78f455367e73d1bfaa497 --- /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-2023 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/soc/Kconfig b/sound/soc/Kconfig index 1cf11cf51e1dd6912c3bdce14c9e651b4fd955c2..32296faf39c5eedb75977c49aab5f823e7af1205 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -59,6 +59,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 62a5f87c3cfc435b4ff8b1984f77ec65b3e53578..2cdfbe1db8c292bbc805f82e5bed4bc18827a115 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_SND_SOC) += mxs/ obj-$(CONFIG_SND_SOC) += nuc900/ obj-$(CONFIG_SND_SOC) += omap/ 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 efb095dbcd7145a5267bb7bda73552be43324095..75e8f6389967c93fc1e1790d6373ac7e08f86e5b 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -606,6 +606,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 7ae7c85e8219f5ff035dacaea3abc4b831296056..1ba362d2dd65119e274d1eae048f4a2aa7187bc1 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -76,6 +76,8 @@ snd-soc-es8316-objs := es8316.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-ics43432-objs := ics43432.o @@ -336,6 +338,8 @@ obj-$(CONFIG_SND_SOC_ES8316) += snd-soc-es8316.o obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o +obj-$(CONFIG_SND_SOC_ES8336)+= snd-soc-es8336.o +obj-$(CONFIG_SND_SOC_ES8388) += snd-soc-es8388.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o diff --git a/sound/soc/codecs/es8336.c b/sound/soc/codecs/es8336.c new file mode 100644 index 0000000000000000000000000000000000000000..ebb395600f5b2053dff384fa12cea9b52d4a9028 --- /dev/null +++ b/sound/soc/codecs/es8336.c @@ -0,0 +1,1072 @@ +// 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" + +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; + struct delayed_work work; + + struct gpio_desc *spk_ctl_gpio; + struct gpio_desc *hp_det_gpio; + bool muted; + bool hp_inserted; + + u8 mic_src; + 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) +{ + gpiod_set_value(es8336->spk_ctl_gpio, enable); +} + +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 15db Boost", + "lin2-rin2 with 15db 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", 1, + 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)), + SND_SOC_DAPM_MIXER("Left Hp mixer", SND_SOC_NOPM, + 4, 1, &es8336_out_left_mix[0], + ARRAY_SIZE(es8336_out_left_mix)), + SND_SOC_DAPM_MIXER("Right Hp mixer", SND_SOC_NOPM, + 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 15db Boost", "MIC1"}, + {"Differential Mux", "lin2-rin2 with 15db 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_read32(component, ES8336_IFACE); + adciface = snd_soc_component_read32(component, ES8336_ADC_IFACE); + daciface = snd_soc_component_read32(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_ADC_PDN_LINSEL_REG22, 0xC0, 0x00); + 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_ADC_PDN_LINSEL_REG22, 0xC0, 0xc0); + 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) +{ + struct snd_soc_component *component = dai->component; + struct es8336_priv *es8336 = snd_soc_component_get_drvdata(component); + + es8336->muted = mute; + if (mute) { + es8336_enable_spk(es8336, false); + msleep(100); + snd_soc_component_write(component, ES8336_DAC_SET1_REG30, 0x20); + } else if (dai->playback_active) { + snd_soc_component_write(component, ES8336_DAC_SET1_REG30, 0x00); + msleep(130); + if (!es8336->hp_inserted) + es8336_enable_spk(es8336, true); + } + return 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); + snd_soc_component_update_bits(component, ES8336_ADC_PDN_LINSEL_REG22, 0xC0, 0xC0); + 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, + .digital_mute = es8336_mute, + .shutdown = es8336_pcm_shutdown, +}; + +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_rates = 1, +}; + +static int es8336_init_regs(struct snd_soc_component *component) +{ + struct es8336_priv *es8336 = snd_soc_component_get_drvdata(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, es8336->mic_src); + 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_read32(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_update_bits(component, ES8336_ADC_PDN_LINSEL_REG22, 0xC0, 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; + + es8336 = container_of(work, struct es8336_priv, work.work); + + es8336->hp_inserted = gpiod_get_value(es8336->hp_det_gpio); + 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_read32(component, ES8336_CLKMGR_ADCDIV2_REG05); + if (!ret) { + es8336_reset(component); /* UPDATED BY DAVID,15-3-5 */ + ret = snd_soc_component_read32(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_update_bits(component, ES8336_ADC_PDN_LINSEL_REG22, 0xC0, 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, + const struct i2c_device_id *id) +{ + struct es8336_priv *es8336; + int ret = -1; + int hp_irq; + + es8336 = devm_kzalloc(&i2c->dev, sizeof(*es8336), GFP_KERNEL); + if (!es8336) + return -ENOMEM; + + es8336->debounce_time = 200; + 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); + + es8336->spk_ctl_gpio = devm_gpiod_get_index_optional(&i2c->dev, "sel", 0, + GPIOD_OUT_HIGH); + ret = of_property_read_u8(i2c->dev.of_node, "mic-src", &es8336->mic_src); + if (ret != 0) { + dev_dbg(&i2c->dev, "mic1-src return %d", ret); + es8336->mic_src = 0x20; + } + dev_dbg(&i2c->dev, "mic1-src %x", es8336->mic_src); + + if (!es8336->spk_ctl_gpio) + dev_info(&i2c->dev, "Can not get spk_ctl_gpio\n"); + else + es8336_enable_spk(es8336, false); + + es8336->hp_det_gpio = devm_gpiod_get_index_optional(&i2c->dev, "det", 0, + GPIOD_IN); + + if (!es8336->hp_det_gpio) { + dev_info(&i2c->dev, "Can not get hp_det_gpio\n"); + } else { + INIT_DELAYED_WORK(&es8336->work, hp_work); + hp_irq = gpiod_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 = snd_soc_register_component(&i2c->dev, + &soc_component_dev_es8336, + &es8336_dai, 1); + + return ret; +} + +static int es8336_i2c_remove(struct i2c_client *client) +{ + kfree(i2c_get_clientdata(client)); + return 0; +} + +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..d9eda8edaf1193ff1d849894907563e832db7620 --- /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..86d1120d3bb85f1b6f4d02e88bc5bd844afc5bd4 --- /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 (c) 2021-2023 Phytium Technology Co., Ltd. + * Author: Yiqun Zhang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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) +{ + 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; + /* fallthru */ + case 11289600: + es8388->sysclk_constraints = &constraints_11289; + es8388->mclk_ratios = ratios_11289; + break; + case 24576000: + mclkdiv2 = 1; + /* fallthru */ + 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, + .digital_mute = es8388_mute, + .set_sysclk = es8388_set_sysclk, + .set_fmt = es8388_set_dai_fmt, +}; + +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_rates = 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_rw = 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, + .non_legacy_dai_naming = 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, + const struct i2c_device_id *id) +{ + 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/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index 7994e8ddc7d217d2a7c23eafd23f9f6880f0daf9..44914e7986f553edf5cb91de9c320c3d892588c7 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -285,6 +286,8 @@ struct hdmi_codec_priv { uint8_t eld[MAX_ELD_BYTES]; struct snd_pcm_chmap *chmap_info; unsigned int chmap_idx; + struct snd_soc_jack *jack; + unsigned int jack_status; }; static const struct snd_soc_dapm_widget hdmi_widgets[] = { @@ -700,6 +703,44 @@ static int hdmi_dai_probe(struct snd_soc_dai *dai) return snd_soc_dapm_add_routes(dapm, &route, 1); } +static void hdmi_codec_jack_report(struct hdmi_codec_priv *hcp, + unsigned int jack_status) +{ + if (hcp->jack && jack_status != hcp->jack_status) { + snd_soc_jack_report(hcp->jack, jack_status, SND_JACK_LINEOUT); + hcp->jack_status = jack_status; + } +} + +static void plugged_cb(struct device *dev, bool plugged) +{ + struct hdmi_codec_priv *hcp = dev_get_drvdata(dev); + + if (plugged) + hdmi_codec_jack_report(hcp, SND_JACK_LINEOUT); + else + hdmi_codec_jack_report(hcp, 0); +} + +static int hdmi_codec_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, + void *data) +{ + struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component); + int ret = -EOPNOTSUPP; + + if (hcp->hcd.ops->hook_plugged_cb) { + hcp->jack = jack; + ret = hcp->hcd.ops->hook_plugged_cb(component->dev->parent, + hcp->hcd.data, + plugged_cb, + component->dev); + if (ret) + hcp->jack = NULL; + } + return ret; +} + static const struct snd_soc_dai_driver hdmi_i2s_dai = { .name = "i2s-hifi", .id = DAI_ID_I2S, @@ -751,6 +792,7 @@ static const struct snd_soc_component_driver hdmi_driver = { .use_pmdown_time = 1, .endianness = 1, .non_legacy_dai_naming = 1, + .set_jack = hdmi_codec_set_jack, }; static int hdmi_codec_probe(struct platform_device *pdev) diff --git a/sound/soc/phytium/Kconfig b/sound/soc/phytium/Kconfig new file mode 100755 index 0000000000000000000000000000000000000000..bce2cd9d94a2f1332daddb0e1ce1917e9defbba9 --- /dev/null +++ b/sound/soc/phytium/Kconfig @@ -0,0 +1,30 @@ +config SND_SOC_PHYTIUM_I2S + tristate "Phytium I2S Device Driver" + 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 on Px210. diff --git a/sound/soc/phytium/Makefile b/sound/soc/phytium/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..db3c0659e8442884fddeb23499deb0bbea95564c --- /dev/null +++ b/sound/soc/phytium/Makefile @@ -0,0 +1,13 @@ +# 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..9a9e5a1e1e1ca9d23d5e1050f127e4fd600c878e --- /dev/null +++ b/sound/soc/phytium/local.h @@ -0,0 +1,328 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020-2023 Phytium Technology Co., Ltd. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef __PHYTIUM_I2S_LOCAL_H +#define __PHYTIUM_I2S_LOCAL_H + +#include +#include +#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 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; + + 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 100755 index 0000000000000000000000000000000000000000..284b130540ffab213f3d1bdbd9eefbe26cfa69d3 --- /dev/null +++ b/sound/soc/phytium/phytium_i2s.c @@ -0,0 +1,1415 @@ +// 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); + } + 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_CBM_CFM: + if (dev->capability & PHYTIUM_I2S_SLAVE) + ret = 0; + else + ret = -EINVAL; + break; + case SND_SOC_DAIFMT_CBS_CFS: + 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_dai *dai) +{ + return 0; +} + +static int phytium_i2s_resume(struct snd_soc_dai *dai) +{ + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(dai); + if (dai->playback_active) + phytium_i2s_config(dev, SNDRV_PCM_STREAM_PLAYBACK); + if (dai->capture_active) + phytium_i2s_config(dev, SNDRV_PCM_STREAM_CAPTURE); + 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, + .suspend = phytium_i2s_suspend, + .resume = phytium_i2s_resume, + .symmetric_rates = 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_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + 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_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + 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_pcm_runtime *rtd) +{ + struct i2s_phytium *i2s = snd_soc_dai_get_drvdata(rtd->cpu_dai); + size_t size = phytium_pcm_hardware.buffer_bytes_max; + + return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, + SNDRV_DMA_TYPE_DEV, + i2s->pdev, size, size); +} + +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_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + 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; + + 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 || + format_val != azx_dev->format_val || + runtime->no_period_wakeup != azx_dev->no_period_wakeup) { + + azx_dev->bufsize = bufsize; + azx_dev->period_bytes = period_bytes; + azx_dev->format_val = format_val; + 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, 0x8180); + 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(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), 0x1c8); + else + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DEV_ADDR(0), paddr + 0x1c8); + 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), 0x2);//0x2 + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DLENTH(0), 0x0);//0x0 + } else { + 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), 0x1c0); + else + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DEV_ADDR(1), paddr + 0x1c0); + 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), 0x8);//0x8 + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_DLENTH(1), 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_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + 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(0), 0x0); + else + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(1), 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(0), 0x1); + else + i2s_write_reg(azx_dev->sd_addr, DMA_CHALX_CTL(1), 0x5); + + azx_dev->running = true; +} + +static int phytium_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + 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; +} + +static void phytium_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +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(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); + } else { + 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); + } + } +} + +static int phytium_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + 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_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct i2s_phytium *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + int stream = substream->stream; + + u32 pos = i2s_read_reg(dev->regs_db, DMA_LPIB(stream)); + + return bytes_to_frames(substream->runtime, pos); +} + +static const struct snd_pcm_ops phytium_pcm_ops = { + .open = phytium_pcm_open, + .close = phytium_pcm_close, + .hw_params = phytium_pcm_hw_params, + .prepare = phytium_pcm_prepare, + .hw_free = phytium_pcm_hw_free, + .trigger = phytium_pcm_trigger, + .pointer = phytium_pcm_pointer, +}; + +static const struct snd_soc_component_driver phytium_i2s_component = { + .name = "phytium-i2s", + .pcm_new = phytium_pcm_new, + .pcm_free = phytium_pcm_free, + .ops = &phytium_pcm_ops, +}; + +/* 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 |= DWC_I2S_PLAY; + } + if (COMP1_RX_ENABLED(comp1)) { + idx2 = COMP2_RX_WORDSIZE_0(comp2); + dev->capability |= DWC_I2S_RECORD; + } + + return 0; +} + +static int phytium_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 << idx; + else + azx_dev->sd_int_sta_mask = 1 << 8; + + 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_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_dma_alloc_pages, + .dma_free_pages = phytium_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; + + 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 { + device_property_read_string(&pdev->dev, "dai-name", &dai_drv->name); + if (ret < 0) { + dev_err(&pdev->dev, "missing dai-name property\n"); + goto failed_get_dai_name; + } + i2s->pdev = &pdev->dev; + if ((&pdev->dev)->of_node) { + clk = devm_clk_get(&pdev->dev, NULL); + i2s->clk_base = clk_get_rate(clk); + } + else if (has_acpi_companion(&pdev->dev)) { + ret = device_property_read_u32(&pdev->dev, "i2s_clk", &i2s->clk_base); + if (ret < 0) { + dev_info(&pdev->dev, "missing i2s_clk property from acpi, use default value\n"); + i2s->clk_base = 600000000; + } + } + } + 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}, + { } +}; +MODULE_DEVICE_TABLE(acpi, phytium_i2s_acpi_match); +#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 100755 index 0000000000000000000000000000000000000000..581907f0d8455ecb36eda605c23070f2bf021d6b --- /dev/null +++ b/sound/soc/phytium/pmdk_dp.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021-2023 Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include + +#define DAI_CNT(pmdk_dai_) sizeof(pmdk_dai_)/sizeof(struct snd_soc_dai_link) + +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 = "HDMI/DP,pcm=0", + .mask = SND_JACK_LINEOUT, + }, +}; + +static struct snd_soc_jack_pin dp1_pins[] = { + { + .pin = "HDMI/DP,pcm=1", + .mask = SND_JACK_LINEOUT, + }, +}; + +static struct snd_soc_jack_pin dp2_pins[] = { + { + .pin = "HDMI/DP,pcm=2", + .mask = SND_JACK_LINEOUT, + }, +}; + +#define SMDK_DAI_FMT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBS_CFS) + +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 = runtime->codec_dai->component; + int ret; + + ret = snd_soc_card_jack_new(card, "HDMI/DP,pcm=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 = runtime->codec_dai->component; + int ret; + + ret = snd_soc_card_jack_new(card, "HDMI/DP,pcm=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 = runtime->codec_dai->component; + int ret; + + ret = snd_soc_card_jack_new(card, "HDMI/DP,pcm=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; +} + +static struct snd_soc_dai_link pmdk_dai_local[] = { +{ + .name = "Phytium dp0-audio", + .stream_name = "Playback", + .cpu_dai_name = "phytium-i2s-dp0", + .codec_dai_name = "i2s-hifi", + .platform_name = "snd-soc-dummy", + .codec_name = "hdmi-audio-codec.0", + .dai_fmt = SMDK_DAI_FMT, + .init = pmdk_dp0_init, +},{ + .name = "Phytium dp1-audio", + .stream_name = "Playback", + .cpu_dai_name = "phytium-i2s-dp1", + .codec_dai_name = "i2s-hifi", + .platform_name = "snd-soc-dummy", + .codec_name = "hdmi-audio-codec.1", + .dai_fmt = SMDK_DAI_FMT, + .init = pmdk_dp1_init, +}, +{ + .name = "Phytium dp2-audio", + .stream_name = "Playback", + .cpu_dai_name = "phytium-i2s-dp2", + .codec_dai_name = "i2s-hifi", + .platform_name = "snd-soc-dummy", + .codec_name = "hdmi-audio-codec.2", + .dai_fmt = SMDK_DAI_FMT, + .init = pmdk_dp2_init, +}, +}; + +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; + char dp_mask; + int i,j = 0; + card->dev = &pdev->dev; + + device_property_read_u32(&pdev->dev, "num-dp", &num_dp); + device_property_read_u8(&pdev->dev, "dp-mask", &dp_mask); + pmdk_dai = devm_kzalloc(&pdev->dev, num_dp * sizeof(*pmdk_dai), GFP_KERNEL); + if (!pmdk_dai) + return -ENOMEM; + + if (!num_dp || num_dp > DAI_CNT(pmdk_dai_local)) + return -EINVAL; + + for(i = 0; i < num_dp; i++) { + for (; j < DAI_CNT(pmdk_dai_local); j++) { + if (BIT(j) & dp_mask) { + pmdk_dai[i] = pmdk_dai_local[j]; + j++; + break; + } + } + } + + 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..38919f40694c7f00fe27761b562d441df7da3ce5 --- /dev/null +++ b/sound/soc/phytium/pmdk_es8336.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021-2023 Phytium Technology 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) + +static struct snd_soc_dai_link pmdk_dai[] = { + { + .name = "ES8336 HIFI", + .stream_name = "ES8336 HIFI", + .cpu_dai_name = "phytium-i2s-lsd", + .codec_dai_name = "es8336-hifi", + .platform_name = "snd-soc-dummy", + .codec_name = "i2c-ESSX8336:00", + .dai_fmt = PMDK_DAI_FMT, + }, +}; + +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..0a1bd3b52d3b6f72719423ff7a23cb652911cb8a --- /dev/null +++ b/sound/soc/phytium/pmdk_es8388.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021-2023 Phytium Technology 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(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) + +static struct snd_soc_dai_link pmdk_dai[] = { + { + .name = "ES8388 HIFI", + .stream_name = "ES8388 HIFI", + .cpu_dai_name = "phytium-i2s-lsd", + .codec_dai_name = "es8388-hifi", + .platform_name = "snd-soc-dummy", + .codec_name = "i2c-ESSX8388:00", + .dai_fmt = PMDK_DAI_FMT, + .init = pmdk_es8388_init, + }, +}; + +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");