diff --git a/Documentation/devicetree/bindings/arm/analog/adi,sc5xx.yaml b/Documentation/devicetree/bindings/arm/analog/adi,sc5xx.yaml new file mode 100644 index 00000000000000..fc37242b32b9ca --- /dev/null +++ b/Documentation/devicetree/bindings/arm/analog/adi,sc5xx.yaml @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/arm/analog/adi,sc5xx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices SC59X 64-Bit ARM-based Processor Families + +maintainers: + - Arturs Artamonovs + - Utsav Agarwal + +properties: + $nodename: + const: '/' + compatible: + description: SC59X 64-Bit Boards + items: + - enum: + - adi,sc598-som-ezkit # Analog Devices SC598 EZKit + - adi,sc598-som-ezlite # Analog Devices SC598 EZLite + - const: adi,sc59x-64 + +additionalProperties: true diff --git a/Documentation/devicetree/bindings/clock/adi,sc5xx-clocks.yaml b/Documentation/devicetree/bindings/clock/adi,sc5xx-clocks.yaml new file mode 100644 index 00000000000000..11f66543af940d --- /dev/null +++ b/Documentation/devicetree/bindings/clock/adi,sc5xx-clocks.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/adi,sc5xx-clocks.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Clock Tree Drivers for Analog Devices ADSP-SC5XX Processors + +maintainers: + - Arturs Artamonovs + - Utsav Agarwal + +description: | + These drivers read in the processors CDU (clock distribution unit) + and CGU (clock generation unit) values to determine various clock + rates + +properties: + compatible: + oneOf: + - items: + - enum: + - adi,sc598-cgu0 + - adi,sc598-cgu1 + - adi,sc598-cdu + - adi,sc598-pll + + '#clock-cells': + const: 1 + + reg: + minItems: 1 + maxItems: 2 + + clocks: + description: + Specifies the CLKIN0 and CLKIN1 reference clock(s) from which the + output frequencies are derived via CDU+CGU + minItems: 1 + maxItems: 2 + + clock-names: + description: + String reference names for CLKIN0 and CLKIN1 + minItems: 1 + maxItems: 2 + +required: + - compatible + - reg + - clocks + - '#clock-cells' + - clock-names + +additionalProperties: false + +examples: + - | + clk3: clocks@3108d000 { + compatible = "adi,sc598-clocks"; + reg = <0x3108d000 0x1000>, + <0x3108e000 0x1000>, + <0x3108f000 0x1000>, + <0x310a9000 0x1000>; + #clock-cells = <1>; + clocks = <&sys_clkin0>, <&sys_clkin1>; + clock-names = "sys_clkin0", "sys_clkin1"; + status = "okay"; + }; diff --git a/Documentation/devicetree/bindings/gpio/adi,sc5xx-gpio.yaml b/Documentation/devicetree/bindings/gpio/adi,sc5xx-gpio.yaml new file mode 100644 index 00000000000000..22d414b70a8497 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/adi,sc5xx-gpio.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/adi,sc5xx-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices GPIO Port Driver for SC5XX-family processors + +maintainers: + - Arturs Artamonovs + - Utsav Agarwal + +description: | + Analog Devices GPIO Port Driver for SC5XX-family processors + +properties: + compatible: + enum: + - adi,sc5xx-gpio + + gpio-controller: true + + "#gpio-cells": + const: 2 + + gpio-ranges: + description: Associated pinmux controller and the GPIO range values + + adi,pint: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: Associated pin interrupt controller driver + items: + - items: + - description: phandle to pin interrupt controller driver + - description: interrupt value + + reg: + description: PORT GPIO control registers + +required: + - compatible + - reg + - "#gpio-cells" + - gpio-controller + - gpio-ranges + +additionalProperties: false + +examples: + - | + gpa: gport@31004000 { + compatible = "adi,sc5xx-gpio"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x31004000 0x7f>; + gpio-ranges = <&pinctrl0 0 0 16>; + }; diff --git a/Documentation/devicetree/bindings/i2c/adi,sc5xx-i2c.yaml b/Documentation/devicetree/bindings/i2c/adi,sc5xx-i2c.yaml new file mode 100644 index 00000000000000..a6514c85a67252 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/adi,sc5xx-i2c.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/adi,sc5xx-i2c.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices I2C Peripheral for SC5XX Processor Family + +maintainers: + - Arturs Artamonovs + - Utsav Agarwal + +description: Analog Devices I2C Peripheral driver for SC5XX Processor Family + +properties: + compatible: + enum: + - adi,sc5xx-i2c + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clock-khz: + $ref: /schemas/types.yaml#/definitions/uint32 + + clocks: + maxItems: 1 + + clock-names: + maxItems: 1 + +required: + - compatible + - "#address-cells" + - "#size-cells" + - reg + - interrupts + - clock-khz + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + #include + #include + #include + + i2c@31001400 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "adi,sc5xx-i2c"; + reg = <0x31001400 0xff>; + interrupts = ; + clock-khz = <100>; + clocks = <&clk ADSP_CLK_CGU0_SCLK0>; + clock-names = "sclk0"; + }; + diff --git a/Documentation/devicetree/bindings/pinctrl/adi,sc5xx-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/adi,sc5xx-pinctrl.yaml new file mode 100644 index 00000000000000..a88e677f75eddb --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/adi,sc5xx-pinctrl.yaml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pinctrl/adi,sc5xx-pinctrl.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices Pinmuxing Control for SC5XX Processor Family + +maintainers: + - Arturs Artamonovs + - Utsav Agarwal + +description: | + Pinmuxing Control Driver for Configuring Processor Pins/Pads + +properties: + compatible: + enum: + - adi,sc5xx-pinctrl + reg: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 1 + + "adi,port-sizes": + $ref: /schemas/types.yaml#/definitions/uint32-array + maxItems: 9 + description: Space delimited integer list denoting number of pins per port + Ports A-I exist, so this is up to 9 items long + + "adi,no-drive-strength": + type: boolean + description: Indicate missing drive strength registers + + "adi,no-pull-up-down": + type: boolean + description: Indicate missing pull up/down enable registers + +patternProperties: + '-pins$': + type: object + additionalProperties: false + + properties: + pins: + type: object + description: | + A pinctrl node should contain a pin property, specifying the actual + pins to use. + + properties: + pinmux: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: | + pinmux is used to specify which of the available functionalities + for a given pin are actually used. + + additionalProperties: false + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + - "adi,port-sizes" + +additionalProperties: false + +examples: + - | + pinctrl0: pinctrl@31004600 { + compatible = "adi,sc5xx-pinctrl"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x31004600 0x400>; + adi,port-sizes = <16 16 16 16 16 16 16 16 7>; + }; + diff --git a/Documentation/devicetree/bindings/reset/adi,sc5xx-reset.yaml b/Documentation/devicetree/bindings/reset/adi,sc5xx-reset.yaml new file mode 100644 index 00000000000000..bd1bb8bc2d7a6b --- /dev/null +++ b/Documentation/devicetree/bindings/reset/adi,sc5xx-reset.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/reset/adi,sc5xx-reset.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices Reset Controller for SC5XX processor family + +description: SHARC and ARM core reset control unit for starting/stopping/resetting + processors + +maintainers: + - Arturs Artamonovs + - Utsav Agarwal + +properties: + compatible: + enum: + - adi,sc5xx-reset + + reg: + maxItems: 1 + + "#reset-cells": + const: 0 + +required: + - compatible + - reg + - "#reset-cells" + +additionalProperties: false + +examples: + - | + rcu: rcu@3108c000 { + compatible = "adi,sc5xx-reset"; + reg = <0x3108c000 0x1000>; + #reset-cells = <0>; + }; + diff --git a/Documentation/devicetree/bindings/serial/adi,sc5xx-uart.yaml b/Documentation/devicetree/bindings/serial/adi,sc5xx-uart.yaml new file mode 100644 index 00000000000000..1b524c4a0c59bb --- /dev/null +++ b/Documentation/devicetree/bindings/serial/adi,sc5xx-uart.yaml @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/serial/adi,sc5xx-uart.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices UART Driver for SC5XX-family processors + +maintainers: + - Arturs Artamonovs + - Utsav Agarwal + +description: Analog Devices UART Driver for SC59X-family processors + +properties: + compatible: + enum: + - adi,sc5xx-uart + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + description: Clock name + items: + - const: sclk0 + + interrupt-names: + items: + - const: tx + - const: rx + - const: status + + interrupts: + minItems: 3 + maxItems: 3 + description: GIC interrupt numbers + + adi,use-edbo: + type: boolean + description: Increase by 16 incoming bit sampling rate for better + granularity and accuracy at high speed rates. + +required: + - compatible + - reg + - clocks + - clock-names + - interrupt-names + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + #include + + uart0: serial@31003000 { + compatible = "adi,sc5xx-uart"; + reg = <0x31003000 0x40>; + clocks = <&clk ADSP_CLK_CGU0_SCLK0>; + clock-names = "sclk0"; + interrupt-parent = <&gic>; + interrupt-names = "tx", "rx", "status"; + interrupts = , + , + ; + adi,use-edbo; + }; + diff --git a/Documentation/devicetree/bindings/soc/adi/adi,sc5xx-pads.yaml b/Documentation/devicetree/bindings/soc/adi/adi,sc5xx-pads.yaml new file mode 100644 index 00000000000000..b44b4e1d8608c8 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/adi/adi,sc5xx-pads.yaml @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/soc/adi/adi,sc5xx-pads.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices PADS-related system config for SC5XX processor family + +maintainers: + - Arturs Artamonovs + - Utsav Agarwal + +description: Allows other drivers to control the PADS-related system config register. + This register ties into many drivers and adds silicon controls for items + like voltage selection and endian selection. + +properties: + compatible: + enum: + - adi,sc5xx-pads + + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + pads: pads@31004600 { + compatible = "adi,sc5xx-pads"; + reg = <0x31004600 0x100>; + }; + diff --git a/MAINTAINERS b/MAINTAINERS index d0f18fdba068b0..661a527b229676 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2016,6 +2016,25 @@ F: include/dt-bindings/reset/actions,* F: include/linux/soc/actions/ N: owl +ARM/ADI SoC Support +M: Arturs Artamonovs +S: Maintained +F: Documentation/devicetree/bindings/arm/analog/adi,sc5xx.yaml +F: Documentation/devicetree/bindings/clock/adi,sc5xx-clocks.yaml +F: Documentation/devicetree/bindings/gpio/adi,sc5xx-gpio.yaml +F: Documentation/devicetree/bindings/i2c/adi,sc5xx-i2c.yaml +F: Documentation/devicetree/bindings/pinctrl/adi,sc5xx-pinctrl.yaml +F: Documentation/devicetree/bindings/serial/adi,sc5xx-uart.yaml +F: Documentation/devicetree/bindings/reset/adi,sc5xx-reset.yaml +F: Documentation/devicetree/bindings/soc/adi/* +F: arch/arm64/boot/dts/adi/* +F: drivers/clk/adi/* +F: drivers/gpio/gpio-sc5xx.c +F: drivers/i2c/busses/i2c-adi-sc5xx.c +F: drivers/pinctrl/pinctrl-sc5xx.c +F: drivers/soc/adi/* +F: drivers/tty/serial/adi_uart.c + ARM/AIROHA SOC SUPPORT M: Matthias Brugger M: AngeloGioacchino Del Regno diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 6c6d11536b42ec..e0624d6dc0769f 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -292,6 +292,16 @@ config ARCH_ROCKCHIP This enables support for the ARMv8 based Rockchip chipsets, like the RK3368. +config ARCH_SC59X_64 + bool "ADI 64-bit SC59X Platforms" + select TIMER_OF + select GPIOLIB + select PINCTRL + select COUNTER + help + This enables support for Analog Devices Incorporated's + Family of ARM64 DSP processors + config ARCH_SEATTLE bool "AMD Seattle SoC Family" help diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile index 21cd3a87f38530..9b3996a8e01d8e 100644 --- a/arch/arm64/boot/dts/Makefile +++ b/arch/arm64/boot/dts/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 subdir-y += actions +subdir-y += adi subdir-y += airoha subdir-y += allwinner subdir-y += altera diff --git a/arch/arm64/boot/dts/adi/Makefile b/arch/arm64/boot/dts/adi/Makefile new file mode 100644 index 00000000000000..1bf54bc97095e1 --- /dev/null +++ b/arch/arm64/boot/dts/adi/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +dtb-$(CONFIG_ARCH_SC59X_64) += sc598-som-ezkit.dtb diff --git a/arch/arm64/boot/dts/adi/sc598-som-ezkit.dts b/arch/arm64/boot/dts/adi/sc598-som-ezkit.dts new file mode 100644 index 00000000000000..a8db6d5ea764f9 --- /dev/null +++ b/arch/arm64/boot/dts/adi/sc598-som-ezkit.dts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright 2021-2024 - Analog Devices Inc. + * Author: Nathan Barrett-Morrison + */ + +/dts-v1/; + +#include "sc598-som.dtsi" + +/ { + model = "ADI 64-bit SC598 SOM EZ Kit"; + compatible = "adi,sc598-som-ezkit", "adi,sc59x-64"; +}; diff --git a/arch/arm64/boot/dts/adi/sc598-som.dtsi b/arch/arm64/boot/dts/adi/sc598-som.dtsi new file mode 100644 index 00000000000000..93c1d45a52a052 --- /dev/null +++ b/arch/arm64/boot/dts/adi/sc598-som.dtsi @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright 2021-2024 - Analog Devices Inc. + * Author: Nathan Barrett-Morrison + */ + +/dts-v1/; + +#include +#include +#include "sc59x-64.dtsi" + +/ { + chosen { + stdout-path = &uart1; + bootargs = "earlycon=adi_uart,0x31003000 console=ttySC0,115200 mem=224M"; + }; + + memory@90000000 { + device_type = "memory"; + reg = <0x90000000 0x0e000000>; + }; + + memory@20040000 { + device_type = "memory"; + reg = <0x20040000 0x40000>; + }; + + scb: scb-bus { + sec: sec@31089000 { + adi,sharc-cores = <2>; + }; + }; + +}; + +&uart0 { + pinctrl-0 = <&uart0_default>; + pinctrl-names = "default"; + status = "okay"; +}; + +&i2c0 { + status = "okay"; +}; + +&i2c1 { + status = "disabled"; +}; + +&pinctrl0 { + uart0_default: uart0-default-pins { + pins { + pinmux = , + ; + }; + }; + i2c2_pins: i2c2-default-pins { + pins { + pinmux = , + ; + }; + }; +}; diff --git a/arch/arm64/boot/dts/adi/sc59x-64.dtsi b/arch/arm64/boot/dts/adi/sc59x-64.dtsi new file mode 100644 index 00000000000000..634f977a2b27c4 --- /dev/null +++ b/arch/arm64/boot/dts/adi/sc59x-64.dtsi @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright 2021-2024 - Analog Devices Inc. + * Author: Nathan Barrett-Morrison + */ + +#include +#include +#include + +/ { + model = "ADI 64-bit SC59X"; + compatible = "adi,sc59x-64"; + + interrupt-parent = <&gic>; + #address-cells = <1>; + #size-cells = <1>; + + chosen { }; + + aliases { + serial0 = &uart0; + serial2 = &uart2; + serial3 = &uart3; + }; + + cpus { + #address-cells = <0x2>; + #size-cells = <0x0>; + + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a55"; + reg = <0x0 0x0>; + enable-method = "spin-table"; + cpu-release-addr = <0x0 0xdeadbeef>; + clocks = <&clk_cgu0 ADSP_CLK_ARM>, <&clk_cgu0 ADSP_CLK_DDR>; + }; + }; + + pmu { + compatible = "arm,armv8-pmuv3"; + interrupts = ; + interrupt-parent = <&gic>; + }; + + gic: interrupt-controller@31200000 { + compatible = "arm,gic-v3"; + #interrupt-cells = <3>; + interrupt-controller; + reg = <0x31200000 0x40000>, /* GIC Dist */ + <0x31240000 0x40000>; /* GICR */ + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = , /* Physical Secure */ + , /* Physical Non-Secure */ + , /* Virtual */ + ; /* Hypervisor */ + }; + + clocks { + sys_clkin0: oscillator@1 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "sys_clkin0"; + }; + + sys_clkin1: oscillator@2 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "sys_clkin1"; + }; + clk_dummy: oscillator@3 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + clock-output-names = "dummy"; + }; + }; + + clk_cgu0: clocks@3108d000 { + compatible = "adi,sc598-cgu0"; + reg = <0x3108d000 0x1000>; + #clock-cells = <1>; + clocks = <&sys_clkin0>, <&sys_clkin1>; + clock-names = "sys_clkin0", "sys_clkin1"; + status = "okay"; + }; + + clk_cgu1: clocks@3108e000 { + compatible = "adi,sc598-cgu1"; + reg = <0x3108e000 0x1000>; + #clock-cells = <1>; + clocks = <&sys_clkin0>, <&sys_clkin1>; + clock-names = "sys_clkin0", "sys_clkin1"; + status = "okay"; + }; + + clk_cdu: clocks@3108f000 { + compatible = "adi,sc598-cdu"; + reg = <0x3108f000 0x1000>; + #clock-cells = <1>; + clocks = <&clk_dummy>; + clock-names = "clk_dummy"; + status = "okay"; + }; + + clk_ddr_pll: clocks@310a9000 { + compatible = "adi,sc598-pll"; + reg = <0x310a9000 0x1000>; + #clock-cells = <1>; + clocks = <&clk_dummy>; + clock-names = "clk_dummy"; + status = "okay"; + }; + + scb: scb-bus { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + rcu: rcu@3108c000 { + compatible = "adi,sc5xx-reset"; + reg = <0x3108c000 0x1000>; + #reset-cells = <0>; + status = "okay"; + }; + + sec: sec@31089000 { + compatible = "adi,sc5xx-sec"; + reg = <0x31089000 0x1000>; + adi,rcu = <&rcu>; + status = "okay"; + }; + + uart0: uart@31003000 { + compatible = "adi,sc5xx-uart"; + reg = <0x31003000 0x40>; + clocks = <&clk_cgu0 ADSP_CLK_CGU0_SCLK0>; + clock-names = "sclk0"; + interrupt-parent = <&gic>; + interrupt-names = "tx", "rx", "status"; + interrupts = , + , + ; + adi,use-edbo; + status = "disabled"; + }; + + uart1: uart@31003400 { + compatible = "adi,sc5xx-uart"; + reg = <0x31003400 0x40>; + clocks = <&clk_cgu0 ADSP_CLK_CGU0_SCLK0>; + clock-names = "sclk0"; + interrupt-parent = <&gic>; + interrupt-names = "tx", "rx", "status"; + interrupts = , + , + ; + adi,use-edbo; + status = "disabled"; + }; + + uart2: uart@31003800 { + compatible = "adi,sc5xx-uart"; + reg = <0x31003800 0x40>; + clocks = <&clk_cgu0 ADSP_CLK_CGU0_SCLK0>; + clock-names = "sclk0"; + interrupt-parent = <&gic>; + interrupt-names = "tx", "rx", "status"; + interrupts = , + , + ; + adi,use-edbo; + status = "disabled"; + }; + + uart3: uart@31003c00 { + compatible = "adi,sc5xx-uart"; + reg = <0x31003C00 0x40>; + clocks = <&clk_cgu0 ADSP_CLK_CGU0_SCLK0>; + clock-names = "sclk0"; + interrupt-parent = <&gic>; + interrupt-names = "tx", "rx", "status"; + interrupts = , + , + ; + adi,use-edbo; + status = "disabled"; + }; + + i2c0: twi@31001400 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "adi,sc5xx-i2c"; + reg = <0x31001400 0xFF>; + interrupts = ; + clock-khz = <100>; + clocks = <&clk_cgu0 ADSP_CLK_CGU0_SCLK0>; + clock-names = "sclk0"; + status = "disabled"; + }; + + i2c1: twi@31001500 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "adi,sc5xx-i2c"; + reg = <0x31001500 0xFF>; + interrupts = ; + clock-khz = <100>; + clocks = <&clk_cgu0 ADSP_CLK_CGU0_SCLK0>; + clock-names = "sclk0"; + status = "disabled"; + }; + + i2c3: twi@31001000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "adi,sc5xx-i2c"; + reg = <0x31001000 0xFF>; + interrupts = ; + clock-khz = <100>; + clocks = <&clk_cgu0 ADSP_CLK_CGU0_SCLK0>; + clock-names = "sclk0"; + status = "disabled"; + }; + + i2c4: twi@31001100 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "adi,sc5xx-i2c"; + reg = <0x31001100 0xFF>; + interrupts = ; + clock-khz = <100>; + clocks = <&clk_cgu0 ADSP_CLK_CGU0_SCLK0>; + clock-names = "sclk0"; + status = "disabled"; + }; + + i2c5: twi@31001200 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "adi,sc5xx-i2c"; + reg = <0x31001200 0xFF>; + interrupts = ; + clock-khz = <100>; + clocks = <&clk_cgu0 ADSP_CLK_CGU0_SCLK0>; + clock-names = "sclk0"; + status = "disabled"; + }; + + pinctrl0: pinctrl@31004600 { + compatible = "adi,sc5xx-pinctrl"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x31004600 0x400>; + adi,port-sizes = <16 16 16 16 16 16 16 16 7>; + }; + + gpa: gport@31004000 { + compatible = "adi,sc5xx-gpio"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x31004000 0x7F>; + gpio-ranges = <&pinctrl0 0 0 16>; + status = "okay"; + }; + + gpb: gport@31004080 { + compatible = "adi,sc5xx-gpio"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x31004080 0x7F>; + gpio-ranges = <&pinctrl0 0 16 16>; + status = "okay"; + }; + + gpc: gport@31004100 { + compatible = "adi,sc5xx-gpio"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x31004100 0x7F>; + gpio-ranges = <&pinctrl0 0 32 16>; + status = "okay"; + }; + + gpd: gport@31004180 { + compatible = "adi,sc5xx-gpio"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x31004180 0x7F>; + gpio-ranges = <&pinctrl0 0 48 16>; + }; + + gpe: gport@31004200 { + compatible = "adi,sc5xx-gpio"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x31004200 0x7F>; + gpio-ranges = <&pinctrl0 0 64 16>; + }; + + gpf: gport@31004280 { + compatible = "adi,sc5xx-gpio"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x31004280 0x7F>; + gpio-ranges = <&pinctrl0 0 80 16>; + }; + + gpg: gport@31004300 { + compatible = "adi,sc5xx-gpio"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x31004300 0x7F>; + gpio-ranges = <&pinctrl0 0 96 16>; + }; + + gph: gport@31004380 { + compatible = "adi,sc5xx-gpio"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x31004380 0x7F>; + gpio-ranges = <&pinctrl0 0 112 16>; + }; + + gpi: gport@31004400 { + compatible = "adi,sc5xx-gpio"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x31004400 0x7F>; + gpio-ranges = <&pinctrl0 0 128 7>; + }; + + }; +}; diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index f988dd79add899..5bbc5c7b6d42ef 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -67,6 +67,7 @@ CONFIG_ARCH_SEATTLE=y CONFIG_ARCH_INTEL_SOCFPGA=y CONFIG_ARCH_STM32=y CONFIG_ARCH_SYNQUACER=y +CONFIG_ARCH_SC59X_64=y CONFIG_ARCH_TEGRA=y CONFIG_ARCH_TESLA_FSD=y CONFIG_ARCH_SPRD=y @@ -475,6 +476,7 @@ CONFIG_SERIAL_8250_OMAP=y CONFIG_SERIAL_8250_MT6577=y CONFIG_SERIAL_8250_UNIPHIER=y CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_SERIAL_ADI_UART=y CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y CONFIG_SERIAL_MESON=y @@ -515,6 +517,7 @@ CONFIG_TCG_TIS_I2C_CR50=m CONFIG_TCG_TIS_I2C_INFINEON=y CONFIG_I2C_CHARDEV=y CONFIG_I2C_MUX=y +CONFIG_I2C_ADI_SC5XX=y CONFIG_I2C_MUX_PCA954x=y CONFIG_I2C_BCM2835=m CONFIG_I2C_CADENCE=m @@ -542,6 +545,7 @@ CONFIG_I2C_UNIPHIER_F=y CONFIG_I2C_RCAR=y CONFIG_I2C_CROS_EC_TUNNEL=y CONFIG_SPI=y +CONFIG_SPI_ADI=y CONFIG_SPI_ARMADA_3700=y CONFIG_SPI_BCM2835=m CONFIG_SPI_BCM2835AUX=m @@ -640,6 +644,7 @@ CONFIG_PINCTRL_SM8450_LPASS_LPI=m CONFIG_PINCTRL_SC8280XP_LPASS_LPI=m CONFIG_PINCTRL_SM8550_LPASS_LPI=m CONFIG_PINCTRL_SM8650_LPASS_LPI=m +CONFIG_GPIO_ADI_ADSP_PORT=y CONFIG_GPIO_ALTERA=m CONFIG_GPIO_DAVINCI=y CONFIG_GPIO_DWAPB=y @@ -1522,6 +1527,7 @@ CONFIG_RESET_QCOM_AOSS=y CONFIG_RESET_QCOM_PDC=m CONFIG_RESET_RZG2L_USBPHY_CTRL=y CONFIG_RESET_TI_SCI=y +CONFIG_RESET_SC5XX=y CONFIG_PHY_XGENE=y CONFIG_PHY_CAN_TRANSCEIVER=m CONFIG_PHY_SUN4I_USB=y diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 0fe07a594b4e1b..aa50d43c3b79d9 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -42,6 +42,16 @@ config COMMON_CLK_WM831X source "drivers/clk/versatile/Kconfig" +config COMMON_CLK_ADI_SC598 + bool "Clock driver for ADI SC598 SoCs" + depends on OF + default y if ARCH_SC59X_64 + help + This driver supports the system clocks on Analog Devices SC598-series + SoCs. It includes CGU and CDU clocks and supports gating unused clocks. + Modifying PLL configuration is not supported; that must be done prior + to booting the kernel. Clock dividers after the PLLs may be configured. + config CLK_HSDK bool "PLL Driver for HSDK platform" depends on ARC_SOC_HSDK || COMPILE_TEST diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index fb8878a5d7d93d..6c1d6243afba2a 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -94,6 +94,7 @@ obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o # please keep this section sorted lexicographically by directory path name obj-y += actions/ +obj-y += adi/ obj-y += analogbits/ obj-$(CONFIG_COMMON_CLK_AT91) += at91/ obj-$(CONFIG_ARCH_ARTPEC) += axis/ diff --git a/drivers/clk/adi/Makefile b/drivers/clk/adi/Makefile new file mode 100644 index 00000000000000..5af85e7fc90e48 --- /dev/null +++ b/drivers/clk/adi/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +#SC598 +obj-$(CONFIG_COMMON_CLK_ADI_SC598) += clk-adi-pll.o +obj-$(CONFIG_COMMON_CLK_ADI_SC598) += clk.o +obj-$(CONFIG_COMMON_CLK_ADI_SC598) += clk-adi-sc598.o diff --git a/drivers/clk/adi/clk-adi-pll.c b/drivers/clk/adi/clk-adi-pll.c new file mode 100644 index 00000000000000..9e996227ac8c3c --- /dev/null +++ b/drivers/clk/adi/clk-adi-pll.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CGU PLL driver for ADI SC59X processors + * + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#include +#include +#include +#include + +#include "clk.h" + +struct clk_sc5xx_cgu_pll { + struct clk_hw hw; + void __iomem *base; + spinlock_t *lock; + int prepared; + u32 mask; + u32 msel; + u32 m_offset; + u8 shift; +}; + +struct clk_sc5xx_cgu_pll *to_clk_sc5xx_cgu_pll(struct clk_hw *hw) +{ + return container_of(hw, struct clk_sc5xx_cgu_pll, hw); +} + +static long sc5xx_cgu_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct clk_sc5xx_cgu_pll *pll = to_clk_sc5xx_cgu_pll(hw); + unsigned long m, m2, new_rate, nr2, prate2; + unsigned long prate = *parent_rate; + struct clk_hw *parent_hw; + int parent_inc; + + parent_hw = clk_hw_get_parent(hw); + + m = rate / prate; + + if (m > pll->msel) { + /* cannot scale this far, need bigger input */ + parent_inc = m / pll->msel; + prate = clk_hw_round_rate(parent_hw, prate * (parent_inc + 1)); + } else if (m == 0) { + pr_err("%s: Cannot use VCO to reduce parent clock rate, requested %lu, clamping to %lu\n", + __func__, rate, prate); + return prate; + } + + new_rate = prate * m; + + if (new_rate != rate) { + /* + * Check if we could get an integer match by halving parent rate since we + * know at least about the DF bit before the VCO, although we don't know + * if we're already using it or not + */ + prate2 = clk_hw_round_rate(parent_hw, prate / 2); + m2 = rate / prate2; + nr2 = prate * m2; + if (m2 <= pll->msel && nr2 == rate) { + m = m2; + new_rate = nr2; + prate = prate2; + } + } + + *parent_rate = prate; + return new_rate; +} + +static unsigned long sc5xx_cgu_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_sc5xx_cgu_pll *pll = to_clk_sc5xx_cgu_pll(hw); + u32 reg = readl(pll->base); + u32 m = ((reg & pll->mask) >> pll->shift) + pll->m_offset; + + if (m == 0) + m = pll->msel; + + return parent_rate * m; + +} + +static int sc5xx_cgu_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_sc5xx_cgu_pll *pll = to_clk_sc5xx_cgu_pll(hw); + u32 m; + + m = (rate / parent_rate) - pll->m_offset; + + if (m >= pll->msel) + m = 0; + + /* reminder for implementation: lock around read/modify to control reg */ + pr_err("%s: set_rate not permitted yet, but we would write %d to m\n", __func__, + m); + return -ENOENT; +} + +static const struct clk_ops clk_sc5xx_cgu_pll_ops = { + .recalc_rate = sc5xx_cgu_pll_recalc_rate, + .round_rate = sc5xx_cgu_pll_round_rate, + .set_rate = sc5xx_cgu_pll_set_rate, +}; + +struct clk *sc5xx_cgu_pll(const char *name, const char *parent_name, + void __iomem *base, u8 shift, u8 width, u32 m_offset, + spinlock_t *lock) +{ + struct clk_sc5xx_cgu_pll *pll; + struct clk *clk; + struct clk_init_data init; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.flags = CLK_SET_RATE_PARENT; + init.parent_names = &parent_name; + init.num_parents = 1; + init.ops = &clk_sc5xx_cgu_pll_ops; + + pll->base = base; + pll->hw.init = &init; + pll->lock = lock; + pll->shift = shift; + pll->mask = GENMASK(width-1, 0) << shift; + pll->msel = pll->mask + 1; + pll->m_offset = m_offset; + + clk = clk_register(NULL, &pll->hw); + if (IS_ERR(clk)) { + pr_err("%s: Failed to register, code %lu\n", __func__, + PTR_ERR(clk)); + } + + return clk; +} + +MODULE_DESCRIPTION("Analog Devices CLock PLL driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Greg Malysa "); diff --git a/drivers/clk/adi/clk-adi-sc598.c b/drivers/clk/adi/clk-adi-sc598.c new file mode 100644 index 00000000000000..bc6dcedfbedc19 --- /dev/null +++ b/drivers/clk/adi/clk-adi-sc598.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Clock support for ADI processor + * + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "clk.h" + +static const char * const cgu1_in_sels[] = {"sys_clkin0", "sys_clkin1"}; +static const char * const cgu0_s1sels[] = {"cgu0_s1seldiv", "cgu0_s1selexdiv"}; +static const char * const cgu1_s0sels[] = {"cgu1_s0seldiv", "cgu1_s0selexdiv"}; +static const char * const cgu1_s1sels[] = {"cgu1_s1seldiv", "cgu1_s1selexdiv"}; +static const char * const sharc0_sels[] = {"cclk0_0", "dummy", "dummy", "dummy"}; +static const char * const sharc1_sels[] = {"cclk0_0", "dummy", "dummy", "dummy"}; +static const char * const arm_sels[] = {"dummy", "dummy", "cclk2_0", "cclk2_1"}; +static const char * const cdu_ddr_sels[] = {"dclk_0", "dclk_1", "dummy", "dummy"}; +static const char * const can_sels[] = {"dummy", "oclk_1", "dummy", "dummy"}; +static const char * const spdif_sels[] = {"sclk1_0", "dummy", "dummy", "dummy"}; +static const char * const spi_sels[] = {"sclk0_0", "oclk_0", "dummy", "dummy"}; +static const char * const gige_sels[] = {"sclk0_0", "sclk0_1", "dummy", "dummy"}; +static const char * const lp_sels[] = {"oclk_0", "sclk0_0", "cclk0_1", "dummy"}; +static const char * const lp_ddr_sels[] = {"oclk_0", "dclk_0", "sysclk_1", "dummy"}; +static const char * const ospi_refclk_sels[] = {"sysclk_0", "sclk0_0", "sclk1_1", "dummy"}; +static const char * const trace_sels[] = {"sclk0_0", "dummy", "dummy", "dummy"}; +static const char * const emmc_sels[] = {"oclk_0", "sclk0_1", "dclk_0_half", "dclk_1_half"}; +static const char * const emmc_timer_sels[] = {"dummy", "sclk1_1_half", "dummy", "dummy"}; + +static const char * const ddr_sels[] = {"cdu_ddr", "3pll_ddiv"}; + +static struct adi_clk_branch cgu0_branches[] __initdata = { + /* + * Clock Generation Unit 0 + */ + DIVIDER(ADSP_CLK_CGU0_PLL_IN, "cgu0_df", "sys_clkin0", CGU_CTL, + 0, 1, 0), + PLL(ADSP_CLK_CGU0_VCO_OUT, "cgu0_vco_msel", "cgu0_df", CGU_CTL, + CGU_MSEL_SHIFT, CGU_MSEL_WIDTH, 0), + FIXED(ADSP_CLK_CGU0_VCO_2_OUT, "cgu0_vco", "cgu0_vco_msel", CLK_SET_RATE_PARENT, 2, 1), + FIXED(ADSP_CLK_CGU0_PLLCLK, "cgu0_pllclk", "cgu0_vco", CLK_SET_RATE_PARENT, 1, 2), + DIVIDER(ADSP_CLK_CGU0_CDIV, "cgu0_cdiv", "cgu0_pllclk", CGU_DIV, + 0, 5, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU0_SYSCLK, "sysclk_0", "cgu0_pllclk", CGU_DIV, + 8, 5, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU0_DDIV, "cgu0_ddiv", "cgu0_pllclk", CGU_DIV, + 16, 5, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU0_ODIV, "cgu0_odiv", "cgu0_pllclk", CGU_DIV, + 22, 7, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU0_S0SELDIV, "cgu0_s0seldiv", "sysclk_0", CGU_DIV, + 5, 3, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU0_S1SELDIV, "cgu0_s1seldiv", "sysclk_0", CGU_DIV, + 13, 3, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU0_S1SELEXDIV, "cgu0_s1selexdiv", "cgu0_pllclk", + CGU_DIVEX, 16, 8, CLK_DIVIDER_MAX_AT_ZERO), + MUX(ADSP_CLK_CGU0_S1SEL, "cgu0_sclk1sel", cgu0_s1sels, CLK_SET_RATE_PARENT, CGU_CTL, 17, 1), + FIXED(ADSP_CLK_CGU0_CCLK2, "cclk2_0", "cgu0_vco", CLK_SET_RATE_PARENT, 1, 3), + CGU_GATE(ADSP_CLK_CGU0_CCLK0, "cclk0_0", "cgu0_cdiv", CGU_CCBF_DIS, 0), + CGU_GATE(ADSP_CLK_CGU0_OCLK, "oclk_0", "cgu0_odiv", CGU_SCBF_DIS, 3), + CGU_GATE(ADSP_CLK_CGU0_DCLK, "dclk_0", "cgu0_ddiv", CGU_SCBF_DIS, 2), + CGU_GATE(ADSP_CLK_CGU0_SCLK1, "sclk1_0", "cgu0_sclk1sel", CGU_SCBF_DIS, 1), + CGU_GATE(ADSP_CLK_CGU0_SCLK0, "sclk0_0", "cgu0_s0seldiv", CGU_SCBF_DIS, 0), + FIXED(ADSP_CLK_DCLK0_HALF, "dclk_0_half", "dclk_0", CLK_SET_RATE_PARENT, 1, 2), +}; + +static struct adi_clk_branch cgu1_branches[] __initdata = { + /* + * Clock Generation Unit 1 + */ + DIVIDER(ADSP_CLK_CGU1_PLL_IN, "cgu1_df", "cgu1_in_sel", CGU_CTL, + 0, 1, 0), + PLL(ADSP_CLK_CGU1_VCO_OUT, "cgu1_vco_msel", "cgu1_df", CGU_CTL, + CGU_MSEL_SHIFT, CGU_MSEL_WIDTH, 0), + FIXED(ADSP_CLK_CGU1_VCO_2_OUT, "cgu1_vco", "cgu1_vco_msel", CLK_SET_RATE_PARENT, 2, 1), + FIXED(ADSP_CLK_CGU1_PLLCLK, "cgu1_pllclk", "cgu1_vco", CLK_SET_RATE_PARENT, 1, 2), + DIVIDER(ADSP_CLK_CGU1_CDIV, "cgu1_cdiv", "cgu1_pllclk", CGU_DIV, + 0, 5, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU1_SYSCLK, "sysclk_1", "cgu1_pllclk", CGU_DIV, + 8, 5, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU1_DDIV, "cgu1_ddiv", "cgu1_pllclk", CGU_DIV, + 16, 5, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU1_ODIV, "cgu1_odiv", "cgu1_pllclk", CGU_DIV, + 22, 7, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU1_S0SELDIV, "cgu1_s0seldiv", "sysclk_1", CGU_DIV, + 5, 3, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU1_S1SELDIV, "cgu1_s1seldiv", "sysclk_1", CGU_DIV, + 13, 3, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU1_S0SELEXDIV, "cgu1_s0selexdiv", "cgu1_pllclk", CGU_DIVEX, 0, 8, CLK_DIVIDER_MAX_AT_ZERO), + DIVIDER(ADSP_CLK_CGU1_S1SELEXDIV, "cgu1_s1selexdiv", "cgu1_pllclk", CGU_DIVEX, 16, 8, CLK_DIVIDER_MAX_AT_ZERO), + MUX(ADSP_CLK_CGU1_S0SEL, "cgu1_sclk0sel", cgu1_s0sels, CLK_SET_RATE_PARENT, CGU_CTL, 16, 1), + MUX(ADSP_CLK_CGU1_S1SEL, "cgu1_sclk1sel", cgu1_s1sels, CLK_SET_RATE_PARENT, CGU_CTL, 17, 1), + FIXED(ADSP_CLK_CGU1_CCLK2, "cclk2_1", "cgu1_vco", CLK_SET_RATE_PARENT, 1, 3), + CGU_GATE(ADSP_CLK_CGU1_CCLK0, "cclk0_1", "cgu1_cdiv", CGU_CCBF_DIS, 0), + CGU_GATE(ADSP_CLK_CGU1_OCLK, "oclk_1", "cgu1_odiv", CGU_SCBF_DIS, 3), + CGU_GATE(ADSP_CLK_CGU1_DCLK, "dclk_1", "cgu1_ddiv", CGU_SCBF_DIS, 2), + CGU_GATE(ADSP_CLK_CGU1_SCLK1, "sclk1_1", "cgu1_sclk1sel", CGU_SCBF_DIS, 1), + CGU_GATE(ADSP_CLK_CGU1_SCLK0, "sclk0_1", "cgu1_sclk0sel", CGU_SCBF_DIS, 0), + FIXED(ADSP_CLK_CGU1_SCLK1_HALF, "sclk1_1_half", "sclk1_1", CLK_SET_RATE_PARENT, 1, 2), +}; + +static struct adi_clk_branch cdu_branches[] __initdata = { + /* + * Clock Distribution Unit + */ + MUX(ADSP_CLK_CGU1_IN, "cgu1_in_sel", cgu1_in_sels, CLK_SET_RATE_PARENT, CDU_CLKINSEL, 0, 1), + CDU_MUX(ADSP_CLK_SHARC0_SEL, "sharc0_sel", sharc0_sels, CDU_CFG0, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_SHARC1_SEL, "sharc1_sel", sharc1_sels, CDU_CFG1, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_ARM_SEL, "arm_sel", arm_sels, CDU_CFG2, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_CDU_DDR_SEL, "cdu_ddr_sel", cdu_ddr_sels, CDU_CFG3, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + FIXED(ADSP_CLK_DCLK1_HALF, "dclk_1_half", "dclk_1", CLK_SET_RATE_PARENT, 1, 2), + CDU_MUX(ADSP_CLK_CAN_SEL, "can_sel", can_sels, CDU_CFG4, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_SPDIF_SEL, "spdif_sel", spdif_sels, CDU_CFG5, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_SPI_SEL, "spi_sel", spi_sels, CDU_CFG6, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_GIGE_SEL, "gige_sel", gige_sels, CDU_CFG7, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_LP_SEL, "lp_sel", lp_sels, CDU_CFG8, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_LP_DDR_SEL, "lp_ddr_sel", lp_ddr_sels, CDU_CFG9, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_OSPI_REFCLK_SEL, "ospi_refclk_sel", ospi_refclk_sels, CDU_CFG10, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_TRACE_SEL, "trace_sel", trace_sels, CDU_CFG12, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_EMMC_SEL, "emmc_sel", emmc_sels, CDU_CFG13, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + CDU_MUX(ADSP_CLK_EMMC_TIMER_QMC_SEL, "emmc_timer_qmc_sel", emmc_timer_sels, CDU_CFG14, + CDU_MUX_SHIFT, CDU_MUX_WIDTH), + + CDU_GATE(ADSP_CLK_SHARC0, "sharc0", "sharc0_sel", CDU_CFG0, CLK_IS_CRITICAL), + CDU_GATE(ADSP_CLK_SHARC1, "sharc1", "sharc1_sel", CDU_CFG1, CLK_IS_CRITICAL), + CDU_GATE(ADSP_CLK_ARM, "arm", "arm_sel", CDU_CFG2, CLK_IS_CRITICAL), + CDU_GATE(ADSP_CLK_CDU_DDR, "cdu_ddr", "cdu_ddr_sel", CDU_CFG3, 0), + CDU_GATE(ADSP_CLK_CAN, "can", "can_sel", CDU_CFG4, 0), + CDU_GATE(ADSP_CLK_SPDIF, "spdif", "spdif_sel", CDU_CFG5, 0), + CDU_GATE(ADSP_CLK_SPI, "spi", "spi_sel", CDU_CFG6, 0), + CDU_GATE(ADSP_CLK_GIGE, "gige", "gige_sel", CDU_CFG7, 0), + CDU_GATE(ADSP_CLK_LP, "lp", "lp_sel", CDU_CFG8, 0), + CDU_GATE(ADSP_CLK_LP_DDR, "lp_ddr", "lp_ddr_sel", CDU_CFG9, 0), + CDU_GATE(ADSP_CLK_OSPI_REFCLK, "ospi_refclk", "ospi_refclk_sel", CDU_CFG10, 0), + CDU_GATE(ADSP_CLK_TRACE, "trace", "trace_sel", CDU_CFG12, 0), + CDU_GATE(ADSP_CLK_EMMC, "emmc", "emmc_sel", CDU_CFG13, 0), + CDU_GATE(ADSP_CLK_EMMC_TIMER_QMC, "emmc_timer_qmc", "emmc_timer_qmc_sel", CDU_CFG14, 0), +}; + +static struct adi_clk_branch pll3_branches[] __initdata = { + DIVIDER(ADSP_CLK_3PLL_PLL_IN, "3pll_df", "cgu1_in_sel", PLL3_CONTROL, + 3, 1, 0), + PLL(ADSP_CLK_3PLL_VCO_OUT, "pll3_vco_msel", "3pll_df", PLL3_CONTROL, + PLL3_MSEL_SHIFT, PLL3_MSEL_WIDTH, 1), + FIXED(ADSP_CLK_3PLL_VCO_2_OUT, "3pll_vco", "pll3_vco_msel", CLK_SET_RATE_PARENT, 2, 1), + FIXED(ADSP_CLK_3PLL_PLLCLK, "3pll_pllclk", "3pll_vco", CLK_SET_RATE_PARENT, 1, 2), + DIVIDER(ADSP_CLK_3PLL_DDIV, "3pll_ddiv", "3pll_pllclk", PLL3_CONTROL, + 12, 5, 0), + MUX(ADSP_CLK_DDR, "ddr", ddr_sels, CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, PLL3_CONTROL, + 11, 1), +}; + +static void sc5xx_clock_setup(struct device_node *np, + struct adi_clk_branch *branch_list, + unsigned int nr_clk) +{ + struct adi_clk_provider *ctx; + void __iomem *reg_base; + + reg_base = of_iomap(np, 0); + if (IS_ERR(reg_base)) { + pr_err("Unable to remap clock registers\n"); + return; + } + + ctx = adi_clk_init(np, reg_base, ADSP_CLK_NR_CLKS); + if (IS_ERR(ctx)) { + pr_err("%s: clk init failed\n", __func__); + iounmap(reg_base); + return; + } + + adi_clk_register_branches(ctx, branch_list, nr_clk); + + if (of_clk_add_provider(np, of_clk_src_onecell_get, &ctx->clk_data)) + pr_err("%s: could not register clock provider\n", __func__); +} + +static void sc598_clock_setup_cgu0(struct device_node *np) +{ + sc5xx_clock_setup(np, cgu0_branches, ARRAY_SIZE(cgu0_branches)); +} + +static void sc598_clock_setup_cgu1(struct device_node *np) +{ + sc5xx_clock_setup(np, cgu1_branches, ARRAY_SIZE(cgu1_branches)); +} + +static void sc598_clock_setup_cdu(struct device_node *np) +{ + sc5xx_clock_setup(np, cdu_branches, ARRAY_SIZE(cdu_branches)); +} + +static void sc598_clock_setup_pll(struct device_node *np) +{ + sc5xx_clock_setup(np, pll3_branches, ARRAY_SIZE(pll3_branches)); +} + + + +CLK_OF_DECLARE(adi_sc598_clocks_cgu0, "adi,sc598-cgu0", sc598_clock_setup_cgu0); +CLK_OF_DECLARE(adi_sc598_clocks_cgu1, "adi,sc598-cgu1", sc598_clock_setup_cgu1); +CLK_OF_DECLARE(adi_sc598_clocks_cdu, "adi,sc598-cdu", sc598_clock_setup_cdu); +CLK_OF_DECLARE(adi_sc598_clocks_pll, "adi,sc598-pll", sc598_clock_setup_pll); + +MODULE_DESCRIPTION("Analog Devices Clock driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Greg Malysa "); diff --git a/drivers/clk/adi/clk.c b/drivers/clk/adi/clk.c new file mode 100644 index 00000000000000..18a82a8d45832b --- /dev/null +++ b/drivers/clk/adi/clk.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Clock support for ADI processor + * + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#include +#include + +#include "clk.h" + +struct adi_clk_provider *adi_clk_init(struct device_node *np, + void __iomem *reg_base, + unsigned long nr_clks) +{ + struct adi_clk_provider *ctx; + struct clk **clk_table; + int i; + + ctx = kzalloc(sizeof(struct adi_clk_provider), GFP_KERNEL); + if (!ctx) + return ERR_PTR(-ENOMEM); + + clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL); + if (!clk_table) + goto err_free; + + for (i = 0; i < nr_clks; ++i) + clk_table[i] = ERR_PTR(-ENOENT); + + ctx->reg_base = reg_base; + ctx->clk_data.clks = clk_table; + ctx->clk_data.clk_num = nr_clks; + ctx->clk_node = np; + spin_lock_init(&ctx->lock); + + return ctx; + +err_free: + kfree(ctx); + + return ERR_PTR(-ENOMEM); +} + +static void adi_clk_add_lookup(struct adi_clk_provider *ctx, + struct clk *clk, unsigned int id) +{ + ctx->clk_data.clks[id] = clk; +} + +void adi_clk_register_branches(struct adi_clk_provider *ctx, + struct adi_clk_branch *list, + unsigned int nr_clk) +{ + struct clk *clk = NULL; + unsigned int idx; + + for (idx = 0; idx < nr_clk; idx++, list++) { + clk = NULL; + switch (list->branch_type) { + case branch_divider: + clk = clk_register_divider(NULL, list->name, + list->parent_names[0], + CLK_SET_RATE_PARENT, + ctx->reg_base + list->offset, + list->shift, + list->div_width, + list->div_flags, + &ctx->lock); + break; + case branch_mux: + clk = clk_register_mux(NULL, + list->name, + list->parent_names, + list->num_parents, //check if that match + list->flags, + ctx->reg_base + list->offset, + list->shift, + list->mux_width, + 0, + &ctx->lock); + break; + case branch_cdu_mux: + clk = clk_register_mux(NULL, + list->name, + list->parent_names, + list->num_parents, //check if that match + CLK_SET_RATE_PARENT, + ctx->reg_base + list->offset, + CDU_MUX_SHIFT, + CDU_MUX_WIDTH, + 0, + &ctx->lock); + break; + case branch_pll: + clk = sc5xx_cgu_pll(list->name, + list->parent_names[0], + ctx->reg_base + list->offset, + list->pll_shift, + list->pll_width, + list->pll_m, + &ctx->lock); + break; + case branch_cgu_gate: + clk = clk_register_gate(NULL, + list->name, + list->parent_names[0], + CLK_SET_RATE_PARENT, + ctx->reg_base + list->offset, + list->gate_bit, + CLK_GATE_SET_TO_DISABLE, + &ctx->lock); + + break; + case branch_cdu_gate: + clk = clk_register_gate(NULL, + list->name, + list->parent_names[0], + CLK_SET_RATE_PARENT | list->flags, + ctx->reg_base + list->offset, + CDU_EN_BIT, + 0, + &ctx->lock); + + break; + case branch_of_node: + clk = of_clk_get_by_name(ctx->clk_node, list->name); + break; + } + + /* none of the cases above matched */ + if (!clk) { + pr_err("%s: unknown clock type %d\n", + __func__, list->branch_type); + continue; + } + + if (IS_ERR(clk)) { + pr_err("%s: failed to register clock %s: %ld\n", + __func__, list->name, PTR_ERR(clk)); + continue; + } + + adi_clk_add_lookup(ctx, clk, list->id); + } +} diff --git a/drivers/clk/adi/clk.h b/drivers/clk/adi/clk.h new file mode 100644 index 00000000000000..6cc130524dc6c9 --- /dev/null +++ b/drivers/clk/adi/clk.h @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Clock support for ADI processors + * + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#ifndef CLK_ADI_CLK_H +#define CLK_ADI_CLK_H + +#include +#include + +#define CGU_CTL 0x00 +#define CGU_PLLCTL 0x04 +#define CGU_STAT 0x08 +#define CGU_DIV 0x0C +#define CGU_CLKOUTSEL 0x10 +#define CGU_OSCWDCTL 0x14 +#define CGU_TSCTL 0x18 +#define CGU_TSVALUE0 0x1C +#define CGU_TSVALUE1 0x20 +#define CGU_TSCOUNT0 0x24 +#define CGU_TSCOUNT1 0x28 +#define CGU_CCBF_DIS 0x2C +#define CGU_CCBF_STAT 0x30 +#define CGU_SCBF_DIS 0x38 +#define CGU_SCBF_STAT 0x3C +#define CGU_DIVEX 0x40 +#define CGU_REVID 0x48 + +#define CDU_CFG0 0x00 +#define CDU_CFG1 0x04 +#define CDU_CFG2 0x08 +#define CDU_CFG3 0x0C +#define CDU_CFG4 0x10 +#define CDU_CFG5 0x14 +#define CDU_CFG6 0x18 +#define CDU_CFG7 0x1C +#define CDU_CFG8 0x20 +#define CDU_CFG9 0x24 +#define CDU_CFG10 0x28 +#define CDU_CFG11 0x2C +#define CDU_CFG12 0x30 +#define CDU_CFG13 0x34 +#define CDU_CFG14 0x38 + +#define PLL3_CONTROL 0x2c + +#define CDU_CLKINSEL 0x44 + +#define CGU_MSEL_SHIFT 8 +#define CGU_MSEL_WIDTH 7 + +#define PLL3_MSEL_SHIFT 4 +#define PLL3_MSEL_WIDTH 7 + +#define CDU_MUX_SIZE 4 +#define CDU_MUX_SHIFT 1 +#define CDU_MUX_WIDTH 2 +#define CDU_EN_BIT 0 + +struct adi_clk_provider { + void __iomem *reg_base; + struct clk_onecell_data clk_data; + struct device_node *clk_node; + struct regmap *grf; + spinlock_t lock; +}; + +enum adi_clk_branch_type { + branch_divider, + branch_cdu_mux, + branch_mux, + branch_pll, + branch_cgu_gate, + branch_cdu_gate, + branch_of_node, +}; + +struct adi_clk_branch { + unsigned int id; + enum adi_clk_branch_type branch_type; + const char *name; + const char *const *parent_names; //should be const? + u8 num_parents; + unsigned long flags; + unsigned long offset; //void ** iomem offset?! + u8 div_flags; + u8 shift; + u8 div_width; + u32 mux_width; + unsigned int fact_mult; + unsigned int fact_div; + u8 gate_bit; + u8 pll_shift; + u8 pll_width; + u32 pll_m; + unsigned long rate; +}; + +#define DIVIDER(_id, cname, pname, o, s, w, df) \ +{ \ + .id = _id, \ + .branch_type = branch_divider, \ + .name = cname, \ + .parent_names = (const char *[]){ pname }, \ + .num_parents = 1, \ + .div_flags = df, \ + .offset = o, \ + .shift = s, \ + .div_width = w, \ +} + +#define CDU_MUX(_id, cname, pname, o, s, w) \ +{ \ + .id = _id, \ + .branch_type = branch_cdu_mux, \ + .name = cname, \ + .parent_names = pname, \ + .num_parents = ARRAY_SIZE(pname), \ + .offset = o, \ + .shift = s, \ + .mux_width = w, \ +} + +#define MUX(_id, cname, pname, f, o, s, w) \ +{ \ + .id = _id, \ + .branch_type = branch_mux, \ + .name = cname, \ + .parent_names = pname, \ + .num_parents = ARRAY_SIZE(pname), \ + .flags = f, \ + .offset = o, \ + .shift = s, \ + .mux_width = w, \ +} + +#define FIXED(_id, cname, pname, f, mult, div) \ +{ \ + .id = _id, \ + .branch_type = branch_mux, \ + .name = cname, \ + .parent_names = (const char *[]){ pname }, \ + .num_parents = 1, \ + .flags = f, \ + .fact_mult = mult, \ + .fact_div = div, \ +} + +#define PLL(_id, cname, pname, o, s, w, m_offset) \ +{ \ + .id = _id, \ + .branch_type = branch_pll, \ + .name = cname, \ + .parent_names = (const char *[]){ pname }, \ + .num_parents = 1, \ + .offset = o, \ + .pll_shift = s, \ + .pll_width = w, \ + .pll_m = m_offset, \ +} + +#define CGU_GATE(_id, cname, pname, o, b) \ +{ \ + .id = _id, \ + .branch_type = branch_cgu_gate, \ + .name = cname, \ + .parent_names = (const char *[]){ pname }, \ + .num_parents = 1, \ + .offset = o, \ + .gate_bit = b, \ +} + +#define CDU_GATE(_id, cname, pname, o, f) \ +{ \ + .id = _id, \ + .branch_type = branch_cdu_gate, \ + .name = cname, \ + .parent_names = (const char *[]){ pname }, \ + .num_parents = 1, \ + .offset = o, \ + .flags = f, \ +} + +#define OF_CLOCK(_id, cname) \ +{ \ + .id = _id, \ + .name = cname, \ + .branch_type = branch_of_node, \ +} + +struct clk_sc5xx_cgu_pll *to_clk_sc5xx_cgu_pll(struct clk_hw *hw); +struct clk *sc5xx_cgu_pll(const char *name, const char *parent_name, + void __iomem *base, u8 shift, u8 width, u32 m_offset, spinlock_t *lock); + +struct adi_clk_provider *adi_clk_init(struct device_node *np, + void __iomem *base, unsigned long nr_clks); + +void adi_clk_register_branches(struct adi_clk_provider *ctx, + struct adi_clk_branch *list, + unsigned int nr_clk); +#endif diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index d93cd4f722b401..54f8ffdbe950c4 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -625,6 +625,15 @@ config GPIO_SAMA5D2_PIOBU The difference from regular GPIOs is that they maintain their value during backup/self-refresh. +config GPIO_SC5XX + bool "ADI ADSP-SC5XX PORT GPIO driver" + default y if ARCH_SC59X_64 + depends on OF_GPIO + select GPIO_GENERIC + help + Say Y to enable the ADSP PORT-based GPIO driver for Analog Devices + ADSP chips. + config GPIO_SIFIVE tristate "SiFive GPIO support" depends on OF_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 1429e8c0229b92..df5ae87054b784 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -145,6 +145,7 @@ obj-$(CONFIG_GPIO_ROCKCHIP) += gpio-rockchip.o obj-$(CONFIG_GPIO_RTD) += gpio-rtd.o obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o +obj-$(CONFIG_GPIO_SC5XX) += gpio-sc5xx.o obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o obj-$(CONFIG_GPIO_SCH) += gpio-sch.o obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o diff --git a/drivers/gpio/gpio-sc5xx.c b/drivers/gpio/gpio-sc5xx.c new file mode 100644 index 00000000000000..1058a85fa02269 --- /dev/null +++ b/drivers/gpio/gpio-sc5xx.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ADSP PORT gpio driver + * + * (C) Copyright 2022-2024 - Analog Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include "gpiolib.h" + +static int adsp_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) +{ + struct adsp_gpio_port *port = to_adsp_gpio_port(chip); + + writew(BIT(offset), port->regs + ADSP_PORT_REG_DIR_CLEAR); + writew(BIT(offset), port->regs + ADSP_PORT_REG_INEN_SET); + return 0; +} + +static int adsp_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct adsp_gpio_port *port = to_adsp_gpio_port(chip); + + /* + * For open drain ports, they've already been configured by pinctrl and + * we should not modify their output characteristics + */ + if (port->open_drain & BIT(offset)) + return 0; + + writew(BIT(offset), port->regs + ADSP_PORT_REG_INEN_CLEAR); + + if (value) + writew(BIT(offset), port->regs + ADSP_PORT_REG_DATA_SET); + else + writew(BIT(offset), port->regs + ADSP_PORT_REG_DATA_CLEAR); + + writew(BIT(offset), port->regs + ADSP_PORT_REG_DIR_SET); + return 0; +} + +static void adsp_gpio_set_value(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct adsp_gpio_port *port = to_adsp_gpio_port(chip); + + /* + * For open drain ports, set as input if driving a 1, set as output + * if driving a 0 + */ + if (port->open_drain & BIT(offset)) { + if (value) { + writew(BIT(offset), port->regs + ADSP_PORT_REG_DIR_CLEAR); + writew(BIT(offset), port->regs + ADSP_PORT_REG_INEN_SET); + } else { + writew(BIT(offset), port->regs + ADSP_PORT_REG_INEN_CLEAR); + writew(BIT(offset), port->regs + ADSP_PORT_REG_DATA_CLEAR); + writew(BIT(offset), port->regs + ADSP_PORT_REG_DIR_SET); + } + } else { + if (value) + writew(BIT(offset), port->regs + ADSP_PORT_REG_DATA_SET); + else + writew(BIT(offset), port->regs + ADSP_PORT_REG_DATA_CLEAR); + } +} + +static int adsp_gpio_get_value(struct gpio_chip *chip, unsigned int offset) +{ + struct adsp_gpio_port *port = to_adsp_gpio_port(chip); + + return !!(readw(port->regs + ADSP_PORT_REG_DATA) & BIT(offset)); +} + +static int adsp_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) +{ + struct adsp_gpio_port *port = to_adsp_gpio_port(chip); + irq_hw_number_t irq = offset + port->irq_offset; + int map = irq_find_mapping(port->irq_domain, irq); + + if (map) + return map; + + return irq_create_mapping(port->irq_domain, irq); +} + +static int adsp_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adsp_gpio_port *gpio; + + gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + gpio->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(gpio->regs)) + return PTR_ERR(gpio->regs); + + gpio->dev = dev; + + spin_lock_init(&gpio->lock); + + gpio->gpio.label = "adsp-gpio"; + gpio->gpio.direction_input = adsp_gpio_direction_input; + gpio->gpio.direction_output = adsp_gpio_direction_output; + gpio->gpio.get = adsp_gpio_get_value; + gpio->gpio.set = adsp_gpio_set_value; + gpio->gpio.to_irq = adsp_gpio_to_irq; + gpio->gpio.request = gpiochip_generic_request; + gpio->gpio.free = gpiochip_generic_free; + gpio->gpio.ngpio = ADSP_PORT_NGPIO; + gpio->gpio.parent = dev; + gpio->gpio.base = -1; + return devm_gpiochip_add_data(dev, &gpio->gpio, gpio); +} + +static const struct of_device_id adsp_gpio_of_match[] = { + { .compatible = "adi,sc5xx-gpio", }, + { }, +}; +MODULE_DEVICE_TABLE(of, adsp_gpio_of_match); + +static struct platform_driver adsp_gpio_driver = { + .driver = { + .name = "sc5xx-gpio", + .of_match_table = adsp_gpio_of_match, + }, + .probe = adsp_gpio_probe, +}; + +module_platform_driver(adsp_gpio_driver); + +MODULE_DESCRIPTION("Analog Devices GPIO driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Greg Malysa "); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 2254abda5c46c9..0c4136db4406ee 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -519,6 +519,14 @@ config I2C_BRCMSTB If you do not need I2C interface, say N. +config I2C_ADI_SC5XX + tristate "ADI SC5XX I2C support" + depends on ARCH_SC59X_64 + help + This is the I2C bus driver for ADI on-chip TWI interface. + + This driver can also be built as a module. + config I2C_CADENCE tristate "Cadence I2C Controller" depends on ARCH_ZYNQ || ARM64 || XTENSA || RISCV || COMPILE_TEST diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index ecc07c50f2a0fe..ec47df70d61432 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_I2C_HYDRA) += i2c-hydra.o obj-$(CONFIG_I2C_POWERMAC) += i2c-powermac.o # Embedded system I2C/SMBus host controller drivers +obj-$(CONFIG_I2C_ADI_SC5XX) += i2c-adi-sc5xx.o obj-$(CONFIG_I2C_ALTERA) += i2c-altera.o obj-$(CONFIG_I2C_AMD_MP2) += i2c-amd-mp2-pci.o i2c-amd-mp2-plat.o obj-$(CONFIG_I2C_ASPEED) += i2c-aspeed.o diff --git a/drivers/i2c/busses/i2c-adi-sc5xx.c b/drivers/i2c/busses/i2c-adi-sc5xx.c new file mode 100644 index 00000000000000..6e8bc521056436 --- /dev/null +++ b/drivers/i2c/busses/i2c-adi-sc5xx.c @@ -0,0 +1,863 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADI On-Chip Two Wire Interface Driver + * + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TWI_PRESCALE Masks */ +#define TWI_ENA 0x0080 /* TWI Enable */ + +/* TWI_MASTER_CTL Masks */ +#define MEN 0x0001 /* Master Mode Enable */ +#define MDIR 0x0004 /* Master Transmit Direction (RX/TX*) */ +#define FAST 0x0008 /* Use Fast Mode Timing Specs */ +#define STOP 0x0010 /* Issue Stop Condition */ +#define RSTART 0x0020 /* Repeat Start or Stop* At End Of Transfer */ +#define SDAOVR 0x4000 /* Serial Data Override */ +#define SCLOVR 0x8000 /* Serial Clock Override */ + +/* TWI_MASTER_STAT Masks */ +#define LOSTARB 0x0002 /* Lost Arbitration Indicator (Xfer Aborted) */ +#define ANAK 0x0004 /* Address Not Acknowledged */ +#define DNAK 0x0008 /* Data Not Acknowledged */ +#define BUFRDERR 0x0010 /* Buffer Read Error */ +#define BUFWRERR 0x0020 /* Buffer Write Error */ +#define SDASEN 0x0040 /* Serial Data Sense */ +#define BUSBUSY 0x0100 /* Bus Busy Indicator */ + +/* TWI_INT_SRC and TWI_INT_ENABLE Masks */ +#define MCOMP 0x0010 /* Master Transfer Complete */ +#define MERR 0x0020 /* Master Transfer Error */ +#define XMTSERV 0x0040 /* Transmit FIFO Service */ +#define RCVSERV 0x0080 /* Receive FIFO Service */ + +/* TWI_FIFO_STAT Masks */ +#define XMTSTAT 0x0003 /* Transmit FIFO Status */ +#define XMT_FULL 0x0003 /* Transmit FIFO Full (2 Bytes To Write) */ +#define RCVSTAT 0x000C /* Receive FIFO Status */ + +/* SMBus mode*/ +#define TWI_I2C_MODE_STANDARD 1 +#define TWI_I2C_MODE_STANDARDSUB 2 +#define TWI_I2C_MODE_COMBINED 3 +#define TWI_I2C_MODE_REPEAT 4 + +/* + * ADI I2C/TWI registers layout + */ +#define ADI_I2C_CLKDIV_REG 0x0 +#define ADI_I2C_CTL_REG 0x4 +#define ADI_I2C_SLVCTTL_REG 0x8 +#define ADI_I2C_SLVSTAT_REG 0xC +#define ADI_I2C_SLVADDR_REG 0x10 +#define ADI_I2C_MSTRCTRL_REG 0x14 +#define ADI_I2C_MSTRSTAT_REG 0x18 +#define ADI_I2C_MSTRADDR_REG 0x1C +#define ADI_I2C_ISTAT_REG 0x20 +#define ADI_I2C_IMSK_REG 0x24 +#define ADI_I2C_FIFOCTL_REG 0x28 +#define ADI_I2C_FIFOSTAT_REG 0x2C +#define ADI_I2C_TXDATA8_REG 0x80 +#define ADI_I2C_TXDATA16_REG 0x84 +#define ADI_I2C_RXDATA8_REG 0x88 +#define ADI_I2C_RXDATA16_REG 0x8C + +struct adi_twi_iface { + int irq; + //spinlock_t lock; + char read_write; + u8 command; + u8 *trans_ptr; + int read_num; + int write_num; + int cur_mode; + int manual_stop; + int result; + unsigned int twi_clk; + struct i2c_adapter adap; + struct completion complete; + struct i2c_msg *pmsg; + int msg_num; + int cur_msg; + u16 saved_clkdiv; + u16 saved_control; + void __iomem *reg_base; + struct clk *sclk; +}; + +static void adi_twi_handle_interrupt(struct adi_twi_iface *i2c, + unsigned short twi_int_status, + bool polling) +{ + u16 write_value; + unsigned short mast_stat = readw(&i2c->reg_base + ADI_I2C_MSTRSTAT_REG); + + if (twi_int_status & XMTSERV) { + if (i2c->write_num <= 0) { + /* start receive immediately after complete sending in + * combine mode. + */ + if (i2c->cur_mode == TWI_I2C_MODE_COMBINED) { + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) | MDIR; + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else if (i2c->manual_stop) { + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) | STOP; + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else if (i2c->cur_mode == TWI_I2C_MODE_REPEAT && + i2c->cur_msg + 1 < i2c->msg_num) { + if (i2c->pmsg[i2c->cur_msg + 1].flags & I2C_M_RD) { + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) + | MDIR; + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else { + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) + & ~MDIR; + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } + } + } + /* Transmit next data */ + while (i2c->write_num > 0 && + (readw(i2c->reg_base + ADI_I2C_FIFOSTAT_REG) & XMTSTAT) != XMT_FULL) { + writew(*(i2c->trans_ptr++), &i2c->reg_base + ADI_I2C_TXDATA8_REG); + i2c->write_num--; + } + } + if (twi_int_status & RCVSERV) { + while (i2c->read_num > 0 && + (readw(i2c->reg_base + ADI_I2C_FIFOSTAT_REG) & RCVSTAT)) { + /* Receive next data */ + *i2c->trans_ptr = readw(&i2c->reg_base + ADI_I2C_RXDATA8_REG); + if (i2c->cur_mode == TWI_I2C_MODE_COMBINED) { + /* Change combine mode into sub mode after + * read first data. + */ + i2c->cur_mode = TWI_I2C_MODE_STANDARDSUB; + /* Get read number from first byte in block + * combine mode. + */ + if (i2c->read_num == 1 && i2c->manual_stop) + i2c->read_num = *i2c->trans_ptr + 1; + } + i2c->trans_ptr++; + i2c->read_num--; + } + + if (i2c->read_num == 0) { + if (i2c->manual_stop) { + /* Temporary workaround to avoid possible bus stall - + * Flush FIFO before issuing the STOP condition + */ + readw(&i2c->reg_base + ADI_I2C_RXDATA16_REG); + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) | STOP; + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else if (i2c->cur_mode == TWI_I2C_MODE_REPEAT && + i2c->cur_msg + 1 < i2c->msg_num) { + if (i2c->pmsg[i2c->cur_msg + 1].flags & I2C_M_RD) { + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) | + MDIR; + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else { + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) & + ~MDIR; + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } + } + } + } + if (twi_int_status & MERR) { + writew(0, &i2c->reg_base + ADI_I2C_IMSK_REG); + writew(0x3e, &i2c->reg_base + ADI_I2C_MSTRSTAT_REG); + writew(0, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + i2c->result = -EIO; + + if (mast_stat & LOSTARB) + dev_dbg(&i2c->adap.dev, "Lost Arbitration\n"); + if (mast_stat & ANAK) + dev_dbg(&i2c->adap.dev, "Address Not Acknowledged\n"); + if (mast_stat & DNAK) + dev_dbg(&i2c->adap.dev, "Data Not Acknowledged\n"); + if (mast_stat & BUFRDERR) + dev_dbg(&i2c->adap.dev, "Buffer Read Error\n"); + if (mast_stat & BUFWRERR) + dev_dbg(&i2c->adap.dev, "Buffer Write Error\n"); + + /* Faulty slave devices, may drive SDA low after a transfer + * finishes. To release the bus this code generates up to 9 + * extra clocks until SDA is released. + */ + + if (readw(&i2c->reg_base + ADI_I2C_MSTRSTAT_REG) & SDASEN) { + int cnt = 9; + + do { + writew(SCLOVR, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + udelay(6); + writew(0, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + udelay(6); + } while ((readw(&i2c->reg_base + ADI_I2C_MSTRSTAT_REG) & SDASEN) && cnt--); + + writew(SDAOVR | SCLOVR, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + udelay(6); + writew(SDAOVR, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + udelay(6); + writew(0, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } + + /* If it is a quick transfer, only address without data, + * not an err, return 1. + */ + if (i2c->cur_mode == TWI_I2C_MODE_STANDARD && !i2c->trans_ptr && + (twi_int_status & MCOMP) && (mast_stat & DNAK)) + i2c->result = 1; + + if (!polling) + complete(&i2c->complete); + return; + } + if (twi_int_status & MCOMP) { + if (twi_int_status & (XMTSERV | RCVSERV) && + (readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) & MEN) == 0 && + (i2c->cur_mode == TWI_I2C_MODE_REPEAT || + i2c->cur_mode == TWI_I2C_MODE_COMBINED)) { + i2c->result = -1; + writew(0, &i2c->reg_base + ADI_I2C_IMSK_REG); + writew(0, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else if (i2c->cur_mode == TWI_I2C_MODE_COMBINED) { + if (i2c->read_num == 0) { + /* set the read number to 1 and ask for manual + * stop in block combine mode + */ + i2c->read_num = 1; + i2c->manual_stop = 1; + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) | (0xff << 6); + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else { + /* set the readd number in other + * combine mode. + */ + write_value = (readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) + & (~(0xff << 6))) | (i2c->read_num << 6); + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } + /* remove restart bit and enable master receive */ + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) & ~RSTART; + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else if (i2c->cur_mode == TWI_I2C_MODE_REPEAT && + i2c->cur_msg + 1 < i2c->msg_num) { + i2c->cur_msg++; + i2c->trans_ptr = i2c->pmsg[i2c->cur_msg].buf; + i2c->write_num = i2c->pmsg[i2c->cur_msg].len; + i2c->read_num = i2c->pmsg[i2c->cur_msg].len; + /* Set Transmit device address */ + writew(i2c->pmsg[i2c->cur_msg].addr, &i2c->reg_base + ADI_I2C_MSTRADDR_REG); + if (i2c->pmsg[i2c->cur_msg].flags & I2C_M_RD) { + i2c->read_write = I2C_SMBUS_READ; + } else { + i2c->read_write = I2C_SMBUS_WRITE; + /* Transmit first data */ + if (i2c->write_num > 0) { + writew(*(i2c->trans_ptr++), + &i2c->reg_base + ADI_I2C_TXDATA8_REG); + i2c->write_num--; + } + } + + if (i2c->pmsg[i2c->cur_msg].len <= 255) { + write_value = (readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) + & (~(0xff << 6))) + | (i2c->pmsg[i2c->cur_msg].len << 6); + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + i2c->manual_stop = 0; + } else { + write_value = (readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) + | (0xff << 6)); + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + i2c->manual_stop = 1; + } + + /* remove restart bit before last message */ + if (i2c->cur_msg + 1 == i2c->msg_num) { + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) & ~RSTART; + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } + + } else { + i2c->result = 1; + writew(0, &i2c->reg_base + ADI_I2C_IMSK_REG); + writew(0, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } + if (!polling) + complete(&i2c->complete); + } +} + +/* Interrupt handler */ +static irqreturn_t adi_twi_handle_all_interrupts(struct adi_twi_iface *i2c, + bool polling) +{ + unsigned short twi_int_status; + + twi_int_status = readw(&i2c->reg_base + ADI_I2C_ISTAT_REG); + if (!twi_int_status) + return IRQ_NONE; + writew(twi_int_status, &i2c->reg_base + ADI_I2C_ISTAT_REG); + adi_twi_handle_interrupt(i2c, twi_int_status, polling); + + return IRQ_HANDLED; +} + +static irqreturn_t adi_twi_interrupt_entry(int irq, void *dev_id) +{ + struct adi_twi_iface *i2c = dev_id; + + return adi_twi_handle_all_interrupts(i2c, false); +} + +/* + * One i2c master transfer + */ +static int adi_twi_do_master_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num, bool polling) +{ + struct adi_twi_iface *i2c = adap->algo_data; + struct i2c_msg *pmsg; + int ret = 0; + u16 write_value; + + if (!(readw(&i2c->reg_base + ADI_I2C_CTL_REG) & TWI_ENA)) + return -ENXIO; + + if (readw(&i2c->reg_base + ADI_I2C_MSTRSTAT_REG) & BUSBUSY) + return -EAGAIN; + + i2c->pmsg = msgs; + i2c->msg_num = num; + i2c->cur_msg = 0; + + pmsg = &msgs[0]; + if (pmsg->flags & I2C_M_TEN) { + dev_err(&adap->dev, "10 bits addr not supported!\n"); + return -EINVAL; + } + + if (i2c->msg_num > 1) + i2c->cur_mode = TWI_I2C_MODE_REPEAT; + i2c->manual_stop = 0; + i2c->trans_ptr = pmsg->buf; + i2c->write_num = pmsg->len; + i2c->read_num = pmsg->len; + i2c->result = 0; + if (!polling) + init_completion(&i2c->complete); + /* Set Transmit device address */ + writew(pmsg->addr, &i2c->reg_base + ADI_I2C_MSTRADDR_REG); + + /* FIFO Initiation. Data in FIFO should be + * discarded before start a new operation. + */ + writew(0x3, &i2c->reg_base + ADI_I2C_FIFOCTL_REG); + writew(0, &i2c->reg_base + ADI_I2C_FIFOCTL_REG); + + if (pmsg->flags & I2C_M_RD) { + i2c->read_write = I2C_SMBUS_READ; + } else { + i2c->read_write = I2C_SMBUS_WRITE; + /* Transmit first data */ + if (i2c->write_num > 0) { + writew(*(i2c->trans_ptr++), &i2c->reg_base + ADI_I2C_TXDATA8_REG); + i2c->write_num--; + } + } + + /* clear int stat */ + writew(MERR | MCOMP | XMTSERV | RCVSERV, &i2c->reg_base + ADI_I2C_ISTAT_REG); + + /* Interrupt mask . Enable XMT, RCV interrupt */ + writew(MCOMP | MERR | RCVSERV | XMTSERV, &i2c->reg_base + ADI_I2C_IMSK_REG); + + if (pmsg->len <= 255) { + writew(pmsg->len << 6, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else { + writew(0xff << 6, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + i2c->manual_stop = 1; + } + + /* Master enable */ + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) | + MEN | + ((i2c->msg_num > 1) ? RSTART : 0) | + ((i2c->read_write == I2C_SMBUS_READ) ? MDIR : 0) | + ((i2c->twi_clk > 100) ? FAST : 0); + + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + + if (polling) { + int timeout = 50000; + + for (;;) { + irqreturn_t handled = adi_twi_handle_all_interrupts(i2c, true); + + if (handled == IRQ_HANDLED && i2c->result) + break; + if (--timeout == 0) { + i2c->result = -1; + dev_err(&adap->dev, "master polling timeout"); + break; + } + } + } else { /* interrupt driven */ + while (!i2c->result) { + if (!wait_for_completion_timeout(&i2c->complete, adap->timeout)) { + i2c->result = -1; + dev_err(&adap->dev, "master transfer timeout"); + } + } + } + + if (i2c->result == 1) + ret = i2c->cur_msg + 1; + else + ret = i2c->result; + + return ret; +} + +/* + * Generic i2c master transfer entrypoint + */ +static int adi_twi_master_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + return adi_twi_do_master_xfer(adap, msgs, num, false); +} + +static int adi_twi_master_xfer_atomic(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + return adi_twi_do_master_xfer(adap, msgs, num, true); +} + +/* + * One I2C SMBus transfer + */ +static int adi_twi_do_smbus_xfer(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data, + bool polling) +{ + struct adi_twi_iface *i2c = adap->algo_data; + int ret = 0; + u16 write_value; + + if (!(readw(&i2c->reg_base + ADI_I2C_CTL_REG) & TWI_ENA)) + return -ENXIO; + + if (readw(&i2c->reg_base + ADI_I2C_MSTRSTAT_REG) & BUSBUSY) + return -EAGAIN; + + i2c->write_num = 0; + i2c->read_num = 0; + + /* Prepare datas & select mode */ + switch (size) { + case I2C_SMBUS_QUICK: + i2c->trans_ptr = NULL; + i2c->cur_mode = TWI_I2C_MODE_STANDARD; + break; + case I2C_SMBUS_BYTE: + if (!data) { + i2c->trans_ptr = NULL; + } else { + if (read_write == I2C_SMBUS_READ) + i2c->read_num = 1; + else + i2c->write_num = 1; + i2c->trans_ptr = &data->byte; + } + i2c->cur_mode = TWI_I2C_MODE_STANDARD; + break; + case I2C_SMBUS_BYTE_DATA: + if (read_write == I2C_SMBUS_READ) { + i2c->read_num = 1; + i2c->cur_mode = TWI_I2C_MODE_COMBINED; + } else { + i2c->write_num = 1; + i2c->cur_mode = TWI_I2C_MODE_STANDARDSUB; + } + i2c->trans_ptr = &data->byte; + break; + case I2C_SMBUS_WORD_DATA: + if (read_write == I2C_SMBUS_READ) { + i2c->read_num = 2; + i2c->cur_mode = TWI_I2C_MODE_COMBINED; + } else { + i2c->write_num = 2; + i2c->cur_mode = TWI_I2C_MODE_STANDARDSUB; + } + i2c->trans_ptr = (u8 *)&data->word; + break; + case I2C_SMBUS_PROC_CALL: + i2c->write_num = 2; + i2c->read_num = 2; + i2c->cur_mode = TWI_I2C_MODE_COMBINED; + i2c->trans_ptr = (u8 *)&data->word; + break; + case I2C_SMBUS_BLOCK_DATA: + if (read_write == I2C_SMBUS_READ) { + i2c->read_num = 0; + i2c->cur_mode = TWI_I2C_MODE_COMBINED; + } else { + i2c->write_num = data->block[0] + 1; + i2c->cur_mode = TWI_I2C_MODE_STANDARDSUB; + } + i2c->trans_ptr = data->block; + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + if (read_write == I2C_SMBUS_READ) { + i2c->read_num = data->block[0]; + i2c->cur_mode = TWI_I2C_MODE_COMBINED; + } else { + i2c->write_num = data->block[0]; + i2c->cur_mode = TWI_I2C_MODE_STANDARDSUB; + } + i2c->trans_ptr = (u8 *)&data->block[1]; + break; + default: + return -1; + } + + i2c->result = 0; + i2c->manual_stop = 0; + i2c->read_write = read_write; + i2c->command = command; + if (!polling) + init_completion(&i2c->complete); + + /* FIFO Initiation. Data in FIFO should be discarded before + * start a new operation. + */ + writew(0x3, &i2c->reg_base + ADI_I2C_FIFOCTL_REG); + writew(0, &i2c->reg_base + ADI_I2C_FIFOCTL_REG); + + /* clear int stat */ + writew(MERR | MCOMP | XMTSERV | RCVSERV, &i2c->reg_base + ADI_I2C_ISTAT_REG); + + /* Set Transmit device address */ + writew(addr, &i2c->reg_base + ADI_I2C_MSTRADDR_REG); + + switch (i2c->cur_mode) { + case TWI_I2C_MODE_STANDARDSUB: + writew(i2c->command, &i2c->reg_base + ADI_I2C_TXDATA8_REG); + + write_value = MCOMP | MERR; + if (i2c->read_write == I2C_SMBUS_READ) + write_value |= RCVSERV; + else + write_value |= XMTSERV; + + writew(write_value, &i2c->reg_base + ADI_I2C_IMSK_REG); + + if (i2c->write_num + 1 <= 255) { + writew((i2c->write_num + 1) << 6, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else { + writew(0xff << 6, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + i2c->manual_stop = 1; + } + /* Master enable */ + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) | MEN; + if (i2c->twi_clk > 100) + write_value |= FAST; + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + break; + case TWI_I2C_MODE_COMBINED: + writew(i2c->command, &i2c->reg_base + ADI_I2C_TXDATA8_REG); + writew(MCOMP | MERR | RCVSERV | XMTSERV, &i2c->reg_base + ADI_I2C_IMSK_REG); + + if (i2c->write_num > 0) + writew((i2c->write_num + 1) << 6, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + else + writew(0x1 << 6, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + /* Master enable */ + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) | MEN | RSTART; + if (i2c->twi_clk > 100) + write_value |= FAST; + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + break; + default: + writew(0, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + if (size != I2C_SMBUS_QUICK) { + /* Don't access xmit data register when this is a + * read operation. + */ + if (i2c->read_write != I2C_SMBUS_READ) { + if (i2c->write_num > 0) { + writew(*(i2c->trans_ptr++), + &i2c->reg_base + ADI_I2C_TXDATA8_REG); + if (i2c->write_num <= 255) { + writew(i2c->write_num << 6, + &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else { + writew(0xff << 6, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + i2c->manual_stop = 1; + } + i2c->write_num--; + } else { + writew(i2c->command, &i2c->reg_base + ADI_I2C_TXDATA8_REG); + writew(1 << 6, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } + } else { + if (i2c->read_num > 0 && i2c->read_num <= 255) { + writew(i2c->read_num << 6, + &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + } else if (i2c->read_num > 255) { + writew(0xff << 6, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + i2c->manual_stop = 1; + } else { + break; + } + } + } + write_value = MCOMP | MERR; + if (i2c->read_write == I2C_SMBUS_READ) + write_value |= RCVSERV; + else + write_value |= XMTSERV; + writew(write_value, &i2c->reg_base + ADI_I2C_IMSK_REG); + + /* Master enable */ + write_value = readw(&i2c->reg_base + ADI_I2C_MSTRCTRL_REG) | MEN | + ((i2c->read_write == I2C_SMBUS_READ) ? MDIR : 0) | + ((i2c->twi_clk > 100) ? FAST : 0); + writew(write_value, &i2c->reg_base + ADI_I2C_MSTRCTRL_REG); + break; + } + + if (polling) { + int timeout = 50000; + + for (;;) { + irqreturn_t handled = adi_twi_handle_all_interrupts(i2c, true); + + if (handled == IRQ_HANDLED && i2c->result) + break; + if (--timeout == 0) { + i2c->result = -1; + dev_err(&adap->dev, "smbus polling timeout"); + break; + } + } + } else { /* interrupt driven */ + while (!i2c->result) { + if (!wait_for_completion_timeout(&i2c->complete, adap->timeout)) { + i2c->result = -1; + dev_err(&adap->dev, "smbus transfer timeout"); + } + } + } + + ret = (i2c->result >= 0) ? 0 : -1; + + return ret; +} + +/* + * Generic I2C SMBus transfer entrypoint + */ +static int adi_twi_smbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags, + char read_write, u8 command, int size, union i2c_smbus_data *data) +{ + return adi_twi_do_smbus_xfer(adap, addr, flags, + read_write, command, size, data, false); +} + +static int adi_twi_smbus_xfer_atomic(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data) +{ + return adi_twi_do_smbus_xfer(adap, addr, flags, + read_write, command, size, data, true); +} + +/* + * Return what the adapter supports + */ +static u32 adi_twi_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_PROC_CALL | + I2C_FUNC_I2C | I2C_FUNC_SMBUS_I2C_BLOCK; +} + +static const struct i2c_algorithm adi_twi_algorithm = { + .master_xfer = adi_twi_master_xfer, + .master_xfer_atomic = adi_twi_master_xfer_atomic, + .smbus_xfer = adi_twi_smbus_xfer, + .smbus_xfer_atomic = adi_twi_smbus_xfer_atomic, + .functionality = adi_twi_functionality, +}; + +static int i2c_adi_twi_suspend(struct device *dev) +{ + struct adi_twi_iface *i2c = dev_get_drvdata(dev); + + i2c->saved_clkdiv = readw(&i2c->reg_base + ADI_I2C_CLKDIV_REG); + i2c->saved_control = readw(&i2c->reg_base + ADI_I2C_CTL_REG); + + free_irq(i2c->irq, i2c); + + /* Disable TWI */ + writew(i2c->saved_control & ~TWI_ENA, &i2c->reg_base + ADI_I2C_CTL_REG); + + return 0; +} + +static int i2c_adi_twi_resume(struct device *dev) +{ + struct adi_twi_iface *i2c = dev_get_drvdata(dev); + + int ret = request_irq(i2c->irq, adi_twi_interrupt_entry, + 0, to_platform_device(dev)->name, i2c); + if (ret) { + dev_err(dev, "Can't get IRQ %d !\n", i2c->irq); + return -ENODEV; + } + + /* Resume TWI interface clock as specified */ + writew(i2c->saved_clkdiv, &i2c->reg_base + ADI_I2C_CLKDIV_REG); + + /* Resume TWI */ + writew(i2c->saved_control, &i2c->reg_base + ADI_I2C_CTL_REG); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(i2c_adi_twi_pm, i2c_adi_twi_suspend, i2c_adi_twi_resume); + +static const struct of_device_id adi_twi_of_match[] = { + { + .compatible = "adi,sc5xx-i2c", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, adi_twi_of_match); + +static int i2c_adi_twi_probe(struct platform_device *pdev) +{ + struct adi_twi_iface *i2c; + struct i2c_adapter *adap; + struct device_node *node = pdev->dev.of_node; + int ret; + unsigned int clkhilow; + u16 write_value; + + i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + i2c->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(i2c->reg_base)) + return PTR_ERR(i2c->reg_base); + + i2c->sclk = devm_clk_get(&pdev->dev, "sclk0"); + if (IS_ERR(i2c->sclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2c->sclk), "cannot get sclk0"); + + /* Set TWI internal clock as 10MHz */ + ret = clk_prepare_enable(i2c->sclk); + if (ret) + return ret; + + i2c->irq = platform_get_irq(pdev, 0); + if (i2c->irq < 0) + return i2c->irq; + + ret = devm_request_irq(&pdev->dev, i2c->irq, adi_twi_interrupt_entry, + 0, pdev->name, i2c); + if (ret) + return ret; + + if (of_property_read_u32(node, "clock-khz", &i2c->twi_clk)) + i2c->twi_clk = 50; + + adap = &i2c->adap; + adap->nr = pdev->id; + strscpy(adap->name, pdev->name, sizeof(adap->name)); + adap->algo = &adi_twi_algorithm; + adap->algo_data = i2c; + adap->class = I2C_CLASS_DEPRECATED; + adap->dev.parent = &pdev->dev; + adap->dev.of_node = node; + adap->timeout = 5 * HZ; + adap->retries = 3; + + write_value = ((clk_get_rate(i2c->sclk) / 1000 / 1000 + 5) / 10) & 0x7F; + writew(write_value, &i2c->reg_base + ADI_I2C_CTL_REG); + + /* + * We will not end up with a CLKDIV=0 because no one will specify + * 20kHz SCL or less in Kconfig now. (5 * 1000 / 20 = 250) + */ + clkhilow = ((10 * 1000 / i2c->twi_clk) + 1) / 2; + + /* Set Twi interface clock as specified */ + write_value = (clkhilow << 8) | clkhilow; + writew(write_value, &i2c->reg_base + ADI_I2C_CLKDIV_REG); + + /* Enable TWI */ + write_value = readw(&i2c->reg_base + ADI_I2C_CTL_REG) | TWI_ENA; + writew(write_value, &i2c->reg_base + ADI_I2C_CTL_REG); + + ret = i2c_add_numbered_adapter(adap); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, i2c); + + return 0; +} + +static void i2c_adi_twi_remove(struct platform_device *pdev) +{ + struct adi_twi_iface *i2c = platform_get_drvdata(pdev); + + clk_disable_unprepare(i2c->sclk); + i2c_del_adapter(&i2c->adap); +} + +static struct platform_driver i2c_adi_twi_driver = { + .probe = i2c_adi_twi_probe, + .remove = i2c_adi_twi_remove, + .driver = { + .name = "sc5xx-i2c", + .pm = &i2c_adi_twi_pm, + .of_match_table = of_match_ptr(adi_twi_of_match), + }, +}; + +module_platform_driver(i2c_adi_twi_driver); + +MODULE_AUTHOR("Bryan Wu, Sonic Zhang"); +MODULE_DESCRIPTION("ADI on-chip I2C TWI Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 354536de564b67..5507187ab280ff 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -472,6 +472,17 @@ config PINCTRL_ROCKCHIP help This support pinctrl and GPIO driver for Rockchip SoCs. +config PINCTRL_SC5XX + bool "ADSP-SC5XX pinctrl driver" + default y if ARCH_SC59X_64 + depends on OF + select PINMUX + select GPIOLIB + select GENERIC_PINCONF + help + Say Y here to enable the ADSP-SC5XX pinctrl driver. This is required for + correct peripheral functionality on the SoC. + config PINCTRL_SCMI tristate "Pinctrl driver using SCMI protocol interface" depends on ARM_SCMI_PROTOCOL || COMPILE_TEST diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 97823f52b972a3..b71a74108fe0db 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_PINCTRL_PIC32) += pinctrl-pic32.o obj-$(CONFIG_PINCTRL_PISTACHIO) += pinctrl-pistachio.o obj-$(CONFIG_PINCTRL_RK805) += pinctrl-rk805.o obj-$(CONFIG_PINCTRL_ROCKCHIP) += pinctrl-rockchip.o +obj-$(CONFIG_PINCTRL_SC5XX) += pinctrl-sc5xx.o obj-$(CONFIG_PINCTRL_SCMI) += pinctrl-scmi.o obj-$(CONFIG_PINCTRL_SINGLE) += pinctrl-single.o obj-$(CONFIG_PINCTRL_ST) += pinctrl-st.o diff --git a/drivers/pinctrl/pinctrl-sc5xx.c b/drivers/pinctrl/pinctrl-sc5xx.c new file mode 100644 index 00000000000000..fea4c985dd89d3 --- /dev/null +++ b/drivers/pinctrl/pinctrl-sc5xx.c @@ -0,0 +1,917 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices ADSP family pinctrl driver. + * + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "pinconf.h" +#include "pinctrl-utils.h" + +/* Convert from pinmux constants in device tree to actual settings */ +#define ADSP_PINMUX_PIN(p) ((p & 0xffffff00) >> 8) +#define ADSP_PINMUX_FUNC(p) (p & 0xff) + +/* Details of the PORT_MUX register */ +#define ADSP_PORT_PORT_MUX_BITS 2 +#define ADSP_PORT_PORT_MUX_MASK GENMASK(1, 0) + +/* Number of pin alternate functions, see pin_functions array */ +#define ADSP_NUMBER_OF_PIN_FUNCTIONS ARRAY_SIZE(pin_functions) + +/* Information for drive strength registers */ +#define ADSP_PADS_DS_BITS 3 +#define ADSP_PADS_DS_PINS_PER_REG 8 +#define ADSP_PADS_DS_HIGH 2 +#define ADSP_PADS_DS_LOW 1 + +/* Information for pull up/pull down enable registers */ +#define ADSP_PADS_PUD_PINS_PER_REG 16 + +#define ADSP_PADS_REG_PCFG0 0x04 +#define ADSP_PADS_REG_PCFG1 0x08 +/* Convert from pin number (e.g. 0-143) to drive strength register offset */ +#define ADSP_PADS_PORTx_DS(p) (0x0c + 0x04*(p/ADSP_PADS_DS_PINS_PER_REG)) +#define ADSP_PADS_NONPORTS_DS 0x50 +/* Convert from pin number to pull up enable register offset */ +#define ADSP_PADS_PORTx_PUE(p) (0x98 + 0x04*(p/ADSP_PADS_PUD_PINS_PER_REG)) +/* Convert from pin number to pull down enable register offset */ +#define ADSP_PADS_PORTx_PDE(p) (0xc4 + 0x04*(p/ADSP_PADS_PUD_PINS_PER_REG)) + +/* Non GPIO PORT drive strength settings */ +#define ADSP_NONPORTS_DS_CKOUT 0 +#define ADSP_NONPORTS_DS_RESOUTB 1 +#define ADSP_NONPORTS_DS_FAULTB 2 +#define ADSP_NONPORTS_DS_LP1CK 3 +#define ADSP_NONPORTS_DS_LP0CK 4 +#define ADSP_NONPORTS_DS_OSPI 5 + +/* DAI pad configuration offsets */ +#define ADSP_PADS_REG_DAI0_0_DS 0x78 +#define ADSP_PADS_REG_DAI0_1_DS 0x7c +#define ADSP_PADS_REG_DAI1_0_DS 0x80 +#define ADSP_PADS_REG_DAI1_1_DS 0x84 + +#define ADSP_PADS_REG_DAI0_PUE 0xbc +#define ADSP_PADS_REG_DAI1_PUE 0xc0 +#define ADSP_PADS_REG_DAI0_PDE 0xfc +#define ADSP_PADS_REG_DAI1_PDE 0x100 + +/* + * Represents a function setting for pins, controls the mux modes essentially + */ +struct adsp_pin_function { + const char *name; + /* 0 for gpio, 1-4 for alt functions 0-3 */ + uint8_t mode; +}; + +/* + * Available pin function settings in the pin mux for GPIO-associated pins + */ +static const struct adsp_pin_function pin_functions[] = { + { + .name = "gpio", + .mode = 0, + }, { + .name = "alt0", + .mode = 1, + }, { + .name = "alt1", + .mode = 2, + }, { + .name = "alt2", + .mode = 3, + }, { + .name = "alt3", + .mode = 4, + } +}; + +/* + * One pinctrl instance per chip, unifies the interface to the port mux and pad + * conf registers in the PORT instances + * @todo pads registers should be routed through system configuration abstraction + * to remove the need for feature testing/listing "missing" registers here + */ +struct adsp_pinctrl { + struct device *dev; + struct pinctrl_dev *pin_dev; + void __iomem *regs; + const char **group_names; + unsigned int *pins; + spinlock_t lock; + size_t num_ports; + uint32_t *pin_counts; + uint32_t total_pins; + + /* Are the drive strength registers missing on this part? */ + bool ds_missing; + + /* Are the pull up/down enable registers missing on this part? */ + bool pude_missing; +}; + +/* + * Custom pinconf properties + */ +#define ADSP_PIN_CONFIG_TRU_TOGGLE (PIN_CONFIG_END+1) + +static const struct pinconf_generic_params adsp_custom_bindings[] = { + /* Configure this pin as a toggle pin which flip each time a trigger event + * is received by the pin controller from the TRU + */ + {"adi,tru-toggle", ADSP_PIN_CONFIG_TRU_TOGGLE, 0} +}; + +static const struct pin_config_item adsp_conf_items[] = { + PCONFDUMP(ADSP_PIN_CONFIG_TRU_TOGGLE, "tru-toggle", NULL, false), +}; + +/* does not need lock */ +static void adsp_set_pin_gpio(struct adsp_gpio_port *port, unsigned int offset, bool gpio) +{ + if (gpio) + writew(BIT(offset), port->regs + ADSP_PORT_REG_FER_CLEAR); + else + writew(BIT(offset), port->regs + ADSP_PORT_REG_FER_SET); +} + +/* + * Configure a pin either for gpio or an alternate function + */ +static void adsp_portmux_setup(struct adsp_gpio_port *port, unsigned int offset, + const struct adsp_pin_function *func) +{ + if (func->mode == 0) { + adsp_set_pin_gpio(port, offset, true); + } else { + unsigned long flags; + u32 val; + u32 f = (func->mode - 1) & ADSP_PORT_PORT_MUX_MASK; + + spin_lock_irqsave(&port->lock, flags); + + val = readl(port->regs + ADSP_PORT_REG_PORT_MUX); + val &= ~(ADSP_PORT_PORT_MUX_MASK << (ADSP_PORT_PORT_MUX_BITS * offset)); + val |= f << (ADSP_PORT_PORT_MUX_BITS * offset); + writel(val, port->regs + ADSP_PORT_REG_PORT_MUX); + + spin_unlock_irqrestore(&port->lock, flags); + + adsp_set_pin_gpio(port, offset, false); + } +} + +/* pin control operations */ +static int adsp_pinctrl_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct adsp_pinctrl *adsp_pinctrl = pinctrl_dev_get_drvdata(pctldev); + + return adsp_pinctrl->total_pins; +} + +static const char *adsp_pinctrl_get_group_name(struct pinctrl_dev *pctldev, + unsigned int selector) +{ + struct adsp_pinctrl *adsp_pinctrl = pinctrl_dev_get_drvdata(pctldev); + + return adsp_pinctrl->group_names[selector]; +} + +static int adsp_pinctrl_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector, + const unsigned int **pins, unsigned int *num_pins) +{ + struct adsp_pinctrl *adsp_pinctrl = pinctrl_dev_get_drvdata(pctldev); + *pins = &adsp_pinctrl->pins[selector]; + *num_pins = 1; + return 0; +} + +static int adsp_pinctrl_dt_subnode_to_map(struct pinctrl_dev *pctldev, + struct device_node *np, struct pinctrl_map **map, unsigned int *reserved_maps, + unsigned int *num_maps) +{ + struct adsp_pinctrl *adsp_pinctrl = pinctrl_dev_get_drvdata(pctldev); + const char *group; + unsigned long *configs; + unsigned int num_configs; + int num_pins; + unsigned int reserve = 0; + u32 array[2]; + int sz, i; + int ret; + + num_pins = of_property_count_u32_elems(np, "pinmux"); + if (num_pins <= 0) { + dev_err(adsp_pinctrl->dev, "Must have at least one `pinmux` entry in %pOFn.\n", + np); + return -EINVAL; + } + + ret = pinconf_generic_parse_dt_config(np, pctldev, &configs, &num_configs); + if (ret) + return ret; + + /* One configuration for the whole group, potentially */ + reserve = num_pins; + if (num_configs) + reserve = reserve * 2; + + ret = pinctrl_utils_reserve_map(pctldev, map, reserved_maps, num_maps, reserve); + if (ret) + goto exit; + + sz = of_property_read_variable_u32_array(np, "pinmux", array, 2, 2); + sz = (sz == -EINVAL) ? 0 : sz; /* Missing property is OK */ + if (sz < 0) + return dev_err_probe(adsp_pinctrl->dev, sz, "invalid pinmux\n"); + + + for (i = 0; i < sz; i += 2) { + u32 pin = array[i]; + u32 func = array[i + 1]; + + if (func >= ADSP_NUMBER_OF_PIN_FUNCTIONS) { + dev_err(adsp_pinctrl->dev, + "Function number %d is not available for pin %d in %pOFn.n\n", + func, pin, np); + goto exit; + } + + group = adsp_pinctrl->group_names[pin]; + ret = pinctrl_utils_add_map_mux(pctldev, map, reserved_maps, num_maps, + group, pin_functions[func].name); + if (ret) + goto exit; + + if (num_configs) { + ret = pinctrl_utils_add_map_configs(pctldev, map, reserved_maps, num_maps, + group, configs, num_configs, PIN_MAP_TYPE_CONFIGS_GROUP); + if (ret) + goto exit; + } + } + + ret = 0; +exit: + kfree(configs); + return ret; +} + +/** + * Handle device tree structures like: + * + * pinctrl_uart0_hwflow: uart0_hwflow_pins { + * pins_rxtx_ { + * pinmux = <1>, <2>; + * some-padconf-flag; + * }; + * pins_hwflow { + * pinmux = <3>, <4>; + * some-other-padconf-flag; + * }; + * }; + * + * where &pinctrl_uart0_hwflow is passed as an entry in pinctrl-0 on uart driver and + * enables all sub-pins at once + */ +static int adsp_pinctrl_dt_node_to_map(struct pinctrl_dev *pctldev, + struct device_node *np, struct pinctrl_map **map, unsigned int *num_maps) +{ + unsigned int reserved_maps; + struct device_node *child_np; + int ret; + + reserved_maps = 0; + *map = NULL; + *num_maps = 0; + + for_each_child_of_node(np, child_np) { + ret = adsp_pinctrl_dt_subnode_to_map(pctldev, child_np, map, + &reserved_maps, num_maps); + if (ret < 0) + goto exit; + } + return 0; + +exit: + pinctrl_utils_free_map(pctldev, *map, *num_maps); + return ret; +} + +static const struct pinctrl_ops adsp_pctlops = { + .get_groups_count = adsp_pinctrl_get_groups_count, + .get_group_name = adsp_pinctrl_get_group_name, + .get_group_pins = adsp_pinctrl_get_group_pins, + .dt_node_to_map = adsp_pinctrl_dt_node_to_map, + .dt_free_map = pinconf_generic_dt_free_map, +}; + +/* pin mux operations */ +static int adsp_pinmux_get_functions_count(struct pinctrl_dev *pctldev) +{ + return ADSP_NUMBER_OF_PIN_FUNCTIONS; +} + +static const char *adsp_pinmux_get_function_name(struct pinctrl_dev *pctldev, + unsigned int selector) +{ + return pin_functions[selector].name; +} + +static int adsp_pinmux_get_function_groups(struct pinctrl_dev *pctldev, + unsigned int selector, const char * const **groups, unsigned * const num_groups) +{ + struct adsp_pinctrl *adsp_pinctrl = pinctrl_dev_get_drvdata(pctldev); + + *groups = adsp_pinctrl->group_names; + *num_groups = adsp_pinctrl->total_pins; + return 0; +} + +/* Each group is exactly 1 pin and group id == pin id */ +static int adsp_pinmux_set_mux(struct pinctrl_dev *pctldev, unsigned int func, + unsigned int group) +{ + struct adsp_gpio_port *port; + struct pinctrl_gpio_range *range; + u32 offset; + + range = pinctrl_find_gpio_range_from_pin(pctldev, group); + if (!range || !range->gc) + return -EPROBE_DEFER; + + offset = group - range->pin_base; + + port = to_adsp_gpio_port(range->gc); + adsp_portmux_setup(port, offset, &pin_functions[func]); + + return 0; +} + +static int adsp_pinmux_request_gpio(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, unsigned int pin) +{ + struct adsp_gpio_port *port = to_adsp_gpio_port(range->gc); + u32 offset = pin - range->pin_base; + + adsp_set_pin_gpio(port, offset, true); + return 0; +} + +static void adsp_pinmux_release_gpio(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, unsigned int pin) +{ + struct adsp_gpio_port *port = to_adsp_gpio_port(range->gc); + u32 offset = pin - range->pin_base; + + adsp_set_pin_gpio(port, offset, false); +} + +static const struct pinmux_ops adsp_pmxops = { + .get_functions_count = adsp_pinmux_get_functions_count, + .get_function_name = adsp_pinmux_get_function_name, + .get_function_groups = adsp_pinmux_get_function_groups, + .set_mux = adsp_pinmux_set_mux, + .gpio_request_enable = adsp_pinmux_request_gpio, + .gpio_disable_free = adsp_pinmux_release_gpio, +}; + +/* pin configuration operations */ +static bool __adsp_pinconf_is_pue(struct adsp_pinctrl *p, unsigned int pin) +{ + u32 offset = ADSP_PADS_PORTx_PUE(pin); + u32 val, bit; + + if (p->pude_missing) + return 0; + + val = readl(p->regs + offset); + bit = BIT(pin & (ADSP_PADS_PUD_PINS_PER_REG-1)); + return !!(val & bit); +} + +static bool __adsp_pinconf_is_pde(struct adsp_pinctrl *p, unsigned int pin) +{ + u32 offset = ADSP_PADS_PORTx_PDE(pin); + u32 val, bit; + + if (p->pude_missing) + return 0; + + val = readl(p->regs + offset); + bit = BIT(pin & (ADSP_PADS_PUD_PINS_PER_REG-1)); + return !!(val & bit); +} + +static u32 __adsp_pinconf_get_ds(struct adsp_pinctrl *p, unsigned int pin) +{ + u32 offset = ADSP_PADS_PORTx_DS(pin); + u32 val, shift, mask; + + if (p->ds_missing) + return 0; + + val = readl(p->regs + offset); + shift = (pin & (ADSP_PADS_DS_PINS_PER_REG-1)) * ADSP_PADS_DS_BITS; + mask = GENMASK(ADSP_PADS_DS_BITS-1, 0) << shift; + val = val & mask; + + if (val == ADSP_PADS_DS_HIGH) + return 1; + return 0; +} + +/* seems we return -EINVAL for disabled static option, -ENOTSUPP for not supported, + * and otherwise the argument is included in config + */ +static int adsp_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *config) +{ + struct adsp_pinctrl *adsp_pinctrl = pinctrl_dev_get_drvdata(pctldev); + struct pinctrl_gpio_range *range; + struct adsp_gpio_port *port; + u32 offset, val; + u32 param = pinconf_to_config_param(*config); + u32 arg = 0; + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + if (__adsp_pinconf_is_pue(adsp_pinctrl, pin) || + __adsp_pinconf_is_pde(adsp_pinctrl, pin)) + return -EINVAL; + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + if (!__adsp_pinconf_is_pde(adsp_pinctrl, pin)) + return -EINVAL; + break; + case PIN_CONFIG_BIAS_PULL_UP: + if (!__adsp_pinconf_is_pue(adsp_pinctrl, pin)) + return -EINVAL; + break; + case PIN_CONFIG_DRIVE_STRENGTH: + arg = __adsp_pinconf_get_ds(adsp_pinctrl, pin); + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + range = pinctrl_find_gpio_range_from_pin_nolock(pctldev, pin); + offset = pin - range->pin_base; + port = to_adsp_gpio_port(range->gc); + + if (!(port->open_drain & BIT(offset))) + return -EINVAL; + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + range = pinctrl_find_gpio_range_from_pin_nolock(pctldev, pin); + offset = pin - range->pin_base; + port = to_adsp_gpio_port(range->gc); + + if (port->open_drain & BIT(offset)) + return -EINVAL; + break; + case ADSP_PIN_CONFIG_TRU_TOGGLE: + range = pinctrl_find_gpio_range_from_pin_nolock(pctldev, pin); + offset = pin - range->pin_base; + port = to_adsp_gpio_port(range->gc); + + val = readl(port->regs + ADSP_PORT_REG_TRIG_TGL); + if (!(val & BIT(offset))) + return -EINVAL; + break; + default: + return -EOPNOTSUPP; + } + + *config = pinconf_to_config_packed(param, arg); + + return 0; +} + +static void __adsp_pinconf_pue(struct adsp_pinctrl *p, unsigned int pin, bool state) +{ + u32 offset = ADSP_PADS_PORTx_PUE(pin); + u32 val, bit; + + if (p->pude_missing) { + dev_warn(p->dev, + "Pull Up Enable is not supported by this PADS HW (tried to set PUE for pin %d)\n", + pin); + return; + } + + val = readl(p->regs + offset); + bit = BIT(pin & (ADSP_PADS_PUD_PINS_PER_REG-1)); + + if (state) + writel(val | bit, p->regs + offset); + else + writel(val & ~bit, p->regs + offset); +} + +static void __adsp_pinconf_pde(struct adsp_pinctrl *p, unsigned int pin, bool state) +{ + u32 offset = ADSP_PADS_PORTx_PDE(pin); + u32 val, bit; + + if (p->pude_missing) { + dev_warn(p->dev, + "Pull Down Enable is not supported by this PADS HW (tried to set PDE for pin %d)\n", + pin); + return; + } + + val = readl(p->regs + offset); + bit = BIT(pin & (ADSP_PADS_PUD_PINS_PER_REG-1)); + + if (state) + writel(val | bit, p->regs + offset); + else + writel(val & ~bit, p->regs + offset); +} + +static void __adsp_pinconf_ds(struct adsp_pinctrl *p, unsigned int pin, bool high) +{ + u32 offset = ADSP_PADS_PORTx_DS(pin); + u32 val, shift, mask; + + if (p->ds_missing) { + dev_warn(p->dev, + "Drive strength is not supported by this PADS HW (tried to set drive strength for pin %d)\n", + pin); + return; + } + + val = readl(p->regs + offset); + shift = (pin & (ADSP_PADS_DS_PINS_PER_REG-1)) * ADSP_PADS_DS_BITS; + mask = GENMASK(ADSP_PADS_DS_BITS-1, 0) << shift; + val = val & ~mask; + + if (high) + writel(val | (ADSP_PADS_DS_HIGH << shift), p->regs + offset); + else + writel(val | (ADSP_PADS_DS_LOW << shift), p->regs + offset); +} + +static int adsp_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *config, unsigned int num_configs) +{ + struct adsp_pinctrl *adsp_pinctrl = pinctrl_dev_get_drvdata(pctldev); + struct pinctrl_gpio_range *range; + struct adsp_gpio_port *port; + u32 param, arg, val; + u32 offset; + int cfg; + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&adsp_pinctrl->lock, flags); + + for (cfg = 0; cfg < num_configs; ++cfg) { + param = pinconf_to_config_param(config[cfg]); + arg = pinconf_to_config_argument(config[cfg]); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + __adsp_pinconf_pue(adsp_pinctrl, pin, false); + __adsp_pinconf_pde(adsp_pinctrl, pin, false); + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + __adsp_pinconf_pde(adsp_pinctrl, pin, !!arg); + break; + case PIN_CONFIG_BIAS_PULL_UP: + __adsp_pinconf_pue(adsp_pinctrl, pin, !!arg); + break; + case PIN_CONFIG_DRIVE_STRENGTH: + /* This only supports high/low-speed drive strength (see HRM) + * so assume any positive value means we would like high-speed strength + */ + __adsp_pinconf_ds(adsp_pinctrl, pin, !!arg); + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + range = pinctrl_find_gpio_range_from_pin(pctldev, pin); + offset = pin - range->pin_base; + port = to_adsp_gpio_port(range->gc); + + spin_lock(&port->lock); + val = readw(port->regs + ADSP_PORT_REG_DATA); + val &= BIT(offset); + + if (val) { + /* open drain with value of 1 => configure as input */ + writew(BIT(offset), port->regs + ADSP_PORT_REG_DIR_CLEAR); + writew(BIT(offset), port->regs + ADSP_PORT_REG_INEN_SET); + } else { + /* open drain with value of 0 => configure as output, drive 0 */ + writew(BIT(offset), port->regs + ADSP_PORT_REG_INEN_CLEAR); + writew(BIT(offset), port->regs + ADSP_PORT_REG_DATA_CLEAR); + writew(BIT(offset), port->regs + ADSP_PORT_REG_DIR_SET); + } + + port->open_drain |= BIT(offset); + spin_unlock(&port->lock); + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + range = pinctrl_find_gpio_range_from_pin(pctldev, pin); + offset = pin - range->pin_base; + port = to_adsp_gpio_port(range->gc); + + spin_lock(&port->lock); + + /* + * by default make the pin an input when exiting open drain mode; + * user can correct later with GPIO in/out configuration + */ + if (port->open_drain & BIT(offset)) { + port->open_drain &= ~BIT(offset); + writew(BIT(offset), port->regs + ADSP_PORT_REG_DIR_CLEAR); + writew(BIT(offset), port->regs + ADSP_PORT_REG_INEN_SET); + } + + spin_unlock(&port->lock); + break; + case ADSP_PIN_CONFIG_TRU_TOGGLE: + range = pinctrl_find_gpio_range_from_pin(pctldev, pin); + offset = pin - range->pin_base; + port = to_adsp_gpio_port(range->gc); + + spin_lock(&port->lock); + val = readl(port->regs + ADSP_PORT_REG_TRIG_TGL); + val |= BIT(offset); + writel(val, port->regs + ADSP_PORT_REG_TRIG_TGL); + spin_unlock(&port->lock); + break; + default: + ret = -EOPNOTSUPP; + goto end; + } + } + +end: + spin_unlock_irqrestore(&adsp_pinctrl->lock, flags); + return ret; +} + +/* Config for all pins must match or we have an error regarding group structure */ +static int adsp_pinconf_group_get(struct pinctrl_dev *pctldev, unsigned int group, + unsigned long *config) +{ + const unsigned int *pins; + unsigned int npins, i; + unsigned long first; + int ret; + + ret = adsp_pinctrl_get_group_pins(pctldev, group, &pins, &npins); + if (ret) + return ret; + + for (i = 0; i < npins; ++i) { + ret = adsp_pinconf_get(pctldev, pins[i], config); + if (ret) + return ret; + + if (i == 0) + first = *config; + + if (first != *config) + return -EOPNOTSUPP; + } + + return 0; +} + +static int adsp_pinconf_group_set(struct pinctrl_dev *pctldev, unsigned int group, + unsigned long *configs, unsigned int num_configs) +{ + const unsigned int *pins; + unsigned int npins, i; + int ret; + + ret = adsp_pinctrl_get_group_pins(pctldev, group, &pins, &npins); + if (ret) + return ret; + + for (i = 0; i < npins; ++i) { + ret = adsp_pinconf_set(pctldev, pins[i], configs, num_configs); + if (ret) + return ret; + } + + return 0; +} + +static const struct pinconf_ops adsp_confops = { + .is_generic = true, + .pin_config_get = adsp_pinconf_get, + .pin_config_set = adsp_pinconf_set, + .pin_config_group_get = adsp_pinconf_group_get, + .pin_config_group_set = adsp_pinconf_group_set, +#ifdef CONFIG_DEBUG_FS + .pin_config_config_dbg_show = pinconf_generic_dump_config, +#endif +}; + +/* + * We want to make one group per pin so that we can refer to the pins by group + * later on when mux assignments are made + */ +static int adsp_pinctrl_init_groups(struct adsp_pinctrl *adsp_pinctrl, + struct pinctrl_desc *desc) +{ + struct device *dev = adsp_pinctrl->dev; + struct pinctrl_pin_desc *all_pins; + size_t port, pin; + unsigned int i, pin_total; + int num_ports; + int ret; + + num_ports = of_property_count_u32_elems(dev->of_node, "adi,port-sizes"); + + if (num_ports < 0) + return num_ports; + + if (num_ports == 0) { + dev_err(dev, "pinctrl missing `adi,port-sizes` port size definition\n"); + return -ENOENT; + } + + adsp_pinctrl->num_ports = num_ports; + + adsp_pinctrl->pin_counts = devm_kcalloc(dev, sizeof(*adsp_pinctrl->pin_counts), + num_ports, GFP_KERNEL); + if (!adsp_pinctrl->pin_counts) + return -ENOMEM; + + ret = of_property_read_u32_array(dev->of_node, "adi,port-sizes", + adsp_pinctrl->pin_counts, num_ports); + if (ret) + return ret; + + pin_total = 0; + + for (i = 0; i < num_ports; ++i) + pin_total += adsp_pinctrl->pin_counts[i]; + + adsp_pinctrl->total_pins = pin_total; + + all_pins = devm_kcalloc(dev, sizeof(*all_pins), adsp_pinctrl->total_pins, + GFP_KERNEL); + + adsp_pinctrl->pins = devm_kcalloc(dev, sizeof(adsp_pinctrl->pins), + adsp_pinctrl->total_pins, GFP_KERNEL); + if (!adsp_pinctrl->pins) + return -ENOMEM; + + adsp_pinctrl->group_names = devm_kcalloc(dev, sizeof(*adsp_pinctrl->group_names), + adsp_pinctrl->total_pins, GFP_KERNEL); + if (!adsp_pinctrl->group_names) + return -ENOMEM; + + i = 0; + for (port = 0; port < adsp_pinctrl->num_ports; ++port) { + for (pin = 0; pin < adsp_pinctrl->pin_counts[port]; ++pin) { + adsp_pinctrl->group_names[i] = devm_kasprintf(dev, GFP_KERNEL, + "p%c%zu", (char) ('A' + port), pin); + adsp_pinctrl->pins[i] = i; + + all_pins[i].name = adsp_pinctrl->group_names[i]; + all_pins[i].number = i; + i += 1; + } + } + + desc->pins = all_pins; + desc->npins = adsp_pinctrl->total_pins; + + return 0; +} + +static void adsp_set_nongpio_ds(struct adsp_pinctrl *p, int type, bool high) +{ + u32 val = readl(p->regs + ADSP_PADS_NONPORTS_DS); + u32 shift = ADSP_PADS_DS_BITS * type; + u32 mask = GENMASK(ADSP_PADS_DS_BITS-1, 0) << shift; + + val = val & ~mask; + + if (high) + writel(val | (ADSP_PADS_DS_HIGH << shift), p->regs + ADSP_PADS_NONPORTS_DS); + else + writel(val | (ADSP_PADS_DS_LOW << shift), p->regs + ADSP_PADS_NONPORTS_DS); +} + +static int adsp_pinctrl_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct adsp_pinctrl *adsp_pinctrl; + struct pinctrl_desc *pnctrl_desc; + struct resource *res; + u32 val; + int ret; + + adsp_pinctrl = devm_kzalloc(dev, sizeof(*adsp_pinctrl), GFP_KERNEL); + if (!adsp_pinctrl) + return -ENOMEM; + + adsp_pinctrl->dev = dev; + pnctrl_desc = devm_kzalloc(dev, sizeof(*pnctrl_desc), GFP_KERNEL); + if (!pnctrl_desc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + adsp_pinctrl->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(adsp_pinctrl->regs)) + return PTR_ERR(adsp_pinctrl->regs); + + /* Different features are available in different hw revisions; no way to read this + * from an ID register so the missing features need to be specified in dts + */ + adsp_pinctrl->ds_missing = of_property_read_bool(np, "adi,no-drive-strength"); + adsp_pinctrl->pude_missing = of_property_read_bool(np, "adi,no-pull-up-down"); + + /* Only if requested, adjust non-port drive strengths */ + ret = of_property_read_u32(np, "adi,clkout-drive-strength", &val); + if (!ret) + adsp_set_nongpio_ds(adsp_pinctrl, ADSP_NONPORTS_DS_CKOUT, !!val); + + ret = of_property_read_u32(np, "adi,resoutb-drive-strength", &val); + if (!ret) + adsp_set_nongpio_ds(adsp_pinctrl, ADSP_NONPORTS_DS_RESOUTB, !!val); + + ret = of_property_read_u32(np, "adi,faultb-drive-strength", &val); + if (!ret) + adsp_set_nongpio_ds(adsp_pinctrl, ADSP_NONPORTS_DS_FAULTB, !!val); + + ret = of_property_read_u32(np, "adi,lp1ck-drive-strength", &val); + if (!ret) + adsp_set_nongpio_ds(adsp_pinctrl, ADSP_NONPORTS_DS_LP1CK, !!val); + + ret = of_property_read_u32(np, "adi,lp0ck-drive-strength", &val); + if (!ret) + adsp_set_nongpio_ds(adsp_pinctrl, ADSP_NONPORTS_DS_LP0CK, !!val); + + ret = of_property_read_u32(np, "adi,ospi-drive-strength", &val); + if (!ret) + adsp_set_nongpio_ds(adsp_pinctrl, ADSP_NONPORTS_DS_OSPI, !!val); + + pnctrl_desc->name = dev_name(dev); + pnctrl_desc->pctlops = &adsp_pctlops; + pnctrl_desc->confops = &adsp_confops; + pnctrl_desc->pmxops = &adsp_pmxops; + pnctrl_desc->owner = THIS_MODULE; + pnctrl_desc->num_custom_params = ARRAY_SIZE(adsp_custom_bindings); + pnctrl_desc->custom_params = adsp_custom_bindings; + pnctrl_desc->custom_conf_items = adsp_conf_items; + + spin_lock_init(&adsp_pinctrl->lock); + ret = adsp_pinctrl_init_groups(adsp_pinctrl, pnctrl_desc); + if (ret) + return ret; + + ret = devm_pinctrl_register_and_init(dev, pnctrl_desc, adsp_pinctrl, + &adsp_pinctrl->pin_dev); + if (ret) + return ret; + + platform_set_drvdata(pdev, adsp_pinctrl); + ret = pinctrl_enable(adsp_pinctrl->pin_dev); + return ret; +} + +static const struct of_device_id adsp_pinctrl_of_match[] = { + { .compatible = "adi,sc5xx-pinctrl", }, + { }, +}; +MODULE_DEVICE_TABLE(of, adsp_pinctrl_of_match); + +static struct platform_driver adsp_pinctrl_driver = { + .driver = { + .name = "sc5xx-pinctrl", + .of_match_table = adsp_pinctrl_of_match, + .suppress_bind_attrs = true, + }, + .probe = adsp_pinctrl_probe, +}; + +static int __init adsp_pinctrl_init(void) +{ + return platform_driver_register(&adsp_pinctrl_driver); +} + +/* + * We want the pinctrl driver to be available at arch init time not at the + * later device init time + */ +arch_initcall(adsp_pinctrl_init); + +MODULE_DESCRIPTION("Analog Devices Pinctrl driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Greg Malysa "); diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 5484a65f66b953..08b1e143b00905 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -231,6 +231,14 @@ config RESET_RZG2L_USBPHY_CTRL Support for USBPHY Control found on RZ/G2L family. It mainly controls reset and power down of the USB/PHY. +config RESET_SC5XX + bool "ADI SC5CC Reset Driver" + depends on ARCH_SC59X_64 + default y + help + This enables simple reset controller for ADI ADSP-SC5xx family. + Support software reset. + config RESET_SCMI tristate "Reset driver controlled via ARM SCMI interface" depends on ARM_SCMI_PROTOCOL || COMPILE_TEST diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index 4411a2a124d7de..e4ff525a7c71ab 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -4,6 +4,7 @@ obj-y += hisilicon/ obj-y += starfive/ obj-y += sti/ obj-y += tegra/ +obj-$(CONFIG_RESET_SC5XX) += reset-sc5xx.o obj-$(CONFIG_RESET_A10SR) += reset-a10sr.o obj-$(CONFIG_RESET_ATH79) += reset-ath79.o obj-$(CONFIG_RESET_AXS10X) += reset-axs10x.o diff --git a/drivers/reset/reset-sc5xx.c b/drivers/reset/reset-sc5xx.c new file mode 100644 index 00000000000000..f058ee051d1cc8 --- /dev/null +++ b/drivers/reset/reset-sc5xx.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Analog Devices Reset Control Unit + * + * (C) Copyright 2022-2024 - Analog Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ADI_RCU_REBOOT_PRIORITY 255 +#define ADI_RCU_CORE_INIT_TIMEOUT msecs_to_jiffies(2000) + +struct adi_rcu { + struct notifier_block reboot_notifier; + void __iomem *ioaddr; + struct device *dev; +}; + +static struct adi_rcu *to_adi_rcu(const struct notifier_block *nb) +{ + return container_of(nb, struct adi_rcu, reboot_notifier); +} + +/* + * RCU memory accessors for other drivers that need it + */ +static u32 adi_rcu_readl(struct adi_rcu *rcu, int offset) +{ + return readl(rcu->ioaddr + offset); +} + +static void adi_rcu_writel(u32 val, struct adi_rcu *rcu, int offset) +{ + writel(val, rcu->ioaddr + offset); +} + +static int adi_rcu_reboot(struct notifier_block *nb, unsigned long mode, + void *cmd) +{ + struct adi_rcu *adi_rcu = to_adi_rcu(nb); + u32 val; + + dev_info(adi_rcu->dev, "Reboot requested\n"); + + val = adi_rcu_readl(adi_rcu, ADI_RCU_REG_CTL); + adi_rcu_writel(val | ADI_RCU_CTL_SYSRST, adi_rcu, ADI_RCU_REG_CTL); + + dev_err(adi_rcu->dev, "Unable to reboot via RCU\n"); + return NOTIFY_DONE; +} + +static int adi_rcu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adi_rcu *adi_rcu = NULL; + void __iomem *base; + int ret; + + adi_rcu = devm_kzalloc(dev, sizeof(*adi_rcu), GFP_KERNEL); + if (!adi_rcu) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) { + dev_err(dev, "Cannot map RCU base address\n"); + return PTR_ERR(base); + } + + adi_rcu->ioaddr = base; + adi_rcu->dev = dev; + adi_rcu->reboot_notifier.priority = ADI_RCU_REBOOT_PRIORITY; + adi_rcu->reboot_notifier.notifier_call = adi_rcu_reboot; + ret = devm_register_reboot_notifier(dev, &adi_rcu->reboot_notifier); + if (ret) { + dev_err(dev, + "Unable to register restart handler: %d\n", + ret); + return ret; + } + + dev_set_drvdata(dev, adi_rcu); + + return 0; +} + +static const struct of_device_id adi_rcu_match[] = { + {.compatible = "adi,sc5xx-reset" }, + { } +}; + +MODULE_DEVICE_TABLE(of, adi_rcu_match); + +static struct platform_driver adi_rcu_driver = { + .probe = adi_rcu_probe, + .driver = { + .name = "ADI Reset Control Unit", + .of_match_table = of_match_ptr(adi_rcu_match), + }, +}; + +module_platform_driver(adi_rcu_driver); + +MODULE_DESCRIPTION("Analog Devices RCU driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Greg Malysa "); \ No newline at end of file diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 28e4beeabf8f37..1935ec1b07de88 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -471,6 +471,23 @@ config SERIAL_SA1100_CONSOLE your boot loader (lilo or loadlin) about how to pass options to the kernel at boot time.) +config SERIAL_ADI_UART + tristate "ADI uart serial port support" + depends on ARCH_SC59X_64 + select SERIAL_CORE + select SERIAL_CORE_CONSOLE + help + Add support for the built-in adi uart driver. + +config SERIAL_ADI_UART_CONSOLE + bool "Console on ADI uart serial port" + depends on SERIAL_ADI_UART + default y + select SERIAL_CORE_CONSOLE + help + If you have enabled the ADI UART serial port, you can + make it the console by answering Y to this option. + config SERIAL_IMX tristate "IMX serial port support" depends on ARCH_MXC || COMPILE_TEST diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 6ff74f0a9530c4..9d4920b51b55af 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_SERIAL_JSM) += jsm/ obj-$(CONFIG_SERIAL_LANTIQ) += lantiq.o obj-$(CONFIG_SERIAL_LITEUART) += liteuart.o obj-$(CONFIG_SERIAL_HS_LPC32XX) += lpc32xx_hs.o +obj-$(CONFIG_SERIAL_ADI_UART) += adi_uart.o obj-$(CONFIG_SERIAL_MAX3100) += max3100.o obj-$(CONFIG_SERIAL_MAX310X) += max310x.o obj-$(CONFIG_SERIAL_MCF) += mcf.o diff --git a/drivers/tty/serial/adi_uart.c b/drivers/tty/serial/adi_uart.c new file mode 100644 index 00000000000000..73153777c3309b --- /dev/null +++ b/drivers/tty/serial/adi_uart.c @@ -0,0 +1,1047 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADI On-Chip Two Wire Interface Driver + * + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_SERIAL_ADI_UART_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +#define SUPPORT_SYSRQ +#endif + +#define DRIVER_NAME "adi-uart" + +struct adi_uart_serial_port { + struct uart_port port; + struct device *dev; + unsigned int old_status; + int tx_irq; + int rx_irq; + int status_irq; + unsigned int lsr; + unsigned int hwflow_mode; + struct gpio_desc *hwflow_en_pin; + bool hwflow_en; + /* Use enable-divide-by-one in divisor? */ + bool edbo; + struct clk *clk; +}; + +#define ADI_UART_NO_HWFLOW 0 +#define ADI_UART_HWFLOW_PERI 1 + +#define ADI_UART_NR_PORTS 4 +static struct adi_uart_serial_port *adi_uart_serial_ports[ADI_UART_NR_PORTS]; + +/* UART_CTL Masks */ +#define UCEN 0x1 /* Enable UARTx Clocks */ +#define LOOP_ENA 0x2 /* Loopback Mode Enable */ +#define UMOD_MDB 0x10 /* Enable MDB Mode */ +#define UMOD_IRDA 0x20 /* Enable IrDA Mode */ +#define UMOD_MASK 0x30 /* Uart Mode Mask */ +#define WLS(x) (((x-5) & 0x03) << 8) /* Word Length Select */ +#define WLS_MASK 0x300 /* Word length Select Mask */ +#define WLS_OFFSET 8 /* Word length Select Offset */ +#define STB 0x1000 /* Stop Bits */ +#define STBH 0x2000 /* Half Stop Bits */ +#define PEN 0x4000 /* Parity Enable */ +#define EPS 0x8000 /* Even Parity Select */ +#define STP 0x10000 /* Stick Parity */ +#define FPE 0x20000 /* Force Parity Error On Transmit */ +#define FFE 0x40000 /* Force Framing Error On Transmit */ +#define SB 0x80000 /* Set Break */ +#define LCR_MASK (SB | STP | EPS | PEN | STB | WLS_MASK) +#define FCPOL 0x400000 /* Flow Control Pin Polarity */ +#define RPOLC 0x800000 /* IrDA RX Polarity Change */ +#define TPOLC 0x1000000 /* IrDA TX Polarity Change */ +#define MRTS 0x2000000 /* Manual Request To Send */ +#define XOFF 0x4000000 /* Transmitter Off */ +#define ARTS 0x8000000 /* Automatic Request To Send */ +#define ACTS 0x10000000 /* Automatic Clear To Send */ +#define RFIT 0x20000000 /* Receive FIFO IRQ Threshold */ +#define RFRT 0x40000000 /* Receive FIFO RTS Threshold */ + +/* UART_STAT Masks */ +#define DR 0x01 /* Data Ready */ +#define OE 0x02 /* Overrun Error */ +#define PE 0x04 /* Parity Error */ +#define FE 0x08 /* Framing Error */ +#define BI 0x10 /* Break Interrupt */ +#define THRE 0x20 /* THR Empty */ +#define TEMT 0x80 /* TSR and UART_THR Empty */ +#define TFI 0x100 /* Transmission Finished Indicator */ + +#define ASTKY 0x200 /* Address Sticky */ +#define ADDR 0x400 /* Address bit status */ +#define RO 0x800 /* Reception Ongoing */ +#define SCTS 0x1000 /* Sticky CTS */ +#define CTS 0x10000 /* Clear To Send */ +#define RFCS 0x20000 /* Receive FIFO Count Status */ + +/* UART_CLOCK Masks */ +#define EDBO 0x80000000 /* Enable Devide by One */ + +/* UART_IER Masks */ +#define ERBFI 0x01 /* Enable Receive Buffer Full Interrupt */ +#define ETBEI 0x02 /* Enable Transmit Buffer Empty Interrupt */ +#define ELSI 0x04 /* Enable RX Status Interrupt */ +#define EDSSI 0x08 /* Enable Modem Status Interrupt */ +#define EDTPTI 0x10 /* Enable DMA Transmit PIRQ Interrupt */ +#define ETFI 0x20 /* Enable Transmission Finished Interrupt */ +#define ERFCI 0x40 /* Enable Receive FIFO Count Interrupt */ + +# define OFFSET_REDIV 0x00 /* Version ID Register */ +# define OFFSET_CTL 0x04 /* Control Register */ +# define OFFSET_STAT 0x08 /* Status Register */ +# define OFFSET_SCR 0x0C /* SCR Scratch Register */ +# define OFFSET_CLK 0x10 /* Clock Rate Register */ +# define OFFSET_IER 0x14 /* Interrupt Enable Register */ +# define OFFSET_IER_SET 0x18 /* Set Interrupt Enable Register */ +# define OFFSET_IER_CLEAR 0x1C /* Clear Interrupt Enable Register */ +# define OFFSET_RBR 0x20 /* Receive Buffer register */ +# define OFFSET_THR 0x24 /* Transmit Holding register */ + +#define UART_GET_CHAR(p) readl(p->port.membase + OFFSET_RBR) +#define UART_GET_CLK(p) readl(p->port.membase + OFFSET_CLK) +#define UART_GET_CTL(p) readl(p->port.membase + OFFSET_CTL) +#define UART_GET_GCTL(p) UART_GET_CTL(p) +#define UART_GET_LCR(p) UART_GET_CTL(p) +#define UART_GET_MCR(p) UART_GET_CTL(p) +#define UART_GET_STAT(p) readl(p->port.membase + OFFSET_STAT) +#define UART_GET_MSR(p) UART_GET_STAT(p) + +#define UART_PUT_CHAR(p, v) writel(v, p->port.membase + OFFSET_THR) +#define UART_PUT_CLK(p, v) writel(v, p->port.membase + OFFSET_CLK) +#define UART_PUT_CTL(p, v) writel(v, p->port.membase + OFFSET_CTL) +#define UART_PUT_GCTL(p, v) UART_PUT_CTL(p, v) +#define UART_PUT_LCR(p, v) UART_PUT_CTL(p, v) +#define UART_PUT_MCR(p, v) UART_PUT_CTL(p, v) +#define UART_PUT_STAT(p, v) writel(v, p->port.membase + OFFSET_STAT) + +#define UART_CLEAR_IER(p, v) writel(v, p->port.membase + OFFSET_IER_CLEAR) +#define UART_GET_IER(p) readl(p->port.membase + OFFSET_IER) +#define UART_SET_IER(p, v) writel(v, p->port.membase + OFFSET_IER_SET) + +#define UART_CLEAR_LSR(p) UART_PUT_STAT(p, -1) +#define UART_GET_LSR(p) UART_GET_STAT(p) +#define UART_PUT_LSR(p, v) UART_PUT_STAT(p, v) + +/* This handles hard CTS/RTS */ +#define UART_CLEAR_SCTS(p) UART_PUT_STAT(p, SCTS) +#define UART_GET_CTS(x) (UART_GET_MSR(x) & CTS) +#define UART_DISABLE_RTS(x) UART_PUT_MCR(x, UART_GET_MCR(x) & ~(ARTS | MRTS)) +#define UART_ENABLE_RTS(x) UART_PUT_MCR(x, UART_GET_MCR(x) | MRTS | ARTS) +#define UART_ENABLE_INTS(x, v) UART_SET_IER(x, v) +#define UART_DISABLE_INTS(x) UART_CLEAR_IER(x, 0xF) + +#define DMA_RX_XCOUNT 512 +#define DMA_RX_YCOUNT (PAGE_SIZE / DMA_RX_XCOUNT) + +#define DMA_RX_FLUSH_JIFFIES (msecs_to_jiffies(50)) + + +static void adi_uart_serial_tx_chars(struct adi_uart_serial_port *uart); +static void adi_uart_serial_reset_irda(struct uart_port *port); + + +static struct adi_uart_serial_port *to_adi_serial_port(struct uart_port *port) +{ + return container_of(port, struct adi_uart_serial_port, port); +} + +static unsigned int adi_uart_serial_get_mctrl(struct uart_port *port) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + + if (!uart->hwflow_mode || !uart->hwflow_en) + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; + + /* CTS PIN is negative assertive. */ + if (UART_GET_CTS(uart)) + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; + else + return TIOCM_DSR | TIOCM_CAR; +} + +static void adi_uart_serial_set_mctrl(struct uart_port *port, + unsigned int mctrl) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + + if (!uart->hwflow_mode || !uart->hwflow_en) + return; + + /* RTS PIN is negative assertive. */ + if (mctrl & TIOCM_RTS) + UART_ENABLE_RTS(uart); + else + UART_DISABLE_RTS(uart); +} + +/* + * Handle any change of modem status signal. + */ +static irqreturn_t adi_uart_serial_mctrl_cts_int(int irq, void *dev_id) +{ + struct adi_uart_serial_port *uart = dev_id; + unsigned int status = adi_uart_serial_get_mctrl(&uart->port); + struct tty_struct *tty = uart->port.state->port.tty; + + if (uart->hwflow_mode == ADI_UART_HWFLOW_PERI) { + UART_CLEAR_SCTS(uart); + if (tty->hw_stopped) { + if (status) { + tty->hw_stopped = 0; + uart_write_wakeup(&uart->port); + } + } else { + if (!status) + tty->hw_stopped = 1; + } + } + + uart_handle_cts_change(&uart->port, status & TIOCM_CTS); + + return IRQ_HANDLED; +} + +/* + * interrupts are disabled on entry + */ +static void adi_uart_serial_stop_tx(struct uart_port *port) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + + while (!(UART_GET_LSR(uart) & TEMT)) + cpu_relax(); + + UART_PUT_LSR(uart, TFI); + UART_CLEAR_IER(uart, ETBEI); +} + +/* + * port is locked and interrupts are disabled + */ +static void adi_uart_serial_start_tx(struct uart_port *port) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + struct tty_struct *tty = uart->port.state->port.tty; + + /* + * To avoid losting RX interrupt, we reset IR function + * before sending data. + */ + if (tty->termios.c_line == N_IRDA) + adi_uart_serial_reset_irda(port); + + UART_SET_IER(uart, ETBEI); + adi_uart_serial_tx_chars(uart); +} + +/* + * Interrupts are enabled + */ +static void adi_uart_serial_stop_rx(struct uart_port *port) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + + UART_CLEAR_IER(uart, ERBFI); +} + +/* + * Set the modem control timer to fire immediately. + */ +static void adi_uart_serial_enable_ms(struct uart_port *port) +{ +} + +static void adi_uart_serial_rx_chars(struct adi_uart_serial_port *uart) +{ + unsigned int status, ch, flg; + + status = UART_GET_LSR(uart); + UART_CLEAR_LSR(uart); + + ch = UART_GET_CHAR(uart); + uart->port.icount.rx++; + + if (status & BI) { + uart->port.icount.brk++; + if (uart_handle_break(&uart->port)) + goto ignore_char; + status &= ~(PE | FE); + } + if (status & PE) + uart->port.icount.parity++; + if (status & OE) + uart->port.icount.overrun++; + if (status & FE) + uart->port.icount.frame++; + + status &= uart->port.read_status_mask; + + if (status & BI) + flg = TTY_BREAK; + else if (status & PE) + flg = TTY_PARITY; + else if (status & FE) + flg = TTY_FRAME; + else + flg = TTY_NORMAL; + + if (uart_handle_sysrq_char(&uart->port, ch)) + goto ignore_char; + + uart_insert_char(&uart->port, status, OE, ch, flg); + + ignore_char: + tty_flip_buffer_push(&uart->port.state->port); +} + +static void adi_uart_serial_tx_chars(struct adi_uart_serial_port *uart) +{ + struct tty_port *tport = &uart->port.state->port; + unsigned char c; + + if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(&uart->port)) { + /* Clear TFI bit */ + UART_PUT_LSR(uart, TFI); + /* Anomaly notes: + * 05000215 - we always clear ETBEI within last UART TX + * interrupt to end a string. It is always set + * when start a new tx. + */ + UART_CLEAR_IER(uart, ETBEI); + return; + } + + if (uart->port.x_char) { + UART_PUT_CHAR(uart, uart->port.x_char); + uart->port.icount.tx++; + uart->port.x_char = 0; + } + + if (UART_GET_LSR(uart) & THRE) { + /* pop data from fifo */ + if (kfifo_get(&tport->xmit_fifo, &c)) { + UART_PUT_CHAR(uart, c); + uart->port.icount.tx++; + } + } + + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) + uart_write_wakeup(&uart->port); +} + +static irqreturn_t adi_uart_serial_rx_int(int irq, void *dev_id) +{ + struct adi_uart_serial_port *uart = dev_id; + + while (UART_GET_LSR(uart) & DR) + adi_uart_serial_rx_chars(uart); + + return IRQ_HANDLED; +} + +static irqreturn_t adi_uart_serial_tx_int(int irq, void *dev_id) +{ + struct adi_uart_serial_port *uart = dev_id; + + spin_lock(&uart->port.lock); + if (UART_GET_LSR(uart) & THRE) + adi_uart_serial_tx_chars(uart); + spin_unlock(&uart->port.lock); + + return IRQ_HANDLED; +} + +/* + * Return TIOCSER_TEMT when transmitter is not busy. + */ +static unsigned int adi_uart_serial_tx_empty(struct uart_port *port) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + unsigned int lsr; + + lsr = UART_GET_LSR(uart); + if (lsr & TEMT) + return TIOCSER_TEMT; + else + return 0; +} + +static void adi_uart_serial_break_ctl(struct uart_port *port, int break_state) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + u32 lcr = UART_GET_LCR(uart); + + if (break_state) + lcr |= SB; + else + lcr &= ~SB; + UART_PUT_LCR(uart, lcr); +} + +static int adi_uart_serial_startup(struct uart_port *port) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + int ret; + + ret = clk_prepare_enable(uart->clk); + if (ret) + return ret; + + if (uart->hwflow_mode == ADI_UART_HWFLOW_PERI) { + /* CTS RTS PINs are negative assertive. */ + UART_PUT_MCR(uart, UART_GET_MCR(uart) | ACTS); + UART_SET_IER(uart, EDSSI); + } + + UART_SET_IER(uart, ERBFI); + return 0; +} + +static void adi_uart_serial_shutdown(struct uart_port *port) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + + dev_dbg(uart->dev, "in serial_shutdown\n"); + + clk_disable_unprepare(uart->clk); +} + +static void adi_uart_serial_set_termios(struct uart_port *port, + struct ktermios *termios, const struct ktermios *old) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + unsigned long flags; + unsigned int baud, quot; + unsigned int ier, lcr = 0; + unsigned long timeout; + + if (uart->hwflow_mode == ADI_UART_HWFLOW_PERI) + termios->c_cflag |= CRTSCTS; + + switch (termios->c_cflag & CSIZE) { + case CS8: + lcr = WLS(8); + break; + case CS7: + lcr = WLS(7); + break; + case CS6: + lcr = WLS(6); + break; + case CS5: + lcr = WLS(5); + break; + default: + dev_err(port->dev, "%s: word length not supported\n", + __func__); + } + + if (termios->c_cflag & CSTOPB) + lcr |= STB; + if (termios->c_cflag & PARENB) + lcr |= PEN; + if (!(termios->c_cflag & PARODD)) + lcr |= EPS; + if (termios->c_cflag & CMSPAR) + lcr |= STP; + if (termios->c_cflag & CRTSCTS) + uart->hwflow_en = true; + else + uart->hwflow_en = false; + + spin_lock_irqsave(&uart->port.lock, flags); + + port->read_status_mask = OE; + if (termios->c_iflag & INPCK) + port->read_status_mask |= (FE | PE); + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= BI; + + /* + * Characters to ignore + */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= FE | PE; + if (termios->c_iflag & IGNBRK) { + port->ignore_status_mask |= BI; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= OE; + } + + /* + * uart_get_divisor has a hardcoded /16 factor that will cause integer + * round off errors if we're in divide-by-one mode + */ + if (uart->edbo) { + baud = uart_get_baud_rate(port, termios, old, 0, + port->uartclk); + quot = EDBO | DIV_ROUND_CLOSEST(port->uartclk, baud); + } else { + baud = uart_get_baud_rate(port, termios, old, 0, + port->uartclk/16); + quot = uart_get_divisor(port, baud); + } + + /* Wait till the transfer buffer is empty */ + timeout = jiffies + msecs_to_jiffies(10); + while (UART_GET_GCTL(uart) & UCEN && !(UART_GET_LSR(uart) & TEMT)) + if (time_after(jiffies, timeout)) { + dev_warn(port->dev, + "timeout waiting for TX buffer empty\n"); + break; + } + + /* Wait till the transfer buffer is empty */ + timeout = jiffies + msecs_to_jiffies(10); + while (UART_GET_GCTL(uart) & UCEN && !(UART_GET_LSR(uart) & TEMT)) + if (time_after(jiffies, timeout)) { + dev_warn(port->dev, + "timeout waiting for TX buffer empty\n"); + break; + } + + /* Disable UART */ + ier = UART_GET_IER(uart); + UART_PUT_GCTL(uart, UART_GET_GCTL(uart) & ~UCEN); + UART_DISABLE_INTS(uart); + + UART_PUT_CLK(uart, quot); + + UART_PUT_LCR(uart, (UART_GET_LCR(uart) & ~LCR_MASK) | lcr); + + /* Enable UART */ + UART_ENABLE_INTS(uart, ier); + UART_PUT_GCTL(uart, UART_GET_GCTL(uart) | UCEN); + + /* Port speed changed, update the per-port timeout. */ + uart_update_timeout(port, termios->c_cflag, baud); + + spin_unlock_irqrestore(&uart->port.lock, flags); +} + +static const char *adi_uart_serial_type(struct uart_port *port) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + + return uart->port.type == PORT_BFIN ? "ADI-UART" : NULL; +} + +/* + * Release the memory region(s) being used by 'port'. + */ +static void adi_uart_serial_release_port(struct uart_port *port) +{ +} + +/* + * Request the memory region(s) being used by 'port'. + */ +static int adi_uart_serial_request_port(struct uart_port *port) +{ + return 0; +} + +/* + * Configure/autoconfigure the port. + */ +static void adi_uart_serial_config_port(struct uart_port *port, int flags) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + + if (flags & UART_CONFIG_TYPE && + adi_uart_serial_request_port(&uart->port) == 0) + uart->port.type = PORT_BFIN; +} + +/* + * Verify the new serial_struct (for TIOCSSERIAL). + * The only change we allow are to the flags and type, and + * even then only between PORT_BFIN and PORT_UNKNOWN + */ +static int +adi_uart_serial_verify_port(struct uart_port *port, struct serial_struct *ser) +{ + return 0; +} + +/* + * Enable the IrDA function if tty->ldisc.num is N_IRDA. + * In other cases, disable IrDA function. + */ +static void adi_uart_serial_set_ldisc(struct uart_port *port, + struct ktermios *termios) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + unsigned int val; + + switch (termios->c_line) { + case N_IRDA: + val = UART_GET_GCTL(uart); + val |= (UMOD_IRDA | RPOLC); + UART_PUT_GCTL(uart, val); + break; + default: + val = UART_GET_GCTL(uart); + val &= ~(UMOD_MASK | RPOLC); + UART_PUT_GCTL(uart, val); + } +} + +static void adi_uart_serial_reset_irda(struct uart_port *port) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + unsigned int val; + + val = UART_GET_GCTL(uart); + val &= ~(UMOD_MASK | RPOLC); + UART_PUT_GCTL(uart, val); + val |= (UMOD_IRDA | RPOLC); + UART_PUT_GCTL(uart, val); +} + +#ifdef CONFIG_CONSOLE_POLL +static void adi_uart_serial_poll_put_char(struct uart_port *port, + unsigned char chr) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + + while (!(UART_GET_LSR(uart) & THRE)) + cpu_relax(); + + UART_PUT_CHAR(uart, (unsigned char)chr); +} + +static int adi_uart_serial_poll_get_char(struct uart_port *port) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + unsigned char chr; + + while (!(UART_GET_LSR(uart) & DR)) + cpu_relax(); + + chr = UART_GET_CHAR(uart); + + return chr; +} +#endif + + +static const struct uart_ops adi_uart_serial_pops = { + .tx_empty = adi_uart_serial_tx_empty, + .set_mctrl = adi_uart_serial_set_mctrl, + .get_mctrl = adi_uart_serial_get_mctrl, + .stop_tx = adi_uart_serial_stop_tx, + .start_tx = adi_uart_serial_start_tx, + .stop_rx = adi_uart_serial_stop_rx, + .enable_ms = adi_uart_serial_enable_ms, + .break_ctl = adi_uart_serial_break_ctl, + .startup = adi_uart_serial_startup, + .shutdown = adi_uart_serial_shutdown, + .set_termios = adi_uart_serial_set_termios, + .set_ldisc = adi_uart_serial_set_ldisc, + .type = adi_uart_serial_type, + .release_port = adi_uart_serial_release_port, + .request_port = adi_uart_serial_request_port, + .config_port = adi_uart_serial_config_port, + .verify_port = adi_uart_serial_verify_port, +#ifdef CONFIG_CONSOLE_POLL + .poll_put_char = adi_uart_serial_poll_put_char, + .poll_get_char = adi_uart_serial_poll_get_char, +#endif +}; + +#ifdef CONFIG_SERIAL_ADI_UART_CONSOLE +static void adi_uart_serial_console_putchar(struct uart_port *port, + unsigned char ch) +{ + struct adi_uart_serial_port *uart = to_adi_serial_port(port); + + while (!(UART_GET_LSR(uart) & THRE)) + barrier(); + UART_PUT_CHAR(uart, ch); +} + +static void __init +adi_uart_serial_console_get_options(struct adi_uart_serial_port *uart, + int *baud, int *parity, int *bits) +{ + unsigned int status; + + status = UART_GET_IER(uart) & (ERBFI | ETBEI); + if (status == (ERBFI | ETBEI)) { + /* ok, the port was enabled */ + u32 lcr, clk; + + lcr = UART_GET_LCR(uart); + + *parity = 'n'; + if (lcr & PEN) { + if (lcr & EPS) + *parity = 'e'; + else + *parity = 'o'; + } + *bits = ((lcr & WLS_MASK) >> WLS_OFFSET) + 5; + + clk = UART_GET_CLK(uart); + + /* Only the lowest 16 bits are the divisor */ + if (clk & EDBO) + *baud = uart->port.uartclk / (clk & 0xffff); + else + *baud = uart->port.uartclk / (16*clk); + } + pr_debug("%s:baud = %d, parity = %c, bits= %d\n", __func__, + *baud, *parity, *bits); +} + +static void +adi_uart_serial_console_write(struct console *co, const char *s, + unsigned int count) +{ + struct adi_uart_serial_port *uart = adi_uart_serial_ports[co->index]; + unsigned long flags; + + spin_lock_irqsave(&uart->port.lock, flags); + uart_console_write(&uart->port, s, count, + adi_uart_serial_console_putchar); + spin_unlock_irqrestore(&uart->port.lock, flags); + +} + +static int __init +adi_uart_serial_console_setup(struct console *co, char *options) +{ + struct adi_uart_serial_port *uart; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + + /* + * Check whether an invalid uart number has been specified, and + * if so, search for the first available port that does have + * console support. + */ + if (co->index < 0 || co->index >= ADI_UART_NR_PORTS) + return -ENODEV; + + uart = adi_uart_serial_ports[co->index]; + if (!uart) + return -ENODEV; + + if (uart->hwflow_mode == ADI_UART_HWFLOW_PERI) + flow = 'r'; + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + else + adi_uart_serial_console_get_options(uart, &baud, &parity, + &bits); + + return uart_set_options(&uart->port, co, baud, parity, bits, flow); +} + +static struct uart_driver adi_uart_serial_reg; + +static struct console adi_uart_serial_console = { + .name = "ttySC", + .write = adi_uart_serial_console_write, + .device = uart_console_device, + .setup = adi_uart_serial_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &adi_uart_serial_reg, +}; + + +#define ADI_SERIAL_UART_CONSOLE (&adi_uart_serial_console) +#else +#define ADI_SERIAL_UART_CONSOLE NULL +#endif + +static struct uart_driver adi_uart_serial_reg = { + .owner = THIS_MODULE, + .driver_name = DRIVER_NAME, + .dev_name = "ttySC", + .major = TTY_MAJOR, +#ifdef CONFIG_ARCH_SC59X_64 + // Other serial drivers are using 64 -- + // Can probably disable in the future and set this back to 64 + .minor = 74, +#else + .minor = 64, +#endif + .nr = ADI_UART_NR_PORTS, + .cons = ADI_SERIAL_UART_CONSOLE, +}; + +static int adi_uart_serial_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct adi_uart_serial_port *uart = platform_get_drvdata(pdev); + + clk_disable(uart->clk); + return uart_suspend_port(&adi_uart_serial_reg, &uart->port); +} + +static int adi_uart_serial_resume(struct platform_device *pdev) +{ + struct adi_uart_serial_port *uart = platform_get_drvdata(pdev); + int ret; + + ret = clk_enable(uart->clk); + if (ret) + return ret; + + return uart_resume_port(&adi_uart_serial_reg, &uart->port); +} + +static int adi_uart_serial_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct adi_uart_serial_port *uart = NULL; + int ret = 0; + int uartid; + dev_info(dev, "Serial probe\n"); + + uartid = of_alias_get_id(np, "serial"); + + if (uartid < 0) { + dev_err(&pdev->dev, "failed to get alias/pdev id, errno %d\n", + uartid); + ret = -ENODEV; + return ret; + } + + if (adi_uart_serial_ports[uartid] == NULL) { + uart = kzalloc(sizeof(*uart), GFP_KERNEL); + if (!uart) + return -ENOMEM; + + adi_uart_serial_ports[uartid] = uart; + uart->dev = &pdev->dev; + + uart->clk = devm_clk_get(dev, "sclk0"); + if (IS_ERR(uart->clk)) + return -ENODEV; + + spin_lock_init(&uart->port.lock); + uart->port.uartclk = clk_get_rate(uart->clk); + uart->port.fifosize = 8; + uart->port.ops = &adi_uart_serial_pops; + uart->port.line = uartid; + uart->port.iotype = UPIO_MEM; + uart->port.flags = UPF_BOOT_AUTOCONF; + + uart->port.membase = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(uart->port.membase)) + return PTR_ERR(uart->port.membase); + + uart->tx_irq = platform_get_irq_byname(pdev, "tx"); + uart->rx_irq = platform_get_irq_byname(pdev, "rx"); + uart->status_irq = + platform_get_irq_byname(pdev, "status"); + uart->port.irq = uart->rx_irq; + ret = devm_request_threaded_irq(dev, uart->rx_irq, + adi_uart_serial_rx_int, NULL, 0, "ADI UART RX", + uart); + if (ret) { + dev_err(dev, "Unable to attach UART RX int\n"); + return ret; + } + + ret = devm_request_threaded_irq(dev, uart->tx_irq, + adi_uart_serial_tx_int, NULL, 0, "ADI UART TX", + uart); + if (ret) { + dev_err(dev, "Unable to attach UART TX int\n"); + return ret; + } + + /* adi,uart-has-rtscts is deprecated */ + if (of_property_read_bool(np, "uart-has-rtscts") || + of_property_read_bool(np, "adi,uart-has-rtscts")) { + uart->hwflow_mode = ADI_UART_HWFLOW_PERI; + ret = devm_request_threaded_irq(dev, uart->status_irq, + adi_uart_serial_mctrl_cts_int, NULL, 0, + "ADI UART Modem Status", + uart); + if (ret) { + uart->hwflow_mode = ADI_UART_NO_HWFLOW; + dev_info(dev, + "Unable to attach UART Modem Status int.\n"); + } + } else + uart->hwflow_mode = ADI_UART_NO_HWFLOW; + + uart->edbo = false; + if (of_property_read_bool(np, "adi,use-edbo")) + uart->edbo = true; + + if (uart->hwflow_mode == ADI_UART_HWFLOW_PERI) { + uart->hwflow_en_pin = devm_gpiod_get(dev, "hwflow-en", + GPIOD_OUT_HIGH); + if (IS_ERR(uart->hwflow_en_pin)) { + dev_err(dev, + "hwflow-en required in peripheral hwflow mode\n"); + return PTR_ERR(uart->hwflow_en_pin); + } + } + } + + uart = adi_uart_serial_ports[uartid]; + uart->port.dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, uart); + + ret = uart_add_one_port(&adi_uart_serial_reg, &uart->port); + if (!ret) + return 0; + + if (uart) { + adi_uart_serial_ports[uartid] = NULL; + kfree(uart); + } + + return ret; +} + +static void adi_uart_serial_remove(struct platform_device *pdev) +{ + struct adi_uart_serial_port *uart = platform_get_drvdata(pdev); + + dev_set_drvdata(&pdev->dev, NULL); + + if (uart) { + uart_remove_one_port(&adi_uart_serial_reg, &uart->port); + adi_uart_serial_ports[uart->port.line] = NULL; + kfree(uart); + } +} + +static const struct of_device_id adi_uart_dt_match[] = { + { .compatible = "adi,sc5xx-uart"}, + {}, +}; +MODULE_DEVICE_TABLE(of, adi_uart_dt_match); + +static struct platform_driver adi_uart_serial_driver = { + .probe = adi_uart_serial_probe, + .remove = adi_uart_serial_remove, + .suspend = adi_uart_serial_suspend, + .resume = adi_uart_serial_resume, + .driver = { + .name = DRIVER_NAME, + .of_match_table = adi_uart_dt_match, + }, +}; + +static int __init adi_uart_serial_init(void) +{ + int ret; + + pr_info("ADI serial driver\n"); + + ret = uart_register_driver(&adi_uart_serial_reg); + if (ret) { + pr_err("failed to register %s:%d\n", + adi_uart_serial_reg.driver_name, ret); + } + + ret = platform_driver_register(&adi_uart_serial_driver); + if (ret) { + pr_err("fail to register ADI uart\n"); + uart_unregister_driver(&adi_uart_serial_reg); + } + + return ret; +} + +static void __exit adi_uart_serial_exit(void) +{ + platform_driver_unregister(&adi_uart_serial_driver); + uart_unregister_driver(&adi_uart_serial_reg); +} + +module_init(adi_uart_serial_init); +module_exit(adi_uart_serial_exit); + +/* Early Console Support */ +static inline u32 adi_uart_read(struct uart_port *port, u32 off) +{ + return readl(port->membase + off); +} + +static inline void adi_uart_write(struct uart_port *port, u32 val, + u32 off) +{ + writel(val, port->membase + off); +} + + +static void adi_uart_wait_bit_set(struct uart_port *port, unsigned int offset, + u32 bit) +{ + while (!(adi_uart_read(port, offset) & bit)) + cpu_relax(); +} + + +static void adi_uart_console_putchar(struct uart_port *port, unsigned char ch) +{ + /* wait for the hardware fifo to clear up */ + adi_uart_wait_bit_set(port, OFFSET_STAT, THRE); + + /* queue the character for transmission */ + adi_uart_write(port, ch, OFFSET_THR); +} + + +static void adi_uart_early_write(struct console *con, const char *s, + unsigned int n) +{ + struct earlycon_device *dev = con->data; + + uart_console_write(&dev->port, s, n, adi_uart_console_putchar); +} + + +static int __init adi_uart_early_console_setup(struct earlycon_device *device, + const char *opt) +{ + if (!device->port.membase) + return -ENODEV; + + device->con->write = adi_uart_early_write; + return 0; +} + +EARLYCON_DECLARE(adi_uart, adi_uart_early_console_setup); + +MODULE_AUTHOR("Sonic Zhang, Aubrey Li"); +MODULE_DESCRIPTION("Blackfin/ADSP generic serial port driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(BFIN_SERIAL_MAJOR); diff --git a/include/dt-bindings/clock/adi-sc5xx-clock.h b/include/dt-bindings/clock/adi-sc5xx-clock.h new file mode 100644 index 00000000000000..69a794a543e386 --- /dev/null +++ b/include/dt-bindings/clock/adi-sc5xx-clock.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ADSP SC5xx clock device tree bindings + * + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#ifndef DT_BINDINGS_CLOCK_ADI_SC5XX_CLOCK_H +#define DT_BINDINGS_CLOCK_ADI_SC5XX_CLOCK_H + +#define ADSP_CLK_DUMMY 0 +#define ADSP_CLK_SYS_CLKIN0 1 +#define ADSP_CLK_SYS_CLKIN1 2 +#define ADSP_CLK_CGU0_PLL_IN 3 +#define ADSP_CLK_CGU0_VCO_OUT 4 +#define ADSP_CLK_CGU0_PLLCLK 5 +#define ADSP_CLK_CGU1_IN 6 +#define ADSP_CLK_CGU1_PLL_IN 7 +#define ADSP_CLK_CGU1_VCO_OUT 8 +#define ADSP_CLK_CGU1_PLLCLK 9 +#define ADSP_CLK_CGU0_CDIV 10 +#define ADSP_CLK_CGU0_SYSCLK 11 +#define ADSP_CLK_CGU0_DDIV 12 +#define ADSP_CLK_CGU0_ODIV 13 +#define ADSP_CLK_CGU0_S0SELDIV 14 +#define ADSP_CLK_CGU0_S1SELDIV 15 +#define ADSP_CLK_CGU0_S1SELEXDIV 16 +#define ADSP_CLK_CGU0_S1SEL 17 +#define ADSP_CLK_CGU1_CDIV 18 +#define ADSP_CLK_CGU1_SYSCLK 19 +#define ADSP_CLK_CGU1_DDIV 20 +#define ADSP_CLK_CGU1_ODIV 21 +#define ADSP_CLK_CGU1_S0SELDIV 22 +#define ADSP_CLK_CGU1_S1SELDIV 23 +#define ADSP_CLK_CGU1_S0SELEXDIV 24 +#define ADSP_CLK_CGU1_S1SELEXDIV 25 +#define ADSP_CLK_CGU1_S0SEL 26 +#define ADSP_CLK_CGU1_S1SEL 27 +#define ADSP_CLK_CGU0_CCLK2 28 +#define ADSP_CLK_CGU0_CCLK0 29 +#define ADSP_CLK_CGU0_OCLK 30 +#define ADSP_CLK_CGU0_DCLK 31 +#define ADSP_CLK_CGU0_SCLK1 32 +#define ADSP_CLK_CGU0_SCLK0 33 +#define ADSP_CLK_CGU1_CCLK0 34 +#define ADSP_CLK_CGU1_OCLK 35 +#define ADSP_CLK_CGU1_DCLK 36 +#define ADSP_CLK_CGU1_SCLK1 37 +#define ADSP_CLK_CGU1_SCLK0 38 +#define ADSP_CLK_CGU1_CCLK2 39 +#define ADSP_CLK_DCLK0_HALF 40 +#define ADSP_CLK_DCLK1_HALF 41 +#define ADSP_CLK_CGU1_SCLK1_HALF 42 +#define ADSP_CLK_SHARC0_SEL 43 +#define ADSP_CLK_SHARC1_SEL 44 +#define ADSP_CLK_ARM_SEL 45 +#define ADSP_CLK_CDU_DDR_SEL 46 +#define ADSP_CLK_CAN_SEL 47 +#define ADSP_CLK_SPDIF_SEL 48 +#define ADSP_CLK_SPI_SEL 49 +#define ADSP_CLK_GIGE_SEL 50 +#define ADSP_CLK_LP_SEL 51 +#define ADSP_CLK_LP_DDR_SEL 52 +#define ADSP_CLK_OSPI_REFCLK_SEL 53 +#define ADSP_CLK_TRACE_SEL 54 +#define ADSP_CLK_EMMC_SEL 55 +#define ADSP_CLK_EMMC_TIMER_QMC_SEL 56 +#define ADSP_CLK_SHARC0 57 +#define ADSP_CLK_SHARC1 58 +#define ADSP_CLK_ARM 59 +#define ADSP_CLK_CDU_DDR 60 +#define ADSP_CLK_CAN 61 +#define ADSP_CLK_SPDIF 62 +#define ADSP_CLK_SPI 63 +#define ADSP_CLK_GIGE 64 +#define ADSP_CLK_LP 65 +#define ADSP_CLK_LP_DDR 66 +#define ADSP_CLK_OSPI_REFCLK 67 +#define ADSP_CLK_TRACE 68 +#define ADSP_CLK_EMMC 69 +#define ADSP_CLK_EMMC_TIMER_QMC 70 +#define ADSP_CLK_3PLL_PLL_IN 71 +#define ADSP_CLK_3PLL_VCO_OUT 72 +#define ADSP_CLK_3PLL_PLLCLK 73 +#define ADSP_CLK_3PLL_DDIV 74 +#define ADSP_CLK_DDR_SEL 75 +#define ADSP_CLK_DDR 76 +#define ADSP_CLK_CGU0_VCO_2_OUT 77 +#define ADSP_CLK_CGU1_VCO_2_OUT 78 +#define ADSP_CLK_3PLL_VCO_2_OUT 79 + +#define ADSP_CLK_NR_CLKS (ADSP_CLK_3PLL_VCO_2_OUT+1) + +#endif diff --git a/include/dt-bindings/pinctrl/adi-adsp.h b/include/dt-bindings/pinctrl/adi-adsp.h new file mode 100644 index 00000000000000..dc5b86a0d9190a --- /dev/null +++ b/include/dt-bindings/pinctrl/adi-adsp.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0*/ +/* + * Macros for populating pinmux properties on the pincontroller + * + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#ifndef DT_BINDINGS_PINCTRL_ADI_ADSP_H +#define DT_BINDINGS_PINCTRL_ADI_ADSP_H + +#define ADI_ADSP_PINFUNC_GPIO 0 +#define ADI_ADSP_PINFUNC_ALT0 1 +#define ADI_ADSP_PINFUNC_ALT1 2 +#define ADI_ADSP_PINFUNC_ALT2 3 +#define ADI_ADSP_PINFUNC_ALT3 4 + +#define ADI_ADSP_PINMUX(port, pin, func) ((((port - 'A')*16 + pin) << 8) + func) + +#endif diff --git a/include/linux/soc/adi/adsp-gpio-port.h b/include/linux/soc/adi/adsp-gpio-port.h new file mode 100644 index 00000000000000..09cc9f8bff5673 --- /dev/null +++ b/include/linux/soc/adi/adsp-gpio-port.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#ifndef GPIO_ADI_ADSP_PORT_H +#define GPIO_ADI_ADSP_PORT_H + +#include + +/* Number of GPIOs per port instance */ +#define ADSP_PORT_NGPIO 16 + +/* PORT memory layout */ +#define ADSP_PORT_REG_FER 0x00 +#define ADSP_PORT_REG_FER_SET 0x04 +#define ADSP_PORT_REG_FER_CLEAR 0x08 +#define ADSP_PORT_REG_DATA 0x0c +#define ADSP_PORT_REG_DATA_SET 0x10 +#define ADSP_PORT_REG_DATA_CLEAR 0x14 +#define ADSP_PORT_REG_DIR 0x18 +#define ADSP_PORT_REG_DIR_SET 0x1c +#define ADSP_PORT_REG_DIR_CLEAR 0x20 +#define ADSP_PORT_REG_INEN 0x24 +#define ADSP_PORT_REG_INEN_SET 0x28 +#define ADSP_PORT_REG_INEN_CLEAR 0x2c +#define ADSP_PORT_REG_PORT_MUX 0x30 +#define ADSP_PORT_REG_DATA_TGL 0x34 +#define ADSP_PORT_REG_POLAR 0x38 +#define ADSP_PORT_REG_POLAR_SET 0x3c +#define ADSP_PORT_REG_POLAR_CLEAR 0x40 +#define ADSP_PORT_REG_LOCK 0x44 +#define ADSP_PORT_REG_TRIG_TGL 0x48 + +/* + * One gpio instance per PORT instance in the hardware, provides the per-PORT + * interface to the hardware. Referenced in GPIO and PINCTRL drivers + */ +struct adsp_gpio_port { + struct device *dev; + void __iomem *regs; + struct gpio_chip gpio; + struct irq_domain *irq_domain; + u32 irq_offset; + u32 open_drain; + //lock gpio port when setting for GPIO or alternative functions + spinlock_t lock; +}; + +static inline struct adsp_gpio_port *to_adsp_gpio_port(struct gpio_chip + *chip) +{ + return container_of(chip, struct adsp_gpio_port, gpio); +} + +#endif diff --git a/include/linux/soc/adi/rcu.h b/include/linux/soc/adi/rcu.h new file mode 100644 index 00000000000000..5195cd943fdf09 --- /dev/null +++ b/include/linux/soc/adi/rcu.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#ifndef SOC_ADI_RCU_H +#define SOC_ADI_RCU_H + +/* Register offsets */ +#define ADI_RCU_REG_CTL 0x00 +#define ADI_RCU_REG_STAT 0x04 +#define ADI_RCU_REG_CRCTL 0x08 +#define ADI_RCU_REG_CRSTAT 0x0c + +#ifdef CONFIG_ARCH_SC58X +#define ADI_RCU_REG_SIDIS 0x10 +#define ADI_RCU_REG_SISTAT 0x14 +#define ADI_RCU_REG_SVECT_LCK 0x18 +#define ADI_RCU_REG_BCODE 0x1c +#define ADI_RCU_REG_SVECT0 0x20 +#define ADI_RCU_REG_SVECT1 0x24 +#define ADI_RCU_REG_SVECT2 0x28 +#define ADI_RCU_REG_MSG 0x60 +#define ADI_RCU_REG_MSG_SET 0x64 +#define ADI_RCU_REG_MSG_CLR 0x68 +#else +#define ADI_RCU_REG_SRRQSTAT 0x18 +#define ADI_RCU_REG_SIDIS 0x1c +#define ADI_RCU_REG_SISTAT 0x20 +#define ADI_RCU_REG_BCODE 0x28 +#define ADI_RCU_REG_SVECT0 0x2c +#define ADI_RCU_REG_SVECT1 0x30 +#define ADI_RCU_REG_SVECT2 0x34 +#define ADI_RCU_REG_MSG 0x6c +#define ADI_RCU_REG_MSG_SET 0x70 +#define ADI_RCU_REG_MSG_CLR 0x74 +#endif + +/* Register bit definitions */ +#define ADI_RCU_CTL_SYSRST BIT(0) + +/* Bit values for the RCU0_MSG register */ +#define RCU0_MSG_C0IDLE 0x00000100 /* Core 0 Idle */ +#define RCU0_MSG_C1IDLE 0x00000200 /* Core 1 Idle */ +#define RCU0_MSG_C2IDLE 0x00000400 /* Core 2 Idle */ +#define RCU0_MSG_CRR0 0x00001000 /* Core 0 reset request */ +#define RCU0_MSG_CRR1 0x00002000 /* Core 1 reset request */ +#define RCU0_MSG_CRR2 0x00004000 /* Core 2 reset request */ +#define RCU0_MSG_C1ACTIVATE 0x00080000 /* Core 1 Activated */ +#define RCU0_MSG_C2ACTIVATE 0x00100000 /* Core 2 Activated */ + +struct adi_rcu; +struct adi_sec; +#endif diff --git a/include/linux/soc/adi/sc59x.h b/include/linux/soc/adi/sc59x.h new file mode 100644 index 00000000000000..469ccd5f5e093b --- /dev/null +++ b/include/linux/soc/adi/sc59x.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#ifndef SOC_ADI_SC59X_H +#define SOC_ADI_SC59X_H + +#define SC59x_SYSTEM_L2_VIRT_BASE 0xFF020000 +#define SC59x_SYSTEM_L2_SIZE 0x2C0000 + +// General Purpose Timer Block Registers +#define TIMER_GROUP 0x31018004 + +// TIMER0 +#define TIMER0_CONFIG 0x31018060 + +// CGU0 +#define REG_CGU0_CTL 0x3108D000 // CGU0 Control Register +#define REG_CGU0_STAT 0x3108D008 // CGU0 Status Register +#define REG_CGU0_DIV 0x3108D00C // CGU0 Clocks Divisor Register + +// UART0 +#define UART0_REVID 0x31003000 // UART0 Revision ID Register + +// UART1 +#define UART1_REVID 0x31003400 // UART1 Revision ID Register + +// UART2 +#define UART2_REVID 0x31003800 // UART2 Revision ID Register + +// WDOG0 +#define REG_WDOG0_CTL 0x31008000 // WDOG0 Control Register + +// WDOG1 +#define REG_WDOG1_CTL 0x31008800 // WDOG1 Control Register + +// CRC0 MMR +#define REG_CRC0_CTL 0x310A5000 // CRC0 Control Register +#define REG_CRC0_DCNT 0x310A5004 // CRC0 Data Word Count Register +#define REG_CRC0_FILLVAL 0x310A5018 // CRC0 Fill Value Register + +// DMA Channel Registers +#define REG_DMA18_DSCPTR_NXT 0x310A7000 // DMA8 Pointer to Next Initial Descriptor +#define REG_DMA8_CFG 0x310A7008 // DMA8 Configuration Register +#define REG_DMA9_DSCPTR_NXT 0x310A7080 // DMA9 Pointer to Next Initial Descriptor +#define REG_DMA9_CFG 0x310A7088 // DMA9 Configuration Register +#define REG_DMA9_STAT 0x310A70B0 // DMA9 Status Register +#define REG_DMA18_DSCPTR_NXT 0x310A7100 // DMA18 Pointer to Next Initial Descriptor +#define REG_DMA18_CFG 0x310A7108 // DMA18 Configuration Register +#define REG_DMA19_DSCPTR_NXT 0x310A7180 // DMA19 Pointer to Next Initial Descriptor +#define REG_DMA19_CFG 0x310A7188 // DMA19 Configuration Register +#define REG_DMA19_STAT 0x310A71B0 // DMA19 Status Register + +// L2CTL0 +#define L2CTL0_CTL 0x31080000 // L2CTL0 Control Register +#define L2CTL0_STAT 0x31080010 // L2CTL0 Status Register +#define L2CTL0_ERRADDR0 0x31080040 // L2CTL0 ECC Error Address 0 Register +#define L2CTL0_ET0 0x31080080 // L2CTL0 Error Type 0 Register +#define L2CTL0_EADDR0 0x31080084 // L2CTL0 Error Type 0 Address Register +#define L2CTL0_ET1 0x31080088 // L2CTL0 Error Type 1 Register +#define L2CTL0_EADDR1 0x3108008C // L2CTL0 Error Type 1 Address Register + +// SEC Core Interface (SCI) Register Definitions +#define SEC_COMMON_BASE 0x31089000 +#define SEC_SCI_BASE 0x31089440 +#define SEC_SSI_BASE 0x31089800 + +#define SEC_SCI_OFF 0x00000040 +#define SEC_CCTL 0x00000000 // SEC Core Control Register n +#define SEC_CSID 0x0000001C // SEC Core IRQ Source ID Register n + +#define SEC_CCTL_EN 0x00000001 // SEC Core Control Register Enable bit + +// SEC Fault Management Interface (SFI) Register Definitions +#define SEC_FCTL 0x00000010 // SEC Fault Control Register + +// SEC Global Register Definitions +#define SEC_GCTL 0x00000000 // SEC Global Control Register +#define SEC_RAISE 0x00000008 // SEC Global Raise Register +#define SEC_END 0x0000000C // SEC Global End Register + +// SEC_SCTL +#define SEC_SCTL_CTG 0x0F000000 // Core Target Select + +// SEC Source Interface (SSI) Register Definitions +#define SEC_SCTL0 0x00000000 // SEC Source Control Register n + +// SEC_SCTL +#define SEC_SCTL_SRC_EN 0x00000004 // SEN: Enable +#define SEC_SCTL_FAULT_EN 0x00000002 // FEN: Enable +#define SEC_SCTL_INT_EN 0x00000001 // IEN: Enable + +// TRU0 +// 0x3108A000 + (0x4 * n) +#define REG_TRU0_SSR160 0x3108A280 // TRU0 Slave Select Register +#define REG_TRU0_SSR164 0x3108A290 // TRU0 Slave Select Register +#define REG_TRU0_SSR168 0x3108A2A0 // TRU0 Slave Select Register +#define REG_TRU0_MTR 0x3108A7E0 // TRU0 Master Trigger Register +#define REG_TRU0_GCTL 0x3108A7F4 // TRU0 Global Control Register + +// Trigger Master Definitions +#define TRGM_SOFT0 136 // Software-driven Trigger 3 +#define TRGM_SOFT1 137 // Software-driven Trigger 3 +#define TRGM_SOFT2 138 // Software-driven Trigger 4 +#define TRGM_SOFT3 139 // Software-driven Trigger 3 +#define TRGM_SOFT4 140 // Software-driven Trigger 4 +#define TRGM_SOFT5 141 // Software-driven Trigger 5 + +// RCU0 +#define REG_RCU0_CTL 0x3108C000 // RCU0 Control Register +#define REG_RCU0_STAT 0x3108C004 // RCU0 Status Register +#define REG_RCU0_CRCTL 0x3108C008 // RCU0 Core Reset Control Register +#define REG_RCU0_CRSTAT 0x3108C00C // RCU0 Core Reset Status Register +#define REG_RCU0_SIDIS 0x3108C01C // RCU0 System Interface Disable Register +#define REG_RCU0_SISTAT 0x3108C020 // RCU0 System Interface Status Register +#define REG_RCU0_BCODE 0x3108C028 // RCU0 Boot Code Register +#define REG_RCU0_MSG_SET 0x3108C070 // RCU0 Message Set Bits Register +#define REG_RCU0_SVECT1 0x3108C030 // Software Vector Register 1 +#define REG_RCU0_SVECT2 0x3108C034 // Software Vector Register 2 + +// SPU0 +#define REG_SPU0_CTL 0x3108B000 // SPU0 Control Register + +// LP0 +#define LP0_CTL 0x30FFE000 // LP0 Control Register + +// LP1 +#define LP1_CTL 0x30FFE100 // LP1 Control Register + +// PADS0 +#define REG_PADS0_BASE 0x31004600 // PADS Base Register +#define REG_PADS0_PCFG0 0x31004604 // PADS0 Peripheral Configuration0 Register +#define REG_PADS0_DAI0_IE 0x31004690 // PADS DAI0 IE Register +#define REG_PADS0_DAI1_IE 0x31004694 // PADS DAI1 IE Register +#define BITM_PADS_PCFG0_EMACRESET 0x00000004 // Reset Enable for RGMII +#define ENUM_PADS_PCFG0_EMACPHY_MII 0x00000000 // EMACPHYISEL: MII Interface +#define ENUM_PADS_PCFG0_EMACPHY_RGMII 0x00000008 // EMACPHYISEL: RGMII Interface +#define ENUM_PADS_PCFG0_EMACPHY_RMII 0x00000010 // EMACPHYISEL: RMII Interface +#define ENUM_PADS_PCFG0_EMAC0_RMII_CLK 0x00000000 // EMAC0: EMAC0_RMII CLK +#define ENUM_PADS_PCFG0_EMAC0_SCLK1 0x00000001 // EMAC0: SCLK +#define ENUM_PADS_PCFG0_EMAC0_EXT_CLK 0x00000002 // EMAC0: External Clock +#define ENUM_PADS_PCFG0_EMAC0_SCLK3 0x00000003 // EMAC0: SCLK + +#endif diff --git a/include/linux/soc/adi/system_config.h b/include/linux/soc/adi/system_config.h new file mode 100644 index 00000000000000..246271c7f341a7 --- /dev/null +++ b/include/linux/soc/adi/system_config.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Implementation of adi_system_config, potential replacement for syscon that + * generalizes it to support arbitrary regmap registration and requires the + * driver to be initialized first + * + * Copyright 2022-2024 - Analog Devices Inc. + */ + +#ifndef SOC_ADI_SYSTEM_CONFIG_H +#define SOC_ADI_SYSTEM_CONFIG_H + +#include +#include +#include + +struct adi_system_register { + u32 id; + u32 offset; + u32 mask; + u8 shift; + bool is_bits; +}; + +struct adi_system_config { + /* User configured */ + struct adi_system_register *registers; + unsigned int max_register; + size_t len; + + /* Internal data populated during usage */ + struct regmap_config config; + struct regmap *mmio_regmap; + struct device_node *np; + struct list_head list; + struct regmap *system_regmap; +}; + +/* + * All possible system register IDs across all platforms supported by this + * driver. + */ +enum adi_system_reg_id { + ADI_SYSTEM_REG_EMAC0_PTPCLK0 = 0, + ADI_SYSTEM_REG_EMAC0_EMACRESET, + ADI_SYSTEM_REG_EMAC0_PHYISEL, + ADI_SYSTEM_REG_CNT0UDSEL, + ADI_SYSTEM_REG_CNT0DGSEL, + ADI_SYSTEM_REG_TWI0VSEL, + ADI_SYSTEM_REG_TWI1VSEL, + ADI_SYSTEM_REG_TWI2VSEL, + ADI_SYSTEM_REG_PUMSIDLC, + ADI_SYSTEM_REG_PUMSIHL, + ADI_SYSTEM_REG_PUTMS, + ADI_SYSTEM_REG_EMAC0_AUXIE, + ADI_SYSTEM_REG_FAULT_DIS, + ADI_SYSTEM_REG_EMAC0_ENDIANNESS, + ADI_SYSTEM_REG_EMAC1_ENDIANNESS, + ADI_SYSTEM_REG_MSHC_CCLK_DIV_EN, + ADI_SYSTEM_REG_DAI0_IE, + ADI_SYSTEM_REG_DAI1_IE, + __ADI_SYSTEM_REG_COUNT +}; + +#endif diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 9c007a106330b9..ce3c50dfa5cacd 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -109,6 +109,9 @@ /* Xilinx uartlite */ #define PORT_UARTLITE 74 +/* Blackfin */ +#define PORT_BFIN 75 + /* Broadcom BCM7271 UART */ #define PORT_BCM7271 76