This enables support for Apple's in-house ARM SoC family, starting
with the Apple M1.
+config ARCH_BAIKAL
+ bool "Baikal Electronics SoC family"
+ select DW_APB_TIMER_OF
+ select GPIOLIB
+ select GPIO_DWAPB
+ select MULTIPLEXER
+ select OF_GPIO
+ select PINCTRL
+ help
+ This enables support for Baikal Electronics SoC family
+
menuconfig ARCH_BCM
bool "Broadcom SoC Support"
help
This enables support for Xilinx ZynqMP Family
+# The GPIO number here must be sorted by descending number.
+# In a case of multiplatform kernel we want the highest value
+# required by selected platforms
+config ARCH_NR_GPIO
+ int
+ default 0
+ help
+ Maximum number of GPIOs in the system.
+
+ If unsure, leave the default value.
+
endmenu # "Platform selection"
subdir-y += apm
subdir-y += apple
subdir-y += arm
+subdir-y += baikal
subdir-y += bitmain
subdir-y += broadcom
subdir-y += cavium
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+dtb-$(CONFIG_ARCH_BAIKAL) += bm1000-dbm10.dtb
+dtb-$(CONFIG_ARCH_BAIKAL) += bm1000-dbm20.dtb
+dtb-$(CONFIG_ARCH_BAIKAL) += bm1000-mbm10.dtb
+dtb-$(CONFIG_ARCH_BAIKAL) += bm1000-mbm20.dtb
+dtb-$(CONFIG_ARCH_BAIKAL) += bm1000-qemu-m.dtb
+dtb-$(CONFIG_ARCH_BAIKAL) += bs1000-dbs.dtb
+dtb-$(CONFIG_ARCH_BAIKAL) += bs1000-dbs-ov.dtb
+dtb-$(CONFIG_ARCH_BAIKAL) += bs1000-qemu-s.dtb
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree include file for BE-M1000 SoC CoreSight subsystem
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ */
+
+#include <dt-bindings/arm/coresight-cti-dt.h>
+
+#define CA57_DEBUG(cl, c, base, n) \
+ debug##c: debug@##base##10000 { \
+ compatible = "arm,coresight-cpu-debug", "arm,primecell"; \
+ reg = <0x0 0x##base##10000 0x0 0x1000>; \
+ clocks = <&cs_pclk>; \
+ clock-names = "apb_pclk"; \
+ cpu = <&cpu##c>; \
+ }; \
+ \
+ etm##c: etm@##base##40000 { \
+ compatible = "arm,coresight-etm4x", "arm,primecell"; \
+ reg = <0x0 0x##base##40000 0x0 0x1000>; \
+ clocks = <&cs_pclk>, <&cs_atclk>; \
+ clock-names = "apb_pclk", "atclk"; \
+ cpu = <&cpu##c>; \
+ \
+ out-ports { \
+ port { \
+ etm##c##_out_port: endpoint { \
+ remote-endpoint = \
+ <&funnel##cl##_in_port##n>; \
+ }; \
+ }; \
+ }; \
+ }; \
+ \
+ cti##cl##_##n: cti@##base##20000 { \
+ compatible = "arm,coresight-cti-v8-arch", "arm,coresight-cti", \
+ "arm,primecell"; \
+ reg = <0x0 0x##base##20000 0x0 0x1000>; \
+ clocks = <&cs_pclk>; \
+ clock-names = "apb_pclk"; \
+ \
+ cpu = <&cpu##c>; \
+ arm,cs-dev-assoc = <&etm##c>; \
+ }
+
+#define CA57_CS(cl, c0, c1, cs_base, debug0_base, debug1_base) \
+ CA57_DEBUG(cl,c0,debug0_base,0); \
+ CA57_DEBUG(cl,c1,debug1_base,1); \
+ \
+ funnel##cl: funnel##cl { \
+ /* \
+ * non-configurable replicators don't show up on the \
+ * AMBA bus. As such no need to add "arm,primecell". \
+ */ \
+ compatible = "arm,coresight-static-funnel"; \
+ \
+ out-ports { \
+ port { \
+ funnel##cl##_out_port: endpoint { \
+ remote-endpoint = \
+ <&etf##cl##_in_port>; \
+ }; \
+ }; \
+ }; \
+ \
+ in-ports { \
+ #address-cells = <1>; \
+ #size-cells = <0>; \
+ \
+ port@0 { \
+ reg = <0>; \
+ funnel##cl##_in_port0: endpoint { \
+ remote-endpoint = \
+ <&etm##c0##_out_port>; \
+ }; \
+ }; \
+ \
+ port@1 { \
+ reg = <1>; \
+ funnel##cl##_in_port1: endpoint { \
+ remote-endpoint = \
+ <&etm##c1##_out_port>; \
+ }; \
+ }; \
+ }; \
+ }; \
+ \
+ etf##cl: etf@##cs_base { /* ETF 512B */ \
+ compatible = "arm,coresight-tmc", "arm,primecell"; \
+ reg = <0x0 0x##cs_base 0x0 0x1000>; \
+ clocks = <&cs_pclk>, <&cs_atclk>; \
+ clock-names = "apb_pclk", "atclk"; \
+ \
+ out-ports { \
+ port { \
+ etf##cl##_out_port: endpoint { \
+ remote-endpoint = \
+ <&funnel_in_port##cl>; \
+ }; \
+ }; \
+ }; \
+ \
+ in-ports { \
+ port { \
+ etf##cl##_in_port: endpoint { \
+ remote-endpoint = \
+ <&funnel##cl##_out_port>; \
+ }; \
+ }; \
+ }; \
+ }
+
+/ {
+ clocks {
+ cs_pclk: cs_pclk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <250000000>;
+ clock-output-names = "cs_pclk";
+ };
+
+ cs_atclk: cs_atclk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <250000000>;
+ clock-output-names = "cs_atclk";
+ };
+ };
+
+ soc {
+ /* CA57 MM's */
+
+ CA57_CS(0,0,1,5009000,52,53);
+ CA57_CS(1,2,3,500a000,54,55);
+ CA57_CS(2,4,5,500b000,56,57);
+ CA57_CS(3,6,7,500c000,58,59);
+
+ /* CoreSight MM */
+
+ tpiu: tpiu@500e000 {
+ compatible = "arm,coresight-tpiu", "arm,primecell";
+ reg = <0x0 0x500e000 0x0 0x1000>;
+ clocks = <&cs_pclk>, <&cs_atclk>;
+ clock-names = "apb_pclk", "atclk";
+
+ in-ports {
+ port {
+ tpiu_in_port: endpoint {
+ remote-endpoint =
+ <&etb_out_port>;
+ };
+ };
+ };
+ };
+
+ etb: etb@5007000 {
+ compatible = "arm,coresight-tmc", "arm,primecell";
+ reg = <0x0 0x5007000 0x0 0x1000>;
+ clocks = <&cs_pclk>, <&cs_atclk>;
+ clock-names = "apb_pclk", "atclk";
+
+ out-ports {
+ port {
+ etb_out_port: endpoint {
+ remote-endpoint =
+ <&tpiu_in_port>;
+ };
+ };
+ };
+
+ in-ports {
+ port {
+ etb_in_port: endpoint {
+ remote-endpoint =
+ <&replicator_out_port1>;
+ };
+ };
+ };
+ };
+
+ etr: etr@5008000 {
+ compatible = "arm,coresight-tmc", "arm,primecell";
+ reg = <0x0 0x5008000 0x0 0x1000>;
+ clocks = <&cs_pclk>, <&cs_atclk>;
+ clock-names = "apb_pclk", "atclk";
+
+ in-ports {
+ port {
+ etr_in_port: endpoint {
+ remote-endpoint =
+ <&replicator_out_port0>;
+ };
+ };
+ };
+ };
+
+ replicator: replicator@5010000 {
+ compatible = "arm,coresight-dynamic-replicator", "arm,primecell";
+ reg = <0x0 0x5010000 0x0 0x1000>;
+ clocks = <&cs_pclk>, <&cs_atclk>;
+ clock-names = "apb_pclk", "atclk";
+
+ out-ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* replicator output ports */
+ port@0 {
+ reg = <0>;
+ replicator_out_port0: endpoint {
+ remote-endpoint =
+ <&etr_in_port>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ replicator_out_port1: endpoint {
+ remote-endpoint =
+ <&etb_in_port>;
+ };
+ };
+ };
+
+ in-ports {
+ port {
+ replicator_in_port: endpoint {
+ remote-endpoint =
+ <&etf_out_port>;
+ };
+ };
+ };
+ };
+
+ etf: etf@500d000 { /* ETF 2048B */
+ compatible = "arm,coresight-tmc", "arm,primecell";
+ reg = <0x0 0x500d000 0x0 0x1000>;
+ clocks = <&cs_pclk>, <&cs_atclk>;
+ clock-names = "apb_pclk", "atclk";
+
+ out-ports {
+ port {
+ etf_out_port: endpoint {
+ remote-endpoint =
+ <&replicator_in_port>;
+ };
+ };
+ };
+
+ in-ports {
+ port {
+ etf_in_port: endpoint {
+ remote-endpoint =
+ <&funnel_out_port>;
+ };
+ };
+ };
+ };
+
+ funnel: funnel@5011000 {
+ compatible = "arm,coresight-dynamic-funnel", "arm,primecell";
+ reg = <0x0 0x5011000 0x0 0x1000>;
+ clocks = <&cs_pclk>, <&cs_atclk>;
+ clock-names = "apb_pclk", "atclk";
+
+ out-ports {
+ port {
+ funnel_out_port: endpoint {
+ remote-endpoint =
+ <&etf_in_port>;
+ };
+ };
+ };
+
+ in-ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ funnel_in_port0: endpoint {
+ remote-endpoint =
+ <&etf0_out_port>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ funnel_in_port1: endpoint {
+ remote-endpoint =
+ <&etf1_out_port>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+ funnel_in_port2: endpoint {
+ remote-endpoint =
+ <&etf2_out_port>;
+ };
+ };
+
+ port@3 {
+ reg = <3>;
+ funnel_in_port3: endpoint {
+ remote-endpoint =
+ <&etf3_out_port>;
+ };
+ };
+
+ port@4 {
+ reg = <4>;
+ funnel_in_port4: endpoint {
+ remote-endpoint =
+ <&stm0_out_port>;
+ };
+ };
+#if 0
+ /*
+ * Disable stm1 port due to coresight-stm driver
+ * doesn't initialize stm node without stimulus area.
+ */
+ port@05 {
+ reg = <5>;
+ funnel_in_port5: endpoint {
+ remote-endpoint =
+ <&stm1_out_port>;
+ };
+ };
+#endif
+ };
+ };
+
+ stm0: stm@5001000 {
+ compatible = "arm,coresight-stm", "arm,primecell";
+ reg = <0x0 0x5001000 0x0 0x1000>,
+ <0x0 0x7000000 0x0 0x1000000>;
+ reg-names = "stm-base", "stm-stimulus-base";
+ clocks = <&cs_pclk>, <&cs_atclk>;
+ clock-names = "apb_pclk", "atclk";
+
+ out-ports {
+ port {
+ stm0_out_port: endpoint {
+ remote-endpoint =
+ <&funnel_in_port4>;
+ };
+ };
+ };
+ };
+#if 0
+ /*
+ * Disable stm1 node due to coresight-stm driver
+ * doesn't initialize stm node without stimulus area.
+ */
+ stm1: stm@5002000 {
+ compatible = "arm,coresight-stm", "arm,primecell";
+ reg = <0x0 0x5002000 0x0 0x1000>;
+ reg-names = "stm-base";
+ clocks = <&cs_pclk>, <&cs_atclk>;
+ clock-names = "apb_pclk", "atclk";
+
+ out-ports {
+ port {
+ stm1_out_port: endpoint {
+ remote-endpoint =
+ <&funnel_in_port1>;
+ };
+ };
+ };
+ };
+#endif
+ cti0: cti@5004000 {
+ compatible = "arm,coresight-cti", "arm,primecell";
+ reg = <0x0 0x5004000 0x0 0x1000>;
+ clocks = <&cs_pclk>;
+ clock-names = "apb_pclk";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ trig-conns@0 {
+ reg = <0>;
+ arm,trig-in-sigs = <0>;
+ arm,trig-conn-name = "ccn_dbgwatchtrig";
+ };
+
+ trig-conns@1 {
+ reg = <1>;
+ arm,trig-in-sigs = <2 3 4 5>;
+ arm,trig-in-types = <STM_TOUT_SPTE STM_TOUT_SW
+ STM_TOUT_HETE STM_ASYNCOUT>;
+ arm,trig-out-sigs = <6 7>;
+ arm,trig-out-types = <STM_HWEVENT STM_HWEVENT>;
+ arm,cs-dev-assoc = <&stm0>;
+ };
+
+ trig-conns@3 {
+ reg = <3>;
+ arm,trig-out-sigs = <0 1>;
+ arm,trig-out-types = <GEN_HALTREQ
+ GEN_RESTARTREQ>;
+ arm,trig-conn-name = "cctr";
+ };
+
+ trig-conns@4 {
+ reg = <4>;
+ arm,trig-out-sigs = <2>;
+ arm,trig-conn-name = "ccn_pmusnapshot";
+ };
+
+ trig-conns@5 {
+ reg = <5>;
+ arm,trig-out-sigs = <3 4>;
+ arm,trig-out-types = <GEN_INTREQ GEN_INTREQ>;
+ arm,trig-conn-name = "gic_cs_ppii";
+ };
+#if 0
+ /*
+ * Disable stm1 events due to coresight-stm driver
+ * doesn't initialize stm node without stimulus area.
+ */
+ trig-conns@6 {
+ reg = <6>;
+ arm,trig-in-sigs = <6 7>;
+ arm,trig-in-types = <STM_TOUT_HETE STM_ASYNCOUT>;
+ arm,cs-dev-assoc = <&stm1>;
+ };
+#endif
+ };
+
+ cti1: cti@5005000 {
+ compatible = "arm,coresight-cti", "arm,primecell";
+ reg = <0x0 0x5005000 0x0 0x1000>;
+ clocks = <&cs_pclk>;
+ clock-names = "apb_pclk";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ trig-conns@0 {
+ reg = <0>;
+ arm,trig-in-sigs = <0 1>;
+ arm,trig-in-types = <SNK_ACQCOMP SNK_FULL>;
+ arm,trig-out-sigs = <0 1>;
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>;
+ arm,cs-dev-assoc = <&etf0>;
+ };
+
+ trig-conns@1 {
+ reg = <1>;
+ arm,trig-in-sigs = <2 3>;
+ arm,trig-in-types = <SNK_ACQCOMP SNK_FULL>;
+ arm,trig-out-sigs = <2 3>;
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>;
+ arm,cs-dev-assoc = <&etf1>;
+ };
+
+ trig-conns@2 {
+ reg = <2>;
+ arm,trig-in-sigs = <4 5>;
+ arm,trig-in-types = <SNK_ACQCOMP SNK_FULL>;
+ arm,trig-out-sigs = <4 5>;
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>;
+ arm,cs-dev-assoc = <&etf2>;
+ };
+
+ trig-conns@3 {
+ reg = <3>;
+ arm,trig-in-sigs = <6 7>;
+ arm,trig-in-types = <SNK_ACQCOMP SNK_FULL>;
+ arm,trig-out-sigs = <6 7>;
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>;
+ arm,cs-dev-assoc = <&etf3>;
+ };
+ };
+
+ cti2: cti@5006000 {
+ compatible = "arm,coresight-cti", "arm,primecell";
+ reg = <0x0 0x5006000 0x0 0x1000>;
+ clocks = <&cs_pclk>;
+ clock-names = "apb_pclk";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ trig-conns@0 {
+ reg = <0>;
+ arm,trig-in-sigs = <0 1>;
+ arm,trig-in-types = <SNK_ACQCOMP SNK_FULL>;
+ arm,trig-out-sigs = <0 1>;
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>;
+ arm,cs-dev-assoc = <&etr>;
+ };
+
+ trig-conns@1 {
+ reg = <1>;
+ arm,trig-in-sigs = <2 3>;
+ arm,trig-in-types = <SNK_ACQCOMP SNK_FULL>;
+ arm,trig-out-sigs = <2 3>;
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>;
+ arm,cs-dev-assoc = <&etb>;
+ };
+
+ trig-conns@2 {
+ reg = <2>;
+ arm,trig-in-sigs = <4 5>;
+ arm,trig-in-types = <SNK_ACQCOMP SNK_FULL>;
+ arm,trig-out-sigs = <4 5>;
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>;
+ arm,cs-dev-assoc = <&etf>;
+ };
+
+ trig-conns@3 {
+ reg = <3>;
+ arm,trig-out-sigs = <6 7>;
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>;
+ arm,cs-dev-assoc = <&tpiu>;
+ };
+ };
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree source for Baikal Electronics DBM boards
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ */
+
+#include <dt-bindings/input/input.h>
+#include "bm1000.dtsi"
+
+/ {
+ aliases {
+ ethernet0 = &gmac0;
+ ethernet1 = &gmac1;
+ mdio-gpio0 = &mdio_gpio;
+ };
+
+ chosen { };
+
+ buttons-backlight {
+ compatible = "gpio-keys";
+ autorepeat;
+ button-brightness-down {
+ label = "Brightness Down Button";
+ linux,code = <KEY_BRIGHTNESSDOWN>;
+ gpios = <&porta 18 GPIO_ACTIVE_LOW>;
+ debounce-interval = <50>;
+ };
+ button-brightness-up {
+ label = "Brightness Up Button";
+ linux,code = <KEY_BRIGHTNESSUP>;
+ gpios = <&porta 17 GPIO_ACTIVE_LOW>;
+ debounce-interval = <50>;
+ };
+ button-brightness-toggle {
+ label = "Brightness Toggle Button";
+ linux,code = <KEY_BRIGHTNESS_TOGGLE>;
+ gpios = <&porta 31 GPIO_ACTIVE_LOW>;
+ debounce-interval = <50>;
+ };
+ };
+
+ panel {
+ /* Change status to "okay" to make use of LVDS LCD panel */
+ status = "disabled";
+ compatible = "panel-lvds";
+ width-mm = <223>;
+ height-mm = <125>;
+ data-mapping = "vesa-24";
+ panel-timing {
+ /* 1920x1080 @ 60 Hz */
+ /* Replace values below with actual panel timings */
+ clock-frequency = <148500000>;
+ hactive = <1920>;
+ vactive = <1080>;
+ hsync-len = <44>;
+ hfront-porch = <88>;
+ hback-porch = <148>;
+ vsync-len = <5>;
+ vfront-porch = <4>;
+ vback-porch = <36>;
+ };
+ port {
+ panel_in: endpoint@0 {
+ remote-endpoint = <&vdu_lvds_out>;
+ };
+ };
+ };
+
+ soc {
+ mdio_gpio: mdio {
+ compatible = "baikal,mdio-gpio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ mdc-pin = <&porta 28 GPIO_ACTIVE_HIGH>;
+ mdio-pin = <&porta 29 GPIO_ACTIVE_HIGH>;
+ rst-pin = <&porta 30 GPIO_ACTIVE_LOW>;
+ clocks = <&gpio_clk>;
+ clock-names = "gpioclk";
+ status = "okay";
+
+ ethphy2: ethernet-phy@c {
+ compatible = "ethernet-phy-ieee802.3-c45";
+ reg = <0xc>;
+ phy-mode = "xgmii";
+ mv,line-mode = "KR";
+ mv,host-mode = "KX4";
+ };
+
+ ethphy3: ethernet-phy@e {
+ compatible = "ethernet-phy-ieee802.3-c45";
+ reg = <0xe>;
+ phy-mode = "xgmii";
+ mv,line-mode = "KR";
+ mv,host-mode = "KX4";
+ };
+ };
+ };
+
+ sound {
+ compatible = "baikal,snd-soc-baikal";
+ baikal,cpu-dai = <&i2s>;
+ baikal,audio-codec = <&tlv320aic3x>;
+ baikal,dai-name = "tlv320aic3x";
+ baikal,codec-name = "tlv320aic3x-hifi";
+ baikal,stream-name = "tlv320aic3x hifi";
+ };
+
+ sfp-xgmac0 {
+ compatible = "sff,sfp";
+ i2c-bus = <&i2c1>;
+ };
+
+ sfp-xgmac1 {
+ compatible = "sff,sfp";
+ i2c-bus = <&i2c1>;
+ };
+};
+
+&dmac_lsp {
+ status = "okay";
+};
+
+&dmac_m2m {
+ status = "okay";
+};
+
+&gmac0 {
+ status = "okay";
+ phy-handle = <ðphy0>;
+ phy-mode = "rgmii-id";
+};
+
+&gmac1 {
+ status = "okay";
+ phy-handle = <ðphy1>;
+ phy-mode = "rgmii-id";
+};
+
+&gpio32 {
+ status = "okay";
+};
+
+&hda {
+ status = "okay";
+};
+
+&hdmi {
+ status = "okay";
+};
+
+&i2c1 {
+ status = "okay";
+
+ tlv320aic3x: tlv320aic3x@18 {
+ #sound-dai-cells = <0>;
+ compatible = "ti,tlv320aic3x";
+ reg = <0x18>;
+ reset-gpios = <&porta 4 GPIO_ACTIVE_LOW>;
+ status = "okay";
+ ai3x-micbias-vg = <1>;
+ ai3x-ocmv = <1>;
+ };
+};
+
+&i2c2 {
+ status = "okay";
+};
+
+&i2s {
+ status = "okay";
+ sound-dai = <&tlv320aic3x>;
+};
+
+&mdio0 {
+ ethphy0: ethernet-phy@3 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x3>;
+ txd0-skew-ps = <0>;
+ txd1-skew-ps = <0>;
+ txd2-skew-ps = <0>;
+ txd3-skew-ps = <0>;
+ txc-skew-ps = <0xff>;
+ };
+};
+
+&mdio1 {
+ ethphy1: ethernet-phy@3 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x3>;
+ txd0-skew-ps = <0>;
+ txd1-skew-ps = <0>;
+ txd2-skew-ps = <0>;
+ txd3-skew-ps = <0>;
+ txc-skew-ps = <0xff>;
+ };
+};
+
+#if 0
+&mmc {
+ /* eMMC */
+ no-sdio;
+ no-sd;
+ non-removable;
+ bus-width = <8>;
+ max-frequency = <200000000>;
+ status = "okay";
+}
+#else
+&mmc {
+ /* SD */
+ no-mmc;
+ no-sdio;
+ disable-wp;
+ bus-width = <4>;
+ max-frequency = <25000000>;
+ status = "okay";
+};
+#endif
+
+&pcie0 {
+ status = "okay";
+ bm1000,gen3-eq-fb-mode = <0>;
+ bm1000,phy-rx-ctle = <0xf4>;
+ bm1000,phy-rx-dfe = <0>;
+};
+
+&pcie1 {
+ status = "okay";
+ bm1000,gen3-eq-fb-mode = <0>;
+ bm1000,phy-rx-ctle = <0x64>;
+ bm1000,phy-rx-dfe = <0>;
+};
+
+&pcie2 {
+ status = "okay";
+ bm1000,gen3-eq-fb-mode = <0>;
+ bm1000,phy-rx-ctle = <0x34>;
+ bm1000,phy-rx-dfe = <0>;
+};
+
+&pvt_cluster0 {
+ status = "okay";
+};
+
+&pvt_cluster1 {
+ status = "okay";
+};
+
+&pvt_cluster2 {
+ status = "okay";
+};
+
+&pvt_cluster3 {
+ status = "okay";
+};
+
+&pvt_mali {
+ status = "okay";
+};
+
+&sata0 {
+ status = "okay";
+};
+
+&sata1 {
+ status = "okay";
+};
+
+&smbus1 {
+ status = "okay";
+};
+
+&smbus2 {
+ status = "okay";
+};
+
+&spi0 {
+ num-cs = <4>;
+ cs-gpios = <&porta 24 GPIO_ACTIVE_LOW>, /* SS0 XP8 - DD53 normal flash */
+ <&porta 25 GPIO_ACTIVE_LOW>, /* SS1 XP9 */
+ <&porta 26 GPIO_ACTIVE_LOW>, /* SS2 XP10 */
+ <&porta 27 GPIO_ACTIVE_LOW>; /* SS3 XP11 */
+ status = "okay";
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <12500000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <1>;
+ status = "okay";
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "bl1";
+ reg = <0x000000 0x40000>;
+ };
+
+ partition@40000 {
+ label = "dtb";
+ reg = <0x040000 0x40000>;
+ };
+
+ partition@80000 {
+ label = "uefi-vars";
+ reg = <0x080000 0xc0000>;
+ };
+
+ partition@140000 {
+ label = "fip";
+ reg = <0x140000 0x6c0000>;
+ };
+ };
+ };
+};
+
+&spi1 {
+ num-cs = <8>;
+ cs-gpios = <&porta 0 GPIO_ACTIVE_LOW>;
+ status = "okay";
+
+ espi_test0@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>; /* CS #0 */
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <10000000>;
+ spi-cpha;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <1>;
+ status = "okay";
+ };
+};
+
+&timer1 {
+ status = "okay";
+};
+
+&timer2 {
+ status = "okay";
+};
+
+&timer3 {
+ status = "okay";
+};
+
+&timer4 {
+ status = "okay";
+};
+
+&uart1 {
+ status = "okay";
+};
+
+&uart2 {
+ status = "okay";
+};
+
+&usb2 {
+ status = "okay";
+};
+
+&usb3 {
+ status = "okay";
+};
+
+&vdec {
+ status = "okay";
+};
+
+&vdu {
+ /*
+ * 'lvds-lanes' property is mandatory to enable LVDS. Valid values are 1, 2 or 4.
+ * It is also mandatory to provide actual values for 'enable-gpios' property.
+ * Also make sure that panel's 'status' (see above) is "okay".
+ */
+ status = "okay";
+ enable-gpios = <&porta 16 GPIO_ACTIVE_LOW>;
+ lvds-lanes = <2>;
+ backlight {
+ min-brightness-level = <10>;
+ default-brightness-level = <60>;
+ brightness-level-step = <2>;
+ pwm-frequency = <20000>;
+ };
+ ports {
+ port@1 {
+ reg = <1>;
+ vdu_lvds_out: endpoint@0 {
+ remote-endpoint = <&panel_in>;
+ };
+ };
+ };
+};
+
+&xgmac0 {
+ status = "okay";
+ ext-phy-handle = <ðphy2>;
+};
+
+&xgmac1 {
+ status = "okay";
+ ext-phy-handle = <ðphy3>;
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree source for Baikal Electronics DBM 1.0 board
+ * Copyright (C) 2022 Baikal Electronics, JSC
+ */
+
+/dts-v1/;
+
+#include "bm1000-dbm.dtsi"
+
+/ {
+ model = "Baikal Electronics DBM 1.0";
+ compatible = "baikal,dbm10", "baikal,bm1000";
+};
+
+&i2c1 {
+ rtc@56 {
+ compatible = "abracon,abeoz9";
+ reg = <0x56>;
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree source for Baikal Electronics DBM 2.0 board
+ * Copyright (C) 2022 Baikal Electronics, JSC
+ */
+
+/dts-v1/;
+
+#include "bm1000-dbm.dtsi"
+
+/ {
+ model = "Baikal Electronics DBM 2.0";
+ compatible = "baikal,dbm20", "baikal,bm1000";
+};
+
+&i2c2 {
+ rtc@56 {
+ compatible = "abracon,abeoz9";
+ reg = <0x56>;
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree include file for MBM-compatible boards
+ * Copyright (C) 2021-2023 Baikal Electronics, JSC
+ */
+
+#include "bm1000.dtsi"
+
+/ {
+ aliases {
+ ethernet0 = &gmac0;
+ ethernet1 = &gmac1;
+ };
+
+ chosen { };
+
+ leds {
+ compatible = "gpio-leds";
+ led0 {
+ gpios = <&porta 8 GPIO_ACTIVE_HIGH>;
+ default-state = "keep";
+ };
+ };
+
+ sound {
+ compatible = "simple-audio-card";
+ simple-audio-card,name = "MITX-Sound-Card";
+ simple-audio-card,bitclock-master = <&codec0>;
+ simple-audio-card,frame-master = <&codec0>;
+ simple-audio-card,widgets =
+ "Microphone", "Mic Jack",
+ "Headphone", "Headphones",
+ "Speaker", "AUX Out",
+ "Line", "Line In";
+ simple-audio-card,routing =
+ "Headphones", "RHP",
+ "Headphones", "LHP",
+ "AUX Out", "AUXOUT1",
+ "AUX Out", "AUXOUT2",
+ "L2", "Mic Jack",
+ "R2", "Mic Jack",
+ "LAUX", "Line In",
+ "RAUX", "Line In";
+ simple-audio-card,mic-det-gpio = <&porta 26 GPIO_ACTIVE_LOW>;
+ simple-audio-card,format = "i2s";
+ simple-audio-card,cpu {
+ sound-dai = <&i2s>;
+ };
+ codec0: simple-audio-card,codec {
+ sound-dai = <&nau8822 0>;
+ };
+ };
+};
+
+&dmac_lsp {
+ status = "okay";
+};
+
+&dmac_m2m {
+ status = "okay";
+};
+
+&gmac0 {
+ status = "okay";
+ phy-handle = <ðphy0>;
+ phy-mode = "rgmii-id";
+};
+
+&gmac1 {
+ status = "okay";
+ phy-handle = <ðphy1>;
+ phy-mode = "rgmii-id";
+};
+
+&gpio32 {
+ status = "okay";
+};
+
+&hdmi {
+ status = "okay";
+};
+
+&i2c1 {
+ status = "okay";
+
+ bmc@8 {
+ compatible = "tp,mitx2-bmc";
+ reg = <0x08>;
+ };
+
+ nau8822: nau8822@1a {
+ compatible = "nuvoton,nau8822";
+ #sound-dai-cells = <1>;
+ reg = <0x1a>;
+ };
+
+ gpio@50 {
+ compatible = "nxp,pca9670";
+ #gpio-cells = <2>;
+ gpio-controller;
+ reg = <0x50>;
+ };
+
+ rtc@51 {
+ compatible = "nxp,pcf2129", "nxp,pcf2127";
+ reg = <0x51>;
+ };
+
+ /* FRU */
+ eeprom@53 {
+ compatible = "atmel,24c32";
+ pagesize = <32>;
+ reg = <0x53>;
+ };
+};
+
+&i2c2 {
+ status = "okay";
+};
+
+&i2s {
+ status = "okay";
+};
+
+&mdio0 {
+ ethphy0: ethernet-phy@3 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x3>;
+ txd0-skew-ps = <0>;
+ txd1-skew-ps = <0>;
+ txd2-skew-ps = <0>;
+ txd3-skew-ps = <0>;
+ txc-skew-ps = <0xff>;
+ };
+};
+
+&mdio1 {
+ ethphy1: ethernet-phy@3 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x3>;
+ txd0-skew-ps = <0>;
+ txd1-skew-ps = <0>;
+ txd2-skew-ps = <0>;
+ txd3-skew-ps = <0>;
+ txc-skew-ps = <0xff>;
+ };
+};
+
+&mmc {
+ /* SD */
+ no-mmc;
+ no-sdio;
+ disable-wp;
+ bus-width = <4>;
+ max-frequency = <25000000>;
+ status = "okay";
+};
+
+&pcie0 {
+ status = "okay";
+ reset-gpios = <&porta 6 GPIO_ACTIVE_LOW>;
+ bm1000,gen3-eq-fb-mode = <0>;
+ bm1000,phy-rx-ctle = <0x34>;
+ bm1000,phy-rx-dfe = <0>;
+};
+
+&pcie2 {
+ status = "okay";
+ reset-gpios = <&porta 3 GPIO_ACTIVE_LOW>;
+ bm1000,gen3-eq-fb-mode = <0>;
+ bm1000,phy-rx-ctle = <0x34>;
+ bm1000,phy-rx-dfe = <0>;
+};
+
+&porta {
+ pcieclk {
+ gpio-hog;
+ gpios = <1 GPIO_ACTIVE_LOW>;
+ output-high;
+ line-name = "pcie-x8-clock";
+ };
+};
+
+&pvt_cluster0 {
+ status = "okay";
+};
+
+&pvt_cluster1 {
+ status = "okay";
+};
+
+&pvt_cluster2 {
+ status = "okay";
+};
+
+&pvt_cluster3 {
+ status = "okay";
+};
+
+&pvt_mali {
+ status = "okay";
+};
+
+&sata0 {
+ status = "okay";
+};
+
+&sata1 {
+ status = "okay";
+};
+
+&smbus1 {
+ status = "okay";
+};
+
+&smbus2 {
+ status = "okay";
+};
+
+&spi0 {
+ num-cs = <4>;
+ cs-gpios = <0>;
+ status = "okay";
+};
+
+&timer1 {
+ status = "okay";
+};
+
+&timer2 {
+ status = "okay";
+};
+
+&timer3 {
+ status = "okay";
+};
+
+&timer4 {
+ status = "okay";
+};
+
+&uart1 {
+ status = "okay";
+};
+
+&uart2 {
+ status = "okay";
+};
+
+&usb2 {
+ status = "okay";
+};
+
+&usb3 {
+ status = "okay";
+};
+
+&vdec {
+ status = "okay";
+};
+
+&vdu {
+ status = "okay";
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree source for MBM 1.0 compatible boards:
+ * - TP-TF307-MB-A0 Rev.1.0 (BM1BM1-A)
+ * - TF307-MB-S-C Rev.3.0
+ *
+ * Copyright (C) 2021-2022 Baikal Electronics, JSC
+ */
+
+/dts-v1/;
+
+#include "bm1000-mbm.dtsi"
+
+/ {
+ model = "Baikal Electronics MBM 1.0";
+ compatible = "baikal,mbm10", "baikal,bm1000";
+
+ sound {
+ simple-audio-card,hp-det-gpio = <&porta 27 GPIO_ACTIVE_HIGH>;
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree source for MBM 2.0 compatible boards:
+ * - TF307-MB-S-D Rev.4.0 (BM1BM1-D)
+ *
+ * Copyright (C) 2021-2023 Baikal Electronics, JSC
+ */
+
+/dts-v1/;
+
+#include <dt-bindings/input/input.h>
+#include "bm1000-mbm.dtsi"
+
+/ {
+ model = "Baikal Electronics MBM 2.0";
+ compatible = "baikal,mbm20", "baikal,bm1000";
+
+ buttons-backlight {
+ compatible = "gpio-keys";
+ autorepeat;
+ button-brightness-down {
+ label = "Brightness Down Button";
+ linux,code = <KEY_BRIGHTNESSDOWN>;
+ gpios = <&porta 18 GPIO_ACTIVE_LOW>;
+ debounce-interval = <50>;
+ };
+ button-brightness-up {
+ label = "Brightness Up Button";
+ linux,code = <KEY_BRIGHTNESSUP>;
+ gpios = <&porta 17 GPIO_ACTIVE_LOW>;
+ debounce-interval = <50>;
+ };
+ button-brightness-toggle {
+ label = "Brightness Toggle Button";
+ linux,code = <KEY_BRIGHTNESS_TOGGLE>;
+ gpios = <&porta 31 GPIO_ACTIVE_LOW>;
+ debounce-interval = <50>;
+ };
+ };
+
+ panel {
+ /*
+ * In order to utilize LVDS LCD panel, make sure that
+ * 'status' is "okay" along with &vdu 'status' (see below).
+ */
+ status = "disabled";
+ compatible = "panel-lvds";
+ width-mm = <223>;
+ height-mm = <125>;
+ data-mapping = "vesa-24";
+ panel-timing {
+ /* 1920x1080 @ 60 Hz */
+ /* Replace values below with actual panel timings */
+ clock-frequency = <148500000>;
+ hactive = <1920>;
+ vactive = <1080>;
+ hsync-len = <44>;
+ hfront-porch = <88>;
+ hback-porch = <148>;
+ vsync-len = <5>;
+ vfront-porch = <4>;
+ vback-porch = <36>;
+ };
+ port {
+ panel_in: endpoint@0 {
+ remote-endpoint = <&vdu_lvds_out>;
+ };
+ };
+ };
+
+ sound {
+ simple-audio-card,hp-det-gpio = <&porta 29 GPIO_ACTIVE_HIGH>;
+ };
+};
+
+&gmac0 {
+ snps,reset-gpios = <&porta 19 GPIO_ACTIVE_LOW>;
+ snps,reset-delays-us = <0 10000 50000>;
+};
+
+&gmac1 {
+ snps,reset-gpios = <&porta 20 GPIO_ACTIVE_LOW>;
+ snps,reset-delays-us = <0 10000 50000>;
+};
+
+&vdu {
+ /*
+ * 'lvds-lanes' property is mandatory to enable LVDS. Valid values are 1, 2 or 4.
+ * It is also mandatory to provide actual values for 'enable-gpios' property.
+ * Also make sure that panel's 'status' (see above) is "okay".
+ */
+ enable-gpios = <&porta 16 GPIO_ACTIVE_LOW>;
+ lvds-lanes = <2>;
+ backlight {
+ min-brightness-level = <10>;
+ default-brightness-level = <60>;
+ brightness-level-step = <2>;
+ pwm-frequency = <20000>;
+ };
+ ports {
+ port@1 {
+ reg = <1>;
+ vdu_lvds_out: endpoint@0 {
+ remote-endpoint = <&panel_in>;
+ };
+ };
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree source for Baikal Electronics QEMU-M virtual platform
+ * Copyright (C) 2018-2023 Baikal Electronics, JSC
+ */
+
+/dts-v1/;
+
+#include "bm1000.dtsi"
+
+/ {
+ model = "Baikal Electronics QEMU-M";
+ compatible = "baikal,qemu-m", "baikal,bm1000";
+
+ aliases {
+ ethernet0 = &gmac0;
+ ethernet1 = &gmac1;
+ };
+
+ chosen { };
+
+ memory@80000000 {
+ device_type = "memory";
+ reg = <0x00000000 0x80000000 0x0 0x80000000>; /* 2 GiB */
+ };
+
+ /*
+ * Device is necessary for UEFI to boot on QEMU,
+ * need to replace it with something later.
+ */
+ flash@0 {
+ compatible = "cfi-flash";
+ reg = <0x0 0x4000000 0x0 0x4000000>;
+ bank-width = <0x4>;
+ };
+
+ panel: panel {
+ compatible = "auo,b133htn01"; /* 1920x1080 */
+ port {
+ lcd_panel: endpoint {
+ remote-endpoint = <&vdu_pads>;
+ };
+ };
+ };
+};
+
+&gmac0 {
+ status = "okay";
+ phy-handle = <ðphy0>;
+ phy-mode = "rgmii-id";
+};
+
+&gmac1 {
+ status = "okay";
+ phy-handle = <ðphy1>;
+ phy-mode = "rgmii-id";
+};
+
+&mdio0 {
+ ethphy0: ethernet-phy@3 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x3>;
+ };
+};
+
+&mdio1 {
+ ethphy1: ethernet-phy@3 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x3>;
+ };
+};
+
+&mmc {
+ /* SD */
+ no-mmc;
+ no-sdio;
+ disable-wp;
+ bus-width = <4>;
+ max-frequency = <25000000>;
+ status = "okay";
+
+};
+
+&sata0 {
+ status = "okay";
+};
+
+&sata1 {
+ status = "okay";
+};
+
+&spi0 {
+ num-cs = <6>;
+ status = "okay";
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <12500000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <1>;
+ status = "okay";
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "bl1";
+ reg = <0x000000 0x40000>;
+ };
+
+ partition@40000 {
+ label = "dtb";
+ reg = <0x040000 0x40000>;
+ };
+
+ partition@80000 {
+ label = "uefi-vars";
+ reg = <0x080000 0xc0000>;
+ };
+
+ partition@140000 {
+ label = "fip";
+ reg = <0x140000 0x6c0000>;
+ };
+ };
+ };
+};
+
+&uart1 {
+ status = "okay";
+ clocks = <&avlsp_cmu0 1>, <&apb_clk>, <&apb_clk>;
+ clock-names = "soc_uartclk", "apb_pclk", "baudclk";
+};
+
+&vdu {
+ port {
+ vdu_pads: endpoint {
+ remote-endpoint = <&lcd_panel>;
+ };
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree include file for BE-M1000 SoC
+ * Copyright (C) 2017-2023 Baikal Electronics, JSC
+ */
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+#define CLUSTER_OPP_TABLE(idx) \
+ cluster##idx##_opp: opp-table-cluster##idx { \
+ compatible = "operating-points-v2"; \
+ opp-shared; \
+ \
+ opp-500000000 { \
+ opp-hz = /bits/ 64 <500000000>; \
+ clock-latency-ns = <10000000>; \
+ }; \
+ opp-600000000 { \
+ opp-hz = /bits/ 64 <600000000>; \
+ clock-latency-ns = <10000000>; \
+ }; \
+ opp-700000000 { \
+ opp-hz = /bits/ 64 <700000000>; \
+ clock-latency-ns = <10000000>; \
+ }; \
+ opp-800000000 { \
+ opp-hz = /bits/ 64 <800000000>; \
+ clock-latency-ns = <10000000>; \
+ }; \
+ opp-900000000 { \
+ opp-hz = /bits/ 64 <900000000>; \
+ clock-latency-ns = <10000000>; \
+ }; \
+ opp-1000000000 { \
+ opp-hz = /bits/ 64 <1000000000>; \
+ clock-latency-ns = <10000000>; \
+ }; \
+ opp-1100000000 { \
+ opp-hz = /bits/ 64 <1100000000>; \
+ clock-latency-ns = <10000000>; \
+ }; \
+ opp-1200000000 { \
+ opp-hz = /bits/ 64 <1200000000>; \
+ clock-latency-ns = <10000000>; \
+ }; \
+ opp-1300000000 { \
+ opp-hz = /bits/ 64 <1300000000>; \
+ clock-latency-ns = <10000000>; \
+ }; \
+ opp-1400000000 { \
+ opp-hz = /bits/ 64 <1400000000>; \
+ clock-latency-ns = <10000000>; \
+ }; \
+ opp-1500000000 { \
+ opp-hz = /bits/ 64 <1500000000>; \
+ clock-latency-ns = <10000000>; \
+ }; \
+ }
+
+/ {
+ compatible = "baikal,bm1000";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ interrupt-parent = <&gic>;
+
+ aliases {
+ i2c1 = &i2c1;
+ i2c2 = &i2c2;
+ i2c3 = &smbus1;
+ i2c4 = &smbus2;
+ serial0 = &uart1;
+ serial1 = &uart2;
+ };
+
+ clocks {
+ osc25: oscillator25 { /* external oscillator */
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <25000000>;
+ clock-output-names = "osc25";
+ };
+ osc27: oscillator27 { /* external oscillator */
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <27000000>;
+ clock-output-names = "osc27";
+ };
+ cpu_clk: cpu_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <1500000000>;
+ clock-output-names = "cpuclk";
+ };
+ apb_clk: apb_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <200000000>;
+ clock-output-names = "apb_pclk";
+ };
+ uart_clk: uart_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <7372800>;
+ clock-output-names = "soc_uartclk";
+ };
+ i2c_clk: i2c_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <100000000>;
+ clock-output-names = "soc_i2cclk";
+ };
+ smbus_clk: smbus_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "soc_smbusclk";
+ };
+ timer1_clk: timer1_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "soc_timer1clk";
+ };
+ timer2_clk: timer2_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "soc_timer2clk";
+ };
+ timer3_clk: timer3_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "soc_timer3clk";
+ };
+ timer4_clk: timer4_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "soc_timer4clk";
+ };
+ gpio_clk: gpio_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <1000000>;
+ clock-output-names = "soc_gpioclk";
+ };
+ spi_clk: spi_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "soc_spiclk";
+ };
+ soc_ethclk: ethclk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "eth_clk";
+ };
+ soc_xgbeclk: xgbeclk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <156250000>;
+ clock-output-names = "xgbe_clk";
+ };
+ soc_smc50mhz: clk50mhz {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "smc_clk";
+ };
+ soc_faxiclk: refclk400mhz {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <400000000>;
+ clock-output-names = "faxi_clk";
+ };
+ soc_tmp_clk: refclkXXXmhz {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <100000000>;
+ clock-output-names = "tmpclk";
+ };
+ gpu_clk: gpu_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <750000000>;
+ clock-output-names = "gpuclk";
+ };
+ clk_ahb: clk_ahb {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <100000000>;
+ clock-output-names = "clk_ahb";
+ };
+ clk_xin: clk_xin {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <100000000>;
+ clock-output-names = "clk_xin";
+ };
+ avlsp_cmu1_div7: avlsp_cmu1_div7 {
+ compatible = "allwinner,sun4i-a10-pll3-2x-clk", "fixed-factor-clock";
+ #clock-cells = <0>;
+ clocks = <&avlsp_cmu1>;
+ clock-div = <7>;
+ clock-mult = <1>;
+ clock-output-names = "lvds_clk";
+ };
+ };
+
+ psci {
+ compatible = "arm,psci-1.0", "arm,psci-0.2";
+ method = "smc";
+ };
+
+ cpus {
+ #address-cells = <2>;
+ #size-cells = <0>;
+
+ /* Do not use 'cpu-map'. It leads to wrong topology. */
+
+ cpu0: cpu@0 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x0>;
+ enable-method = "psci";
+ i-cache-size = <0xc000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <256>;
+ next-level-cache = <&cluster0_l2>;
+ clocks = <&cluster0_cmu0>;
+ operating-points-v2 = <&cluster0_opp>;
+ cpu-idle-states = <&CPU_SLEEP>;
+ };
+
+ cpu1: cpu@1 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x1>;
+ enable-method = "psci";
+ i-cache-size = <0xc000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <256>;
+ next-level-cache = <&cluster0_l2>;
+ clocks = <&cluster0_cmu0>;
+ operating-points-v2 = <&cluster0_opp>;
+ cpu-idle-states = <&CPU_SLEEP>;
+ };
+
+ cpu2: cpu@100 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x100>;
+ enable-method = "psci";
+ i-cache-size = <0xc000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <256>;
+ next-level-cache = <&cluster1_l2>;
+ clocks = <&cluster1_cmu0>;
+ operating-points-v2 = <&cluster1_opp>;
+ cpu-idle-states = <&CPU_SLEEP>;
+ };
+
+ cpu3: cpu@101 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x101>;
+ enable-method = "psci";
+ i-cache-size = <0xc000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <256>;
+ next-level-cache = <&cluster1_l2>;
+ clocks = <&cluster1_cmu0>;
+ operating-points-v2 = <&cluster1_opp>;
+ cpu-idle-states = <&CPU_SLEEP>;
+ };
+
+ cpu4: cpu@200 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x200>;
+ enable-method = "psci";
+ i-cache-size = <0xc000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <256>;
+ next-level-cache = <&cluster2_l2>;
+ clocks = <&cluster2_cmu0>;
+ operating-points-v2 = <&cluster2_opp>;
+ cpu-idle-states = <&CPU_SLEEP>;
+ };
+
+ cpu5: cpu@201 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x201>;
+ enable-method = "psci";
+ i-cache-size = <0xc000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <256>;
+ next-level-cache = <&cluster2_l2>;
+ clocks = <&cluster2_cmu0>;
+ operating-points-v2 = <&cluster2_opp>;
+ cpu-idle-states = <&CPU_SLEEP>;
+ };
+
+ cpu6: cpu@300 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x300>;
+ enable-method = "psci";
+ i-cache-size = <0xc000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <256>;
+ next-level-cache = <&cluster3_l2>;
+ clocks = <&cluster3_cmu0>;
+ operating-points-v2 = <&cluster3_opp>;
+ cpu-idle-states = <&CPU_SLEEP>;
+ };
+
+ cpu7: cpu@301 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x301>;
+ enable-method = "psci";
+ i-cache-size = <0xc000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <256>;
+ next-level-cache = <&cluster3_l2>;
+ clocks = <&cluster3_cmu0>;
+ operating-points-v2 = <&cluster3_opp>;
+ cpu-idle-states = <&CPU_SLEEP>;
+ };
+
+ idle-states {
+ entry-method = "psci";
+
+ CPU_SLEEP: cpu-sleep {
+ compatible = "arm,idle-state";
+ local-timer-stop;
+ arm,psci-suspend-param = <0x0000001>;
+ entry-latency-us = <300>;
+ exit-latency-us = <1200>;
+ min-residency-us = <2000>;
+ };
+ };
+
+ cluster0_l2: l2-cache0 {
+ compatible = "cache";
+ cache-size = <0x100000>;
+ cache-line-size = <64>;
+ cache-sets = <1024>;
+ cache-unified;
+ cache-level = <2>;
+ next-level-cache = <&l3>;
+ };
+
+ cluster1_l2: l2-cache1 {
+ compatible = "cache";
+ cache-size = <0x100000>;
+ cache-line-size = <64>;
+ cache-sets = <1024>;
+ cache-unified;
+ cache-level = <2>;
+ next-level-cache = <&l3>;
+ };
+
+ cluster2_l2: l2-cache2 {
+ compatible = "cache";
+ cache-size = <0x100000>;
+ cache-line-size = <64>;
+ cache-sets = <1024>;
+ cache-unified;
+ cache-level = <2>;
+ next-level-cache = <&l3>;
+ };
+
+ cluster3_l2: l2-cache3 {
+ compatible = "cache";
+ cache-size = <0x100000>;
+ cache-line-size = <64>;
+ cache-sets = <1024>;
+ cache-unified;
+ cache-level = <2>;
+ next-level-cache = <&l3>;
+ };
+
+ l3: l3-cache {
+ cache-size = <0x800000>;
+ cache-unified;
+ cache-level = <3>;
+ };
+ };
+
+ CLUSTER_OPP_TABLE(0);
+ CLUSTER_OPP_TABLE(1);
+ CLUSTER_OPP_TABLE(2);
+ CLUSTER_OPP_TABLE(3);
+
+ pmu {
+ compatible = "arm,cortex-a57-pmu";
+ interrupts = <GIC_PPI 7 IRQ_TYPE_LEVEL_LOW>;
+ };
+
+ timer {
+ compatible = "arm,armv8-timer";
+ interrupts = <GIC_PPI 13 IRQ_TYPE_LEVEL_LOW>,
+ <GIC_PPI 14 IRQ_TYPE_LEVEL_LOW>,
+ <GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
+ <GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
+ };
+
+ soc {
+ compatible = "simple-bus";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+
+ cluster0_cmu0: clock-controller@28000000 {
+ compatible = "baikal,bm1000-cmu";
+ reg = <0x0 0x28000000 0x0 0x10000>;
+ clock-output-names = "bm1000-cluster0-cmu0";
+ #clock-cells = <0>;
+ clocks = <&osc25>;
+ clock-frequency = <1500000000>;
+ min = <500000000>;
+ max = <1500000000>;
+ };
+
+ cluster1_cmu0: clock-controller@c000000 {
+ compatible = "baikal,bm1000-cmu";
+ reg = <0x0 0xc000000 0x0 0x10000>;
+ clock-output-names = "bm1000-cluster1-cmu0";
+ #clock-cells = <0>;
+ clocks = <&osc25>;
+ clock-frequency = <1500000000>;
+ min = <500000000>;
+ max = <1500000000>;
+ };
+
+ cluster2_cmu0: clock-controller@a000000 {
+ compatible = "baikal,bm1000-cmu";
+ reg = <0x0 0xa000000 0x0 0x10000>;
+ clock-output-names = "bm1000-cluster2-cmu0";
+ #clock-cells = <0>;
+ clocks = <&osc25>;
+ clock-frequency = <1500000000>;
+ min = <500000000>;
+ max = <1500000000>;
+ };
+
+ cluster3_cmu0: clock-controller@26000000 {
+ compatible = "baikal,bm1000-cmu";
+ reg = <0x0 0x26000000 0x0 0x10000>;
+ clock-output-names = "bm1000-cluster3-cmu0";
+ #clock-cells = <0>;
+ clocks = <&osc25>;
+ clock-frequency = <1500000000>;
+ min = <500000000>;
+ max = <1500000000>;
+ };
+
+ avlsp_cmu0: clock-controller@20000000 {
+ compatible = "baikal,bm1000-cmu";
+ reg = <0x0 0x20000000 0x0 0x10000>;
+ clock-output-names = "bm1000-avlsp-cmu0";
+ #clock-cells = <1>;
+ clocks = <&osc25>;
+ clock-frequency = <1200000000>;
+ min = <800000>;
+ max = <2100000000>;
+ clock-names = "gpio",
+ "uart1",
+ "uart2",
+ "apb",
+ "spi",
+ "espi",
+ "i2c1",
+ "i2c2",
+ "timer1",
+ "timer2",
+ "timer3",
+ "timer4",
+ "hclk",
+ "smbus1",
+ "smbus2",
+ "hda_sys_clk",
+ "hda_clk48",
+ "mshc_axi",
+ "mshc_ahb",
+ "mshc_tx_x2",
+ "mshc_b",
+ "mshc_tm",
+ "mshc_cqetm",
+ "hwa_clu",
+ "hwa_clu_hf",
+ "hwa_axi",
+ "vdu_axi",
+ "smmu";
+ clock-indices = <0>, <1>, <2>, <3>, <4>, <5>, <6>,
+ <7>, <8>, <9>, <10>, <11>, <12>, <13>,
+ <14>, <15>, <16>, <17>, <18>, <19>, <20>,
+ <21>, <22>, <23>, <24>, <25>, <26>, <27>;
+ };
+
+ avlsp_cmu1: clock-controller@20010000 {
+ compatible = "baikal,bm1000-cmu";
+ reg = <0x0 0x20010000 0x0 0x10000>;
+ clock-output-names = "bm1000-avlsp-cmu1";
+ #clock-cells = <0>;
+ clocks = <&osc27>;
+ clock-frequency = <1039500000>;
+ min = <13500000>;
+ max = <2100000000>;
+ };
+
+ mali_cmu0: clock-controller@2a000000 {
+ compatible = "baikal,bm1000-cmu";
+ reg = <0x0 0x2a000000 0x0 0x10000>;
+ clock-output-names = "bm1000-mali-cmu0";
+ #clock-cells = <0>;
+ clocks = <&osc25>;
+ clock-frequency = <750000000>;
+ min = <400000000>;
+ max = <800000000>;
+ clock-names = "aclk";
+ };
+
+ usb_cmu0: clock-controller@2c000000 {
+ compatible = "baikal,bm1000-cmu";
+ reg = <0x0 0x2c000000 0x0 0x10000>;
+ clock-output-names = "bm1000-usb-cmu0";
+ #clock-cells = <1>;
+ clocks = <&osc25>;
+ clock-frequency = <800000000>;
+ min = <100000000>;
+ max = <800000000>;
+ clock-names = "sata_ref_alt_clk",
+ "sata_aclk_u0",
+ "sata_aclk_u1",
+ "usb2_phy0_ref_clk",
+ "usb2_phy1_ref_clk",
+ "usb2_aclk",
+ "usb2_clk_sofitp",
+ "usb3_phy0_ref_clk",
+ "usb3_phy1_ref_clk",
+ "usb3_phy2_ref_clk",
+ "usb3_phy3_ref_clk",
+ "usb3_aclk",
+ "usb3_clk_sofitp",
+ "usb3_clk_suspend",
+ "smmu_aclk",
+ "dmac_aclk",
+ "gic_aclk";
+ clock-indices = <0>, <1>, <2>, <3>, <4>, <5>,
+ <6>, <7>, <8>, <9>, <10>, <11>,
+ <12>, <13>, <14>, <15>, <16>;
+ };
+
+ xgbe_cmu0: clock-controller@30000000 {
+ compatible = "baikal,bm1000-cmu";
+ reg = <0x0 0x30000000 0x0 0x10000>;
+ clock-output-names = "bm1000-xgbe-cmu0";
+ #clock-cells = <1>;
+ clocks = <&osc25>;
+ clock-frequency = <1250000000>;
+ min = <50000000>;
+ max = <1250000000>;
+ clock-names = "csr50mhz",
+ "gmac0_tx2",
+ "gmac1_tx2",
+ "hdmi_aclk",
+ "isfr";
+ clock-indices = <0>, <10>, <13>, <15>, <17>;
+ };
+
+ xgbe_cmu1: clock-controller@30010000 {
+ compatible = "baikal,bm1000-cmu";
+ reg = <0x0 0x30010000 0x0 0x10000>;
+ clock-output-names = "bm1000-xgbe-cmu1";
+ #clock-cells = <1>;
+ clocks = <&osc27>;
+ clock-frequency = <25250000>;
+ min = <13500000>;
+ max = <600000000>;
+ clock-names = "pixelclk";
+ clock-indices = <0>;
+ };
+
+ pcie_gpr: syscon@2050000 {
+ compatible = "baikal,bm1000-pcie-gpr", "syscon";
+ reg = <0x0 0x2050000 0x0 0x100>;
+ };
+
+ pcie0: pcie@2200000 { /* PCIe x4 #0 */
+ device_type = "pci";
+ compatible = "baikal,bm1000-pcie";
+ reg = <0x00000000 0x02200000 0x0 0x1000>,
+ <0x00000040 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 426 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 427 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 429 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 430 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 431 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 432 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 433 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 434 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 435 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 436 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 437 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "aer", "pme", "msi",
+ "msi0", "msi1", "msi2", "msi3",
+ "msi4", "msi5", "msi6", "msi7";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00000000 0x0 0x4ff00000 0x0 0x100000>,
+ <0x82000000 0x0 0x40100000 0x0 0x40100000 0x0 0xfe00000>;
+ num-lanes = <4>;
+ num-viewport = <4>;
+ msi-parent = <&its>;
+ msi-map = <0x0 &its 0x0 0x10000>;
+ status = "disabled";
+ };
+
+ pcie1: pcie@2210000 { /* PCIe x4 #1 */
+ device_type = "pci";
+ compatible = "baikal,bm1000-pcie";
+ reg = <0x00000000 0x02210000 0x0 0x1000>,
+ <0x00000050 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 402 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 403 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 405 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 406 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 407 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 408 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 409 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 410 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 411 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 412 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 413 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "aer", "pme", "msi",
+ "msi0", "msi1", "msi2", "msi3",
+ "msi4", "msi5", "msi6", "msi7";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00100000 0x0 0x5ff00000 0x0 0x100000>,
+ <0x82000000 0x0 0x50000000 0x0 0x50000000 0x0 0xff00000>;
+ num-lanes = <4>;
+ num-viewport = <4>;
+ msi-parent = <&its>;
+ msi-map = <0x0 &its 0x10000 0x10000>;
+ status = "disabled";
+ };
+
+ pcie2: pcie@2220000 { /* PCIe x8 */
+ device_type = "pci";
+ compatible = "baikal,bm1000-pcie";
+ reg = <0x00000000 0x02220000 0x0 0x1000>,
+ <0x00000060 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 378 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 379 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 381 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 382 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 383 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 384 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 385 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 386 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 387 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 388 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 389 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "aer", "pme", "msi",
+ "msi0", "msi1", "msi2", "msi3",
+ "msi4", "msi5", "msi6", "msi7";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00200000 0x0 0x7ff00000 0x0 0x100000>,
+ <0x82000000 0x0 0x60000000 0x0 0x60000000 0x0 0x1ff00000>;
+ num-lanes = <8>;
+ num-viewport = <16>;
+ msi-parent = <&its>;
+ msi-map = <0x0 &its 0x20000 0x10000>;
+ status = "disabled";
+ };
+
+ ccn: ccn@9000000 {
+ compatible = "arm,ccn-504";
+ reg = <0x0 0x9000000 0 0x1000000>;
+ interrupts = <GIC_SPI 127 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ ddr0: memory-controller@e200000 {
+ compatible = "baikal,bm1000-edac-mc";
+ reg = <0x0 0x0e200000 0x0 0x10000>;
+ interrupts = <GIC_SPI 129 IRQ_TYPE_LEVEL_HIGH>, /* dfi_alert_err */
+ <GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err */
+ <GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH>, /* ecc_uncorrected_err */
+ <GIC_SPI 132 IRQ_TYPE_LEVEL_HIGH>, /* sbr_done */
+ <GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err_fault */
+ <GIC_SPI 134 IRQ_TYPE_LEVEL_HIGH>; /* ecc_uncorrected_err_fault */
+ clocks = <&soc_smc50mhz>;
+ clock-names = "apb_pclk";
+ status = "disabled";
+ };
+
+ ddr1: memory-controller@22200000 {
+ compatible = "baikal,bm1000-edac-mc";
+ reg = <0x0 0x22200000 0x0 0x10000>;
+ interrupts = <GIC_SPI 139 IRQ_TYPE_LEVEL_HIGH>, /* dfi_alert_err */
+ <GIC_SPI 140 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err */
+ <GIC_SPI 141 IRQ_TYPE_LEVEL_HIGH>, /* ecc_uncorrected_err */
+ <GIC_SPI 142 IRQ_TYPE_LEVEL_HIGH>, /* sbr_done */
+ <GIC_SPI 143 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err_fault */
+ <GIC_SPI 144 IRQ_TYPE_LEVEL_HIGH>; /* ecc_uncorrected_err_fault */
+ clocks = <&soc_smc50mhz>;
+ clock-names = "apb_pclk";
+ status = "disabled";
+ };
+
+ gpio32: gpio@20200000 {
+ compatible = "snps,dw-apb-gpio";
+ reg = <0x0 0x20200000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+
+ porta: gpio-controller@0 {
+ compatible = "snps,dw-apb-gpio-port";
+ gpio-controller;
+ #gpio-cells = <2>;
+ ngpios = <32>;
+ reg = <0>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>;
+ };
+ };
+
+ spi0: spi@20210000 {
+ compatible = "snps,dw-apb-ssi";
+ reg = <0x0 0x20210000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&avlsp_cmu0 4>;
+ clock-names = "ssi_clk";
+ status = "disabled";
+ };
+
+ i2s: i2s@20220000 {
+ compatible = "snps,designware-i2s";
+ reg = <0x0 0x20220000 0x0 0x10000>;
+ #sound-dai-cells = <0>;
+ system-clock-frequency = <12000000>;
+ interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>, /* rx_da */
+ /* <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH>, rx_or */
+ <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>, /* tx_emp */
+ <GIC_SPI 107 IRQ_TYPE_LEVEL_HIGH>; /* tx_or */
+ /*
+ * dmas = <&dmac_lsp 0 1 0>,
+ * <&dmac_lsp 1 0 1>;
+ * dma-names = "tx", "rx";
+ */
+ clocks = <&soc_tmp_clk>;
+ clock-names = "i2sclk";
+ status = "disabled";
+ };
+
+ uart1: serial@20230000 {
+ compatible = "baikal,bm1000-uart", "snps,dw-apb-uart";
+ reg = <0x0 0x20230000 0x0 0x100>;
+ interrupts = <GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>;
+ reg-shift = <2>;
+ reg-io-width = <4>;
+ clocks = <&avlsp_cmu0 1>, <&apb_clk>;
+ clock-names = "baudclk", "apb_pclk";
+ status = "disabled";
+ };
+
+ uart2: serial@20240000 {
+ compatible = "baikal,bm1000-uart", "snps,dw-apb-uart";
+ reg = <0x0 0x20240000 0x0 0x100>;
+ interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>;
+ reg-shift = <2>;
+ reg-io-width = <4>;
+ clocks = <&avlsp_cmu0 2>, <&apb_clk>;
+ clock-names = "baudclk", "apb_pclk";
+ status = "disabled";
+ };
+
+ i2c1: i2c@20250000 {
+ compatible = "snps,designware-i2c";
+ reg = <0x0 0x20250000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 108 IRQ_TYPE_LEVEL_HIGH>;
+ i2c-sda-hold-time-ns = <500>;
+ clock-frequency = <400000>;
+ clocks = <&i2c_clk>;
+ clock-names = "ref";
+ status = "disabled";
+ };
+
+ i2c2: i2c@20260000 {
+ compatible = "snps,designware-i2c";
+ reg = <0x0 0x20260000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>;
+ i2c-sda-hold-time-ns = <500>;
+ clock-frequency = <400000>;
+ clocks = <&i2c_clk>;
+ clock-names = "ref";
+ status = "disabled";
+ };
+
+ smbus1: i2c@20270000 {
+ compatible = "baikal,bm1000-smbus";
+ reg = <0x0 0x20270000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>;
+ clock-frequency = <100000>;
+ clocks = <&smbus_clk>;
+ clock-names = "soc_smbusclk";
+ status = "disabled";
+ };
+
+ smbus2: i2c@20280000 {
+ compatible = "baikal,bm1000-smbus";
+ reg = <0x0 0x20280000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 111 IRQ_TYPE_LEVEL_HIGH>;
+ clock-frequency = <100000>;
+ clocks = <&smbus_clk>;
+ clock-names = "soc_smbusclk";
+ status = "disabled";
+ };
+
+ timer1: timer@20290000 {
+ compatible = "snps,dw-apb-timer";
+ reg = <0x0 0x20290000 0x0 0x14>;
+ interrupts = <GIC_SPI 95 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&timer1_clk>;
+ clock-names = "timer";
+ status = "disabled";
+ };
+
+ timer2: timer@20290014 {
+ compatible = "snps,dw-apb-timer";
+ reg = <0x0 0x20290014 0x0 0x14>;
+ interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&timer2_clk>;
+ clock-names = "timer";
+ status = "disabled";
+ };
+
+ timer3: timer@20290028 {
+ compatible = "snps,dw-apb-timer";
+ reg = <0x0 0x20290028 0x0 0x14>;
+ interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&timer3_clk>;
+ clock-names = "timer";
+ status = "disabled";
+ };
+
+ timer4: timer@2029003c {
+ compatible = "snps,dw-apb-timer";
+ reg = <0x0 0x2029003c 0x0 0x14>;
+ interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&timer4_clk>;
+ clock-names = "timer";
+ status = "disabled";
+ };
+
+ spi1: spi@202a0000 {
+ compatible = "baikal,bm1000-espi";
+ reg = <0x0 0x202a0000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&avlsp_cmu0 5>;
+ clock-names = "soc_espiclk";
+ status = "disabled";
+ /*
+ * Block Configuration:
+ * - master/slave
+ * - 32-bit APB slave
+ * - tx-fifo = rx-fifo = 256 byte
+ * - 4 SPI IO channels
+ * - 8 slave select IO channels
+ * - DMA - missing
+ * - M-flash controller - missing
+ */
+ };
+
+ dmac_lsp: dma-controller@202b0000 {
+ compatible = "snps,dma-spear1340";
+ reg = <0x0 0x202b0000 0x0 0x10000>;
+ interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 21 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 43 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 46 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 47 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 48 IRQ_TYPE_LEVEL_HIGH>;
+
+ clocks = <&avlsp_cmu0 12>;
+ clock-names = "hclk";
+
+ dma-channels = <8>;
+ dma-requests = <16>;
+ dma-masters = <2>;
+ #dma-cells = <4>;
+
+ chan_allocation_order = <0>;
+ chan_priority = <0>;
+ block_size = <0xff>;
+ data-width = <4 16>;
+ multi-block = <1 1 1 1 1 1 1 1>;
+ snps,max-burst-len = <32 32 32 32 32 32 32 32>;
+ snps,dma-protection-control = <1>;
+
+ status = "disabled";
+ };
+
+ hda: hda@202c0000 {
+ compatible = "baikal,bm1000-hda";
+ reg = <0x0 0x202c0000 0x0 0x1000>;
+ interrupts = <GIC_SPI 54 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&avlsp_cmu0 15>, <&avlsp_cmu0 16>;
+ clock-names = "hda_sys_clk", "hda_clk48";
+ status = "disabled";
+ force-polling-mode;
+ broken-response-irq;
+ increment-codec-address;
+ cyclic-codec-probe;
+ };
+
+ mmc: mmc@202e0000 {
+ compatible = "baikal,dwcmshc-sdhci";
+ reg = <0x0 0x202e0000 0x0 0x10000>;
+ interrupts = <GIC_SPI 51 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&avlsp_cmu0 18>, <&avlsp_cmu0 19>;
+ clock-names = "bus", "core";
+ no-1-8-v;
+ status = "disabled";
+ };
+
+ vdec: vdec@24200000 {
+ compatible = "baikal,d5500-vxd";
+ reg = <0x0 0x24200000 0x0 0x10000>;
+ interrupts = <GIC_SPI 497 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+
+ mali: gpu@2a200000 {
+ compatible = "arm,mali-midgard", "arm,mali-t628";
+ reg = <0x0 0x2a200000 0x0 0x4000>;
+ #cooling-cells = <2>; /* min followed by max */
+ interrupts = <GIC_SPI 175 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 176 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 174 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "job", "mmu", "gpu";
+ clocks = <&mali_cmu0>;
+ clock-names = "gpuclk";
+ operating-points-v2 = <&gpu_opp_table>;
+ dma-coherent;
+
+ gpu_opp_table: opp-table {
+ compatible = "operating-points-v2", "operating-points-v2-mali";
+
+ opp-400000000 {
+ opp-hz = /bits/ 64 <400000000>;
+ clock-latency-ns = <10000000>;
+ };
+ opp-450000000 {
+ opp-hz = /bits/ 64 <450000000>;
+ clock-latency-ns = <10000000>;
+ };
+ opp-500000000 {
+ opp-hz = /bits/ 64 <500000000>;
+ clock-latency-ns = <10000000>;
+ };
+ opp-550000000 {
+ opp-hz = /bits/ 64 <550000000>;
+ clock-latency-ns = <10000000>;
+ };
+ opp-600000000 {
+ opp-hz = /bits/ 64 <600000000>;
+ clock-latency-ns = <10000000>;
+ };
+ opp-650000000 {
+ opp-hz = /bits/ 64 <650000000>;
+ clock-latency-ns = <10000000>;
+ };
+ opp-700000000 {
+ opp-hz = /bits/ 64 <700000000>;
+ clock-latency-ns = <10000000>;
+ };
+ opp-750000000 {
+ opp-hz = /bits/ 64 <750000000>;
+ clock-latency-ns = <10000000>;
+ };
+ };
+ };
+
+ usb2: usb@2c400000 {
+ compatible = "snps,dwc3";
+ reg = <0x0 0x2c400000 0x0 0x100000>;
+ interrupts = <GIC_SPI 235 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "dwc_usb3";
+ dr_mode="host";
+ dma-coherent;
+ clocks = <&usb_cmu0 5>, <&usb_cmu0 6>;
+ clock-names = "bus_early", "ref";
+ phys = <&usb_phy 0>;
+ phy-names = "usb2-phy";
+ };
+
+ usb3: usb@2c500000 {
+ compatible = "snps,dwc3";
+ reg = <0x0 0x2c500000 0x0 0x100000>;
+ interrupts = <GIC_SPI 237 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "dwc_usb3";
+ dr_mode="host";
+ dma-coherent;
+ clocks = <&usb_cmu0 11>, <&usb_cmu0 12>, <&usb_cmu0 13>;
+ clock-names = "bus_early", "ref", "suspend";
+ phys = <&usb_phy 1>, <&usb_phy 2>;
+ phy-names = "usb3-phy", "usb2-phy";
+ };
+
+ usb_phy: usb_phy {
+ compatible = "baikal,bm1000-usb-phy";
+ status = "okay";
+ clocks = <&usb_cmu0 3>, <&usb_cmu0 4>, <&usb_cmu0 7>,
+ <&usb_cmu0 8>, <&usb_cmu0 9>, <&usb_cmu0 10>;
+ #phy-cells = <1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ usb-phy@0 {
+ reg = <0>;
+ enable;
+ };
+ usb-phy@1 {
+ reg = <1>;
+ enable;
+ };
+ usb-phy@2 {
+ reg = <2>;
+ enable;
+ };
+ };
+
+ sata0: sata@2c600000 {
+ compatible = "baikal,bm1000-ahci";
+ reg = <0x0 0x2c600000 0 0x10000>;
+ interrupts = <GIC_SPI 233 IRQ_TYPE_LEVEL_HIGH>;
+ ports-implemented = <1>;
+ dma-coherent;
+ clocks = <&soc_faxiclk>;
+ clock-names = "aclk";
+ status = "disabled";
+ };
+
+ sata1: sata@2c610000 {
+ compatible = "baikal,bm1000-ahci";
+ reg = <0x0 0x2c610000 0 0x10000>;
+ interrupts = <GIC_SPI 234 IRQ_TYPE_LEVEL_HIGH>;
+ ports-implemented = <1>;
+ dma-coherent;
+ clocks = <&soc_faxiclk>;
+ clock-names = "aclk";
+ status = "disabled";
+ };
+
+ dmac_m2m: dma-controller@2c630000 {
+ compatible = "arm,pl330", "arm,primecell";
+ reg = <0x0 0x2c630000 0 0x10000>;
+ #dma-cells = <1>;
+ #dma-channels = <8>;
+ #dma-requests = <32>;
+ interrupts = <GIC_SPI 223 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 224 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 225 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 226 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 227 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 228 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 229 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 231 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&soc_faxiclk>;
+ clock-names = "apb_pclk";
+ status = "disabled";
+ };
+
+ gic: interrupt-controller@2d000000 {
+ compatible = "arm,gic-v3";
+ reg = <0x0 0x2d000000 0x0 0x10000>, /* GICD */
+ <0x0 0x2d100000 0x0 0x200000>, /* GICR */
+ <0x0 0x10200000 0x0 0x2000>, /* GICC */
+ <0x0 0x10210000 0x0 0x1000>, /* GICH */
+ <0x0 0x10220000 0x0 0x2000>; /* GICV */
+ #interrupt-cells = <3>;
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ interrupt-controller;
+ interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
+
+ its: interrupt-controller@2d020000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x2d020000 0x0 0x20000>; /* GITS */
+ msi-controller;
+ };
+ };
+
+ xgmac0: ethernet@30200000 {
+ compatible = "amd,xgbe-seattle-v1a";
+ reg = <0x0 0x30200000 0x0 0x10000>,
+ <0x0 0x30210000 0x0 0x10000>;
+ interrupts = <GIC_SPI 293 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 294 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 295 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 296 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 297 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 298 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 299 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 300 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 301 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 302 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 304 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 305 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 306 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 307 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 308 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 309 IRQ_TYPE_LEVEL_HIGH>;
+ fsl,num-rx-queues = <3>;
+ clocks = <&soc_xgbeclk>, <&soc_xgbeclk>, <&soc_xgbeclk>;
+ clock-names = "dma_clk", "ptp_clk", "xgbe_clk";
+ phy-mode = "xgmii";
+ be,pcs-mode = "KX4";
+ status = "disabled";
+ amd,per-channel-interrupt;
+ amd,speed-set = <0>;
+ #stream-id-cells = <16>;
+ };
+
+ xgmac1: ethernet@30220000 {
+ compatible = "amd,xgbe-seattle-v1a";
+ reg = <0x0 0x30220000 0x0 0x10000>,
+ <0x0 0x30230000 0x0 0x10000>;
+ interrupts = <GIC_SPI 310 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 311 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 312 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 313 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 314 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 315 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 316 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 317 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 318 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 319 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 320 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 321 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 322 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 323 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 324 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 325 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 326 IRQ_TYPE_LEVEL_HIGH>;
+ fsl,num-rx-queues = <3>;
+ clocks = <&soc_xgbeclk>, <&soc_xgbeclk>, <&soc_xgbeclk>;
+ clock-names = "dma_clk", "ptp_clk", "xgbe_clk";
+ phy-mode = "xgmii";
+ be,pcs-mode = "KX4";
+ status = "disabled";
+ amd,per-channel-interrupt;
+ amd,speed-set = <0>;
+ #stream-id-cells = <16>;
+ };
+
+ stmmac_axi_setup: stmmac-axi-config {
+ snps,wr_osr_lmt = <0x0>;
+ snps,rd_osr_lmt = <0x0>;
+ snps,blen = <0 0 0 0 0 0 4>;
+ };
+
+ gmac0: ethernet@30240000 {
+ compatible = "baikal,bm1000-gmac";
+ reg = <0x0 0x30240000 0x0 0x10000>;
+ interrupts = <GIC_SPI 291 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "macirq";
+ max-speed = <1000>;
+ clocks = <&soc_ethclk>, <&xgbe_cmu0 10>;
+ clock-names = "stmmaceth", "tx2_clk";
+ snps,axi-config = <&stmmac_axi_setup>;
+ snps,fixed-burst;
+ snps,no-pbl-x8;
+ snps,txpbl = <4>;
+ snps,rxpbl = <4>;
+ status = "disabled";
+ dma-coherent;
+
+ mdio0: mdio {
+ compatible = "snps,dwmac-mdio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ };
+
+ gmac1: ethernet@30250000 {
+ compatible = "baikal,bm1000-gmac";
+ reg = <0x0 0x30250000 0x0 0x10000>;
+ interrupts = <GIC_SPI 292 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "macirq";
+ max-speed = <1000>;
+ clocks = <&soc_ethclk>, <&xgbe_cmu0 13>;
+ clock-names = "stmmaceth", "tx2_clk";
+ snps,axi-config = <&stmmac_axi_setup>;
+ snps,fixed-burst;
+ snps,no-pbl-x8;
+ snps,txpbl = <4>;
+ snps,rxpbl = <4>;
+ status = "disabled";
+ dma-coherent;
+
+ mdio1: mdio {
+ compatible = "snps,dwmac-mdio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ };
+
+ vdu: vdu@202d0000 {
+ compatible = "baikal,vdu";
+ reg = <0x0 0x202d0000 0x0 0x1000>,
+ <0x0 0x30260000 0x0 0x1000>;
+ reg-names = "lvds_regs", "hdmi_regs";
+ interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 329 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "lvds_irq", "hdmi_irq";
+ clocks = <&avlsp_cmu1_div7>, <&xgbe_cmu1 0>;
+ clock-names = "lvds_pclk", "hdmi_pclk";
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ vdu_hdmi_out: endpoint {
+ remote-endpoint = <&hdmi_tx_in>;
+ };
+ };
+ };
+ };
+
+ hdmi: hdmi@30280000 {
+ compatible = "baikal,hdmi";
+ reg = <0x0 0x30280000 0x0 0x20000>;
+ reg-io-width = <4>;
+ interrupts = <GIC_SPI 331 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&xgbe_cmu0 0>, <&xgbe_cmu0 17>;
+ clock-names = "iahb", "isfr";
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ hdmi_tx_in: endpoint {
+ remote-endpoint = <&vdu_hdmi_out>;
+ };
+ };
+ port@1 {
+ reg = <1>;
+ hdmi_tx_out: endpoint {
+ remote-endpoint = <&hdmi_con>;
+ };
+ };
+ };
+ };
+
+ pvt_cluster0: pvt@28200000 {
+ compatible = "baikal,bm1000-pvt";
+ reg = <0x0 0x28200000 0x0 0x1000>;
+ interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_cluster1: pvt@c200000 {
+ compatible = "baikal,bm1000-pvt";
+ reg = <0x0 0xc200000 0x0 0x1000>;
+ interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_cluster2: pvt@a200000 {
+ compatible = "baikal,bm1000-pvt";
+ reg = <0x0 0xa200000 0x0 0x1000>;
+ interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_cluster3: pvt@26200000 {
+ compatible = "baikal,bm1000-pvt";
+ reg = <0x0 0x26200000 0x0 0x1000>;
+ interrupts = <GIC_SPI 125 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_mali: pvt@2a060000 {
+ compatible = "baikal,bm1000-pvt";
+ reg = <0x0 0x2a060000 0x0 0x1000>;
+ interrupts = <GIC_SPI 221 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ };
+
+ hdmi-out {
+ compatible = "hdmi-connector";
+ label = "HDMI0 OUT";
+ type = "a";
+
+ port {
+ hdmi_con: endpoint {
+ remote-endpoint = <&hdmi_tx_out>;
+ };
+ };
+ };
+};
+
+#include "bm1000-coresight.dtsi"
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree include file for BE-S1000 SoC clocks
+ * Copyright (C) 2021-2023 Baikal Electronics, JSC
+ */
+
+#define CLK_TYPE_NO 0
+#define CLK_TYPE_REFCLK 1 /* fout = 25 MHz or 100 MHz */
+#define CLK_TYPE_PLL 2 /* fout = fref * NF / (NR * OD) */
+#define CLK_TYPE_CLKCH 3 /* fout = fref / (VAL_CLKDIV + 1) */
+#define CLK_TYPE_CLKEN 4 /* fout = fref / DIV_VAL */
+#define CLK_TYPE_CLKDIV 5 /* fout = fref / 2^(DIV_VAL - 1) */
+
+#define CA75_CLOCKS(num, base) \
+ /* pll */ \
+ cluster##num##_cmu0_pll: \
+ cluster##num##_cmu0_pll { \
+ compatible = "baikal,bs1000-cmu"; \
+ reg = <(base + 0x0)>; \
+ type = <CLK_TYPE_PLL>; \
+ clocks = <&clk_ref>; \
+ #clock-cells = <0>; \
+ /*clock-frequency = <1600000000>;*/ \
+ clock-output-names = "cmu0_pll"; \
+ }; \
+ cluster##num##_cmu1_pll: \
+ cluster##num##_cmu1_pll { \
+ compatible = "baikal,bs1000-cmu"; \
+ reg = <(base + 0x10000)>; \
+ type = <CLK_TYPE_PLL>; \
+ clocks = <&clk_ref>; \
+ #clock-cells = <0>; \
+ /*clock-frequency = <2000000000>;*/ \
+ clock-output-names = "cmu1_pll"; \
+ }; \
+ /* clkch */ \
+ cluster##num##_cmu0: \
+ cluster##num##_cmu0 { \
+ compatible = "baikal,bs1000-cmu"; \
+ reg = <(base + 0x100)>; \
+ type = <CLK_TYPE_CLKCH>; \
+ clocks = <&cluster##num##_cmu0_pll>; \
+ #clock-cells = <1>; \
+ clock-output-names = \
+ "sclk", /* 0 */ \
+ "periph", /* 1 */ \
+ "pclk", /* 2 */ \
+ "at", /* 3 */ \
+ "gic", /* 4 */ \
+ "cfg_int", /* 5 */ \
+ "cfg_out", /* 6 */ \
+ "bisr", /* 7 */ \
+ "crp"; /* 8 */ \
+ }; \
+ /* clken */ \
+ cluster##num##_cmu0_en0: \
+ cluster##num##_cmu0_en0 { \
+ compatible = "baikal,bs1000-cmu"; \
+ reg = <(base + 0x500)>; \
+ type = <CLK_TYPE_CLKEN>; \
+ clocks = <&cluster##num##_cmu0 0>; /* "sclk" */ \
+ #clock-cells = <0>; \
+ clock-output-names = "clken"; \
+ }; \
+ cluster##num##_cmu0_en1: \
+ cluster##num##_cmu0_en1 { \
+ compatible = "baikal,bs1000-cmu"; \
+ reg = <(base + 0x510)>; \
+ type = <CLK_TYPE_CLKEN>; \
+ clocks = <&cluster##num##_cmu0 1>; /* "periph" */ \
+ #clock-cells = <1>; \
+ clock-output-names = \
+ "cntclken", /* 0 */ \
+ "tsclken"; /* 1 */ \
+ }; \
+ /* core */ \
+ cluster##num##_cmu1: \
+ cluster##num##_cmu1 { \
+ compatible = "baikal,bs1000-cmu"; \
+ reg = <(base + 0x10020)>; \
+ type = <CLK_TYPE_CLKDIV>; \
+ clocks = <&cluster##num##_cmu1_pll>; \
+ #clock-cells = <1>; \
+ clock-output-names = \
+ "core0", /* 0 */ \
+ "core1", /* 1 */ \
+ "core2", /* 2 */ \
+ "core3"; /* 3 */ \
+ }
+
+/ {
+ soc {
+ clocks {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* TODO: it is fixed-clocks for QEMU, remove it */
+ apb_clk: apb_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <200000000>;
+ clock-output-names = "apb_pclk";
+ };
+ osc25: oscillator25 {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <25000000>;
+ clock-output-names = "osc25";
+ };
+ osc27: oscillator27 {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <27000000>;
+ clock-output-names = "osc27";
+ };
+ uart1_clk: uart1_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <8000000>;
+ clock-output-names = "uart1_clk";
+ };
+ uart2_clk: uart2_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <8000000>;
+ clock-output-names = "uart2_clk";
+ };
+ uart_clk: uart_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <7372800>;
+ clock-output-names = "soc_uartclk";
+ };
+ i2c_clk: i2c_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <200000000>;
+ clock-output-names = "soc_i2cclk";
+ };
+ timer1_clk: timer1_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "soc_timer1clk";
+ };
+ timer2_clk: timer2_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "soc_timer2clk";
+ };
+ timer3_clk: timer3_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "soc_timer3clk";
+ };
+ timer4_clk: timer4_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "soc_timer4clk";
+ };
+ wdt_clk: wdt_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <1000000>;
+ clock-output-names = "wdtclk";
+ };
+ gpio_clk: gpio_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <1000000>;
+ clock-output-names = "soc_gpioclk";
+ };
+ spi_clk: spi_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "soc_spiclk";
+ };
+ soc_ethclk: ethclk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "eth_clk";
+ };
+ soc_xgbeclk: xgbeclk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <156250000>;
+ clock-output-names = "xgbe_clk";
+ };
+ soc_smc50mhz: clk50mhz {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <50000000>;
+ clock-output-names = "smc_clk";
+ };
+ soc_faxiclk: refclk400mhz {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <400000000>;
+ clock-output-names = "faxi_clk";
+ };
+ soc_tmp_clk: refclkXXXmhz {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <100000000>;
+ clock-output-names = "tmpclk";
+ };
+ clk_ahb: clk_ahb {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <100000000>;
+ clock-output-names = "clk_ahb";
+ };
+ clk_xin: clk_xin {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <100000000>;
+ clock-output-names = "clk_xin";
+ };
+
+ /* CLK_REF */
+ clk_ref: clk_ref {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0xdead>;
+ type = <CLK_TYPE_REFCLK>;
+ #clock-cells = <0>;
+ //clock-frequency = <25000000>; // 25 or 100 MHz
+ clock-output-names = "clk_ref";
+ };
+
+ /* CA75 */
+ CA75_CLOCKS(0, 0x04000000);
+ CA75_CLOCKS(1, 0x08000000);
+ CA75_CLOCKS(2, 0x0c000000);
+ CA75_CLOCKS(3, 0x10000000);
+ CA75_CLOCKS(4, 0x14000000);
+ CA75_CLOCKS(5, 0x18000000);
+ CA75_CLOCKS(6, 0x1c000000);
+ CA75_CLOCKS(7, 0x20000000);
+ CA75_CLOCKS(8, 0x24000000);
+ CA75_CLOCKS(9, 0x28000000);
+ CA75_CLOCKS(10,0x2c000000);
+ CA75_CLOCKS(11,0x30000000);
+
+ /* PCIe */
+ /* pci.pll */
+ cmu_pci_0_pll: cmu_pci_0_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x38000000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <2000000000>; */
+ clock-output-names = "pci_0_pll";
+ };
+ cmu_pci_1_pll: cmu_pci_1_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x3c000000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <2000000000>; */
+ clock-output-names = "pci_1_pll";
+ };
+ cmu_pci_2_pll: cmu_pci_2_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x44000000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <2000000000>; */
+ clock-output-names = "pci_2_pll";
+ };
+ cmu_pci_3_pll: cmu_pci_3_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x48000000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <2000000000>; */
+ clock-output-names = "pci_3_pll";
+ };
+ cmu_pci_4_pll: cmu_pci_4_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x4c000000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <2000000000>; */
+ clock-output-names = "pci_4_pll";
+ };
+
+ /* pci.clkch */
+ cmu_pci_0: cmu_pci_0 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x38000020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_pci_0_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "nic_cfg0m_ssa_x16p0s", // 0
+ "nic_cfg1m_ssa_x8p1s", // 1
+ "nic_cfg2m_mmu_tcus", // 2
+ "nic_cfg3m_ssa_apbs", // 3
+ "nic_cfg4m_nic_slv_gpvs", // 4
+ "nic_slv0m_ssa_x16p0s", // 5
+ "nic_slv1m_ssa_x8p1s", // 6
+ "ssa_x16p0m_nic_mstr0s", // 7
+ "ssa_x8p1m_nic_mstr1s", // 8
+ "nic_mstrm_mmu_tbu0s", // 9
+ "a4sw_intern", // 10
+ "phy_ref", // 11
+ "ssa_aux", // 12
+ "ahb2axim_axi2ahbs", // 13
+ "ahb2ahbm_outp_ahbs", // 14
+ "ssa_x16p0m_cxlas"; // 15
+ };
+ cmu_pci_1: cmu_pci_1 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x3c000020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_pci_1_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "nic_cfg0m_ssa_x16p0s", // 0
+ "nic_cfg1m_ssa_x8p1s", // 1
+ "nic_cfg2m_mmu_tcus", // 2
+ "nic_cfg3m_ssa_apbs", // 3
+ "nic_cfg4m_nic_slv_gpvs", // 4
+ "nic_slv0m_ssa_x16p0s", // 5
+ "nic_slv1m_ssa_x8p1s", // 6
+ "ssa_x16p0m_nic_mstr0s", // 7
+ "ssa_x8p1m_nic_mstr1s", // 8
+ "nic_mstrm_mmu_tbu0s", // 9
+ "a4sw_intern", // 10
+ "phy_ref", // 11
+ "ssa_aux", // 12
+ "ahb2axim_axi2ahbs", // 13
+ "ahb2ahbm_outp_ahbs", // 14
+ "ssa_x16p0m_cxlas"; // 15
+ };
+ cmu_pci_2: cmu_pci_2 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x44000020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_pci_2_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "a", // 0
+ "b"; // 1
+ };
+ cmu_pci_3: cmu_pci_3 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x48000020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_pci_3_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "a", // 0
+ "b"; // 1
+ };
+ cmu_pci_4: cmu_pci_4 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x4c000020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_pci_4_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "a", // 0
+ "b"; // 1
+ };
+
+ /* DDR */
+ /* ddr.pll */
+ cmu_ddr_0_pll: cmu_ddr_0_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x50000000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <2200000000>; */
+ clock-output-names = "ddr_0_pll";
+ };
+ cmu_ddr_1_pll: cmu_ddr_1_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x54000000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <2200000000>; */
+ clock-output-names = "ddr_1_pll";
+ };
+ cmu_ddr_2_pll: cmu_ddr_2_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x58000000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <2200000000>; */
+ clock-output-names = "ddr_3_pll";
+ };
+ cmu_ddr_3_pll: cmu_ddr_3_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x60000000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <2200000000>; */
+ clock-output-names = "ddr_3_pll";
+ };
+ cmu_ddr_4_pll: cmu_ddr_4_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x64000000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <2200000000>; */
+ clock-output-names = "ddr_4_pll";
+ };
+ cmu_ddr_5_pll: cmu_ddr_5_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x68000000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <2200000000>; */
+ clock-output-names = "ddr_5_pll";
+ };
+
+ /* ddr.clkch */
+ cmu_ddr_0: cmu_ddr_0 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x50000020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_ddr_0_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "ddr_ctrl_phy", // 0
+ "ddr_axi_slv", // 1
+ "ddr_apb_slv"; // 2
+ };
+ cmu_ddr_1: cmu_ddr_1 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x54000020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_ddr_1_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "ddr_ctrl_phy", // 0
+ "ddr_axi_slv", // 1
+ "ddr_apb_slv", // 2
+ "axi_ext_cfg", // 3
+ "axi_middle_cfg", // 4
+ "axi_top_cfg", // 5
+ "axi_bottom_cfg"; // 6
+ };
+ cmu_ddr_2: cmu_ddr_2 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x58000020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_ddr_2_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "ddr_ctrl_phy", // 0
+ "ddr_axi_slv", // 1
+ "ddr_apb_slv"; // 2
+ };
+ cmu_ddr_3: cmu_ddr_3 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x60000020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_ddr_3_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "ddr_ctrl_phy", // 0
+ "ddr_axi_slv", // 1
+ "ddr_apb_slv"; // 2
+ };
+ cmu_ddr_4: cmu_ddr_4 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x64000020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_ddr_4_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "ddr_ctrl_phy", // 0
+ "ddr_axi_slv", // 1
+ "ddr_apb_slv", // 2
+ "axi_ext_cfg", // 3
+ "axi_middle_cfg", // 4
+ "axi_top_cfg", // 5
+ "axi_bottom_cfg"; // 6
+ };
+ cmu_ddr_5: cmu_ddr_5 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x68000020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_ddr_5_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "ddr_ctrl_phy", // 0
+ "ddr_axi_slv", // 1
+ "ddr_apb_slv"; // 2
+ };
+
+ /* System Control */
+ /* sc.pll */
+ cmu_sc_0_pll: cmu_sc_0_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x00400000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <1200000000>; */
+ clock-output-names = "sc_0_pll";
+ };
+ cmu_sc_1_pll: cmu_sc_1_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x00410000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <1200000000>; */
+ clock-output-names = "sc_1_pll";
+ };
+ cmu_sc_2_pll: cmu_sc_2_pll {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x00420000>;
+ type = <CLK_TYPE_PLL>;
+ clocks = <&clk_ref>;
+ #clock-cells = <0>;
+ /* clock-frequency = <1500000000>; */
+ clock-output-names = "sc_2_pll";
+ };
+
+ /* sc.clkch */
+ cmu_sc_0: cmu_sc_0 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x00400020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_sc_0_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "cmn", // 0
+ "cmn_cs"; // 1
+ };
+ cmu_sc_1: cmu_sc_1 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x00410020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_sc_1_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "apb_cfg", // 0
+ "scp_efuse", // 1
+ "scp_ext_cfg", // 2
+ "scp_apb_spi", // 3
+ "scp_uart", // 4
+ "scp_gpio8_x16", // 5, additional divisor by 16
+ "scp_spi", // 6
+ "scp_smbus_i2c0", // 7
+ "scp_smbus_i2c1", // 8
+ "gmac1_apb", // 9
+ "gmac1_axi", // 10
+ "gmac2_apb", // 11
+ "gmac2_axi", // 12
+ "usb_h", // 13
+ "usb_ohci", // 14
+ "lsp_apb", // 15
+ "lsp_gpio_x16", // 16, additional divisor by 16
+ "lsp_uart", // 17
+ "lsp_uart_arm1", // 18
+ "lsp_uart_arm2", // 19
+ "lsp_spi1", // 20
+ "lsp_spi2", // 21
+ "lsp_espi", // 22
+ "lsp_smbus_i2c2", // 23
+ "lsp_smbus_i2c3", // 24
+ "lsp_smbus_i2c4", // 25
+ "lsp_smbus_i2c5", // 26
+ "lsp_smbus_i2c6", // 27
+ "lsp_timer1", // 28
+ "lsp_timer2", // 29
+ "lsp_timer3", // 30
+ "lsp_timer4", // 31
+ "lsp_wdt_x16", // 32, additional divisor by 16
+ "tcu", // 33
+ "sc_axi_cfg", // 34
+ "ca75_ahb_cfg"; // 35
+ };
+ cmu_sc_2: cmu_sc_2 {
+ compatible = "baikal,bs1000-cmu";
+ reg = <0x00420020>;
+ type = <CLK_TYPE_CLKCH>;
+ clocks = <&cmu_sc_2_pll>;
+ #clock-cells = <1>;
+ clock-output-names =
+ "scp", // 0
+ "gmac1_ptp", // 1
+ "gmac1_txx2", // 2
+ "gmac2_ptp", // 3
+ "gmac2_txx2", // 4
+ "gic_distr", // 5
+ "dbg", // 6
+ "cnt_valueb", // 7
+ "ts_valueb", // 8
+ "lpd", // 9
+ "tbu", // 10
+ "hs_cfg", // 11
+ "scp_bisr"; // 12
+ };
+ };
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree include file for BE-S1000 SoC CoreSight subsystem
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ */
+
+#include <dt-bindings/arm/coresight-cti-dt.h>
+
+#define CA75_DEBUG(cl, c, base, n) \
+ debug##c: debug@##base##n##10000 { \
+ compatible = "arm,coresight-cpu-debug", "arm,primecell"; \
+ reg = <0x0 0x##base##n##10000 0x0 0x1000>; \
+ clocks = <&cluster##cl##_cmu0 2>; \
+ clock-names = "apb_pclk"; \
+ cpu = <&cpu##c>; \
+ }; \
+ \
+ etm##c: etm@##base##n##40000 { \
+ compatible = "arm,coresight-etm4x", "arm,primecell"; \
+ reg = <0x0 0x##base##n##40000 0x0 0x1000>; \
+ clocks = <&cluster##cl##_cmu0 2>; \
+ clock-names = "apb_pclk"; \
+ cpu = <&cpu##c>; \
+ \
+ out-ports { \
+ port { \
+ etm##c##_out_port: endpoint { \
+ remote-endpoint = \
+ <&funnel##cl##_in_port##n>; \
+ }; \
+ }; \
+ }; \
+ }; \
+ \
+ cti##cl##_##n: cti@##base##n##20000 { \
+ compatible = "arm,coresight-cti-v8-arch", "arm,coresight-cti", \
+ "arm,primecell"; \
+ reg = <0x0 0x##base##n##20000 0x0 0x1000>; \
+ clocks = <&cluster##cl##_cmu0 2>; \
+ clock-names = "apb_pclk"; \
+ cpu = <&cpu##c>; \
+ arm,cs-dev-assoc = <&etm##c>; \
+ }
+
+#define CA75_CS(cl, c0, c1, c2, c3, cs_base, debug_base) \
+ CA75_DEBUG(cl,c0,debug_base,0); \
+ CA75_DEBUG(cl,c1,debug_base,1); \
+ CA75_DEBUG(cl,c2,debug_base,2); \
+ CA75_DEBUG(cl,c3,debug_base,3); \
+ \
+ funnel##cl: funnel@##cs_base##020000 { \
+ compatible = "arm,coresight-dynamic-funnel", "arm,primecell"; \
+ reg = <0x0 0x##cs_base##020000 0x0 0x1000>; \
+ clocks = <&cluster##cl##_cmu0 2>; \
+ clock-names = "apb_pclk"; \
+ \
+ out-ports { \
+ port { \
+ funnel##cl##_out_port: endpoint { \
+ remote-endpoint = \
+ <&etb##cl##_in_port>; \
+ }; \
+ }; \
+ }; \
+ \
+ in-ports { \
+ #address-cells = <1>; \
+ #size-cells = <0>; \
+ \
+ port@0 { \
+ reg = <0>; \
+ funnel##cl##_in_port0: endpoint { \
+ remote-endpoint = \
+ <&etm##c0##_out_port>; \
+ }; \
+ }; \
+ \
+ port@1 { \
+ reg = <1>; \
+ funnel##cl##_in_port1: endpoint { \
+ remote-endpoint = \
+ <&etm##c1##_out_port>; \
+ }; \
+ }; \
+ \
+ port@2 { \
+ reg = <2>; \
+ funnel##cl##_in_port2: endpoint { \
+ remote-endpoint = \
+ <&etm##c2##_out_port>; \
+ }; \
+ }; \
+ \
+ port@3 { \
+ reg = <3>; \
+ funnel##cl##_in_port3: endpoint { \
+ remote-endpoint = \
+ <&etm##c3##_out_port>; \
+ }; \
+ }; \
+ }; \
+ }; \
+ \
+ etb##cl: etb@##cs_base##030000 { \
+ compatible = "arm,coresight-tmc", "arm,primecell"; \
+ reg = <0x0 0x##cs_base##030000 0x0 0x1000>; \
+ clocks = <&cluster##cl##_cmu0 2>; \
+ clock-names = "apb_pclk"; \
+ \
+ in-ports { \
+ port { \
+ etb##cl##_in_port: endpoint { \
+ remote-endpoint = \
+ <&funnel##cl##_out_port>; \
+ }; \
+ }; \
+ }; \
+ }; \
+ \
+ cti##cl: cti@##cs_base##010000 { \
+ compatible = "arm,coresight-cti", "arm,primecell"; \
+ reg = <0x0 0x##cs_base##010000 0x0 0x1000>; \
+ clocks = <&cluster##cl##_cmu0 2>; \
+ clock-names = "apb_pclk"; \
+ \
+ #address-cells = <1>; \
+ #size-cells = <0>; \
+ \
+ trig-conns@0 { \
+ reg = <0>; \
+ arm,trig-in-sigs = <0 1 2>; \
+ arm,trig-in-types = <SNK_ACQCOMP SNK_FULL \
+ SNK_FLUSHCOMP>; \
+ arm,trig-out-sigs = <0 1>; \
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>; \
+ arm,cs-dev-assoc = <&etb##cl>; \
+ }; \
+ \
+ trig-conns@1 { \
+ reg = <1>; \
+ arm,trig-out-sigs = <2>; \
+ arm,trig-conn-name = "pmu_snapshot"; \
+ }; \
+ }
+
+/ {
+ soc {
+ /* CA75 Blocks */
+
+ CA75_CS(0,0,1,2,3,6,7);
+ CA75_CS(1,4,5,6,7,a,b);
+ CA75_CS(2,8,9,10,11,e,f);
+ CA75_CS(3,12,13,14,15,12,13);
+ CA75_CS(4,16,17,18,19,16,17);
+ CA75_CS(5,20,21,22,23,1a,1b);
+ CA75_CS(6,24,25,26,27,1e,1f);
+ CA75_CS(7,28,29,30,31,22,23);
+ CA75_CS(8,32,33,34,35,26,27);
+ CA75_CS(9,36,37,38,39,2a,2b);
+ CA75_CS(10,40,41,42,43,2e,2f);
+ CA75_CS(11,44,45,46,47,32,33);
+
+ /* STM Block */
+
+ etr: etr@2080000 {
+ compatible = "arm,coresight-tmc", "arm,primecell";
+ reg = <0x0 0x2080000 0x0 0x1000>;
+ clocks = <&cmu_sc_1 6>;
+ clock-names = "apb_pclk";
+
+ in-ports {
+ port {
+ etr_in_port: endpoint {
+ remote-endpoint =
+ <&etf_out_port>;
+ };
+ };
+ };
+ };
+
+ etf: etf@2070000 {
+ compatible = "arm,coresight-tmc", "arm,primecell";
+ reg = <0x0 0x2070000 0x0 0x1000>;
+ clocks = <&cmu_sc_1 6>;
+ clock-names = "apb_pclk";
+
+ out-ports {
+ port {
+ etf_out_port: endpoint {
+ remote-endpoint =
+ <&etr_in_port>;
+ };
+ };
+ };
+
+ in-ports {
+ port {
+ etf_in_port: endpoint {
+ remote-endpoint =
+ <&stm_out_port>;
+ };
+ };
+ };
+ };
+
+ stm: stm@2060000 {
+ compatible = "arm,coresight-stm", "arm,primecell";
+ reg = <0x0 0x2060000 0x0 0x1000>,
+ <0x0 0x3000000 0x0 0x1000000>;
+ reg-names = "stm-base", "stm-stimulus-base";
+ clocks = <&cmu_sc_1 6>;
+ clock-names = "apb_pclk";
+
+ out-ports {
+ port {
+ stm_out_port: endpoint {
+ remote-endpoint =
+ <&etf_in_port>;
+ };
+ };
+ };
+ };
+
+ /* CMN Block */
+
+ funnel: funnel@2050000 {
+ compatible = "arm,coresight-dynamic-funnel",
+ "arm,primecell";
+ reg = <0x0 0x2050000 0x0 0x1000>;
+ clocks = <&cmu_sc_1 6>;
+ clock-names = "apb_pclk";
+
+ out-ports {
+ port {
+ funnel_out_port: endpoint {
+ remote-endpoint =
+ <&etb_in_port>;
+ };
+ };
+ };
+
+ in-ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ funnel_in_port0: endpoint {
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ funnel_in_port1: endpoint {
+ };
+ };
+ };
+ };
+
+ etb: etb@2040000 {
+ compatible = "arm,coresight-tmc", "arm,primecell";
+ reg = <0x0 0x2040000 0x0 0x1000>;
+ clocks = <&cmu_sc_1 6>;
+ clock-names = "apb_pclk";
+
+ in-ports {
+ port {
+ etb_in_port: endpoint {
+ remote-endpoint =
+ <&funnel_out_port>;
+ };
+ };
+ };
+ };
+
+ /* CTI */
+
+ cti: cti@2010000 {
+ compatible = "arm,coresight-cti", "arm,primecell";
+ reg = <0x0 0x2010000 0x0 0x1000>;
+ clocks = <&cmu_sc_1 6>;
+ clock-names = "apb_pclk";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ trig-conns@0 {
+ reg = <0>;
+ arm,trig-out-sigs = <0 1>;
+ arm,trig-out-types = <GEN_HALTREQ
+ GEN_RESTARTREQ>;
+ arm,trig-conn-name = "tsgen";
+ };
+
+ trig-conns@1 {
+ reg = <1>;
+ arm,trig-in-sigs = <0 1 2>;
+ arm,trig-in-types = <SNK_FULL SNK_ACQCOMP
+ SNK_FLUSHCOMP>;
+ arm,trig-out-sigs = <2 3>;
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>;
+ arm,cs-dev-assoc = <&etb>;
+ };
+
+ trig-conns@2 {
+ reg = <2>;
+ arm,trig-in-sigs = <3 4>;
+ arm,trig-conn-name = "cmn_debug_watchtrig";
+ };
+
+ trig-conns@3 {
+ reg = <3>;
+ arm,trig-out-sigs = <4>;
+ arm,trig-conn-name = "cmn_pmu_snapshot";
+ };
+
+ trig-conns@4 {
+ reg = <4>;
+ arm,trig-in-sigs = <5 6 7>;
+ arm,trig-in-types = <SNK_ACQCOMP SNK_FULL
+ SNK_FLUSHCOMP>;
+ arm,trig-out-sigs = <5 6>;
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>;
+ arm,cs-dev-assoc = <&etf>;
+ };
+
+ trig-conns@5 {
+ reg = <5>;
+ arm,trig-in-sigs = <8 9 10>;
+ arm,trig-in-types = <SNK_ACQCOMP SNK_FULL
+ SNK_FLUSHCOMP>;
+ arm,trig-out-sigs = <7 8>;
+ arm,trig-out-types = <SNK_FLUSHIN SNK_TRIGIN>;
+ arm,cs-dev-assoc = <&etr>;
+ };
+
+ trig-conns@6 {
+ reg = <6>;
+ arm,trig-in-sigs = <11 12 13 14>;
+ arm,trig-in-types = <STM_TOUT_SPTE STM_TOUT_SW
+ STM_TOUT_HETE STM_ASYNCOUT>;
+ arm,trig-out-sigs = <9 10 11 12>;
+ arm,trig-out-types = <STM_HWEVENT STM_HWEVENT
+ STM_HWEVENT STM_HWEVENT>;
+ arm,cs-dev-assoc = <&stm>;
+ };
+
+ trig-conns@7 {
+ reg = <7>;
+ arm,trig-out-sigs = <13>;
+ arm,trig-conn-name = "dpeventstatus";
+ };
+
+ trig-conns@8 {
+ reg = <8>;
+ arm,trig-out-sigs = <14 15 16 17>;
+ arm,trig-out-types = <GEN_INTREQ GEN_INTREQ
+ GEN_INTREQ GEN_INTREQ>;
+ arm,trig-conn-name = "cti_interrupt";
+ };
+ };
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree include file for DBS socket X6.16 mezzanine board
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ */
+
+&espi {
+ num-cs = <4>;
+ status = "okay";
+
+ flash@0 {
+ compatible = "rohm,dh2228fv";
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <10000000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <1>;
+ status = "okay";
+ };
+
+ flash@1 {
+ compatible = "rohm,dh2228fv";
+ reg = <1>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <10000000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <1>;
+ status = "okay";
+ };
+
+ flash@2 {
+ compatible = "rohm,dh2228fv";
+ reg = <2>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <10000000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <1>;
+ status = "okay";
+ };
+
+ flash@3 {
+ compatible = "rohm,dh2228fv";
+ reg = <3>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <10000000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <1>;
+ status = "okay";
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree include file for DBS socket X11.16 mezzanine board
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ */
+
+&qspi2 {
+ num-cs = <2>;
+ status = "okay";
+
+ flash@0 {
+ compatible = "rohm,dh2228fv";
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <10000000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <1>;
+ status = "okay";
+ };
+
+ flash@1 {
+ compatible = "rohm,dh2228fv";
+ reg = <1>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <10000000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <1>;
+ status = "okay";
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree source for DBS-OV compatible boards:
+ * - DBS-OV vA0
+ *
+ * Copyright (C) 2022-2023 Baikal Electronics, JSC
+ */
+
+/dts-v1/;
+
+#include "bs1000-dbs.dtsi"
+
+/ {
+ model = "Baikal Electronics DBS-OV";
+ compatible = "baikal,dbs-ov", "baikal,bs1000";
+};
+
+&pcie0_p0 {
+ num-lanes = <16>;
+ status = "okay";
+};
+
+&pcie0_p0_ep {
+ num-lanes = <16>;
+ status = "okay";
+};
+
+/delete-node/ &pcie0_p0_ep;
+
+&pcie1_p0 {
+ num-lanes = <16>;
+ status = "okay";
+};
+
+&pcie1_p0_ep {
+ num-lanes = <16>;
+ status = "okay";
+};
+
+/delete-node/ &pcie1_p0_ep;
+
+&pcie2_p0 {
+ num-lanes = <16>;
+ status = "okay";
+};
+
+&pcie2_p0_ep {
+ num-lanes = <16>;
+ status = "okay";
+};
+
+/delete-node/ &pcie2_p0_ep;
+
+&pcie3_p0 {
+ num-lanes = <8>;
+ status = "okay";
+};
+
+&pcie3_p2 {
+ num-lanes = <4>;
+ status = "okay";
+};
+
+&pcie3_p3 {
+ num-lanes = <4>;
+ status = "okay";
+};
+
+&pcie4_p0 {
+ num-lanes = <8>;
+ status = "okay";
+};
+
+&pcie4_p2 {
+ num-lanes = <4>;
+ status = "okay";
+};
+
+&pcie4_p3 {
+ num-lanes = <4>;
+ status = "okay";
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree source for DBS compatible boards:
+ * - DBS vA0
+ *
+ * Copyright (C) 2021-2023 Baikal Electronics, JSC
+ */
+
+/dts-v1/;
+
+#include "bs1000-dbs.dtsi"
+
+/ {
+ model = "Baikal Electronics DBS";
+ compatible = "baikal,dbs", "baikal,bs1000";
+};
+
+&pcie0_p0 {
+ num-lanes = <8>;
+ status = "okay";
+};
+
+&pcie0_p0_ep {
+ num-lanes = <8>;
+ status = "okay";
+};
+
+/delete-node/ &pcie0_p0_ep;
+
+&pcie0_p1 {
+ num-lanes = <8>;
+ status = "okay";
+};
+
+&pcie1_p0 {
+ num-lanes = <8>;
+ status = "okay";
+};
+
+&pcie1_p0_ep {
+ num-lanes = <8>;
+ status = "okay";
+};
+
+/delete-node/ &pcie1_p0_ep;
+
+&pcie1_p1 {
+ num-lanes = <8>;
+ status = "okay";
+};
+
+&pcie2_p0 {
+ num-lanes = <8>;
+ status = "okay";
+};
+
+&pcie2_p0_ep {
+ num-lanes = <8>;
+ status = "okay";
+};
+
+/delete-node/ &pcie2_p0_ep;
+
+&pcie2_p1 {
+ num-lanes = <8>;
+ status = "okay";
+};
+
+&pcie3_p0 {
+ num-lanes = <4>;
+ status = "okay";
+};
+
+&pcie3_p1 {
+ num-lanes = <4>;
+ status = "okay";
+};
+
+&pcie3_p2 {
+ num-lanes = <4>;
+ status = "okay";
+};
+
+&pcie3_p3 {
+ num-lanes = <4>;
+ status = "okay";
+};
+
+&pcie4_p0 {
+ num-lanes = <4>;
+ status = "okay";
+};
+
+&pcie4_p1 {
+ num-lanes = <4>;
+ status = "okay";
+};
+
+&pcie4_p2 {
+ num-lanes = <4>;
+ status = "okay";
+};
+
+&pcie4_p3 {
+ num-lanes = <4>;
+ status = "okay";
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree include file for DBS-compatible boards
+ * Copyright (C) 2021-2023 Baikal Electronics, JSC
+ */
+
+#include "bs1000.dtsi"
+
+/ {
+ aliases {
+ ethernet0 = &gmac0;
+ ethernet1 = &gmac1;
+ };
+
+ chosen { };
+};
+
+&ehci {
+ status = "okay";
+};
+
+&gmac0 {
+ status = "okay";
+ phy-handle = <ðphy0>;
+ phy-mode = "rgmii-rxid";
+};
+
+&gmac1 {
+ status = "okay";
+ phy-handle = <ðphy1>;
+ phy-mode = "rgmii-rxid";
+};
+
+&gpio32 {
+ status = "okay";
+};
+
+&i2c2 {
+ status = "okay";
+};
+
+&i2c3 {
+ status = "okay";
+};
+
+&i2c4 {
+ status = "okay";
+};
+
+&mdio0 {
+ ethphy0: ethernet-phy@1 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x1>;
+ };
+};
+
+&mdio1 {
+ ethphy1: ethernet-phy@1 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x1>;
+ };
+};
+
+&mux {
+ status = "okay";
+};
+
+&mux0 {
+ status = "okay";
+};
+
+&mux1 {
+ status = "okay";
+};
+
+&mux2 {
+ status = "okay";
+};
+
+&ohci {
+ status = "okay";
+};
+
+&pvt_cluster0 {
+ status = "okay";
+};
+
+&pvt_cluster1 {
+ status = "okay";
+};
+
+&pvt_cluster2 {
+ status = "okay";
+};
+
+&pvt_cluster3 {
+ status = "okay";
+};
+
+&pvt_cluster4 {
+ status = "okay";
+};
+
+&pvt_cluster5 {
+ status = "okay";
+};
+
+&pvt_cluster6 {
+ status = "okay";
+};
+
+&pvt_cluster7 {
+ status = "okay";
+};
+
+&pvt_cluster8 {
+ status = "okay";
+};
+
+&pvt_cluster9 {
+ status = "okay";
+};
+
+&pvt_cluster10 {
+ status = "okay";
+};
+
+&pvt_cluster11 {
+ status = "okay";
+};
+
+&pvt_ddr0 {
+ status = "okay";
+};
+
+&pvt_ddr1 {
+ status = "okay";
+};
+
+&pvt_ddr2 {
+ status = "okay";
+};
+
+&pvt_ddr3 {
+ status = "okay";
+};
+
+&pvt_ddr4 {
+ status = "okay";
+};
+
+&pvt_ddr5 {
+ status = "okay";
+};
+
+&pvt_pcie0 {
+ status = "okay";
+};
+
+&pvt_pcie1 {
+ status = "okay";
+};
+
+&pvt_pcie2 {
+ status = "okay";
+};
+
+&pvt_pcie3 {
+ status = "okay";
+};
+
+&pvt_pcie4 {
+ status = "okay";
+};
+
+&qspi1 {
+ num-cs = <4>;
+ status = "okay";
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <10000000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <1>;
+ status = "okay";
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "bl1";
+ reg = <0x000000 0x40000>;
+ };
+
+ partition@40000 {
+ label = "dtb";
+ reg = <0x040000 0x40000>;
+ };
+
+ partition@80000 {
+ label = "uefi-vars";
+ reg = <0x080000 0xc0000>;
+ };
+
+ partition@140000 {
+ label = "fip";
+ reg = <0x140000 0x6c0000>;
+ };
+ };
+ };
+};
+
+&timer1 {
+ status = "okay";
+};
+
+&timer2 {
+ status = "okay";
+};
+
+&timer3 {
+ status = "okay";
+};
+
+&timer4 {
+ status = "okay";
+};
+
+&uart_a1 {
+ status = "okay";
+};
+
+&uart_a2 {
+ status = "okay";
+};
+
+&uart_s {
+ status = "okay";
+};
+
+&wdt {
+ status = "okay";
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree source for Baikal Electronics QEMU-S virtual platform
+ * Copyright (C) 2021-2022 Baikal Electronics, JSC
+ */
+
+/dts-v1/;
+
+#include "bs1000.dtsi"
+
+/ {
+ model = "Baikal Electronics QEMU-S";
+ compatible = "baikal,qemu-s", "baikal,bs1000";
+
+ aliases {
+ ethernet0 = &gmac0;
+ ethernet1 = &gmac1;
+ };
+
+ chosen { };
+
+ /*
+ * Device is necessary for UEFI to boot on QEMU,
+ * need to replace it with something later.
+ */
+ flash@0 {
+ compatible = "cfi-flash";
+ reg = <0x0 0x4000000 0x0 0x4000000>;
+ bank-width = <0x4>;
+ };
+};
+
+&ehci {
+ status = "okay";
+};
+
+&gmac0 {
+ status = "okay";
+ phy-handle = <ðphy0>;
+ phy-mode = "rgmii-rxid";
+};
+
+&gmac1 {
+ status = "okay";
+ phy-handle = <ðphy1>;
+ phy-mode = "rgmii-rxid";
+};
+
+&mdio0 {
+ ethphy0: ethernet-phy@3 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x3>;
+ };
+};
+
+&mdio1 {
+ ethphy1: ethernet-phy@3 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x3>;
+ };
+};
+
+&ohci {
+ status = "okay";
+};
+
+&qspi1 {
+ num-cs = <4>;
+ status = "okay";
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <10000000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <1>;
+ status = "okay";
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "bl1";
+ reg = <0x000000 0x40000>;
+ };
+
+ partition@40000 {
+ label = "dtb";
+ reg = <0x040000 0x40000>;
+ };
+
+ partition@80000 {
+ label = "uefi-vars";
+ reg = <0x080000 0xc0000>;
+ };
+
+ partition@140000 {
+ label = "fip";
+ reg = <0x140000 0x6c0000>;
+ };
+ };
+ };
+};
+
+&timer1 {
+ status = "okay";
+};
+
+&uart_a1 {
+ status = "okay";
+};
+
+&uart_a2 {
+ status = "okay";
+};
+
+&uart_s {
+ status = "okay";
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device tree include file for BE-S1000 SoC
+ * Copyright (C) 2021-2023 Baikal Electronics, JSC
+ */
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+#include "bs1000-clocks.dtsi"
+
+#define CA75_CLUSTER(group, a,b,c,d) \
+ cluster##group: \
+ cluster##group { \
+ core0 { \
+ cpu = <&cpu##a>; \
+ }; \
+ core1 { \
+ cpu = <&cpu##b>; \
+ }; \
+ core2 { \
+ cpu = <&cpu##c>; \
+ }; \
+ core3 { \
+ cpu = <&cpu##d>; \
+ }; \
+ l3_##group: l3-cache##group { \
+ compatible = "cache"; \
+ cache-size = <0x200000>; \
+ cache-line-size = <64>; \
+ cache-sets = <2048>; \
+ cache-unified; \
+ cache-level = <3>; \
+ next-level-cache = <&l4>; \
+ }; \
+ }
+
+#define CA75_CPU(core, group, affinity) \
+ cpu##core: cpu@##affinity { \
+ device_type = "cpu"; \
+ compatible = "arm,cortex-a75"; \
+ reg = <0x0 0x##affinity>; \
+ enable-method = "psci"; \
+ i-cache-size = <0x10000>; \
+ i-cache-line-size = <64>; \
+ i-cache-sets = <256>; \
+ d-cache-size = <0x10000>; \
+ d-cache-line-size = <64>; \
+ d-cache-sets = <64>; \
+ next-level-cache = <&l2_##core>; \
+ operating-points-v2 = <&cpu_opp_table>; \
+ clocks = <&cluster##group##_cmu1 (core%4)>; \
+ l2_##core: l2-cache { \
+ compatible = "cache"; \
+ cache-size = <0x80000>; \
+ cache-line-size = <64>; \
+ cache-sets = <1024>; \
+ cache-unified; \
+ cache-level = <2>; \
+ next-level-cache = <&l3_##group>; \
+ }; \
+ }
+
+/ {
+ compatible = "baikal,bs1000";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ interrupt-parent = <&gic>;
+
+ aliases {
+ i2c2 = &i2c2;
+ i2c3 = &i2c3;
+ i2c4 = &i2c4;
+ i2c5 = &i2c5;
+ i2c6 = &i2c6;
+ serial0 = &uart_a1;
+ serial1 = &uart_a2;
+ serial2 = &uart_s;
+ };
+
+ psci {
+ compatible = "arm,psci-1.0", "arm,psci-0.2";
+ method = "smc";
+ };
+
+ cpus {
+ #address-cells = <2>;
+ #size-cells = <0>;
+
+ cpu-map {
+ CA75_CLUSTER(0, 0, 1, 2, 3 );
+ CA75_CLUSTER(1, 4, 5, 6, 7 );
+ CA75_CLUSTER(2, 8, 9, 10,11);
+ CA75_CLUSTER(3, 12,13,14,15);
+ CA75_CLUSTER(4, 16,17,18,19);
+ CA75_CLUSTER(5, 20,21,22,23);
+ CA75_CLUSTER(6, 24,25,26,27);
+ CA75_CLUSTER(7, 28,29,30,31);
+ CA75_CLUSTER(8, 32,33,34,35);
+ CA75_CLUSTER(9, 36,37,38,39);
+ CA75_CLUSTER(10, 40,41,42,43);
+ CA75_CLUSTER(11, 44,45,46,47);
+ };
+
+ CA75_CPU(0,0,0);
+ CA75_CPU(1,0,100);
+ CA75_CPU(2,0,200);
+ CA75_CPU(3,0,300);
+ CA75_CPU(4,1,10000);
+ CA75_CPU(5,1,10100);
+ CA75_CPU(6,1,10200);
+ CA75_CPU(7,1,10300);
+ CA75_CPU(8,2,20000);
+ CA75_CPU(9,2,20100);
+ CA75_CPU(10,2,20200);
+ CA75_CPU(11,2,20300);
+ CA75_CPU(12,3,30000);
+ CA75_CPU(13,3,30100);
+ CA75_CPU(14,3,30200);
+ CA75_CPU(15,3,30300);
+ CA75_CPU(16,4,40000);
+ CA75_CPU(17,4,40100);
+ CA75_CPU(18,4,40200);
+ CA75_CPU(19,4,40300);
+ CA75_CPU(20,5,50000);
+ CA75_CPU(21,5,50100);
+ CA75_CPU(22,5,50200);
+ CA75_CPU(23,5,50300);
+ CA75_CPU(24,6,60000);
+ CA75_CPU(25,6,60100);
+ CA75_CPU(26,6,60200);
+ CA75_CPU(27,6,60300);
+ CA75_CPU(28,7,70000);
+ CA75_CPU(29,7,70100);
+ CA75_CPU(30,7,70200);
+ CA75_CPU(31,7,70300);
+ CA75_CPU(32,8,80000);
+ CA75_CPU(33,8,80100);
+ CA75_CPU(34,8,80200);
+ CA75_CPU(35,8,80300);
+ CA75_CPU(36,9,90000);
+ CA75_CPU(37,9,90100);
+ CA75_CPU(38,9,90200);
+ CA75_CPU(39,9,90300);
+ CA75_CPU(40,10,a0000);
+ CA75_CPU(41,10,a0100);
+ CA75_CPU(42,10,a0200);
+ CA75_CPU(43,10,a0300);
+ CA75_CPU(44,11,b0000);
+ CA75_CPU(45,11,b0100);
+ CA75_CPU(46,11,b0200);
+ CA75_CPU(47,11,b0300);
+
+ l4: l4-cache {
+ cache-size = <0x2000000>;
+ cache-unified;
+ cache-level = <4>;
+ };
+ };
+
+ cpu_opp_table: opp_table {
+ compatible = "operating-points-v2";
+ opp-250000000 {
+ opp-hz = /bits/ 64 <250000000>;
+ };
+ opp-500000000 {
+ opp-hz = /bits/ 64 <500000000>;
+ };
+ opp-1000000000 {
+ opp-hz = /bits/ 64 <1000000000>;
+ };
+ opp-2000000000 {
+ opp-hz = /bits/ 64 <2000000000>;
+ };
+ };
+
+ pmu {
+ compatible = "arm,cortex-a75-pmu", "arm,armv8-pmuv3";
+ interrupts = <GIC_PPI 7 IRQ_TYPE_LEVEL_LOW>;
+ };
+
+ timer {
+ compatible = "arm,armv8-timer";
+ interrupts = <GIC_PPI 13 IRQ_TYPE_LEVEL_LOW>,
+ <GIC_PPI 14 IRQ_TYPE_LEVEL_LOW>,
+ <GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
+ <GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
+ };
+
+ soc {
+ compatible = "simple-bus";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+
+ ohci: usb@a00000 {
+ compatible = "generic-ohci";
+ reg = <0x0 0xa00000 0x0 0x1000>;
+ interrupts = <GIC_SPI 78 IRQ_TYPE_LEVEL_HIGH>;
+ dr_mode = "host";
+ dma-coherent;
+ clocks = <&cmu_sc_1 13>, <&cmu_sc_1 14>;
+ clock-names = "ohci_hclk", "ohci_clk48";
+ status = "disabled";
+ };
+
+ ehci: usb@a10000 {
+ compatible = "generic-ehci";
+ reg = <0x0 0xa10000 0x0 0x1000>;
+ interrupts = <GIC_SPI 82 IRQ_TYPE_LEVEL_HIGH>;
+ dr_mode = "host";
+ dma-coherent;
+ clocks = <&cmu_sc_1 13>;
+ clock-names = "ehci_hclk";
+ status = "disabled";
+ };
+
+ gmac0: ethernet@a20000 {
+ compatible = "baikal,bs1000-gmac";
+ reg = <0x0 0xa20000 0x0 0x10000>;
+ interrupts = <GIC_SPI 76 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "macirq";
+ clocks = <&cmu_sc_1 9>, <&cmu_sc_2 1>, <&cmu_sc_2 2>;
+ clock-names = "stmmaceth", "ptp_ref", "tx2_clk";
+ dma-coherent;
+ status = "disabled";
+
+ mdio0: mdio {
+ compatible = "snps,dwmac-mdio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ };
+
+ gmac1: ethernet@a30000 {
+ compatible = "baikal,bs1000-gmac";
+ reg = <0x0 0xa30000 0x0 0x10000>;
+ interrupts = <GIC_SPI 77 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "macirq";
+ clocks = <&cmu_sc_1 11>, <&cmu_sc_2 3>, <&cmu_sc_2 4>;
+ clock-names = "stmmaceth", "ptp_ref", "tx2_clk";
+ dma-coherent;
+ status = "disabled";
+
+ mdio1: mdio {
+ compatible = "snps,dwmac-mdio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ };
+
+ uart_a1: serial@c00000 {
+ compatible = "arm,pl011", "arm,primecell";
+ reg = <0x0 0xc00000 0x0 0x1000>;
+ interrupts = <GIC_SPI 61 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&uart1_clk>, <&apb_clk>;
+ // clocks = <&cmu_sc_1 18>, <&cmu_sc_1 15>; // "lsp_uart_arm1", "lsp_apb"
+ clock-names = "uartclk", "apb_pclk";
+ status = "disabled";
+ };
+
+ uart_a2: serial@c10000 {
+ compatible = "arm,pl011", "arm,primecell";
+ reg = <0x0 0xc10000 0x0 0x1000>;
+ interrupts = <GIC_SPI 62 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&uart2_clk>, <&apb_clk>;
+ // clocks = <&cmu_sc_1 19>, <&cmu_sc_1 15>; // "lsp_uart_arm2", "lsp_apb"
+ clock-names = "uartclk", "apb_pclk";
+ status = "disabled";
+ };
+
+ qspi1: spi@c20000 {
+ compatible = "snps,dw-apb-ssi";
+ reg = <0x0 0xc20000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 63 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spi_clk>;
+ // clocks = <&cmu_sc_1 20>; // "lsp_spi1"
+ status = "disabled";
+ };
+
+ gpio32: gpio@c50000 {
+ compatible = "snps,dw-apb-gpio";
+ reg = <0x0 0xc50000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&gpio_clk>;
+ // clocks = <&cmu_sc_1 16>; // "lsp_gpio_x16"
+ status = "disabled";
+
+ porta: gpio-controller@0 {
+ compatible = "snps,dw-apb-gpio-port";
+ gpio-controller;
+ #gpio-cells = <2>;
+ ngpios = <32>;
+ reg = <0>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <GIC_SPI 54 IRQ_TYPE_LEVEL_HIGH>;
+ };
+ };
+
+ i2c2: i2c@c90000 {
+ compatible = "snps,designware-i2c";
+ reg = <0x0 0xc90000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 65 IRQ_TYPE_LEVEL_HIGH>;
+ i2c-sda-hold-time-ns = <500>;
+ clock-frequency = <400000>;
+ clocks = <&i2c_clk>;
+ // clocks = <&cmu_sc_1 23>; // "lsp_smbus_i2c2"
+ clock-names = "ref";
+ status = "disabled";
+ };
+
+ i2c3: i2c@ca0000 {
+ compatible = "snps,designware-i2c";
+ reg = <0x0 0xca0000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>;
+ i2c-sda-hold-time-ns = <500>;
+ clock-frequency = <400000>;
+ clocks = <&i2c_clk>;
+ // clocks = <&cmu_sc_1 24>; // "lsp_smbus_i2c3"
+ clock-names = "ref";
+ status = "disabled";
+ };
+
+ i2c4: i2c@cb0000 {
+ compatible = "snps,designware-i2c";
+ reg = <0x0 0xcb0000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
+ i2c-sda-hold-time-ns = <500>;
+ clock-frequency = <400000>;
+ clocks = <&i2c_clk>;
+ // clocks = <&cmu_sc_1 25>; // "lsp_smbus_i2c4"
+ clock-names = "ref";
+ status = "disabled";
+ };
+
+ wdt: watchdog@ce0000 {
+ compatible = "snps,dw-wdt";
+ reg = <0x0 0xce0000 0x0 0x1000>;
+ interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&wdt_clk 0>;
+ // clocks = <&cmu_sc_1 32>; // "lsp_wdt_x16"
+ clock-names = "tclk";
+ snps,watchdog-tops = <0x000000ff 0x000001ff 0x000003ff 0x000007ff
+ 0x0000ffff 0x0001ffff 0x0003ffff 0x0007ffff
+ 0x000fffff 0x001fffff 0x003fffff 0x007fffff
+ 0x00ffffff 0x01ffffff 0x03ffffff 0x07ffffff>;
+ status = "disabled";
+ };
+
+ timer1: timer@cf0000 {
+ compatible = "snps,dw-apb-timer";
+ reg = <0x0 0xcf0000 0x0 0x14>;
+ interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&timer1_clk>;
+ // clocks = <&cmu_sc_1 28>; // "lsp_timer1"
+ clock-names = "timer";
+ status = "disabled";
+ };
+
+ timer2: timer@cf0014 {
+ compatible = "snps,dw-apb-timer";
+ reg = <0x0 0xcf0014 0x0 0x14>;
+ interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&timer2_clk>;
+ // clocks = <&cmu_sc_1 29>; // "lsp_timer2"
+ clock-names = "timer";
+ status = "disabled";
+ };
+
+ timer3: timer@cf0028 {
+ compatible = "snps,dw-apb-timer";
+ reg = <0x0 0xcf0028 0x0 0x14>;
+ interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&timer3_clk>;
+ // clocks = <&cmu_sc_1 30>; // "lsp_timer3"
+ clock-names = "timer";
+ status = "disabled";
+ };
+
+ timer4: timer@cf003c {
+ compatible = "snps,dw-apb-timer";
+ reg = <0x0 0xcf003c 0x0 0x14>;
+ interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&timer4_clk>;
+ //clocks = <&cmu_sc_1 31>; // "lsp_timer4"
+ clock-names = "timer";
+ status = "disabled";
+ };
+
+ gic: interrupt-controller@1000000 {
+ compatible = "arm,gic-v3";
+ reg = <0x0 0x1000000 0x0 0x10000>, /* GICD */
+ <0x0 0x1240000 0x0 0x600000>; /* GICR */
+ /* TODO: optional ranges - GICC, GICH, GICV */
+ #interrupt-cells = <3>;
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ interrupt-controller;
+ interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
+
+ its0: interrupt-controller@1040000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x1040000 0x0 0x20000>;
+ msi-controller;
+ };
+ its1: interrupt-controller@1060000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x1060000 0x0 0x20000>;
+ msi-controller;
+ };
+ its2: interrupt-controller@1080000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x1080000 0x0 0x20000>;
+ msi-controller;
+ };
+ its3: interrupt-controller@10a0000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x10a0000 0x0 0x20000>;
+ msi-controller;
+ };
+ its4: interrupt-controller@10c0000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x10c0000 0x0 0x20000>;
+ msi-controller;
+ };
+ its5: interrupt-controller@10e0000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x10e0000 0x0 0x20000>;
+ msi-controller;
+ };
+ its6: interrupt-controller@1100000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x1100000 0x0 0x20000>;
+ msi-controller;
+ };
+ its7: interrupt-controller@1120000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x1120000 0x0 0x20000>;
+ msi-controller;
+ };
+ its8: interrupt-controller@1140000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x1140000 0x0 0x20000>;
+ msi-controller;
+ };
+ its9: interrupt-controller@1160000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x1160000 0x0 0x20000>;
+ msi-controller;
+ };
+ its10: interrupt-controller@1180000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x1180000 0x0 0x20000>;
+ msi-controller;
+ };
+ its11: interrupt-controller@11a0000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x11a0000 0x0 0x20000>;
+ msi-controller;
+ };
+ its12: interrupt-controller@11c0000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x11c0000 0x0 0x20000>;
+ msi-controller;
+ };
+ its13: interrupt-controller@11e0000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x11e0000 0x0 0x20000>;
+ msi-controller;
+ };
+ its14: interrupt-controller@1200000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x1200000 0x0 0x20000>;
+ msi-controller;
+ };
+ its15: interrupt-controller@1220000 {
+ compatible = "arm,gic-v3-its";
+ reg = <0x0 0x1220000 0x0 0x20000>;
+ msi-controller;
+ };
+ };
+
+ pcie0_p0: pcie@39000000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x39000000 0x0 0x400000>,
+ <0x00000000 0x38d40000 0x0 0x100>,
+ <0x00000700 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 145 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 147 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00000000 0x701 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x702 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its0>;
+ msi-map = <0x0 &its0 0x0 0x10000>;
+ status = "disabled";
+ };
+
+ pcie0_p0_ep: pcie-ep@39000000 {
+ compatible = "baikal,bs1000-pcie-ep";
+ reg = <0x00000000 0x39000000 0x0 0x400000>,
+ <0x00000000 0x38d40000 0x0 0x100>,
+ <0x00000700 0x00000000 0x1 0x00000000>;
+ reg-names = "dbi", "apb", "addr_space";
+ num-ib-windows = <4>;
+ num-ob-windows = <4>;
+ status = "disabled";
+ };
+
+ pcie0_p1: pcie@39400000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x39400000 0x0 0x400000>,
+ <0x00000000 0x38d41000 0x0 0x100>,
+ <0x00000740 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 146 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00100000 0x741 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x742 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its0>;
+ msi-map = <0x0 &its0 0x10000 0x10000>;
+ status = "disabled";
+ };
+
+ pcie1_p0: pcie@3d000000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x3d000000 0x0 0x400000>,
+ <0x00000000 0x3cd40000 0x0 0x100>,
+ <0x00000780 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 190 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00200000 0x781 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x782 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its2>;
+ msi-map = <0x0 &its2 0x0 0x10000>;
+ status = "disabled";
+ };
+
+ pcie1_p0_ep: pcie-ep@3d000000 {
+ compatible = "baikal,bs1000-pcie-ep";
+ reg = <0x00000000 0x3d000000 0x0 0x400000>,
+ <0x00000000 0x3cd40000 0x0 0x100>,
+ <0x00000780 0x00000000 0x1 0x00000000>;
+ reg-names = "dbi", "apb", "addr_space";
+ num-ib-windows = <4>;
+ num-ob-windows = <4>;
+ status = "disabled";
+ };
+
+ pcie1_p1: pcie@3d400000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x3d400000 0x0 0x400000>,
+ <0x00000000 0x3cd41000 0x0 0x100>,
+ <0x000007c0 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 189 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 191 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00300000 0x7c1 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x7c2 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its2>;
+ msi-map = <0x0 &its2 0x10000 0x10000>;
+ status = "disabled";
+ };
+
+ pcie2_p0: pcie@45000000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x45000000 0x0 0x400000>,
+ <0x00000000 0x44d40000 0x0 0x100>,
+ <0x00000500 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00400000 0x501 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x502 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its4>;
+ msi-map = <0x0 &its4 0x0 0x10000>;
+ status = "disabled";
+ };
+
+ pcie2_p0_ep: pcie-ep@45000000 {
+ compatible = "baikal,bs1000-pcie-ep";
+ reg = <0x00000000 0x45000000 0x0 0x400000>,
+ <0x00000000 0x44d40000 0x0 0x100>,
+ <0x00000500 0x00000000 0x1 0x00000000>;
+ reg-names = "dbi", "apb", "addr_space";
+ num-ib-windows = <4>;
+ num-ob-windows = <4>;
+ status = "disabled";
+ };
+
+ pcie2_p1: pcie@45400000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x45400000 0x0 0x400000>,
+ <0x00000000 0x44d41000 0x0 0x100>,
+ <0x00000540 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00500000 0x541 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x542 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its4>;
+ msi-map = <0x0 &its4 0x10000 0x10000>;
+ status = "disabled";
+ };
+
+ pcie3_p0: pcie@49000000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x49000000 0x0 0x400000>,
+ <0x00000000 0x48d40000 0x0 0x100>,
+ <0x00000400 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 249 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 253 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00600000 0x401 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x402 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its6>;
+ msi-map = <0x0 &its6 0x0 0x10000>;
+ status = "disabled";
+ };
+
+ pcie3_p1: pcie@49400000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x49400000 0x0 0x400000>,
+ <0x00000000 0x48d41000 0x0 0x100>,
+ <0x00000440 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 250 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 254 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00700000 0x441 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x442 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its7>;
+ msi-map = <0x0 &its7 0x10000 0x10000>;
+ status = "disabled";
+ };
+
+ pcie3_p2: pcie@49800000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x49800000 0x0 0x400000>,
+ <0x00000000 0x48d42000 0x0 0x100>,
+ <0x00000480 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 251 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 255 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00800000 0x481 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x482 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its8>;
+ msi-map = <0x0 &its8 0x20000 0x10000>;
+ status = "disabled";
+ };
+
+ pcie3_p3: pcie@49c00000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x49c00000 0x0 0x400000>,
+ <0x00000000 0x48d43000 0x0 0x100>,
+ <0x000004c0 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 252 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 256 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00900000 0x4c1 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x4c2 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its9>;
+ msi-map = <0x0 &its9 0x30000 0x10000>;
+ status = "disabled";
+ };
+
+ pcie4_p0: pcie@4d000000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x4d000000 0x0 0x400000>,
+ <0x00000000 0x4cd40000 0x0 0x100>,
+ <0x00000600 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 318 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 322 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00a00000 0x601 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x602 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its11>;
+ msi-map = <0x0 &its11 0x0 0x10000>;
+ status = "disabled";
+ };
+
+ pcie4_p1: pcie@4d400000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x4d400000 0x0 0x400000>,
+ <0x00000000 0x4cd41000 0x0 0x100>,
+ <0x00000640 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 319 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 323 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00b00000 0x641 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x642 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its12>;
+ msi-map = <0x0 &its12 0x10000 0x10000>;
+ status = "disabled";
+ };
+
+ pcie4_p2: pcie@4d800000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x4d800000 0x0 0x400000>,
+ <0x00000000 0x4cd42000 0x0 0x100>,
+ <0x00000680 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 320 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 324 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00c00000 0x681 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x682 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its13>;
+ msi-map = <0x0 &its13 0x20000 0x10000>;
+ status = "disabled";
+ };
+
+ pcie4_p3: pcie@4dc00000 {
+ device_type = "pci";
+ compatible = "baikal,bs1000-pcie";
+ reg = <0x00000000 0x4dc00000 0x0 0x400000>,
+ <0x00000000 0x4cd43000 0x0 0x100>,
+ <0x000006c0 0x00000000 0x0 0x10000000>;
+ reg-names = "dbi", "apb", "config";
+ bus-range = <0x0 0xff>;
+ interrupts = <GIC_SPI 321 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 325 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr", "msi";
+ #interrupt-cells = <1>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0x0 0x00d00000 0x6c1 0x00000000 0x0 0x100000>,
+ <0x82000000 0x0 0x40000000 0x6c2 0x00000000 0x0 0x40000000>;
+ num-viewport = <4>;
+ msi-parent = <&its14>;
+ msi-map = <0x0 &its14 0x30000 0x10000>;
+ status = "disabled";
+ };
+
+ ddr0: memory-controller@53000000 {
+ compatible = "baikal,bs1000-edac-mc";
+ reg = <0x0 0x53000000 0x0 0x10000>;
+ interrupts = <GIC_SPI 358 IRQ_TYPE_LEVEL_HIGH>, /* dfi_alert_err */
+ <GIC_SPI 356 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err */
+ <GIC_SPI 355 IRQ_TYPE_LEVEL_HIGH>, /* ecc_uncorrected_err */
+ <GIC_SPI 352 IRQ_TYPE_LEVEL_HIGH>, /* sbr_done */
+ <GIC_SPI 354 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err_fault */
+ <GIC_SPI 353 IRQ_TYPE_LEVEL_HIGH>; /* ecc_uncorrected_err_fault */
+ // clocks = <&cmu_ddr_0 0>;
+ // clock-names = "apb_pclk";
+ status = "disabled";
+ };
+
+ ddr1: memory-controller@57000000 {
+ compatible = "baikal,bs1000-edac-mc";
+ reg = <0x0 0x57000000 0x0 0x10000>;
+ interrupts = <GIC_SPI 375 IRQ_TYPE_LEVEL_HIGH>, /* dfi_alert_err */
+ <GIC_SPI 373 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err */
+ <GIC_SPI 372 IRQ_TYPE_LEVEL_HIGH>, /* ecc_uncorrected_err */
+ <GIC_SPI 369 IRQ_TYPE_LEVEL_HIGH>, /* sbr_done */
+ <GIC_SPI 371 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err_fault */
+ <GIC_SPI 370 IRQ_TYPE_LEVEL_HIGH>; /* ecc_uncorrected_err_fault */
+ // clocks = <&cmu_ddr_1 0>;
+ // clock-names = "apb_pclk";
+ status = "disabled";
+ };
+
+ ddr2: memory-controller@5b000000 {
+ compatible = "baikal,bs1000-edac-mc";
+ reg = <0x0 0x5b000000 0x0 0x10000>;
+ interrupts = <GIC_SPI 392 IRQ_TYPE_LEVEL_HIGH>, /* dfi_alert_err */
+ <GIC_SPI 390 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err */
+ <GIC_SPI 389 IRQ_TYPE_LEVEL_HIGH>, /* ecc_uncorrected_err */
+ <GIC_SPI 386 IRQ_TYPE_LEVEL_HIGH>, /* sbr_done */
+ <GIC_SPI 388 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err_fault */
+ <GIC_SPI 387 IRQ_TYPE_LEVEL_HIGH>; /* ecc_uncorrected_err_fault */
+ // clocks = <&cmu_ddr_2 0>;
+ // clock-names = "apb_pclk";
+ status = "disabled";
+ };
+
+ ddr3: memory-controller@63000000 {
+ compatible = "baikal,bs1000-edac-mc";
+ reg = <0x0 0x63000000 0x0 0x10000>;
+ interrupts = <GIC_SPI 409 IRQ_TYPE_LEVEL_HIGH>, /* dfi_alert_err */
+ <GIC_SPI 407 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err */
+ <GIC_SPI 406 IRQ_TYPE_LEVEL_HIGH>, /* ecc_uncorrected_err */
+ <GIC_SPI 403 IRQ_TYPE_LEVEL_HIGH>, /* sbr_done */
+ <GIC_SPI 405 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err_fault */
+ <GIC_SPI 404 IRQ_TYPE_LEVEL_HIGH>; /* ecc_uncorrected_err_fault */
+ // clocks = <&cmu_ddr_3 0>;
+ // clock-names = "apb_pclk";
+ status = "disabled";
+ };
+
+ ddr4: memory-controller@67000000 {
+ compatible = "baikal,bs1000-edac-mc";
+ reg = <0x0 0x67000000 0x0 0x10000>;
+ interrupts = <GIC_SPI 426 IRQ_TYPE_LEVEL_HIGH>, /* dfi_alert_err */
+ <GIC_SPI 424 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err */
+ <GIC_SPI 423 IRQ_TYPE_LEVEL_HIGH>, /* ecc_uncorrected_err */
+ <GIC_SPI 420 IRQ_TYPE_LEVEL_HIGH>, /* sbr_done */
+ <GIC_SPI 422 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err_fault */
+ <GIC_SPI 421 IRQ_TYPE_LEVEL_HIGH>; /* ecc_uncorrected_err_fault */
+ // clocks = <&cmu_ddr_4 0>;
+ // clock-names = "apb_pclk";
+ status = "disabled";
+ };
+
+ ddr5: memory-controller@6b000000 {
+ compatible = "baikal,bs1000-edac-mc";
+ reg = <0x0 0x6b000000 0x0 0x10000>;
+ interrupts = <GIC_SPI 443 IRQ_TYPE_LEVEL_HIGH>, /* dfi_alert_err */
+ <GIC_SPI 441 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err */
+ <GIC_SPI 440 IRQ_TYPE_LEVEL_HIGH>, /* ecc_uncorrected_err */
+ <GIC_SPI 437 IRQ_TYPE_LEVEL_HIGH>, /* sbr_done */
+ <GIC_SPI 439 IRQ_TYPE_LEVEL_HIGH>, /* ecc_corrected_err_fault */
+ <GIC_SPI 438 IRQ_TYPE_LEVEL_HIGH>; /* ecc_uncorrected_err_fault */
+ // clocks = <&cmu_ddr_5 0>;
+ // clock-names = "apb_pclk";
+ status = "disabled";
+ };
+
+ pvt_cluster0: pvt@4030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x4030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_cluster1: pvt@8030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x8030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_cluster2: pvt@c030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0xc030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_cluster3: pvt@10030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x10030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_cluster4: pvt@14030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x14030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_cluster5: pvt@18030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x18030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_cluster6: pvt@1c030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x1c030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_cluster7: pvt@20030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x20030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_cluster8: pvt@24030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x24030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_cluster9: pvt@28030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x28030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_cluster10: pvt@2c030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x2c030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_cluster11: pvt@30030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x30030000 0x0 0x1000>;
+ status = "disabled";
+ };
+ pvt_pcie0: pvt@38030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x38030000 0x0 0x1000>;
+ interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_pcie1: pvt@3c030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x3c030000 0x0 0x1000>;
+ interrupts = <GIC_SPI 206 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_pcie2: pvt@44030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x44030000 0x0 0x1000>;
+ interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_pcie3: pvt@48030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x48030000 0x0 0x1000>;
+ interrupts = <GIC_SPI 275 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_pcie4: pvt@4c030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x4c030000 0x0 0x1000>;
+ interrupts = <GIC_SPI 344 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_ddr0: pvt@50030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x50030000 0x0 0x1000>;
+ interrupts = <GIC_SPI 363 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_ddr1: pvt@54030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x54030000 0x0 0x1000>;
+ interrupts = <GIC_SPI 380 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_ddr2: pvt@58030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x58030000 0x0 0x1000>;
+ interrupts = <GIC_SPI 397 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_ddr3: pvt@60030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x60030000 0x0 0x1000>;
+ interrupts = <GIC_SPI 414 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_ddr4: pvt@64030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x64030000 0x0 0x1000>;
+ interrupts = <GIC_SPI 431 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ pvt_ddr5: pvt@68030000 {
+ compatible = "baikal,bs1000-pvt";
+ reg = <0x0 0x68030000 0x0 0x1000>;
+ interrupts = <GIC_SPI 448 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+
+ mux: mux-controller {
+ compatible = "baikal,bs1000-lsp-mux";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ #mux-control-cells = <1>;
+ status = "disabled";
+
+ qspi2: spi@c30000 {
+ compatible = "snps,dw-apb-ssi";
+ reg = <0x0 0xc30000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spi_clk>;
+ // clocks = <&cmu_sc_1 21>; // "lsp_spi2"
+ };
+
+ espi: spi@c40000 {
+ compatible = "baikal,bs1000-espi";
+ reg = <0x0 0xc40000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spi_clk>;
+ // clocks = <&cmu_sc_1 22>; // "lsp_espi"
+ };
+
+ gpio16: gpio@c60000 {
+ compatible = "snps,dw-apb-gpio";
+ reg = <0x0 0xc60000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&gpio_clk>;
+ // clocks = <&cmu_sc_1 16>; // "lsp_gpio_x16"
+
+ portb: gpio-controller@0 {
+ compatible = "snps,dw-apb-gpio-port";
+ gpio-controller;
+ #gpio-cells = <2>;
+ ngpios = <16>;
+ reg = <0>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <GIC_SPI 51 IRQ_TYPE_LEVEL_HIGH>;
+ };
+ };
+
+ gpio8_1: gpio@c70000 {
+ compatible = "snps,dw-apb-gpio";
+ reg = <0x0 0xc70000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&gpio_clk>;
+ // clocks = <&cmu_sc_1 16>; // "lsp_gpio_x16"
+
+ portc: gpio-controller@0 {
+ compatible = "snps,dw-apb-gpio-port";
+ gpio-controller;
+ #gpio-cells = <2>;
+ ngpios = <8>;
+ reg = <0>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>;
+ };
+ };
+
+ gpio8_2: gpio@c80000 {
+ compatible = "snps,dw-apb-gpio";
+ reg = <0x0 0xc80000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&gpio_clk>;
+ // clocks = <&cmu_sc_1 16>; // "lsp_gpio_x16"
+
+ portd: gpio-controller@0 {
+ compatible = "snps,dw-apb-gpio-port";
+ gpio-controller;
+ #gpio-cells = <2>;
+ ngpios = <8>;
+ reg = <0>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>;
+ };
+ };
+
+ i2c5: i2c@cc0000 {
+ compatible = "snps,designware-i2c";
+ reg = <0x0 0xcc0000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>;
+ i2c-sda-hold-time-ns = <500>;
+ clock-frequency = <400000>;
+ clocks = <&i2c_clk>;
+ // clocks = <&cmu_sc_1 26>; // "lsp_smbus_i2c5"
+ clock-names = "ref";
+ };
+
+ i2c6: i2c@cd0000 {
+ compatible = "snps,designware-i2c";
+ reg = <0x0 0xcd0000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
+ i2c-sda-hold-time-ns = <500>;
+ clock-frequency = <400000>;
+ clocks = <&i2c_clk>;
+ // clocks = <&cmu_sc_1 27>; // "lsp_smbus_i2c6"
+ clock-names = "ref";
+ };
+
+ uart_s: serial@e00000 {
+ compatible = "snps,dw-apb-uart";
+ reg = <0x0 0xe00000 0x0 0x100>;
+ interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;
+ reg-shift = <2>;
+ reg-io-width = <4>;
+ clocks = <&uart_clk>, <&apb_clk>;
+ // clocks = <&cmu_sc_1 17>, <&cmu_sc_1 15>; // "lsp_uart", "lsp_apb"
+ clock-names = "baudclk", "apb_pclk";
+ };
+ };
+
+ mux0: mux0 {
+ compatible = "baikal,bs1000-lsp-mux-channel";
+ mux-controls = <&mux 0>;
+ mux-state0 = <&gpio8_1>;
+ mux-state1 = <&uart_s &i2c5 &i2c6>;
+ status = "disabled";
+ };
+
+ mux1: mux1 {
+ compatible = "baikal,bs1000-lsp-mux-channel";
+ mux-controls = <&mux 1>;
+ mux-state0 = <&gpio8_2>;
+ mux-state1 = <&qspi2>;
+ status = "disabled";
+ };
+
+ mux2: mux2 {
+ compatible = "baikal,bs1000-lsp-mux-channel";
+ mux-controls = <&mux 2>;
+ mux-state0 = <&gpio16>;
+ mux-state1 = <&espi>;
+ status = "disabled";
+ };
+ };
+};
+
+#include "bs1000-coresight.dtsi"
--- /dev/null
+CONFIG_LOCALVERSION="-baikal-arm64"
+CONFIG_DEFAULT_HOSTNAME="baikal"
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_AUDIT=y
+CONFIG_NO_HZ_IDLE=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_BPF_JIT=y
+CONFIG_PREEMPT=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_TASKSTATS=y
+CONFIG_TASK_DELAY_ACCT=y
+CONFIG_TASK_XACCT=y
+CONFIG_TASK_IO_ACCOUNTING=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_HUGETLB=y
+CONFIG_CPUSETS=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_USER_NS=y
+CONFIG_SCHED_AUTOGROUP=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_PROFILING=y
+CONFIG_ARCH_BAIKAL=y
+# CONFIG_ARM64_ERRATUM_826319 is not set
+# CONFIG_ARM64_ERRATUM_827319 is not set
+# CONFIG_ARM64_ERRATUM_824069 is not set
+# CONFIG_ARM64_ERRATUM_819472 is not set
+# CONFIG_ARM64_ERRATUM_832075 is not set
+# CONFIG_ARM64_ERRATUM_834220 is not set
+# CONFIG_ARM64_ERRATUM_845719 is not set
+# CONFIG_ARM64_ERRATUM_843419 is not set
+# CONFIG_ARM64_ERRATUM_1024718 is not set
+# CONFIG_ARM64_ERRATUM_1418040 is not set
+# CONFIG_ARM64_ERRATUM_1165522 is not set
+# CONFIG_ARM64_ERRATUM_1530923 is not set
+# CONFIG_ARM64_ERRATUM_2441007 is not set
+# CONFIG_ARM64_ERRATUM_1286807 is not set
+# CONFIG_ARM64_ERRATUM_1463225 is not set
+# CONFIG_ARM64_ERRATUM_1542419 is not set
+# CONFIG_ARM64_ERRATUM_1508412 is not set
+# CONFIG_ARM64_ERRATUM_2051678 is not set
+# CONFIG_ARM64_ERRATUM_2077057 is not set
+# CONFIG_ARM64_ERRATUM_2658417 is not set
+# CONFIG_ARM64_ERRATUM_2139208 is not set
+# CONFIG_ARM64_ERRATUM_2067961 is not set
+# CONFIG_ARM64_ERRATUM_2253138 is not set
+# CONFIG_ARM64_ERRATUM_2441009 is not set
+# CONFIG_ARM64_ERRATUM_2064142 is not set
+# CONFIG_ARM64_ERRATUM_2038923 is not set
+# CONFIG_ARM64_ERRATUM_1902691 is not set
+# CONFIG_ARM64_ERRATUM_2457168 is not set
+# CONFIG_CAVIUM_ERRATUM_22375 is not set
+# CONFIG_CAVIUM_ERRATUM_23154 is not set
+# CONFIG_CAVIUM_ERRATUM_27456 is not set
+# CONFIG_CAVIUM_ERRATUM_30115 is not set
+# CONFIG_CAVIUM_TX2_ERRATUM_219 is not set
+# CONFIG_FUJITSU_ERRATUM_010001 is not set
+# CONFIG_HISILICON_ERRATUM_161600802 is not set
+# CONFIG_QCOM_FALKOR_ERRATUM_1003 is not set
+# CONFIG_QCOM_FALKOR_ERRATUM_1009 is not set
+# CONFIG_QCOM_QDF2400_ERRATUM_0065 is not set
+# CONFIG_QCOM_FALKOR_ERRATUM_E1041 is not set
+# CONFIG_NVIDIA_CARMEL_CNP_ERRATUM is not set
+# CONFIG_SOCIONEXT_SYNQUACER_PREITS is not set
+CONFIG_ARM64_VA_BITS_48=y
+CONFIG_SCHED_MC=y
+CONFIG_NR_CPUS=48
+CONFIG_HOTPLUG_CPU=y
+CONFIG_HZ_1000=y
+CONFIG_KEXEC_FILE=y
+CONFIG_COMPAT=y
+CONFIG_ARMV8_DEPRECATED=y
+CONFIG_SWP_EMULATION=y
+CONFIG_CP15_BARRIER_EMULATION=y
+CONFIG_SETEND_EMULATION=y
+# CONFIG_SUSPEND is not set
+CONFIG_PM=y
+CONFIG_CPU_IDLE_GOV_LADDER=y
+CONFIG_ARM_PSCI_CPUIDLE=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_BAIKAL_CPUFREQ=y
+CONFIG_ACPI=y
+CONFIG_VIRTUALIZATION=y
+CONFIG_KVM=y
+CONFIG_KPROBES=y
+CONFIG_JUMP_LABEL=y
+CONFIG_STATIC_KEYS_SELFTEST=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_BLK_DEV_THROTTLING=y
+CONFIG_BLK_WBT=y
+CONFIG_BLK_CGROUP_IOLATENCY=y
+CONFIG_BLK_CGROUP_IOCOST=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_IOSCHED_BFQ=y
+CONFIG_BFQ_GROUP_IOSCHED=y
+CONFIG_BINFMT_MISC=m
+CONFIG_ZSWAP=y
+CONFIG_Z3FOLD=m
+CONFIG_SLAB=y
+CONFIG_KSM=y
+CONFIG_TRANSPARENT_HUGEPAGE=y
+CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y
+CONFIG_CMA=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_PACKET_DIAG=y
+CONFIG_UNIX=y
+CONFIG_UNIX_DIAG=y
+CONFIG_XFRM_USER=y
+CONFIG_XFRM_SUB_POLICY=y
+CONFIG_NET_KEY=y
+CONFIG_NET_KEY_MIGRATE=y
+CONFIG_INET=y
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_FIB_TRIE_STATS=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_IP_ROUTE_MULTIPATH=y
+CONFIG_IP_ROUTE_VERBOSE=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+CONFIG_IP_PNP_BOOTP=y
+CONFIG_NET_IPIP=m
+CONFIG_NET_IPGRE_DEMUX=m
+CONFIG_NET_IPGRE=m
+CONFIG_NET_IPGRE_BROADCAST=y
+CONFIG_IP_MROUTE=y
+CONFIG_IP_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IP_PIMSM_V1=y
+CONFIG_IP_PIMSM_V2=y
+CONFIG_NET_IPVTI=m
+CONFIG_NET_FOU=m
+CONFIG_INET_AH=m
+CONFIG_INET_ESP=m
+CONFIG_INET_IPCOMP=m
+CONFIG_INET_DIAG=m
+CONFIG_INET_UDP_DIAG=m
+CONFIG_INET_DIAG_DESTROY=y
+CONFIG_TCP_CONG_ADVANCED=y
+CONFIG_TCP_CONG_VEGAS=m
+CONFIG_TCP_CONG_NV=m
+CONFIG_TCP_CONG_SCALABLE=m
+CONFIG_TCP_CONG_VENO=m
+CONFIG_TCP_MD5SIG=y
+CONFIG_IPV6=m
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_ROUTE_INFO=y
+CONFIG_IPV6_OPTIMISTIC_DAD=y
+CONFIG_INET6_AH=m
+CONFIG_INET6_ESP=m
+CONFIG_INET6_IPCOMP=m
+CONFIG_IPV6_MIP6=m
+CONFIG_IPV6_ILA=m
+CONFIG_IPV6_VTI=m
+CONFIG_IPV6_SIT_6RD=y
+CONFIG_IPV6_GRE=m
+CONFIG_IPV6_MULTIPLE_TABLES=y
+CONFIG_IPV6_MROUTE=y
+CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IPV6_PIMSM_V2=y
+CONFIG_NETWORK_SECMARK=y
+CONFIG_NETWORK_PHY_TIMESTAMPING=y
+CONFIG_NETFILTER=y
+CONFIG_NF_CONNTRACK=m
+CONFIG_NF_CONNTRACK_SECMARK=y
+CONFIG_NF_CONNTRACK_EVENTS=y
+CONFIG_NF_CONNTRACK_TIMEOUT=y
+CONFIG_NF_CONNTRACK_TIMESTAMP=y
+CONFIG_NF_CONNTRACK_AMANDA=m
+CONFIG_NF_CONNTRACK_FTP=m
+CONFIG_NF_CONNTRACK_H323=m
+CONFIG_NF_CONNTRACK_IRC=m
+CONFIG_NF_CONNTRACK_NETBIOS_NS=m
+CONFIG_NF_CONNTRACK_SNMP=m
+CONFIG_NF_CONNTRACK_PPTP=m
+CONFIG_NF_CONNTRACK_SANE=m
+CONFIG_NF_CONNTRACK_SIP=m
+CONFIG_NF_CONNTRACK_TFTP=m
+CONFIG_NF_CT_NETLINK=m
+CONFIG_NF_CT_NETLINK_TIMEOUT=m
+CONFIG_NF_CT_NETLINK_HELPER=m
+CONFIG_NETFILTER_NETLINK_GLUE_CT=y
+CONFIG_NF_TABLES=m
+CONFIG_NFT_NUMGEN=m
+CONFIG_NFT_CT=m
+CONFIG_NFT_LOG=m
+CONFIG_NFT_LIMIT=m
+CONFIG_NFT_MASQ=m
+CONFIG_NFT_REDIR=m
+CONFIG_NFT_QUEUE=m
+CONFIG_NFT_QUOTA=m
+CONFIG_NFT_REJECT=m
+CONFIG_NFT_COMPAT=m
+CONFIG_NFT_HASH=m
+CONFIG_NETFILTER_XT_SET=m
+CONFIG_NETFILTER_XT_TARGET_AUDIT=m
+CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m
+CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m
+CONFIG_NETFILTER_XT_TARGET_CONNMARK=m
+CONFIG_NETFILTER_XT_TARGET_CONNSECMARK=m
+CONFIG_NETFILTER_XT_TARGET_CT=m
+CONFIG_NETFILTER_XT_TARGET_DSCP=m
+CONFIG_NETFILTER_XT_TARGET_HMARK=m
+CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m
+CONFIG_NETFILTER_XT_TARGET_LOG=m
+CONFIG_NETFILTER_XT_TARGET_MARK=m
+CONFIG_NETFILTER_XT_TARGET_NFLOG=m
+CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m
+CONFIG_NETFILTER_XT_TARGET_TEE=m
+CONFIG_NETFILTER_XT_TARGET_TPROXY=m
+CONFIG_NETFILTER_XT_TARGET_TRACE=m
+CONFIG_NETFILTER_XT_TARGET_SECMARK=m
+CONFIG_NETFILTER_XT_TARGET_TCPMSS=m
+CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m
+CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
+CONFIG_NETFILTER_XT_MATCH_BPF=m
+CONFIG_NETFILTER_XT_MATCH_CGROUP=m
+CONFIG_NETFILTER_XT_MATCH_CLUSTER=m
+CONFIG_NETFILTER_XT_MATCH_COMMENT=m
+CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m
+CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m
+CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_CONNMARK=m
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m
+CONFIG_NETFILTER_XT_MATCH_CPU=m
+CONFIG_NETFILTER_XT_MATCH_DCCP=m
+CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m
+CONFIG_NETFILTER_XT_MATCH_DSCP=m
+CONFIG_NETFILTER_XT_MATCH_ESP=m
+CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_HELPER=m
+CONFIG_NETFILTER_XT_MATCH_IPCOMP=m
+CONFIG_NETFILTER_XT_MATCH_IPRANGE=m
+CONFIG_NETFILTER_XT_MATCH_IPVS=m
+CONFIG_NETFILTER_XT_MATCH_LENGTH=m
+CONFIG_NETFILTER_XT_MATCH_LIMIT=m
+CONFIG_NETFILTER_XT_MATCH_MAC=m
+CONFIG_NETFILTER_XT_MATCH_MARK=m
+CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m
+CONFIG_NETFILTER_XT_MATCH_NFACCT=m
+CONFIG_NETFILTER_XT_MATCH_OSF=m
+CONFIG_NETFILTER_XT_MATCH_OWNER=m
+CONFIG_NETFILTER_XT_MATCH_POLICY=m
+CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m
+CONFIG_NETFILTER_XT_MATCH_QUOTA=m
+CONFIG_NETFILTER_XT_MATCH_RATEEST=m
+CONFIG_NETFILTER_XT_MATCH_REALM=m
+CONFIG_NETFILTER_XT_MATCH_RECENT=m
+CONFIG_NETFILTER_XT_MATCH_SOCKET=m
+CONFIG_NETFILTER_XT_MATCH_STATE=m
+CONFIG_NETFILTER_XT_MATCH_STATISTIC=m
+CONFIG_NETFILTER_XT_MATCH_STRING=m
+CONFIG_NETFILTER_XT_MATCH_TCPMSS=m
+CONFIG_NETFILTER_XT_MATCH_TIME=m
+CONFIG_NETFILTER_XT_MATCH_U32=m
+CONFIG_IP_SET=m
+CONFIG_IP_SET_BITMAP_IP=m
+CONFIG_IP_SET_BITMAP_IPMAC=m
+CONFIG_IP_SET_BITMAP_PORT=m
+CONFIG_IP_SET_HASH_IP=m
+CONFIG_IP_SET_HASH_IPMARK=m
+CONFIG_IP_SET_HASH_IPPORT=m
+CONFIG_IP_SET_HASH_IPPORTIP=m
+CONFIG_IP_SET_HASH_IPPORTNET=m
+CONFIG_IP_SET_HASH_MAC=m
+CONFIG_IP_SET_HASH_NETPORTNET=m
+CONFIG_IP_SET_HASH_NET=m
+CONFIG_IP_SET_HASH_NETNET=m
+CONFIG_IP_SET_HASH_NETPORT=m
+CONFIG_IP_SET_HASH_NETIFACE=m
+CONFIG_IP_SET_LIST_SET=m
+CONFIG_IP_VS=m
+CONFIG_IP_VS_IPV6=y
+CONFIG_IP_VS_PROTO_TCP=y
+CONFIG_IP_VS_PROTO_UDP=y
+CONFIG_IP_VS_PROTO_ESP=y
+CONFIG_IP_VS_PROTO_AH=y
+CONFIG_IP_VS_PROTO_SCTP=y
+CONFIG_IP_VS_RR=m
+CONFIG_IP_VS_WRR=m
+CONFIG_IP_VS_LC=m
+CONFIG_IP_VS_WLC=m
+CONFIG_IP_VS_FO=m
+CONFIG_IP_VS_OVF=m
+CONFIG_IP_VS_LBLC=m
+CONFIG_IP_VS_LBLCR=m
+CONFIG_IP_VS_DH=m
+CONFIG_IP_VS_SH=m
+CONFIG_IP_VS_SED=m
+CONFIG_IP_VS_NQ=m
+CONFIG_IP_VS_FTP=m
+CONFIG_IP_VS_PE_SIP=m
+CONFIG_NF_LOG_ARP=m
+CONFIG_IP_NF_IPTABLES=m
+CONFIG_IP_NF_MATCH_AH=m
+CONFIG_IP_NF_MATCH_ECN=m
+CONFIG_IP_NF_MATCH_RPFILTER=m
+CONFIG_IP_NF_MATCH_TTL=m
+CONFIG_IP_NF_FILTER=m
+CONFIG_IP_NF_TARGET_REJECT=m
+CONFIG_IP_NF_TARGET_SYNPROXY=m
+CONFIG_IP_NF_NAT=m
+CONFIG_IP_NF_TARGET_MASQUERADE=m
+CONFIG_IP_NF_TARGET_NETMAP=m
+CONFIG_IP_NF_TARGET_REDIRECT=m
+CONFIG_IP_NF_MANGLE=m
+CONFIG_IP_NF_TARGET_CLUSTERIP=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPTABLES=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NF_TABLES_BRIDGE=m
+CONFIG_NFT_BRIDGE_META=m
+CONFIG_BRIDGE_NF_EBTABLES=m
+CONFIG_BRIDGE_EBT_BROUTE=m
+CONFIG_BRIDGE_EBT_T_FILTER=m
+CONFIG_BRIDGE_EBT_T_NAT=m
+CONFIG_BRIDGE_EBT_802_3=m
+CONFIG_BRIDGE_EBT_AMONG=m
+CONFIG_BRIDGE_EBT_ARP=m
+CONFIG_BRIDGE_EBT_IP=m
+CONFIG_BRIDGE_EBT_IP6=m
+CONFIG_BRIDGE_EBT_LIMIT=m
+CONFIG_BRIDGE_EBT_MARK=m
+CONFIG_BRIDGE_EBT_PKTTYPE=m
+CONFIG_BRIDGE_EBT_STP=m
+CONFIG_BRIDGE_EBT_VLAN=m
+CONFIG_BRIDGE_EBT_ARPREPLY=m
+CONFIG_BRIDGE_EBT_DNAT=m
+CONFIG_BRIDGE_EBT_MARK_T=m
+CONFIG_BRIDGE_EBT_REDIRECT=m
+CONFIG_BRIDGE_EBT_SNAT=m
+CONFIG_BRIDGE_EBT_LOG=m
+CONFIG_BRIDGE_EBT_NFLOG=m
+CONFIG_IP_SCTP=m
+CONFIG_SCTP_COOKIE_HMAC_SHA1=y
+CONFIG_RDS=m
+CONFIG_RDS_TCP=m
+CONFIG_TIPC=m
+CONFIG_ATM=m
+CONFIG_ATM_CLIP=m
+CONFIG_ATM_LANE=m
+CONFIG_ATM_MPOA=m
+CONFIG_ATM_BR2684=m
+CONFIG_ATM_BR2684_IPFILTER=y
+CONFIG_L2TP=m
+CONFIG_L2TP_DEBUGFS=m
+CONFIG_L2TP_V3=y
+CONFIG_L2TP_IP=m
+CONFIG_L2TP_ETH=m
+CONFIG_BRIDGE=m
+CONFIG_BRIDGE_VLAN_FILTERING=y
+CONFIG_NET_DSA=m
+CONFIG_VLAN_8021Q=m
+CONFIG_VLAN_8021Q_GVRP=y
+CONFIG_VLAN_8021Q_MVRP=y
+CONFIG_IEEE802154=m
+CONFIG_IEEE802154_NL802154_EXPERIMENTAL=y
+CONFIG_MAC802154=m
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_CBQ=m
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_PRIO=m
+CONFIG_NET_SCH_MULTIQ=m
+CONFIG_NET_SCH_RED=m
+CONFIG_NET_SCH_SFB=m
+CONFIG_NET_SCH_SFQ=m
+CONFIG_NET_SCH_TEQL=m
+CONFIG_NET_SCH_TBF=m
+CONFIG_NET_SCH_GRED=m
+CONFIG_NET_SCH_DSMARK=m
+CONFIG_NET_SCH_NETEM=m
+CONFIG_NET_SCH_DRR=m
+CONFIG_NET_SCH_MQPRIO=m
+CONFIG_NET_SCH_CHOKE=m
+CONFIG_NET_SCH_QFQ=m
+CONFIG_NET_SCH_CODEL=m
+CONFIG_NET_SCH_FQ_CODEL=m
+CONFIG_NET_SCH_FQ=m
+CONFIG_NET_SCH_HHF=m
+CONFIG_NET_SCH_PIE=m
+CONFIG_NET_SCH_PLUG=m
+CONFIG_NET_CLS_BASIC=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_PERF=y
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_RSVP=m
+CONFIG_NET_CLS_RSVP6=m
+CONFIG_NET_CLS_FLOW=m
+CONFIG_NET_CLS_CGROUP=m
+CONFIG_NET_CLS_BPF=m
+CONFIG_NET_CLS_FLOWER=m
+CONFIG_NET_CLS_MATCHALL=m
+CONFIG_NET_EMATCH=y
+CONFIG_NET_EMATCH_CMP=m
+CONFIG_NET_EMATCH_NBYTE=m
+CONFIG_NET_EMATCH_U32=m
+CONFIG_NET_EMATCH_META=m
+CONFIG_NET_EMATCH_TEXT=m
+CONFIG_NET_EMATCH_IPSET=m
+CONFIG_NET_CLS_ACT=y
+CONFIG_NET_ACT_POLICE=m
+CONFIG_NET_ACT_GACT=m
+CONFIG_GACT_PROB=y
+CONFIG_NET_ACT_MIRRED=m
+CONFIG_NET_ACT_IPT=m
+CONFIG_NET_ACT_NAT=m
+CONFIG_NET_ACT_PEDIT=m
+CONFIG_NET_ACT_SKBEDIT=m
+CONFIG_NET_ACT_CSUM=m
+CONFIG_NET_ACT_VLAN=m
+CONFIG_NET_ACT_BPF=m
+CONFIG_NET_ACT_CONNMARK=m
+CONFIG_NET_ACT_SKBMOD=m
+CONFIG_NET_ACT_IFE=m
+CONFIG_NET_ACT_TUNNEL_KEY=m
+CONFIG_NET_IFE_SKBMARK=m
+CONFIG_NET_IFE_SKBPRIO=m
+CONFIG_NET_IFE_SKBTCINDEX=m
+CONFIG_DCB=y
+CONFIG_BATMAN_ADV=m
+# CONFIG_BATMAN_ADV_BATMAN_V is not set
+CONFIG_BATMAN_ADV_NC=y
+CONFIG_OPENVSWITCH=m
+CONFIG_VSOCKETS=m
+CONFIG_VIRTIO_VSOCKETS=m
+CONFIG_NETLINK_DIAG=m
+CONFIG_HSR=m
+CONFIG_CGROUP_NET_PRIO=y
+CONFIG_NET_PKTGEN=y
+CONFIG_BT=m
+CONFIG_BT_RFCOMM=m
+CONFIG_BT_BNEP=m
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+CONFIG_BT_HIDP=m
+CONFIG_BT_HCIUART=m
+CONFIG_BT_HCIUART_H4=y
+CONFIG_CFG80211=m
+CONFIG_CFG80211_WEXT=y
+CONFIG_MAC80211=m
+CONFIG_MAC80211_MESH=y
+CONFIG_RFKILL=y
+CONFIG_RFKILL_GPIO=m
+CONFIG_NET_9P=m
+CONFIG_CEPH_LIB_USE_DNS_RESOLVER=y
+CONFIG_PCI=y
+CONFIG_PCIEPORTBUS=y
+CONFIG_HOTPLUG_PCI_PCIE=y
+CONFIG_PCIE_PTM=y
+CONFIG_PCI_REALLOC_ENABLE_AUTO=y
+CONFIG_PCI_STUB=m
+CONFIG_PCI_IOV=y
+CONFIG_PCI_PRI=y
+CONFIG_PCI_PASID=y
+CONFIG_HOTPLUG_PCI=y
+CONFIG_PCIE_BAIKAL_HOST=y
+CONFIG_PCIE_BAIKAL_EP=y
+CONFIG_PCI_ENDPOINT=y
+CONFIG_PCI_ENDPOINT_CONFIGFS=y
+CONFIG_PCI_EPF_TEST=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_CONNECTOR=y
+CONFIG_EFI_GENERIC_STUB_INITRD_CMDLINE_LOADER=y
+CONFIG_EFI_BOOTLOADER_CONTROL=y
+CONFIG_MTD=y
+CONFIG_MTD_CMDLINE_PARTS=y
+CONFIG_MTD_BAIKAL_PARTS=y
+CONFIG_MTD_BLOCK=y
+CONFIG_MTD_JEDECPROBE=y
+CONFIG_MTD_SPI_NOR=y
+CONFIG_ZRAM=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_DRBD=y
+CONFIG_BLK_DEV_NBD=y
+CONFIG_BLK_DEV_RAM=y
+CONFIG_BLK_DEV_RAM_SIZE=16384
+CONFIG_ATA_OVER_ETH=m
+CONFIG_VIRTIO_BLK=m
+CONFIG_BLK_DEV_RBD=y
+CONFIG_BLK_DEV_NVME=y
+CONFIG_TP_BMC=y
+CONFIG_EEPROM_AT24=y
+CONFIG_EEPROM_AT25=m
+CONFIG_RAID_ATTRS=m
+CONFIG_BLK_DEV_SD=y
+CONFIG_BLK_DEV_SR=m
+CONFIG_CHR_DEV_SG=y
+CONFIG_CHR_DEV_SCH=y
+CONFIG_SCSI_SCAN_ASYNC=y
+CONFIG_SCSI_FC_ATTRS=m
+CONFIG_SCSI_SAS_LIBSAS=m
+CONFIG_SCSI_SRP_ATTRS=m
+CONFIG_ISCSI_TCP=m
+CONFIG_SCSI_HPSA=m
+CONFIG_SCSI_QLA_FC=m
+CONFIG_SCSI_VIRTIO=m
+CONFIG_ATA=y
+CONFIG_SATA_AHCI=y
+CONFIG_SATA_AHCI_PLATFORM=y
+CONFIG_AHCI_DWC=y
+CONFIG_SATA_SIL24=m
+# CONFIG_ATA_SFF is not set
+CONFIG_MD=y
+CONFIG_MD_LINEAR=m
+CONFIG_MD_MULTIPATH=m
+CONFIG_MD_FAULTY=m
+CONFIG_BCACHE=m
+CONFIG_BLK_DEV_DM=m
+CONFIG_DM_CRYPT=m
+CONFIG_DM_SNAPSHOT=m
+CONFIG_DM_THIN_PROVISIONING=m
+CONFIG_DM_CACHE=m
+CONFIG_DM_MIRROR=m
+CONFIG_DM_RAID=m
+CONFIG_DM_ZERO=m
+CONFIG_DM_MULTIPATH=m
+CONFIG_DM_MULTIPATH_QL=m
+CONFIG_DM_MULTIPATH_ST=m
+CONFIG_DM_UEVENT=y
+CONFIG_DM_SWITCH=m
+CONFIG_TARGET_CORE=m
+CONFIG_TCM_IBLOCK=m
+CONFIG_TCM_FILEIO=m
+CONFIG_TCM_PSCSI=m
+CONFIG_LOOPBACK_TARGET=m
+CONFIG_ISCSI_TARGET=m
+CONFIG_FIREWIRE=m
+CONFIG_FIREWIRE_OHCI=m
+CONFIG_FIREWIRE_SBP2=m
+CONFIG_FIREWIRE_NET=m
+CONFIG_NETDEVICES=y
+CONFIG_BONDING=m
+CONFIG_DUMMY=m
+CONFIG_NET_FC=y
+CONFIG_NET_TEAM=m
+CONFIG_MACVLAN=m
+CONFIG_MACVTAP=m
+CONFIG_IPVLAN=m
+CONFIG_VXLAN=m
+CONFIG_GENEVE=m
+CONFIG_GTP=m
+CONFIG_MACSEC=m
+CONFIG_NETCONSOLE=m
+CONFIG_NETCONSOLE_DYNAMIC=y
+CONFIG_TUN=m
+CONFIG_VETH=m
+CONFIG_NLMON=m
+CONFIG_NET_VRF=m
+CONFIG_ATM_TCP=m
+CONFIG_AMD_XGBE=y
+CONFIG_E1000E=m
+CONFIG_IXGB=m
+CONFIG_IXGBE=m
+CONFIG_IXGBEVF=m
+CONFIG_STMMAC_ETH=y
+CONFIG_DWMAC_BAIKAL=y
+CONFIG_MARVELL_PHY=y
+CONFIG_MICREL_PHY=y
+CONFIG_REALTEK_PHY=y
+CONFIG_MDIO_BITBANG=y
+CONFIG_MDIO_GPIO=m
+CONFIG_USB_USBNET=y
+# CONFIG_USB_NET_ZAURUS is not set
+CONFIG_ATH10K=m
+CONFIG_ATH10K_PCI=m
+CONFIG_RTL8192CE=m
+CONFIG_RTL8192SE=m
+CONFIG_RTL8192DE=m
+CONFIG_RTL8723AE=m
+CONFIG_RTL8723BE=m
+CONFIG_RTL8188EE=m
+CONFIG_RTL8192EE=m
+CONFIG_RTL8821AE=m
+# CONFIG_IEEE802154_DRIVERS is not set
+CONFIG_INPUT_MOUSEDEV=y
+CONFIG_INPUT_MOUSEDEV_PSAUX=y
+CONFIG_INPUT_EVDEV=y
+# CONFIG_MOUSE_PS2 is not set
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_8250_CONSOLE=y
+CONFIG_SERIAL_8250_NR_UARTS=2
+CONFIG_SERIAL_8250_RUNTIME_UARTS=2
+CONFIG_SERIAL_8250_EXTENDED=y
+CONFIG_SERIAL_8250_DW=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_BAIKAL_SMBUS=y
+CONFIG_I2C_DESIGNWARE_SLAVE=y
+CONFIG_I2C_DESIGNWARE_PLATFORM=y
+CONFIG_SPI=y
+CONFIG_SPI_BAIKAL_ESPI=y
+CONFIG_SPI_DESIGNWARE=y
+CONFIG_SPI_DW_DMA=y
+CONFIG_SPI_DW_MMIO=y
+CONFIG_SPI_SPIDEV=y
+CONFIG_GPIO_GENERIC_PLATFORM=y
+CONFIG_GPIO_PCF857X=y
+CONFIG_SENSORS_BT1_PVT=y
+CONFIG_WATCHDOG=y
+CONFIG_WATCHDOG_NOWAYOUT=y
+# CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED is not set
+CONFIG_GPIO_WATCHDOG=m
+CONFIG_DW_WATCHDOG=y
+CONFIG_MEDIA_SUPPORT=y
+CONFIG_MEDIA_CAMERA_SUPPORT=y
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_PWC=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_DRM=y
+# CONFIG_DRM_I2C_CH7006 is not set
+# CONFIG_DRM_I2C_SIL164 is not set
+CONFIG_DRM_BAIKAL_VDU=y
+CONFIG_DRM_RADEON=m
+CONFIG_DRM_AMDGPU=m
+CONFIG_DRM_NOUVEAU=m
+CONFIG_DRM_VGEM=m
+CONFIG_DRM_QXL=m
+CONFIG_DRM_VIRTIO_GPU=m
+CONFIG_DRM_PANEL_BAIKAL_LVDS=y
+CONFIG_DRM_PANEL_LVDS=y
+CONFIG_DRM_BAIKAL_HDMI=y
+CONFIG_DRM_BAIKAL_HDMI_AHB_AUDIO=y
+CONFIG_DRM_CIRRUS_QEMU=m
+CONFIG_DRM_PANFROST=m
+CONFIG_FB=y
+CONFIG_LCD_CLASS_DEVICE=y
+CONFIG_BACKLIGHT_CLASS_DEVICE=y
+CONFIG_SOUND=y
+CONFIG_SND=y
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_MIXER_OSS=m
+CONFIG_SND_PCM_OSS=m
+CONFIG_SND_HRTIMER=m
+# CONFIG_SND_SUPPORT_OLD_API is not set
+CONFIG_SND_SEQUENCER=m
+CONFIG_SND_SEQUENCER_OSS=m
+CONFIG_SND_DUMMY=m
+CONFIG_SND_HDA_BAIKAL=y
+CONFIG_SND_HDA_CODEC_REALTEK=y
+# CONFIG_SND_SPI is not set
+CONFIG_SND_USB_AUDIO=y
+# CONFIG_SND_FIREWIRE is not set
+CONFIG_SND_SOC=y
+CONFIG_SND_BAIKAL_I2S=y
+CONFIG_SND_BAIKAL_PIO_PCM=y
+CONFIG_SND_SOC_AC97_CODEC=m
+CONFIG_SND_SOC_SPDIF=m
+CONFIG_SND_SOC_TLV320AIC3X_I2C=y
+CONFIG_SND_SOC_NAU8822=y
+CONFIG_SND_SIMPLE_CARD=y
+CONFIG_HID_BATTERY_STRENGTH=y
+CONFIG_HIDRAW=y
+CONFIG_UHID=m
+CONFIG_HID_LOGITECH=m
+CONFIG_HID_LOGITECH_HIDPP=m
+CONFIG_USB_ULPI_BUS=y
+CONFIG_USB=y
+CONFIG_USB_MON=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_EHCI_ROOT_HUB_TT=y
+CONFIG_USB_EHCI_HCD_PLATFORM=y
+CONFIG_USB_OHCI_HCD=y
+CONFIG_USB_OHCI_HCD_PLATFORM=y
+CONFIG_USB_HCD_TEST_MODE=y
+CONFIG_USB_ACM=m
+CONFIG_USB_STORAGE=y
+CONFIG_USB_UAS=y
+CONFIG_USBIP_CORE=m
+CONFIG_USBIP_VHCI_HCD=m
+CONFIG_USBIP_HOST=m
+CONFIG_USB_DWC3=y
+CONFIG_USB_DWC3_ULPI=y
+# CONFIG_USB_DWC3_HAPS is not set
+CONFIG_USB_SERIAL=m
+CONFIG_USB_SERIAL_CH341=m
+CONFIG_USB_SERIAL_CP210X=m
+CONFIG_USB_SERIAL_FTDI_SIO=m
+CONFIG_USB_ULPI=y
+CONFIG_MMC=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_OF_DWCMSHC=y
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_USER=y
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_ABEOZ9=y
+CONFIG_RTC_DRV_PCF2127=y
+CONFIG_DMADEVICES=y
+CONFIG_PL330_DMA=m
+CONFIG_DW_DMAC=y
+CONFIG_VIRT_DRIVERS=y
+CONFIG_VIRTIO_PCI=m
+CONFIG_VIRTIO_BALLOON=m
+CONFIG_VIRTIO_INPUT=m
+CONFIG_VIRTIO_MMIO=m
+CONFIG_VHOST_NET=m
+CONFIG_VHOST_SCSI=m
+CONFIG_VHOST_VSOCK=m
+CONFIG_VHOST_CROSS_ENDIAN_LEGACY=y
+CONFIG_BAIKAL_DW_APB_TIMER=y
+# CONFIG_FSL_ERRATUM_A008585 is not set
+# CONFIG_HISILICON_ERRATUM_161010101 is not set
+# CONFIG_ARM64_ERRATUM_858921 is not set
+CONFIG_MAILBOX=y
+CONFIG_ARM_MHU=m
+CONFIG_ARM_SMMU=y
+CONFIG_ARM_SMMU_V3=y
+CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y
+CONFIG_USB_PHY_BAIKAL=y
+CONFIG_ARM_CCN=y
+CONFIG_MUX_BAIKAL=y
+CONFIG_EXT4_FS=y
+CONFIG_EXT4_FS_POSIX_ACL=y
+CONFIG_EXT4_FS_SECURITY=y
+CONFIG_XFS_FS=y
+CONFIG_XFS_QUOTA=y
+CONFIG_XFS_POSIX_ACL=y
+CONFIG_XFS_RT=y
+CONFIG_BTRFS_FS=m
+CONFIG_BTRFS_FS_POSIX_ACL=y
+CONFIG_FS_ENCRYPTION=y
+CONFIG_FANOTIFY=y
+CONFIG_AUTOFS4_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=y
+CONFIG_ISO9660_FS=y
+CONFIG_UDF_FS=m
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_IOCHARSET="ascii"
+CONFIG_FAT_DEFAULT_UTF8=y
+CONFIG_NTFS_FS=y
+CONFIG_PROC_KCORE=y
+CONFIG_PROC_CHILDREN=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_HUGETLBFS=y
+CONFIG_EFIVAR_FS=y
+CONFIG_SQUASHFS=y
+CONFIG_SQUASHFS_FILE_DIRECT=y
+CONFIG_SQUASHFS_XATTR=y
+CONFIG_SQUASHFS_LZ4=y
+CONFIG_SQUASHFS_LZO=y
+CONFIG_SQUASHFS_XZ=y
+CONFIG_SQUASHFS_ZSTD=y
+CONFIG_PSTORE=y
+# CONFIG_PSTORE_DEFLATE_COMPRESS is not set
+CONFIG_NFS_FS=y
+CONFIG_NFS_V3_ACL=y
+CONFIG_NFS_V4=y
+CONFIG_NFS_SWAP=y
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_NFS_V4_1_IMPLEMENTATION_ID_DOMAIN="y"
+CONFIG_NFS_V4_1_MIGRATION=y
+CONFIG_NFSD=y
+CONFIG_NFSD_V3_ACL=y
+CONFIG_NFSD_V4=y
+CONFIG_NFSD_BLOCKLAYOUT=y
+CONFIG_NFSD_SCSILAYOUT=y
+CONFIG_SUNRPC_DEBUG=y
+CONFIG_9P_FS=m
+CONFIG_9P_FS_POSIX_ACL=y
+CONFIG_9P_FS_SECURITY=y
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_CODEPAGE_852=y
+CONFIG_NLS_CODEPAGE_866=y
+CONFIG_NLS_CODEPAGE_1251=y
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_NLS_ISO8859_5=y
+CONFIG_NLS_KOI8_R=y
+CONFIG_NLS_UTF8=y
+CONFIG_PERSISTENT_KEYRINGS=y
+CONFIG_ENCRYPTED_KEYS=y
+CONFIG_CRYPTO_CRYPTD=y
+CONFIG_CRYPTO_ARC4=y
+CONFIG_CRYPTO_MD4=y
+CONFIG_CRYPTO_CRC32=y
+CONFIG_CRYPTO_DEFLATE=y
+CONFIG_CRYPTO_LZ4=y
+CONFIG_CRYPTO_LZ4HC=y
+CONFIG_CRYPTO_ANSI_CPRNG=y
+CONFIG_CRYPTO_USER_API_HASH=y
+CONFIG_CRYPTO_USER_API_SKCIPHER=y
+CONFIG_CRYPTO_USER_API_AEAD=y
+CONFIG_DMA_CMA=y
+CONFIG_CMA_SIZE_MBYTES=256
+CONFIG_IRQ_POLL=y
+CONFIG_PRINTK_TIME=y
+CONFIG_DYNAMIC_DEBUG=y
+CONFIG_STRIP_ASM_SYMS=y
+CONFIG_PAGE_EXTENSION=y
+CONFIG_PAGE_POISONING=y
+CONFIG_IO_STRICT_DEVMEM=y
+CONFIG_CORESIGHT=m
+CONFIG_CORESIGHT_LINK_AND_SINK_TMC=m
+CONFIG_CORESIGHT_SINK_TPIU=m
+CONFIG_CORESIGHT_SOURCE_ETM4X=m
+CONFIG_ETM4X_IMPDEF_FEATURE=y
+CONFIG_CORESIGHT_STM=m
+CONFIG_CORESIGHT_CPU_DEBUG=m
+CONFIG_CORESIGHT_CTI=m
+CONFIG_CORESIGHT_TRBE=m
+CONFIG_TEST_USER_COPY=m
+CONFIG_TEST_BPF=m
+CONFIG_TEST_FIRMWARE=m
+CONFIG_TEST_STATIC_KEYS=m
ALTRA_ECAM_QUIRK(1, 13),
ALTRA_ECAM_QUIRK(1, 14),
ALTRA_ECAM_QUIRK(1, 15),
+
+#define BAIKAL_ECAM(table_id, rev, seg, ops) \
+ { "BAIKAL", table_id, rev, seg, MCFG_BUS_ANY, ops }
+
+ /* Baikal-M Synopsys DesignWare PCIe */
+ BAIKAL_ECAM("BKLEMCFG", 1, 0, &baikal_m_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 1, 1, &baikal_m_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 1, 2, &baikal_m_pcie_ecam_ops),
+
+ /* Baikal-S Synopsys DesignWare PCIe */
+ BAIKAL_ECAM("BKLEMCFG", 2, 0, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 1, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 2, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 3, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 4, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 5, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 6, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 7, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 8, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 9, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 10, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 11, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 12, &baikal_s_pcie_ecam_ops),
+ BAIKAL_ECAM("BKLEMCFG", 2, 13, &baikal_s_pcie_ecam_ops),
#endif /* ARM64 */
#ifdef CONFIG_LOONGARCH
.init = ahci_bt1_init,
};
+static struct ahci_dwc_plat_data ahci_bm1000_plat = {
+ .pflags = AHCI_PLATFORM_GET_RESETS | AHCI_PLATFORM_RST_TRIGGER,
+};
+
static const struct of_device_id ahci_dwc_of_match[] = {
{ .compatible = "snps,dwc-ahci", &ahci_dwc_plat },
{ .compatible = "snps,spear-ahci", &ahci_dwc_plat },
{ .compatible = "baikal,bt1-ahci", &ahci_bt1_plat },
+ { .compatible = "baikal,bm1000-ahci", &ahci_bm1000_plat },
{},
};
MODULE_DEVICE_TABLE(of, ahci_dwc_of_match);
obj-$(CONFIG_COMMON_CLK_AT91) += at91/
obj-$(CONFIG_ARCH_ARTPEC) += axis/
obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/
+obj-$(CONFIG_ARCH_BAIKAL) += baikal/
obj-$(CONFIG_CLK_BAIKAL_T1) += baikal-t1/
obj-y += bcm/
obj-$(CONFIG_ARCH_BERLIN) += berlin/
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+obj-y += clk-bm1000.o
+obj-y += clk-bs1000.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2015-2023 Baikal Electronics, JSC
+ * Author: Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
+ */
+
+#include <linux/acpi.h>
+#include <linux/arm-smccc.h>
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BAIKAL_SMC_CMU_CMD 0x82000000
+#define CMU_PLL_SET_RATE 0
+#define CMU_PLL_GET_RATE 1
+#define CMU_PLL_ENABLE 2
+#define CMU_PLL_DISABLE 3
+#define CMU_PLL_ROUND_RATE 4
+#define CMU_PLL_IS_ENABLED 5
+#define CMU_CLK_CH_SET_RATE 6
+#define CMU_CLK_CH_GET_RATE 7
+#define CMU_CLK_CH_ENABLE 8
+#define CMU_CLK_CH_DISABLE 9
+#define CMU_CLK_CH_ROUND_RATE 10
+#define CMU_CLK_CH_IS_ENABLED 11
+
+struct baikal_clk_cmu {
+ struct clk_hw hw;
+ u32 base;
+ unsigned int parent;
+ const char *name;
+ u32 is_clk_ch;
+};
+
+#define to_baikal_cmu(_hw) container_of(_hw, struct baikal_clk_cmu, hw)
+
+static int baikal_clk_enable(struct clk_hw *hw)
+{
+ struct arm_smccc_res res;
+ struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+ u32 cmd;
+
+ if (pclk->is_clk_ch)
+ cmd = CMU_CLK_CH_ENABLE;
+ else
+ cmd = CMU_PLL_ENABLE;
+
+ /* If clock valid */
+ arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, 0,
+ pclk->parent, 0, 0, 0, &res);
+
+ pr_debug("%s(%s, %s@0x%x): %s\n",
+ __func__,
+ pclk->name,
+ pclk->is_clk_ch ? "clkch" : "pll",
+ pclk->base,
+ res.a0 ? "error" : "ok");
+
+ return res.a0;
+}
+
+static void baikal_clk_disable(struct clk_hw *hw)
+{
+ struct arm_smccc_res res;
+ struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+ u32 cmd;
+
+ if (pclk->is_clk_ch)
+ cmd = CMU_CLK_CH_DISABLE;
+ else
+ cmd = CMU_PLL_DISABLE;
+
+ /* If clock valid */
+ arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, 0,
+ pclk->parent, 0, 0, 0, &res);
+
+ pr_debug("%s(%s, %s@0x%x): %s\n",
+ __func__,
+ pclk->name,
+ pclk->is_clk_ch ? "clkch" : "pll",
+ pclk->base,
+ res.a0 ? "error" : "ok");
+}
+
+static int baikal_clk_is_enabled(struct clk_hw *hw)
+{
+ struct arm_smccc_res res;
+ struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+ u32 cmd;
+
+ if (pclk->is_clk_ch)
+ cmd = CMU_CLK_CH_IS_ENABLED;
+ else
+ cmd = CMU_PLL_IS_ENABLED;
+
+ /* If clock valid */
+ arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, 0,
+ pclk->parent, 0, 0, 0, &res);
+
+ pr_debug("%s(%s, %s@0x%x): %s\n",
+ __func__,
+ pclk->name,
+ pclk->is_clk_ch ? "clkch" : "pll",
+ pclk->base,
+ res.a0 ? "true" : "false");
+
+ return res.a0;
+}
+
+static unsigned long baikal_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct arm_smccc_res res;
+ struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+ u32 cmd;
+ unsigned long parent;
+
+ if (pclk->is_clk_ch) {
+ cmd = CMU_CLK_CH_GET_RATE;
+ parent = pclk->parent;
+ } else {
+ cmd = CMU_PLL_GET_RATE;
+ parent = parent_rate;
+ }
+
+ /* If clock valid */
+ arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, 0,
+ parent, 0, 0, 0, &res);
+
+ pr_debug("%s(%s, %s@0x%x): %ld Hz\n",
+ __func__,
+ pclk->name,
+ pclk->is_clk_ch ? "clkch" : "pll",
+ pclk->base,
+ res.a0);
+
+ /* Return actual freq */
+ return res.a0;
+}
+
+static int baikal_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct arm_smccc_res res;
+ struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+ u32 cmd;
+ unsigned long parent;
+
+ if (pclk->is_clk_ch) {
+ cmd = CMU_CLK_CH_SET_RATE;
+ parent = pclk->parent;
+ } else {
+ cmd = CMU_PLL_SET_RATE;
+ parent = parent_rate;
+ }
+
+ arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, rate,
+ parent, 0, 0, 0, &res);
+
+ pr_debug("%s(%s, %s@0x%x, %ld Hz): %s\n",
+ __func__,
+ pclk->name,
+ pclk->is_clk_ch ? "clkch" : "pll",
+ pclk->base,
+ rate,
+ res.a0 ? "error" : "ok");
+
+ return res.a0;
+}
+
+static long baikal_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct arm_smccc_res res;
+ struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+ u32 cmd;
+ unsigned long parent;
+
+ if (pclk->is_clk_ch) {
+ cmd = CMU_CLK_CH_ROUND_RATE;
+ parent = pclk->parent;
+ } else {
+ cmd = CMU_PLL_ROUND_RATE;
+ parent = *prate;
+ }
+
+ /* If clock valid */
+ arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, rate,
+ parent, 0, 0, 0, &res);
+
+ pr_debug("%s(%s, %s@0x%x): %ld Hz\n",
+ __func__,
+ pclk->name,
+ pclk->is_clk_ch ? "clkch" : "pll",
+ pclk->base,
+ res.a0);
+
+ /* Return actual freq */
+ return res.a0;
+}
+
+static const struct clk_ops baikal_clk_ops = {
+ .enable = baikal_clk_enable,
+ .disable = baikal_clk_disable,
+ .is_enabled = baikal_clk_is_enabled,
+ .recalc_rate = baikal_clk_recalc_rate,
+ .set_rate = baikal_clk_set_rate,
+ .round_rate = baikal_clk_round_rate,
+};
+
+static int baikal_clk_probe(struct platform_device *pdev)
+{
+ struct clk_init_data init;
+ struct clk_init_data *init_ch;
+ struct baikal_clk_cmu *cmu;
+ struct baikal_clk_cmu **cmu_ch;
+ struct device_node *node = pdev->dev.of_node;
+
+ struct clk *clk;
+ struct clk_onecell_data *clk_ch;
+
+ int i = 0, number, rc;
+ u32 index;
+ u64 base;
+ struct property *prop;
+ const __be32 *p;
+ const char *clk_ch_name;
+ const char *parent_name;
+
+ cmu = kmalloc(sizeof(*cmu), GFP_KERNEL);
+ if (!cmu)
+ return -ENOMEM;
+
+ of_property_read_string(node, "clock-output-names", &cmu->name);
+ of_property_read_u32(node, "clock-frequency", &cmu->parent);
+ rc = of_property_read_u64(node, "reg", &base);
+ if (rc)
+ return rc;
+
+ cmu->base = base;
+
+ parent_name = of_clk_get_parent_name(node, 0);
+
+ /* Setup clock init structure */
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.name = cmu->name;
+ init.ops = &baikal_clk_ops;
+ init.flags = CLK_IGNORE_UNUSED;
+
+ cmu->hw.init = &init;
+ cmu->is_clk_ch = 0;
+
+ pr_debug("%s: add %s, parent %s\n",
+ __func__, cmu->name, parent_name ? parent_name : "null");
+
+ clk = clk_register(NULL, &cmu->hw);
+ if (IS_ERR(clk)) {
+ pr_err("%s: could not register clk %s\n", __func__, cmu->name);
+ return -ENOMEM;
+ }
+
+ /* Register the clock for lookup */
+ rc = clk_register_clkdev(clk, cmu->name, NULL);
+ if (rc != 0) {
+ pr_err("%s: could not register lookup clk %s\n",
+ __func__, cmu->name);
+ }
+
+ /* FIXME: we probably shouldn't enable it here */
+ clk_prepare_enable(clk);
+
+ number = of_property_count_u32_elems(node, "clock-indices");
+
+ if (number > 0) {
+ clk_ch = kmalloc(sizeof(*clk_ch), GFP_KERNEL);
+ if (!clk_ch)
+ return -ENOMEM;
+
+ /* Get the last index to find out max number of children*/
+ of_property_for_each_u32(node, "clock-indices", prop, p, index) {
+ ;
+ }
+
+ clk_ch->clks = kcalloc(index + 1, sizeof(struct clk *), GFP_KERNEL);
+ clk_ch->clk_num = index + 1;
+ cmu_ch = kcalloc((index + 1), sizeof(struct baikal_clk_cmu *), GFP_KERNEL);
+ if (!cmu_ch) {
+ kfree(clk_ch);
+ return -ENOMEM;
+ }
+
+ init_ch = kcalloc((number + 1), sizeof(struct clk_init_data), GFP_KERNEL);
+ if (!init_ch) {
+ kfree(cmu_ch);
+ kfree(clk_ch);
+ return -ENOMEM;
+ }
+
+ of_property_for_each_u32(node, "clock-indices", prop, p, index) {
+ of_property_read_string_index(node, "clock-names",
+ i, &clk_ch_name);
+ pr_debug("%s: clkch <%s>, index %d, i %d\n", __func__, clk_ch_name, index, i);
+ init_ch[i].parent_names = &cmu->name;
+ init_ch[i].num_parents = 1;
+ init_ch[i].name = clk_ch_name;
+ init_ch[i].ops = &baikal_clk_ops;
+ init_ch[i].flags = CLK_IGNORE_UNUSED;
+
+ cmu_ch[index] = kmalloc(sizeof(*cmu_ch[index]), GFP_KERNEL);
+ cmu_ch[index]->name = clk_ch_name;
+ cmu_ch[index]->base = index;
+ cmu_ch[index]->parent = cmu->base;
+ cmu_ch[index]->is_clk_ch = 1;
+ cmu_ch[index]->hw.init = &init_ch[i];
+ clk_ch->clks[index] = clk_register(NULL, &cmu_ch[index]->hw);
+
+ if (IS_ERR(clk_ch->clks[index])) {
+ pr_err("%s: could not register clk %s\n",
+ __func__, clk_ch_name);
+ }
+
+ /* Register the clock for lookup */
+ rc = clk_register_clkdev(clk_ch->clks[index], clk_ch_name, NULL);
+ if (rc != 0) {
+ pr_err("%s: could not register lookup clk %s\n",
+ __func__, clk_ch_name);
+ }
+
+ /* FIXME: we probably shouldn't enable it here */
+ clk_prepare_enable(clk_ch->clks[index]);
+ i++;
+ }
+
+ return of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get, clk_ch);
+ }
+
+ return of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, clk);
+}
+
+static int baikal_clk_remove(struct platform_device *pdev)
+{
+ of_clk_del_provider(pdev->dev.of_node);
+ return 0;
+}
+
+#ifdef CONFIG_ACPI
+const char *baikal_acpi_clk_osc25_str[] = { "osc25" };
+const char *baikal_acpi_clk_osc27_str[] = { "osc27" };
+
+static struct clk *baikal_acpi_clk_osc25;
+static struct clk *baikal_acpi_clk_osc27;
+
+#define BAIKAL_CMU_CLK_CH 0x0
+#define BAIKAL_FIXED_CLK 0x1
+#define BAIKAL_FIXED_FACTOR_CLK 0x2
+#define BAIKAL_CMU_CLK 0xffffffffffffffff
+
+struct baikal_acpi_clk_data {
+ struct clk *cmu_clk;
+ struct clk_lookup *cmu_clk_l;
+ struct clk_lookup **cmu_clk_refs_l;
+ struct clk **clk;
+ struct clk_lookup **clk_l;
+ u8 *type;
+ unsigned int clk_num;
+ unsigned int cmu_clk_refs_num;
+};
+
+static int baikal_acpi_clk_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct acpi_device *ref_dev, *adev = to_acpi_device_node(pdev->dev.fwnode);
+ struct clk_init_data init, *init_ch;
+ struct baikal_clk_cmu *cmu, *cmu_ch;
+ struct baikal_acpi_clk_data *clk_data = NULL;
+ union acpi_object *package, *element;
+ acpi_status status;
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ int osc27, i, ret = 0;
+ char *str, *str2;
+
+ cmu = devm_kzalloc(dev, sizeof(*cmu), GFP_KERNEL);
+ if (!cmu)
+ return -ENOMEM;
+
+ status = acpi_evaluate_object_typed(adev->handle, "PROP", NULL, &buffer, ACPI_TYPE_PACKAGE);
+ if (ACPI_FAILURE(status)) {
+ dev_err(dev, "failed to get PROP data\n");
+ return -ENODEV;
+ }
+
+ package = buffer.pointer;
+ if (package->package.count != 4) {
+ dev_err(dev, "invalid PROP data\n");
+ ret = -EINVAL;
+ goto ret;
+ }
+
+ element = &package->package.elements[0];
+ if (element->type != ACPI_TYPE_INTEGER) {
+ dev_err(dev, "failed to get CMU id\n");
+ ret = -EINVAL;
+ goto ret;
+ }
+
+ cmu->base = element->integer.value;
+
+ element = &package->package.elements[1];
+ if (element->type != ACPI_TYPE_STRING) {
+ dev_err(dev, "failed to get CMU clock name\n");
+ ret = -EINVAL;
+ goto ret;
+ }
+
+ str = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+ if (!str) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ memcpy(str, element->string.pointer, element->string.length);
+ cmu->name = str;
+
+ element = &package->package.elements[2];
+ if (element->type != ACPI_TYPE_INTEGER) {
+ dev_err(dev, "failed to get CMU frequency\n");
+ ret = -EINVAL;
+ goto ret;
+ }
+
+ cmu->parent = element->integer.value;
+
+ element = &package->package.elements[3];
+ if (element->type != ACPI_TYPE_INTEGER) {
+ dev_err(dev, "failed to get CMU osc type\n");
+ ret = -EINVAL;
+ goto ret;
+ }
+
+ osc27 = element->integer.value ? 1 : 0;
+
+ acpi_os_free(buffer.pointer);
+ buffer.length = ACPI_ALLOCATE_BUFFER;
+ buffer.pointer = NULL;
+
+ init.parent_names = osc27 ? baikal_acpi_clk_osc27_str : baikal_acpi_clk_osc25_str;
+ init.num_parents = 1;
+ init.name = cmu->name;
+ init.ops = &baikal_clk_ops;
+ init.flags = CLK_IGNORE_UNUSED;
+
+ cmu->hw.init = &init;
+ cmu->is_clk_ch = 0;
+
+ clk_data = devm_kzalloc(dev, sizeof(*clk_data), GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ clk_data->cmu_clk = clk_register(NULL, &cmu->hw);
+ if (IS_ERR(clk_data->cmu_clk)) {
+ dev_err(dev, "failed to register CMU clock\n");
+ return PTR_ERR(clk_data->cmu_clk);
+ }
+
+ clk_data->cmu_clk_l = clkdev_create(clk_data->cmu_clk, cmu->name, NULL);
+ if (!clk_data->cmu_clk_l) {
+ dev_err(dev, "failed to register CMU clock lookup\n");
+ clk_unregister(clk_data->cmu_clk);
+ return -ENOMEM;
+ }
+
+ clk_prepare_enable(clk_data->cmu_clk);
+
+ platform_set_drvdata(pdev, clk_data);
+
+ /* CPU clock */
+ status = acpi_evaluate_object_typed(adev->handle, "CMU", NULL, &buffer, ACPI_TYPE_PACKAGE);
+ if (ACPI_FAILURE(status)) {
+ buffer.pointer = NULL;
+ goto clk_channels;
+ }
+
+ package = buffer.pointer;
+ if (!package->package.count || package->package.count % 2) {
+ dev_err(dev, "invalid CMU data\n");
+ ret = -EINVAL;
+ goto ret;
+ }
+
+ clk_data->cmu_clk_refs_num = package->package.count >> 1;
+ clk_data->cmu_clk_refs_l = devm_kzalloc(dev,
+ clk_data->cmu_clk_refs_num * sizeof(struct clk_lookup *),
+ GFP_KERNEL);
+ if (!clk_data->cmu_clk_refs_l) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ for (i = 0; i < clk_data->cmu_clk_refs_num; ++i) {
+ ref_dev = NULL;
+
+ element = &package->package.elements[2 * i];
+ if (element->type == ACPI_TYPE_LOCAL_REFERENCE && element->reference.handle)
+ ref_dev = acpi_fetch_acpi_dev(element->reference.handle);
+
+ element = &package->package.elements[2 * i + 1];
+ if (element->type == ACPI_TYPE_STRING) {
+ str2 = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+ if (str2)
+ memcpy(str2, element->string.pointer, element->string.length);
+ } else {
+ str2 = NULL;
+ }
+
+ if (ref_dev && acpi_get_first_physical_node(ref_dev))
+ clk_data->cmu_clk_refs_l[i] =
+ clkdev_create(clk_data->cmu_clk, str2, "%s",
+ dev_name(acpi_get_first_physical_node(ref_dev)));
+ else
+ clk_data->cmu_clk_refs_l[i] = clkdev_create(clk_data->cmu_clk, str2, NULL);
+ }
+
+ acpi_os_free(buffer.pointer);
+ buffer.length = ACPI_ALLOCATE_BUFFER;
+ buffer.pointer = NULL;
+
+clk_channels:
+ status = acpi_evaluate_object_typed(adev->handle, "CLKS", NULL, &buffer, ACPI_TYPE_PACKAGE);
+ if (ACPI_FAILURE(status)) {
+ buffer.pointer = NULL;
+ clk_data = NULL;
+ goto ret;
+ }
+
+ package = buffer.pointer;
+ if (!package->package.count || package->package.count % 4) {
+ dev_err(dev, "invalid CLKS data\n");
+ ret = -EINVAL;
+ goto ret;
+ }
+
+ clk_data->clk_num = package->package.count >> 2;
+ clk_data->clk = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk *), GFP_KERNEL);
+ if (!clk_data->clk) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ clk_data->clk_l = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk_lookup *),
+ GFP_KERNEL);
+ if (!clk_data->clk_l) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ clk_data->type = devm_kzalloc(dev, clk_data->clk_num * sizeof(u8), GFP_KERNEL);
+ if (!clk_data->type) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ init_ch = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk_init_data), GFP_KERNEL);
+ if (!init_ch) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ cmu_ch = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct baikal_clk_cmu), GFP_KERNEL);
+ if (!cmu_ch) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ for (i = 0; i < clk_data->clk_num; ++i) {
+ ref_dev = NULL;
+
+ element = &package->package.elements[4 * i];
+ if (element->type == ACPI_TYPE_LOCAL_REFERENCE && element->reference.handle)
+ ref_dev = acpi_fetch_acpi_dev(element->reference.handle);
+
+ element = &package->package.elements[4 * i + 1];
+ if (element->type == ACPI_TYPE_STRING) {
+ str = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+ if (str)
+ memcpy(str, element->string.pointer, element->string.length);
+ } else {
+ dev_err(dev, "failed to process clock device name #%i\n", i);
+ continue;
+ }
+
+ element = &package->package.elements[4 * i + 2];
+ if (element->type == ACPI_TYPE_INTEGER) {
+ if (element->integer.value != BAIKAL_CMU_CLK) {
+ init_ch[i].parent_names = &cmu->name;
+ init_ch[i].num_parents = 1;
+ init_ch[i].name = str;
+ init_ch[i].ops = &baikal_clk_ops;
+ init_ch[i].flags = CLK_IGNORE_UNUSED;
+
+ cmu_ch[i].name = str;
+ cmu_ch[i].base = element->integer.value;
+ cmu_ch[i].parent = cmu->base;
+ cmu_ch[i].is_clk_ch = 1;
+ cmu_ch[i].hw.init = &init_ch[i];
+
+ clk_data->type[i] = BAIKAL_CMU_CLK_CH;
+ clk_data->clk[i] = clk_register(ref_dev ? &ref_dev->dev : NULL,
+ &cmu_ch[i].hw);
+ if (IS_ERR(clk_data->clk[i])) {
+ dev_err(dev, "failed to register CMU channel clock #%i\n", i);
+ clk_data->clk[i] = NULL;
+ continue;
+ }
+ }
+ } else if (element->type == ACPI_TYPE_PACKAGE &&
+ element->package.count == 3 &&
+ element->package.elements[0].type == ACPI_TYPE_INTEGER &&
+ element->package.elements[1].type == ACPI_TYPE_INTEGER &&
+ element->package.elements[2].type == ACPI_TYPE_INTEGER) {
+ /* Fixed clock */
+ struct clk_hw *hw;
+ u64 type = element->package.elements[0].integer.value;
+ u64 val1 = element->package.elements[1].integer.value;
+ u64 val2 = element->package.elements[2].integer.value;
+
+ switch (type) {
+ case BAIKAL_FIXED_CLK:
+ clk_data->type[i] = BAIKAL_FIXED_CLK;
+ hw = clk_hw_register_fixed_rate_with_accuracy(ref_dev ?
+ &ref_dev->dev : NULL,
+ str, NULL, 0,
+ val1, val2);
+ if (IS_ERR(hw)) {
+ dev_err(dev, "failed to register fixed clock #%i\n", i);
+ continue;
+ }
+ clk_data->clk[i] = hw->clk;
+ break;
+ case BAIKAL_FIXED_FACTOR_CLK:
+ clk_data->type[i] = BAIKAL_FIXED_FACTOR_CLK;
+ hw = clk_hw_register_fixed_factor(ref_dev ? &ref_dev->dev : NULL,
+ str, cmu->name, 0, val1, val2);
+ if (IS_ERR(hw)) {
+ dev_err(dev, "failed to register fixed-factor clock #%i\n", i);
+ continue;
+ }
+ clk_data->clk[i] = hw->clk;
+ break;
+ default:
+ dev_err(dev, "failed to create clock #%i\n", i);
+ continue;
+ }
+ } else {
+ dev_err(dev, "failed to process clock device id #%i\n", i);
+ continue;
+ }
+
+ element = &package->package.elements[4 * i + 3];
+ if (element->type == ACPI_TYPE_STRING) {
+ str2 = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+ if (str2)
+ memcpy(str2, element->string.pointer, element->string.length);
+ } else {
+ str2 = NULL;
+ }
+
+ if (ref_dev || str2) {
+ if (!clk_data->clk[i]) {
+ if (ref_dev)
+ clk_data->clk_l[i] = clkdev_create(clk_data->cmu_clk,
+ str2, "%s",
+ dev_name(&ref_dev->dev));
+ else
+ clk_data->clk_l[i] = clkdev_create(clk_data->cmu_clk,
+ str2, NULL);
+ } else {
+ if (ref_dev)
+ clk_data->clk_l[i] = clkdev_create(clk_data->clk[i],
+ str2, "%s",
+ dev_name(&ref_dev->dev));
+ else
+ clk_data->clk_l[i] = clkdev_create(clk_data->clk[i],
+ str2, NULL);
+ }
+
+ if (!clk_data->clk_l[i]) {
+ dev_err(dev, "failed to register clock lookup #%i\n", i);
+ clk_unregister(clk_data->clk[i]);
+ clk_data->clk[i] = NULL;
+ continue;
+ }
+ }
+
+ clk_prepare_enable(clk_data->clk[i]);
+ }
+
+ clk_data = NULL;
+
+ret:
+ if (buffer.pointer)
+ acpi_os_free(buffer.pointer);
+
+ if (clk_data) {
+ clk_disable_unprepare(clk_data->cmu_clk);
+ clkdev_drop(clk_data->cmu_clk_l);
+ clk_unregister(clk_data->cmu_clk);
+ }
+
+ return ret;
+}
+
+static int baikal_acpi_clk_remove(struct platform_device *pdev)
+{
+ struct baikal_acpi_clk_data *clk_data = platform_get_drvdata(pdev);
+ int i;
+
+ if (clk_data) {
+ clk_disable_unprepare(clk_data->cmu_clk);
+ clkdev_drop(clk_data->cmu_clk_l);
+ clk_unregister(clk_data->cmu_clk);
+
+ for (i = 0; i < clk_data->cmu_clk_refs_num; ++i) {
+ if (clk_data->cmu_clk_refs_l[i])
+ clkdev_drop(clk_data->cmu_clk_refs_l[i]);
+ }
+
+ for (i = 0; i < clk_data->clk_num; ++i) {
+ if (clk_data->clk_l[i])
+ clkdev_drop(clk_data->clk_l[i]);
+ if (clk_data->clk[i]) {
+ switch (clk_data->type[i]) {
+ case BAIKAL_FIXED_CLK:
+ clk_unregister_fixed_rate(clk_data->clk[i]);
+ break;
+ case BAIKAL_FIXED_FACTOR_CLK:
+ clk_unregister_fixed_factor(clk_data->clk[i]);
+ break;
+ default:
+ clk_unregister(clk_data->clk[i]);
+ break;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static const struct acpi_device_id baikal_acpi_clk_device_ids[] = {
+ { "BKLE0001" },
+ { }
+};
+
+static struct platform_driver baikal_acpi_clk_driver = {
+ .probe = baikal_acpi_clk_probe,
+ .remove = baikal_acpi_clk_remove,
+ .driver = {
+ .name = "bm1000-cmu-acpi",
+ .acpi_match_table = ACPI_PTR(baikal_acpi_clk_device_ids)
+ }
+};
+
+static int __init bm1000_cmu_driver_acpi_init(void)
+{
+ if (!acpi_disabled) {
+ struct clk_lookup *baikal_acpi_clk_lookup_osc25;
+ struct clk_lookup *baikal_acpi_clk_lookup_osc27;
+
+ baikal_acpi_clk_osc25 = clk_register_fixed_rate(NULL, baikal_acpi_clk_osc25_str[0],
+ NULL, 0, 25000000);
+ if (IS_ERR(baikal_acpi_clk_osc25)) {
+ pr_err("%s: failed to register osc25 clock\n", __func__);
+ return PTR_ERR(baikal_acpi_clk_osc25);
+ }
+
+ baikal_acpi_clk_osc27 = clk_register_fixed_rate(NULL, baikal_acpi_clk_osc27_str[0],
+ NULL, 0, 27000000);
+ if (IS_ERR(baikal_acpi_clk_osc27)) {
+ clk_unregister_fixed_rate(baikal_acpi_clk_osc25);
+ pr_err("%s: failed to register osc27 clock\n", __func__);
+ return PTR_ERR(baikal_acpi_clk_osc27);
+ }
+
+ baikal_acpi_clk_lookup_osc25 = clkdev_create(baikal_acpi_clk_osc25, NULL, "%s",
+ baikal_acpi_clk_osc25_str[0]);
+ if (!baikal_acpi_clk_lookup_osc25) {
+ clk_unregister_fixed_rate(baikal_acpi_clk_osc27);
+ clk_unregister_fixed_rate(baikal_acpi_clk_osc25);
+ pr_err("%s: failed to register osc25 clock lookup\n", __func__);
+ return -ENOMEM;
+ }
+
+ baikal_acpi_clk_lookup_osc27 = clkdev_create(baikal_acpi_clk_osc27, NULL, "%s",
+ baikal_acpi_clk_osc27_str[0]);
+ if (!baikal_acpi_clk_lookup_osc27) {
+ clkdev_drop(baikal_acpi_clk_lookup_osc25);
+ clk_unregister_fixed_rate(baikal_acpi_clk_osc27);
+ clk_unregister_fixed_rate(baikal_acpi_clk_osc25);
+ pr_err("%s: failed to register osc27 clock lookup\n", __func__);
+ return -ENOMEM;
+ }
+
+ clk_prepare_enable(baikal_acpi_clk_osc25);
+ clk_prepare_enable(baikal_acpi_clk_osc27);
+
+ return platform_driver_register(&baikal_acpi_clk_driver);
+ }
+
+ return 0;
+}
+
+device_initcall(bm1000_cmu_driver_acpi_init);
+#endif
+
+static const struct of_device_id baikal_clk_of_match[] = {
+ { .compatible = "baikal,bm1000-cmu" },
+ { }
+};
+
+static struct platform_driver bm1000_cmu_driver = {
+ .probe = baikal_clk_probe,
+ .remove = baikal_clk_remove,
+ .driver = {
+ .name = "bm1000-cmu",
+ .of_match_table = baikal_clk_of_match
+ }
+};
+module_platform_driver(bm1000_cmu_driver);
+
+MODULE_DESCRIPTION("Baikal BE-M1000 clock driver");
+MODULE_AUTHOR("Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bm1000-cmu");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021-2023 Baikal Electronics, JSC
+ * Author: Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
+ */
+
+#include <linux/acpi.h>
+#include <linux/arm-smccc.h>
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BAIKAL_SMC_CLK_ROUND 0x82000400
+#define BAIKAL_SMC_CLK_SET 0x82000401
+#define BAIKAL_SMC_CLK_GET 0x82000402
+#define BAIKAL_SMC_CLK_ENABLE 0x82000403
+#define BAIKAL_SMC_CLK_DISABLE 0x82000404
+#define BAIKAL_SMC_CLK_IS_ENABLED 0x82000405
+
+struct baikal_clk {
+ struct clk_hw hw;
+ u32 base;
+};
+
+#define to_baikal_clk(_hw) container_of(_hw, struct baikal_clk, hw)
+
+static int baikal_clk_enable(struct clk_hw *hw)
+{
+ struct baikal_clk *clk = to_baikal_clk(hw);
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(BAIKAL_SMC_CLK_ENABLE, clk->base, 0, 0, 0, 0, 0, 0, &res);
+ return res.a0;
+}
+
+static void baikal_clk_disable(struct clk_hw *hw)
+{
+ struct baikal_clk *clk = to_baikal_clk(hw);
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(BAIKAL_SMC_CLK_DISABLE, clk->base, 0, 0, 0, 0, 0, 0, &res);
+}
+
+static int baikal_clk_is_enabled(struct clk_hw *hw)
+{
+ struct baikal_clk *clk = to_baikal_clk(hw);
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(BAIKAL_SMC_CLK_IS_ENABLED, clk->base, 0, 0, 0, 0, 0, 0, &res);
+ return res.a0;
+}
+
+static unsigned long baikal_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct baikal_clk *clk = to_baikal_clk(hw);
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(BAIKAL_SMC_CLK_GET, clk->base, 0, 0, 0, 0, 0, 0, &res);
+ return res.a0;
+}
+
+static int baikal_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct baikal_clk *clk = to_baikal_clk(hw);
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(BAIKAL_SMC_CLK_SET, clk->base, rate, 0, 0, 0, 0, 0, &res);
+ return res.a0;
+}
+
+static long baikal_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct baikal_clk *clk = to_baikal_clk(hw);
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(BAIKAL_SMC_CLK_ROUND, clk->base, rate, 0, 0, 0, 0, 0, &res);
+ return res.a0;
+}
+
+static const struct clk_ops baikal_clk_ops = {
+ .enable = baikal_clk_enable,
+ .disable = baikal_clk_disable,
+ .is_enabled = baikal_clk_is_enabled,
+ .recalc_rate = baikal_clk_recalc_rate,
+ .set_rate = baikal_clk_set_rate,
+ .round_rate = baikal_clk_round_rate,
+};
+
+static int baikal_clk_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct clk_init_data init;
+ struct baikal_clk *cmu;
+ struct clk_onecell_data *clk_data;
+ const char *clk_name;
+ struct property *prop;
+ const __be32 *p;
+ int clk_index;
+ int clk_index_max;
+ int clk_index_cnt;
+ int clk_name_cnt;
+ int clk_cnt;
+ int i;
+ u32 base;
+ struct clk *clk;
+ int ret;
+ int multi;
+
+ ret = of_property_read_u32(node, "reg", &base);
+ if (ret)
+ base = 0;
+
+ clk_index_cnt = of_property_count_u32_elems(node, "clock-indices");
+ clk_name_cnt = of_property_count_strings(node, "clock-output-names");
+ clk_cnt = clk_index_cnt > clk_name_cnt ? clk_index_cnt : clk_name_cnt;
+ if (clk_cnt < 1)
+ clk_cnt = 1;
+
+ multi = clk_cnt > 1;
+
+ if (multi) {
+ clk_index_max = clk_cnt - 1;
+ of_property_for_each_u32(node, "clock-indices", prop, p, clk_index) {
+ if (clk_index_max < clk_index)
+ clk_index_max = clk_index;
+ }
+
+ clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
+ clk_data->clks = kcalloc(clk_index_max + 1, sizeof(struct clk *), GFP_KERNEL);
+ clk_data->clk_num = clk_index_max + 1;
+ }
+
+ for (i = 0; i < clk_cnt; i++) {
+ ret = of_property_read_u32_index(node, "clock-indices", i, &clk_index);
+ if (ret)
+ clk_index = i;
+
+ ret = of_property_read_string_index(node, "clock-output-names", i, &clk_name);
+ if (ret) {
+ if (multi)
+ init.name = kasprintf(GFP_KERNEL, "%s.%d", node->name, clk_index);
+ else
+ init.name = kasprintf(GFP_KERNEL, "%s", node->name);
+ } else {
+ init.name = kasprintf(GFP_KERNEL, "%s.%s", node->name, clk_name);
+ }
+
+ init.ops = &baikal_clk_ops;
+ init.flags = CLK_IGNORE_UNUSED;
+ init.parent_names = NULL;
+ init.num_parents = 0;
+
+ cmu = kmalloc(sizeof(*cmu), GFP_KERNEL);
+ cmu->base = base + 0x10 * clk_index;
+ cmu->hw.init = &init;
+
+ clk = clk_register(NULL, &cmu->hw);
+ if (!IS_ERR(clk)) {
+ clk_register_clkdev(clk, init.name, NULL);
+ if (multi)
+ clk_data->clks[clk_index] = clk;
+ }
+ }
+
+ if (multi)
+ ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get, clk_data);
+ else
+ ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, clk);
+
+ return ret;
+}
+
+static int baikal_clk_remove(struct platform_device *pdev)
+{
+ of_clk_del_provider(pdev->dev.of_node);
+ return 0;
+}
+
+#ifdef CONFIG_ACPI
+const char *baikal_acpi_ref_clk_str[] = { "baikal_ref_clk" };
+
+static struct clk *baikal_acpi_ref_clk;
+
+struct baikal_acpi_clk_data {
+ struct clk *cmu_clk;
+ struct clk_lookup *cmu_clk_l;
+ struct clk **clk;
+ struct clk_lookup **clk_l;
+ unsigned int clk_num;
+};
+
+static int baikal_acpi_clk_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct acpi_device *ref_dev, *adev = to_acpi_device_node(pdev->dev.fwnode);
+ struct clk_init_data init, *init_ch;
+ struct baikal_clk *cmu, *cmu_ch;
+ struct baikal_acpi_clk_data *clk_data = NULL;
+ union acpi_object *package, *element;
+ acpi_status status;
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ int size, i, index, ret = 0;
+ char *str, *str2;
+ const char *cmu_name;
+
+ cmu = devm_kzalloc(dev, sizeof(*cmu), GFP_KERNEL);
+ if (!cmu)
+ return -ENOMEM;
+
+ status = acpi_evaluate_object_typed(adev->handle, "PROP", NULL, &buffer, ACPI_TYPE_PACKAGE);
+ if (ACPI_FAILURE(status)) {
+ dev_err(dev, "failed to get PROP data\n");
+ return -ENODEV;
+ }
+
+ package = buffer.pointer;
+ if (package->package.count != 2) {
+ dev_err(dev, "invalid PROP data\n");
+ ret = -EINVAL;
+ goto ret;
+ }
+
+ element = &package->package.elements[0];
+ if (element->type != ACPI_TYPE_INTEGER) {
+ dev_err(dev, "failed to get CMU id\n");
+ ret = -EINVAL;
+ goto ret;
+ }
+
+ cmu->base = element->integer.value;
+
+ element = &package->package.elements[1];
+ if (element->type != ACPI_TYPE_STRING) {
+ dev_err(dev, "failed to get CMU clock name\n");
+ ret = -EINVAL;
+ goto ret;
+ }
+
+ str = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+ if (!str) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ memcpy(str, element->string.pointer, element->string.length);
+ cmu_name = str;
+
+ acpi_os_free(buffer.pointer);
+ buffer.length = ACPI_ALLOCATE_BUFFER;
+ buffer.pointer = NULL;
+
+ init.parent_names = baikal_acpi_ref_clk_str;
+ init.num_parents = 1;
+ init.name = cmu_name;
+ init.ops = &baikal_clk_ops;
+ init.flags = CLK_IGNORE_UNUSED;
+
+ cmu->hw.init = &init;
+
+ clk_data = devm_kzalloc(dev, sizeof(*clk_data), GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ clk_data->cmu_clk = clk_register(NULL, &cmu->hw);
+ if (IS_ERR(clk_data->cmu_clk)) {
+ dev_err(dev, "failed to register CMU clock\n");
+ return PTR_ERR(clk_data->cmu_clk);
+ }
+
+ clk_data->cmu_clk_l = clkdev_create(clk_data->cmu_clk, cmu_name, NULL);
+ if (!clk_data->cmu_clk_l) {
+ dev_err(dev, "failed to register CMU clock lookup\n");
+ clk_unregister(clk_data->cmu_clk);
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, clk_data);
+
+ status = acpi_evaluate_object_typed(adev->handle, "CLKS", NULL, &buffer, ACPI_TYPE_PACKAGE);
+ if (ACPI_FAILURE(status)) {
+ buffer.pointer = NULL;
+ goto ret;
+ }
+
+ package = buffer.pointer;
+ if (!package->package.count || package->package.count % 4) {
+ dev_err(dev, "invalid CLKS data\n");
+ ret = -EINVAL;
+ goto ret;
+ }
+
+ clk_data->clk_num = package->package.count >> 2;
+ clk_data->clk = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk *), GFP_KERNEL);
+ if (!clk_data->clk) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ clk_data->clk_l = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk_lookup *), GFP_KERNEL);
+ if (!clk_data->clk_l) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ init_ch = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk_init_data), GFP_KERNEL);
+ if (!init_ch) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ cmu_ch = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct baikal_clk), GFP_KERNEL);
+ if (!cmu_ch) {
+ ret = -ENOMEM;
+ goto ret;
+ }
+
+ for (i = 0; i < clk_data->clk_num; ++i) {
+ ref_dev = NULL;
+ size = 0;
+
+ element = &package->package.elements[4 * i];
+ if (element->type == ACPI_TYPE_LOCAL_REFERENCE && element->reference.handle)
+ ref_dev = acpi_fetch_acpi_dev(element->reference.handle);
+
+ element = &package->package.elements[4 * i + 1];
+ if (element->type == ACPI_TYPE_STRING) {
+ if (ref_dev)
+ size = strlen(dev_name(&ref_dev->dev)) + 1;
+
+ str = devm_kzalloc(dev, size + element->string.length + 1, GFP_KERNEL);
+ if (str) {
+ if (ref_dev) {
+ memcpy(str, dev_name(&ref_dev->dev), size - 1);
+ str[size - 1] = '_';
+ memcpy(str + size, element->string.pointer, element->string.length);
+ } else {
+ memcpy(str, element->string.pointer, element->string.length);
+ }
+ }
+ } else {
+ dev_err(dev, "failed to process clock device name #%i\n", i);
+ continue;
+ }
+
+ element = &package->package.elements[4 * i + 2];
+ if (element->type == ACPI_TYPE_INTEGER) {
+ index = element->integer.value;
+ } else {
+ dev_err(dev, "failed to process clock device id #%i\n", i);
+ continue;
+ }
+
+ element = &package->package.elements[4 * i + 3];
+ if (element->type == ACPI_TYPE_STRING) {
+ str2 = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+ if (str2)
+ memcpy(str2, element->string.pointer, element->string.length);
+ } else {
+ str2 = NULL;
+ }
+
+ init_ch[i].parent_names = &cmu_name;
+ init_ch[i].num_parents = 1;
+ init_ch[i].name = str;
+ init_ch[i].ops = &baikal_clk_ops;
+ init_ch[i].flags = CLK_IGNORE_UNUSED;
+
+ cmu_ch[i].base = cmu->base + 0x20 + 0x10 * index;
+ cmu_ch[i].hw.init = &init_ch[i];
+
+ clk_data->clk[i] = clk_register(ref_dev ? &ref_dev->dev : NULL, &cmu_ch[i].hw);
+ if (IS_ERR(clk_data->clk[i])) {
+ dev_err(dev, "failed to register CMU channel clock #%i\n", i);
+ clk_data->clk[i] = NULL;
+ continue;
+ }
+
+ if (ref_dev)
+ clk_data->clk_l[i] = clkdev_create(clk_data->clk[i], str2, "%s", dev_name(&ref_dev->dev));
+ else
+ clk_data->clk_l[i] = clkdev_create(clk_data->clk[i], str2, NULL);
+
+ if (!clk_data->clk_l[i]) {
+ dev_err(dev, "failed to register CMU channel clock lookup #%i\n", i);
+ clk_unregister(clk_data->clk[i]);
+ clk_data->clk[i] = NULL;
+ continue;
+ }
+ }
+
+ clk_data = NULL;
+
+ret:
+ if (buffer.pointer)
+ acpi_os_free(buffer.pointer);
+
+ if (clk_data) {
+ clk_disable_unprepare(clk_data->cmu_clk);
+ clkdev_drop(clk_data->cmu_clk_l);
+ clk_unregister(clk_data->cmu_clk);
+ }
+
+ return ret;
+}
+
+static int baikal_acpi_clk_remove(struct platform_device *pdev)
+{
+ struct baikal_acpi_clk_data *clk_data = platform_get_drvdata(pdev);
+ int i;
+
+ if (clk_data) {
+ clk_disable_unprepare(clk_data->cmu_clk);
+ clkdev_drop(clk_data->cmu_clk_l);
+ clk_unregister(clk_data->cmu_clk);
+
+ for (i = 0; i < clk_data->clk_num; ++i) {
+ if (clk_data->clk_l[i])
+ clkdev_drop(clk_data->clk_l[i]);
+ if (clk_data->clk[i])
+ clk_unregister(clk_data->clk[i]);
+ }
+ }
+
+ return 0;
+}
+
+static const struct acpi_device_id baikal_acpi_clk_device_ids[] = {
+ { "BKLE0001" },
+ { }
+};
+
+static struct platform_driver baikal_acpi_clk_driver = {
+ .probe = baikal_acpi_clk_probe,
+ .remove = baikal_acpi_clk_remove,
+ .driver = {
+ .name = "bs1000-cmu-acpi",
+ .acpi_match_table = ACPI_PTR(baikal_acpi_clk_device_ids)
+ }
+};
+
+static int __init baikal_acpi_clk_driver_init(void)
+{
+ if (!acpi_disabled) {
+ struct clk_lookup *baikal_acpi_ref_clk_lookup;
+
+ baikal_acpi_ref_clk = clk_register_fixed_rate(NULL, baikal_acpi_ref_clk_str[0], NULL, 0, 25000000);
+ if (IS_ERR(baikal_acpi_ref_clk)) {
+ pr_err("%s: failed to register reference clock\n", __func__);
+ return PTR_ERR(baikal_acpi_ref_clk);
+ }
+
+ baikal_acpi_ref_clk_lookup = clkdev_create(baikal_acpi_ref_clk, NULL, "%s", baikal_acpi_ref_clk_str[0]);
+ if (!baikal_acpi_ref_clk_lookup) {
+ clk_unregister_fixed_rate(baikal_acpi_ref_clk);
+ pr_err("%s: failed to register reference clock lookup\n", __func__);
+ return -ENOMEM;
+ }
+
+ clk_prepare_enable(baikal_acpi_ref_clk);
+
+ return platform_driver_register(&baikal_acpi_clk_driver);
+ }
+
+ return 0;
+}
+
+device_initcall(baikal_acpi_clk_driver_init);
+#endif
+
+static const struct of_device_id baikal_clk_of_match[] = {
+ { .compatible = "baikal,bs1000-cmu" },
+ { /* sentinel */ }
+};
+
+static struct platform_driver bs1000_cmu_driver = {
+ .probe = baikal_clk_probe,
+ .remove = baikal_clk_remove,
+ .driver = {
+ .name = "bs1000-cmu",
+ .of_match_table = baikal_clk_of_match
+ }
+};
+module_platform_driver(bs1000_cmu_driver);
+
+MODULE_DESCRIPTION("Baikal BE-S1000 clock driver");
+MODULE_AUTHOR("Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bs1000-cmu");
select DW_APB_TIMER
select TIMER_OF
+config BAIKAL_DW_APB_TIMER
+ bool "Baikal ACPI support for DW APB timer driver"
+ depends on ARCH_BAIKAL && ACPI
+ select DW_APB_TIMER
+ help
+ Enables the support for the dw_apb timer in ACPI.
+
config FTTMR010_TIMER
bool "Faraday Technology timer driver" if COMPILE_TEST
depends on HAS_IOMEM
obj-$(CONFIG_OMAP_DM_SYSTIMER) += timer-ti-dm-systimer.o
obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o
obj-$(CONFIG_DW_APB_TIMER_OF) += dw_apb_timer_of.o
+obj-$(CONFIG_BAIKAL_DW_APB_TIMER) += baikal_dw_apb_timer.o
obj-$(CONFIG_FTTMR010_TIMER) += timer-fttmr010.o
obj-$(CONFIG_IXP4XX_TIMER) += timer-ixp4xx.o
obj-$(CONFIG_ROCKCHIP_TIMER) += timer-rockchip.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Baikal DW APB timer driver for ACPI
+ *
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ * Author: Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
+ *
+ * Implementation based on dw_apb_timer_of.c
+ */
+
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/dw_apb_timer.h>
+#include <linux/platform_device.h>
+#include <linux/sched_clock.h>
+
+static int __init timer_get_base_and_rate(struct platform_device *pdev,
+ void __iomem **base, unsigned long *rate)
+{
+ struct device *dev = &pdev->dev;
+ struct clk *clk;
+ int ret;
+
+ *base = devm_platform_ioremap_resource(pdev, 0);
+ if (!*base) {
+ dev_err(dev, "Unable to map regs\n");
+ return PTR_ERR(*base);
+ }
+
+ clk = devm_clk_get(dev, "timer");
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return ret;
+
+ *rate = clk_get_rate(clk);
+ if (!*rate) {
+ clk_disable_unprepare(clk);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __init add_clockevent(struct platform_device *pdev)
+{
+ struct dw_apb_clock_event_device *ced;
+ void __iomem *iobase;
+ unsigned long rate;
+ int irq, ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "No IRQ for clock event timer\n");
+ return -EINVAL;
+ }
+
+ ret = timer_get_base_and_rate(pdev, &iobase, &rate);
+ if (ret)
+ return ret;
+
+ ced = dw_apb_clockevent_init(-1, pdev->name, 300, iobase, irq, rate);
+ if (!ced)
+ return -EINVAL;
+
+ dw_apb_clockevent_register(ced);
+
+ return 0;
+}
+
+static void __iomem *sched_io_base;
+static unsigned long sched_rate;
+
+static int __init add_clocksource(struct platform_device *pdev)
+{
+ struct dw_apb_clocksource *cs;
+ void __iomem *iobase;
+ unsigned long rate;
+ int ret;
+
+ ret = timer_get_base_and_rate(pdev, &iobase, &rate);
+ if (ret)
+ return ret;
+
+ cs = dw_apb_clocksource_init(300, pdev->name, iobase, rate);
+ if (!cs)
+ return -EINVAL;
+
+ dw_apb_clocksource_start(cs);
+ dw_apb_clocksource_register(cs);
+
+ sched_io_base = iobase + 0x04;
+ sched_rate = rate;
+
+ return 0;
+}
+
+static u64 notrace read_sched_clock(void)
+{
+ return ~readl_relaxed(sched_io_base);
+}
+
+static int num_called;
+
+static int __init baikal_dw_apb_timer_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ switch (num_called) {
+ case 1:
+ pr_debug("%s: found clocksource timer\n", __func__);
+ ret = add_clocksource(pdev);
+ if (ret)
+ return ret;
+ sched_clock_register(read_sched_clock, 32, sched_rate);
+ break;
+ default:
+ pr_debug("%s: found clockevent timer\n", __func__);
+ ret = add_clockevent(pdev);
+ if (ret)
+ return ret;
+ break;
+ }
+
+ ++num_called;
+
+ return 0;
+}
+
+static const struct of_device_id baikal_dw_apb_timer_of_match[] = {
+ { .compatible = "baikal,bm1000-dw-apb-timer" },
+ {}
+};
+
+static struct platform_driver baikal_dw_apb_timer = {
+ .driver = {
+ .name = "baikal-dw-apb-timer",
+ .of_match_table = baikal_dw_apb_timer_of_match,
+ .suppress_bind_attrs = true
+ },
+ .probe = baikal_dw_apb_timer_probe
+};
+builtin_platform_driver(baikal_dw_apb_timer);
This add the CPUFreq driver support for Intel PXA2xx SOCs.
If in doubt, say N.
+
+config BAIKAL_CPUFREQ
+ bool "Baikal-M cpufreq support for ACPI"
+ depends on ARCH_BAIKAL && ACPI
+ help
+ This adds the CPUFreq driver for Baikal-M.
+
+ If in doubt, say N.
obj-$(CONFIG_ARM_TEGRA194_CPUFREQ) += tegra194-cpufreq.o
obj-$(CONFIG_ARM_TI_CPUFREQ) += ti-cpufreq.o
obj-$(CONFIG_ARM_VEXPRESS_SPC_CPUFREQ) += vexpress-spc-cpufreq.o
+obj-$(CONFIG_BAIKAL_CPUFREQ) += baikal-cpufreq.o
##################################################################################
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Baikal-M cpufreq driver
+ *
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ * Author: Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
+ *
+ * Implementation based on cpufreq-dt.c
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+
+#include "../opp/opp.h"
+
+struct private_data {
+ struct list_head node;
+
+ cpumask_var_t cpus;
+ struct device *cpu_dev;
+ struct cpufreq_frequency_table *freq_table;
+};
+
+static LIST_HEAD(priv_list);
+
+static struct freq_attr *baikal_cpufreq_attr[] = {
+ &cpufreq_freq_attr_scaling_available_freqs,
+ NULL,
+};
+
+static int baikal_cpufreq_target_index(struct cpufreq_policy *policy,
+ unsigned int index)
+{
+ struct private_data *priv = policy->driver_data;
+ unsigned long freq = policy->freq_table[index].frequency;
+
+ return dev_pm_opp_set_rate(priv->cpu_dev, freq * 1000);
+}
+
+static struct private_data *baikal_cpufreq_find_data(int cpu)
+{
+ struct private_data *priv;
+
+ list_for_each_entry(priv, &priv_list, node) {
+ if (cpumask_test_cpu(cpu, priv->cpus))
+ return priv;
+ }
+
+ return NULL;
+}
+
+static int baikal_cpufreq_init(struct cpufreq_policy *policy)
+{
+ struct private_data *priv;
+ struct device *cpu_dev;
+ struct clk *cpu_clk;
+ unsigned int transition_latency;
+ int ret;
+
+ priv = baikal_cpufreq_find_data(policy->cpu);
+ if (!priv) {
+ pr_err("failed to find data for cpu%d\n", policy->cpu);
+ return -ENODEV;
+ }
+ cpu_dev = priv->cpu_dev;
+
+ cpu_clk = clk_get(cpu_dev, NULL);
+ if (IS_ERR(cpu_clk)) {
+ ret = PTR_ERR(cpu_clk);
+ dev_err(cpu_dev, "%s: failed to get clk: %d\n", __func__, ret);
+ return ret;
+ }
+
+ transition_latency = dev_pm_opp_get_max_transition_latency(cpu_dev);
+ if (!transition_latency)
+ transition_latency = CPUFREQ_ETERNAL;
+
+ cpumask_copy(policy->cpus, priv->cpus);
+ policy->driver_data = priv;
+ policy->clk = cpu_clk;
+ policy->freq_table = priv->freq_table;
+ policy->suspend_freq = dev_pm_opp_get_suspend_opp_freq(cpu_dev) / 1000;
+ policy->cpuinfo.transition_latency = transition_latency;
+ policy->dvfs_possible_from_any_cpu = true;
+
+ return 0;
+}
+
+static int baikal_cpufreq_online(struct cpufreq_policy *policy)
+{
+ return 0;
+}
+
+static int baikal_cpufreq_offline(struct cpufreq_policy *policy)
+{
+ return 0;
+}
+
+static int baikal_cpufreq_exit(struct cpufreq_policy *policy)
+{
+ clk_put(policy->clk);
+ return 0;
+}
+
+static struct cpufreq_driver baikal_cpufreq_driver = {
+ .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK |
+ CPUFREQ_IS_COOLING_DEV,
+ .verify = cpufreq_generic_frequency_table_verify,
+ .target_index = baikal_cpufreq_target_index,
+ .get = cpufreq_generic_get,
+ .init = baikal_cpufreq_init,
+ .exit = baikal_cpufreq_exit,
+ .online = baikal_cpufreq_online,
+ .offline = baikal_cpufreq_offline,
+ .name = "baikal-cpufreq",
+ .attr = baikal_cpufreq_attr,
+ .suspend = cpufreq_generic_suspend,
+};
+
+static unsigned long baikal_cpufreq_opp_table_hz[] = {
+ 500000000,
+ 600000000,
+ 700000000,
+ 800000000,
+ 900000000,
+ 1000000000,
+ 1100000000,
+ 1200000000,
+ 1300000000,
+ 1400000000,
+ 1500000000
+};
+
+static struct dev_pm_opp *baikal_cpufreq_opp_get(struct opp_table *opp_table,
+ int num)
+{
+ struct dev_pm_opp *opp = NULL, *temp;
+
+ mutex_lock(&opp_table->lock);
+ list_for_each_entry(temp, &opp_table->opp_list, node) {
+ if (!temp->removed && temp->dynamic &&
+ temp->rates[0] == baikal_cpufreq_opp_table_hz[num]) {
+ opp = temp;
+ break;
+ }
+ }
+ mutex_unlock(&opp_table->lock);
+
+ return opp;
+}
+
+static int baikal_cpufreq_add_opp(struct device *dev)
+{
+ struct opp_table *opp_table = NULL;
+ struct dev_pm_opp *opp;
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(baikal_cpufreq_opp_table_hz); ++i) {
+ ret = dev_pm_opp_add(dev, baikal_cpufreq_opp_table_hz[i], 0);
+ if (ret)
+ return ret;
+
+ if (!opp_table) {
+ opp_table = dev_pm_opp_get_opp_table(dev);
+ if (!IS_ERR_OR_NULL(opp_table))
+ opp_table->clock_latency_ns_max = 10000000;
+ }
+
+ opp = baikal_cpufreq_opp_get(opp_table, i);
+ if (opp) {
+ mutex_lock(&opp_table->lock);
+ opp->clock_latency_ns = 10000000;
+ mutex_unlock(&opp_table->lock);
+ }
+ }
+
+ if (!IS_ERR_OR_NULL(opp_table))
+ dev_pm_opp_put_opp_table(opp_table);
+
+ return 0;
+}
+
+static void baikal_dev_pm_opp_remove_all_dynamic(const struct cpumask *cpumask)
+{
+ struct device *cpu_dev;
+ int cpu;
+
+ if (cpumask_empty(cpumask))
+ return;
+
+ for_each_cpu(cpu, cpumask) {
+ cpu_dev = get_cpu_device(cpu);
+ if (!cpu_dev)
+ continue;
+
+ dev_pm_opp_remove_all_dynamic(cpu_dev);
+ }
+}
+
+static int baikal_cpufreq_add_opp_table(const struct cpumask *cpumask)
+{
+ struct device *cpu_dev;
+ int cpu, ret;
+
+ if (cpumask_empty(cpumask))
+ return -ENODEV;
+
+ for_each_cpu(cpu, cpumask) {
+ cpu_dev = get_cpu_device(cpu);
+ if (!cpu_dev) {
+ pr_err("%s: failed to get cpu%d device\n", __func__,
+ cpu);
+ ret = -ENODEV;
+ goto remove_table;
+ }
+
+ ret = baikal_cpufreq_add_opp(cpu_dev);
+ if (ret)
+ goto remove_table;
+ }
+
+ return 0;
+
+remove_table:
+ baikal_dev_pm_opp_remove_all_dynamic(cpumask);
+
+ return ret;
+}
+
+static int baikal_cpufreq_early_init(struct device *dev, int cpu)
+{
+ struct private_data *priv;
+ struct device *cpu_dev, *tmp;
+ int ret, i;
+
+ /* Check if this CPU is already covered by some other policy */
+ if (baikal_cpufreq_find_data(cpu))
+ return 0;
+
+ cpu_dev = get_cpu_device(cpu);
+ if (!cpu_dev)
+ return -EPROBE_DEFER;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ if (!alloc_cpumask_var(&priv->cpus, GFP_KERNEL))
+ return -ENOMEM;
+
+ cpumask_set_cpu(cpu, priv->cpus);
+ priv->cpu_dev = cpu_dev;
+
+ for_each_possible_cpu(i) {
+ if (i == cpu_dev->id)
+ continue;
+
+ tmp = get_cpu_device(i);
+ if (!tmp) {
+ ret = -ENODEV;
+ goto free_cpumask;
+ }
+
+ if (ACPI_COMPANION(cpu_dev)->dev.parent ==
+ ACPI_COMPANION(tmp)->dev.parent)
+ cpumask_set_cpu(i, priv->cpus);
+ }
+
+ ret = baikal_cpufreq_add_opp_table(priv->cpus);
+ if (ret)
+ goto free_cpumask;
+
+ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &priv->freq_table);
+ if (ret) {
+ dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
+ goto out;
+ }
+
+ list_add(&priv->node, &priv_list);
+ return 0;
+
+out:
+ dev_pm_opp_remove_all_dynamic(priv->cpu_dev);
+free_cpumask:
+ free_cpumask_var(priv->cpus);
+ return ret;
+}
+
+static void baikal_cpufreq_release(void)
+{
+ struct private_data *priv, *tmp;
+
+ list_for_each_entry_safe(priv, tmp, &priv_list, node) {
+ dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &priv->freq_table);
+ baikal_dev_pm_opp_remove_all_dynamic(priv->cpus);
+ free_cpumask_var(priv->cpus);
+ list_del(&priv->node);
+ }
+}
+
+static int baikal_cpufreq_probe(struct platform_device *pdev)
+{
+ int ret, cpu;
+
+ for_each_possible_cpu(cpu) {
+ ret = baikal_cpufreq_early_init(&pdev->dev, cpu);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add cpufreq table: %d\n", ret);
+ goto err;
+ }
+ }
+
+ ret = cpufreq_register_driver(&baikal_cpufreq_driver);
+ if (ret) {
+ dev_err(&pdev->dev, "failed register driver: %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+err:
+ baikal_cpufreq_release();
+ return ret;
+}
+
+static int baikal_cpufreq_remove(struct platform_device *pdev)
+{
+ cpufreq_unregister_driver(&baikal_cpufreq_driver);
+ baikal_cpufreq_release();
+ return 0;
+}
+
+static struct platform_driver baikal_cpufreq_platform_driver = {
+ .driver = {
+ .name = "baikal-cpufreq",
+ .suppress_bind_attrs = true
+ },
+ .probe = baikal_cpufreq_probe,
+ .remove = baikal_cpufreq_remove
+};
+builtin_platform_driver(baikal_cpufreq_platform_driver);
+
+static int __init baikal_cpufreq_driver_init(void)
+{
+ if (!acpi_disabled)
+ return PTR_ERR_OR_ZERO(platform_device_register_data(NULL, "baikal-cpufreq", -1,
+ NULL, 0));
+
+ return 0;
+}
+core_initcall(baikal_cpufreq_driver_init);
+
+MODULE_ALIAS("platform:baikal-cpufreq");
+MODULE_AUTHOR("Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal-M cpufreq driver");
+MODULE_LICENSE("GPL");
{ .compatible = "arm,integrator-ap", },
{ .compatible = "arm,integrator-cp", },
+ { .compatible = "baikal,bm1000", },
+ { .compatible = "baikal,bs1000", },
+
{ .compatible = "hisilicon,hi3660", },
{ .compatible = "fsl,imx27", },
.m_master = 0,
.p_master = 1,
};
+ struct fwnode_reference_args args = {};
+
+ if (!fwnode_property_get_reference_args(dma_spec->dev->fwnode,
+ "baikal,masters", NULL, 3,
+ dma_spec->slave_id, &args)) {
+ if (args.nargs > 1) {
+ slave.m_master = args.args[0];
+ slave.p_master = args.args[1];
+ }
+ if (args.nargs == 3)
+ slave.channels = args.args[2];
+ }
return dw_dma_filter(chan, &slave);
}
unsigned int dw_params;
unsigned int i;
int err;
+ int irq_num;
+
dw->pdata = devm_kzalloc(chip->dev, sizeof(*dw->pdata), GFP_KERNEL);
if (!dw->pdata)
tasklet_setup(&dw->tasklet, dw_dma_tasklet);
- err = request_irq(chip->irq, dw_dma_interrupt, IRQF_SHARED,
- dw->name, dw);
- if (err)
- goto err_pdata;
+ irq_num = chip->irq_num;
+ while (irq_num--) {
+ err = request_irq(chip->irq[irq_num], dw_dma_interrupt,
+ IRQF_SHARED, dw->name, dw);
+ if (err)
+ goto err_pdata;
+ }
INIT_LIST_HEAD(&dw->dma.channels);
for (i = 0; i < pdata->nr_channels; i++) {
return 0;
err_dma_register:
- free_irq(chip->irq, dw);
+ irq_num = chip->irq_num;
+ while (irq_num--)
+ free_irq(chip->irq[irq_num], dw);
err_pdata:
pm_runtime_put_sync_suspend(chip->dev);
return err;
{
struct dw_dma *dw = chip->dw;
struct dw_dma_chan *dwc, *_dwc;
+ int irq_num;
pm_runtime_get_sync(chip->dev);
do_dw_dma_off(dw);
dma_async_device_unregister(&dw->dma);
- free_irq(chip->irq, dw);
+ irq_num = chip->irq_num;
+ while (irq_num--)
+ free_irq(chip->irq[irq_num], dw);
+
tasklet_kill(&dw->tasklet);
list_for_each_entry_safe(dwc, _dwc, &dw->dma.channels,
struct platform_device;
+struct dw_dma_platform_data *dw_dma_parse_properties(struct platform_device *pdev);
+
#ifdef CONFIG_OF
-struct dw_dma_platform_data *dw_dma_parse_dt(struct platform_device *pdev);
void dw_dma_of_controller_register(struct dw_dma *dw);
void dw_dma_of_controller_free(struct dw_dma *dw);
#else
-static inline struct dw_dma_platform_data *dw_dma_parse_dt(struct platform_device *pdev)
-{
- return NULL;
-}
static inline void dw_dma_of_controller_register(struct dw_dma *dw) {}
static inline void dw_dma_of_controller_free(struct dw_dma *dw) {}
#endif
return dma_request_channel(cap, dw_dma_filter, &slave);
}
-struct dw_dma_platform_data *dw_dma_parse_dt(struct platform_device *pdev)
-{
- struct device_node *np = pdev->dev.of_node;
- struct dw_dma_platform_data *pdata;
- u32 tmp, arr[DW_DMA_MAX_NR_MASTERS];
- u32 nr_masters;
- u32 nr_channels;
-
- if (of_property_read_u32(np, "dma-masters", &nr_masters))
- return NULL;
- if (nr_masters < 1 || nr_masters > DW_DMA_MAX_NR_MASTERS)
- return NULL;
-
- if (of_property_read_u32(np, "dma-channels", &nr_channels))
- return NULL;
- if (nr_channels > DW_DMA_MAX_NR_CHANNELS)
- return NULL;
-
- pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
- if (!pdata)
- return NULL;
-
- pdata->nr_masters = nr_masters;
- pdata->nr_channels = nr_channels;
-
- of_property_read_u32(np, "chan_allocation_order", &pdata->chan_allocation_order);
- of_property_read_u32(np, "chan_priority", &pdata->chan_priority);
-
- of_property_read_u32(np, "block_size", &pdata->block_size);
-
- /* Try deprecated property first */
- if (!of_property_read_u32_array(np, "data_width", arr, nr_masters)) {
- for (tmp = 0; tmp < nr_masters; tmp++)
- pdata->data_width[tmp] = BIT(arr[tmp] & 0x07);
- }
-
- /* If "data_width" and "data-width" both provided use the latter one */
- of_property_read_u32_array(np, "data-width", pdata->data_width, nr_masters);
-
- memset32(pdata->multi_block, 1, nr_channels);
- of_property_read_u32_array(np, "multi-block", pdata->multi_block, nr_channels);
-
- memset32(pdata->max_burst, DW_DMA_MAX_BURST, nr_channels);
- of_property_read_u32_array(np, "snps,max-burst-len", pdata->max_burst, nr_channels);
-
- of_property_read_u32(np, "snps,dma-protection-control", &pdata->protctl);
- if (pdata->protctl > CHAN_PROTCTL_MASK)
- return NULL;
-
- return pdata;
-}
-
void dw_dma_of_controller_register(struct dw_dma *dw)
{
struct device *dev = dw->dma.dev;
#define DRV_NAME "dw_dmac"
+struct dw_dma_platform_data *dw_dma_parse_properties(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dw_dma_platform_data *pdata;
+ u32 tmp, arr[DW_DMA_MAX_NR_MASTERS];
+ u32 nr_masters;
+ u32 nr_channels;
+
+ if (device_property_read_u32(dev, "dma-masters", &nr_masters))
+ return NULL;
+ if (nr_masters < 1 || nr_masters > DW_DMA_MAX_NR_MASTERS)
+ return NULL;
+
+ if (device_property_read_u32(dev, "dma-channels", &nr_channels))
+ return NULL;
+ if (nr_channels > DW_DMA_MAX_NR_CHANNELS)
+ return NULL;
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+
+ pdata->nr_masters = nr_masters;
+ pdata->nr_channels = nr_channels;
+
+ device_property_read_u32(dev, "chan_allocation_order", &pdata->chan_allocation_order);
+ device_property_read_u32(dev, "chan_priority", &pdata->chan_priority);
+
+ device_property_read_u32(dev, "block_size", &pdata->block_size);
+
+ /* Try deprecated property first */
+ if (!device_property_read_u32_array(dev, "data_width", arr, nr_masters)) {
+ for (tmp = 0; tmp < nr_masters; tmp++)
+ pdata->data_width[tmp] = BIT(arr[tmp] & 0x07);
+ }
+
+ /* If "data_width" and "data-width" both provided use the latter one */
+ device_property_read_u32_array(dev, "data-width", pdata->data_width, nr_masters);
+
+ memset32(pdata->multi_block, 1, nr_channels);
+ device_property_read_u32_array(dev, "multi-block", pdata->multi_block, nr_channels);
+
+ memset32(pdata->max_burst, DW_DMA_MAX_BURST, nr_channels);
+ device_property_read_u32_array(dev, "snps,max-burst-len", pdata->max_burst, nr_channels);
+
+ device_property_read_u32(dev, "snps,dma-protection-control", &pdata->protctl);
+ if (pdata->protctl > CHAN_PROTCTL_MASK)
+ return NULL;
+
+ return pdata;
+}
+
static int dw_probe(struct platform_device *pdev)
{
const struct dw_dma_chip_pdata *match;
struct dw_dma_chip_pdata *data;
struct dw_dma_chip *chip;
struct device *dev = &pdev->dev;
+ int irq_num;
int err;
match = device_get_match_data(dev);
if (!chip)
return -ENOMEM;
- chip->irq = platform_get_irq(pdev, 0);
- if (chip->irq < 0)
- return chip->irq;
+ irq_num = platform_irq_count(pdev);
+ if (!irq_num)
+ dev_err(&pdev->dev, "no irq found on device\n");
+
+ chip->irq = devm_kzalloc(dev, irq_num * sizeof(int), GFP_KERNEL);
+ chip->irq_num = irq_num;
+
+ while (irq_num--)
+ chip->irq[irq_num] = platform_get_irq(pdev, irq_num);
chip->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(chip->regs))
if (!data->pdata)
data->pdata = dev_get_platdata(dev);
if (!data->pdata)
- data->pdata = dw_dma_parse_dt(pdev);
+ data->pdata = dw_dma_parse_properties(pdev);
chip->dev = dev;
chip->id = pdev->id;
{ "INTL9C60", (kernel_ulong_t)&dw_dma_chip_pdata },
{ "80862286", (kernel_ulong_t)&dw_dma_chip_pdata },
{ "808622C0", (kernel_ulong_t)&dw_dma_chip_pdata },
+ { "BKLE0005", (kernel_ulong_t)&dw_dma_chip_pdata },
/* Elkhart Lake iDMA 32-bit (PSE DMA) */
{ "80864BB4", (kernel_ulong_t)&xbar_chip_pdata },
source "drivers/gpu/drm/i2c/Kconfig"
source "drivers/gpu/drm/arm/Kconfig"
+source "drivers/gpu/drm/baikal/Kconfig"
config DRM_RADEON
tristate "ATI Radeon"
obj-$(CONFIG_DRM_HYPERV) += hyperv/
obj-y += solomon/
obj-$(CONFIG_DRM_SPRD) += sprd/
+obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal/
--- /dev/null
+config DRM_BAIKAL_VDU
+ tristate "DRM Support for Baikal-M VDU"
+ depends on DRM
+ depends on ARM || ARM64 || COMPILE_TEST
+ depends on COMMON_CLK
+ select DRM_KMS_HELPER
+ select DRM_GEM_DMA_HELPER
+ select DRM_PANEL
+ select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
+ help
+ Choose this option for DRM support for the Baikal-M Video Display Unit (VDU).
+ If M is selected the module will be called baikal_vdu_drm.
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+baikal_vdu_drm-y += baikal_vdu_backlight.o \
+ baikal_vdu_crtc.o \
+ baikal_vdu_drv.o \
+ baikal_vdu_panel.o \
+ baikal_vdu_plane.o
+
+baikal_vdu_drm-$(CONFIG_DEBUG_FS) += baikal_vdu_debugfs.o
+
+obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal_vdu_drm.o
+obj-$(CONFIG_DRM_BAIKAL_HDMI) += baikal-hdmi.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Baikal Electronics BE-M1000 DesignWare HDMI 2.0 Tx PHY support driver
+ *
+ * Copyright (C) 2019-2022 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <drm/drm_modes.h>
+
+#include <drm/bridge/dw_hdmi.h>
+
+#include "baikal_vdu_drm.h"
+
+int fixed_clock = 0;
+int max_clock = 0;
+
+static const struct dw_hdmi_mpll_config baikal_hdmi_mpll_cfg[] = {
+ /* pixelclk opmode gmp */
+ { 44900000, { { 0x00b3, 0x0000 }, }, },
+ { 90000000, { { 0x0072, 0x0001 }, }, },
+ { 182750000, { { 0x0051, 0x0002 }, }, },
+ { 340000000, { { 0x0040, 0x0003 }, }, },
+ { 594000000, { { 0x1a40, 0x0003 }, }, },
+ { ~0UL, { { 0x0000, 0x0000 }, }, }
+};
+
+static const struct dw_hdmi_curr_ctrl baikal_hdmi_cur_ctr[] = {
+ /* pixelclk current */
+ { 44900000, { 0x0000, }, },
+ { 90000000, { 0x0008, }, },
+ { 182750000, { 0x001b, }, },
+ { 340000000, { 0x0036, }, },
+ { 594000000, { 0x003f, }, },
+ { ~0UL, { 0x0000, }, }
+};
+
+static const struct dw_hdmi_phy_config baikal_hdmi_phy_cfg[] = {
+ /* pixelclk symbol term vlev */
+ { 148250000, 0x8009, 0x0004, 0x0232},
+ { 218250000, 0x8009, 0x0004, 0x0230},
+ { 288000000, 0x8009, 0x0004, 0x0273},
+ { 340000000, 0x8029, 0x0004, 0x0273},
+ { 594000000, 0x8039, 0x0004, 0x014a},
+ { ~0UL, 0x0000, 0x0000, 0x0000}
+};
+
+static enum drm_mode_status
+baikal_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ if (mode->clock < 13500)
+ return MODE_CLOCK_LOW;
+ if (mode->clock >= 340000)
+ return MODE_CLOCK_HIGH;
+ if (fixed_clock && mode->clock != fixed_clock)
+ return MODE_BAD;
+ if (max_clock && mode->clock > max_clock)
+ return MODE_BAD;
+
+ return MODE_OK;
+}
+
+static struct dw_hdmi_plat_data baikal_dw_hdmi_plat_data = {
+ .mpll_cfg = baikal_hdmi_mpll_cfg,
+ .cur_ctr = baikal_hdmi_cur_ctr,
+ .phy_config = baikal_hdmi_phy_cfg,
+ .mode_valid = baikal_hdmi_mode_valid,
+};
+
+static int baikal_dw_hdmi_probe(struct platform_device *pdev)
+{
+ struct baikal_hdmi_bridge *priv;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, priv);
+
+ priv->hdmi = dw_hdmi_probe(pdev, &baikal_dw_hdmi_plat_data);
+ if (IS_ERR(priv->hdmi))
+ return PTR_ERR(priv->hdmi);
+
+ priv->bridge = dw_hdmi_get_bridge(priv->hdmi);
+ return 0;
+}
+
+static int baikal_dw_hdmi_remove(struct platform_device *pdev)
+{
+ struct baikal_hdmi_bridge *priv = platform_get_drvdata(pdev);
+
+ dw_hdmi_remove(priv->hdmi);
+ return 0;
+}
+
+static const struct of_device_id baikal_dw_hdmi_of_table[] = {
+ { .compatible = "baikal,hdmi" },
+ { /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, baikal_dw_hdmi_of_table);
+
+static struct platform_driver baikal_dw_hdmi_platform_driver = {
+ .probe = baikal_dw_hdmi_probe,
+ .remove = baikal_dw_hdmi_remove,
+ .driver = {
+ .name = "baikal-dw-hdmi",
+ .of_match_table = baikal_dw_hdmi_of_table,
+ },
+};
+
+module_param(fixed_clock, int, 0644);
+module_param(max_clock, int, 0644);
+
+module_platform_driver(baikal_dw_hdmi_platform_driver);
+
+MODULE_AUTHOR("Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal BE-M1000 SoC DesignWare HDMI 2.0 Tx + Gen2 PHY Driver");
+MODULE_LICENSE("GPL");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+/**
+ * baikal_vdu_backlight.c
+ * Implementation of backlight functions for
+ * Baikal Electronics BE-M1000 SoC's VDU
+ */
+
+#include <linux/clk.h>
+#include <linux/input.h>
+#include <linux/module.h>
+
+#include <drm/drm_atomic_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define BAIKAL_VDU_MIN_BRIGHTNESS 0
+#define BAIKAL_VDU_DEFAULT_BRIGHTNESS 50
+#define BAIKAL_VDU_BRIGHTNESS_STEP 5
+#define BAIKAL_VDU_DEFAULT_PWM_FREQ 10000
+
+static int baikal_vdu_backlight_update_status(struct backlight_device *bl_dev)
+{
+ struct baikal_vdu_private *priv = bl_get_data(bl_dev);
+ int brightness_on = 1;
+ int brightness = bl_dev->props.brightness;
+ u8 pwmdc;
+
+ if (bl_dev->props.power != FB_BLANK_UNBLANK ||
+ bl_dev->props.fb_blank != FB_BLANK_UNBLANK ||
+ bl_dev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) {
+ brightness_on = 0;
+ brightness = priv->min_brightness;
+ }
+
+ if (priv->enable_gpio)
+ gpiod_set_value_cansleep(priv->enable_gpio, brightness_on);
+
+ pwmdc = brightness ? ((brightness << 6) / 25 - 1) : 0;
+
+ writel(pwmdc, priv->regs + PWMDCR);
+
+ return 0;
+}
+
+static const struct backlight_ops baikal_vdu_backlight_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .update_status = baikal_vdu_backlight_update_status,
+};
+
+static void baikal_vdu_input_event(struct input_handle *handle,
+ unsigned int type, unsigned int code,
+ int value)
+{
+ struct baikal_vdu_private *priv = handle->private;
+ int brightness;
+
+ if (type != EV_KEY || value == 0)
+ return;
+
+ switch (code) {
+ case KEY_BRIGHTNESSDOWN:
+ brightness = priv->bl_dev->props.brightness -
+ priv->brightness_step;
+ if (brightness >= priv->min_brightness)
+ backlight_device_set_brightness(priv->bl_dev,
+ brightness);
+ break;
+
+ case KEY_BRIGHTNESSUP:
+ brightness = priv->bl_dev->props.brightness +
+ priv->brightness_step;
+ backlight_device_set_brightness(priv->bl_dev, brightness);
+ break;
+
+ case KEY_BRIGHTNESS_TOGGLE:
+ priv->brightness_on = !priv->brightness_on;
+ if (priv->brightness_on)
+ backlight_enable(priv->bl_dev);
+ else
+ backlight_disable(priv->bl_dev);
+ break;
+
+ default:
+ return;
+ }
+
+ backlight_force_update(priv->bl_dev, BACKLIGHT_UPDATE_HOTKEY);
+}
+
+static int baikal_vdu_input_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct input_handle *handle;
+ int ret;
+
+ handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+ if (!handle)
+ return -ENOMEM;
+
+ handle->private = handler->private;
+ handle->name = KBUILD_MODNAME;
+ handle->dev = dev;
+ handle->handler = handler;
+
+ ret = input_register_handle(handle);
+ if (ret)
+ goto err_free_handle;
+
+ ret = input_open_device(handle);
+ if (ret)
+ goto err_unregister_handle;
+
+ return 0;
+
+err_unregister_handle:
+ input_unregister_handle(handle);
+err_free_handle:
+ kfree(handle);
+ return ret;
+}
+
+static void baikal_vdu_input_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+ kfree(handle);
+}
+
+static const struct input_device_id baikal_vdu_input_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_KEY) },
+ },
+
+ { }, /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(input, baikal_vdu_input_ids);
+
+int baikal_vdu_backlight_create(struct drm_device *drm)
+{
+ struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(drm);
+ struct baikal_vdu_private *priv = &crossbar->lvds;
+ struct device *dev = drm->dev;
+ struct backlight_properties props;
+ struct input_handler *handler;
+ struct fwnode_handle *node;
+ u32 min_brightness = BAIKAL_VDU_MIN_BRIGHTNESS;
+ u32 dfl_brightness = BAIKAL_VDU_DEFAULT_BRIGHTNESS;
+ u32 brightness_step = BAIKAL_VDU_BRIGHTNESS_STEP;
+ u32 pwm_frequency = 0;
+ int ret = 0;
+ unsigned long rate;
+ unsigned int pwmfr = 0;
+
+ priv->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_ASIS);
+ if (IS_ERR(priv->enable_gpio)) {
+ dev_warn(dev, "failed to get ENABLE GPIO\n");
+ priv->enable_gpio = NULL;
+ }
+
+ if (priv->enable_gpio && gpiod_get_direction(priv->enable_gpio) != 0)
+ gpiod_direction_output(priv->enable_gpio, 1);
+
+ node = fwnode_get_named_child_node(dev->fwnode, is_of_node(dev->fwnode) ?
+ "backlight" : "BCKL");
+ if (!node)
+ return 0;
+
+ fwnode_property_read_u32(node, "min-brightness-level", &min_brightness);
+ fwnode_property_read_u32(node, "default-brightness-level", &dfl_brightness);
+ fwnode_property_read_u32(node, "brightness-level-step", &brightness_step);
+ fwnode_property_read_u32(node, "pwm-frequency", &pwm_frequency);
+
+ if (pwm_frequency == 0) {
+ dev_warn(dev, "using default PWM frequency %u\n",
+ BAIKAL_VDU_DEFAULT_PWM_FREQ);
+ pwm_frequency = BAIKAL_VDU_DEFAULT_PWM_FREQ;
+ }
+
+ memset(&props, 0, sizeof(props));
+ props.max_brightness = 100;
+ props.type = BACKLIGHT_RAW;
+ props.scale = BACKLIGHT_SCALE_LINEAR;
+
+ if (min_brightness > props.max_brightness) {
+ dev_warn(dev, "invalid min brightness level: %u, using %u\n",
+ min_brightness, props.max_brightness);
+ min_brightness = props.max_brightness;
+ }
+
+ if (dfl_brightness > props.max_brightness ||
+ dfl_brightness < min_brightness) {
+ dev_warn(dev,
+ "invalid default brightness level: %u, using %u\n",
+ dfl_brightness, props.max_brightness);
+ dfl_brightness = props.max_brightness;
+ }
+
+ priv->min_brightness = min_brightness;
+ priv->brightness_step = brightness_step;
+ priv->brightness_on = true;
+
+ props.brightness = dfl_brightness;
+ props.power = FB_BLANK_UNBLANK;
+
+ priv->bl_dev =
+ devm_backlight_device_register(dev, dev_name(dev), dev, priv,
+ &baikal_vdu_backlight_ops,
+ &props);
+ if (IS_ERR(priv->bl_dev)) {
+ dev_err(dev, "failed to register backlight device\n");
+ ret = PTR_ERR(priv->bl_dev);
+ priv->bl_dev = NULL;
+ goto out;
+ }
+
+ handler = devm_kzalloc(dev, sizeof(*handler), GFP_KERNEL);
+ if (!handler) {
+ dev_err(dev, "failed to allocate input handler\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ handler->private = priv;
+ handler->event = baikal_vdu_input_event;
+ handler->connect = baikal_vdu_input_connect;
+ handler->disconnect = baikal_vdu_input_disconnect;
+ handler->name = KBUILD_MODNAME;
+ handler->id_table = baikal_vdu_input_ids;
+
+ ret = input_register_handler(handler);
+ if (ret) {
+ dev_err(dev, "failed to register input handler\n");
+ goto out;
+ }
+
+ /* Hold PWM Clock Domain Reset, disable clocking */
+ writel(0, priv->regs + PWMFR);
+
+ rate = clk_get_rate(priv->clk);
+ pwmfr |= PWMFR_PWMFCD(rate / pwm_frequency - 1) | PWMFR_PWMFCI;
+ writel(pwmfr, priv->regs + PWMFR);
+
+ /* Release PWM Clock Domain Reset, enable clocking */
+ writel(pwmfr | PWMFR_PWMPCR | PWMFR_PWMFCE, priv->regs + PWMFR);
+
+ backlight_update_status(priv->bl_dev);
+out:
+ fwnode_handle_put(node);
+ return ret;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+/**
+ * baikal_vdu_crtc.c
+ * Implementation of the CRTC functions for Baikal Electronics BE-M1000 VDU driver
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/dma-buf.h>
+#include <linux/shmem_fs.h>
+#include <linux/version.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_vblank.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+irqreturn_t baikal_vdu_irq(int irq, void *data)
+{
+ struct baikal_vdu_private *priv = data;
+ irqreturn_t status = IRQ_NONE;
+ u32 raw_stat;
+ u32 irq_stat;
+
+ priv->counters[0]++;
+ irq_stat = readl(priv->regs + IVR);
+ raw_stat = readl(priv->regs + ISR);
+
+ if (raw_stat & INTR_UFU) {
+ priv->counters[4]++;
+ status = IRQ_HANDLED;
+ }
+
+ if (raw_stat & INTR_IFO) {
+ priv->counters[5]++;
+ status = IRQ_HANDLED;
+ }
+
+ if (raw_stat & INTR_OFU) {
+ priv->counters[6]++;
+ status = IRQ_HANDLED;
+ }
+
+ if (irq_stat & INTR_FER) {
+ priv->counters[11]++;
+ priv->counters[12] = readl(priv->regs + DBAR);
+ priv->counters[13] = readl(priv->regs + DCAR);
+ priv->counters[14] = readl(priv->regs + MRR);
+ status = IRQ_HANDLED;
+ }
+
+ priv->counters[3] |= raw_stat;
+
+ /* Clear all interrupts */
+ writel(raw_stat, priv->regs + ISR);
+
+ return status;
+}
+
+static void baikal_vdu_crtc_helper_mode_set_nofb(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+ const struct drm_display_mode *mode = &crtc->state->mode;
+ unsigned long rate;
+ unsigned int ppl, hsw, hfp, hbp;
+ unsigned int lpp, vsw, vfp, vbp;
+ unsigned int reg;
+ int ret = 0;
+
+ drm_mode_debug_printmodeline(mode);
+
+ rate = mode->clock * 1000;
+
+ if (rate != clk_get_rate(priv->clk)) {
+ DRM_DEV_DEBUG_DRIVER(dev->dev, "Requested pixel clock is %lu Hz\n", rate);
+
+ /* hold clock domain reset; disable clocking */
+ writel(0, priv->regs + PCTR);
+
+ if (__clk_is_enabled(priv->clk))
+ clk_disable_unprepare(priv->clk);
+ ret = clk_set_rate(priv->clk, rate);
+
+ if (ret >= 0) {
+ clk_prepare_enable(priv->clk);
+ if (!__clk_is_enabled(priv->clk))
+ ret = -1;
+ }
+
+ /* release clock domain reset; enable clocking */
+ reg = readl(priv->regs + PCTR);
+ reg |= PCTR_PCR + PCTR_PCI;
+ writel(reg, priv->regs + PCTR);
+ }
+
+ if (ret < 0)
+ DRM_ERROR("Cannot set desired pixel clock (%lu Hz)\n", rate);
+
+ ppl = mode->hdisplay / 16;
+ if (priv->index == CRTC_LVDS && priv-> num_lanes == 2) {
+ hsw = mode->hsync_end - mode->hsync_start;
+ hfp = mode->hsync_start - mode->hdisplay - 1;
+ } else {
+ hsw = mode->hsync_end - mode->hsync_start - 1;
+ hfp = mode->hsync_start - mode->hdisplay;
+ }
+ hbp = mode->htotal - mode->hsync_end;
+
+ lpp = mode->vdisplay;
+ vsw = mode->vsync_end - mode->vsync_start;
+ vfp = mode->vsync_start - mode->vdisplay;
+ vbp = mode->vtotal - mode->vsync_end;
+
+ writel((HTR_HFP(hfp) & HTR_HFP_MASK) |
+ (HTR_PPL(ppl) & HTR_PPL_MASK) |
+ (HTR_HBP(hbp) & HTR_HBP_MASK) |
+ (HTR_HSW(hsw) & HTR_HSW_MASK),
+ priv->regs + HTR);
+
+ if (mode->hdisplay > 4080 || ppl * 16 != mode->hdisplay)
+ writel((HPPLOR_HPPLO(mode->hdisplay) & HPPLOR_HPPLO_MASK) | HPPLOR_HPOE,
+ priv->regs + HPPLOR);
+
+ writel((VTR1_VSW(vsw) & VTR1_VSW_MASK) |
+ (VTR1_VFP(vfp) & VTR1_VFP_MASK) |
+ (VTR1_VBP(vbp) & VTR1_VBP_MASK),
+ priv->regs + VTR1);
+
+ writel(lpp & VTR2_LPP_MASK, priv->regs + VTR2);
+
+ writel((HVTER_VSWE(vsw >> VTR1_VSW_LSB_WIDTH) & HVTER_VSWE_MASK) |
+ (HVTER_HSWE(hsw >> HTR_HSW_LSB_WIDTH) & HVTER_HSWE_MASK) |
+ (HVTER_VBPE(vbp >> VTR1_VBP_LSB_WIDTH) & HVTER_VBPE_MASK) |
+ (HVTER_VFPE(vfp >> VTR1_VFP_LSB_WIDTH) & HVTER_VFPE_MASK) |
+ (HVTER_HBPE(hbp >> HTR_HBP_LSB_WIDTH) & HVTER_HBPE_MASK) |
+ (HVTER_HFPE(hfp >> HTR_HFP_LSB_WIDTH) & HVTER_HFPE_MASK),
+ priv->regs + HVTER);
+
+ /* Set polarities */
+ reg = readl(priv->regs + CR1);
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ reg |= CR1_HSP;
+ else
+ reg &= ~CR1_HSP;
+ reg &= ~CR1_VSP; // always set VSP to active high
+ reg |= CR1_DEP; // set DE to active high;
+ writel(reg, priv->regs + CR1);
+}
+
+static enum drm_mode_status baikal_vdu_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+ if (!priv->mode_override && (mode->hdisplay > 2560 ||
+ mode->vdisplay > 1440))
+ return MODE_BAD;
+ else
+ return MODE_OK;
+}
+
+static void baikal_vdu_crtc_helper_enable(struct drm_crtc *crtc,
+ struct drm_atomic_state *old_state)
+{
+ struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+ const char *data_mapping = NULL;
+ u32 cntl, gpio;
+
+ DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n");
+ clk_prepare_enable(priv->clk);
+
+ /* Set 16-word input FIFO watermark */
+ /* Enable and Power Up */
+ cntl = readl(priv->regs + CR1);
+ cntl &= ~(CR1_FDW_MASK | CR1_OPS_MASK);
+ cntl |= CR1_LCE | CR1_FDW_16_WORDS;
+
+ if (priv->index == CRTC_LVDS) {
+ if (priv->bridge->of_node) {
+ of_property_read_string(priv->bridge->of_node,
+ "data-mapping", &data_mapping);
+ } else {
+ struct fwnode_handle *fwnode;
+
+ fwnode = fwnode_find_reference(priv->bridge->dev->dev->fwnode,
+ "baikal,lvds-panel", 0);
+ if (!IS_ERR_OR_NULL(fwnode))
+ fwnode_property_read_string(fwnode, "data-mapping",
+ &data_mapping);
+ }
+
+ if (!data_mapping) {
+ cntl |= CR1_OPS_LCD18;
+ } else if (!strncmp(data_mapping, "vesa-24", 7))
+ cntl |= CR1_OPS_LCD24;
+ else if (!strncmp(data_mapping, "jeida-18", 8))
+ cntl |= CR1_OPS_LCD18;
+ else {
+ dev_warn(crtc->dev->dev, "%s data mapping is not supported, vesa-24 is set\n", data_mapping);
+ cntl |= CR1_OPS_LCD24;
+ }
+ gpio = GPIOR_UHD_ENB;
+ if (priv->num_lanes == 4)
+ gpio |= GPIOR_UHD_QUAD_PORT;
+ else if (priv->num_lanes == 2)
+ gpio |= GPIOR_UHD_DUAL_PORT;
+ else
+ gpio |= GPIOR_UHD_SNGL_PORT;
+ writel(gpio, priv->regs + GPIOR);
+ } else
+ cntl |= CR1_OPS_LCD24;
+ writel(cntl, priv->regs + CR1);
+
+ writel(0x3ffff, priv->regs + ISR);
+ writel(INTR_FER, priv->regs + IMR);
+}
+
+void baikal_vdu_crtc_helper_disable(struct drm_crtc *crtc)
+{
+ struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+
+ writel(0x3ffff, priv->regs + ISR);
+ writel(0, priv->regs + IMR);
+
+ /* Disable clock */
+ DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "disabling pixel clock\n");
+ clk_disable_unprepare(priv->clk);
+}
+
+static void baikal_vdu_crtc_helper_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_state *old_state)
+{
+ struct drm_pending_vblank_event *event = crtc->state->event;
+
+ if (event) {
+ crtc->state->event = NULL;
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0)
+ drm_crtc_arm_vblank_event(crtc, event);
+ else
+ drm_crtc_send_vblank_event(crtc, event);
+ spin_unlock_irq(&crtc->dev->event_lock);
+ }
+}
+
+const struct drm_crtc_funcs crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = drm_atomic_helper_crtc_reset,
+ .destroy = drm_crtc_cleanup,
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+const struct drm_crtc_helper_funcs crtc_helper_funcs = {
+ .mode_set_nofb = baikal_vdu_crtc_helper_mode_set_nofb,
+ .mode_valid = baikal_vdu_mode_valid,
+ .atomic_flush = baikal_vdu_crtc_helper_atomic_flush,
+ .disable = baikal_vdu_crtc_helper_disable,
+ .atomic_enable = baikal_vdu_crtc_helper_enable,
+};
+
+int baikal_vdu_crtc_create(struct baikal_vdu_private *priv)
+{
+ struct drm_device *dev = priv->drm;
+ struct drm_crtc *crtc = &priv->crtc;
+
+ drm_crtc_init_with_planes(dev, crtc,
+ &priv->primary, NULL,
+ &crtc_funcs, "primary");
+ drm_crtc_helper_add(crtc, &crtc_helper_funcs);
+
+ DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n");
+ clk_prepare_enable(priv->clk);
+
+ return 0;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#include <linux/seq_file.h>
+#include <linux/device.h>
+#include <drm/drm_debugfs.h>
+#include <drm/drm_device.h>
+#include <drm/drm_file.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define REGDEF(reg) { reg, #reg }
+static const struct {
+ u32 reg;
+ const char *name;
+} baikal_vdu_reg_defs[] = {
+ REGDEF(CR1),
+ REGDEF(HTR),
+ REGDEF(VTR1),
+ REGDEF(VTR2),
+ REGDEF(PCTR),
+ REGDEF(ISR),
+ REGDEF(IMR),
+ REGDEF(IVR),
+ REGDEF(ISCR),
+ REGDEF(DBAR),
+ REGDEF(DCAR),
+ REGDEF(DEAR),
+ REGDEF(PWMFR),
+ REGDEF(PWMDCR),
+ REGDEF(HVTER),
+ REGDEF(HPPLOR),
+ REGDEF(GPIOR),
+ REGDEF(MRR),
+};
+
+#define REGS_HDMI "regs_hdmi"
+#define REGS_LVDS "regs_lvds"
+
+int baikal_vdu_debugfs_regs(struct seq_file *m, void *unused)
+{
+ struct drm_info_node *node = (struct drm_info_node *)m->private;
+ struct drm_device *dev = node->minor->dev;
+ struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(dev);
+ char *filename = m->file->f_path.dentry->d_iname;
+ struct baikal_vdu_private *priv = NULL;
+ int i;
+
+ if (!strcmp(REGS_HDMI, filename))
+ priv = &crossbar->hdmi;
+ if (!strcmp(REGS_LVDS, filename))
+ priv = &crossbar->lvds;
+
+ if (!priv || !priv->regs)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(baikal_vdu_reg_defs); i++) {
+ seq_printf(m, "%s (0x%04x): 0x%08x\n",
+ baikal_vdu_reg_defs[i].name, baikal_vdu_reg_defs[i].reg,
+ readl(priv->regs + baikal_vdu_reg_defs[i].reg));
+ }
+ for (i = 0; i < ARRAY_SIZE(priv->counters); i++) {
+ seq_printf(m, "COUNTER[%d]: 0x%08x\n", i, priv->counters[i]);
+ }
+
+ return 0;
+}
+
+static const struct drm_info_list baikal_vdu_hdmi_debugfs_list[] = {
+ {REGS_HDMI, baikal_vdu_debugfs_regs, 0},
+};
+
+static const struct drm_info_list baikal_vdu_lvds_debugfs_list[] = {
+ {REGS_LVDS, baikal_vdu_debugfs_regs, 0},
+};
+
+void baikal_vdu_hdmi_debugfs_init(struct drm_minor *minor)
+{
+ drm_debugfs_create_files(baikal_vdu_hdmi_debugfs_list,
+ ARRAY_SIZE(baikal_vdu_hdmi_debugfs_list),
+ minor->debugfs_root, minor);
+}
+
+void baikal_vdu_lvds_debugfs_init(struct drm_minor *minor)
+{
+ drm_debugfs_create_files(baikal_vdu_lvds_debugfs_list,
+ ARRAY_SIZE(baikal_vdu_lvds_debugfs_list),
+ minor->debugfs_root, minor);
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#ifndef __BAIKAL_VDU_DRM_H__
+#define __BAIKAL_VDU_DRM_H__
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include <drm/bridge/dw_hdmi.h>
+
+#include <linux/workqueue.h>
+#include <linux/gpio.h>
+#include <linux/backlight.h>
+
+#define CRTC_HDMI 0
+#define CRTC_LVDS 1
+
+#define crtc_to_baikal_vdu(x) \
+ container_of(x, struct baikal_vdu_private, crtc)
+#define bridge_to_baikal_lvds_bridge(x) \
+ container_of(x, struct baikal_lvds_bridge, bridge)
+#define connector_to_baikal_lvds_bridge(x) \
+ container_of(x, struct baikal_lvds_bridge, connector)
+#define drm_to_baikal_vdu_crossbar(x) \
+ container_of(x, struct baikal_vdu_crossbar, drm)
+
+#define VDU_NAME_LEN 5
+
+struct baikal_vdu_private {
+ struct drm_device *drm;
+ struct drm_crtc crtc;
+ struct drm_encoder encoder;
+ struct drm_bridge *bridge;
+ struct drm_plane primary;
+ void *regs;
+ int irq;
+ struct clk *clk;
+ spinlock_t lock;
+ u32 counters[20];
+ int mode_override;
+ int index;
+ char name[VDU_NAME_LEN];
+ char irq_name[VDU_NAME_LEN + 4];
+ char pclk_name[VDU_NAME_LEN + 5];
+ char regs_name[VDU_NAME_LEN + 5];
+ int num_lanes;
+ int data_mapping;
+ int off;
+ int ready;
+
+ /* backlight */
+ struct gpio_desc *enable_gpio;
+ struct backlight_device *bl_dev;
+ int min_brightness;
+ int brightness_step;
+ bool brightness_on;
+};
+
+struct baikal_vdu_crossbar {
+ struct drm_device drm;
+ struct baikal_vdu_private hdmi;
+ struct baikal_vdu_private lvds;
+};
+
+struct baikal_lvds_bridge {
+ struct baikal_vdu_private *vdu;
+ struct drm_bridge bridge;
+ struct drm_connector connector;
+ struct drm_panel *panel;
+ u32 connector_type;
+};
+
+struct baikal_hdmi_bridge {
+ struct dw_hdmi *hdmi;
+ struct drm_bridge *bridge;
+};
+
+/* Generic functions */
+inline void baikal_vdu_switch_on(struct baikal_vdu_private *priv);
+
+inline void baikal_vdu_switch_off(struct baikal_vdu_private *priv);
+
+/* Bridge functions */
+bool bridge_is_baikal_lvds_bridge(const struct drm_bridge *bridge);
+
+struct drm_bridge *devm_baikal_lvds_bridge_add(struct device *dev,
+ struct drm_panel *panel,
+ u32 connector_type);
+
+/* CRTC Functions */
+int baikal_vdu_crtc_create(struct baikal_vdu_private *priv);
+
+irqreturn_t baikal_vdu_irq(int irq, void *data);
+
+int baikal_vdu_primary_plane_init(struct baikal_vdu_private *priv);
+
+/* Backlight Functions */
+int baikal_vdu_backlight_create(struct drm_device *drm);
+
+/* Debugfs functions */
+void baikal_vdu_hdmi_debugfs_init(struct drm_minor *minor);
+
+void baikal_vdu_lvds_debugfs_init(struct drm_minor *minor);
+
+#endif /* __BAIKAL_VDU_DRM_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ * Bugfixes by Alexey Sheplyakov <asheplyakov@altlinux.org>
+ *
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_aperture.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define DRIVER_NAME "baikal-vdu"
+#define DRIVER_DESC "Baikal VDU DRM driver"
+#define DRIVER_DATE "20230221"
+
+#define BAIKAL_SMC_LOG_DISABLE 0x82000200
+#define IFIFO_SIZE 16384
+#define UHD_FIFO_SIZE 16384
+
+int mode_override = 0;
+int hdmi_off = 0;
+int lvds_off = 0;
+
+static struct drm_mode_config_funcs mode_config_funcs = {
+ .fb_create = drm_gem_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+const struct drm_encoder_funcs baikal_vdu_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static struct drm_bridge *devm_baikal_get_bridge(struct device *dev,
+ u32 port, u32 endpoint)
+{
+ struct baikal_hdmi_bridge *priv_hdmi;
+ struct device *tmp;
+ struct drm_bridge *bridge;
+ struct drm_panel *panel;
+ struct fwnode_handle *fwnode;
+ int ret = 0;
+
+ if (is_of_node(dev->fwnode)) {
+ ret = drm_of_find_panel_or_bridge(to_of_node(dev->fwnode), port,
+ endpoint, &panel, &bridge);
+ } else {
+ if (port == CRTC_HDMI) {
+ fwnode = fwnode_find_reference(dev->fwnode,
+ "baikal,hdmi-bridge", 0);
+ if (IS_ERR_OR_NULL(fwnode))
+ return ERR_PTR(-ENODEV);
+
+ tmp = bus_find_device_by_fwnode(&platform_bus_type, fwnode);
+ if (IS_ERR_OR_NULL(tmp))
+ return ERR_PTR(-ENODEV);
+
+ priv_hdmi = dev_get_drvdata(tmp);
+ if (!priv_hdmi)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ bridge = priv_hdmi->bridge;
+ panel = NULL;
+ } else if (port == CRTC_LVDS) {
+ fwnode = fwnode_find_reference(dev->fwnode,
+ "baikal,lvds-panel", 0);
+ if (IS_ERR_OR_NULL(fwnode))
+ return ERR_PTR(-ENODEV);
+
+ panel = fwnode_drm_find_panel(fwnode);
+ if (IS_ERR(panel))
+ return ERR_CAST(panel);
+ } else {
+ return ERR_PTR(-ENODEV);
+ }
+ }
+
+ if (ret)
+ return ERR_PTR(ret);
+
+ if (panel) {
+ bridge = devm_baikal_lvds_bridge_add(dev, panel, DRM_MODE_CONNECTOR_LVDS);
+ }
+
+ return bridge;
+}
+
+static int baikal_vdu_remove_efifb(struct drm_device *dev)
+{
+ int err;
+ err = drm_aperture_remove_framebuffers("baikal-vdudrmfb", false);
+ if (err)
+ dev_warn(dev->dev, "failed to remove firmware framebuffer\n");
+ return err;
+}
+
+inline void baikal_vdu_switch_off(struct baikal_vdu_private *priv)
+{
+ u32 cntl = readl(priv->regs + CR1);
+ cntl &= ~CR1_LCE;
+ writel(cntl, priv->regs + CR1);
+}
+
+inline void baikal_vdu_switch_on(struct baikal_vdu_private *priv)
+{
+ u32 cntl = readl(priv->regs + CR1);
+ cntl |= CR1_LCE;
+ writel(cntl, priv->regs + CR1);
+}
+
+static int baikal_vdu_modeset_init(struct baikal_vdu_private *priv)
+{
+ struct drm_device *dev = priv->drm;
+ struct drm_encoder *encoder;
+ int ret = 0;
+ int index;
+
+ if (priv == NULL)
+ return -EINVAL;
+ index = priv->index;
+
+ ret = baikal_vdu_primary_plane_init(priv);
+ if (ret != 0) {
+ dev_err(dev->dev, "%s: failed to init primary plane\n", priv->name);
+ return ret;
+ }
+
+ ret = baikal_vdu_crtc_create(priv);
+ if (ret) {
+ dev_err(dev->dev, "%s: failed to create CRTC\n", priv->name);
+ return ret;
+ }
+
+ if (priv->bridge) {
+ encoder = &priv->encoder;
+ ret = drm_encoder_init(dev, encoder, &baikal_vdu_encoder_funcs,
+ DRM_MODE_ENCODER_NONE, NULL);
+ if (ret) {
+ dev_err(dev->dev, "%s: failed to create DRM encoder\n", priv->name);
+ return ret;
+ }
+ encoder->crtc = &priv->crtc;
+ encoder->possible_crtcs = BIT(drm_crtc_index(encoder->crtc));
+ priv->bridge->encoder = encoder;
+ ret = drm_bridge_attach(encoder, priv->bridge, NULL, 0);
+ if (ret) {
+ dev_err(dev->dev, "%s: failed to attach DRM bridge (%d)\n", priv->name, ret);
+ return ret;
+ }
+ } else {
+ dev_err(dev->dev, "%s: no bridge or panel attached\n", priv->name);
+ return -ENODEV;
+ }
+
+ priv->mode_override = mode_override;
+
+ return ret;
+}
+
+static int baikal_dumb_create(struct drm_file *file, struct drm_device *dev,
+ struct drm_mode_create_dumb *args)
+{
+ args->pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+ args->size = args->pitch * args->height + IFIFO_SIZE + UHD_FIFO_SIZE;
+
+ return drm_gem_dma_dumb_create_internal(file, dev, args);
+}
+
+DEFINE_DRM_GEM_DMA_FOPS(baikal_drm_fops);
+
+static struct drm_driver vdu_drm_driver = {
+ .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+ DRM_GEM_DMA_DRIVER_OPS,
+ .lastclose = drm_fb_helper_lastclose,
+ .ioctls = NULL,
+ .fops = &baikal_drm_fops,
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .date = DRIVER_DATE,
+ .major = 2,
+ .minor = 0,
+ .patchlevel = 0,
+ .dumb_create = baikal_dumb_create,
+ .dumb_map_offset = drm_gem_dumb_map_offset,
+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_import = drm_gem_prime_import,
+ .gem_prime_import_sg_table = drm_gem_dma_prime_import_sg_table,
+ .gem_prime_mmap = drm_gem_prime_mmap,
+};
+
+static int baikal_vdu_allocate_resources(struct platform_device *pdev,
+ struct baikal_vdu_private *priv)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *mem;
+ int ret;
+
+ mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, priv->regs_name);
+ if (!mem)
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, priv->index);
+
+ if (!mem) {
+ dev_err(dev, "%s %s: no MMIO resource specified\n", __func__, priv->name);
+ return -EINVAL;
+ }
+
+ priv->regs = devm_ioremap_resource(dev, mem);
+ if (IS_ERR(priv->regs)) {
+ dev_err(dev, "%s %s: MMIO allocation failed\n", __func__, priv->name);
+ return PTR_ERR(priv->regs);
+ }
+
+ if (priv->off) {
+ baikal_vdu_switch_off(priv);
+ return -EPERM;
+ } else {
+ ret = baikal_vdu_modeset_init(priv);
+ if (ret) {
+ dev_err(dev, "%s %s: failed to init modeset\n", __func__, priv->name);
+ if (ret == -ENODEV) {
+ baikal_vdu_switch_off(priv);
+ }
+ return ret;
+ } else {
+ writel(MRR_MAX_VALUE, priv->regs + MRR);
+ spin_lock_init(&priv->lock);
+ return 0;
+ }
+ }
+}
+
+static int baikal_vdu_allocate_irq(struct platform_device *pdev,
+ struct baikal_vdu_private *priv)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ priv->irq = fwnode_irq_get_byname(dev->fwnode, priv->irq_name);
+ if (priv->irq < 0) {
+ dev_err(dev, "%s %s: no IRQ resource specified\n", __func__, priv->name);
+ return -EINVAL;
+ }
+
+ /* turn off interrupts before requesting the irq */
+ writel(0, priv->regs + IMR);
+ ret = request_irq(priv->irq, baikal_vdu_irq, IRQF_SHARED, dev->driver->name, priv);
+ if (ret != 0)
+ dev_err(dev, "%s %s: IRQ %d allocation failed\n", __func__, priv->name, priv->irq);
+ return ret;
+}
+
+static void baikal_vdu_free_irq(struct baikal_vdu_private *priv)
+{
+ writel(0, priv->regs + IMR);
+ writel(0x3ffff, priv->regs + ISR);
+ free_irq(priv->irq, priv->drm->dev);
+}
+
+static int baikal_vdu_allocate_clk(struct baikal_vdu_private *priv)
+{
+ priv->clk = clk_get(priv->drm->dev, priv->pclk_name);
+ if (IS_ERR(priv->clk)) {
+ dev_err(priv->drm->dev, "%s: unable to get %s, err %ld\n", priv->name, priv->pclk_name, PTR_ERR(priv->clk));
+ return PTR_ERR(priv->clk);
+ } else
+ return 0;
+}
+
+static void baikal_vdu_set_name(struct baikal_vdu_private *priv, int index, const char *name)
+{
+ char *c;
+ int len = sizeof(priv->name) / sizeof(priv->name[0]) - 1;
+ strncpy(priv->name, name, len);
+ for (c = priv->name; c < priv->name + len && *c; c++) {
+ *c = toupper(*c);
+ }
+ sprintf(priv->irq_name, "%s_irq", name);
+ sprintf(priv->pclk_name, "%s_pclk", name);
+ sprintf(priv->regs_name, "%s_regs", name);
+ priv->index = index;
+}
+
+static int baikal_vdu_bridge_init(struct baikal_vdu_private *priv, struct drm_device *drm) {
+ int ret = 0;
+ struct device *dev;
+ struct drm_bridge *bridge;
+ if (!priv || !drm)
+ return -ENODEV;
+ priv->drm = drm;
+ dev = drm->dev;
+ bridge = devm_baikal_get_bridge(dev, priv->index, 0);
+ if (IS_ERR(bridge)) {
+ ret = PTR_ERR(bridge);
+ if (ret == -EPROBE_DEFER) {
+ dev_info(dev, "%s: bridge probe deferred\n", priv->name);
+ }
+ priv->bridge = NULL;
+ } else {
+ priv->bridge = bridge;
+ }
+ return ret;
+}
+
+static int baikal_vdu_resources_init(struct platform_device *pdev, struct baikal_vdu_private *priv)
+{
+ int ret = baikal_vdu_allocate_resources(pdev, priv);
+ if (ret)
+ return 0;
+ ret = baikal_vdu_allocate_irq(pdev, priv);
+ if (ret)
+ return 0;
+ ret = baikal_vdu_allocate_clk(priv);
+ if (ret) {
+ baikal_vdu_free_irq(priv);
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+static int baikal_vdu_drm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct baikal_lvds_bridge *panel_bridge;
+ struct baikal_vdu_crossbar *crossbar;
+ struct baikal_vdu_private *hdmi;
+ struct baikal_vdu_private *lvds;
+ struct drm_device *drm;
+ struct drm_mode_config *mode_config;
+ struct arm_smccc_res res;
+ int ret;
+
+ crossbar = devm_drm_dev_alloc(dev, &vdu_drm_driver,
+ struct baikal_vdu_crossbar, drm);
+ if (IS_ERR(crossbar))
+ return PTR_ERR(crossbar);
+
+ drm = &crossbar->drm;
+ platform_set_drvdata(pdev, drm);
+ hdmi = &crossbar->hdmi;
+ baikal_vdu_set_name(hdmi, CRTC_HDMI, "hdmi");
+ lvds = &crossbar->lvds;
+ baikal_vdu_set_name(lvds, CRTC_LVDS, "lvds");
+
+ ret = baikal_vdu_bridge_init(hdmi, drm);
+ if (ret == -EPROBE_DEFER) {
+ goto out_drm;
+ }
+
+ ret = device_property_read_u32(&pdev->dev, "lvds-lanes",
+ &lvds->num_lanes);
+ if (ret)
+ lvds->num_lanes = 0;
+ if (lvds->num_lanes) {
+ ret = baikal_vdu_bridge_init(lvds, drm);
+ if (ret == -EPROBE_DEFER) {
+ goto out_drm;
+ }
+ }
+
+ drm_mode_config_init(drm);
+ mode_config = &drm->mode_config;
+ mode_config->funcs = &mode_config_funcs;
+ mode_config->min_width = 1;
+ mode_config->max_width = 8192;
+ mode_config->min_height = 1;
+ mode_config->max_height = 8192;
+
+ hdmi->off = hdmi_off;
+ hdmi->ready = baikal_vdu_resources_init(pdev, hdmi);
+ if (lvds->num_lanes) {
+ lvds->off = lvds_off;
+ lvds->ready = baikal_vdu_resources_init(pdev, lvds);
+ } else {
+ lvds->ready = 0;
+ lvds->bridge = NULL;
+ dev_info(dev, "No 'lvds-lanes' property found\n");
+ }
+ if (lvds->ready) {
+ ret = baikal_vdu_backlight_create(drm);
+ if (ret) {
+ dev_err(dev, "LVDS: failed to create backlight\n");
+ }
+ if (bridge_is_baikal_lvds_bridge(lvds->bridge)) {
+ panel_bridge = bridge_to_baikal_lvds_bridge(lvds->bridge);
+ panel_bridge->vdu = lvds;
+ } else {
+ // TODO implement handling of third-party bridges
+ }
+ }
+ if (hdmi->bridge) {
+ // TODO implement functions specific to HDMI bridge
+ }
+
+ hdmi->ready = hdmi->ready & !hdmi->off;
+ lvds->ready = lvds->ready & !lvds->off;
+ dev_info(dev, "%s output %s\n", hdmi->name, hdmi->ready ? "enabled" : "disabled");
+ dev_info(dev, "%s output %s\n", lvds->name, lvds->ready ? "enabled" : "disabled");
+ baikal_vdu_remove_efifb(drm);
+
+ if (hdmi->ready || lvds->ready) {
+ /* Disable SCP debug output as it may affect VDU performance */
+ arm_smccc_smc(BAIKAL_SMC_LOG_DISABLE, 0, 0, 0, 0, 0, 0, 0, &res);
+
+ drm_mode_config_reset(drm);
+ drm_kms_helper_poll_init(drm);
+ ret = drm_dev_register(drm, 0);
+ if (ret) {
+ dev_err(dev, "failed to register DRM device\n");
+ goto out_config;
+ }
+ drm_fbdev_generic_setup(drm, 32);
+#if defined(CONFIG_DEBUG_FS)
+ if (hdmi->ready)
+ baikal_vdu_hdmi_debugfs_init(drm->primary);
+ if (lvds->ready)
+ baikal_vdu_lvds_debugfs_init(drm->primary);
+#endif
+ return 0;
+ } else {
+ dev_err(dev, "no active outputs configured\n");
+ ret = -ENODEV;
+ }
+out_config:
+ drm_mode_config_cleanup(drm);
+out_drm:
+ dev_err(dev, "failed to probe: %d\n", ret);
+ return ret;
+}
+
+static int baikal_vdu_drm_remove(struct platform_device *pdev)
+{
+ struct drm_device *drm = platform_get_drvdata(pdev);
+ struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(drm);
+
+ drm_dev_unregister(drm);
+ drm_mode_config_cleanup(drm);
+ if (crossbar->hdmi.irq)
+ free_irq(crossbar->hdmi.irq, drm->dev);
+ if (crossbar->lvds.irq)
+ free_irq(crossbar->lvds.irq, drm->dev);
+
+ return 0;
+}
+
+static const struct of_device_id baikal_vdu_of_match[] = {
+ { .compatible = "baikal,vdu" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, baikal_vdu_of_match);
+
+static struct platform_driver baikal_vdu_platform_driver = {
+ .probe = baikal_vdu_drm_probe,
+ .remove = baikal_vdu_drm_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = baikal_vdu_of_match,
+ },
+};
+
+module_param(mode_override, int, 0644);
+module_param(hdmi_off, int, 0644);
+module_param(lvds_off, int, 0644);
+
+module_platform_driver(baikal_vdu_platform_driver);
+
+MODULE_AUTHOR("Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal Electronics BE-M1000 Video Display Unit (VDU) DRM Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_SOFTDEP("pre: baikal_hdmi");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+static void baikal_lvds_connector_force(struct drm_connector *connector)
+{
+ struct baikal_lvds_bridge *bridge = connector_to_baikal_lvds_bridge(connector);
+ struct baikal_vdu_private *priv = bridge->vdu;
+ u32 cntl = readl(priv->regs + CR1);
+ if (connector->force == DRM_FORCE_OFF)
+ cntl &= ~CR1_LCE;
+ else
+ cntl |= CR1_LCE;
+ writel(cntl, priv->regs + CR1);
+}
+
+static int baikal_lvds_connector_get_modes(struct drm_connector *connector)
+{
+ struct baikal_lvds_bridge *panel_bridge =
+ connector_to_baikal_lvds_bridge(connector);
+
+ return drm_panel_get_modes(panel_bridge->panel, connector);
+}
+
+static const struct drm_connector_helper_funcs
+baikal_lvds_bridge_connector_helper_funcs = {
+ .get_modes = baikal_lvds_connector_get_modes,
+};
+
+static const struct drm_connector_funcs baikal_lvds_bridge_connector_funcs = {
+ .reset = drm_atomic_helper_connector_reset,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+ .force = baikal_lvds_connector_force,
+};
+
+static int baikal_lvds_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags)
+{
+ struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+ struct drm_connector *connector = &panel_bridge->connector;
+ int ret;
+
+ if (!bridge->encoder) {
+ DRM_ERROR("Missing encoder\n");
+ return -ENODEV;
+ }
+
+ drm_connector_helper_add(connector,
+ &baikal_lvds_bridge_connector_helper_funcs);
+
+ ret = drm_connector_init(bridge->dev, connector,
+ &baikal_lvds_bridge_connector_funcs,
+ panel_bridge->connector_type);
+ if (ret) {
+ DRM_ERROR("Failed to initialize connector\n");
+ return ret;
+ }
+
+ ret = drm_connector_attach_encoder(&panel_bridge->connector,
+ bridge->encoder);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void baikal_lvds_bridge_pre_enable(struct drm_bridge *bridge)
+{
+ struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+ baikal_vdu_switch_on(panel_bridge->vdu);
+ drm_panel_prepare(panel_bridge->panel);
+}
+
+static void baikal_lvds_bridge_enable(struct drm_bridge *bridge)
+{
+ struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+ drm_panel_enable(panel_bridge->panel);
+}
+
+static void baikal_lvds_bridge_disable(struct drm_bridge *bridge)
+{
+ struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+ drm_panel_disable(panel_bridge->panel);
+}
+
+static void baikal_lvds_bridge_post_disable(struct drm_bridge *bridge)
+{
+ struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+ drm_panel_unprepare(panel_bridge->panel);
+ baikal_vdu_switch_off(panel_bridge->vdu);
+}
+
+static const struct drm_bridge_funcs baikal_lvds_bridge_funcs = {
+ .attach = baikal_lvds_bridge_attach,
+ .pre_enable = baikal_lvds_bridge_pre_enable,
+ .enable = baikal_lvds_bridge_enable,
+ .disable = baikal_lvds_bridge_disable,
+ .post_disable = baikal_lvds_bridge_post_disable,
+};
+
+static struct drm_bridge *baikal_lvds_bridge_add(struct drm_panel *panel,
+ u32 connector_type)
+{
+ struct baikal_lvds_bridge *panel_bridge;
+
+ if (!panel)
+ return ERR_PTR(-EINVAL);
+
+ panel_bridge = devm_kzalloc(panel->dev, sizeof(*panel_bridge),
+ GFP_KERNEL);
+ if (!panel_bridge)
+ return ERR_PTR(-ENOMEM);
+
+ panel_bridge->connector_type = connector_type;
+ panel_bridge->panel = panel;
+
+ panel_bridge->bridge.funcs = &baikal_lvds_bridge_funcs;
+#ifdef CONFIG_OF
+ panel_bridge->bridge.of_node = panel->dev->of_node;
+#endif
+
+ drm_bridge_add(&panel_bridge->bridge);
+
+ return &panel_bridge->bridge;
+}
+
+static void baikal_lvds_bridge_remove(struct drm_bridge *bridge)
+{
+ struct baikal_lvds_bridge *panel_bridge;
+
+ if (!bridge)
+ return;
+
+ if (bridge->funcs != &baikal_lvds_bridge_funcs)
+ return;
+
+ panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+ drm_bridge_remove(bridge);
+ devm_kfree(panel_bridge->panel->dev, bridge);
+}
+
+static void devm_baikal_lvds_bridge_release(struct device *dev, void *res)
+{
+ struct drm_bridge **bridge = res;
+
+ baikal_lvds_bridge_remove(*bridge);
+}
+
+struct drm_bridge *devm_baikal_lvds_bridge_add(struct device *dev,
+ struct drm_panel *panel,
+ u32 connector_type)
+{
+ struct drm_bridge **ptr, *bridge;
+
+ ptr = devres_alloc(devm_baikal_lvds_bridge_release, sizeof(*ptr),
+ GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ bridge = baikal_lvds_bridge_add(panel, connector_type);
+ if (!IS_ERR(bridge)) {
+ *ptr = bridge;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return bridge;
+}
+
+bool bridge_is_baikal_lvds_bridge(const struct drm_bridge *bridge)
+{
+ return bridge && (bridge->funcs == &baikal_lvds_bridge_funcs);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/of_graph.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+static void baikal_vdu_primary_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_state *old_state)
+{
+ struct baikal_vdu_private *priv;
+ struct drm_plane_state *state = plane->state;
+ struct drm_crtc *crtc = state->crtc;
+ struct drm_framebuffer *fb = state->fb;
+ uint32_t cntl;
+ uint32_t addr;
+ unsigned long flags;
+
+ if (!fb)
+ return;
+
+ priv = crtc_to_baikal_vdu(crtc);
+ addr = drm_fb_dma_get_gem_addr(fb, state, 0);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ writel(addr, priv->regs + DBAR);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ priv->counters[16]++;
+
+ cntl = readl(priv->regs + CR1);
+ cntl &= ~CR1_BPP_MASK;
+
+ /* Note that the the hardware's format reader takes 'r' from
+ * the low bit, while DRM formats list channels from high bit
+ * to low bit as you read left to right.
+ */
+ switch (fb->format->format) {
+ case DRM_FORMAT_BGR888:
+ cntl |= CR1_BPP24 | CR1_FBP | CR1_BGR;
+ break;
+ case DRM_FORMAT_RGB888:
+ cntl |= CR1_BPP24 | CR1_FBP;
+ break;
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XBGR8888:
+ cntl |= CR1_BPP24 | CR1_BGR;
+ break;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ cntl |= CR1_BPP24;
+ break;
+ case DRM_FORMAT_BGR565:
+ cntl |= CR1_BPP16_565 | CR1_BGR;
+ break;
+ case DRM_FORMAT_RGB565:
+ cntl |= CR1_BPP16_565;
+ break;
+ case DRM_FORMAT_ABGR1555:
+ case DRM_FORMAT_XBGR1555:
+ cntl |= CR1_BPP16_555 | CR1_BGR;
+ break;
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_XRGB1555:
+ cntl |= CR1_BPP16_555;
+ break;
+ default:
+ WARN_ONCE(true, "Unknown FB format 0x%08x, set XRGB8888 instead\n",
+ fb->format->format);
+ cntl |= CR1_BPP24;
+ break;
+ }
+
+ writel(cntl, priv->regs + CR1);
+}
+
+static const struct drm_plane_helper_funcs baikal_vdu_primary_plane_helper_funcs = {
+ .atomic_update = baikal_vdu_primary_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs baikal_vdu_primary_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .reset = drm_atomic_helper_plane_reset,
+ .destroy = drm_plane_cleanup,
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+int baikal_vdu_primary_plane_init(struct baikal_vdu_private *priv)
+{
+ struct drm_device *drm = priv->drm;
+ struct drm_plane *plane = &priv->primary;
+ static const u32 formats[] = {
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+ };
+ int ret;
+
+ ret = drm_universal_plane_init(drm, plane, 0,
+ &baikal_vdu_primary_plane_funcs,
+ formats,
+ ARRAY_SIZE(formats),
+ NULL,
+ DRM_PLANE_TYPE_PRIMARY,
+ NULL);
+ if (ret)
+ return ret;
+
+ drm_plane_helper_add(plane, &baikal_vdu_primary_plane_helper_funcs);
+
+ return 0;
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#ifndef __BAIKAL_VDU_REGS_H__
+#define __BAIKAL_VDU_REGS_H__
+
+#define CR1 0x000
+#define HTR 0x008
+#define VTR1 0x00C
+#define VTR2 0x010
+#define PCTR 0x014
+#define ISR 0x018
+#define IMR 0x01C
+#define IVR 0x020
+#define ISCR 0x024
+#define DBAR 0x028
+#define DCAR 0x02C
+#define DEAR 0x030
+#define PWMFR 0x034
+#define PWMDCR 0x038
+#define HVTER 0x044
+#define HPPLOR 0x048
+#define GPIOR 0x1F8
+#define MRR 0xFFC
+
+#define INTR_UFU BIT(16)
+#define INTR_BAU BIT(7)
+#define INTR_VCT BIT(6)
+#define INTR_MBE BIT(5)
+#define INTR_FER BIT(4)
+#define INTR_IFO BIT(3)
+#define INTR_OFU BIT(0)
+
+#define CR1_FBP BIT(19)
+#define CR1_FDW_MASK GENMASK(17, 16)
+#define CR1_FDW_4_WORDS (0 << 16)
+#define CR1_FDW_8_WORDS (1 << 16)
+#define CR1_FDW_16_WORDS (2 << 16)
+#define CR1_OPS_MASK GENMASK(14, 12)
+#define CR1_OPS_LCD18 (0 << 13)
+#define CR1_OPS_LCD24 (1 << 13)
+#define CR1_OPS_565 (0 << 12)
+#define CR1_OPS_555 (1 << 12)
+#define CR1_VSP BIT(11)
+#define CR1_HSP BIT(10)
+#define CR1_DEP BIT(8)
+#define CR1_BGR BIT(5)
+#define CR1_BPP_MASK GENMASK(4, 2)
+#define CR1_BPP1 (0 << 2)
+#define CR1_BPP2 (1 << 2)
+#define CR1_BPP4 (2 << 2)
+#define CR1_BPP8 (3 << 2)
+#define CR1_BPP16 (4 << 2)
+#define CR1_BPP18 (5 << 2)
+#define CR1_BPP24 (6 << 2)
+#define CR1_LCE BIT(0)
+
+#define CR1_BPP16_555 ((CR1_BPP16) | (CR1_OPS_555))
+#define CR1_BPP16_565 ((CR1_BPP16) | (CR1_OPS_565))
+
+#define VTR1_VBP_MASK GENMASK(23, 16)
+#define VTR1_VBP(x) ((x) << 16)
+#define VTR1_VBP_LSB_WIDTH 8
+#define VTR1_VFP_MASK GENMASK(15, 8)
+#define VTR1_VFP(x) ((x) << 8)
+#define VTR1_VFP_LSB_WIDTH 8
+#define VTR1_VSW_MASK GENMASK(7, 0)
+#define VTR1_VSW(x) ((x) << 0)
+#define VTR1_VSW_LSB_WIDTH 8
+
+#define VTR2_LPP_MASK GENMASK(11, 0)
+
+#define HTR_HSW_MASK GENMASK(31, 24)
+#define HTR_HSW(x) ((x) << 24)
+#define HTR_HSW_LSB_WIDTH 8
+#define HTR_HBP_MASK GENMASK(23, 16)
+#define HTR_HBP(x) ((x) << 16)
+#define HTR_HBP_LSB_WIDTH 8
+#define HTR_PPL_MASK GENMASK(15, 8)
+#define HTR_PPL(x) ((x) << 8)
+#define HTR_HFP_MASK GENMASK(7, 0)
+#define HTR_HFP(x) ((x) << 0)
+#define HTR_HFP_LSB_WIDTH 8
+
+#define PCTR_PCI2 BIT(11)
+#define PCTR_PCR BIT(10)
+#define PCTR_PCI BIT(9)
+#define PCTR_PCB BIT(8)
+#define PCTR_PCD_MASK GENMASK(7, 0)
+#define PCTR_MAX_PCD 128
+
+#define ISCR_VSC_OFF 0x0
+#define ISCR_VSC_VSW 0x4
+#define ISCR_VSC_VBP 0x5
+#define ISCR_VSC_VACTIVE 0x6
+#define ISCR_VSC_VFP 0x7
+
+#define PWMFR_PWMPCR BIT(24)
+#define PWMFR_PWMFCI BIT(23)
+#define PWMFR_PWMFCE BIT(22)
+#define PWMFR_PWMFCD_MASK GENMASK(21, 0)
+#define PWMFR_PWMFCD(x) ((x) << 0)
+
+#define HVTER_VSWE_MASK GENMASK(25, 24)
+#define HVTER_VSWE(x) ((x) << 24)
+#define HVTER_HSWE_MASK GENMASK(17, 16)
+#define HVTER_HSWE(x) ((x) << 16)
+#define HVTER_VBPE_MASK GENMASK(13, 12)
+#define HVTER_VBPE(x) ((x) << 12)
+#define HVTER_VFPE_MASK GENMASK(9, 8)
+#define HVTER_VFPE(x) ((x) << 8)
+#define HVTER_HBPE_MASK GENMASK(5, 4)
+#define HVTER_HBPE(x) ((x) << 4)
+#define HVTER_HFPE_MASK GENMASK(1, 0)
+#define HVTER_HFPE(x) ((x) << 0)
+
+#define HPPLOR_HPOE BIT(31)
+#define HPPLOR_HPPLO_MASK GENMASK(11, 0)
+#define HPPLOR_HPPLO(x) ((x) << 0)
+
+#define GPIOR_UHD_MASK GENMASK(23, 16)
+#define GPIOR_UHD_SNGL_PORT (0 << 18)
+#define GPIOR_UHD_DUAL_PORT (1 << 18)
+#define GPIOR_UHD_QUAD_PORT (2 << 18)
+#define GPIOR_UHD_ENB BIT(17)
+
+#define MRR_DEAR_MRR_MASK GENMASK(31, 3)
+#define MRR_OUTSTND_RQ_MASK GENMASK(2, 0)
+#define MRR_OUTSTND_RQ(x) ((x >> 1) << 0)
+#define MRR_MAX_VALUE ((0xffffffff & MRR_DEAR_MRR_MASK) | MRR_OUTSTND_RQ(4))
+
+#endif /* __BAIKAL_VDU_REGS_H__ */
select DRM_KMS_HELPER
select DRM_MIPI_DSI
select DRM_PANEL_BRIDGE
+
+config DRM_BAIKAL_HDMI
+ tristate "Baikal-M HDMI transmitter"
+ select DRM_DW_HDMI
+ help
+ Choose this if you want to use HDMI on Baikal-M.
+
+config DRM_BAIKAL_HDMI_AHB_AUDIO
+ tristate "Baikal-M HDMI Audio interface"
+ depends on DRM_BAIKAL_HDMI && SND
+ select SND_PCM
+ select SND_PCM_ELD
+ select SND_PCM_IEC958
+ help
+ Support the AHB Audio interface which is part of the
+ Baikal-M HDMI Tx block.
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
+obj-$(CONFIG_DRM_BAIKAL_HDMI_AHB_AUDIO) += baikal-hdmi-ahb-audio.o
obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o
obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o
obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Baikal Electronics BE-M1000 DesignWare HDMI AHB audio driver
+ *
+ * Copyright (C) 2020-2022 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_edid.h>
+
+#include <sound/asoundef.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_drm_eld.h>
+#include <sound/pcm_iec958.h>
+
+#include "dw-hdmi-audio.h"
+
+#define DRIVER_NAME "dw-hdmi-ahb-audio"
+
+#define BAIKAL_HDMI_REG_SHIFT 2
+
+/* Provide some bits rather than bit offsets */
+enum {
+ HDMI_AHB_DMA_CONF0_SW_FIFO_RST = BIT(7),
+ HDMI_AHB_DMA_CONF0_EN_HLOCK = BIT(3),
+ HDMI_AHB_DMA_START_START = BIT(0),
+ HDMI_AHB_DMA_STOP_STOP = BIT(0),
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_OVERRUN = BIT(6),
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR = BIT(5),
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST = BIT(4),
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY = BIT(3),
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE = BIT(2),
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL = BIT(1),
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY = BIT(0),
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL =
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_OVERRUN |
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR |
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST |
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY |
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE |
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL |
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY,
+ HDMI_IH_AHBDMAAUD_STAT0_OVERRUN = BIT(6),
+ HDMI_IH_AHBDMAAUD_STAT0_ERROR = BIT(5),
+ HDMI_IH_AHBDMAAUD_STAT0_LOST = BIT(4),
+ HDMI_IH_AHBDMAAUD_STAT0_RETRY = BIT(3),
+ HDMI_IH_AHBDMAAUD_STAT0_DONE = BIT(2),
+ HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL = BIT(1),
+ HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY = BIT(0),
+ HDMI_IH_AHBDMAAUD_STAT0_ALL =
+ HDMI_IH_AHBDMAAUD_STAT0_OVERRUN |
+ HDMI_IH_AHBDMAAUD_STAT0_ERROR |
+ HDMI_IH_AHBDMAAUD_STAT0_LOST |
+ HDMI_IH_AHBDMAAUD_STAT0_RETRY |
+ HDMI_IH_AHBDMAAUD_STAT0_DONE |
+ HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL |
+ HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY,
+ HDMI_AHB_DMA_CONF0_INCR16 = 2 << 1,
+ HDMI_AHB_DMA_CONF0_INCR8 = 1 << 1,
+ HDMI_AHB_DMA_CONF0_INCR4 = 0,
+ HDMI_AHB_DMA_CONF0_BURST_MODE = BIT(0),
+ HDMI_AHB_DMA_MASK_DONE = BIT(7),
+
+ HDMI_REVISION_ID = 0x0001,
+ HDMI_IH_AHBDMAAUD_STAT0 = 0x0109,
+ HDMI_IH_MUTE_AHBDMAAUD_STAT0 = 0x0189,
+ HDMI_FC_AUDICONF2 = 0x1027,
+ HDMI_FC_AUDSCONF = 0x1063,
+ HDMI_FC_AUDSCONF_LAYOUT1 = 1 << 0,
+ HDMI_FC_AUDSCONF_LAYOUT0 = 0 << 0,
+ HDMI_AHB_DMA_CONF0 = 0x3600,
+ HDMI_AHB_DMA_START = 0x3601,
+ HDMI_AHB_DMA_STOP = 0x3602,
+ HDMI_AHB_DMA_THRSLD = 0x3603,
+ HDMI_AHB_DMA_STRADDR0 = 0x3604,
+ HDMI_AHB_DMA_STPADDR0 = 0x3608,
+ HDMI_AHB_DMA_MASK = 0x3614,
+ HDMI_AHB_DMA_POL = 0x3615,
+ HDMI_AHB_DMA_CONF1 = 0x3616,
+ HDMI_AHB_DMA_BUFFPOL = 0x361a,
+};
+
+struct dw_hdmi_channel_conf {
+ u8 conf1;
+ u8 ca;
+};
+
+/*
+ * The default mapping of ALSA channels to HDMI channels and speaker
+ * allocation bits. Note that we can't do channel remapping here -
+ * channels must be in the same order.
+ *
+ * Mappings for alsa-lib pcm/surround*.conf files:
+ *
+ * Front Sur4.0 Sur4.1 Sur5.0 Sur5.1 Sur7.1
+ * Channels 2 4 6 6 6 8
+ *
+ * Our mapping from ALSA channel to CEA686D speaker name and HDMI channel:
+ *
+ * Number of ALSA channels
+ * ALSA Channel 2 3 4 5 6 7 8
+ * 0 FL:0 = = = = = =
+ * 1 FR:1 = = = = = =
+ * 2 FC:3 RL:4 LFE:2 = = =
+ * 3 RR:5 RL:4 FC:3 = =
+ * 4 RR:5 RL:4 = =
+ * 5 RR:5 = =
+ * 6 RC:6 =
+ * 7 RLC/FRC RLC/FRC
+ */
+static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = {
+ { 0x03, 0x00 }, /* FL,FR */
+ { 0x0b, 0x02 }, /* FL,FR,FC */
+ { 0x33, 0x08 }, /* FL,FR,RL,RR */
+ { 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */
+ { 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */
+ { 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */
+ { 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */
+};
+
+struct snd_dw_hdmi {
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ spinlock_t lock;
+ struct dw_hdmi_audio_data data;
+ struct snd_pcm_substream *substream;
+ void (*reformat)(struct snd_dw_hdmi *, size_t, size_t);
+ void *buf_src;
+ void *buf_dst;
+ dma_addr_t buf_addr;
+ unsigned buf_offset;
+ unsigned buf_period;
+ unsigned buf_size;
+ unsigned channels;
+ u8 revision;
+ u8 iec_offset;
+ u8 cs[192][8];
+};
+
+static inline void baikal_hdmi_writeb(u8 val, void __iomem *base, uint offset)
+{
+ writeb(val, base + (offset << BAIKAL_HDMI_REG_SHIFT));
+}
+
+static inline void baikal_hdmi_writeb_relaxed(u8 val, void __iomem *base, uint offset)
+{
+ writeb_relaxed(val, base + (offset << BAIKAL_HDMI_REG_SHIFT));
+}
+
+static inline u8 baikal_hdmi_readb_relaxed(void __iomem *base, uint offset)
+{
+ return readb_relaxed(base + (offset << BAIKAL_HDMI_REG_SHIFT));
+}
+
+static void baikal_hdmi_writel(u32 val, void __iomem *base, uint offset)
+{
+ writeb_relaxed(val, base + (offset << BAIKAL_HDMI_REG_SHIFT));
+ writeb_relaxed(val >> 8, base + ((offset + 1) << BAIKAL_HDMI_REG_SHIFT));
+ writeb_relaxed(val >> 16, base + ((offset + 2) << BAIKAL_HDMI_REG_SHIFT));
+ writeb_relaxed(val >> 24, base + ((offset + 3) << BAIKAL_HDMI_REG_SHIFT));
+}
+
+/*
+ * Convert to hardware format: The userspace buffer contains IEC958 samples,
+ * with the PCUV bits in bits 31..28 and audio samples in bits 27..4. We
+ * need these to be in bits 27..24, with the IEC B bit in bit 28, and audio
+ * samples in 23..0.
+ *
+ * Default preamble in bits 3..0: 8 = block start, 4 = even 2 = odd
+ *
+ * Ideally, we could do with having the data properly formatted in userspace.
+ */
+static void dw_hdmi_reformat_iec958(struct snd_dw_hdmi *dw,
+ size_t offset, size_t bytes)
+{
+ u32 *src = dw->buf_src + offset;
+ u32 *dst = dw->buf_dst + offset;
+ u32 *end = dw->buf_src + offset + bytes;
+
+ do {
+ u32 b, sample = *src++;
+
+ b = (sample & 8) << (28 - 3);
+
+ sample >>= 4;
+
+ *dst++ = sample | b;
+ } while (src < end);
+}
+
+static u32 parity(u32 sample)
+{
+ sample ^= sample >> 16;
+ sample ^= sample >> 8;
+ sample ^= sample >> 4;
+ sample ^= sample >> 2;
+ sample ^= sample >> 1;
+ return (sample & 1) << 27;
+}
+
+static void dw_hdmi_reformat_s24(struct snd_dw_hdmi *dw,
+ size_t offset, size_t bytes)
+{
+ u32 *src = dw->buf_src + offset;
+ u32 *dst = dw->buf_dst + offset;
+ u32 *end = dw->buf_src + offset + bytes;
+
+ do {
+ unsigned i;
+ u8 *cs;
+
+ cs = dw->cs[dw->iec_offset++];
+ if (dw->iec_offset >= 192)
+ dw->iec_offset = 0;
+
+ i = dw->channels;
+ do {
+ u32 sample = *src++;
+
+ sample &= ~0xff000000;
+ sample |= *cs++ << 24;
+ sample |= parity(sample & ~0xf8000000);
+
+ *dst++ = sample;
+ } while (--i);
+ } while (src < end);
+}
+
+static void dw_hdmi_create_cs(struct snd_dw_hdmi *dw,
+ struct snd_pcm_runtime *runtime)
+{
+ u8 cs[4];
+ unsigned ch, i, j;
+
+ snd_pcm_create_iec958_consumer(runtime, cs, sizeof(cs));
+
+ memset(dw->cs, 0, sizeof(dw->cs));
+
+ for (ch = 0; ch < 8; ch++) {
+ cs[2] &= ~IEC958_AES2_CON_CHANNEL;
+ cs[2] |= (ch + 1) << 4;
+
+ for (i = 0; i < ARRAY_SIZE(cs); i++) {
+ unsigned c = cs[i];
+
+ for (j = 0; j < 8; j++, c >>= 1)
+ dw->cs[i * 8 + j][ch] = (c & 1) << 2;
+ }
+ }
+ dw->cs[0][0] |= BIT(4);
+}
+
+static void dw_hdmi_start_dma(struct snd_dw_hdmi *dw)
+{
+ void __iomem *base = dw->data.base;
+ unsigned offset = dw->buf_offset;
+ unsigned period = dw->buf_period;
+ u32 start, stop;
+
+ dw->reformat(dw, offset, period);
+
+ /* Clear all irqs before enabling irqs and starting DMA */
+ baikal_hdmi_writeb_relaxed(HDMI_IH_AHBDMAAUD_STAT0_ALL,
+ base, HDMI_IH_AHBDMAAUD_STAT0);
+
+ start = dw->buf_addr + offset;
+ stop = start + period - 1;
+
+ /* Setup the hardware start/stop addresses */
+ baikal_hdmi_writel(start, base, HDMI_AHB_DMA_STRADDR0);
+ baikal_hdmi_writel(stop, base, HDMI_AHB_DMA_STPADDR0);
+
+ baikal_hdmi_writeb_relaxed((u8)~HDMI_AHB_DMA_MASK_DONE, base, HDMI_AHB_DMA_MASK);
+ baikal_hdmi_writeb(HDMI_AHB_DMA_START_START, base, HDMI_AHB_DMA_START);
+
+ offset += period;
+ if (offset >= dw->buf_size)
+ offset = 0;
+ dw->buf_offset = offset;
+}
+
+static void dw_hdmi_stop_dma(struct snd_dw_hdmi *dw)
+{
+ /* Disable interrupts before disabling DMA */
+ baikal_hdmi_writeb_relaxed(~0, dw->data.base, HDMI_AHB_DMA_MASK);
+ baikal_hdmi_writeb_relaxed(HDMI_AHB_DMA_STOP_STOP, dw->data.base, HDMI_AHB_DMA_STOP);
+}
+
+static irqreturn_t snd_dw_hdmi_irq(int irq, void *data)
+{
+ struct snd_dw_hdmi *dw = data;
+ struct snd_pcm_substream *substream;
+ unsigned stat;
+
+ stat = baikal_hdmi_readb_relaxed(dw->data.base, HDMI_IH_AHBDMAAUD_STAT0);
+ if (!stat)
+ return IRQ_NONE;
+
+ baikal_hdmi_writeb_relaxed(stat, dw->data.base, HDMI_IH_AHBDMAAUD_STAT0);
+
+ substream = dw->substream;
+ if (stat & HDMI_IH_AHBDMAAUD_STAT0_DONE && substream) {
+ snd_pcm_period_elapsed(substream);
+
+ spin_lock(&dw->lock);
+ if (dw->substream)
+ dw_hdmi_start_dma(dw);
+ spin_unlock(&dw->lock);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static struct snd_pcm_hardware dw_hdmi_hw = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID,
+ .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .rates = SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_176400 |
+ SNDRV_PCM_RATE_192000,
+ .channels_min = 2,
+ .channels_max = 8,
+ .buffer_bytes_max = 1024 * 1024,
+ .period_bytes_min = 256,
+ .period_bytes_max = 8192, /* ERR004323: must limit to 8k */
+ .periods_min = 2,
+ .periods_max = 16,
+ .fifo_size = 0,
+};
+
+static int dw_hdmi_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dw_hdmi *dw = substream->private_data;
+ void __iomem *base = dw->data.base;
+ u8 *eld;
+ int ret;
+
+ runtime->hw = dw_hdmi_hw;
+
+ eld = dw->data.get_eld(dw->data.hdmi);
+ if (eld) {
+ ret = snd_pcm_hw_constraint_eld(runtime, eld);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = snd_pcm_limit_hw_rates(runtime);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ /* Limit the buffer size to the size of the preallocated buffer */
+ ret = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+ 0, substream->dma_buffer.bytes);
+ if (ret < 0)
+ return ret;
+
+ /* Clear FIFO */
+ baikal_hdmi_writeb_relaxed(HDMI_AHB_DMA_CONF0_SW_FIFO_RST,
+ base, HDMI_AHB_DMA_CONF0);
+
+ /* Configure interrupt polarities */
+ baikal_hdmi_writeb_relaxed(~0, base, HDMI_AHB_DMA_POL);
+ baikal_hdmi_writeb_relaxed(~0, base, HDMI_AHB_DMA_BUFFPOL);
+
+ /* Keep interrupts masked, and clear any pending */
+ baikal_hdmi_writeb_relaxed(~0, base, HDMI_AHB_DMA_MASK);
+ baikal_hdmi_writeb_relaxed(~0, base, HDMI_IH_AHBDMAAUD_STAT0);
+
+ ret = request_irq(dw->data.irq, snd_dw_hdmi_irq, IRQF_SHARED,
+ "dw-hdmi-audio", dw);
+ if (ret)
+ return ret;
+
+ /* Un-mute done interrupt */
+ baikal_hdmi_writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL &
+ ~HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE,
+ base, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+
+ return 0;
+}
+
+static int dw_hdmi_close(struct snd_pcm_substream *substream)
+{
+ struct snd_dw_hdmi *dw = substream->private_data;
+
+ /* Mute all interrupts */
+ baikal_hdmi_writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL,
+ dw->data.base, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+
+ free_irq(dw->data.irq, dw);
+
+ return 0;
+}
+
+static int dw_hdmi_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int dw_hdmi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ /* Allocate the PCM runtime buffer, which is exposed to userspace. */
+ return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(params));
+}
+
+static int dw_hdmi_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dw_hdmi *dw = substream->private_data;
+ u8 threshold, conf0, conf1, layout, ca;
+
+ /* Setup as per 3.0.5 FSL 4.1.0 BSP */
+ switch (dw->revision) {
+ case 0x0a:
+ conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR4;
+ if (runtime->channels == 2)
+ threshold = 126;
+ else
+ threshold = 124;
+ break;
+ case 0x1a:
+ conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR8;
+ threshold = 128;
+ break;
+ case 0x2a: /* this revision is used in Baikal-M SoC */
+ conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR16;
+ threshold = 128;
+ break;
+ default:
+ /* NOTREACHED */
+ return -EINVAL;
+ }
+
+ dw_hdmi_set_sample_rate(dw->data.hdmi, runtime->rate);
+
+ /* Minimum number of bytes in the fifo. */
+ runtime->hw.fifo_size = threshold * 32;
+
+ conf0 |= HDMI_AHB_DMA_CONF0_EN_HLOCK;
+ conf1 = default_hdmi_channel_config[runtime->channels - 2].conf1;
+ ca = default_hdmi_channel_config[runtime->channels - 2].ca;
+
+ /*
+ * For >2 channel PCM audio, we need to select layout 1
+ * and set an appropriate channel map.
+ */
+ if (runtime->channels > 2)
+ layout = HDMI_FC_AUDSCONF_LAYOUT1;
+ else
+ layout = HDMI_FC_AUDSCONF_LAYOUT0;
+
+ baikal_hdmi_writeb_relaxed(threshold, dw->data.base, HDMI_AHB_DMA_THRSLD);
+ baikal_hdmi_writeb_relaxed(conf0, dw->data.base, HDMI_AHB_DMA_CONF0);
+ baikal_hdmi_writeb_relaxed(conf1, dw->data.base, HDMI_AHB_DMA_CONF1);
+ baikal_hdmi_writeb_relaxed(layout, dw->data.base, HDMI_FC_AUDSCONF);
+ baikal_hdmi_writeb_relaxed(ca, dw->data.base, HDMI_FC_AUDICONF2);
+
+ switch (runtime->format) {
+ case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
+ dw->reformat = dw_hdmi_reformat_iec958;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ dw_hdmi_create_cs(dw, runtime);
+ dw->reformat = dw_hdmi_reformat_s24;
+ break;
+ }
+ dw->iec_offset = 0;
+ dw->channels = runtime->channels;
+ dw->buf_src = runtime->dma_area;
+ dw->buf_dst = substream->dma_buffer.area;
+ dw->buf_addr = substream->dma_buffer.addr;
+ dw->buf_period = snd_pcm_lib_period_bytes(substream);
+ dw->buf_size = snd_pcm_lib_buffer_bytes(substream);
+
+ return 0;
+}
+
+static int dw_hdmi_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_dw_hdmi *dw = substream->private_data;
+ unsigned long flags;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ spin_lock_irqsave(&dw->lock, flags);
+ dw->buf_offset = 0;
+ dw->substream = substream;
+ dw_hdmi_start_dma(dw);
+ dw_hdmi_audio_enable(dw->data.hdmi);
+ spin_unlock_irqrestore(&dw->lock, flags);
+ substream->runtime->delay = substream->runtime->period_size;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ spin_lock_irqsave(&dw->lock, flags);
+ dw->substream = NULL;
+ dw_hdmi_stop_dma(dw);
+ dw_hdmi_audio_disable(dw->data.hdmi);
+ spin_unlock_irqrestore(&dw->lock, flags);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t dw_hdmi_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dw_hdmi *dw = substream->private_data;
+
+ /*
+ * We are unable to report the exact hardware position as
+ * reading the 32-bit DMA position using 8-bit reads is racy.
+ */
+ return bytes_to_frames(runtime, dw->buf_offset);
+}
+
+static struct snd_pcm_ops snd_dw_hdmi_ops = {
+ .open = dw_hdmi_open,
+ .close = dw_hdmi_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = dw_hdmi_hw_params,
+ .hw_free = dw_hdmi_hw_free,
+ .prepare = dw_hdmi_prepare,
+ .trigger = dw_hdmi_trigger,
+ .pointer = dw_hdmi_pointer,
+ .page = snd_pcm_lib_get_vmalloc_page,
+};
+
+static int snd_dw_hdmi_probe(struct platform_device *pdev)
+{
+ const struct dw_hdmi_audio_data *data = pdev->dev.platform_data;
+ struct device *dev = pdev->dev.parent;
+ struct snd_dw_hdmi *dw;
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ unsigned revision;
+ int ret;
+
+ baikal_hdmi_writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL,
+ data->base, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+ revision = baikal_hdmi_readb_relaxed(data->base, HDMI_REVISION_ID);
+ if (revision != 0x0a && revision != 0x1a && revision != 0x2a) {
+ dev_err(dev, "dw-hdmi-audio: unknown revision 0x%02x\n",
+ revision);
+ return -ENXIO;
+ }
+
+ ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+ THIS_MODULE, sizeof(struct snd_dw_hdmi), &card);
+ if (ret < 0)
+ return ret;
+
+ strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver));
+ strlcpy(card->shortname, "DW-HDMI", sizeof(card->shortname));
+ snprintf(card->longname, sizeof(card->longname),
+ "%s rev 0x%02x, irq %d", card->shortname, revision,
+ data->irq);
+
+ dw = card->private_data;
+ dw->card = card;
+ dw->data = *data;
+ dw->revision = revision;
+
+ spin_lock_init(&dw->lock);
+
+ ret = snd_pcm_new(card, "DW HDMI", 0, 1, 0, &pcm);
+ if (ret < 0)
+ goto err;
+
+ dw->pcm = pcm;
+ pcm->private_data = dw;
+ strlcpy(pcm->name, DRIVER_NAME, sizeof(pcm->name));
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_dw_hdmi_ops);
+
+ /*
+ * To support 8-channel 96kHz audio reliably, we need 512k
+ * to satisfy alsa with our restricted period (ERR004323).
+ */
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+ dev, 128 * 1024, 1024 * 1024);
+
+ ret = snd_card_register(card);
+ if (ret < 0)
+ goto err;
+
+ platform_set_drvdata(pdev, dw);
+
+ return 0;
+
+err:
+ snd_card_free(card);
+ return ret;
+}
+
+static int snd_dw_hdmi_remove(struct platform_device *pdev)
+{
+ struct snd_dw_hdmi *dw = platform_get_drvdata(pdev);
+
+ snd_card_free(dw->card);
+
+ return 0;
+}
+
+#if defined(CONFIG_PM_SLEEP) && defined(IS_NOT_BROKEN)
+/*
+ * This code is fine, but requires implementation in the dw_hdmi_trigger()
+ * method which is currently missing as I have no way to test this.
+ */
+static int snd_dw_hdmi_suspend(struct device *dev)
+{
+ struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
+
+ snd_power_change_state(dw->card, SNDRV_CTL_POWER_D3cold);
+ snd_pcm_suspend_all(dw->pcm);
+
+ return 0;
+}
+
+static int snd_dw_hdmi_resume(struct device *dev)
+{
+ struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
+
+ snd_power_change_state(dw->card, SNDRV_CTL_POWER_D0);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(snd_dw_hdmi_pm, snd_dw_hdmi_suspend,
+ snd_dw_hdmi_resume);
+#define PM_OPS &snd_dw_hdmi_pm
+#else
+#define PM_OPS NULL
+#endif
+
+static struct platform_driver snd_dw_hdmi_driver = {
+ .probe = snd_dw_hdmi_probe,
+ .remove = snd_dw_hdmi_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .pm = PM_OPS,
+ },
+};
+
+module_platform_driver(snd_dw_hdmi_driver);
+
+MODULE_AUTHOR("Russell King <rmk+kernel@arm.linux.org.uk>");
+MODULE_DESCRIPTION("Synopsis Designware HDMI AHB ALSA interface");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
if (!plat_data->regm) {
const struct regmap_config *reg_config;
- of_property_read_u32(np, "reg-io-width", &val);
+ device_property_read_u32(dev, "reg-io-width", &val);
switch (val) {
case 4:
reg_config = &hdmi_regmap_32bit_config;
}
EXPORT_SYMBOL_GPL(dw_hdmi_resume);
+struct drm_bridge *dw_hdmi_get_bridge(struct dw_hdmi *hdmi)
+{
+ if (IS_ERR_OR_NULL(hdmi))
+ return NULL;
+
+ return &hdmi->bridge;
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_get_bridge);
+
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
}
EXPORT_SYMBOL(drm_panel_get_modes);
+/**
+ * fwnode_drm_find_panel - look up a panel using a fwnode
+ * @fwnode: fwnode of the panel
+ *
+ * Searches the set of registered panels for one that matches the given fwnode.
+ * If a matching panel is found, return a pointer to it.
+ *
+ * Return: A pointer to the panel registered for the specified fwnode or
+ * an ERR_PTR() if no panel matching the fwnode can be found.
+ *
+ * Possible error codes returned by this function:
+ *
+ * - EPROBE_DEFER: the panel device has not been probed yet, and the caller
+ * should retry later
+ * - ENODEV: the device is not available
+ */
+struct drm_panel *fwnode_drm_find_panel(const struct fwnode_handle *fwnode)
+{
+ struct drm_panel *panel;
+
+ if (!fwnode_device_is_available(fwnode))
+ return ERR_PTR(-ENODEV);
+
+ mutex_lock(&panel_lock);
+
+ list_for_each_entry(panel, &panel_list, list) {
+ if (panel->dev->fwnode == fwnode) {
+ mutex_unlock(&panel_lock);
+ return panel;
+ }
+ }
+
+ mutex_unlock(&panel_lock);
+ return ERR_PTR(-EPROBE_DEFER);
+}
+EXPORT_SYMBOL(fwnode_drm_find_panel);
+
#ifdef CONFIG_OF
/**
* of_drm_find_panel - look up a panel using a device tree node
NT35596 1080x1920 video mode panel as found in some Asus
Zenfone 2 Laser Z00T devices.
+config DRM_PANEL_BAIKAL_LVDS
+ tristate "Baikal LVDS panel driver"
+ select VIDEOMODE_HELPERS
+ help
+ This driver supports LVDS panels that don't require device-specific
+ handling of power supplies or control signals. Used in Baikal SoCs
+ for ACPI.
+
config DRM_PANEL_BOE_BF060Y8M_AJ0
tristate "Boe BF060Y8M-AJ0 panel"
depends on OF
obj-$(CONFIG_DRM_PANEL_ABT_Y030XX067A) += panel-abt-y030xx067a.o
obj-$(CONFIG_DRM_PANEL_ARM_VERSATILE) += panel-arm-versatile.o
obj-$(CONFIG_DRM_PANEL_ASUS_Z00T_TM5P5_NT35596) += panel-asus-z00t-tm5p5-n35596.o
+obj-$(CONFIG_DRM_PANEL_BAIKAL_LVDS) += panel-baikal-lvds.o
obj-$(CONFIG_DRM_PANEL_BOE_BF060Y8M_AJ0) += panel-boe-bf060y8m-aj0.o
obj-$(CONFIG_DRM_PANEL_BOE_HIMAX8279D) += panel-boe-himax8279d.o
obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_NL6) += panel-boe-tv101wum-nl6.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Baikal LVDS panel driver
+ *
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ * Author: Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
+ *
+ * Implementation based on panel-lvds.c
+ */
+
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include <video/display_timing.h>
+#include <video/videomode.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_panel.h>
+
+struct panel_lvds {
+ struct drm_panel panel;
+ struct device *dev;
+ unsigned int width;
+ unsigned int height;
+ struct drm_display_mode dmode;
+ u32 bus_flags;
+ unsigned int bus_format;
+ enum drm_panel_orientation orientation;
+};
+
+static inline struct panel_lvds *to_panel_lvds(struct drm_panel *panel)
+{
+ return container_of(panel, struct panel_lvds, panel);
+}
+
+static int panel_lvds_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct panel_lvds *lvds = to_panel_lvds(panel);
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, &lvds->dmode);
+ if (!mode)
+ return 0;
+
+ mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+
+ connector->display_info.width_mm = lvds->dmode.width_mm;
+ connector->display_info.height_mm = lvds->dmode.height_mm;
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &lvds->bus_format, 1);
+ connector->display_info.bus_flags = lvds->bus_flags;
+
+ /*
+ * TODO: Remove once all drm drivers call
+ * drm_connector_set_orientation_from_panel()
+ */
+ drm_connector_set_panel_orientation(connector, lvds->orientation);
+
+ return 1;
+}
+
+static enum drm_panel_orientation panel_lvds_get_orientation(struct drm_panel *panel)
+{
+ struct panel_lvds *lvds = to_panel_lvds(panel);
+
+ return lvds->orientation;
+}
+
+static const struct drm_panel_funcs panel_lvds_funcs = {
+ .get_modes = panel_lvds_get_modes,
+ .get_orientation = panel_lvds_get_orientation,
+};
+
+static int panel_lvds_get_data_mapping(struct device *dev)
+{
+ const char *mapping;
+ int ret;
+
+ ret = device_property_read_string(dev, "data-mapping", &mapping);
+ if (ret < 0)
+ return -ENODEV;
+
+ if (!strcmp(mapping, "jeida-18"))
+ return MEDIA_BUS_FMT_RGB666_1X7X3_SPWG;
+ if (!strcmp(mapping, "jeida-24"))
+ return MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
+ if (!strcmp(mapping, "vesa-24"))
+ return MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
+
+ return -EINVAL;
+}
+
+static int parse_timing_property(struct device *dev, const char *name,
+ struct timing_entry *result)
+{
+ int count, ret;
+
+ count = device_property_count_u32(dev, name);
+ if (count == 1) {
+ ret = device_property_read_u32(dev, name, &result->typ);
+ result->min = result->typ;
+ result->max = result->typ;
+ } else if (count == 3) {
+ ret = device_property_read_u32_array(dev, name, &result->min, count);
+ } else {
+ dev_err(dev, "illegal timing specification in %s\n", name);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int panel_lvds_get_display_timing(struct device *dev,
+ struct display_timing *dt)
+{
+ u32 val = 0;
+ int ret = 0;
+
+ memset(dt, 0, sizeof(*dt));
+
+ ret |= parse_timing_property(dev, "hback-porch", &dt->hback_porch);
+ ret |= parse_timing_property(dev, "hfront-porch", &dt->hfront_porch);
+ ret |= parse_timing_property(dev, "hactive", &dt->hactive);
+ ret |= parse_timing_property(dev, "hsync-len", &dt->hsync_len);
+ ret |= parse_timing_property(dev, "vback-porch", &dt->vback_porch);
+ ret |= parse_timing_property(dev, "vfront-porch", &dt->vfront_porch);
+ ret |= parse_timing_property(dev, "vactive", &dt->vactive);
+ ret |= parse_timing_property(dev, "vsync-len", &dt->vsync_len);
+ ret |= parse_timing_property(dev, "clock-frequency", &dt->pixelclock);
+
+ dt->flags = 0;
+ if (!device_property_read_u32(dev, "vsync-active", &val))
+ dt->flags |= val ? DISPLAY_FLAGS_VSYNC_HIGH :
+ DISPLAY_FLAGS_VSYNC_LOW;
+ if (!device_property_read_u32(dev, "hsync-active", &val))
+ dt->flags |= val ? DISPLAY_FLAGS_HSYNC_HIGH :
+ DISPLAY_FLAGS_HSYNC_LOW;
+ if (!device_property_read_u32(dev, "de-active", &val))
+ dt->flags |= val ? DISPLAY_FLAGS_DE_HIGH :
+ DISPLAY_FLAGS_DE_LOW;
+ if (!device_property_read_u32(dev, "pixelclk-active", &val))
+ dt->flags |= val ? DISPLAY_FLAGS_PIXDATA_POSEDGE :
+ DISPLAY_FLAGS_PIXDATA_NEGEDGE;
+
+ if (!device_property_read_u32(dev, "syncclk-active", &val))
+ dt->flags |= val ? DISPLAY_FLAGS_SYNC_POSEDGE :
+ DISPLAY_FLAGS_SYNC_NEGEDGE;
+ else if (dt->flags & (DISPLAY_FLAGS_PIXDATA_POSEDGE |
+ DISPLAY_FLAGS_PIXDATA_NEGEDGE))
+ dt->flags |= dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE ?
+ DISPLAY_FLAGS_SYNC_POSEDGE :
+ DISPLAY_FLAGS_SYNC_NEGEDGE;
+
+ if (device_property_read_bool(dev, "interlaced"))
+ dt->flags |= DISPLAY_FLAGS_INTERLACED;
+ if (device_property_read_bool(dev, "doublescan"))
+ dt->flags |= DISPLAY_FLAGS_DOUBLESCAN;
+ if (device_property_read_bool(dev, "doubleclk"))
+ dt->flags |= DISPLAY_FLAGS_DOUBLECLK;
+
+ if (ret) {
+ dev_err(dev, "error reading timing properties\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int panel_lvds_get_display_mode(struct device *dev,
+ struct drm_display_mode *dmode,
+ u32 *bus_flags)
+{
+ u32 width_mm = 0, height_mm = 0;
+ struct display_timing timing;
+ struct videomode vm;
+ int ret;
+
+ ret = panel_lvds_get_display_timing(dev, &timing);
+ if (ret)
+ return ret;
+
+ videomode_from_timing(&timing, &vm);
+
+ memset(dmode, 0, sizeof(*dmode));
+ drm_display_mode_from_videomode(&vm, dmode);
+ if (bus_flags)
+ drm_bus_flags_from_videomode(&vm, bus_flags);
+
+ ret = device_property_read_u32(dev, "width-mm", &width_mm);
+ if (ret)
+ return ret;
+
+ ret = device_property_read_u32(dev, "height-mm", &height_mm);
+ if (ret)
+ return ret;
+
+ dmode->width_mm = width_mm;
+ dmode->height_mm = height_mm;
+
+ drm_mode_debug_printmodeline(dmode);
+
+ return 0;
+}
+
+static int panel_lvds_get_panel_orientation(struct device *dev,
+ enum drm_panel_orientation *orientation)
+{
+ int rotation, ret;
+
+ ret = device_property_read_u32(dev, "rotation", &rotation);
+ if (ret == -EINVAL) {
+ /* Don't return an error if there's no rotation property. */
+ *orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN;
+ return 0;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ if (rotation == 0)
+ *orientation = DRM_MODE_PANEL_ORIENTATION_NORMAL;
+ else if (rotation == 90)
+ *orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP;
+ else if (rotation == 180)
+ *orientation = DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP;
+ else if (rotation == 270)
+ *orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int panel_lvds_parse(struct panel_lvds *lvds)
+{
+ struct device *dev = lvds->dev;
+ int ret;
+
+ ret = panel_lvds_get_panel_orientation(dev, &lvds->orientation);
+ if (ret < 0) {
+ dev_err(dev, "failed to get orientation %d\n", ret);
+ return ret;
+ }
+
+ ret = panel_lvds_get_display_mode(dev, &lvds->dmode, &lvds->bus_flags);
+ if (ret < 0) {
+ dev_err(dev, "problems parsing panel-timing (%d)\n", ret);
+ return ret;
+ }
+
+ ret = panel_lvds_get_data_mapping(dev);
+ if (ret < 0) {
+ dev_err(dev, "invalid or missing data-mapping property\n");
+ return ret;
+ }
+
+ lvds->bus_format = ret;
+
+ lvds->bus_flags |= device_property_read_bool(dev, "data-mirror") ?
+ DRM_BUS_FLAG_DATA_LSB_TO_MSB :
+ DRM_BUS_FLAG_DATA_MSB_TO_LSB;
+
+ return 0;
+}
+
+static int panel_lvds_probe(struct platform_device *pdev)
+{
+ struct panel_lvds *lvds;
+ int ret;
+
+ lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
+ if (!lvds)
+ return -ENOMEM;
+
+ lvds->dev = &pdev->dev;
+
+ ret = panel_lvds_parse(lvds);
+ if (ret < 0)
+ return ret;
+
+ /* Register the panel. */
+ drm_panel_init(&lvds->panel, lvds->dev, &panel_lvds_funcs,
+ DRM_MODE_CONNECTOR_LVDS);
+
+ drm_panel_add(&lvds->panel);
+
+ dev_set_drvdata(lvds->dev, lvds);
+ return 0;
+}
+
+static int panel_lvds_remove(struct platform_device *pdev)
+{
+ struct panel_lvds *lvds = platform_get_drvdata(pdev);
+
+ drm_panel_remove(&lvds->panel);
+
+ drm_panel_disable(&lvds->panel);
+
+ return 0;
+}
+
+static const struct of_device_id panel_lvds_of_table[] = {
+ { .compatible = "baikal,panel-lvds", },
+ { /* Sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, panel_lvds_of_table);
+
+static struct platform_driver panel_baikal_lvds_driver = {
+ .probe = panel_lvds_probe,
+ .remove = panel_lvds_remove,
+ .driver = {
+ .name = "panel-baikal-lvds",
+ .of_match_table = panel_lvds_of_table,
+ },
+};
+
+module_platform_driver(panel_baikal_lvds_driver);
+
+MODULE_AUTHOR("Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal LVDS Panel Driver");
+MODULE_LICENSE("GPL");
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
+#include "../../../opp/opp.h"
+
#include "panfrost_device.h"
#include "panfrost_devfreq.h"
.get_dev_status = panfrost_devfreq_get_dev_status,
};
+static void panfrost_devfreq_opp_remove(void *data)
+{
+ dev_pm_opp_remove_all_dynamic(data);
+}
+
+static struct dev_pm_opp *panfrost_devfreq_opp_get(struct opp_table *opp_table,
+ unsigned long rate)
+{
+ struct dev_pm_opp *opp = NULL, *temp;
+
+ if (opp_table) {
+ mutex_lock(&opp_table->lock);
+ list_for_each_entry(temp, &opp_table->opp_list, node) {
+ if (!temp->removed && temp->dynamic &&
+ temp->rates[0] == rate) {
+ opp = temp;
+ break;
+ }
+ }
+ mutex_unlock(&opp_table->lock);
+ }
+
+ return opp;
+}
+
+static int panfrost_devfreq_opp_add_table(struct device *dev)
+{
+ struct opp_table *opp_table = NULL;
+ struct dev_pm_opp *opp;
+ u64 *arr;
+ int i, count, ret;
+
+ count = device_property_count_u64(dev, "operating-points");
+ if (count < 2 || count % 2)
+ return -ENODEV;
+
+ arr = kcalloc(count, sizeof(u64), GFP_KERNEL);
+ if (!arr)
+ return -ENOMEM;
+
+ ret = device_property_read_u64_array(dev, "operating-points", arr, count);
+ if (ret)
+ goto err;
+
+ count >>= 1;
+ for (i = 0; i < count; ++i) {
+ ret = dev_pm_opp_add(dev, arr[2 * i], 0);
+ if (ret) {
+ if (!IS_ERR_OR_NULL(opp_table)) {
+ dev_pm_opp_put_opp_table(opp_table);
+ dev_pm_opp_remove_all_dynamic(dev);
+ }
+ goto err;
+ }
+
+ if (!opp_table)
+ opp_table = dev_pm_opp_get_opp_table(dev);
+
+ opp = panfrost_devfreq_opp_get(opp_table, i);
+ if (opp) {
+ mutex_lock(&opp_table->lock);
+ opp->clock_latency_ns = arr[2 * i + 1];
+ if (opp->clock_latency_ns > opp_table->clock_latency_ns_max)
+ opp_table->clock_latency_ns_max = opp->clock_latency_ns;
+ mutex_unlock(&opp_table->lock);
+ }
+ }
+
+ dev_pm_opp_put_opp_table(opp_table);
+ devm_add_action_or_reset(dev, panfrost_devfreq_opp_remove, dev);
+
+err:
+ kfree(arr);
+ return ret;
+}
+
int panfrost_devfreq_init(struct panfrost_device *pfdev)
{
int ret;
}
}
- ret = devm_pm_opp_of_add_table(dev);
+ if (dev->of_node)
+ ret = devm_pm_opp_of_add_table(dev);
+ else
+ ret = panfrost_devfreq_opp_add_table(dev);
+
if (ret) {
/* Optional, continue without devfreq */
if (ret == -ENODEV)
}
pfdevfreq->devfreq = devfreq;
+ if (!dev->of_node) {
+ mutex_lock(&devfreq->lock);
+ update_devfreq(devfreq);
+ mutex_unlock(&devfreq->lock);
+ }
+
cooling = devfreq_cooling_em_register(devfreq, NULL);
if (IS_ERR(cooling))
DRM_DEV_INFO(dev, "Failed to register cooling device\n");
platform_set_drvdata(pdev, pfdev);
- pfdev->comp = of_device_get_match_data(&pdev->dev);
+ pfdev->comp = device_get_match_data(&pdev->dev);
if (!pfdev->comp)
return -ENODEV;
dma_set_max_seg_size(pfdev->dev, UINT_MAX);
- irq = platform_get_irq_byname(to_platform_device(pfdev->dev), "gpu");
+ irq = fwnode_irq_get_byname(pfdev->dev->fwnode, "gpu");
if (irq <= 0)
return -ENODEV;
INIT_WORK(&pfdev->reset.work, panfrost_reset_work);
spin_lock_init(&js->job_lock);
- js->irq = platform_get_irq_byname(to_platform_device(pfdev->dev), "job");
+ js->irq = fwnode_irq_get_byname(pfdev->dev->fwnode, "job");
if (js->irq <= 0)
return -ENODEV;
{
int err, irq;
- irq = platform_get_irq_byname(to_platform_device(pfdev->dev), "mmu");
+ irq = fwnode_irq_get_byname(pfdev->dev->fwnode, "mmu");
if (irq <= 0)
return -ENODEV;
config SENSORS_BT1_PVT
tristate "Baikal-T1 Process, Voltage, Temperature sensor driver"
- depends on MIPS_BAIKAL_T1 || COMPILE_TEST
select POLYNOMIAL
help
If you say yes here you get support for Baikal-T1 PVT sensor
* 48380,
* where T = [-48380, 147438] mC and N = [0, 1023].
*/
-static const struct polynomial __maybe_unused poly_temp_to_N = {
- .total_divider = 10000,
- .terms = {
- {4, 18322, 10000, 10000},
- {3, 2343, 10000, 10},
- {2, 87018, 10000, 10},
- {1, 39269, 1000, 1},
- {0, 1720400, 1, 1}
+static const struct pvt_poly poly_temp_to_N[chips_number] = {
+ [bm1000] = {
+ .total_divider = 10000,
+ .terms = {
+ {4, 18322, 10000, 10000},
+ {3, 2343, 10000, 10},
+ {2, 87018, 10000, 10},
+ {1, 39269, 1000, 1},
+ {0, 1720400, 1, 1}
+ }
+ },
+ [bs1000] = {
+ .total_divider = 10000,
+ .terms = {
+ {4, 12569, 10000, 10000},
+ {3, 2476, 10000, 10},
+ {2, 66309, 10000, 10},
+ {1, 36384, 1000, 1},
+ {0, 2070500, 1, 1}
+ }
}
};
-static const struct polynomial poly_N_to_temp = {
- .total_divider = 1,
- .terms = {
- {4, -16743, 1000, 1},
- {3, 81542, 1000, 1},
- {2, -182010, 1000, 1},
- {1, 310200, 1000, 1},
- {0, -48380, 1, 1}
+static const struct pvt_poly poly_N_to_temp[chips_number] = {
+ [bm1000] = {
+ .total_divider = 1,
+ .terms = {
+ {4, -16743, 1000, 1},
+ {3, 81542, 1000, 1},
+ {2, -182010, 1000, 1},
+ {1, 310200, 1000, 1},
+ {0, -48380, 1, 1}
+ }
+ },
+ [bs1000] = {
+ .total_divider = 1,
+ .terms = {
+ {4, 16034, 1000, 1},
+ {3, 15608, 1000, 1},
+ {2, -150890, 1000, 1},
+ {1, 334080, 1000, 1},
+ {0, -62861, 1, 1}
+ }
}
};
* N = (18658e-3*V - 11572) / 10,
* V = N * 10^5 / 18658 + 11572 * 10^4 / 18658.
*/
-static const struct polynomial __maybe_unused poly_volt_to_N = {
- .total_divider = 10,
- .terms = {
- {1, 18658, 1000, 1},
- {0, -11572, 1, 1}
+static const struct pvt_poly poly_volt_to_N[chips_number] = {
+ [bm1000] = {
+ .total_divider = 10,
+ .terms = {
+ {1, 18658, 1000, 1},
+ {0, -11572, 1, 1}
+ }
+ },
+ [bs1000] = {
+ .total_divider = 10,
+ .terms = {
+ {1, 16757, 1000, 1},
+ {0, -8564, 1, 1}
+ }
}
};
-static const struct polynomial poly_N_to_volt = {
- .total_divider = 10,
- .terms = {
- {1, 100000, 18658, 1},
- {0, 115720000, 1, 18658}
+static const struct pvt_poly poly_N_to_volt[chips_number] = {
+ [bm1000] = {
+ .total_divider = 10,
+ .terms = {
+ {1, 100000, 18658, 1},
+ {0, 115720000, 1, 18658}
+ }
+ },
+ [bs1000] = {
+ .total_divider = 10,
+ .terms = {
+ {1, 100000, 16757, 1},
+ {0, 85639000, 1, 16757}
+ }
}
};
-static inline u32 pvt_update(void __iomem *reg, u32 mask, u32 data)
+
+/*
+ * Here is the polynomial calculation function, which performs the
+ * redistributed terms calculations. It's pretty straightforward. We walk
+ * over each degree term up to the free one, and perform the redistributed
+ * multiplication of the term coefficient, its divider (as for the rationale
+ * fraction representation), data power and the rational fraction divider
+ * leftover. Then all of this is collected in a total sum variable, which
+ * value is normalized by the total divider before being returned.
+ */
+static long pvt_calc_poly(const struct pvt_poly *poly, long data)
{
- u32 old;
+ const struct pvt_poly_term *term = poly->terms;
+ long tmp, ret = 0;
+ int deg;
+ do {
+ tmp = term->coef;
+ for (deg = 0; deg < term->deg; ++deg) {
+ tmp = mult_frac(tmp, data, term->divider);
+ }
+
+ ret += tmp / term->divider_leftover;
+ } while ((term++)->deg);
+ return ret / poly->total_divider;
+}
+
+
+static uint32_t writel_pvt(uint32_t val, uint32_t pvt_base, uint32_t offset)
+{
+ struct arm_smccc_res res;
- old = readl_relaxed(reg);
- writel((old & ~mask) | (data & mask), reg);
+ arm_smccc_smc(BAIKAL_SMC_PVT_CMD, PVT_WRITE, pvt_base, offset, val,
+ 0, 0, 0, &res);
+ // pr_debug("wr arm_smccc_smc %x, %x , %x , %x \n",pvt_base, offset, val, res.a0 );
+ return res.a0;
+}
+
+static uint32_t readl_pvt(uint32_t pvt_base, uint32_t offset)
+{
+ struct arm_smccc_res res;
+ arm_smccc_smc(BAIKAL_SMC_PVT_CMD, PVT_READ, pvt_base, offset,
+ 0, 0, 0, 0, &res);
+ // pr_debug("rd arm_smccc_smc %x, %x , %d \n",pvt_base, offset, res.a0 );
+ return res.a0;
+}
+
+static inline u32 pvt_update(void __iomem *pvt_base, void __iomem *offset, u32 mask, u32 data)
+{
+ u32 old;
+ old = readl_pvt(pvt_base, offset);
+ writel_pvt((old & ~mask) | (data & mask), pvt_base, offset);
+ // pr_debug("pvt_update addr %x, %x, mask %x, data %x, %x \n",pvt_base, offset, mask, data, old );
return old & mask;
}
u32 old;
mode = FIELD_PREP(PVT_CTRL_MODE_MASK, mode);
-
- old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
- pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_MODE_MASK | PVT_CTRL_EN,
+ old = pvt_update(pvt->base, PVT_CTRL, PVT_CTRL_EN, 0);
+ pvt_update(pvt->base, PVT_CTRL, PVT_CTRL_MODE_MASK | PVT_CTRL_EN,
mode | old);
}
trim = FIELD_PREP(PVT_CTRL_TRIM_MASK, trim);
- old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
- pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_TRIM_MASK | PVT_CTRL_EN,
+ old = pvt_update(pvt->regs, PVT_CTRL, PVT_CTRL_EN, 0);
+ pvt_update(pvt->regs, PVT_CTRL, PVT_CTRL_TRIM_MASK | PVT_CTRL_EN,
trim | old);
}
{
u32 old;
- old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
- writel(tout, pvt->regs + PVT_TTIMEOUT);
- pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, old);
+ old = pvt_update(pvt->base, PVT_CTRL, PVT_CTRL_EN, 0);
+ writel_pvt(tout, pvt->base, PVT_TTIMEOUT);
+ pvt_update(pvt->base, PVT_CTRL, PVT_CTRL_EN, old);
}
/*
* status before the next conversion happens. Threshold events will be
* handled a bit later.
*/
- thres_sts = readl(pvt->regs + PVT_RAW_INTR_STAT);
+ thres_sts = readl_pvt(pvt->base, PVT_RAW_INTR_STAT);
/*
* Then lets recharge the PVT interface with the next sampling mode.
*/
mutex_lock(&pvt->iface_mtx);
- old = pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
+ old = pvt_update(pvt->base, PVT_INTR_MASK, PVT_INTR_DVALID,
PVT_INTR_DVALID);
- val = readl(pvt->regs + PVT_DATA);
+ val = readl_pvt(pvt->base, PVT_DATA);
pvt_set_mode(pvt, pvt_info[pvt->sensor].mode);
- pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, old);
+ pvt_update(pvt->base, PVT_INTR_MASK, PVT_INTR_DVALID, old);
mutex_unlock(&pvt->iface_mtx);
struct pvt_cache *cache = &pvt->cache[type];
unsigned int seq;
u32 data;
-
+ pr_debug("pvt_read_data into val %x \n", val );
do {
seq = read_seqbegin(&cache->data_seqlock);
data = cache->data;
} while (read_seqretry(&cache->data_seqlock, seq));
-
+ data = readl_pvt(pvt->base, 0xc);
if (type == PVT_TEMP)
*val = polynomial_calc(&poly_N_to_temp, data);
else
- *val = polynomial_calc(&poly_N_to_volt, data);
+ *val = polynomial_calc(&poly_N_to_volt[tp], data);
return 0;
}
u32 data;
/* No need in serialization, since it is just read from MMIO. */
- data = readl(pvt->regs + pvt_info[type].thres_base);
-
+ data = readl_pvt(pvt->base, pvt_info[type].thres_base);
+ int tp = pvt->type_cpu;
if (is_low)
data = FIELD_GET(PVT_THRES_LO_MASK, data);
else
data = FIELD_GET(PVT_THRES_HI_MASK, data);
if (type == PVT_TEMP)
- *val = polynomial_calc(&poly_N_to_temp, data);
+ *val = pvt_calc_poly(&poly_N_to_temp[tp], data);
else
- *val = polynomial_calc(&poly_N_to_volt, data);
+ *val = pvt_calc_poly(&poly_N_to_volt[tp], data);
return 0;
}
if (type == PVT_TEMP) {
val = clamp(val, PVT_TEMP_MIN, PVT_TEMP_MAX);
- data = polynomial_calc(&poly_temp_to_N, val);
+ data = pvt_calc_poly(&poly_temp_to_N[pvt->type_cpu], val);
} else {
val = clamp(val, PVT_VOLT_MIN, PVT_VOLT_MAX);
- data = polynomial_calc(&poly_volt_to_N, val);
+ data = pvt_calc_poly(&poly_volt_to_N[pvt->type_cpu], val);
}
/* Serialize limit update, since a part of the register is changed. */
return ret;
/* Make sure the upper and lower ranges don't intersect. */
- limit = readl(pvt->regs + pvt_info[type].thres_base);
+ limit = readl_pvt(pvt->regs, pvt_info[type].thres_base);
if (is_low) {
limit = FIELD_GET(PVT_THRES_HI_MASK, limit);
data = clamp_val(data, PVT_DATA_MIN, limit);
mask = PVT_THRES_HI_MASK;
}
- pvt_update(pvt->regs + pvt_info[type].thres_base, mask, data);
+ pvt_update(pvt->regs, pvt_info[type].thres_base, mask, data);
mutex_unlock(&pvt->iface_mtx);
* Mask the DVALID interrupt so after exiting from the handler a
* repeated conversion wouldn't happen.
*/
- pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
+ pvt_update(pvt->base, PVT_INTR_MASK, PVT_INTR_DVALID, 0);
+ pvt_update(pvt->base, PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
+
+ pvt_update(pvt->base, PVT_CTRL, PVT_CTRL_EN, 0);
+ pvt_update(pvt->base, PVT_INTR_MASK, PVT_INTR_DVALID,
PVT_INTR_DVALID);
/*
* Nothing special for alarm-less driver. Just read the data, update
* the cache and notify a waiter of this event.
*/
- val = readl(pvt->regs + PVT_DATA);
- if (!(val & PVT_DATA_VALID)) {
- dev_err(pvt->dev, "Got IRQ when data isn't valid\n");
- return IRQ_HANDLED;
- }
+ val = readl_pvt(pvt->regs, PVT_DATA);
+ val &= PVT_DATA_DATA_MASK;
+ pr_debug("irqreturn_t pvt_hard_isr data %x %x \n",data, val );
+ //if (!(val & PVT_DATA_VALID)) {
+ // dev_err(pvt->dev, "Got IRQ when data isn't valid\n");
+ // return IRQ_HANDLED;
+ //}
cache = &pvt->cache[pvt->sensor];
-
+ pr_debug("irqreturn_t pvt_hard_isr cache->data %x \n",cache->data );
WRITE_ONCE(cache->data, FIELD_GET(PVT_DATA_DATA_MASK, val));
complete(&cache->conversion);
{
struct pvt_cache *cache = &pvt->cache[type];
unsigned long timeout;
- u32 data;
+ long data;
int ret;
/*
* Unmask the DVALID interrupt and enable the sensors conversions.
* Do the reverse procedure when conversion is done.
*/
- pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, 0);
- pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
+ pvt_update(pvt->base, PVT_INTR_MASK, PVT_INTR_DVALID, 0);
+ pvt_update(pvt->base, PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
/*
* Wait with timeout since in case if the sensor is suddenly powered
timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout));
ret = wait_for_completion_timeout(&cache->conversion, timeout);
- pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
- pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
+ pvt_update(pvt->base, PVT_CTRL, PVT_CTRL_EN, 0);
+ pvt_update(pvt->base, PVT_INTR_MASK, PVT_INTR_DVALID,
PVT_INTR_DVALID);
-
- data = READ_ONCE(cache->data);
-
+///bs No alarm read hear TODO read from cache as for bt1
+///bs data = READ_ONCE(cache->data);
+ data = readl_pvt(pvt->base, PVT_DATA);
+ data &= PVT_DATA_DATA_MASK;
mutex_unlock(&pvt->iface_mtx);
- if (!ret)
- return -ETIMEDOUT;
+ //bs if (!ret)
+ //bs return -ETIMEDOUT;
+ int tp = pvt->type_cpu;
if (type == PVT_TEMP)
- *val = polynomial_calc(&poly_N_to_temp, data);
+ *val = pvt_calc_poly(&poly_N_to_temp[tp], data);
else
- *val = polynomial_calc(&poly_N_to_volt, data);
+ *val = pvt_calc_poly(&poly_N_to_volt[tp], data);
return 0;
}
{
u32 data;
- data = readl(pvt->regs + PVT_CTRL);
+ data = readl_pvt(pvt->base, PVT_CTRL);
*val = FIELD_GET(PVT_CTRL_TRIM_MASK, data) * PVT_TRIM_STEP;
return 0;
}
pvt->regs = devm_ioremap_resource(pvt->dev, res);
+ pvt->regs = pvt->base= (int)res->start;
if (IS_ERR(pvt->regs))
return PTR_ERR(pvt->regs);
return 0;
}
+static int pvt_check_addr(struct pvt_hwmon *pvt)
+{
+ struct platform_device *pdev = to_platform_device(pvt->dev);
+ int hwmon_type_cpu = (enum chips)device_get_match_data(&pdev->dev);
+
+ if (hwmon_type_cpu == bm1000 &&
+ (pvt->base == MMCA57_0_PVT_BASE ||
+ pvt->base == MMCA57_1_PVT_BASE ||
+ pvt->base == MMCA57_2_PVT_BASE ||
+ pvt->base == MMCA57_3_PVT_BASE ||
+ pvt->base == MMMALI_PVT_BASE)) {
+ pvt->type_cpu = bm1000;
+ } else if (hwmon_type_cpu == bs1000 &&
+ (pvt->base == CA75_0_PVT_BASE ||
+ pvt->base == CA75_1_PVT_BASE ||
+ pvt->base == CA75_2_PVT_BASE ||
+ pvt->base == CA75_3_PVT_BASE ||
+ pvt->base == CA75_4_PVT_BASE ||
+ pvt->base == CA75_5_PVT_BASE ||
+ pvt->base == CA75_6_PVT_BASE ||
+ pvt->base == CA75_7_PVT_BASE ||
+ pvt->base == CA75_8_PVT_BASE ||
+ pvt->base == CA75_9_PVT_BASE ||
+ pvt->base == CA75_10_PVT_BASE ||
+ pvt->base == CA75_11_PVT_BASE ||
+ pvt->base == DDR0_PVT_BASE ||
+ pvt->base == DDR1_PVT_BASE ||
+ pvt->base == DDR2_PVT_BASE ||
+ pvt->base == DDR3_PVT_BASE ||
+ pvt->base == DDR4_PVT_BASE ||
+ pvt->base == DDR5_PVT_BASE ||
+ pvt->base == PCIE0_PVT_BASE ||
+ pvt->base == PCIE1_PVT_BASE ||
+ pvt->base == PCIE2_PVT_BASE ||
+ pvt->base == PCIE3_PVT_BASE ||
+ pvt->base == PCIE4_PVT_BASE)) {
+ pvt->type_cpu = bs1000;
+ } else {
+ dev_err(&pdev->dev, "invalid base address or failed to get type: 0x%x\n", pvt->base);
+ return -EINVAL;
+ }
+ pr_debug("pvt_probe pvt_check_addr. hwmon_type_cpu is %d\n", hwmon_type_cpu);
+ return 0;
+}
static void pvt_disable_clks(void *data)
{
static int pvt_request_clks(struct pvt_hwmon *pvt)
{
- int ret;
+ int ret = 0;
pvt->clks[PVT_CLOCK_APB].id = "pclk";
pvt->clks[PVT_CLOCK_REF].id = "ref";
-
- ret = devm_clk_bulk_get(pvt->dev, PVT_CLOCK_NUM, pvt->clks);
+ //bs ret = devm_clk_bulk_get(pvt->dev, PVT_CLOCK_NUM, pvt->clks);
if (ret) {
dev_err(pvt->dev, "Couldn't get PVT clocks descriptors\n");
return ret;
}
- ret = clk_bulk_prepare_enable(PVT_CLOCK_NUM, pvt->clks);
+ //bs ret = clk_bulk_prepare_enable(PVT_CLOCK_NUM, pvt->clks);
if (ret) {
dev_err(pvt->dev, "Couldn't enable the PVT clocks\n");
return ret;
}
- ret = devm_add_action_or_reset(pvt->dev, pvt_disable_clks, pvt);
+ //bs ret = devm_add_action_or_reset(pvt->dev, pvt_disable_clks, pvt);
if (ret) {
dev_err(pvt->dev, "Can't add PVT clocks disable action\n");
return ret;
* conversion. In the later case alas we won't be able to detect the
* problem.
*/
- pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL);
- pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
+ pvt_update(pvt->regs, PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL);
+ pvt_update(pvt->regs, PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
pvt_set_tout(pvt, 0);
- readl(pvt->regs + PVT_DATA);
+ readl_pvt(pvt->regs, PVT_DATA);
tout = PVT_TOUT_MIN / NSEC_PER_USEC;
usleep_range(tout, 2 * tout);
- data = readl(pvt->regs + PVT_DATA);
+ data = readl_pvt(pvt->regs, PVT_DATA);
if (!(data & PVT_DATA_VALID)) {
ret = -ENODEV;
dev_err(pvt->dev, "Sensor is powered down\n");
}
- pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
+ pvt_update(pvt->regs, PVT_CTRL, PVT_CTRL_EN, 0);
return ret;
}
* accidentally have ISR executed before the driver data is fully
* initialized. Clear the IRQ status as well.
*/
- pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL);
- pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
- readl(pvt->regs + PVT_CLR_INTR);
- readl(pvt->regs + PVT_DATA);
+ pvt_update(pvt->regs, PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL);
+ pvt_update(pvt->regs, PVT_CTRL, PVT_CTRL_EN, 0);
+ readl_pvt(pvt->regs, PVT_CLR_INTR);
+ readl_pvt(pvt->regs, PVT_DATA);
/* Setup default sensor mode, timeout and temperature trim. */
pvt_set_mode(pvt, pvt_info[pvt->sensor].mode);
struct pvt_hwmon *pvt = data;
mutex_lock(&pvt->iface_mtx);
- pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
- pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
+ pvt_update(pvt->regs, PVT_CTRL, PVT_CTRL_EN, 0);
+ pvt_update(pvt->regs, PVT_INTR_MASK, PVT_INTR_DVALID,
PVT_INTR_DVALID);
mutex_unlock(&pvt->iface_mtx);
}
* which theoretically may cause races.
*/
mutex_lock(&pvt->iface_mtx);
- pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, 0);
- pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
+ pvt_update(pvt->regs, PVT_INTR_MASK, PVT_INTR_DVALID, 0);
+ pvt_update(pvt->regs, PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
mutex_unlock(&pvt->iface_mtx);
return 0;
if (ret)
return ret;
- ret = pvt_check_pwr(pvt);
+ //bs ret = pvt_check_pwr(pvt);
if (ret)
return ret;
- ret = pvt_init_iface(pvt);
+ //bs ret = pvt_init_iface(pvt);
if (ret)
return ret;
- ret = pvt_request_irq(pvt);
+ //bs ret = pvt_request_irq(pvt);
if (ret)
return ret;
if (ret)
return ret;
+ ret = pvt_check_addr(pvt);
+ if(ret)
+ return ret;
return 0;
}
static const struct of_device_id pvt_of_match[] = {
- { .compatible = "baikal,bt1-pvt" },
+ { .compatible = "baikal,bt1-pvt", .data = (void *)bm1000 },
+ { .compatible = "baikal,bs1000-pvt", .data = (void *)bs1000 },
+ { .compatible = "baikal,bm1000-pvt", .data = (void *)bm1000 },
{ }
};
MODULE_DEVICE_TABLE(of, pvt_of_match);
#include <linux/mutex.h>
#include <linux/seqlock.h>
+#define BAIKAL_SMC_PVT_CMD 0x82000001
+#define PVT_READ 0
+#define PVT_WRITE 1
+enum chips { bt1000, bm1000, bs1000, chips_number };
+
+/* "baikal,bm1000-pvt" base addresses */
+#define MMCA57_0_PVT_BASE 0x28200000
+#define MMCA57_1_PVT_BASE 0xc200000
+#define MMCA57_2_PVT_BASE 0xa200000
+#define MMCA57_3_PVT_BASE 0x26200000
+#define MMMALI_PVT_BASE 0x2a060000
+
+/* "baikal,bs1000-pvt" base addresses */
+#define CA75_0_PVT_BASE 0x4030000
+#define CA75_1_PVT_BASE 0x8030000
+#define CA75_2_PVT_BASE 0xc030000
+#define CA75_3_PVT_BASE 0x10030000
+#define CA75_4_PVT_BASE 0x14030000
+#define CA75_5_PVT_BASE 0x18030000
+#define CA75_6_PVT_BASE 0x1c030000
+#define CA75_7_PVT_BASE 0x20030000
+#define CA75_8_PVT_BASE 0x24030000
+#define CA75_9_PVT_BASE 0x28030000
+#define CA75_10_PVT_BASE 0x2c030000
+#define CA75_11_PVT_BASE 0x30030000
+#define PCIE0_PVT_BASE 0x38030000
+#define PCIE1_PVT_BASE 0x3c030000
+#define PCIE2_PVT_BASE 0x44030000
+#define PCIE3_PVT_BASE 0x48030000
+#define PCIE4_PVT_BASE 0x4c030000
+#define DDR0_PVT_BASE 0x50030000
+#define DDR1_PVT_BASE 0x54030000
+#define DDR2_PVT_BASE 0x58030000
+#define DDR3_PVT_BASE 0x60030000
+#define DDR4_PVT_BASE 0x64030000
+#define DDR5_PVT_BASE 0x68030000
+
/* Baikal-T1 PVT registers and their bitfields */
#define PVT_CTRL 0x00
#define PVT_CTRL_EN BIT(0)
#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
# define PVT_TOUT_DEF 60000
#else
-# define PVT_TOUT_DEF 0
+# define PVT_TOUT_DEF 1000
#endif
/*
struct device *hwmon;
void __iomem *regs;
+ int base;
+ enum chips type_cpu;
int irq;
struct clk_bulk_data clks[PVT_CLOCK_NUM];
* @total_divider: total data divider.
* @terms: polynomial terms up to a free one.
*/
+#define MAX_POLY_POWER 5
+
struct pvt_poly {
long total_divider;
- struct pvt_poly_term terms[];
+ struct pvt_poly_term terms[MAX_POLY_POWER];
};
#endif /* __HWMON_BT1_PVT_H__ */
/*
* Copyright (c) 2019, The Linaro Limited. All rights reserved.
*/
+#include <linux/acpi.h>
#include <linux/coresight.h>
#include <linux/device.h>
#include <linux/err.h>
#endif
+#ifdef CONFIG_ACPI
+
+#include <acpi/actypes.h>
+#include <acpi/processor.h>
+
+/*
+ * acpi_handle_to_logical_cpuid - Map a given acpi_handle to the
+ * logical CPU id of the corresponding CPU device.
+ *
+ * Returns the logical CPU id when found. Otherwise returns >= nr_cpus_id.
+ */
+static int
+acpi_handle_to_logical_cpuid(acpi_handle handle)
+{
+ int i;
+ struct acpi_processor *pr;
+
+ for_each_possible_cpu(i) {
+ pr = per_cpu(processors, i);
+ if (pr && pr->handle == handle)
+ break;
+ }
+
+ return i;
+}
+
+static int acpi_cti_get_cpu_at_node(struct fwnode_handle *fwnode)
+{
+ int cpu;
+ acpi_handle cpu_handle;
+ acpi_status status;
+ struct acpi_device *adev = to_acpi_device_node(fwnode);
+
+ if (!adev)
+ return -1;
+ status = acpi_get_parent(adev->handle, &cpu_handle);
+ if (ACPI_FAILURE(status))
+ return -1;
+
+ cpu = acpi_handle_to_logical_cpuid(cpu_handle);
+ if (cpu >= nr_cpu_ids)
+ return -1;
+ return cpu;
+}
+
+#else
+static int acpi_cti_get_cpu_at_node(struct fwnode_handle *fwnode)
+{
+ return -1;
+}
+
+#endif
/*
* CTI can be bound to a CPU, or a system device.
* CPU can be declared at the device top level or in a connections node
{
if (is_of_node(fwnode))
return of_cti_get_cpu_at_node(to_of_node(fwnode));
+ else if (is_acpi_device_node(fwnode))
+ return acpi_cti_get_cpu_at_node(fwnode);
return -1;
}
{
if (is_of_node(fwnode))
return of_node_full_name(to_of_node(fwnode));
+ else if (is_acpi_device_node(fwnode))
+ return fwnode_get_name(fwnode);
return "unknown";
}
return -EINVAL;
fwnode_for_each_child_node(fwnode, child) {
- if (cti_plat_node_name_eq(child, CTI_DT_CONNS))
+ if (cti_plat_node_name_eq(child, CTI_DT_CONNS) ||
+ is_acpi_device_node(fwnode))
rc = cti_plat_create_connection(dev, drvdata,
child);
if (rc != 0)
device_property_read_u32(dev, CTI_DT_CTM_ID, &cti_dev->ctm_id);
/* check for a v8 architectural CTI device */
- if (cti_plat_check_v8_arch_compatible(dev))
+ if (cti_plat_check_v8_arch_compatible(dev) ||
+ device_property_present(dev, CTI_DT_V8ARCH_COMPAT))
rc = cti_plat_create_v8_connections(dev, drvdata);
else
rc = cti_plat_create_impdef_connections(dev, drvdata);
255 bytes in length. Any attempt to to a larger transfer will return
an error.
+config I2C_BAIKAL_SMBUS
+ tristate "Baikal-M SoC SMBus interface"
+
config I2C_BCM2835
tristate "Broadcom BCM2835 I2C controller"
depends on ARCH_BCM2835 || ARCH_BRCMSTB || COMPILE_TEST
endif
obj-$(CONFIG_I2C_AU1550) += i2c-au1550.o
obj-$(CONFIG_I2C_AXXIA) += i2c-axxia.o
+obj-$(CONFIG_I2C_BAIKAL_SMBUS) += i2c-baikal-smbus.o
obj-$(CONFIG_I2C_BCM2835) += i2c-bcm2835.o
obj-$(CONFIG_I2C_BCM_IPROC) += i2c-bcm-iproc.o
obj-$(CONFIG_I2C_CADENCE) += i2c-cadence.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SMBus controller driver for Baikal SoC
+ *
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ * Author: Georgy Vlasov <georgy.vlasov@baikalelectronics.ru>
+ */
+
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#define BAIKAL_SMBUS_CR1 0x00
+#define BAIKAL_CR1_IRT BIT(0)
+#define BAIKAL_CR1_TRS BIT(1)
+#define BAIKAL_CR1_IEB BIT(3)
+
+#define BAIKAL_SMBUS_CR2 0x04
+#define BAIKAL_CR2_FTE BIT(1)
+
+#define BAIKAL_SMBUS_FBCR1 0x08
+#define BAIKAL_SMBUS_FIFO 0x0c
+#define BAIKAL_SMBUS_SCD1 0x10
+
+#define BAIKAL_SMBUS_SCD2 0x14
+#define BAIKAL_SCD2_MASK 0x03
+#define BAIKAL_SCD2_SHT BIT(7)
+
+#define BAIKAL_SMBUS_ADR1 0x18
+
+#define BAIKAL_SMBUS_ISR1 0x20
+#define BAIKAL_ISR1_MASK 0x7f
+#define BAIKAL_ISR1_FER BIT(2)
+#define BAIKAL_ISR1_RNK BIT(3)
+#define BAIKAL_ISR1_ALD BIT(4)
+#define BAIKAL_ISR1_TCS BIT(6)
+
+#define BAIKAL_SMBUS_IMR1 0x24
+#define BAIKAL_SMBUS_FBCR2 0x2c
+#define BAIKAL_SMBUS_IMR2 0x48
+
+#define BAIKAL_SMBUS_FIFO_SIZE 16
+
+struct baikal_smbus_dev {
+ struct device *dev;
+ void __iomem *base;
+ struct clk *smbus_clk;
+ struct i2c_adapter adapter;
+ u32 bus_clk_rate;
+ u64 smbus_clk_rate;
+};
+
+static int baikal_smbus_init(struct baikal_smbus_dev *smbus)
+{
+ u32 divider = (smbus->smbus_clk_rate == 0 ?
+ clk_get_rate(smbus->smbus_clk) :
+ smbus->smbus_clk_rate) / smbus->bus_clk_rate - 1;
+
+ writel(BAIKAL_CR1_IRT, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(0, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(0, smbus->base + BAIKAL_SMBUS_CR2);
+ writel(0, smbus->base + BAIKAL_SMBUS_FBCR2);
+ writel(0, smbus->base + BAIKAL_SMBUS_IMR1);
+ writel(0, smbus->base + BAIKAL_SMBUS_IMR2);
+ writel(divider & 0xff, smbus->base + BAIKAL_SMBUS_SCD1);
+ writel(BAIKAL_SCD2_SHT | ((divider >> 8) & BAIKAL_SCD2_MASK),
+ smbus->base + BAIKAL_SMBUS_SCD2);
+
+ return 0;
+}
+
+static int baikal_smbus_xfer(struct i2c_adapter *adap, u16 addr,
+ unsigned short flags, char read_write,
+ u8 command, int size, union i2c_smbus_data *data)
+{
+ struct baikal_smbus_dev *const smbus = adap->dev.driver_data;
+ int ret = -EINVAL;
+
+ switch (size) {
+ case I2C_SMBUS_BYTE:
+ writel(BAIKAL_CR1_IRT, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(0, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(addr, smbus->base + BAIKAL_SMBUS_ADR1);
+ writel(1, smbus->base + BAIKAL_SMBUS_FBCR1);
+
+ if (read_write == I2C_SMBUS_WRITE) {
+ writel(BAIKAL_CR1_TRS | BAIKAL_CR1_IEB,
+ smbus->base + BAIKAL_SMBUS_CR1);
+ writel(command, smbus->base + BAIKAL_SMBUS_FIFO);
+ } else {
+ writel(BAIKAL_CR1_IEB, smbus->base + BAIKAL_SMBUS_CR1);
+ }
+
+ writel(BAIKAL_ISR1_MASK, smbus->base + BAIKAL_SMBUS_ISR1);
+ writel(BAIKAL_CR2_FTE, smbus->base + BAIKAL_SMBUS_CR2);
+
+ while ((readl(smbus->base + BAIKAL_SMBUS_CR2) & BAIKAL_CR2_FTE) &&
+ !(readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_TCS | BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)))
+ ;
+
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)) {
+ ret = -ENXIO;
+ goto exit;
+ }
+
+ if (read_write == I2C_SMBUS_READ) {
+ data->byte = readl(smbus->base + BAIKAL_SMBUS_FIFO);
+
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) & BAIKAL_ISR1_FER) {
+ ret = -EMSGSIZE;
+ goto exit;
+ }
+ }
+
+ ret = 0;
+ break;
+ case I2C_SMBUS_BYTE_DATA:
+ writel(BAIKAL_CR1_IRT, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(0, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(addr, smbus->base + BAIKAL_SMBUS_ADR1);
+ writel(BAIKAL_CR1_TRS | BAIKAL_CR1_IEB, smbus->base + BAIKAL_SMBUS_CR1);
+
+ if (read_write == I2C_SMBUS_WRITE) {
+ writel(2, smbus->base + BAIKAL_SMBUS_FBCR1);
+ writel(command, smbus->base + BAIKAL_SMBUS_FIFO);
+ writel(data->byte, smbus->base + BAIKAL_SMBUS_FIFO);
+ } else {
+ writel(1, smbus->base + BAIKAL_SMBUS_FBCR1);
+ writel(command, smbus->base + BAIKAL_SMBUS_FIFO);
+ }
+
+ writel(BAIKAL_ISR1_MASK, smbus->base + BAIKAL_SMBUS_ISR1);
+ writel(BAIKAL_CR2_FTE, smbus->base + BAIKAL_SMBUS_CR2);
+
+ while ((readl(smbus->base + BAIKAL_SMBUS_CR2) & BAIKAL_CR2_FTE) &&
+ !(readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_TCS | BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)))
+ ;
+
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)) {
+ ret = -ENXIO;
+ goto exit;
+ }
+
+ if (read_write == I2C_SMBUS_READ) {
+ writel(BAIKAL_CR1_IEB, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(1, smbus->base + BAIKAL_SMBUS_FBCR1);
+ writel(BAIKAL_ISR1_MASK, smbus->base + BAIKAL_SMBUS_ISR1);
+ writel(BAIKAL_CR2_FTE, smbus->base + BAIKAL_SMBUS_CR2);
+
+ while ((readl(smbus->base + BAIKAL_SMBUS_CR2) & BAIKAL_CR2_FTE) &&
+ !(readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_TCS | BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)))
+ ;
+
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)) {
+ ret = -ENXIO;
+ goto exit;
+ }
+
+ data->byte = readl(smbus->base + BAIKAL_SMBUS_FIFO);
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) & BAIKAL_ISR1_FER) {
+ ret = -EMSGSIZE;
+ goto exit;
+ }
+ }
+
+ ret = 0;
+ break;
+ case I2C_SMBUS_WORD_DATA:
+ writel(BAIKAL_CR1_IRT, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(0, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(addr, smbus->base + BAIKAL_SMBUS_ADR1);
+ writel(BAIKAL_CR1_TRS | BAIKAL_CR1_IEB, smbus->base + BAIKAL_SMBUS_CR1);
+
+ if (read_write == I2C_SMBUS_WRITE) {
+ writel(3, smbus->base + BAIKAL_SMBUS_FBCR1);
+ writel(command, smbus->base + BAIKAL_SMBUS_FIFO);
+ writel((data->word >> 0) & 0xff,
+ smbus->base + BAIKAL_SMBUS_FIFO);
+ writel((data->word >> 8) & 0xff,
+ smbus->base + BAIKAL_SMBUS_FIFO);
+ } else {
+ writel(1, smbus->base + BAIKAL_SMBUS_FBCR1);
+ writel(command, smbus->base + BAIKAL_SMBUS_FIFO);
+ }
+
+ writel(BAIKAL_ISR1_MASK, smbus->base + BAIKAL_SMBUS_ISR1);
+ writel(BAIKAL_CR2_FTE, smbus->base + BAIKAL_SMBUS_CR2);
+
+ while ((readl(smbus->base + BAIKAL_SMBUS_CR2) & BAIKAL_CR2_FTE) &&
+ !(readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_TCS | BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)))
+ ;
+
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)) {
+ ret = -ENXIO;
+ goto exit;
+ }
+
+ if (read_write == I2C_SMBUS_READ) {
+ writel(BAIKAL_CR1_IEB, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(2, smbus->base + BAIKAL_SMBUS_FBCR1);
+ writel(BAIKAL_ISR1_MASK, smbus->base + BAIKAL_SMBUS_ISR1);
+ writel(BAIKAL_CR2_FTE, smbus->base + BAIKAL_SMBUS_CR2);
+
+ while ((readl(smbus->base + BAIKAL_SMBUS_CR2) & BAIKAL_CR2_FTE) &&
+ !(readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_TCS | BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)))
+ ;
+
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)) {
+ ret = -ENXIO;
+ goto exit;
+ }
+
+ data->word = readl(smbus->base + BAIKAL_SMBUS_FIFO);
+ data->word |= readl(smbus->base + BAIKAL_SMBUS_FIFO) << 8;
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) & BAIKAL_ISR1_FER) {
+ ret = -EMSGSIZE;
+ goto exit;
+ }
+ }
+
+ ret = 0;
+ break;
+ case I2C_SMBUS_BLOCK_DATA:
+ writel(BAIKAL_CR1_IRT, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(0, smbus->base + BAIKAL_SMBUS_CR1);
+ writel(addr, smbus->base + BAIKAL_SMBUS_ADR1);
+ writel(BAIKAL_CR1_TRS | BAIKAL_CR1_IEB, smbus->base + BAIKAL_SMBUS_CR1);
+
+ if (read_write == I2C_SMBUS_WRITE) {
+ unsigned int txedsize = 0;
+
+ while (txedsize < data->block[0] + 1) {
+ unsigned int xblocknum;
+ unsigned int xfersize;
+
+ xfersize = data->block[0] + 1 - txedsize;
+ if (xfersize > BAIKAL_SMBUS_FIFO_SIZE)
+ xfersize = BAIKAL_SMBUS_FIFO_SIZE;
+
+ writel(xfersize, smbus->base + BAIKAL_SMBUS_FBCR1);
+ if (!txedsize) {
+ /*
+ * The first xfer should start with
+ * command byte
+ */
+ writel(command,
+ smbus->base + BAIKAL_SMBUS_FIFO);
+ xblocknum = 1;
+ ++txedsize;
+ } else {
+ xblocknum = 0;
+ }
+
+ while (xblocknum < xfersize) {
+ writel(data->block[txedsize++],
+ smbus->base + BAIKAL_SMBUS_FIFO);
+ ++xblocknum;
+ }
+
+ writel(BAIKAL_ISR1_MASK, smbus->base + BAIKAL_SMBUS_ISR1);
+ writel(BAIKAL_CR2_FTE, smbus->base + BAIKAL_SMBUS_CR2);
+
+ while ((readl(smbus->base + BAIKAL_SMBUS_CR2) & BAIKAL_CR2_FTE) &&
+ !(readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_TCS | BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)))
+ ;
+
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)) {
+ ret = -ENXIO;
+ goto exit;
+ }
+ }
+ } else {
+ unsigned int rxedsize = 0;
+
+ writel(1, smbus->base + BAIKAL_SMBUS_FBCR1);
+ writel(command, smbus->base + BAIKAL_SMBUS_FIFO);
+ writel(BAIKAL_ISR1_MASK, smbus->base + BAIKAL_SMBUS_ISR1);
+ writel(BAIKAL_CR2_FTE, smbus->base + BAIKAL_SMBUS_CR2);
+
+ while ((readl(smbus->base + BAIKAL_SMBUS_CR2) & BAIKAL_CR2_FTE) &&
+ !(readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_TCS | BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)))
+ ;
+
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)) {
+ ret = -ENXIO;
+ goto exit;
+ }
+
+ writel(BAIKAL_CR1_IEB, smbus->base + BAIKAL_SMBUS_CR1);
+
+ while (rxedsize < I2C_SMBUS_BLOCK_MAX) {
+ unsigned int i;
+
+ writel(BAIKAL_SMBUS_FIFO_SIZE, smbus->base + BAIKAL_SMBUS_FBCR1);
+ writel(BAIKAL_ISR1_MASK, smbus->base + BAIKAL_SMBUS_ISR1);
+ writel(BAIKAL_CR2_FTE, smbus->base + BAIKAL_SMBUS_CR2);
+
+ while ((readl(smbus->base + BAIKAL_SMBUS_CR2) & BAIKAL_CR2_FTE) &&
+ !(readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_TCS | BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)))
+ ;
+
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ (BAIKAL_ISR1_ALD | BAIKAL_ISR1_RNK)) {
+ ret = -ENXIO;
+ goto exit;
+ }
+
+ for (i = 0; i < BAIKAL_SMBUS_FIFO_SIZE; ++i) {
+ data->block[1 + rxedsize++] =
+ readl(smbus->base + BAIKAL_SMBUS_FIFO);
+
+ if (readl(smbus->base + BAIKAL_SMBUS_ISR1) &
+ BAIKAL_ISR1_FER) {
+ ret = -EMSGSIZE;
+ goto exit;
+ }
+ }
+ }
+
+ data->block[0] = rxedsize;
+ }
+
+ ret = 0;
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+exit:
+ writel(0, smbus->base + BAIKAL_SMBUS_CR1);
+ return ret;
+}
+
+static u32 baikal_functionality(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+static const struct i2c_algorithm baikal_smbus_algorithm = {
+ .smbus_xfer = baikal_smbus_xfer,
+ .functionality = baikal_functionality
+};
+
+static irqreturn_t baikal_smbus_isr(int irq, void *_dev)
+{
+ return IRQ_HANDLED;
+}
+
+static int baikal_smbus_probe(struct platform_device *pdev)
+{
+ struct baikal_smbus_dev *smbus;
+ void __iomem *base;
+ int irq;
+ int ret;
+
+ smbus = devm_kzalloc(&pdev->dev, sizeof(*smbus), GFP_KERNEL);
+ if (!smbus)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "missing interrupt resource\n");
+ return irq;
+ }
+
+ if (dev_of_node(&pdev->dev)) {
+ smbus->smbus_clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(smbus->smbus_clk)) {
+ dev_err(&pdev->dev, "missing clock\n");
+ return PTR_ERR(smbus->smbus_clk);
+ }
+
+ ret = clk_prepare_enable(smbus->smbus_clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable clock\n");
+ return ret;
+ }
+
+ smbus->smbus_clk_rate = 0;
+#ifdef CONFIG_ACPI
+ } else if (to_acpi_device_node(pdev->dev.fwnode) &&
+ acpi_evaluate_integer(to_acpi_device_node(pdev->dev.fwnode)->handle,
+ "CLK", NULL, &smbus->smbus_clk_rate)) {
+ dev_err(&pdev->dev, "missing clock-frequency value\n");
+ return -EINVAL;
+#endif
+ }
+
+ smbus->base = base;
+ smbus->dev = &pdev->dev;
+
+ device_property_read_u32(&pdev->dev, "clock-frequency",
+ &smbus->bus_clk_rate);
+ if (!smbus->bus_clk_rate)
+ smbus->bus_clk_rate = 100000; /* default clock rate */
+
+ ret = devm_request_irq(&pdev->dev, irq, baikal_smbus_isr, 0,
+ pdev->name, smbus);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim IRQ%d\n", irq);
+ return ret;
+ }
+
+ i2c_set_adapdata(&smbus->adapter, smbus);
+ strscpy(smbus->adapter.name, pdev->name, sizeof(smbus->adapter.name));
+ smbus->adapter.owner = THIS_MODULE;
+ smbus->adapter.algo = &baikal_smbus_algorithm;
+ smbus->adapter.dev.parent = &pdev->dev;
+ if (dev_of_node(&pdev->dev))
+ smbus->adapter.dev.of_node = pdev->dev.of_node;
+ else
+ smbus->adapter.dev.fwnode = pdev->dev.fwnode;
+
+ platform_set_drvdata(pdev, smbus);
+
+ ret = baikal_smbus_init(smbus);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to init SMBus\n");
+ goto err_clk;
+ }
+
+ ret = i2c_add_adapter(&smbus->adapter);
+ if (ret)
+ goto err_clk;
+
+ return 0;
+
+err_clk:
+ if (dev_of_node(&pdev->dev))
+ clk_disable_unprepare(smbus->smbus_clk);
+
+ return ret;
+}
+
+static int baikal_smbus_remove(struct platform_device *pdev)
+{
+ struct baikal_smbus_dev *smbus = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(smbus->smbus_clk);
+ i2c_del_adapter(&smbus->adapter);
+ return 0;
+}
+
+static const struct of_device_id baikal_smbus_match[] = {
+ { .compatible = "baikal,bm1000-smbus" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, baikal_smbus_match);
+
+static struct platform_driver baikal_smbus_driver = {
+ .driver = {
+ .name = "baikal-smbus",
+ .of_match_table = baikal_smbus_match
+ },
+ .probe = baikal_smbus_probe,
+ .remove = baikal_smbus_remove
+};
+
+module_platform_driver(baikal_smbus_driver);
+
+MODULE_AUTHOR("Georgy Vlasov <georgy.vlasov@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal SMBus driver");
+MODULE_LICENSE("GPL v2");
If you do not intend to run this kernel as a guest, say N.
+config TP_BMC
+ tristate "T-Platforms Baikal-T(1)/M BMC"
+ depends on I2C && OF
+ select PINCTRL
+ select GENERIC_PINCONF
+ select SERIO
+ help
+ Say Y here if you want to build a driver for T-Platforms BMC devices
+ embedded into the boards with Baikal-T(1)/M processors. The device main
+ purpose is the CPU kick-starting as well as some additional side-way
+ functionality like power on/off buttons state tracing and full device
+ powering off.
+
+ If you choose to build module, its name will be tp-bt-bmc. If unsure,
+ say N here.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
obj-$(CONFIG_OPEN_DICE) += open-dice.o
obj-$(CONFIG_GP_PCI1XXXX) += mchp_pci1xxxx/
obj-$(CONFIG_VCPU_STALL_DETECTOR) += vcpu_stall_detector.o
+obj-$(CONFIG_TP_BMC) += tp_bmc.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/i2c.h>
+#include <linux/bcd.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/input.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/pm.h>
+#include <linux/rtc.h>
+#include <linux/serio.h>
+#include <linux/platform_device.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+
+enum I2C_REGS {
+ R_ID1 = 0,
+ R_ID2,
+ R_ID3,
+ R_ID4,
+ R_SOFTOFF_RQ,
+ R_PWROFF_RQ,
+ R_PWRBTN_STATE,
+ R_VERSION1,
+ R_VERSION2,
+ R_BOOTREASON,
+ R_BOOTREASON_ARG,
+ R_SCRATCH1,
+ R_SCRATCH2,
+ R_SCRATCH3,
+ R_SCRATCH4,
+ R_CAP,
+ R_GPIODIR0,
+ R_GPIODIR1,
+ R_GPIODIR2,
+ R_COUNT
+};
+
+#define BMC_ID1_VAL 0x49
+#define BMC_ID2_VAL 0x54
+#define BMC_ID3_VAL 0x58
+#define BMC_ID4_VAL0 0x32
+#define BMC_ID4_VAL1 0x2
+
+#define BMC_VERSION1 0
+#define BMC_VERSION2 2
+#define BMC_VERSION2_3 3
+
+#define BMC_CAP_PWRBTN 0x1
+#define BMC_CAP_TOUCHPAD 0x2
+#define BMC_CAP_RTC 0x4
+#define BMC_CAP_FRU 0x8
+#define BMC_CAP_GPIODIR 0x10
+
+#define BMC_SERIO_BUFSIZE 7
+
+struct bmc_poll_data {
+ struct i2c_client *c;
+};
+
+static struct i2c_client *bmc_i2c;
+static struct i2c_client *rtc_i2c;
+static struct i2c_driver mitx2_bmc_i2c_driver;
+static struct input_dev *button_dev;
+static struct bmc_poll_data poll_data;
+static struct task_struct *polling_task;
+#ifdef CONFIG_SERIO
+static struct i2c_client *serio_i2c;
+static struct task_struct *touchpad_task;
+#endif
+static u8 bmc_proto_version[3];
+static u8 bmc_bootreason[2];
+static u8 bmc_scratch[4];
+static int bmc_cap;
+static const char input_name[] = "BMC input dev";
+static u8 prev_softoff_ret;
+
+static int bmc_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ uint8_t rtc_buf[8];
+ struct i2c_msg msg;
+ int t;
+ int rc;
+
+ msg.addr = client->addr;
+ msg.flags = I2C_M_RD;
+ msg.len = 8;
+ msg.buf = rtc_buf;
+ rc = i2c_transfer(client->adapter, &msg, 1);
+ if (rc != 1) {
+ dev_err(dev, "rtc_read_time: i2c_transfer error %d\n", rc);
+ return rc;
+ }
+
+ tm->tm_sec = bcd2bin(rtc_buf[0] & 0x7f);
+ tm->tm_min = bcd2bin(rtc_buf[1] & 0x7f);
+ tm->tm_hour = bcd2bin(rtc_buf[2] & 0x3f);
+ if (rtc_buf[3] & (1 << 6)) /* PM */
+ tm->tm_hour += 12;
+
+ tm->tm_mday = bcd2bin(rtc_buf[4] & 0x3f);
+ tm->tm_mon = bcd2bin(rtc_buf[5] & 0x1f);
+ t = rtc_buf[5] >> 5;
+ tm->tm_wday = (t == 7) ? 0 : t;
+ tm->tm_year = bcd2bin(rtc_buf[6]) + 100; /* year since 1900 */
+ tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
+ tm->tm_isdst = 0;
+
+ return rtc_valid_tm(tm);
+}
+
+static int bmc_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ uint8_t rtc_buf[8];
+ struct i2c_msg msg;
+ int rc;
+ uint8_t seconds, minutes, hours, wday, mday, month, years;
+
+ seconds = bin2bcd(tm->tm_sec);
+ minutes = bin2bcd(tm->tm_min);
+ hours = bin2bcd(tm->tm_hour);
+ wday = tm->tm_wday ? tm->tm_wday : 0x7;
+ mday = bin2bcd(tm->tm_mday);
+ month = bin2bcd(tm->tm_mon);
+ years = bin2bcd(tm->tm_year % 100);
+
+ /* TODO: need sanity check? */
+ rtc_buf[0] = seconds;
+ rtc_buf[1] = minutes;
+ rtc_buf[2] = hours;
+ rtc_buf[3] = 0;
+ rtc_buf[4] = mday;
+ rtc_buf[5] = month | (wday << 5);
+ rtc_buf[6] = years;
+ rtc_buf[7] = 0;
+
+ msg.addr = client->addr;
+ msg.flags = 0;
+ msg.len = 8;
+ msg.buf = rtc_buf;
+
+ dev_dbg(dev, "rtc_set_time: %08x-%08x\n",
+ *(uint32_t *)&rtc_buf[0],
+ *(uint32_t *)&rtc_buf[4]);
+
+ rc = i2c_transfer(client->adapter, &msg, 1);
+ if (rc != 1)
+ dev_err(dev, "i2c write: %d\n", rc);
+
+ return rc == 1 ? 0 : -EIO;
+}
+
+static const struct rtc_class_ops bmc_rtc_ops = {
+ .read_time = bmc_rtc_read_time,
+ .set_time = bmc_rtc_set_time,
+};
+
+#ifdef CONFIG_SERIO
+/* BMC serio (PS/2 touchpad) interface */
+
+static int bmc_serio_write(struct serio *id, unsigned char val)
+{
+ struct i2c_client *client = id->port_data;
+ uint8_t buf[4];
+ struct i2c_msg msg;
+ int rc;
+
+ buf[0] = val;
+ msg.addr = client->addr;
+ msg.flags = 0;
+ msg.len = 1;
+ msg.buf = buf;
+ dev_dbg(&client->dev, "bmc_serio_write: 0x%02x\n", val);
+ rc = i2c_transfer(client->adapter, &msg, 1);
+ if (rc != 1)
+ dev_err(&client->dev, "i2c write: %d\n", rc);
+
+ return rc == 1 ? 0 : -EIO;
+}
+
+/* returns: -1 on error, +1 if more data available, 0 otherwise */
+static int bmc_serio_read(struct i2c_client *client)
+{
+ struct serio *serio = dev_get_drvdata(&client->dev);
+ int i, rc, cnt;
+ uint8_t buf[BMC_SERIO_BUFSIZE];
+ struct i2c_msg msg;
+
+ msg.addr = client->addr;
+ msg.flags = I2C_M_RD;
+ msg.len = BMC_SERIO_BUFSIZE;
+ msg.buf = buf;
+ rc = i2c_transfer(client->adapter, &msg, 1);
+ if (rc != 1) {
+ dev_err(&client->dev,
+ "bmc_serio_read: i2c_transfer error %d\n", rc);
+ return -1;
+ }
+
+ cnt = buf[0];
+ rc = 0;
+ if (cnt > BMC_SERIO_BUFSIZE - 1) {
+ cnt = BMC_SERIO_BUFSIZE - 1;
+ rc = 1;
+ }
+
+ for (i = 0; i < cnt; i++)
+ serio_interrupt(serio, buf[i + 1], 0);
+
+ return 0;
+}
+
+int touchpad_poll_fn(void *data)
+{
+ int ret;
+
+ while (1) {
+ if (kthread_should_stop())
+ break;
+
+ while ((ret = bmc_serio_read(serio_i2c)) > 0)
+ ;
+
+ if (ret < 0)
+ msleep_interruptible(10000);
+
+ msleep_interruptible(10);
+ }
+
+ return 0;
+}
+#endif /* CONFIG_SERIO */
+
+#ifdef CONFIG_PINCTRL
+static uint8_t bmc_pincf_state [3];
+#define BMC_NPINS (sizeof(bmc_pincf_state) * 8)
+
+static struct pinctrl_pin_desc bmc_pin_desc[BMC_NPINS] = {
+ PINCTRL_PIN(0, "P0"),
+ PINCTRL_PIN(1, "P1"),
+ PINCTRL_PIN(2, "P2"),
+ PINCTRL_PIN(3, "P3"),
+ PINCTRL_PIN(4, "P4"),
+ PINCTRL_PIN(5, "P5"),
+ PINCTRL_PIN(6, "P6"),
+ PINCTRL_PIN(7, "P7"),
+ PINCTRL_PIN(8, "P8"),
+ PINCTRL_PIN(9, "P9"),
+ PINCTRL_PIN(10, "P10"),
+ PINCTRL_PIN(11, "P11"),
+ PINCTRL_PIN(12, "P12"),
+ PINCTRL_PIN(13, "P13"),
+ PINCTRL_PIN(14, "P14"),
+ PINCTRL_PIN(15, "P15"),
+ PINCTRL_PIN(16, "P16"),
+ PINCTRL_PIN(17, "P17"),
+ PINCTRL_PIN(18, "P18"),
+ PINCTRL_PIN(19, "P19"),
+ PINCTRL_PIN(20, "P20"),
+ PINCTRL_PIN(21, "P21"),
+ PINCTRL_PIN(22, "P22"),
+ PINCTRL_PIN(23, "P23"),
+};
+
+#define PCTRL_DEV "bmc_pinctrl"
+
+static int bmc_pin_config_get(struct pinctrl_dev *pctldev,
+ unsigned pin,
+ unsigned long *config)
+{
+ int idx, bit;
+
+ if (pin > BMC_NPINS)
+ return -EINVAL;
+
+ idx = pin >> 3;
+ bit = pin & 7;
+
+ *config = !!(bmc_pincf_state[idx] & (1 << bit));
+ return 0;
+}
+
+static int bmc_pin_config_set(struct pinctrl_dev *pctldev,
+ unsigned pin,
+ unsigned long *config,
+ unsigned nc)
+{
+ int idx, bit;
+ enum pin_config_param param;
+ int arg;
+
+ if (pin > BMC_NPINS)
+ return -EINVAL;
+
+ idx = pin >> 3;
+ bit = pin & 7;
+
+ param = pinconf_to_config_param (*config);
+ arg = pinconf_to_config_argument (*config);
+ if (param != PIN_CONFIG_OUTPUT)
+ return -EINVAL;
+
+ if (arg)
+ bmc_pincf_state[idx] |= (1 << bit);
+ else
+ bmc_pincf_state[idx] &= ~(1 << bit);
+
+ dev_dbg(&bmc_i2c->dev,
+ "bmc_pin_config_set: pin %u, dir %lu\n", pin, *config);
+
+ return i2c_smbus_write_byte_data(bmc_i2c, R_GPIODIR0 + idx,
+ bmc_pincf_state[idx]);
+}
+
+void pinconf_generic_dump_config(struct pinctrl_dev *pctldev,
+ struct seq_file *s, unsigned long config);
+
+void pinctrl_utils_free_map(struct pinctrl_dev *pctldev,
+ struct pinctrl_map *map, unsigned num_maps);
+
+static const struct pinconf_ops bmc_confops = {
+ .pin_config_get = bmc_pin_config_get,
+ .pin_config_set = bmc_pin_config_set,
+ .pin_config_config_dbg_show = pinconf_generic_dump_config,
+};
+
+static int bmc_groups_count(struct pinctrl_dev *pctldev)
+{
+ return 0;
+}
+
+static const char *bmc_group_name(struct pinctrl_dev *pctldev,
+ unsigned selector)
+{
+ return NULL;
+}
+
+static const struct pinctrl_ops bmc_ctrl_ops = {
+ .get_groups_count = bmc_groups_count,
+ .get_group_name = bmc_group_name,
+ .dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
+ .dt_free_map = pinctrl_utils_free_map,
+};
+
+static struct pinctrl_desc bmc_pincrtl_desc = {
+ .name = PCTRL_DEV,
+ .pins = bmc_pin_desc,
+ .pctlops = &bmc_ctrl_ops,
+ .npins = BMC_NPINS,
+ .confops = &bmc_confops,
+};
+
+static struct pinctrl_dev *bmc_pinctrl_dev;
+
+static int bmc_pinctrl_register(struct device *dev)
+{
+ struct pinctrl_dev *pctrl_dev;
+ struct platform_device *pbdev;
+
+ pbdev = platform_device_alloc(PCTRL_DEV, -1);
+ pbdev->dev.parent = dev;
+ pbdev->dev.of_node = of_find_node_by_name(dev->of_node, "bmc_pinctrl");
+ platform_device_add(pbdev);
+ pctrl_dev = devm_pinctrl_register(&pbdev->dev, &bmc_pincrtl_desc, NULL);
+ if (IS_ERR(pctrl_dev)) {
+ dev_err(&pbdev->dev,
+ "Can't register pinctrl (%ld)\n", PTR_ERR(pctrl_dev));
+ return PTR_ERR(pctrl_dev);
+ } else {
+ dev_dbg(&pbdev->dev, "BMC pinctrl registered\n");
+ bmc_pinctrl_dev = pctrl_dev;
+ }
+ /* reset all pins to default state */
+ i2c_smbus_write_byte_data(to_i2c_client(dev), R_GPIODIR0, 0);
+ i2c_smbus_write_byte_data(to_i2c_client(dev), R_GPIODIR1, 0);
+ i2c_smbus_write_byte_data(to_i2c_client(dev), R_GPIODIR2, 0);
+ return 0;
+}
+
+static void bmc_pinctrl_unregister(void)
+{
+ if (bmc_pinctrl_dev)
+ devm_pinctrl_unregister(&bmc_i2c->dev, bmc_pinctrl_dev);
+}
+#endif
+
+void bmc_pwroff_rq(void)
+{
+ int ret;
+
+ dev_info(&bmc_i2c->dev, "Write reg R_PWROFF_RQ\n");
+ ret = i2c_smbus_write_byte_data(bmc_i2c, R_PWROFF_RQ, 0x01);
+ dev_info(&bmc_i2c->dev, "ret: %i\n", ret);
+}
+
+int pwroff_rq_poll_fn(void *data)
+{
+ int ret;
+
+ while (1) {
+ if (kthread_should_stop())
+ break;
+
+ dev_dbg(&poll_data.c->dev, "Polling\n");
+ ret = i2c_smbus_read_byte_data(poll_data.c, R_SOFTOFF_RQ);
+ dev_dbg(&poll_data.c->dev, "Polling returned: %i\n", ret);
+
+ if (ret < 0) {
+ dev_err(&poll_data.c->dev,
+ "Could not read register 0x%x\n",
+ R_SOFTOFF_RQ);
+ return -EIO;
+ }
+
+ if (prev_softoff_ret != ret) {
+ dev_info(&poll_data.c->dev, "key change [%i]\n", ret);
+ if (ret != 0) {
+ dev_info(&poll_data.c->dev,
+ "PWROFF \"irq\" detected [%i]\n", ret);
+ input_event(button_dev, EV_KEY, KEY_POWER, 1);
+ } else {
+ input_event(button_dev, EV_KEY, KEY_POWER, 0);
+ }
+
+ input_sync(button_dev);
+ prev_softoff_ret = ret;
+ }
+
+ msleep_interruptible(100);
+ }
+
+ do_exit(1);
+ return 0;
+}
+
+static int mitx2_bmc_validate(struct i2c_client *client)
+{
+ int ret = 0;
+ int i = 0;
+ static const u8 regs[] = {R_ID1, R_ID2, R_ID3};
+ static const u8 vals[] = {BMC_ID1_VAL, BMC_ID2_VAL, BMC_ID3_VAL};
+
+ bmc_proto_version[0] = 0;
+ bmc_proto_version[1] = 0;
+ bmc_proto_version[2] = 0;
+
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ ret = i2c_smbus_read_byte_data(client, regs[i]);
+ if (ret < 0) {
+ dev_err(&client->dev, "Could not read register 0x%x\n",
+ regs[i]);
+ return -EIO;
+ }
+
+ if (ret != vals[i]) {
+ dev_err(&client->dev,
+ "Bad value [0x%02x] in register 0x%x, should be [0x%02x]\n",
+ ret, regs[i], vals[i]);
+ return -ENODEV;
+ }
+ }
+
+ ret = i2c_smbus_read_byte_data(client, R_ID4);
+ if (ret < 0) {
+ dev_err(&client->dev, "Could not read register 0x%x\n", R_ID4);
+ return -EIO;
+ }
+
+ if (ret == BMC_ID4_VAL0) {
+ bmc_proto_version[0] = 0;
+ } else if (ret == BMC_ID4_VAL1) {
+ bmc_proto_version[0] = 2;
+ ret = i2c_smbus_read_byte_data(client, R_VERSION1);
+ if (ret < 0) {
+ dev_err(&client->dev, "Could not read register 0x%x\n",
+ R_VERSION1);
+ return -EIO;
+ }
+
+ bmc_proto_version[1] = ret;
+
+ ret = i2c_smbus_read_byte_data(client, R_VERSION2);
+ if (ret < 0) {
+ dev_err(&client->dev, "Could not read register 0x%x\n",
+ R_VERSION2);
+ return -EIO;
+ }
+
+ bmc_proto_version[2] = ret;
+
+ ret = i2c_smbus_read_byte_data(client, R_BOOTREASON);
+ if (ret < 0) {
+ dev_err(&client->dev, "Could not read register 0x%x\n",
+ R_BOOTREASON);
+ return -EIO;
+ }
+
+ bmc_bootreason[0] = ret;
+
+ ret = i2c_smbus_read_byte_data(client, R_BOOTREASON_ARG);
+ if (ret < 0) {
+ dev_err(&client->dev, "Could not read register 0x%x\n",
+ R_BOOTREASON_ARG);
+ return -EIO;
+ }
+
+ bmc_bootreason[1] = ret;
+
+ for (i = R_SCRATCH1; i <= R_SCRATCH4; i++) {
+ ret = i2c_smbus_read_byte_data(client, i);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Could not read register 0x%x\n", i);
+ return -EIO;
+ }
+
+ bmc_scratch[i - R_SCRATCH1] = ret;
+ }
+
+ if (bmc_proto_version[2] >= BMC_VERSION2_3) {
+ ret = i2c_smbus_read_byte_data(client, R_CAP);
+ if (ret >= 0)
+ bmc_cap = ret;
+ } else {
+ bmc_cap = BMC_CAP_PWRBTN;
+ }
+
+ dev_info(&client->dev,
+ "bootreason %i:%i, extcap 0x%x\n",
+ bmc_bootreason[0],
+ bmc_bootreason[1],
+ bmc_cap);
+ } else {
+ dev_err(&client->dev, "Bad value [0x%02x] in register 0x%x\n",
+ ret, R_ID4);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int bmc_create_client_devices(struct device *bmc_dev)
+{
+ int ret = 0;
+ struct rtc_device *rtc_dev;
+ struct i2c_client *client = to_i2c_client(bmc_dev);
+ int client_addr = client->addr + 1;
+
+ if (bmc_cap & BMC_CAP_TOUCHPAD) {
+#ifdef CONFIG_SERIO
+ struct serio *serio;
+ serio_i2c = i2c_new_ancillary_device(client,
+ "bmc_serio", client_addr);
+ if (IS_ERR(serio_i2c)) {
+ dev_err(&client->dev, "Can't get serio secondary\n");
+ serio_i2c = NULL;
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ serio = devm_kzalloc(&serio_i2c->dev,
+ sizeof(struct serio), GFP_KERNEL);
+ if (!serio) {
+ dev_err(&serio_i2c->dev, "Can't allocate serio\n");
+ ret = -ENOMEM;
+ i2c_unregister_device(serio_i2c);
+ serio_i2c = NULL;
+ goto skip_tp;
+ }
+
+ serio->write = bmc_serio_write;
+ serio->port_data = serio_i2c;
+ serio->id.type = SERIO_PS_PSTHRU;
+ serio_register_port(serio);
+ dev_set_drvdata(&serio_i2c->dev, serio);
+ touchpad_task = kthread_run(touchpad_poll_fn, NULL,
+ "BMC serio poll task");
+skip_tp:
+#endif
+ client_addr++;
+ }
+
+ if (bmc_cap & BMC_CAP_RTC) {
+ rtc_i2c = i2c_new_ancillary_device(client,
+ "bmc_rtc", client_addr);
+ if (IS_ERR(rtc_i2c)) {
+ dev_err(&client->dev, "Can't get RTC secondary\n");
+ rtc_i2c = NULL;
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ rtc_dev = devm_rtc_device_register(&rtc_i2c->dev, "bmc_rtc",
+ &bmc_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc_dev)) {
+ ret = PTR_ERR(rtc_dev);
+ dev_err(&client->dev,
+ "Failed to register RTC device: %d\n", ret);
+ i2c_unregister_device(rtc_i2c);
+ rtc_i2c = NULL;
+ }
+fail:
+ client_addr++;
+ }
+#ifdef CONFIG_PINCTRL
+ if (bmc_cap & BMC_CAP_GPIODIR || 1 /*vvv*/)
+ bmc_pinctrl_register(bmc_dev);
+#endif
+ return ret;
+}
+
+static int mitx2_bmc_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err = 0;
+ int i = 0;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -ENODEV;
+
+ for (i = 0; i < 10; i++) {
+ err = mitx2_bmc_validate(client);
+ if (!err)
+ break;
+
+ msleep_interruptible(20);
+ }
+
+ if (err)
+ return err;
+
+ if (bmc_cap & BMC_CAP_PWRBTN) {
+ button_dev = input_allocate_device();
+ if (!button_dev) {
+ dev_err(&client->dev, "Not enough memory\n");
+ return -ENOMEM;
+ }
+
+ button_dev->id.bustype = BUS_I2C;
+ button_dev->dev.parent = &client->dev;
+ button_dev->name = input_name;
+ button_dev->phys = "bmc-input0";
+ button_dev->evbit[0] = BIT_MASK(EV_KEY);
+ button_dev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER);
+
+ err = input_register_device(button_dev);
+ if (err) {
+ dev_err(&client->dev, "Failed to register device\n");
+ input_free_device(button_dev);
+ return err;
+ }
+
+ dev_dbg(&client->dev, "Starting polling thread\n");
+ poll_data.c = client;
+ polling_task = kthread_run(pwroff_rq_poll_fn, NULL,
+ "BMC poll task");
+ }
+
+ if (bmc_cap || 1 /*vvv*/)
+ err = bmc_create_client_devices(&client->dev);
+
+ bmc_i2c = client;
+ /* register as poweroff handler */
+ pm_power_off = bmc_pwroff_rq;
+
+ return 0;
+}
+
+static void mitx2_bmc_i2c_remove(struct i2c_client *client)
+{
+#ifdef CONFIG_SERIO
+ struct serio *serio;
+#endif
+ if (button_dev) {
+ kthread_stop(polling_task);
+ input_unregister_device(button_dev);
+ }
+#ifdef CONFIG_SERIO
+ if (serio_i2c) {
+ kthread_stop(touchpad_task);
+ serio = dev_get_drvdata(&serio_i2c->dev);
+ serio_unregister_port(serio);
+ i2c_unregister_device(serio_i2c);
+ }
+#endif
+ if (rtc_i2c)
+ i2c_unregister_device(rtc_i2c);
+#ifdef CONFIG_PINCTRL
+ bmc_pinctrl_unregister();
+#endif
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id mitx2_bmc_of_match[] = {
+ { .compatible = "tp,mitx2-bmc" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mitx2_bmc_of_match);
+#endif
+
+static const struct i2c_device_id mitx2_bmc_i2c_id[] = {
+ { "mitx2_bmc", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mitx2_bmc_i2c_id);
+
+static ssize_t version_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%i.%i.%i\n", bmc_proto_version[0],
+ bmc_proto_version[1],
+ bmc_proto_version[2]);
+}
+
+static struct kobj_attribute version_attribute =
+ __ATTR(version, 0664, version_show, NULL);
+
+static ssize_t bootreason_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%i\n", bmc_bootreason[0] |
+ (bmc_bootreason[1] << 8));
+}
+
+static struct kobj_attribute bootreason_attribute =
+ __ATTR(bootreason, 0664, bootreason_show, NULL);
+
+static ssize_t scratch_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%i\n", bmc_scratch[0] |
+ (bmc_scratch[1] << 8) |
+ (bmc_scratch[2] << 16) |
+ (bmc_scratch[3] << 24));
+}
+
+static struct kobj_attribute scratch_attribute =
+ __ATTR(scratch, 0664, scratch_show, NULL);
+
+static struct attribute *bmc_attrs[] = {
+ &version_attribute.attr,
+ &bootreason_attribute.attr,
+ &scratch_attribute.attr,
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(bmc);
+
+static struct i2c_driver mitx2_bmc_i2c_driver = {
+ .driver = {
+ .name = "mitx2-bmc",
+ .of_match_table = of_match_ptr(mitx2_bmc_of_match),
+ .groups = bmc_groups,
+ },
+ .probe = mitx2_bmc_i2c_probe,
+ .remove = mitx2_bmc_i2c_remove,
+ .id_table = mitx2_bmc_i2c_id,
+};
+module_i2c_driver(mitx2_bmc_i2c_driver);
+
+MODULE_AUTHOR("Konstantin Kirik");
+MODULE_DESCRIPTION("mITX2 BMC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("serial:bmc");
sdhci_reset(host, mask);
}
+static void baikal_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ clk_set_rate(pltfm_host->clk, clock * 2);
+ host->mmc->actual_clock = clk_get_rate(pltfm_host->clk) / 2;
+ sdhci_enable_clk(host, 0);
+}
+
+static unsigned int baikal_get_max_clock (struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ return clk_round_rate(pltfm_host->clk, ULONG_MAX) / 2;
+}
+
+static unsigned int baikal_get_min_clock (struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ return clk_round_rate(pltfm_host->clk, 1) / 2;
+}
+
+static const struct sdhci_ops sdhci_dwcmshc_baikal_ops = {
+ .set_clock = baikal_set_clock,
+ .get_max_clock = baikal_get_max_clock,
+ .get_min_clock = baikal_get_min_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .set_uhs_signaling = dwcmshc_set_uhs_signaling,
+ .reset = sdhci_reset,
+ .adma_write_desc = dwcmshc_adma_write_desc,
+};
+
static const struct sdhci_ops sdhci_dwcmshc_ops = {
.set_clock = sdhci_set_clock,
.set_bus_width = sdhci_set_bus_width,
.adma_write_desc = dwcmshc_adma_write_desc,
};
+static const struct sdhci_pltfm_data sdhci_dwcmshc_baikal_pdata = {
+ .ops = &sdhci_dwcmshc_baikal_ops,
+ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
+ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
+ SDHCI_QUIRK2_BROKEN_64_BIT_DMA_MASK,
+};
+
static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = {
.ops = &sdhci_dwcmshc_ops,
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
}
static const struct of_device_id sdhci_dwcmshc_dt_ids[] = {
+ {
+ .compatible = "baikal,dwcmshc-sdhci",
+ .data = &sdhci_dwcmshc_baikal_pdata,
+ },
{
.compatible = "rockchip,rk3588-dwcmshc",
.data = &sdhci_dwcmshc_rk35xx_pdata,
.id = "MLNXBF30",
.driver_data = (kernel_ulong_t)&sdhci_dwcmshc_bf3_pdata,
},
+ {
+ .id = "BKLE0004",
+ .driver_data = (kernel_ulong_t)&sdhci_dwcmshc_baikal_pdata,
+ },
{}
};
#endif
pltfm_host = sdhci_priv(host);
priv = sdhci_pltfm_priv(pltfm_host);
- if (dev->of_node) {
+ if (dev->of_node || pltfm_data == &sdhci_dwcmshc_baikal_pdata) {
pltfm_host->clk = devm_clk_get(dev, "core");
if (IS_ERR(pltfm_host->clk)) {
err = PTR_ERR(pltfm_host->clk);
host->flags &= ~SDHCI_USE_64_BIT_DMA;
/* Try 64-bit mask if hardware is capable of it */
- if (host->flags & SDHCI_USE_64_BIT_DMA) {
+ if (host->flags & SDHCI_USE_64_BIT_DMA &&
+ !(host->quirks2 & SDHCI_QUIRK2_BROKEN_64_BIT_DMA_MASK)) {
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (ret) {
pr_warn("%s: Failed to set 64-bit DMA mask.\n",
#define SDHCI_QUIRK2_USE_32BIT_BLK_CNT (1<<18)
/* Issue CMD and DATA reset together */
#define SDHCI_QUIRK2_ISSUE_CMD_DAT_RESET_TOGETHER (1<<19)
+#define SDHCI_QUIRK2_BROKEN_64_BIT_DMA_MASK (1<<31)
int irq; /* Device IRQ */
void __iomem *ioaddr; /* Mapped address */
partition map. This partition table contains real partition
offsets, which may differ from device to device depending on the
number and location of bad blocks on NAND.
+
+config MTD_BAIKAL_PARTS
+ bool "Baikal flash partition parser for ACPI"
+ depends on ARCH_BAIKAL && ACPI
+ help
+ This provides flash partition parser which derives the partition map
+ from the flash ACPI node.
obj-$(CONFIG_MTD_SHARPSL_PARTS) += sharpslpart.o
obj-$(CONFIG_MTD_REDBOOT_PARTS) += redboot.o
obj-$(CONFIG_MTD_QCOMSMEM_PARTS) += qcomsmempart.o
+obj-$(CONFIG_MTD_BAIKAL_PARTS) += baikal.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Baikal flash partition driver for ACPI
+ *
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ * Author: Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <linux/device/bus.h>
+#include <linux/mtd/partitions.h>
+#include <linux/spi/flash.h>
+#include <linux/spi/spi.h>
+
+#include <acpi/actypes.h>
+
+static int baikal_add_mtd_partitions(struct device *dev, void *unused)
+{
+ struct acpi_device *adev;
+ const union acpi_object *obj;
+ struct flash_platform_data *data;
+ struct mtd_partition *parts;
+ int nr_parts, i, ret;
+
+ if (dev) {
+ adev = ACPI_COMPANION(dev);
+ if (!adev)
+ return 0;
+
+ if (!acpi_dev_get_property(adev, "baikal,partitions",
+ ACPI_TYPE_PACKAGE, &obj)) {
+ if (!obj->package.count || obj->package.count % 3)
+ return 0;
+ nr_parts = obj->package.count / 3;
+
+ data = kzalloc(sizeof(*data) + nr_parts * sizeof(*parts),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ parts = (void *)data + sizeof(*data);
+
+ for (i = 0; i < nr_parts; ++i) {
+ if (obj->package.elements[3 * i].type != ACPI_TYPE_STRING ||
+ obj->package.elements[3 * i + 1].type != ACPI_TYPE_INTEGER ||
+ obj->package.elements[3 * i + 2].type != ACPI_TYPE_INTEGER) {
+ kfree(data);
+ return 0;
+ }
+
+ parts[i].name = kmemdup_nul(obj->package.elements[3 * i].string.pointer,
+ obj->package.elements[3 * i].string.length,
+ GFP_KERNEL);
+ if (!parts[i].name) {
+ kfree(data);
+ return -ENOMEM;
+ }
+
+ parts[i].offset =
+ obj->package.elements[3 * i + 1].integer.value;
+ parts[i].size =
+ obj->package.elements[3 * i + 2].integer.value;
+ }
+
+ data->parts = parts;
+ data->nr_parts = nr_parts;
+ dev->platform_data = data;
+
+ ret = driver_set_override(dev, &to_spi_device(dev)->driver_override,
+ "spi-nor", strlen("spi-nor"));
+ if (ret) {
+ dev->platform_data = NULL;
+ kfree(data);
+ return ret;
+ }
+
+ ret = device_reprobe(dev);
+ if (ret) {
+ dev->platform_data = NULL;
+ kfree(data);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int __init baikal_spi_partitions_init(void)
+{
+ return bus_for_each_dev(&spi_bus_type, NULL, NULL,
+ baikal_add_mtd_partitions);
+}
+
+late_initcall(baikal_spi_partitions_init);
.fixups = &w25q256_fixups },
{ "w25q256jvm", INFO(0xef7019, 0, 64 * 1024, 512)
PARSE_SFDP },
+ { "w25q512jv", INFO(0xef7020, 0, 64 * 1024, 1024)
+ FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+ NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ |
+ SPI_NOR_QUAD_READ) },
{ "w25q256jw", INFO(0xef6019, 0, 64 * 1024, 512)
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ |
SPI_NOR_QUAD_READ) },
To compile the driver as a module, choose M here: the module will
be called mux-mmio.
+config MUX_BAIKAL
+ tristate "Baikal-S SoC low speed peripheral IO Multiplexer"
+ help
+ The driver allows to control three switches of the multiplexer
+ independently:
+ - 8 GPIOs or UART/SMBus/SMBus;
+ - 8 GPIOs or SPI;
+ - 16 GPIOs or eSPI.
+
endmenu
mux-adgs1408-objs := adgs1408.o
mux-gpio-objs := gpio.o
mux-mmio-objs := mmio.o
+mux-baikal-objs := baikal.o
obj-$(CONFIG_MULTIPLEXER) += mux-core.o
obj-$(CONFIG_MUX_ADG792A) += mux-adg792a.o
obj-$(CONFIG_MUX_ADGS1408) += mux-adgs1408.o
obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
obj-$(CONFIG_MUX_MMIO) += mux-mmio.o
+obj-$(CONFIG_MUX_BAIKAL) += mux-baikal.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Multiplexer driver for Baikal-S SoC low speed peripheral devices
+ *
+ * Copyright (C) 2022-2023 Baikal Electronics, JSC
+ * Author: Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
+ */
+
+#include <linux/acpi.h>
+#include <linux/arm-smccc.h>
+#include <linux/of_platform.h>
+#include <linux/module.h>
+#include <linux/mux/consumer.h>
+#include <linux/mux/driver.h>
+#include <linux/platform_device.h>
+
+#define BAIKAL_SMC_LSP_MUX 0x82000600
+#define BAIKAL_LSP_MUX_COUNT 3
+#define BAIKAL_LSP_MUX_STATE_COUNT 2
+#define BAIKAL_LSP_MUX_AS_IS 2
+
+static u8 uart_smbus = BAIKAL_LSP_MUX_AS_IS;
+module_param(uart_smbus, byte, 0);
+MODULE_PARM_DESC(uart_smbus, "UART/SMBus/SMBus. 0=Disable, 1=Enable");
+
+static u8 qspi = BAIKAL_LSP_MUX_AS_IS;
+module_param(qspi, byte, 0);
+MODULE_PARM_DESC(qspi, "QSPI. 0=Disable, 1=Enable");
+
+static u8 espi = BAIKAL_LSP_MUX_AS_IS;
+module_param(espi, byte, 0);
+MODULE_PARM_DESC(espi, "eSPI. 0=Disable, 1=Enable");
+
+static const u8 * const baikal_lsp_mux_init_value[3] = {
+ &uart_smbus,
+ &qspi,
+ &espi
+};
+
+struct baikal_lsp_mux_priv {
+ struct fwnode_handle **fwnode[BAIKAL_LSP_MUX_STATE_COUNT];
+ struct platform_device **plat_dev[BAIKAL_LSP_MUX_STATE_COUNT];
+ u8 count[BAIKAL_LSP_MUX_STATE_COUNT];
+ bool acpi_node_enabled[BAIKAL_LSP_MUX_STATE_COUNT];
+ struct mutex lock;
+ struct mux_control *mux;
+ struct platform_device *pdev;
+};
+
+static char *baikal_lsp_mux_property(unsigned int state)
+{
+ if (acpi_disabled)
+ return state ? "mux-state1" : "mux-state0";
+ else
+ return state ? "DEV1" : "DEV0";
+}
+
+static struct platform_device *
+baikal_lsp_mux_channel_create_device(struct fwnode_handle *fwnode,
+ struct device *parent)
+{
+ if (acpi_disabled) {
+ return of_platform_device_create(to_of_node(fwnode), NULL,
+ parent);
+ } else {
+ return acpi_create_platform_device(to_acpi_device_node(fwnode),
+ NULL);
+ }
+}
+
+static void baikal_lsp_mux_channel_remove_device(struct platform_device *pdev)
+{
+ if (acpi_disabled) {
+ of_platform_device_destroy(&pdev->dev, NULL);
+ } else {
+ platform_device_unregister(pdev);
+ }
+}
+
+#ifdef CONFIG_ACPI
+static int baikal_lsp_mux_channel_enable_nodes_acpi(struct baikal_lsp_mux_priv *priv,
+ unsigned int state)
+{
+ union acpi_object val = {
+ .type = ACPI_TYPE_INTEGER,
+ .integer.type = ACPI_TYPE_INTEGER,
+ .integer.value = state
+ };
+ struct acpi_object_list args = {
+ .count = 1,
+ .pointer = &val
+ };
+ struct acpi_device *adev =
+ to_acpi_device_node(priv->pdev->dev.fwnode);
+ struct device *ref_dev;
+ struct platform_device **pdev;
+ struct fwnode_handle **fwnode;
+ u64 is_enabled;
+ acpi_status status;
+ int i;
+
+ status = acpi_evaluate_integer(adev->handle,
+ state ? "STA1" : "STA0",
+ NULL,
+ &is_enabled);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&adev->dev, "failed to get state for %s\n",
+ baikal_lsp_mux_property(i));
+ return -ENODEV;
+ }
+
+ if (is_enabled) {
+ priv->acpi_node_enabled[state] = true;
+ return 0;
+ }
+
+ status = acpi_evaluate_object(adev->handle, "INIT", &args,
+ NULL);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&adev->dev, "failed to enable %s devices\n",
+ baikal_lsp_mux_property(i));
+ return -ENODEV;
+ }
+
+ priv->acpi_node_enabled[state] = true;
+
+ for (i = 0; i < priv->count[state]; ++i) {
+ fwnode = &priv->fwnode[state][i];
+ pdev = &priv->plat_dev[state][i];
+
+ if (!*fwnode)
+ continue;
+
+ adev = to_acpi_device_node(*fwnode);
+ acpi_scan_lock_acquire();
+ if (adev)
+ acpi_bus_scan(adev->handle);
+ acpi_scan_lock_release();
+
+ ref_dev = bus_find_device_by_fwnode(&platform_bus_type,
+ *fwnode);
+ if (ref_dev)
+ *pdev = to_platform_device(ref_dev);
+ }
+
+ /* Device nodes were created by this function */
+ return 1;
+}
+#else
+static inline int baikal_lsp_mux_channel_enable_nodes_acpi(struct baikal_lsp_mux_priv *priv,
+ unsigned int state)
+{
+ return 0;
+}
+#endif
+
+static void baikal_lsp_mux_channel_remove_devices(struct baikal_lsp_mux_priv *priv,
+ unsigned int state)
+{
+ struct platform_device **pdev;
+ unsigned int i;
+
+ if (state >= BAIKAL_LSP_MUX_STATE_COUNT)
+ return;
+
+ for (i = 0; i < priv->count[state]; ++i) {
+ pdev = &priv->plat_dev[state][i];
+
+ if (*pdev) {
+ baikal_lsp_mux_channel_remove_device(*pdev);
+ *pdev = NULL;
+ }
+ }
+}
+
+static int baikal_lsp_mux_channel_switch_devices(struct baikal_lsp_mux_priv *priv,
+ unsigned int state)
+{
+ struct device *parent = priv->mux->chip->dev.parent;
+ struct platform_device **pdev;
+ struct fwnode_handle **fwnode;
+ int i, ret = 0;
+
+ state = !!state;
+
+ mutex_lock(&priv->lock);
+
+ if (priv->mux->cached_state == state)
+ goto mux_unlock;
+
+ if (priv->mux->cached_state == MUX_IDLE_AS_IS) {
+ for (i = 0; i < BAIKAL_LSP_MUX_STATE_COUNT; ++i)
+ baikal_lsp_mux_channel_remove_devices(priv, i);
+ } else {
+ baikal_lsp_mux_channel_remove_devices(priv, !state);
+ }
+
+ ret = mux_control_select(priv->mux, state);
+ if (ret)
+ goto mux_unlock;
+
+ ret = mux_control_deselect(priv->mux);
+ if (ret)
+ goto mux_unlock;
+
+ if (priv->mux->cached_state == MUX_IDLE_AS_IS) {
+ baikal_lsp_mux_channel_remove_devices(priv, state);
+ ret = -EPROTO;
+ goto mux_unlock;
+ }
+
+ if (!acpi_disabled && !priv->acpi_node_enabled[state]) {
+ ret = baikal_lsp_mux_channel_enable_nodes_acpi(priv, state);
+
+ /* Check if device nodes were created */
+ if (ret == 1) {
+ ret = 0;
+ goto mux_unlock;
+ }
+ }
+
+ if (!ret) {
+ for (i = 0; i < priv->count[state]; ++i) {
+ fwnode = &priv->fwnode[state][i];
+ pdev = &priv->plat_dev[state][i];
+
+ if (!*fwnode)
+ continue;
+
+ if (!*pdev) {
+ *pdev = baikal_lsp_mux_channel_create_device(*fwnode,
+ parent);
+ if (IS_ERR_OR_NULL(*pdev)) {
+ *pdev = NULL;
+ }
+ } else {
+ baikal_lsp_mux_channel_remove_device(*pdev);
+ }
+ }
+ }
+
+mux_unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static ssize_t baikal_lsp_mux_channel_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct baikal_lsp_mux_priv *priv = dev_get_drvdata(dev);
+ unsigned int n = 0;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &n);
+ if (ret)
+ return ret;
+
+ ret = baikal_lsp_mux_channel_switch_devices(priv, n);
+
+ ret = ret ? ret : count;
+
+ return ret;
+}
+
+static ssize_t baikal_lsp_mux_channel_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct baikal_lsp_mux_priv *priv = dev_get_drvdata(dev);
+ return sprintf(buf, "%i\n", priv->mux->cached_state);
+}
+
+static DEVICE_ATTR(enabled, S_IWUSR | S_IRUGO, baikal_lsp_mux_channel_show,
+ baikal_lsp_mux_channel_store);
+
+static struct attribute *baikal_lsp_mux_channel_attrs[] = {
+ &dev_attr_enabled.attr,
+ NULL
+};
+
+static const struct attribute_group baikal_lsp_mux_channel_attr_group = {
+ .attrs = baikal_lsp_mux_channel_attrs
+};
+
+static struct baikal_lsp_mux_priv *
+baikal_lsp_mux_channel_get_of_data(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *mux_dev;
+ struct baikal_lsp_mux_priv *priv;
+ int count[BAIKAL_LSP_MUX_STATE_COUNT];
+ int i, j;
+
+ if (!np)
+ return ERR_PTR(-ENODEV);
+
+ for (i = 0; i < BAIKAL_LSP_MUX_STATE_COUNT; ++i) {
+ count[i] = of_count_phandle_with_args(np,
+ baikal_lsp_mux_property(i), NULL);
+ if (count[i] < 1) {
+ dev_err(dev, "invalid number of phandles in '%s' property\n",
+ baikal_lsp_mux_property(i));
+ return ERR_PTR(-EINVAL);
+ }
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv) +
+ 2 * (count[0] + count[1]) * sizeof(void *),
+ GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+
+ priv->count[0] = count[0];
+ priv->count[1] = count[1];
+ priv->fwnode[0] = (struct fwnode_handle **)(priv + 1);
+ priv->fwnode[1] = priv->fwnode[0] + priv->count[0];
+ priv->plat_dev[0] = (struct platform_device **)(priv->fwnode[1] +
+ priv->count[1]);
+ priv->plat_dev[1] = priv->plat_dev[0] + priv->count[0];
+
+ priv->mux = mux_control_get(dev, NULL);
+ if (IS_ERR(priv->mux))
+ return ERR_CAST(priv->mux);
+
+ for (i = 0; i < BAIKAL_LSP_MUX_STATE_COUNT; ++i) {
+ for (j = 0; j < priv->count[i]; ++j) {
+ mux_dev = of_parse_phandle(np,
+ baikal_lsp_mux_property(i),
+ j);
+ if (!mux_dev) {
+ dev_err(dev, "invalid phandle%i in '%s' property\n",
+ j, baikal_lsp_mux_property(i));
+ return ERR_PTR(-EINVAL);
+ }
+
+ priv->fwnode[i][j] = of_fwnode_handle(mux_dev);
+ }
+ }
+
+ return priv;
+}
+
+#ifdef CONFIG_ACPI
+static struct mux_control *
+baikal_lsp_mux_channel_get_mux_acpi(struct acpi_device *adev)
+{
+ struct device *dev = &adev->dev, *mux_chip_dev;
+ struct acpi_device *mux;
+ struct mux_chip *mux_chip;
+ union acpi_object *package = NULL;
+ union acpi_object *element = NULL;
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status = AE_OK;
+ unsigned int id;
+ int ret;
+
+ status = acpi_evaluate_object_typed(adev->handle, "MUX", NULL,
+ &buffer, ACPI_TYPE_PACKAGE);
+ if (ACPI_FAILURE(status)) {
+ dev_err(dev, "failed to get MUX data\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ package = buffer.pointer;
+ if (package->package.count != 2) {
+ dev_err(dev, "invalid MUX data\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ element = &(package->package.elements[0]);
+ if (element->type != ACPI_TYPE_LOCAL_REFERENCE ||
+ !element->reference.handle) {
+ dev_err(dev, "invalid MUX reference\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ mux = acpi_fetch_acpi_dev(element->reference.handle);
+ if (!mux) {
+ dev_err(dev, "failed to process MUX reference\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ element = &(package->package.elements[1]);
+ if (element->type != ACPI_TYPE_INTEGER) {
+ dev_err(dev, "failed to get MUX index\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ id = element->integer.value;
+
+ mux_chip_dev = bus_find_device_by_fwnode(&platform_bus_type,
+ acpi_fwnode_handle(mux));
+ if (!mux_chip_dev) {
+ dev_err(dev, "failed to get mux chip device\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ mux_chip = dev_get_drvdata(mux_chip_dev);
+ if (!mux_chip) {
+ dev_err(dev, "failed to get mux chip\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ if (id >= mux_chip->controllers) {
+ dev_err(dev, "bad mux controller %u\n", id);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = 0;
+
+err:
+ acpi_os_free(buffer.pointer);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return &mux_chip->mux[id];
+}
+
+static struct baikal_lsp_mux_priv *
+baikal_lsp_mux_channel_get_acpi_data(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+ struct acpi_device *mux_dev;
+ struct baikal_lsp_mux_priv *priv;
+ int count[BAIKAL_LSP_MUX_STATE_COUNT];
+ struct acpi_buffer buffer[BAIKAL_LSP_MUX_STATE_COUNT];
+ union acpi_object *package = NULL;
+ union acpi_object *element = NULL;
+ acpi_status status = AE_OK;
+ int i, j, ret;
+
+ if (!adev)
+ return ERR_PTR(-ENODEV);
+
+ for (i = 0; i < BAIKAL_LSP_MUX_STATE_COUNT; ++i) {
+ buffer[i].length = ACPI_ALLOCATE_BUFFER;
+ buffer[i].pointer = NULL;
+ status = acpi_evaluate_object_typed(adev->handle,
+ baikal_lsp_mux_property(i),
+ NULL,
+ &buffer[i],
+ ACPI_TYPE_PACKAGE);
+ if (ACPI_FAILURE(status)) {
+ dev_err(dev, "failed to get %s data\n",
+ baikal_lsp_mux_property(i));
+ return ERR_PTR(-ENODEV);
+ }
+
+ package = buffer[i].pointer;
+ count[i] = package->package.count;
+ if (count[i] < 1) {
+ dev_err(dev, "invalid number of references in %s\n",
+ baikal_lsp_mux_property(i));
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv) +
+ 2 * (count[0] + count[1]) * sizeof(void *),
+ GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ priv->count[0] = count[0];
+ priv->count[1] = count[1];
+ priv->fwnode[0] = (struct fwnode_handle **)(priv + 1);
+ priv->fwnode[1] = priv->fwnode[0] + priv->count[0];
+ priv->plat_dev[0] = (struct platform_device **)(priv->fwnode[1] +
+ priv->count[1]);
+ priv->plat_dev[1] = priv->plat_dev[0] + priv->count[0];
+
+ priv->mux = baikal_lsp_mux_channel_get_mux_acpi(adev);
+ if (IS_ERR(priv->mux)) {
+ ret = PTR_ERR(priv->mux);
+ goto err;
+ }
+
+ for (i = 0; i < BAIKAL_LSP_MUX_STATE_COUNT; ++i) {
+ package = buffer[i].pointer;
+ for (j = 0; j < priv->count[i]; ++j) {
+ element = &(package->package.elements[j]);
+
+ if (element->type != ACPI_TYPE_LOCAL_REFERENCE ||
+ !element->reference.handle) {
+ dev_err(dev, "invalid reference%i in %s\n", j,
+ baikal_lsp_mux_property(i));
+ ret = -EINVAL;
+ goto err;
+ }
+
+ mux_dev = acpi_fetch_acpi_dev(element->reference.handle);
+ if (!mux_dev) {
+ dev_err(dev, "failed to process reference%i in %s\n",
+ j, baikal_lsp_mux_property(i));
+ ret = -EINVAL;
+ goto err;
+ }
+
+ priv->fwnode[i][j] = acpi_fwnode_handle(mux_dev);
+ }
+ }
+
+ ret = 0;
+
+err:
+ for (i = 0; i < BAIKAL_LSP_MUX_STATE_COUNT; ++i)
+ acpi_os_free(buffer[i].pointer);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return priv;
+}
+#else /* CONFIG_ACPI */
+static inline struct baikal_lsp_mux_priv *
+baikal_lsp_mux_channel_get_acpi_data(struct platform_device *pdev)
+{
+ return ERR_PTR(-ENODEV);
+}
+#endif
+
+static int baikal_lsp_mux_channel_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct baikal_lsp_mux_priv *priv;
+ unsigned int id;
+ int ret;
+
+ if (acpi_disabled)
+ priv = baikal_lsp_mux_channel_get_of_data(pdev);
+ else
+ priv = baikal_lsp_mux_channel_get_acpi_data(pdev);
+
+ if (IS_ERR(priv)) {
+ dev_err(dev, "failed to get mux data\n");
+ return PTR_ERR(priv);
+ }
+
+ priv->pdev = pdev;
+ mutex_init(&priv->lock);
+ platform_set_drvdata(pdev, priv);
+
+ id = mux_control_get_index(priv->mux);
+ ret = baikal_lsp_mux_channel_switch_devices(priv,
+ *baikal_lsp_mux_init_value[id]);
+ if (ret) {
+ dev_err(dev, "failed to initialize mux devices: %i\n", ret);
+ return ret;
+ }
+
+ ret = sysfs_create_group(&dev->kobj, &baikal_lsp_mux_channel_attr_group);
+ if (ret) {
+ dev_err(dev, "failed to create sysfs group: %i\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id baikal_lsp_mux_channel_of_match[] = {
+ { .compatible = "baikal,bs1000-lsp-mux-channel" },
+ {}
+};
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id baikal_lsp_mux_channel_acpi_match[] = {
+ { "BKLE0003" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(acpi, baikal_lsp_mux_channel_acpi_match);
+#endif
+
+static struct platform_driver baikal_lsp_mux_channel_driver = {
+ .driver = {
+ .name = "baikal-lsp-mux-channel",
+ .of_match_table = baikal_lsp_mux_channel_of_match,
+ .acpi_match_table = ACPI_PTR(baikal_lsp_mux_channel_acpi_match),
+ .suppress_bind_attrs = true
+ },
+ .probe = baikal_lsp_mux_channel_probe
+};
+
+static DEFINE_MUTEX(baikal_lsp_mux_lock);
+
+static int baikal_lsp_mux_set(struct mux_control *mux, int state)
+{
+ struct arm_smccc_res res;
+
+ mutex_lock(&baikal_lsp_mux_lock);
+ arm_smccc_smc(BAIKAL_SMC_LSP_MUX,
+ state << mux_control_get_index(mux) & 0x7,
+ 0, 0, 0, 0, 0, 0, &res);
+ mutex_unlock(&baikal_lsp_mux_lock);
+
+ return res.a0;
+}
+
+static const struct mux_control_ops baikal_lsp_mux_ops = {
+ .set = baikal_lsp_mux_set
+};
+
+static int baikal_lsp_mux_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mux_chip *mux_chip;
+ int i, ret;
+
+ mux_chip = devm_mux_chip_alloc(dev, BAIKAL_LSP_MUX_COUNT, 0);
+ if (IS_ERR(mux_chip)) {
+ dev_err(dev, "failed to allocate mux chip\n");
+ return PTR_ERR(mux_chip);
+ }
+
+ mux_chip->ops = &baikal_lsp_mux_ops;
+
+ if (uart_smbus == BAIKAL_LSP_MUX_AS_IS) {
+ uart_smbus = !!device_property_present(dev, "uart-smbus-enabled");
+ }
+ baikal_lsp_mux_set(&mux_chip->mux[0], uart_smbus);
+
+ if (qspi == BAIKAL_LSP_MUX_AS_IS) {
+ qspi = !!device_property_present(dev, "qspi-enabled");
+ }
+ baikal_lsp_mux_set(&mux_chip->mux[1], qspi);
+
+ if (espi == BAIKAL_LSP_MUX_AS_IS) {
+ espi = !!device_property_present(dev, "espi-enabled");
+ }
+ baikal_lsp_mux_set(&mux_chip->mux[2], espi);
+
+ for (i = 0; i < mux_chip->controllers; ++i)
+ mux_chip->mux[i].states = BAIKAL_LSP_MUX_STATE_COUNT;
+
+ ret = devm_mux_chip_register(dev, mux_chip);
+ if (ret) {
+ dev_err(dev, "failed to register mux chip\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, mux_chip);
+
+ return 0;
+}
+
+static const struct of_device_id baikal_lsp_mux_of_match[] = {
+ { .compatible = "baikal,bs1000-lsp-mux" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, baikal_lsp_mux_of_match);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id baikal_lsp_mux_acpi_match[] = {
+ { "BKLE0002" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(acpi, baikal_lsp_mux_acpi_match);
+#endif
+
+static struct platform_driver baikal_lsp_mux_driver = {
+ .driver = {
+ .name = "baikal-lsp-mux",
+ .of_match_table = baikal_lsp_mux_of_match,
+ .acpi_match_table = ACPI_PTR(baikal_lsp_mux_acpi_match),
+ .suppress_bind_attrs = true
+ },
+ .probe = baikal_lsp_mux_probe
+};
+
+static struct platform_driver * const baikal_lsp_mux_drivers[] = {
+ &baikal_lsp_mux_driver,
+ &baikal_lsp_mux_channel_driver
+};
+
+static int __init baikal_lsp_mux_init(void)
+{
+ return platform_register_drivers(baikal_lsp_mux_drivers,
+ ARRAY_SIZE(baikal_lsp_mux_drivers));
+}
+module_init(baikal_lsp_mux_init);
+
+static void __exit baikal_lsp_mux_exit(void)
+{
+ platform_unregister_drivers(baikal_lsp_mux_drivers,
+ ARRAY_SIZE(baikal_lsp_mux_drivers));
+}
+module_exit(baikal_lsp_mux_exit);
+
+MODULE_AUTHOR("Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal-S SoC low speed peripheral mux driver");
+MODULE_LICENSE("GPL v2");
This selects the Anarion SoC glue layer support for the stmmac driver.
+config DWMAC_BAIKAL
+ tristate "Baikal Electronics DWMAC support"
+ depends on OF
+ help
+ Support for Baikal Electronics DWMAC Ethernet.
+
+ This selects the Baikal SoC glue layer support for the stmmac driver.
+
config DWMAC_INGENIC
tristate "Ingenic MAC support"
default MACH_INGENIC
# Ordering matters. Generic driver must be last.
obj-$(CONFIG_STMMAC_PLATFORM) += stmmac-platform.o
obj-$(CONFIG_DWMAC_ANARION) += dwmac-anarion.o
+obj-$(CONFIG_DWMAC_BAIKAL) += dwmac-baikal.o
obj-$(CONFIG_DWMAC_INGENIC) += dwmac-ingenic.o
obj-$(CONFIG_DWMAC_IPQ806X) += dwmac-ipq806x.o
obj-$(CONFIG_DWMAC_LPC18XX) += dwmac-lpc18xx.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Baikal Electronics DWMAC specific glue layer
+ *
+ * Copyright (C) 2015-2022 Baikal Electronics, JSC
+ * Authors: Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
+ * Alexey Sheplyakov <asheplyakov@altlinux.org>
+ */
+
+#include <linux/acpi.h>
+#include <linux/arm-smccc.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include "stmmac.h"
+#include "stmmac_platform.h"
+#include "common.h"
+#include "dwmac_dma.h"
+#include "dwmac1000_dma.h"
+
+#define MAC_GPIO 0x00e0 /* GPIO register */
+#define MAC_GPIO_GPO (1 << 8) /* Output port */
+
+#define BAIKAL_SMC_GMAC_DIV2_ENABLE 0x82000500
+#define BAIKAL_SMC_GMAC_DIV2_DISABLE 0x82000501
+
+struct baikal_gmac {
+ struct device *dev;
+ uint64_t base;
+ struct clk *axi_clk;
+ struct clk *tx2_clk;
+ int has_aux_div2;
+};
+
+static int baikal_gmac_dma_reset(void __iomem *ioaddr)
+{
+ int err;
+ u32 value;
+
+ /* DMA SW reset */
+ value = readl(ioaddr + DMA_BUS_MODE);
+ value |= DMA_BUS_MODE_SFT_RESET;
+ writel(value, ioaddr + DMA_BUS_MODE);
+
+ /* Software DMA reset also resets MAC, so GP_OUT is set to zero.
+ * Which resets PHY as a side effect (if GP_OUT is connected directly
+ * to PHY reset).
+ * TODO: read the PHY reset duration from the device tree.
+ * Meanwhile use 100 milliseconds which seems to be enough for
+ * most PHYs
+ */
+ usleep_range(100000, 120000);
+
+ /* Clear PHY reset */
+ value = readl(ioaddr + MAC_GPIO);
+ value |= MAC_GPIO_GPO;
+ writel(value, ioaddr + MAC_GPIO);
+
+ /* Many PHYs need ~100 milliseconds to calm down after PHY reset
+ * has been cleared. And check for DMA reset below might return
+ * much earlier (i.e. in ~20 milliseconds). As a result reading
+ * PHY registers (after this function returns) might return garbage.
+ * Wait a bit to avoid the problem.
+ */
+ usleep_range(100000, 150000);
+
+ err = readl_poll_timeout(ioaddr + DMA_BUS_MODE, value,
+ !(value & DMA_BUS_MODE_SFT_RESET),
+ 10000, 1000000);
+ if (err) {
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static const struct stmmac_dma_ops baikal_gmac_dma_ops = {
+ .reset = baikal_gmac_dma_reset,
+ .init = dwmac1000_dma_init,
+ .init_rx_chan = dwmac1000_dma_init_rx,
+ .init_tx_chan = dwmac1000_dma_init_tx,
+ .axi = dwmac1000_dma_axi,
+ .dump_regs = dwmac1000_dump_dma_regs,
+ .dma_rx_mode = dwmac1000_dma_operation_mode_rx,
+ .dma_tx_mode = dwmac1000_dma_operation_mode_tx,
+ .enable_dma_transmission = dwmac_enable_dma_transmission,
+ .enable_dma_irq = dwmac_enable_dma_irq,
+ .disable_dma_irq = dwmac_disable_dma_irq,
+ .start_tx = dwmac_dma_start_tx,
+ .stop_tx = dwmac_dma_stop_tx,
+ .start_rx = dwmac_dma_start_rx,
+ .stop_rx = dwmac_dma_stop_rx,
+ .dma_interrupt = dwmac_dma_interrupt,
+ .get_hw_feature = dwmac1000_get_hw_feature,
+ .rx_watchdog = dwmac1000_rx_watchdog
+};
+
+static struct mac_device_info *baikal_gmac_setup(void *ppriv)
+{
+ struct mac_device_info *mac, *old_mac;
+ struct stmmac_priv *priv = ppriv;
+ struct gpio_desc *reset_gpio;
+ int err;
+ u32 value;
+
+ mac = devm_kzalloc(priv->device, sizeof(*mac), GFP_KERNEL);
+ if (!mac) {
+ return NULL;
+ }
+
+ /* Clear PHY reset */
+ value = readl(priv->ioaddr + MAC_GPIO);
+ value |= MAC_GPIO_GPO;
+ writel(value, priv->ioaddr + MAC_GPIO);
+ reset_gpio = devm_gpiod_get_optional(priv->device,
+ "snps,reset",
+ GPIOD_OUT_LOW);
+
+ err = readl_poll_timeout(priv->ioaddr + DMA_BUS_MODE, value,
+ !(value & DMA_BUS_MODE_SFT_RESET),
+ 10000, 1000000);
+
+ if (reset_gpio != NULL) {
+ devm_gpiod_put(priv->device, reset_gpio);
+ }
+
+ if (err) {
+ dev_err(priv->device, "SW reset is not cleared: error %d", err);
+ return NULL;
+ }
+
+ mac->dma = &baikal_gmac_dma_ops;
+ old_mac = priv->hw;
+ priv->hw = mac;
+ err = dwmac1000_setup(priv);
+ priv->hw = old_mac;
+ if (err) {
+ dev_err(priv->device,
+ "%s: dwmac1000_setup failed with error %d",
+ __func__, err);
+ return NULL;
+ }
+
+ return mac;
+}
+
+static void baikal_gmac_fix_mac_speed(void *priv, unsigned int speed)
+{
+ struct arm_smccc_res res;
+ struct baikal_gmac *gmac = priv;
+ unsigned long tx2_clk_freq = 0;
+
+ switch (speed) {
+ case SPEED_1000:
+ tx2_clk_freq = 250000000;
+ if (gmac->has_aux_div2) {
+ arm_smccc_smc(BAIKAL_SMC_GMAC_DIV2_DISABLE,
+ gmac->base, 0, 0, 0, 0, 0, 0, &res);
+ }
+ break;
+ case SPEED_100:
+ tx2_clk_freq = 50000000;
+ if (gmac->has_aux_div2) {
+ arm_smccc_smc(BAIKAL_SMC_GMAC_DIV2_DISABLE,
+ gmac->base, 0, 0, 0, 0, 0, 0, &res);
+ }
+ break;
+ case SPEED_10:
+ tx2_clk_freq = 5000000;
+ if (gmac->has_aux_div2) {
+ tx2_clk_freq *= 2;
+ arm_smccc_smc(BAIKAL_SMC_GMAC_DIV2_ENABLE,
+ gmac->base, 0, 0, 0, 0, 0, 0, &res);
+ }
+ break;
+ }
+
+ if (gmac->tx2_clk != NULL && tx2_clk_freq) {
+ clk_set_rate(gmac->tx2_clk, tx2_clk_freq);
+ }
+}
+
+#ifdef CONFIG_ACPI
+static struct plat_stmmacenet_data *baikal_stmmac_probe_config(struct device *dev,
+ const char **mac)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ u8 nvmem_mac[ETH_ALEN];
+ int ret;
+ bool is_fixed_clk = false;
+
+ plat_dat = devm_kzalloc(dev, sizeof(*plat_dat), GFP_KERNEL);
+ if (!plat_dat) {
+ return ERR_PTR(-ENOMEM);
+ }
+
+ ret = nvmem_get_mac_address(dev, &nvmem_mac);
+ if (ret) {
+ if (ret == -EPROBE_DEFER) {
+ return ERR_PTR(ret);
+ }
+
+ *mac = NULL;
+ } else {
+ *mac = devm_kmemdup(dev, nvmem_mac, ETH_ALEN, GFP_KERNEL);
+ }
+
+ plat_dat->phy_interface = device_get_phy_mode(dev);
+ if (plat_dat->phy_interface < 0) {
+ return NULL;
+ }
+
+ plat_dat->interface = plat_dat->phy_interface;
+
+ if (device_property_read_u32(dev, "max-speed", &plat_dat->max_speed)) {
+ plat_dat->max_speed = -1;
+ }
+
+ plat_dat->bus_id = ACPI_COMPANION(dev)->pnp.instance_no;
+
+ ret = device_property_read_u32(dev, "reg", &plat_dat->phy_addr);
+ if (ret) {
+ dev_err(dev, "couldn't get reg property\n");
+ return ERR_PTR(ret);
+ }
+
+ if (plat_dat->phy_addr >= PHY_MAX_ADDR) {
+ dev_err(dev, "PHY address %i is too large\n",
+ plat_dat->phy_addr);
+ return ERR_PTR(-EINVAL);
+ }
+
+ plat_dat->mdio_bus_data = devm_kzalloc(dev,
+ sizeof(*plat_dat->mdio_bus_data),
+ GFP_KERNEL);
+ if (!plat_dat->mdio_bus_data) {
+ return ERR_PTR(-ENOMEM);
+ }
+
+ plat_dat->mdio_bus_data->needs_reset = true;
+ plat_dat->maxmtu = JUMBO_LEN;
+ plat_dat->multicast_filter_bins = HASH_TABLE_SIZE;
+ plat_dat->unicast_filter_entries = 1;
+ plat_dat->bugged_jumbo = 1; /* TODO: is it really required? */
+
+ plat_dat->dma_cfg = devm_kzalloc(dev,
+ sizeof(*plat_dat->dma_cfg),
+ GFP_KERNEL);
+ if (!plat_dat->dma_cfg) {
+ return ERR_PTR(-ENOMEM);
+ }
+
+ plat_dat->dma_cfg->pbl = DEFAULT_DMA_PBL;
+ device_property_read_u32(dev, "snps,txpbl", &plat_dat->dma_cfg->txpbl);
+ device_property_read_u32(dev, "snps,rxpbl", &plat_dat->dma_cfg->rxpbl);
+ plat_dat->dma_cfg->fixed_burst = device_property_read_bool(dev,
+ "snps,fixed-burst");
+
+ plat_dat->axi = devm_kzalloc(dev, sizeof(*plat_dat->axi), GFP_KERNEL);
+ if (!plat_dat->axi) {
+ return ERR_PTR(-ENOMEM);
+ }
+
+ device_property_read_u32_array(dev, "snps,blen",
+ plat_dat->axi->axi_blen, AXI_BLEN);
+
+ plat_dat->rx_queues_to_use = 1;
+ plat_dat->tx_queues_to_use = 1;
+ plat_dat->rx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB;
+ plat_dat->tx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB;
+
+ if (device_property_read_u32(dev, "stmmac-clk", &plat_dat->clk_ptp_rate)) {
+ plat_dat->clk_ptp_rate = 50000000;
+ }
+
+ plat_dat->stmmac_clk = devm_clk_get(dev, STMMAC_RESOURCE_NAME);
+ if (IS_ERR(plat_dat->stmmac_clk)) {
+ if (!plat_dat->clk_ptp_rate) {
+ dev_err(dev, "stmmaceth clock and 'stmmac-clk' property are missed simultaneously\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ plat_dat->stmmac_clk = clk_register_fixed_rate(NULL,
+ dev_name(dev), NULL, 0,
+ plat_dat->clk_ptp_rate);
+ if (IS_ERR(plat_dat->stmmac_clk))
+ return ERR_CAST(plat_dat->stmmac_clk);
+
+ is_fixed_clk = true;
+ } else {
+ if (!plat_dat->clk_ptp_rate)
+ plat_dat->clk_ptp_rate = clk_get_rate(plat_dat->stmmac_clk);
+ }
+
+ plat_dat->clk_ptp_ref = devm_clk_get(dev, "ptp_ref");
+ if (IS_ERR(plat_dat->clk_ptp_ref)) {
+ plat_dat->clk_ptp_ref = NULL;
+ } else {
+ plat_dat->clk_ptp_rate = clk_get_rate(plat_dat->clk_ptp_ref);
+ }
+
+ clk_prepare_enable(plat_dat->stmmac_clk);
+
+ plat_dat->stmmac_rst = devm_reset_control_get(dev,
+ STMMAC_RESOURCE_NAME);
+ if (IS_ERR(plat_dat->stmmac_rst))
+ plat_dat->stmmac_rst = NULL;
+
+ plat_dat->mdio_bus_data->phy_mask = ~0;
+
+ if (device_get_child_node_count(dev) != 1) {
+ clk_disable_unprepare(plat_dat->stmmac_clk);
+ if (is_fixed_clk)
+ clk_unregister_fixed_rate(plat_dat->stmmac_clk);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return plat_dat;
+}
+
+static int baikal_add_mdio_phy(struct device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev_get_drvdata(dev));
+ struct fwnode_handle *fwnode = device_get_next_child_node(dev, NULL);
+ struct phy_device *phy;
+ int ret;
+
+ phy = get_phy_device(priv->mii, priv->plat->phy_addr, 0);
+ if (IS_ERR(phy)) {
+ return PTR_ERR(phy);
+ }
+
+ phy->irq = priv->mii->irq[priv->plat->phy_addr];
+ phy->mdio.dev.fwnode = fwnode;
+
+ ret = phy_device_register(phy);
+ if (ret) {
+ phy_device_free(phy);
+ return ret;
+ }
+
+ return 0;
+}
+#else
+static struct plat_stmmacenet_data *baikal_stmmac_probe_config(struct device *dev,
+ const char **mac)
+{
+ return NULL;
+}
+
+static int baikal_add_mdio_phy(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+static int baikal_gmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct resource *res;
+ struct baikal_gmac *gmac;
+ struct device_node *dn = NULL;
+ const char *str = NULL;
+ int ret;
+
+ if (acpi_disabled) {
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret) {
+ return ret;
+ }
+ } else {
+ memset(&stmmac_res, 0, sizeof(stmmac_res));
+ stmmac_res.irq = platform_get_irq(pdev, 0);
+ if (stmmac_res.irq < 0) {
+ return stmmac_res.irq;
+ }
+
+ stmmac_res.wol_irq = stmmac_res.irq;
+ stmmac_res.addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(stmmac_res.addr)) {
+ return PTR_ERR(stmmac_res.addr);
+ }
+ }
+
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_warn(&pdev->dev, "no suitable DMA available\n");
+ return ret;
+ }
+
+ if (pdev->dev.of_node) {
+ plat_dat = stmmac_probe_config_dt(pdev, (u8 *)&stmmac_res.mac);
+ if (IS_ERR(plat_dat)) {
+ dev_err(&pdev->dev, "dt configuration failed\n");
+ return PTR_ERR(plat_dat);
+ }
+ } else if (!acpi_disabled) {
+ plat_dat = baikal_stmmac_probe_config(&pdev->dev,
+ (const char **)&stmmac_res.mac);
+ if (IS_ERR(plat_dat)) {
+ dev_err(&pdev->dev, "acpi configuration failed\n");
+ return PTR_ERR(plat_dat);
+ }
+
+ dn = kzalloc(sizeof(struct device_node), GFP_KERNEL);
+ if (!dn) {
+ ret = -ENOMEM;
+ goto err_remove_config_dt;
+ }
+
+ plat_dat->phy_node = dn;
+ } else {
+ plat_dat = dev_get_platdata(&pdev->dev);
+ if (!plat_dat) {
+ dev_err(&pdev->dev, "no platform data provided\n");
+ return -EINVAL;
+ }
+
+ /* Set default value for multicast hash bins */
+ plat_dat->multicast_filter_bins = HASH_TABLE_SIZE;
+
+ /* Set default value for unicast filter entries */
+ plat_dat->unicast_filter_entries = 1;
+ }
+
+ gmac = devm_kzalloc(&pdev->dev, sizeof(*gmac), GFP_KERNEL);
+ if (!gmac) {
+ ret = -ENOMEM;
+ goto err_remove_config_dt;
+ }
+
+ gmac->dev = &pdev->dev;
+ gmac->tx2_clk = devm_clk_get(gmac->dev, "tx2_clk");
+ if (IS_ERR(gmac->tx2_clk)) {
+ dev_warn(&pdev->dev, "couldn't get TX2 clock\n");
+ gmac->tx2_clk = NULL;
+ }
+
+ gmac->axi_clk = devm_clk_get(gmac->dev, "axi_clk");
+ if (IS_ERR(gmac->axi_clk)) {
+ dev_warn(&pdev->dev, "couldn't get AXI clock\n");
+ gmac->axi_clk = NULL;
+ } else {
+ clk_set_rate(gmac->axi_clk, 300000000);
+ }
+
+ if (!acpi_disabled) {
+ device_property_read_string(&pdev->dev, "compatible", &str);
+ }
+
+ if ((gmac->dev->of_node &&
+ of_device_is_compatible(gmac->dev->of_node, "baikal,bs1000-gmac"))
+ || (str && strcasecmp(str, "baikal,bs1000-gmac") == 0)) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ gmac->base = res->start;
+ gmac->has_aux_div2 = 1;
+ } else {
+ gmac->has_aux_div2 = 0;
+ }
+
+ plat_dat->fix_mac_speed = baikal_gmac_fix_mac_speed;
+ plat_dat->bsp_priv = gmac;
+ plat_dat->has_gmac = 1;
+ plat_dat->bugged_jumbo = 1; /* TODO: is it really required? */
+ plat_dat->setup = baikal_gmac_setup;
+
+ ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+ if (ret) {
+ goto err_remove_config_dt;
+ }
+
+ if (!acpi_disabled) {
+ ret = baikal_add_mdio_phy(&pdev->dev);
+ if (ret) {
+ goto err_remove_config_dt;
+ }
+ }
+
+ kfree(dn);
+ return 0;
+
+err_remove_config_dt:
+ stmmac_remove_config_dt(pdev, plat_dat);
+
+ if (!acpi_disabled) {
+ clk_disable_unprepare(plat_dat->stmmac_clk);
+ clk_unregister_fixed_rate(plat_dat->stmmac_clk);
+ }
+
+ kfree(dn);
+ return ret;
+}
+
+static int baikal_gmac_remove(struct platform_device *pdev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev_get_drvdata(&pdev->dev));
+
+ if (!acpi_disabled) {
+ clk_disable_unprepare(priv->plat->stmmac_clk);
+ clk_unregister_fixed_rate(priv->plat->stmmac_clk);
+ priv->plat->stmmac_clk = NULL;
+ }
+
+ return stmmac_pltfr_remove(pdev);
+}
+
+static const struct of_device_id baikal_gmac_dwmac_match[] = {
+ { .compatible = "baikal,bm1000-gmac" },
+ { .compatible = "baikal,bs1000-gmac" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, baikal_gmac_dwmac_match);
+
+static struct platform_driver baikal_gmac_dwmac_driver = {
+ .probe = baikal_gmac_probe,
+ .remove = baikal_gmac_remove,
+ .driver = {
+ .name = "baikal-gmac-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = of_match_ptr(baikal_gmac_dwmac_match)
+ }
+};
+module_platform_driver(baikal_gmac_dwmac_driver);
+
+MODULE_DESCRIPTION("Baikal DWMAC specific glue driver");
+MODULE_LICENSE("GPL");
#include "dwmac1000.h"
#include "dwmac_dma.h"
-static void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
+void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
{
u32 value = readl(ioaddr + DMA_AXI_BUS_MODE);
int i;
writel(value, ioaddr + DMA_AXI_BUS_MODE);
}
+EXPORT_SYMBOL_GPL(dwmac1000_dma_axi);
-static void dwmac1000_dma_init(void __iomem *ioaddr,
- struct stmmac_dma_cfg *dma_cfg, int atds)
+void dwmac1000_dma_init(void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg, int atds)
{
u32 value = readl(ioaddr + DMA_BUS_MODE);
int txpbl = dma_cfg->txpbl ?: dma_cfg->pbl;
/* Mask interrupts by writing to CSR7 */
writel(DMA_INTR_DEFAULT_MASK, ioaddr + DMA_INTR_ENA);
}
+EXPORT_SYMBOL_GPL(dwmac1000_dma_init);
-static void dwmac1000_dma_init_rx(void __iomem *ioaddr,
- struct stmmac_dma_cfg *dma_cfg,
- dma_addr_t dma_rx_phy, u32 chan)
+void dwmac1000_dma_init_rx(void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_rx_phy, u32 chan)
{
/* RX descriptor base address list must be written into DMA CSR3 */
writel(lower_32_bits(dma_rx_phy), ioaddr + DMA_RCV_BASE_ADDR);
}
+EXPORT_SYMBOL_GPL(dwmac1000_dma_init_rx);
-static void dwmac1000_dma_init_tx(void __iomem *ioaddr,
- struct stmmac_dma_cfg *dma_cfg,
- dma_addr_t dma_tx_phy, u32 chan)
+void dwmac1000_dma_init_tx(void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_tx_phy, u32 chan)
{
/* TX descriptor base address list must be written into DMA CSR4 */
writel(lower_32_bits(dma_tx_phy), ioaddr + DMA_TX_BASE_ADDR);
}
+EXPORT_SYMBOL_GPL(dwmac1000_dma_init_tx);
static u32 dwmac1000_configure_fc(u32 csr6, int rxfifosz)
{
return csr6;
}
-static void dwmac1000_dma_operation_mode_rx(void __iomem *ioaddr, int mode,
- u32 channel, int fifosz, u8 qmode)
+void dwmac1000_dma_operation_mode_rx(void __iomem *ioaddr, int mode,
+ u32 channel, int fifosz, u8 qmode)
{
u32 csr6 = readl(ioaddr + DMA_CONTROL);
writel(csr6, ioaddr + DMA_CONTROL);
}
+EXPORT_SYMBOL_GPL(dwmac1000_dma_operation_mode_rx);
-static void dwmac1000_dma_operation_mode_tx(void __iomem *ioaddr, int mode,
- u32 channel, int fifosz, u8 qmode)
+void dwmac1000_dma_operation_mode_tx(void __iomem *ioaddr, int mode,
+ u32 channel, int fifosz, u8 qmode)
{
u32 csr6 = readl(ioaddr + DMA_CONTROL);
writel(csr6, ioaddr + DMA_CONTROL);
}
+EXPORT_SYMBOL_GPL(dwmac1000_dma_operation_mode_tx);
-static void dwmac1000_dump_dma_regs(void __iomem *ioaddr, u32 *reg_space)
+void dwmac1000_dump_dma_regs(void __iomem *ioaddr, u32 *reg_space)
{
int i;
reg_space[DMA_BUS_MODE / 4 + i] =
readl(ioaddr + DMA_BUS_MODE + i * 4);
}
+EXPORT_SYMBOL_GPL(dwmac1000_dump_dma_regs);
-static int dwmac1000_get_hw_feature(void __iomem *ioaddr,
+int dwmac1000_get_hw_feature(void __iomem *ioaddr,
struct dma_features *dma_cap)
{
u32 hw_cap = readl(ioaddr + DMA_HW_FEATURE);
return 0;
}
+EXPORT_SYMBOL_GPL(dwmac1000_get_hw_feature);
-static void dwmac1000_rx_watchdog(void __iomem *ioaddr, u32 riwt,
- u32 queue)
+void dwmac1000_rx_watchdog(void __iomem *ioaddr, u32 riwt,
+ u32 number_chan)
{
writel(riwt, ioaddr + DMA_RX_WATCHDOG);
}
+EXPORT_SYMBOL_GPL(dwmac1000_rx_watchdog);
const struct stmmac_dma_ops dwmac1000_dma_ops = {
.reset = dwmac_dma_reset,
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __DWMAC1000_DMA_H__
+#define __DWMAC1000_DMA_H__
+#include "dwmac1000.h"
+
+void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi);
+void dwmac1000_dma_init(void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg, int atds);
+void dwmac1000_dma_init_rx(void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_rx_phy, u32 chan);
+void dwmac1000_dma_init_tx(void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_tx_phy, u32 chan);
+void dwmac1000_dma_operation_mode_rx(void __iomem *ioaddr, int mode,
+ u32 channel, int fifosz, u8 qmode);
+void dwmac1000_dma_operation_mode_tx(void __iomem *ioaddr, int mode,
+ u32 channel, int fifosz, u8 qmode);
+void dwmac1000_dump_dma_regs(void __iomem *ioaddr, u32 *reg_space);
+
+int dwmac1000_get_hw_feature(void __iomem *ioaddr,
+ struct dma_features *dma_cap);
+
+void dwmac1000_rx_watchdog(void __iomem *ioaddr, u32 riwt, u32 number_chan);
+#endif /* __DWMAC1000_DMA_H__ */
Maintainer: Giuseppe Cavallaro <peppe.cavallaro@st.com>
*******************************************************************************/
+#include <linux/acpi.h>
#include <linux/gpio/consumer.h>
#include <linux/io.h>
#include <linux/iopoll.h>
struct stmmac_priv *priv = netdev_priv(ndev);
unsigned int mii_address = priv->hw->mii.addr;
-#ifdef CONFIG_OF
- if (priv->device->of_node) {
+ if (priv->device->of_node ||
+ is_acpi_device_node(priv->device->fwnode)) {
struct gpio_desc *reset_gpio;
u32 delays[3] = { 0, 0, 0 };
if (delays[2])
msleep(DIV_ROUND_UP(delays[2], 1000));
}
-#endif
/* This is a workaround for problems with the STE101P PHY.
* It doesn't complete its reset until at least one clock cycle
Enables support for the PCIe controller in the ARTPEC-6 SoC to work in
endpoint mode. This uses the DesignWare core.
+config PCIE_BAIKAL
+ bool
+
+config PCIE_BAIKAL_HOST
+ bool "Baikal SoCs PCIe controller - Host Mode"
+ depends on ARCH_BAIKAL || COMPILE_TEST
+ depends on OF || (ACPI && PCI_QUIRKS)
+ depends on PCI_MSI_IRQ_DOMAIN
+ select PCIE_DW_HOST
+ select PCIE_BAIKAL
+ help
+ Enables support for the PCIe controller in Baikal SoCs to work in
+ host mode. There are two instances of PCIe controller in Baikal SoCs.
+ This controller can work either as EP or RC. In order to enable
+ host-specific features PCIE_BAIKAL_HOST must be selected and in order
+ to enable device-specific features PCIE_BAIKAL_EP must be selected.
+ This uses the DesignWare core.
+
+config PCIE_BAIKAL_EP
+ bool "Baikal SoCs PCIe controller - Endpoint Mode"
+ depends on ARCH_BAIKAL || COMPILE_TEST
+ depends on PCI_ENDPOINT
+ select PCIE_DW_EP
+ select PCIE_BAIKAL
+ help
+ Enables support for the PCIe controller in Baikal SoCs to work in
+ host mode. There are two instances of PCIe controller in Baikal SoCs.
+ This controller can work either as EP or RC. In order to enable
+ host-specific features PCIE_BAIKAL_HOST must be selected and in order
+ to enable device-specific features PCIE_BAIKAL_EP must be selected.
+ This uses the DesignWare core.
+
config PCIE_ROCKCHIP_DW_HOST
bool "Rockchip DesignWare PCIe controller"
select PCIE_DW
obj-$(CONFIG_PCIE_QCOM_EP) += pcie-qcom-ep.o
obj-$(CONFIG_PCIE_ARMADA_8K) += pcie-armada8k.o
obj-$(CONFIG_PCIE_ARTPEC6) += pcie-artpec6.o
+obj-$(CONFIG_PCIE_BAIKAL) += pcie-baikal.o
obj-$(CONFIG_PCIE_ROCKCHIP_DW_HOST) += pcie-dw-rockchip.o
obj-$(CONFIG_PCIE_INTEL_GW) += pcie-intel-gw.o
obj-$(CONFIG_PCIE_KEEMBAY) += pcie-keembay.o
obj-$(CONFIG_PCIE_UNIPHIER_EP) += pcie-uniphier-ep.o
obj-$(CONFIG_PCIE_VISCONTI_HOST) += pcie-visconti.o
+pcie-baikal-objs := pcie-baikal-core.o \
+ pcie-baikal-tune.o
+
# The following drivers are for devices that use the generic ACPI
# pci_root.c driver but don't support standard ECAM config access.
# They contain MCFG quirks to replace the generic ECAM accessors with
obj-$(CONFIG_ARM64) += pcie-al.o
obj-$(CONFIG_ARM64) += pcie-hisi.o
obj-$(CONFIG_ARM64) += pcie-tegra194-acpi.o
+obj-$(CONFIG_ARM64) += pcie-baikal-acpi.o
endif
endif
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021-2023 Baikal Electronics, JSC
+ * Author: Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
+ */
+
+#include <linux/pci-ecam.h>
+
+#ifdef CONFIG_PCIE_BAIKAL
+extern const struct pci_ecam_ops baikal_m_pcie_ecam_ops;
+extern const struct pci_ecam_ops baikal_s_pcie_ecam_ops;
+#else
+const struct pci_ecam_ops baikal_m_pcie_ecam_ops = {
+ .bus_shift = 20,
+ .pci_ops = {
+ .map_bus = pci_ecam_map_bus,
+ .read = pci_generic_config_read,
+ .write = pci_generic_config_write
+ }
+};
+
+const struct pci_ecam_ops baikal_s_pcie_ecam_ops = baikal_m_pcie_ecam_ops;
+#endif
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PCIe controller driver for Baikal Electronics SoCs
+ *
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ * Authors: Pavel Parkhomenko <pavel.parkhomenko@baikalelectronics.ru>
+ * Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
+ */
+
+#include <linux/acpi.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/pci.h>
+#include <linux/pci-ecam.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <acpi/acrestyp.h>
+
+#include "pcie-designware.h"
+#include "pcie-baikal.h"
+
+#define BAIKAL_PCIE_LTSSM_MASK 0x3f
+#define BAIKAL_PCIE_LTSSM_STATE_L0 0x11
+
+#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
+struct baikal_pcie_acpi_data {
+ u64 mem_base;
+ phys_addr_t mem_bus_addr;
+ u32 mem_size;
+};
+
+static const struct baikal_pcie_of_data bm1000_pcie_rc_of_data;
+static const struct baikal_pcie_of_data bs1000_pcie_rc_of_data;
+#else
+static inline u16 bm1000_pcie_get_segment_acpi(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+struct baikal_pcie_of_data {
+ enum dw_pcie_device_mode mode;
+ struct dw_pcie_ops dw_pcie_ops;
+ int (*get_resources)(struct platform_device *pdev,
+ struct dw_pcie *pci,
+ const struct baikal_pcie_of_data *data);
+ int (*add_pcie_port)(struct platform_device *pdev);
+};
+
+static const char *baikal_pcie_link_speed_str(const unsigned speed)
+{
+ static const char *speed_str[] = {"2.5", "5.0", "8.0", "16.0"};
+
+ if (speed > 0 && speed <= ARRAY_SIZE(speed_str))
+ return speed_str[speed - 1];
+
+ return "???";
+}
+
+static void baikal_pcie_link_print_status(struct dw_pcie *pci)
+{
+ struct device *dev = pci->dev;
+ u16 exp_cap_off;
+ u32 reg;
+
+ if (!pci->ops->link_up(pci)) {
+ dev_info(dev, "link is down\n");
+ return;
+ }
+
+ exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+ reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKSTA);
+
+ dev_info(dev, "current link is %s GT/s%s, x%u\n",
+ baikal_pcie_link_speed_str(FIELD_GET(PCI_EXP_LNKSTA_CLS, reg)),
+ FIELD_GET(PCI_EXP_LNKSTA_LT, reg) ? " (training)" : "",
+ FIELD_GET(PCI_EXP_LNKSTA_NLW, reg));
+}
+
+static bool baikal_pcie_link_is_training(struct dw_pcie *pci)
+{
+ u16 exp_cap_off;
+ u32 reg;
+
+ exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+ reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKSTA);
+ return FIELD_GET(PCI_EXP_LNKSTA_LT, reg);
+}
+
+static bool baikal_pcie_link_wait_training_done(struct dw_pcie *pci)
+{
+ struct device *dev = pci->dev;
+ unsigned long start_jiffies = jiffies;
+
+ while (baikal_pcie_link_is_training(pci)) {
+ if (time_after(jiffies, start_jiffies + HZ)) {
+ dev_err(dev, "link training timeout occured\n");
+ return false;
+ }
+
+ udelay(100);
+ }
+
+ return true;
+}
+
+#define BM1000_PCIE_GPR_RESET_BASE 0x00
+#define BM1000_PCIE_GPR_RESET(x) ((x * 0x20) + BM1000_PCIE_GPR_RESET_BASE)
+#define BM1000_PCIE_PHY_RST BIT(0)
+#define BM1000_PCIE_PIPE_RST BIT(4) /* x4 controllers only */
+#define BM1000_PCIE_PIPE0_RST BIT(4) /* x8 controller only */
+#define BM1000_PCIE_PIPE1_RST BIT(5) /* x8 controller only */
+#define BM1000_PCIE_CORE_RST BIT(8)
+#define BM1000_PCIE_PWR_RST BIT(9)
+#define BM1000_PCIE_STICKY_RST BIT(10)
+#define BM1000_PCIE_NONSTICKY_RST BIT(11)
+#define BM1000_PCIE_HOT_RST BIT(12)
+#define BM1000_PCIE_ADB_PWRDWN BIT(13)
+
+#define BM1000_PCIE_GPR_STATUS_BASE 0x04
+#define BM1000_PCIE_GPR_STATUS(x) ((x * 0x20) + BM1000_PCIE_GPR_STATUS_BASE)
+
+#define BM1000_PCIE_GPR_GENCTL_BASE 0x08
+#define BM1000_PCIE_GPR_GENCTL(x) ((x * 0x20) + BM1000_PCIE_GPR_GENCTL_BASE)
+#define BM1000_PCIE_LTSSM_ENABLE BIT(1)
+#define BM1000_PCIE_DBI2_MODE BIT(2)
+#define BM1000_PCIE_PHY_MGMT_ENABLE BIT(3)
+
+#define BM1000_PCIE_GPR_MSI_TRANS_CTL2 0xf8
+#define BM1000_PCIE_MSI_TRANS_EN(x) BIT(9 + (x))
+#define BM1000_PCIE_MSI_TRANS_RCNUM(x) ((x) << (2 * (x)))
+#define BM1000_PCIE_MSI_TRANS_RCNUM_MASK(x) ((3) << (2 * (x)))
+
+#define BM1000_PCIE0_DBI_BASE 0x02200000
+#define BM1000_PCIE1_DBI_BASE 0x02210000
+#define BM1000_PCIE2_DBI_BASE 0x02220000
+
+struct bm1000_pcie {
+ struct dw_pcie *pci;
+ unsigned int num;
+ struct regmap *gpr;
+ union {
+ struct gpio_desc *reset_gpio;
+ struct {
+ u8 num : 5;
+ u8 polarity : 1;
+ u8 is_set : 1;
+ } gpio[2];
+ };
+ bool reset_active_low;
+ char reset_name[32];
+ bool retrained;
+};
+
+void bm1000_pcie_phy_enable(struct dw_pcie *pci)
+{
+ struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
+ u32 reg;
+
+ regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), ®);
+ reg |= BM1000_PCIE_PHY_MGMT_ENABLE | BM1000_PCIE_DBI2_MODE;
+ regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+}
+
+void bm1000_pcie_phy_disable(struct dw_pcie *pci)
+{
+ struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
+ u32 reg;
+
+ regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), ®);
+ reg &= ~(BM1000_PCIE_PHY_MGMT_ENABLE | BM1000_PCIE_DBI2_MODE);
+ regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+}
+
+static int bm1000_get_resources(struct platform_device *pdev,
+ struct dw_pcie *pci,
+ const struct baikal_pcie_of_data *data)
+{
+ struct bm1000_pcie *bm = platform_get_drvdata(pdev);
+ struct device *dev = pci->dev;
+ struct resource *res;
+ int reset_gpio;
+ enum of_gpio_flags gpio_flags;
+
+ bm->pci = pci;
+ bm->gpr = syscon_regmap_lookup_by_compatible("baikal,bm1000-pcie-gpr");
+ if (IS_ERR(bm->gpr)) {
+ dev_err(dev, "failed to find PCIe GPR registers\n");
+ return PTR_ERR(bm->gpr);
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
+ if (!res) {
+ dev_err(dev, "failed to find \"dbi\" region\n");
+ return -EINVAL;
+ }
+
+ pci->dbi_base = devm_pci_remap_cfg_resource(dev, res);
+ if (IS_ERR(pci->dbi_base))
+ return PTR_ERR(pci->dbi_base);
+
+ if (res->start == BM1000_PCIE0_DBI_BASE) {
+ bm->num = 0;
+ } else if (res->start == BM1000_PCIE1_DBI_BASE) {
+ bm->num = 1;
+ } else if (res->start == BM1000_PCIE2_DBI_BASE) {
+ bm->num = 2;
+ } else {
+ dev_err(dev, "incorrect \"dbi\" base\n");
+ return -EINVAL;
+ }
+
+ reset_gpio = of_get_named_gpio_flags(dev->of_node, "reset-gpios", 0,
+ &gpio_flags);
+ if (gpio_is_valid(reset_gpio)) {
+ bm->reset_gpio = gpio_to_desc(reset_gpio);
+ bm->reset_active_low = gpio_flags & OF_GPIO_ACTIVE_LOW;
+ snprintf(bm->reset_name, sizeof(bm->reset_name), "pcie%u-reset",
+ bm->num);
+ } else {
+ bm->reset_gpio = NULL;
+ }
+
+ return 0;
+}
+
+static int bm1000_pcie_link_up(struct dw_pcie *pci)
+{
+ struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
+ u32 reg;
+
+ regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), ®);
+ if (!(reg & BM1000_PCIE_LTSSM_ENABLE))
+ return 0;
+
+ regmap_read(bm->gpr, BM1000_PCIE_GPR_STATUS(bm->num), ®);
+ return (reg & BAIKAL_PCIE_LTSSM_MASK) == BAIKAL_PCIE_LTSSM_STATE_L0;
+}
+
+static int bm1000_pcie_start_link(struct dw_pcie *pci)
+{
+ struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
+ u32 reg;
+
+ regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), ®);
+ reg |= BM1000_PCIE_LTSSM_ENABLE;
+ regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+ return 0;
+}
+
+static void bm1000_pcie_link_speed_fixup(struct bm1000_pcie *bm,
+ struct pci_bus *bus)
+{
+ struct pci_dev *dev;
+ struct dw_pcie *pci = bm->pci;
+ unsigned dev_lnkcap_speed;
+ unsigned dev_lnkcap_width;
+ unsigned rc_lnkcap_speed;
+ unsigned rc_lnksta_speed;
+ unsigned rc_target_speed;
+ u16 exp_cap_off;
+ u32 reg;
+
+ /* Return if the bus has already been retrained */
+ if (bm->retrained)
+ return;
+
+ list_for_each_entry(dev, &bus->devices, bus_list)
+ if (dev->subordinate)
+ bm1000_pcie_link_speed_fixup(bm, dev->subordinate);
+
+ /* Skip root bridge and devices not directly attached to the RC */
+ if (pci_is_root_bus(bus) || !pci_is_root_bus(bus->parent))
+ return;
+
+ dev = list_first_entry_or_null(&bus->devices, struct pci_dev, bus_list);
+ if (dev == NULL)
+ return;
+
+ exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+
+ reg = dw_pcie_readl_dbi(pci, exp_cap_off + PCI_EXP_LNKCAP);
+ rc_lnkcap_speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, reg);
+
+ reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKSTA);
+ rc_lnksta_speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, reg);
+
+ if (acpi_disabled) {
+ pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, ®);
+ } else {
+ struct pci_config_window *cfg;
+ int where = pci_pcie_cap(dev) + PCI_EXP_LNKCAP;
+
+ cfg = dev->bus->sysdata;
+ if (dev->bus->number == cfg->busr.start) {
+ if (PCI_SLOT(dev->devfn) > 0)
+ reg = 0;
+ else
+ reg = readl(pci->dbi_base + where);
+ } else {
+ unsigned int busn = dev->bus->number - cfg->busr.start;
+ unsigned int devfn_shift = cfg->ops->bus_shift - 8;
+
+ reg = readl(cfg->win +
+ (busn << cfg->ops->bus_shift) +
+ (dev->devfn << devfn_shift) +
+ where);
+ }
+ }
+
+ dev_lnkcap_speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, reg);
+ dev_lnkcap_width = FIELD_GET(PCI_EXP_LNKCAP_MLW, reg);
+
+ baikal_pcie_link_print_status(pci);
+
+ /*
+ * [2.5 -> 8.0 GT/s] is suitable way of retraining.
+ * [2.5 -> 5.0 GT/s] is used when 8.0 GT/s could not be reached.
+ * [5.0 -> 8.0 GT/s] causes system freezing sometimes.
+ */
+ if (rc_lnkcap_speed < dev_lnkcap_speed)
+ rc_target_speed = rc_lnkcap_speed;
+ else
+ rc_target_speed = dev_lnkcap_speed;
+
+ while (rc_lnksta_speed < rc_target_speed) {
+ unsigned long start_jiffies;
+
+ /* Try to change link speed */
+ dev_info(pci->dev, "retrain link to %s GT/s\n",
+ baikal_pcie_link_speed_str(rc_target_speed));
+
+ /* If link is already training wait for training to complete */
+ baikal_pcie_link_wait_training_done(pci);
+
+ /* Set desired speed */
+ reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKCTL2);
+ reg &= ~PCI_EXP_LNKCTL2_TLS;
+ reg |= rc_target_speed;
+ dw_pcie_writew_dbi(pci, exp_cap_off + PCI_EXP_LNKCTL2, reg);
+
+ /* Deassert and assert PORT_LOGIC_SPEED_CHANGE bit */
+ reg = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
+ reg &= ~PORT_LOGIC_SPEED_CHANGE;
+ dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, reg);
+ reg |= PORT_LOGIC_SPEED_CHANGE;
+ dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, reg);
+
+ /* Wait for link training */
+ start_jiffies = jiffies;
+ for (;;) {
+ /* Wait for link training begin */
+ if (!baikal_pcie_link_is_training(pci)) {
+ if (time_after(jiffies, start_jiffies + HZ)) {
+ dev_err(pci->dev,
+ "link training has not started\n");
+ /*
+ * Don't wait for training_done()
+ * if it hasn't started.
+ */
+ break;
+ }
+
+ udelay(100);
+ continue;
+ }
+
+ /* Wait for link training end */
+ if (!baikal_pcie_link_wait_training_done(pci))
+ break;
+
+ if (!dw_pcie_wait_for_link(pci)) {
+ /* Wait if link switched to config/recovery */
+ baikal_pcie_link_wait_training_done(pci);
+ baikal_pcie_link_print_status(pci);
+ }
+
+ break;
+ }
+
+ /* Check if the link is down after retrain */
+ if (!bm1000_pcie_link_up(pci)) {
+ /*
+ * Check if the link has already been down and
+ * the link is unable to re-establish at 2.5 GT/s
+ */
+ if (rc_lnksta_speed == 0 &&
+ rc_target_speed == PCI_EXP_LNKCTL2_TLS_2_5GT)
+ break;
+
+ rc_lnksta_speed = 0;
+ if (rc_target_speed > PCI_EXP_LNKCTL2_TLS_2_5GT) {
+ /* Try to use lower speed */
+ --rc_target_speed;
+ }
+
+ continue;
+ }
+
+ reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKSTA);
+ rc_lnksta_speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, reg);
+
+ /* Check if the targeted speed has not been reached */
+ if (rc_lnksta_speed < rc_target_speed &&
+ rc_target_speed > PCI_EXP_LNKCTL2_TLS_2_5GT) {
+ /* Try to use lower speed */
+ --rc_target_speed;
+ }
+ }
+
+ bm->retrained = true;
+}
+
+static u16 bm1000_pcie_get_segment_acpi(struct device *dev);
+
+static void bm1000_pcie_set_gpio(u8 num, bool enable)
+{
+ void __iomem *addr;
+ u32 val;
+
+ if (num > 31)
+ return;
+
+ addr = ioremap(0x20200000, 8);
+ if (addr) {
+ val = readl(addr);
+ if (enable)
+ val |= 1 << num;
+ else
+ val &= ~BIT(num);
+ writel(val, addr);
+
+ val = readl(addr + 4);
+ val |= 1 << num;
+ writel(val, addr + 4);
+
+ iounmap(addr);
+ }
+}
+
+static int bm1000_pcie_host_init(struct dw_pcie_rp *pp)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
+ struct device *dev = pci->dev;
+ int err;
+ int linkup;
+ u16 exp_cap_off;
+ u16 ext_cap_err_off;
+ u32 reg;
+
+ /* Disable access to PHY registers and DBI2 mode */
+ bm1000_pcie_phy_disable(pci);
+
+ bm->retrained = false;
+ linkup = bm1000_pcie_link_up(pci);
+
+ /* If link is not established yet, reset the RC */
+ if (!linkup) {
+ /* Disable link training */
+ regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), ®);
+ reg &= ~BM1000_PCIE_LTSSM_ENABLE;
+ regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+
+ /* Assert PERST pin */
+ if (acpi_disabled && bm->reset_gpio) {
+ unsigned long gpio_flags;
+
+ if (bm->reset_active_low) {
+ gpio_flags = GPIOF_ACTIVE_LOW |
+ GPIOF_OUT_INIT_LOW;
+ } else {
+ gpio_flags = GPIOF_OUT_INIT_HIGH;
+ }
+
+ err = devm_gpio_request_one(dev,
+ desc_to_gpio(bm->reset_gpio),
+ gpio_flags, bm->reset_name);
+ if (err) {
+ dev_err(dev, "request GPIO failed (%d)\n", err);
+ return err;
+ }
+ } else if (!acpi_disabled && bm->gpio[0].is_set) {
+ bm1000_pcie_set_gpio(bm->gpio[0].num, ~bm->gpio[0].polarity);
+ }
+
+ /* Reset the RC */
+ regmap_read(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), ®);
+ reg |= BM1000_PCIE_NONSTICKY_RST |
+ BM1000_PCIE_STICKY_RST |
+ BM1000_PCIE_PWR_RST |
+ BM1000_PCIE_CORE_RST |
+ BM1000_PCIE_PHY_RST;
+
+ /* If the RC is PCIe x8, reset PIPE0 and PIPE1 */
+ if (bm->num == 2) {
+ reg |= BM1000_PCIE_PIPE0_RST |
+ BM1000_PCIE_PIPE1_RST;
+ } else {
+ reg |= BM1000_PCIE_PIPE_RST;
+ }
+
+ regmap_write(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), reg);
+
+ if (!acpi_disabled && bm->num == 2 && bm->gpio[1].is_set) {
+ /* Assert PRSNT pin */
+ bm1000_pcie_set_gpio(bm->gpio[1].num, ~bm->gpio[1].polarity);
+ }
+
+ usleep_range(20000, 30000);
+
+ /* Deassert PERST pin */
+ if (acpi_disabled && bm->reset_gpio)
+ gpiod_set_value_cansleep(bm->reset_gpio, 0);
+ else if (!acpi_disabled && bm->gpio[0].is_set)
+ bm1000_pcie_set_gpio(bm->gpio[0].num, bm->gpio[0].polarity);
+
+ /* Deassert PHY reset */
+ regmap_read(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), ®);
+ reg &= ~BM1000_PCIE_PHY_RST;
+ regmap_write(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), reg);
+
+ /* Deassert all software controlled resets */
+ regmap_read(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), ®);
+ reg &= ~(BM1000_PCIE_ADB_PWRDWN |
+ BM1000_PCIE_HOT_RST |
+ BM1000_PCIE_NONSTICKY_RST |
+ BM1000_PCIE_STICKY_RST |
+ BM1000_PCIE_PWR_RST |
+ BM1000_PCIE_CORE_RST |
+ BM1000_PCIE_PHY_RST);
+
+ if (bm->num == 2) {
+ reg &= ~(BM1000_PCIE_PIPE0_RST |
+ BM1000_PCIE_PIPE1_RST);
+ } else {
+ reg &= ~BM1000_PCIE_PIPE_RST;
+ }
+
+ regmap_write(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), reg);
+ }
+
+ /* Enable error reporting */
+ ext_cap_err_off = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_ERR);
+ reg = dw_pcie_readl_dbi(pci, ext_cap_err_off + PCI_ERR_ROOT_COMMAND);
+ reg |= PCI_ERR_ROOT_CMD_COR_EN |
+ PCI_ERR_ROOT_CMD_NONFATAL_EN |
+ PCI_ERR_ROOT_CMD_FATAL_EN;
+ dw_pcie_writel_dbi(pci, ext_cap_err_off + PCI_ERR_ROOT_COMMAND, reg);
+
+ exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+ reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_DEVCTL);
+ reg |= PCI_EXP_DEVCTL_CERE |
+ PCI_EXP_DEVCTL_NFERE |
+ PCI_EXP_DEVCTL_FERE |
+ PCI_EXP_DEVCTL_URRE;
+ dw_pcie_writew_dbi(pci, exp_cap_off + PCI_EXP_DEVCTL, reg);
+
+ reg = dw_pcie_readl_dbi(pci, exp_cap_off + PCI_EXP_RTCTL);
+ reg |= PCI_EXP_RTCTL_SECEE |
+ PCI_EXP_RTCTL_SENFEE |
+ PCI_EXP_RTCTL_SEFEE |
+ PCI_EXP_RTCTL_PMEIE;
+ dw_pcie_writel_dbi(pci, exp_cap_off + PCI_EXP_RTCTL, reg);
+
+ if (linkup) {
+ dev_info(dev, "link is already up\n");
+ } else {
+ /* Use 2.5 GT/s rate for link establishing */
+ reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKCTL2);
+ reg &= ~PCI_EXP_LNKCTL2_TLS;
+ reg |= PCI_EXP_LNKCTL2_TLS_2_5GT;
+ dw_pcie_writew_dbi(pci, exp_cap_off + PCI_EXP_LNKCTL2, reg);
+ }
+
+ regmap_read(bm->gpr, BM1000_PCIE_GPR_MSI_TRANS_CTL2, ®);
+ reg &= ~BM1000_PCIE_MSI_TRANS_RCNUM_MASK(bm->num);
+ if (acpi_disabled)
+ reg |= BM1000_PCIE_MSI_TRANS_RCNUM(bm->num);
+ else
+ reg |= (bm1000_pcie_get_segment_acpi(dev) + 1) << (2 * bm->num);
+ reg |= BM1000_PCIE_MSI_TRANS_EN(bm->num);
+ regmap_write(bm->gpr, BM1000_PCIE_GPR_MSI_TRANS_CTL2, reg);
+
+ /* RX/TX equalizers fine tune */
+ bm1000_pcie_tune(pci);
+
+ return 0;
+}
+
+static irqreturn_t bm1000_pcie_aer_irq_handler(int irq, void *arg)
+{
+ struct bm1000_pcie *bm = arg;
+ struct dw_pcie *pci = bm->pci;
+ struct device *dev = pci->dev;
+ u16 exp_cap_off;
+ u16 ext_cap_err_off;
+ u16 dev_sta;
+ u32 cor_err;
+ u32 root_err;
+ u32 uncor_err;
+
+ exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+ ext_cap_err_off = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_ERR);
+
+ uncor_err = dw_pcie_readl_dbi(pci,
+ ext_cap_err_off + PCI_ERR_UNCOR_STATUS);
+ cor_err = dw_pcie_readl_dbi(pci,
+ ext_cap_err_off + PCI_ERR_COR_STATUS);
+ root_err = dw_pcie_readl_dbi(pci,
+ ext_cap_err_off + PCI_ERR_ROOT_STATUS);
+ dev_sta = dw_pcie_readw_dbi(pci,
+ exp_cap_off + PCI_EXP_DEVSTA);
+
+ dw_pcie_writel_dbi(pci,
+ ext_cap_err_off + PCI_ERR_UNCOR_STATUS, uncor_err);
+ dw_pcie_writel_dbi(pci,
+ ext_cap_err_off + PCI_ERR_COR_STATUS, cor_err);
+ dw_pcie_writel_dbi(pci,
+ ext_cap_err_off + PCI_ERR_ROOT_STATUS, root_err);
+ dw_pcie_writew_dbi(pci,
+ exp_cap_off + PCI_EXP_DEVSTA, dev_sta);
+
+ dev_err(dev,
+ "DevSta:0x%04x RootErr:0x%x UncorErr:0x%x CorErr:0x%x\n",
+ dev_sta, root_err, uncor_err, cor_err);
+
+ return IRQ_HANDLED;
+}
+
+static const struct dw_pcie_host_ops bm1000_pcie_host_ops = {
+ .host_init = bm1000_pcie_host_init,
+};
+
+static int bm1000_add_pcie_port(struct platform_device *pdev)
+{
+ struct bm1000_pcie *bm = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ struct dw_pcie *pci = bm->pci;
+ struct dw_pcie_rp *pp = &pci->pp;
+ int ret;
+
+ pp->irq = platform_get_irq_byname(pdev, "aer");
+ if (pp->irq < 0) {
+ dev_err(dev, "failed to get \"aer\" IRQ\n");
+ return pp->irq;
+ }
+
+ ret = devm_request_irq(dev, pp->irq, bm1000_pcie_aer_irq_handler,
+ IRQF_SHARED, "bm1000-pcie-aer", bm);
+ if (ret) {
+ dev_err(dev, "failed to request IRQ %d\n", pp->irq);
+ return ret;
+ }
+
+ pp->num_vectors = MAX_MSI_IRQS;
+ pp->ops = &bm1000_pcie_host_ops;
+ ret = dw_pcie_host_init(pp);
+ if (ret) {
+ dev_err(dev, "failed to initialize host\n");
+ return ret;
+ }
+
+ bm1000_pcie_link_speed_fixup(bm, pp->bridge->bus);
+ return 0;
+}
+
+#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
+static u16 bm1000_pcie_get_segment_acpi(struct device *dev)
+{
+ struct acpi_pci_root *root = acpi_driver_data(to_acpi_device(dev));
+ return root->segment;
+}
+
+static void bm1000_pcie_prog_outbound_atu(struct dw_pcie *pci, int index,
+ int type, u64 cpu_addr, u64 pci_addr,
+ u32 size, u32 flags)
+{
+ u32 retries, val;
+
+ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT,
+ PCIE_ATU_REGION_DIR_OB | index);
+ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_LOWER_BASE,
+ lower_32_bits(cpu_addr));
+ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_UPPER_BASE,
+ upper_32_bits(cpu_addr));
+ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_LIMIT,
+ lower_32_bits(cpu_addr + size - 1));
+ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_LOWER_TARGET,
+ lower_32_bits(pci_addr));
+ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_UPPER_TARGET,
+ upper_32_bits(pci_addr));
+ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE, type);
+ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_REGION_CTRL2,
+ PCIE_ATU_ENABLE | flags);
+
+ /*
+ * Make sure ATU enable takes effect before any subsequent config
+ * and I/O accesses.
+ */
+ for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; ++retries) {
+ val = dw_pcie_readl_dbi(pci, PCIE_ATU_VIEWPORT_BASE +
+ PCIE_ATU_REGION_CTRL2);
+ if (val & PCIE_ATU_ENABLE)
+ return;
+
+ mdelay(LINK_WAIT_IATU);
+ }
+ dev_err(pci->dev, "Outbound iATU is not being enabled\n");
+}
+
+#define PCIE_IATU_REGION_CTRL_2_REG_SHIFT_MODE BIT(28)
+
+static void bm1000_pcie_setup_rc_acpi(struct dw_pcie_rp *pp,
+ const struct baikal_pcie_acpi_data *mem_data)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ acpi_status status;
+ u64 lanes;
+ u32 val;
+ int i;
+
+ /*
+ * Enable DBI read-only registers for writing/updating configuration.
+ * Write permission gets disabled towards the end of this function.
+ */
+ dw_pcie_dbi_ro_wr_en(pci);
+
+ val = dw_pcie_readl_dbi(pci, PCIE_PORT_LINK_CONTROL);
+ val &= ~PORT_LINK_FAST_LINK_MODE;
+ val |= PORT_LINK_DLL_LINK_EN;
+ dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val);
+
+ status = acpi_evaluate_integer(to_acpi_device(pci->dev)->handle,
+ "NUML", NULL, &lanes);
+ if (ACPI_FAILURE(status)) {
+ dev_dbg(pci->dev, "failed to get num-lanes\n");
+ } else {
+ pci->num_lanes = lanes;
+
+ /* Set the number of lanes */
+ val &= ~PORT_LINK_FAST_LINK_MODE;
+ val &= ~PORT_LINK_MODE_MASK;
+ switch (pci->num_lanes) {
+ case 1:
+ val |= PORT_LINK_MODE_1_LANES;
+ break;
+ case 2:
+ val |= PORT_LINK_MODE_2_LANES;
+ break;
+ case 4:
+ val |= PORT_LINK_MODE_4_LANES;
+ break;
+ case 8:
+ val |= PORT_LINK_MODE_8_LANES;
+ break;
+ default:
+ dev_err(pci->dev, "NUML %u: invalid value\n", pci->num_lanes);
+ goto skip_lanes;
+ }
+ dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val);
+
+ /* Set link width speed control register */
+ val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
+ val &= ~PORT_LOGIC_LINK_WIDTH_MASK;
+ switch (pci->num_lanes) {
+ case 1:
+ val |= PORT_LOGIC_LINK_WIDTH_1_LANES;
+ break;
+ case 2:
+ val |= PORT_LOGIC_LINK_WIDTH_2_LANES;
+ break;
+ case 4:
+ val |= PORT_LOGIC_LINK_WIDTH_4_LANES;
+ break;
+ case 8:
+ val |= PORT_LOGIC_LINK_WIDTH_8_LANES;
+ break;
+ }
+ dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
+ }
+
+skip_lanes:
+ /* Setup RC BARs */
+ dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0x00000004);
+ dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_1, 0x00000000);
+
+ /* Setup interrupt pins */
+ val = dw_pcie_readl_dbi(pci, PCI_INTERRUPT_LINE);
+ val &= 0xffff00ff;
+ val |= 0x00000100;
+ dw_pcie_writel_dbi(pci, PCI_INTERRUPT_LINE, val);
+
+ /* Setup bus numbers */
+ val = dw_pcie_readl_dbi(pci, PCI_PRIMARY_BUS);
+ val &= 0xff000000;
+ val |= 0x00ff0100;
+ dw_pcie_writel_dbi(pci, PCI_PRIMARY_BUS, val);
+
+ /* Setup command register */
+ val = dw_pcie_readl_dbi(pci, PCI_COMMAND);
+ val &= 0xffff0000;
+ val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
+ PCI_COMMAND_MASTER | PCI_COMMAND_SERR;
+ dw_pcie_writel_dbi(pci, PCI_COMMAND, val);
+
+ pci->atu_base = pci->dbi_base + PCIE_ATU_VIEWPORT_BASE;
+ pci->atu_size = PCIE_ATU_VIEWPORT_SIZE;
+ pci->num_ob_windows = 4;
+ pci->num_ib_windows = 0;
+
+ for (i = 0; i < pci->num_ob_windows; ++i)
+ dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, i);
+
+ /* Program ATU */
+ bm1000_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_CFG0,
+ pp->cfg0_base, 0,
+ pp->cfg0_size,
+ PCIE_IATU_REGION_CTRL_2_REG_SHIFT_MODE);
+ bm1000_pcie_prog_outbound_atu(pci, 1, PCIE_ATU_TYPE_CFG1,
+ pp->cfg0_base, 0,
+ pp->cfg0_size,
+ PCIE_IATU_REGION_CTRL_2_REG_SHIFT_MODE);
+ bm1000_pcie_prog_outbound_atu(pci, 2, PCIE_ATU_TYPE_MEM,
+ mem_data->mem_base, mem_data->mem_bus_addr,
+ mem_data->mem_size, 0);
+ bm1000_pcie_prog_outbound_atu(pci, 3, PCIE_ATU_TYPE_IO,
+ pp->io_base, pp->io_bus_addr,
+ pp->io_size, 0);
+
+ dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0);
+
+ /* Program correct class for RC */
+ dw_pcie_writew_dbi(pci, PCI_CLASS_DEVICE, PCI_CLASS_BRIDGE_PCI);
+
+ val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
+ val |= PORT_LOGIC_SPEED_CHANGE;
+ dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
+
+ dw_pcie_dbi_ro_wr_dis(pci);
+}
+
+struct bm1000_pcie_acpi_gpio {
+ struct list_head node;
+ struct acpi_resource_gpio *ares;
+};
+
+static int bm1000_pcie_acpi_dev_filter_gpio(struct acpi_resource *ares, void *data)
+{
+ struct list_head *list = data;
+ struct bm1000_pcie_acpi_gpio *gpio;
+
+ if (ares->type == ACPI_RESOURCE_TYPE_GPIO) {
+ gpio = kzalloc(sizeof(*gpio), GFP_KERNEL);
+ if (gpio) {
+ INIT_LIST_HEAD(&gpio->node);
+ gpio->ares = &ares->data.gpio;
+ list_add_tail(&gpio->node, list);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int bm1000_pcie_get_res_acpi(struct acpi_device *adev,
+ struct acpi_device **res_dev,
+ struct bm1000_pcie *bm,
+ struct baikal_pcie_acpi_data *mem_data)
+{
+ struct device *dev = &adev->dev;
+ struct dw_pcie_rp *pp = &bm->pci->pp;
+ struct resource_entry *entry;
+ struct list_head list, *pos;
+ struct fwnode_handle *fwnode;
+ struct bm1000_pcie_acpi_gpio *gpio, *tmp;
+ int ret;
+ unsigned long flags = IORESOURCE_MEM;
+
+ fwnode = fwnode_get_named_child_node(&adev->fwnode, "RES0");
+ if (!fwnode) {
+ dev_err(dev, "failed to get RES0 subdevice\n");
+ return -EINVAL;
+ }
+
+ *res_dev = to_acpi_device_node(fwnode);
+ if (!*res_dev) {
+ dev_err(dev, "RES0 is not an acpi device node\n");
+ return -EINVAL;
+ }
+
+ INIT_LIST_HEAD(&list);
+ ret = acpi_dev_get_resources(*res_dev, &list,
+ acpi_dev_filter_resource_type_cb,
+ (void *)flags);
+ if (ret < 0) {
+ dev_err(dev, "failed to parse RES0._CRS method, error code %d\n", ret);
+ return ret;
+ }
+
+ if (ret != 2) {
+ dev_err(dev, "invalid number of MEM resources present in RES0._CRS (%i, need 2)\n", ret);
+ return -EINVAL;
+ }
+
+ /* ECAM */
+ pos = list.next;
+ entry = list_entry(pos, struct resource_entry, node);
+ pp->cfg0_size = resource_size(entry->res);
+ pp->cfg0_base = entry->res->start;
+
+ /* DBI */
+ pos = pos->next;
+ entry = list_entry(pos, struct resource_entry, node);
+ if (entry->res->start == BM1000_PCIE0_DBI_BASE) {
+ bm->num = 0;
+ } else if (entry->res->start == BM1000_PCIE1_DBI_BASE) {
+ bm->num = 1;
+ } else if (entry->res->start == BM1000_PCIE2_DBI_BASE) {
+ bm->num = 2;
+ } else {
+ dev_err(dev, "incorrect \"dbi\" base\n");
+ return -EINVAL;
+ }
+ bm->pci->dbi_base = devm_ioremap_resource(dev, entry->res);
+ if (IS_ERR(bm->pci->dbi_base)) {
+ dev_err(dev, "error with dbi ioremap\n");
+ ret = PTR_ERR(bm->pci->dbi_base);
+ return ret;
+ }
+
+ acpi_dev_free_resource_list(&list);
+
+ /* Non-prefetchable memory */
+ INIT_LIST_HEAD(&list);
+ flags = IORESOURCE_MEM;
+ ret = acpi_dev_get_resources(adev, &list,
+ acpi_dev_filter_resource_type_cb,
+ (void *)flags);
+ if (ret < 0) {
+ dev_err(dev, "failed to parse _CRS method, error code %d\n", ret);
+ return ret;
+ }
+
+ if (ret != 1) {
+ dev_err(dev, "invalid number of MEM resources present in _CRS (%i, need 1)\n", ret);
+ return -EINVAL;
+ }
+
+ pos = list.next;
+ entry = list_entry(pos, struct resource_entry, node);
+ mem_data->mem_base = entry->res->start;
+ mem_data->mem_size = resource_size(entry->res);
+ mem_data->mem_bus_addr = entry->res->start - entry->offset;
+
+ acpi_dev_free_resource_list(&list);
+
+ /* I/O */
+ INIT_LIST_HEAD(&list);
+ flags = IORESOURCE_IO;
+ ret = acpi_dev_get_resources(adev, &list,
+ acpi_dev_filter_resource_type_cb,
+ (void *)flags);
+ if (ret < 0) {
+ dev_err(dev, "failed to parse _CRS method, error code %d\n", ret);
+ return ret;
+ }
+
+ if (ret != 1) {
+ dev_err(dev, "invalid number of IO resources present in _CRS (%i, need 1)\n", ret);
+ return -EINVAL;
+ }
+
+ pos = list.next;
+ entry = list_entry(pos, struct resource_entry, node);
+ pp->io_base = entry->res->start;
+ pp->io_size = resource_size(entry->res);
+ pp->io_bus_addr = entry->res->start - entry->offset;
+
+ acpi_dev_free_resource_list(&list);
+
+ /* GPIO */
+ INIT_LIST_HEAD(&list);
+ ret = acpi_dev_get_resources(*res_dev, &list,
+ bm1000_pcie_acpi_dev_filter_gpio,
+ &list);
+ if (ret < 0) {
+ dev_err(dev, "failed to parse RES0._CRS method, error code %d\n", ret);
+ return ret;
+ }
+
+ if (!ret) {
+ u8 count = 0;
+ u8 i;
+
+ list_for_each_entry(gpio, &list, node) {
+ for (i = 0; i < gpio->ares->pin_table_length &&
+ count < ARRAY_SIZE(bm->gpio); ++i) {
+ bm->gpio[count].num = gpio->ares->pin_table[i] & 0x1f;
+ bm->gpio[count].polarity = 1;
+ bm->gpio[count].is_set = 1;
+ ++count;
+ }
+
+ if (count == ARRAY_SIZE(bm->gpio))
+ break;
+ }
+ }
+
+ list_for_each_entry_safe(gpio, tmp, &list, node) {
+ list_del(&gpio->node);
+ kfree(gpio);
+ }
+
+ return 0;
+}
+
+static int bm1000_pcie_get_irq_acpi(struct device *dev,
+ struct acpi_device *res_dev,
+ struct bm1000_pcie *bm)
+{
+ struct dw_pcie_rp *pp = &bm->pci->pp;
+ struct resource res;
+ int ret;
+
+ memset(&res, 0, sizeof(res));
+
+ ret = acpi_irq_get(res_dev->handle, 0, &res);
+ if (ret) {
+ dev_err(dev, "failed to get irq %d\n", 0);
+ return ret;
+ }
+
+ if (res.flags & IORESOURCE_BITS) {
+ struct irq_data *irqd;
+
+ irqd = irq_get_irq_data(res.start);
+ if (!irqd)
+ return -ENXIO;
+
+ irqd_set_trigger_type(irqd, res.flags & IORESOURCE_BITS);
+ }
+
+ pp->irq = res.start;
+
+ ret = devm_request_irq(dev, pp->irq, bm1000_pcie_aer_irq_handler,
+ IRQF_SHARED, "bm1000-pcie-aer", bm);
+ if (ret) {
+ dev_err(dev, "failed to request irq %d\n", pp->irq);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct regmap *bm1000_regmap;
+
+static const struct regmap_config bm1000_pcie_syscon_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4
+};
+
+static struct regmap *bm1000_pcie_get_gpr_acpi(struct bm1000_pcie *bm)
+{
+ struct device *dev;
+ struct acpi_device *adev;
+ acpi_handle handle;
+ acpi_status status = AE_OK;
+ struct list_head list, *pos;
+ struct resource *res;
+ void __iomem *base;
+ struct regmap *regmap = NULL;
+ struct regmap_config config = bm1000_pcie_syscon_regmap_config;
+ unsigned long flags = IORESOURCE_MEM;
+ int ret;
+
+ if (bm1000_regmap)
+ return bm1000_regmap;
+
+ status = acpi_get_handle(NULL, "\\_SB.PGPR", &handle);
+ if (ACPI_FAILURE(status)) {
+ dev_err(dev, "failed to get PCIe GPR device\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ adev = acpi_fetch_acpi_dev(handle);
+ if (!adev) {
+ dev_err(dev, "failed to process PCIe GPR handle\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ dev = &adev->dev;
+ INIT_LIST_HEAD(&list);
+ ret = acpi_dev_get_resources(adev, &list,
+ acpi_dev_filter_resource_type_cb,
+ (void *)flags);
+ if (ret < 0) {
+ dev_err(dev, "failed to parse _CRS method, error code %d\n", ret);
+ return NULL;
+ }
+
+ if (ret != 1) {
+ dev_err(dev, "invalid number of MEM resources present in _CRS (%i, need 1)\n", ret);
+ goto ret;
+ }
+
+ pos = list.next;
+ res = list_entry(pos, struct resource_entry, node)->res;
+
+ base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!base) {
+ dev_err(dev, "error with ioremap\n");
+ goto ret;
+ }
+
+ config.max_register = resource_size(res) - 4;
+
+ regmap = devm_regmap_init_mmio(dev, base, &config);
+ if (IS_ERR(regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ devm_iounmap(dev, base);
+ goto ret;
+ }
+
+ dev_dbg(dev, "regmap %pR registered\n", res);
+
+ bm1000_regmap = regmap;
+
+ret:
+ acpi_dev_free_resource_list(&list);
+ return regmap;
+}
+
+static int bm1000_get_acpi_data(struct device *dev, struct bm1000_pcie *bm,
+ struct baikal_pcie_acpi_data *mem_data)
+{
+ struct acpi_device *adev = to_acpi_device(dev), *res_dev;
+ int ret;
+
+ bm->gpr = bm1000_pcie_get_gpr_acpi(bm);
+ if (IS_ERR_OR_NULL(bm->gpr)) {
+ dev_err(dev, "No PCIe GPR specified\n");
+ return -EINVAL;
+ }
+
+ ret = bm1000_pcie_get_res_acpi(adev, &res_dev, bm, mem_data);
+ if (ret) {
+ dev_err(dev, "failed to get resource info\n");
+ return ret;
+ }
+
+ ret = bm1000_pcie_get_irq_acpi(dev, res_dev, bm);
+ if (ret) {
+ dev_err(dev, "failed to get irq info\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bm1000_pcie_init(struct pci_config_window *cfg)
+{
+ struct device *dev = cfg->parent;
+ struct bm1000_pcie *bm;
+ struct dw_pcie *pci;
+ struct dw_pcie_rp *pp;
+ struct baikal_pcie_acpi_data mem_data = {};
+ int ret;
+
+ pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
+ if (!pci)
+ return -ENOMEM;
+
+ pci->dev = dev;
+ pci->ops = &bm1000_pcie_rc_of_data.dw_pcie_ops;
+
+ bm = devm_kzalloc(dev, sizeof(*bm), GFP_KERNEL);
+ if (!bm)
+ return -ENOMEM;
+
+ cfg->priv = bm;
+ bm->pci = pci;
+ dev_set_drvdata(dev, bm);
+
+ ret = bm1000_get_acpi_data(dev, bm, &mem_data);
+ if (ret) {
+ dev_err(dev, "failed to get data from ACPI\n");
+ return ret;
+ }
+
+ pp = &pci->pp;
+ raw_spin_lock_init(&pp->lock);
+ pp->ops = &bm1000_pcie_host_ops;
+ pp->va_cfg0_base = devm_pci_remap_cfgspace(dev, pp->cfg0_base,
+ pp->cfg0_size);
+ if (!pp->va_cfg0_base) {
+ dev_err(dev, "error with ioremap\n");
+ return -ENOMEM;
+ }
+
+ ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64));
+ if (ret) {
+ dev_err(dev, "failed to enable DMA\n");
+ return ret;
+ }
+
+ ret = bm1000_pcie_host_init(pp);
+ if (ret) {
+ dev_err(dev, "Failed to initialize host\n");
+ return ret;
+ }
+
+ dw_pcie_version_detect(pci);
+ bm1000_pcie_setup_rc_acpi(pp, &mem_data);
+
+ if (!bm1000_pcie_link_up(bm->pci))
+ bm1000_pcie_start_link(bm->pci);
+
+ /* Link will be retrained by 'bm1000_pcie_map_bus()' */
+ bm->retrained = false;
+ return 0;
+}
+
+static void __iomem *bm1000_pcie_map_bus(struct pci_bus *bus,
+ unsigned int devfn, int where)
+{
+ struct pci_config_window *cfg = bus->sysdata;
+ struct bm1000_pcie *bm = cfg->priv;
+ unsigned int devfn_shift = cfg->ops->bus_shift - 8;
+ unsigned int busn = bus->number;
+ void __iomem *base;
+
+ if (!bm->retrained) {
+ struct pci_host_bridge **root_bridge = &bm->pci->pp.bridge;
+
+ if (*root_bridge == NULL) {
+ *root_bridge = to_pci_host_bridge(bus->bridge);
+ } else if ((*root_bridge)->bus->is_added) {
+ struct acpi_device *adev = to_acpi_device(cfg->parent);
+ struct acpi_pci_root *root = adev->driver_data;
+
+ if (root->bus == (*root_bridge)->bus) {
+ pci_bus_add_devices(root->bus);
+ bm1000_pcie_link_speed_fixup(bm, root->bus);
+ bm->retrained = true;
+ }
+ }
+ }
+
+ if (bus->number != cfg->busr.start && !bm1000_pcie_link_up(bm->pci))
+ return NULL;
+
+ if (bus->number == cfg->busr.start) {
+ /*
+ * The DW PCIe core doesn't filter out transactions to other
+ * devices/functions on the root bus num, so we do this here.
+ */
+ if (PCI_SLOT(devfn) > 0)
+ return NULL;
+ else
+ return bm->pci->dbi_base + where;
+ }
+
+ if (busn < cfg->busr.start || busn > cfg->busr.end)
+ return NULL;
+
+ busn -= cfg->busr.start;
+ base = cfg->win + (busn << cfg->ops->bus_shift);
+ return base + (devfn << devfn_shift) + where;
+}
+
+const struct pci_ecam_ops baikal_m_pcie_ecam_ops = {
+ .bus_shift = 20,
+ .init = bm1000_pcie_init,
+ .pci_ops = {
+ .map_bus = bm1000_pcie_map_bus,
+ .read = pci_generic_config_read,
+ .write = pci_generic_config_write
+ }
+};
+#endif
+
+#define BS1000_PCIE_APB_PE_GEN_CTRL3 0x58
+#define BS1000_PCIE_APB_PE_GEN_CTRL3_LTSSM_EN BIT(0)
+
+#define BS1000_PCIE_APB_PE_LINK_DBG2 0xb4
+#define BS1000_PCIE_APB_PE_LINK_DBG2_SMLH_LINK_UP BIT(6)
+#define BS1000_PCIE_APB_PE_LINK_DBG2_RDLH_LINK_UP BIT(7)
+
+#define BS1000_PCIE_APB_PE_ERR_STS 0xe0
+#define BS1000_PCIE_APB_PE_INT_STS 0xe8
+
+#define BS1000_PCIE0_P0_DBI_BASE 0x39000000
+#define BS1000_PCIE0_P1_DBI_BASE 0x39400000
+#define BS1000_PCIE1_P0_DBI_BASE 0x3d000000
+#define BS1000_PCIE1_P1_DBI_BASE 0x3d400000
+#define BS1000_PCIE2_P0_DBI_BASE 0x45000000
+#define BS1000_PCIE2_P1_DBI_BASE 0x45400000
+
+struct bs1000_pcie {
+ struct dw_pcie *pci;
+ void __iomem *apb_base;
+ u64 cpu_addr_mask;
+};
+
+static void bs1000_pcie_ep_init(struct dw_pcie_ep *ep)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
+ enum pci_barno bar;
+
+ for (bar = BAR_0; bar <= BAR_5; bar++)
+ dw_pcie_ep_reset_bar(pci, bar);
+}
+
+static int bs1000_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no,
+ enum pci_epc_irq_type type,
+ u16 interrupt_num)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
+
+ switch (type) {
+ case PCI_EPC_IRQ_LEGACY:
+ return dw_pcie_ep_raise_legacy_irq(ep, func_no);
+ case PCI_EPC_IRQ_MSI:
+ return dw_pcie_ep_raise_msi_irq(ep, func_no, interrupt_num);
+ default:
+ dev_err(pci->dev, "UNKNOWN IRQ type\n");
+ return -EINVAL;
+ }
+}
+
+static const struct pci_epc_features bs1000_pcie_epc_features = {
+ .linkup_notifier = false,
+ .msi_capable = true,
+ .msix_capable = false,
+ .reserved_bar = BIT(BAR_3) | BIT(BAR_5),
+};
+
+static const struct pci_epc_features*
+bs1000_pcie_ep_get_features(struct dw_pcie_ep *ep)
+{
+ return &bs1000_pcie_epc_features;
+}
+
+static const struct dw_pcie_ep_ops bs1000_pcie_ep_ops = {
+ .ep_init = bs1000_pcie_ep_init,
+ .raise_irq = bs1000_pcie_ep_raise_irq,
+ .get_features = bs1000_pcie_ep_get_features,
+};
+
+static int bs1000_get_resources(struct platform_device *pdev,
+ struct dw_pcie *pci,
+ const struct baikal_pcie_of_data *data)
+{
+ struct bs1000_pcie *bs = platform_get_drvdata(pdev);
+ struct device *dev = pci->dev;
+ struct resource *res;
+
+ bs->pci = pci;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
+ if (!res) {
+ dev_err(dev, "failed to find \"dbi\" region\n");
+ return -EINVAL;
+ }
+
+ pci->dbi_base = devm_pci_remap_cfg_resource(dev, res);
+ if (IS_ERR(pci->dbi_base))
+ return PTR_ERR(pci->dbi_base);
+
+ if (res->start == BS1000_PCIE0_P0_DBI_BASE ||
+ res->start == BS1000_PCIE0_P1_DBI_BASE ||
+ res->start == BS1000_PCIE1_P0_DBI_BASE ||
+ res->start == BS1000_PCIE1_P1_DBI_BASE ||
+ res->start == BS1000_PCIE2_P0_DBI_BASE ||
+ res->start == BS1000_PCIE2_P1_DBI_BASE) {
+ bs->cpu_addr_mask = 0x7fffffffff;
+ } else {
+ bs->cpu_addr_mask = 0xffffffffff;
+ }
+
+ bs->apb_base = devm_platform_ioremap_resource_byname(pdev, "apb");
+ if (IS_ERR(bs->apb_base))
+ return PTR_ERR(bs->apb_base);
+
+ if (data->mode == DW_PCIE_EP_TYPE) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "addr_space");
+ if (!res)
+ return -EINVAL;
+
+ pci->ep.phys_base = res->start;
+ pci->ep.addr_size = resource_size(res);
+ pci->ep.ops = &bs1000_pcie_ep_ops;
+ pci->dbi_base2 = pci->dbi_base + 0x100000;
+ }
+
+ return 0;
+}
+
+static u64 bs1000_pcie_cpu_addr_fixup(struct dw_pcie *pci, u64 cpu_addr)
+{
+ struct bs1000_pcie *bs = dev_get_drvdata(pci->dev);
+
+ return cpu_addr & bs->cpu_addr_mask;
+}
+
+static int bs1000_pcie_link_up(struct dw_pcie *pci)
+{
+ struct bs1000_pcie *bs = dev_get_drvdata(pci->dev);
+ u32 reg;
+
+ reg = readl(bs->apb_base + BS1000_PCIE_APB_PE_LINK_DBG2);
+ return ((reg & BAIKAL_PCIE_LTSSM_MASK) == BAIKAL_PCIE_LTSSM_STATE_L0) &&
+ (reg & BS1000_PCIE_APB_PE_LINK_DBG2_SMLH_LINK_UP) &&
+ (reg & BS1000_PCIE_APB_PE_LINK_DBG2_RDLH_LINK_UP);
+}
+
+static int bs1000_pcie_start_link(struct dw_pcie *pci)
+{
+ struct bs1000_pcie *bs = dev_get_drvdata(pci->dev);
+ u32 reg;
+
+ reg = readl(bs->apb_base + BS1000_PCIE_APB_PE_GEN_CTRL3);
+ reg |= BS1000_PCIE_APB_PE_GEN_CTRL3_LTSSM_EN;
+ writel(reg, bs->apb_base + BS1000_PCIE_APB_PE_GEN_CTRL3);
+ return 0;
+}
+
+static int bs1000_pcie_host_init(struct dw_pcie_rp *pp)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ u16 exp_cap_off;
+ u16 ext_cap_err_off;
+ u32 reg;
+
+ /* Enable error reporting */
+ ext_cap_err_off = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_ERR);
+ reg = dw_pcie_readl_dbi(pci, ext_cap_err_off + PCI_ERR_ROOT_COMMAND);
+ reg |= PCI_ERR_ROOT_CMD_COR_EN |
+ PCI_ERR_ROOT_CMD_NONFATAL_EN |
+ PCI_ERR_ROOT_CMD_FATAL_EN;
+ dw_pcie_writel_dbi(pci, ext_cap_err_off + PCI_ERR_ROOT_COMMAND, reg);
+
+ exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+ reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_DEVCTL);
+ reg |= PCI_EXP_DEVCTL_CERE |
+ PCI_EXP_DEVCTL_NFERE |
+ PCI_EXP_DEVCTL_FERE |
+ PCI_EXP_DEVCTL_URRE;
+ dw_pcie_writew_dbi(pci, exp_cap_off + PCI_EXP_DEVCTL, reg);
+
+ reg = dw_pcie_readl_dbi(pci, exp_cap_off + PCI_EXP_RTCTL);
+ reg |= PCI_EXP_RTCTL_SECEE |
+ PCI_EXP_RTCTL_SENFEE |
+ PCI_EXP_RTCTL_SEFEE |
+ PCI_EXP_RTCTL_PMEIE;
+ dw_pcie_writel_dbi(pci, exp_cap_off + PCI_EXP_RTCTL, reg);
+
+ return 0;
+}
+
+static irqreturn_t bs1000_pcie_intr_irq_handler(int irq, void *arg)
+{
+ struct bs1000_pcie *bs = arg;
+ struct dw_pcie *pci = bs->pci;
+ struct device *dev = pci->dev;
+ u16 exp_cap_off;
+ u16 ext_cap_err_off;
+ u16 dev_sta;
+ u32 cor_err;
+ u32 root_err;
+ u32 uncor_err;
+ u32 apb_pe_err;
+ u32 apb_pe_int;
+
+ apb_pe_err = readl(bs->apb_base + BS1000_PCIE_APB_PE_ERR_STS);
+ apb_pe_int = readl(bs->apb_base + BS1000_PCIE_APB_PE_INT_STS);
+
+ exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+ ext_cap_err_off = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_ERR);
+
+ uncor_err = dw_pcie_readl_dbi(pci,
+ ext_cap_err_off + PCI_ERR_UNCOR_STATUS);
+ cor_err = dw_pcie_readl_dbi(pci,
+ ext_cap_err_off + PCI_ERR_COR_STATUS);
+ root_err = dw_pcie_readl_dbi(pci,
+ ext_cap_err_off + PCI_ERR_ROOT_STATUS);
+ dev_sta = dw_pcie_readw_dbi(pci,
+ exp_cap_off + PCI_EXP_DEVSTA);
+
+ writel(apb_pe_err, bs->apb_base + BS1000_PCIE_APB_PE_ERR_STS);
+ writel(apb_pe_int, bs->apb_base + BS1000_PCIE_APB_PE_INT_STS);
+
+ dw_pcie_writel_dbi(pci,
+ ext_cap_err_off + PCI_ERR_UNCOR_STATUS, uncor_err);
+ dw_pcie_writel_dbi(pci,
+ ext_cap_err_off + PCI_ERR_COR_STATUS, cor_err);
+ dw_pcie_writel_dbi(pci,
+ ext_cap_err_off + PCI_ERR_ROOT_STATUS, root_err);
+ dw_pcie_writew_dbi(pci,
+ exp_cap_off + PCI_EXP_DEVSTA, dev_sta);
+
+ dev_err(dev,
+ "DevSta:0x%04x RootErr:0x%x UncorErr:0x%x CorErr:0x%x ApbErr:0x%x ApbInt:0x%x\n",
+ dev_sta, root_err, uncor_err, cor_err, apb_pe_err, apb_pe_int);
+
+ return IRQ_HANDLED;
+}
+
+static const struct dw_pcie_host_ops bs1000_pcie_host_ops = {
+ .host_init = bs1000_pcie_host_init,
+};
+
+static int bs1000_add_pcie_port(struct platform_device *pdev)
+{
+ struct bs1000_pcie *bs = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ struct dw_pcie *pci = bs->pci;
+ struct dw_pcie_rp *pp = &pci->pp;
+ int ret;
+
+ pp->irq = platform_get_irq_byname(pdev, "intr");
+ if (pp->irq < 0) {
+ dev_err(dev, "failed to get \"intr\" IRQ\n");
+ return pp->irq;
+ }
+
+ ret = devm_request_irq(dev, pp->irq, bs1000_pcie_intr_irq_handler,
+ IRQF_SHARED, "bs1000-pcie-intr", bs);
+ if (ret) {
+ dev_err(dev, "failed to request IRQ %d\n", pp->irq);
+ return ret;
+ }
+
+ pp->num_vectors = MAX_MSI_IRQS;
+ pp->ops = &bs1000_pcie_host_ops;
+ ret = dw_pcie_host_init(pp);
+ if (ret) {
+ dev_err(dev, "failed to initialize host\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
+static void dw_pcie_writel_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg,
+ u32 val)
+{
+ dw_pcie_write(pci->atu_base +
+ PCIE_ATU_UNROLL_BASE(PCIE_ATU_REGION_DIR_OB, index) +
+ reg, 0x4, val);
+}
+
+static void bs1000_pcie_prog_outbound_atu(struct dw_pcie *pci, int index,
+ int type, u64 cpu_addr, u64 pci_addr,
+ u32 size, u32 flags)
+{
+ u32 retries, val;
+
+ cpu_addr = bs1000_pcie_cpu_addr_fixup(pci, cpu_addr);
+
+ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE,
+ lower_32_bits(cpu_addr));
+ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE,
+ upper_32_bits(cpu_addr));
+ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_LIMIT,
+ lower_32_bits(cpu_addr + size - 1));
+ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
+ lower_32_bits(pci_addr));
+ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
+ upper_32_bits(pci_addr));
+ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, type);
+ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
+ PCIE_ATU_ENABLE | flags);
+
+ /*
+ * Make sure ATU enable takes effect before any subsequent config
+ * and I/O accesses.
+ */
+ for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; ++retries) {
+ dw_pcie_read(pci->atu_base +
+ PCIE_ATU_UNROLL_BASE(index, PCIE_ATU_REGION_DIR_OB) +
+ PCIE_ATU_UNR_REGION_CTRL2, 0x4, &val);
+ if (val & PCIE_ATU_ENABLE)
+ return;
+
+ mdelay(LINK_WAIT_IATU);
+ }
+ dev_err(pci->dev, "Outbound iATU is not being enabled\n");
+}
+
+static void bs1000_pcie_setup_rc_acpi(struct dw_pcie_rp *pp,
+ const struct baikal_pcie_acpi_data *mem_data)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ acpi_status status;
+ u64 lanes;
+ u32 val;
+ int i;
+
+ /*
+ * Enable DBI read-only registers for writing/updating configuration.
+ * Write permission gets disabled towards the end of this function.
+ */
+ dw_pcie_dbi_ro_wr_en(pci);
+
+ val = dw_pcie_readl_dbi(pci, PCIE_PORT_LINK_CONTROL);
+ val &= ~PORT_LINK_FAST_LINK_MODE;
+ val |= PORT_LINK_DLL_LINK_EN;
+ dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val);
+
+ status = acpi_evaluate_integer(to_acpi_device(pci->dev)->handle,
+ "NUML", NULL, &lanes);
+ if (ACPI_FAILURE(status)) {
+ dev_dbg(pci->dev, "failed to get num-lanes\n");
+ } else {
+ pci->num_lanes = lanes;
+
+ /* Set the number of lanes */
+ val &= ~PORT_LINK_FAST_LINK_MODE;
+ val &= ~PORT_LINK_MODE_MASK;
+ switch (pci->num_lanes) {
+ case 1:
+ val |= PORT_LINK_MODE_1_LANES;
+ break;
+ case 2:
+ val |= PORT_LINK_MODE_2_LANES;
+ break;
+ case 4:
+ val |= PORT_LINK_MODE_4_LANES;
+ break;
+ case 8:
+ val |= PORT_LINK_MODE_8_LANES;
+ break;
+ default:
+ dev_err(pci->dev, "NUML %u: invalid value\n", pci->num_lanes);
+ goto skip_lanes;
+ }
+ dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val);
+
+ /* Set link width speed control register */
+ val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
+ val &= ~PORT_LOGIC_LINK_WIDTH_MASK;
+ switch (pci->num_lanes) {
+ case 1:
+ val |= PORT_LOGIC_LINK_WIDTH_1_LANES;
+ break;
+ case 2:
+ val |= PORT_LOGIC_LINK_WIDTH_2_LANES;
+ break;
+ case 4:
+ val |= PORT_LOGIC_LINK_WIDTH_4_LANES;
+ break;
+ case 8:
+ val |= PORT_LOGIC_LINK_WIDTH_8_LANES;
+ break;
+ }
+ dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
+ }
+
+skip_lanes:
+ /* Setup RC BARs */
+ dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0x00000004);
+ dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_1, 0x00000000);
+
+ /* Setup interrupt pins */
+ val = dw_pcie_readl_dbi(pci, PCI_INTERRUPT_LINE);
+ val &= 0xffff00ff;
+ val |= 0x00000100;
+ dw_pcie_writel_dbi(pci, PCI_INTERRUPT_LINE, val);
+
+ /* Setup bus numbers */
+ val = dw_pcie_readl_dbi(pci, PCI_PRIMARY_BUS);
+ val &= 0xff000000;
+ val |= 0x00ff0100;
+ dw_pcie_writel_dbi(pci, PCI_PRIMARY_BUS, val);
+
+ /* Setup command register */
+ val = dw_pcie_readl_dbi(pci, PCI_COMMAND);
+ val &= 0xffff0000;
+ val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
+ PCI_COMMAND_MASTER | PCI_COMMAND_SERR;
+ dw_pcie_writel_dbi(pci, PCI_COMMAND, val);
+
+ pci->iatu_unroll_enabled = 1;
+ pci->atu_base = pci->dbi_base + DEFAULT_DBI_ATU_OFFSET;
+ pci->atu_size = SZ_4K;
+ pci->num_ob_windows = 4;
+ pci->num_ib_windows = 0;
+
+ for (i = 0; i < pci->num_ob_windows; ++i)
+ dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, i);
+
+ /* Program ATU */
+ bs1000_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_CFG0,
+ pp->cfg0_base, 0,
+ pp->cfg0_size,
+ PCIE_IATU_REGION_CTRL_2_REG_SHIFT_MODE);
+ bs1000_pcie_prog_outbound_atu(pci, 1, PCIE_ATU_TYPE_CFG1,
+ pp->cfg0_base, 0,
+ pp->cfg0_size,
+ PCIE_IATU_REGION_CTRL_2_REG_SHIFT_MODE);
+ bs1000_pcie_prog_outbound_atu(pci, 2, PCIE_ATU_TYPE_MEM,
+ mem_data->mem_base, mem_data->mem_bus_addr,
+ mem_data->mem_size, 0);
+ bs1000_pcie_prog_outbound_atu(pci, 3, PCIE_ATU_TYPE_IO,
+ pp->io_base, pp->io_bus_addr,
+ pp->io_size, 0);
+
+ dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0);
+
+ /* Program correct class for RC */
+ dw_pcie_writew_dbi(pci, PCI_CLASS_DEVICE, PCI_CLASS_BRIDGE_PCI);
+
+ val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
+ val |= PORT_LOGIC_SPEED_CHANGE;
+ dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
+
+ dw_pcie_dbi_ro_wr_dis(pci);
+}
+
+static int bs1000_pcie_get_res_acpi(struct acpi_device *adev,
+ struct acpi_device **res_dev,
+ struct bs1000_pcie *bs,
+ struct baikal_pcie_acpi_data *mem_data)
+{
+ struct device *dev = &adev->dev;
+ struct dw_pcie_rp *pp = &bs->pci->pp;
+ struct resource_entry *entry;
+ struct list_head list, *pos;
+ struct fwnode_handle *fwnode;
+ int ret;
+ unsigned long flags = IORESOURCE_MEM;
+
+ fwnode = fwnode_get_named_child_node(&adev->fwnode, "RES0");
+ if (!fwnode) {
+ dev_err(dev, "failed to get RES0 subdevice\n");
+ return -EINVAL;
+ }
+
+ *res_dev = to_acpi_device_node(fwnode);
+ if (!*res_dev) {
+ dev_err(dev, "RES0 is not an acpi device node\n");
+ return -EINVAL;
+ }
+
+ INIT_LIST_HEAD(&list);
+ ret = acpi_dev_get_resources(*res_dev, &list,
+ acpi_dev_filter_resource_type_cb,
+ (void *)flags);
+ if (ret < 0) {
+ dev_err(dev, "failed to parse RES0._CRS method, error code %d\n", ret);
+ return ret;
+ }
+
+ if (ret != 3) {
+ dev_err(dev, "invalid number of MEM resources present in RES0._CRS (%i, need 3)\n", ret);
+ return -EINVAL;
+ }
+
+ /* ECAM */
+ pos = list.next;
+ entry = list_entry(pos, struct resource_entry, node);
+ pp->cfg0_size = resource_size(entry->res);
+ pp->cfg0_base = entry->res->start;
+
+ /* DBI */
+ pos = pos->next;
+ entry = list_entry(pos, struct resource_entry, node);
+ if (entry->res->start == BS1000_PCIE0_P0_DBI_BASE ||
+ entry->res->start == BS1000_PCIE0_P1_DBI_BASE ||
+ entry->res->start == BS1000_PCIE1_P0_DBI_BASE ||
+ entry->res->start == BS1000_PCIE1_P1_DBI_BASE ||
+ entry->res->start == BS1000_PCIE2_P0_DBI_BASE ||
+ entry->res->start == BS1000_PCIE2_P1_DBI_BASE) {
+ bs->cpu_addr_mask = 0x7fffffffff;
+ } else {
+ bs->cpu_addr_mask = 0xffffffffff;
+ }
+ bs->pci->dbi_base = devm_ioremap_resource(dev, entry->res);
+ if (IS_ERR(bs->pci->dbi_base)) {
+ dev_err(dev, "error with dbi ioremap\n");
+ ret = PTR_ERR(bs->pci->dbi_base);
+ return ret;
+ }
+
+ /* APB */
+ pos = pos->next;
+ entry = list_entry(pos, struct resource_entry, node);
+ bs->apb_base = devm_ioremap_resource(dev, entry->res);
+ if (IS_ERR(bs->apb_base)) {
+ dev_err(dev, "error with apb ioremap\n");
+ ret = PTR_ERR(bs->apb_base);
+ return ret;
+ }
+
+ acpi_dev_free_resource_list(&list);
+
+ /* Non-prefetchable memory */
+ INIT_LIST_HEAD(&list);
+ flags = IORESOURCE_MEM;
+ ret = acpi_dev_get_resources(adev, &list,
+ acpi_dev_filter_resource_type_cb,
+ (void *)flags);
+ if (ret < 0) {
+ dev_err(dev, "failed to parse _CRS method, error code %d\n", ret);
+ return ret;
+ }
+
+ if (ret != 1) {
+ dev_err(dev, "invalid number of MEM resources present in _CRS (%i, need 1)\n", ret);
+ return -EINVAL;
+ }
+
+ pos = list.next;
+ entry = list_entry(pos, struct resource_entry, node);
+ mem_data->mem_base = entry->res->start;
+ mem_data->mem_size = resource_size(entry->res);
+ mem_data->mem_bus_addr = entry->res->start - entry->offset;
+
+ acpi_dev_free_resource_list(&list);
+
+ /* I/O */
+ INIT_LIST_HEAD(&list);
+ flags = IORESOURCE_IO;
+ ret = acpi_dev_get_resources(adev, &list,
+ acpi_dev_filter_resource_type_cb,
+ (void *)flags);
+ if (ret < 0) {
+ dev_err(dev, "failed to parse _CRS method, error code %d\n", ret);
+ return ret;
+ }
+
+ if (ret != 1) {
+ dev_err(dev, "invalid number of IO resources present in _CRS (%i, need 1)\n", ret);
+ return -EINVAL;
+ }
+
+ pos = list.next;
+ entry = list_entry(pos, struct resource_entry, node);
+ pp->io_base = entry->res->start;
+ pp->io_size = resource_size(entry->res);
+ pp->io_bus_addr = entry->res->start - entry->offset;
+
+ acpi_dev_free_resource_list(&list);
+ return 0;
+}
+
+static int bs1000_pcie_get_irq_acpi(struct device *dev,
+ struct acpi_device *res_dev,
+ struct bs1000_pcie *bs)
+{
+ struct dw_pcie_rp *pp = &bs->pci->pp;
+ struct resource res;
+ int ret;
+
+ memset(&res, 0, sizeof(res));
+
+ ret = acpi_irq_get(res_dev->handle, 0, &res);
+ if (ret) {
+ dev_err(dev, "failed to get irq %d\n", 0);
+ return ret;
+ }
+
+ if (res.flags & IORESOURCE_BITS) {
+ struct irq_data *irqd;
+
+ irqd = irq_get_irq_data(res.start);
+ if (!irqd) {
+ return -ENXIO;
+ }
+
+ irqd_set_trigger_type(irqd, res.flags & IORESOURCE_BITS);
+ }
+
+ pp->irq = res.start;
+
+ ret = devm_request_irq(dev, pp->irq, bs1000_pcie_intr_irq_handler,
+ IRQF_SHARED, "bs1000-pcie-intr", bs);
+ if (ret) {
+ dev_err(dev, "failed to request IRQ %d\n", pp->irq);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bs1000_get_acpi_data(struct device *dev, struct bs1000_pcie *bs,
+ struct baikal_pcie_acpi_data *mem_data)
+{
+ struct acpi_device *adev = to_acpi_device(dev), *res_dev;
+ int ret;
+
+ ret = bs1000_pcie_get_res_acpi(adev, &res_dev, bs, mem_data);
+ if (ret) {
+ dev_err(dev, "failed to get resource info\n");
+ return ret;
+ }
+
+ ret = bs1000_pcie_get_irq_acpi(dev, res_dev, bs);
+ if (ret) {
+ dev_err(dev, "failed to get irq info\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bs1000_pcie_init(struct pci_config_window *cfg)
+{
+ struct device *dev = cfg->parent;
+ struct bs1000_pcie *bs;
+ struct dw_pcie *pci;
+ struct dw_pcie_rp *pp;
+ struct baikal_pcie_acpi_data mem_data = {};
+ int ret;
+
+ pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
+ if (!pci)
+ return -ENOMEM;
+
+ pci->dev = dev;
+ pci->ops = &bs1000_pcie_rc_of_data.dw_pcie_ops;
+
+ bs = devm_kzalloc(dev, sizeof(*bs), GFP_KERNEL);
+ if (!bs)
+ return -ENOMEM;
+
+ cfg->priv = bs;
+ bs->pci = pci;
+ dev_set_drvdata(dev, bs);
+
+ ret = bs1000_get_acpi_data(dev, bs, &mem_data);
+ if (ret) {
+ dev_err(dev, "failed to get data from ACPI\n");
+ return ret;
+ }
+
+ pp = &pci->pp;
+ raw_spin_lock_init(&pp->lock);
+ pp->ops = &bs1000_pcie_host_ops;
+ pp->va_cfg0_base = devm_pci_remap_cfgspace(dev, pp->cfg0_base,
+ pp->cfg0_size);
+ if (!pp->va_cfg0_base) {
+ dev_err(dev, "error with ioremap\n");
+ return -ENOMEM;
+ }
+
+ ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64));
+ if (ret) {
+ dev_err(dev, "failed to enable DMA\n");
+ return ret;
+ }
+
+ ret = bs1000_pcie_host_init(pp);
+ if (ret) {
+ dev_err(dev, "failed to initialize host\n");
+ return ret;
+ }
+
+ dw_pcie_version_detect(pci);
+ bs1000_pcie_setup_rc_acpi(pp, &mem_data);
+
+ return 0;
+}
+
+static void __iomem *bs1000_pcie_map_bus(struct pci_bus *bus,
+ unsigned int devfn, int where)
+{
+ struct pci_config_window *cfg = bus->sysdata;
+ struct bs1000_pcie *bs = cfg->priv;
+ unsigned int devfn_shift = cfg->ops->bus_shift - 8;
+ unsigned int busn = bus->number;
+ void __iomem *base;
+
+ if (bs->pci->pp.bridge == NULL)
+ bs->pci->pp.bridge = to_pci_host_bridge(bus->bridge);
+
+ if (bus->number != cfg->busr.start && !bs1000_pcie_link_up(bs->pci))
+ return NULL;
+
+ if (bus->number == cfg->busr.start) {
+ /*
+ * The DW PCIe core doesn't filter out transactions to other
+ * devices/functions on the root bus num, so we do this here.
+ */
+ if (PCI_SLOT(devfn) > 0)
+ return NULL;
+ else
+ return bs->pci->dbi_base + where;
+ }
+
+ if (busn < cfg->busr.start || busn > cfg->busr.end)
+ return NULL;
+
+ busn -= cfg->busr.start;
+ base = cfg->win + (busn << cfg->ops->bus_shift);
+ return base + (devfn << devfn_shift) + where;
+}
+
+const struct pci_ecam_ops baikal_s_pcie_ecam_ops = {
+ .bus_shift = 20,
+ .init = bs1000_pcie_init,
+ .pci_ops = {
+ .map_bus = bs1000_pcie_map_bus,
+ .read = pci_generic_config_read,
+ .write = pci_generic_config_write
+ }
+};
+#endif
+
+union baikal_pcie {
+ struct bm1000_pcie bm1000_pcie;
+ struct bs1000_pcie bs1000_pcie;
+};
+
+static int baikal_pcie_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dw_pcie *pci;
+ union baikal_pcie *bp;
+ const struct baikal_pcie_of_data *data;
+ int ret;
+
+ data = of_device_get_match_data(dev);
+ if (!data)
+ return -EINVAL;
+
+ bp = devm_kzalloc(dev, sizeof(*bp), GFP_KERNEL);
+ if (!bp)
+ return -ENOMEM;
+
+ pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
+ if (!pci)
+ return -ENOMEM;
+
+ pci->dev = dev;
+ pci->ops = &data->dw_pcie_ops;
+
+ platform_set_drvdata(pdev, bp);
+ ret = data->get_resources(pdev, pci, data);
+ if (ret)
+ return ret;
+
+ switch (data->mode) {
+ case DW_PCIE_RC_TYPE:
+ ret = data->add_pcie_port(pdev);
+ if (ret)
+ return ret;
+
+ break;
+ case DW_PCIE_EP_TYPE:
+ ret = dw_pcie_ep_init(&pci->ep);
+ if (ret) {
+ dev_err(dev, "failed to initialize endpoint\n");
+ return ret;
+ }
+
+ break;
+ default:
+ dev_err(dev, "INVALID device type %d\n", data->mode);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct baikal_pcie_of_data bm1000_pcie_rc_of_data = {
+ .mode = DW_PCIE_RC_TYPE,
+ .dw_pcie_ops = {
+ .link_up = bm1000_pcie_link_up,
+ .start_link = bm1000_pcie_start_link,
+ },
+ .get_resources = bm1000_get_resources,
+ .add_pcie_port = bm1000_add_pcie_port,
+};
+
+static const struct baikal_pcie_of_data bs1000_pcie_rc_of_data = {
+ .mode = DW_PCIE_RC_TYPE,
+ .dw_pcie_ops = {
+ .cpu_addr_fixup = bs1000_pcie_cpu_addr_fixup,
+ .link_up = bs1000_pcie_link_up,
+ .start_link = bs1000_pcie_start_link,
+ },
+ .get_resources = bs1000_get_resources,
+ .add_pcie_port = bs1000_add_pcie_port,
+};
+
+static const struct baikal_pcie_of_data bs1000_pcie_ep_of_data = {
+ .mode = DW_PCIE_EP_TYPE,
+ .dw_pcie_ops = {
+ .cpu_addr_fixup = bs1000_pcie_cpu_addr_fixup,
+ .link_up = bs1000_pcie_link_up,
+ .start_link = bs1000_pcie_start_link,
+ },
+ .get_resources = bs1000_get_resources,
+};
+
+static const struct of_device_id baikal_pcie_of_match[] = {
+ {
+ .compatible = "baikal,bm1000-pcie",
+ .data = &bm1000_pcie_rc_of_data,
+ },
+ {
+ .compatible = "baikal,bs1000-pcie",
+ .data = &bs1000_pcie_rc_of_data,
+ },
+ {
+ .compatible = "baikal,bs1000-pcie-ep",
+ .data = &bs1000_pcie_ep_of_data,
+ },
+ { },
+};
+
+static struct platform_driver baikal_pcie_driver = {
+ .driver = {
+ .name = "baikal-pcie",
+ .of_match_table = baikal_pcie_of_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = baikal_pcie_probe,
+};
+builtin_platform_driver(baikal_pcie_driver);
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BM1000 PCIe Controller & Phy Gen3 equalization parameters fine tune.
+ *
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/property.h>
+#include <linux/debugfs.h>
+
+#include "pcie-designware.h"
+#include "pcie-baikal.h"
+
+static int gen3_eq_fb_mode = -1;
+module_param(gen3_eq_fb_mode, int, 0444);
+MODULE_PARM_DESC(gen3_eq_fb_mode, "feedback mode (0 - FOM + DIR, 1 - FOM only)");
+static int gen3_eq_psets = -1;
+module_param(gen3_eq_psets, int, 0444);
+MODULE_PARM_DESC(gen3_eq_psets, "initial presets");
+static int phy_rx_agc = -1;
+module_param(phy_rx_agc, int, 0444);
+MODULE_PARM_DESC(phy_rx_agc,
+ "Phy RX AGC gain ([7:4] - Pre-CTLE gain, [3:0] - Post-CTLE gain)");
+static int phy_rx_ctle = -1;
+module_param(phy_rx_ctle, int, 0444);
+MODULE_PARM_DESC(phy_rx_ctle, "Phy RX CTLE control ([7:4] - zero, [3:0] - pole)");
+static int phy_rx_dfe = -1;
+module_param(phy_rx_dfe, int, 0444);
+MODULE_PARM_DESC(phy_rx_dfe, "Phy RX DFE control (0 - disable, 1 - enable)");
+static int phy_tx_gain = -1;
+module_param(phy_tx_gain, int, 0444);
+MODULE_PARM_DESC(phy_tx_gain, "Phy TX gain value");
+static int phy_tx_turbo = -1;
+module_param(phy_tx_turbo, int, 0444);
+MODULE_PARM_DESC(phy_tx_turbo, "Phy TX turbo mode (0 - disable, 1 - enable)");
+static int phy_rx_ctle_pole = -1;
+module_param(phy_rx_ctle_pole, int, 0444);
+MODULE_PARM_DESC(phy_rx_ctle_pole, "Phy RX CTLE pole range for VMA adaptation "
+ "([7:4] - max, [3:0] - min)");
+static bool debugfs = false;
+module_param(debugfs, bool, 0444);
+MODULE_PARM_DESC(debugfs, "Phy debugfs monitor enable");
+static bool notune = false;
+module_param(notune, bool, 0444);
+MODULE_PARM_DESC(notune, "Gen3 equalization fine tune disable");
+
+static void bm1000_pcie_tune_debugfs_populate(struct dw_pcie *pci);
+
+/* Baikal-M PCIe PHY registers access */
+#define BM1000_PCIE_AXI2MGM_LINENUM 0xd04
+#define BM1000_PCIE_AXI2MGM_LINENUM_LANE_SEL_MASK GENMASK(7, 0)
+#define BM1000_PCIE_AXI2MGM_ADDRCTL 0xd08
+#define BM1000_PCIE_AXI2MGM_ADDRCTL_BUSY BIT(31)
+#define BM1000_PCIE_AXI2MGM_ADDRCTL_DONE BIT(30)
+#define BM1000_PCIE_AXI2MGM_ADDRCTL_RW_FLAG BIT(29)
+#define BM1000_PCIE_AXI2MGM_ADDRCTL_PHY_ADDR_MASK GENMASK(20, 0)
+#define BM1000_PCIE_AXI2MGM_WRITEDATA 0xd0c
+#define BM1000_PCIE_AXI2MGM_WRITEDATA_DATA_MASK GENMASK(15, 0)
+#define BM1000_PCIE_AXI2MGM_READDATA 0xd10
+#define BM1000_PCIE_AXI2MGM_READDATA_DATA_MASK GENMASK(15, 0)
+
+#define BM1000_PCIE_PHY_REG_RETRIES 10
+#define BM1000_PCIE_PHY_REG_RETRY_TIMEOUT 100
+
+static int bm1000_pcie_phy_done(struct dw_pcie *pci)
+{
+ u32 reg;
+ int retries;
+
+ for (retries = 0; retries < BM1000_PCIE_PHY_REG_RETRIES; ++retries) {
+ reg = dw_pcie_readl_dbi(pci, BM1000_PCIE_AXI2MGM_ADDRCTL);
+ if (reg & BM1000_PCIE_AXI2MGM_ADDRCTL_DONE)
+ return 0;
+ udelay(BM1000_PCIE_PHY_REG_RETRY_TIMEOUT);
+ }
+ return -ETIMEDOUT;
+}
+
+static int bm1000_pcie_phy_read(struct dw_pcie *pci, u8 lane,
+ u32 addr, u16 *value)
+{
+ int ret;
+
+ bm1000_pcie_phy_enable(pci);
+ dw_pcie_writel_dbi(pci, BM1000_PCIE_AXI2MGM_LINENUM, lane);
+ dw_pcie_writel_dbi(pci, BM1000_PCIE_AXI2MGM_ADDRCTL,
+ addr & BM1000_PCIE_AXI2MGM_ADDRCTL_PHY_ADDR_MASK);
+ ret = bm1000_pcie_phy_done(pci);
+ if (ret == 0)
+ *value = dw_pcie_readl_dbi(pci, BM1000_PCIE_AXI2MGM_READDATA) &
+ BM1000_PCIE_AXI2MGM_READDATA_DATA_MASK;
+ bm1000_pcie_phy_disable(pci);
+ return ret;
+}
+
+static int bm1000_pcie_phy_write(struct dw_pcie *pci, u8 lanes,
+ u32 addr, u16 value)
+{
+ int ret;
+
+ bm1000_pcie_phy_enable(pci);
+ dw_pcie_writel_dbi(pci, BM1000_PCIE_AXI2MGM_LINENUM, lanes);
+ dw_pcie_writel_dbi(pci, BM1000_PCIE_AXI2MGM_WRITEDATA, value);
+ dw_pcie_writel_dbi(pci, BM1000_PCIE_AXI2MGM_ADDRCTL,
+ (addr & BM1000_PCIE_AXI2MGM_ADDRCTL_PHY_ADDR_MASK) |
+ BM1000_PCIE_AXI2MGM_ADDRCTL_RW_FLAG);
+ ret = bm1000_pcie_phy_done(pci);
+ bm1000_pcie_phy_disable(pci);
+ return ret;
+}
+
+/* Baikal-M PCIe RX/TX equalizers fine tune */
+#define BM1000_PCIE_GEN3_EQ_CONTROL 0x8a8
+#define BM1000_PCIE_GEN3_EQ_FOM_INC_INITIAL_EVAL BIT(24)
+#define BM1000_PCIE_GEN3_EQ_PSET_REQ_VEC_MASK GENMASK(23, 8)
+#define BM1000_PCIE_GEN3_EQ_FB_MODE_MASK GENMASK(3, 0)
+
+#define BM1000_PCIE_PHY_RX_CFG_2 0x18002
+#define BM1000_PCIE_PHY_RX_CFG_2_PCS_SDS_RX_AGC_MVAL GENMASK(9, 0)
+#define BM1000_PCIE_PHY_RX_CFG_5 0x18005
+#define BM1000_PCIE_PHY_RX_CFG_5_RX_AGC_MEN_OVRRD_EN BIT(4)
+#define BM1000_PCIE_PHY_RX_CFG_5_RX_AGC_MEN_OVRRD_VAL BIT(3)
+#define BM1000_PCIE_PHY_RX_LOOP_CTRL 0x18009
+#define BM1000_PCIE_PHY_RX_LOOP_CTRL_CFG_RX_LCTRL_LCTRL_MEN BIT(8)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL 0x1800b
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_ZERO_MASK GENMASK(13, 10)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_RX_CTLE_POLE_OVRRD_EN BIT(9)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_RX_CTLE_POLE_OVRRD_VAL_MASK GENMASK(8, 5)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MAX_MASK GENMASK(4, 3)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MIN_MASK GENMASK(2, 1)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_STEP BIT(0)
+#define BM1000_PCIE_PHY_TX_CFG_1 0x18016
+#define BM1000_PCIE_PHY_TX_CFG_1_TX_VBOOST_EN_OVRRD_EN BIT(11)
+#define BM1000_PCIE_PHY_TX_CFG_1_TX_TURBO_EN_OVRRD_EN BIT(10)
+#define BM1000_PCIE_PHY_TX_CFG_3 0x18018
+#define BM1000_PCIE_PHY_TX_CFG_3_CFG_TX_VBOOST_EN BIT(14)
+#define BM1000_PCIE_PHY_TX_CFG_3_PCS_SDS_TX_GAIN_MASK GENMASK(6, 4)
+#define BM1000_PCIE_PHY_TX_CFG_3_CFG_TX_TURBO_EN BIT(0)
+#define BM1000_PCIE_PHY_RX_PWR_MON_1 0x1802a
+#define BM1000_PCIE_PHY_RX_PWR_MON_1_RX_PWRSM_LANE_PWR_OFF BIT(4)
+#define BM1000_PCIE_PHY_TX_PWR_MON_0 0x1802c
+#define BM1000_PCIE_PHY_TX_PWR_MON_0_TX_PWRSM_LANE_PWR_OFF BIT(15)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_0 0x18048
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_1 0x18049
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2 0x1804a
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_OVRD_EN BIT(5)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C5_MEN_OVRD_VAL BIT(4)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C4_MEN_OVRD_VAL BIT(3)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C3_MEN_OVRD_VAL BIT(2)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C2_MEN_OVRD_VAL BIT(1)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C1_MEN_OVRD_VAL BIT(0)
+
+void bm1000_pcie_tune(struct dw_pcie *pci)
+{
+ struct device *dev = pci->dev;
+ u16 exp_cap_off;
+ u8 lane = 0, lanes = 0;
+ int override;
+ int i, n_lanes;
+ int ret;
+
+ if (notune)
+ return;
+
+ /* Search for active lanes */
+ exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+ n_lanes = FIELD_GET(PCI_EXP_LNKCAP_MLW,
+ dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKCAP));
+ for (i = 0; i < n_lanes; i++) {
+ u16 reg;
+ u8 mask = 1 << i;
+
+ ret = bm1000_pcie_phy_read(pci, mask,
+ BM1000_PCIE_PHY_RX_PWR_MON_1, ®);
+ if (ret != 0 ||
+ FIELD_GET(BM1000_PCIE_PHY_RX_PWR_MON_1_RX_PWRSM_LANE_PWR_OFF,
+ reg) == 1)
+ continue;
+ ret = bm1000_pcie_phy_read(pci, mask,
+ BM1000_PCIE_PHY_TX_PWR_MON_0, ®);
+ if (ret != 0 ||
+ FIELD_GET(BM1000_PCIE_PHY_TX_PWR_MON_0_TX_PWRSM_LANE_PWR_OFF,
+ reg) == 1)
+ continue;
+ lanes |= mask;
+ if (lane == 0)
+ lane = mask;
+ }
+
+ /* Feedback mode */
+ override = gen3_eq_fb_mode;
+ if (override == -1)
+ device_property_read_u32(dev, "bm1000,gen3-eq-fb-mode", &override);
+ if (override >= 0) {
+ u32 reg;
+
+ dev_dbg(dev, "Gen3 fb_mode = %d\n", override);
+ reg = dw_pcie_readl_dbi(pci, BM1000_PCIE_GEN3_EQ_CONTROL);
+ reg &= ~BM1000_PCIE_GEN3_EQ_FB_MODE_MASK;
+ reg |= FIELD_PREP(BM1000_PCIE_GEN3_EQ_FB_MODE_MASK, override);
+ dw_pcie_writel_dbi(pci, BM1000_PCIE_GEN3_EQ_CONTROL, reg);
+ }
+ /* Initial presets */
+ override = gen3_eq_psets;
+ if (override == -1)
+ device_property_read_u32(dev, "bm1000,gen3-eq-psets", &override);
+ if (override >= 0) {
+ u32 reg;
+
+ reg = dw_pcie_readl_dbi(pci, BM1000_PCIE_GEN3_EQ_CONTROL);
+ dev_dbg(dev, "Gen3 initial presets = 0x%x\n", override);
+ reg &= ~(BM1000_PCIE_GEN3_EQ_PSET_REQ_VEC_MASK |
+ BM1000_PCIE_GEN3_EQ_FOM_INC_INITIAL_EVAL);
+ reg |= FIELD_PREP(BM1000_PCIE_GEN3_EQ_PSET_REQ_VEC_MASK,
+ override);
+ dw_pcie_writel_dbi(pci, BM1000_PCIE_GEN3_EQ_CONTROL, reg);
+ }
+ /* Phy RX AGC */
+ override = phy_rx_agc;
+ if (override == -1)
+ device_property_read_u32(dev, "bm1000,phy-rx-agc", &override);
+ if (override >= 0) {
+ u16 reg;
+
+ ret = bm1000_pcie_phy_read(pci, lane, BM1000_PCIE_PHY_RX_CFG_2,
+ ®);
+ if (ret == 0) {
+ reg &= ~BM1000_PCIE_PHY_RX_CFG_2_PCS_SDS_RX_AGC_MVAL;
+ reg |= FIELD_PREP(
+ BM1000_PCIE_PHY_RX_CFG_2_PCS_SDS_RX_AGC_MVAL,
+ override);
+ ret = bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_RX_CFG_2,
+ reg);
+ }
+ if (ret == 0)
+ ret = bm1000_pcie_phy_read(pci, lane,
+ BM1000_PCIE_PHY_RX_CFG_5,
+ ®);
+ if (ret == 0) {
+ reg |= BM1000_PCIE_PHY_RX_CFG_5_RX_AGC_MEN_OVRRD_EN |
+ BM1000_PCIE_PHY_RX_CFG_5_RX_AGC_MEN_OVRRD_VAL;
+ ret = bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_RX_CFG_5,
+ reg);
+ }
+ dev_dbg(dev, "Phy RX AGC = 0x%04x (%d)\n", override, ret);
+ }
+ /* Rhy RX CTLE */
+ override = phy_rx_ctle;
+ if (override == -1)
+ device_property_read_u32(dev, "bm1000,phy-rx-ctle", &override);
+ if (override >= 0) {
+ u16 reg;
+
+ ret = bm1000_pcie_phy_read(pci, lane,
+ BM1000_PCIE_PHY_RX_CTLE_CTRL, ®);
+ if (ret == 0) {
+ reg &= ~(BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_ZERO_MASK |
+ BM1000_PCIE_PHY_RX_CTLE_CTRL_RX_CTLE_POLE_OVRRD_VAL_MASK);
+ reg |= BM1000_PCIE_PHY_RX_CTLE_CTRL_RX_CTLE_POLE_OVRRD_EN |
+ FIELD_PREP(BM1000_PCIE_PHY_RX_CTLE_CTRL_RX_CTLE_POLE_OVRRD_VAL_MASK,
+ override & 0xf) |
+ FIELD_PREP(BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_ZERO_MASK,
+ override >> 4);
+ ret = bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_RX_CTLE_CTRL,
+ reg);
+ }
+ if (ret == 0)
+ ret = bm1000_pcie_phy_read(pci, lane,
+ BM1000_PCIE_PHY_RX_LOOP_CTRL,
+ ®);
+ if (ret == 0) {
+ reg |= BM1000_PCIE_PHY_RX_LOOP_CTRL_CFG_RX_LCTRL_LCTRL_MEN;
+ ret = bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_RX_LOOP_CTRL,
+ reg);
+ }
+ dev_dbg(dev, "Phy RX CTLE = 0x%04x (%d)\n", override, ret);
+ }
+ /* Phy RX DFE */
+ override = phy_rx_dfe;
+ if (override == -1)
+ device_property_read_u32(dev, "bm1000,phy-rx-dfe", &override);
+ if (override == 0) { /* enabled by default - disable only */
+ u16 reg;
+
+ reg = BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_OVRD_EN;
+ reg |= BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C1_MEN_OVRD_VAL |
+ BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C2_MEN_OVRD_VAL |
+ BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C3_MEN_OVRD_VAL |
+ BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C4_MEN_OVRD_VAL |
+ BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C5_MEN_OVRD_VAL;
+ ret = bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_RX_AEQ_VALBBD_2,
+ reg);
+ if (ret == 0)
+ ret = bm1000_pcie_phy_read(pci, lane,
+ BM1000_PCIE_PHY_RX_LOOP_CTRL,
+ ®);
+ if (ret == 0) {
+ reg |= BM1000_PCIE_PHY_RX_LOOP_CTRL_CFG_RX_LCTRL_LCTRL_MEN;
+ ret = bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_RX_LOOP_CTRL,
+ reg);
+ }
+ if (ret == 0) {
+ reg = 0;
+ bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_RX_AEQ_VALBBD_0,
+ reg);
+ bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_RX_AEQ_VALBBD_1,
+ reg);
+ }
+ dev_dbg(dev, "Phy RX DFE = %d (%d)\n", override, ret);
+ }
+ /* Phy TX gain */
+ override = phy_tx_gain;
+ if (override == -1)
+ device_property_read_u32(dev, "bm1000,phy-tx-gain", &override);
+ if (override >= 0) {
+ u16 reg;
+
+ ret = bm1000_pcie_phy_read(pci, lane, BM1000_PCIE_PHY_TX_CFG_3,
+ ®);
+ if (ret == 0) {
+ reg &= ~BM1000_PCIE_PHY_TX_CFG_3_PCS_SDS_TX_GAIN_MASK;
+ reg |= BM1000_PCIE_PHY_TX_CFG_3_CFG_TX_VBOOST_EN;
+ reg |= FIELD_PREP(BM1000_PCIE_PHY_TX_CFG_3_PCS_SDS_TX_GAIN_MASK,
+ override);
+ ret = bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_TX_CFG_3,
+ reg);
+ }
+ if (ret == 0)
+ ret = bm1000_pcie_phy_read(pci, lane,
+ BM1000_PCIE_PHY_TX_CFG_1,
+ ®);
+ if (ret == 0) {
+ reg |= BM1000_PCIE_PHY_TX_CFG_1_TX_VBOOST_EN_OVRRD_EN;
+ ret = bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_TX_CFG_1,
+ reg);
+ }
+ dev_dbg(dev, "Phy TX gain = 0x%x (%d)\n", override, ret);
+ }
+ /* Phy TX turbo */
+ override = phy_tx_turbo;
+ if (override == -1)
+ device_property_read_u32(dev, "bm1000,phy-tx-turbo", &override);
+ if (override >= 0) {
+ u16 reg;
+
+ ret = bm1000_pcie_phy_read(pci, lane, BM1000_PCIE_PHY_TX_CFG_3,
+ ®);
+ if (ret == 0) {
+ if (override == 0)
+ reg &= ~BM1000_PCIE_PHY_TX_CFG_3_CFG_TX_TURBO_EN;
+ else
+ reg |= BM1000_PCIE_PHY_TX_CFG_3_CFG_TX_TURBO_EN;
+ ret = bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_TX_CFG_3,
+ reg);
+ }
+ if (ret == 0)
+ ret = bm1000_pcie_phy_read(pci, lane,
+ BM1000_PCIE_PHY_TX_CFG_1,
+ ®);
+ if (ret == 0) {
+ reg |= BM1000_PCIE_PHY_TX_CFG_1_TX_TURBO_EN_OVRRD_EN;
+ ret = bm1000_pcie_phy_write(pci, lanes,
+ BM1000_PCIE_PHY_TX_CFG_1,
+ reg);
+ }
+ dev_dbg(dev, "Phy TX turbo = %d (%d)\n", override, ret);
+ }
+ /* Phy RX CTLE pole range */
+ override = phy_rx_ctle_pole;
+ if (override == -1)
+ device_property_read_u32(dev, "bm1000,phy-rx-ctle-pole", &override);
+ if (override >= 0) {
+ u16 reg;
+ u8 pole_max = (override >> 4) & 0xf, pole_min = override & 0xf;
+
+ ret = bm1000_pcie_phy_read(pci, lane, BM1000_PCIE_PHY_RX_CTLE_CTRL,
+ ®);
+ if (ret == 0) {
+ reg &= ~(BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MAX_MASK |
+ BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MIN_MASK);
+ reg |= FIELD_PREP(BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MAX_MASK,
+ pole_max);
+ reg |= FIELD_PREP(BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MIN_MASK,
+ pole_min);
+ if (pole_max == pole_min)
+ reg &= ~BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_STEP;
+ else
+ reg |= BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_STEP;
+ ret = bm1000_pcie_phy_write(pci, lanes, BM1000_PCIE_PHY_RX_CTLE_CTRL,
+ reg);
+ }
+ dev_dbg(dev, "Phy RX CTLE pole = 0x%04x (%d)\n", override, ret);
+ }
+
+ /* debugfs populate */
+ if (debugfs)
+ bm1000_pcie_tune_debugfs_populate(pci);
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+#define BM1000_PCIE_PHY_SDS_PIN_MON_1 0x18027
+#define BM1000_PCIE_PHY_SDS_PIN_MON_1_PCS_SDS_TX_SWING GENMASK(5, 1)
+#define BM1000_PCIE_PHY_SDS_PIN_MON_2 0x18028
+#define BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_VBOOST_EN BIT(10)
+#define BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_TURBO_EN BIT(9)
+#define BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_POST_CURSOR GENMASK(8, 4)
+#define BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_PRE_CURSOR GENMASK(3, 0)
+#define BM1000_PCIE_PHY_RX_PWR_MON_0 0x18029
+#define BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_AGC_EN BIT(10)
+#define BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_DFE_EN BIT(9)
+#define BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_CDR_EN BIT(8)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_0 0x18050
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_0_DFE_TAP5 GENMASK(9, 5)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_0_DFE_TAP4 GENMASK(4, 0)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_1 0x18051
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP3 GENMASK(14, 10)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP2 GENMASK(9, 5)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP1 GENMASK(4, 0)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_2 0x18052
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_2_PRE_CTLE_GAIN GENMASK(7, 4)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_2_POST_CTLE_GAIN GENMASK(3, 0)
+#define BM1000_PCIE_PHY_RX_VMA_STATUS_0 0x18057
+#define BM1000_PCIE_PHY_RX_VMA_STATUS_0_CTLE_PEAK GENMASK(5, 2)
+#define BM1000_PCIE_PHY_RX_VMA_STATUS_0_CTLE_POLE GENMASK(1, 0)
+
+static void print_for_all_lanes(struct dw_pcie *pci, struct seq_file *s,
+ u8 n_lanes, u32 addr, u16 mask)
+{
+ int i;
+
+ for (i = 0; i < n_lanes; i++) {
+ u16 reg;
+ u8 lane = 1 << i;
+
+ if (bm1000_pcie_phy_read(pci, lane, addr, ®) == 0)
+ seq_put_hex_ll(s, " ", (reg & mask) >> __bf_shf(mask), 0);
+ else
+ seq_puts(s, " ?");
+ }
+}
+
+static int bm1000_pcie_dbgfs_phy_mon_show(struct seq_file *s, void *data)
+{
+ struct dw_pcie *pci = s->private;
+ u8 n_lanes;
+ u16 exp_cap_off;
+
+ exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+ n_lanes = FIELD_GET(PCI_EXP_LNKCAP_MLW,
+ dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKCAP));
+
+ seq_puts(s, "sds_pin_mon:\n");
+ seq_puts(s, " tx_swing:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_SDS_PIN_MON_1,
+ BM1000_PCIE_PHY_SDS_PIN_MON_1_PCS_SDS_TX_SWING);
+ seq_puts(s, "\n");
+ seq_puts(s, " pre_cursor:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_SDS_PIN_MON_2,
+ BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_PRE_CURSOR);
+ seq_puts(s, "\n");
+ seq_puts(s, " post_cursor:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_SDS_PIN_MON_2,
+ BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_POST_CURSOR);
+ seq_puts(s, "\n");
+ seq_puts(s, " vboost_en:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_SDS_PIN_MON_2,
+ BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_VBOOST_EN);
+ seq_puts(s, "\n");
+ seq_puts(s, " turbo_en:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_SDS_PIN_MON_2,
+ BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_TURBO_EN);
+ seq_puts(s, "\n");
+ seq_puts(s, "rx_vma_status:\n");
+ seq_puts(s, " ctle_peak:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_VMA_STATUS_0,
+ BM1000_PCIE_PHY_RX_VMA_STATUS_0_CTLE_PEAK);
+ seq_puts(s, "\n");
+ seq_puts(s, " ctle_pole:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_VMA_STATUS_0,
+ BM1000_PCIE_PHY_RX_VMA_STATUS_0_CTLE_POLE);
+ seq_puts(s, "\n");
+ seq_puts(s, "rx_aeq_out:\n");
+ seq_puts(s, " pre_ctle_gain:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_2,
+ BM1000_PCIE_PHY_RX_AEQ_OUT_2_PRE_CTLE_GAIN);
+ seq_puts(s, "\n");
+ seq_puts(s, " post_ctle_gain:");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_2,
+ BM1000_PCIE_PHY_RX_AEQ_OUT_2_POST_CTLE_GAIN);
+ seq_puts(s, "\n");
+ seq_puts(s, " dfe_tap1:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_1,
+ BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP1);
+ seq_puts(s, "\n");
+ seq_puts(s, " dfe_tap2:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_1,
+ BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP2);
+ seq_puts(s, "\n");
+ seq_puts(s, " dfe_tap3:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_1,
+ BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP3);
+ seq_puts(s, "\n");
+ seq_puts(s, " dfe_tap4:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_0,
+ BM1000_PCIE_PHY_RX_AEQ_OUT_0_DFE_TAP4);
+ seq_puts(s, "\n");
+ seq_puts(s, " dfe_tap5:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_0,
+ BM1000_PCIE_PHY_RX_AEQ_OUT_0_DFE_TAP5);
+ seq_puts(s, "\n");
+ seq_puts(s, "pwr_mon:\n");
+ seq_puts(s, " tx_pwr_off:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_TX_PWR_MON_0,
+ BM1000_PCIE_PHY_TX_PWR_MON_0_TX_PWRSM_LANE_PWR_OFF);
+ seq_puts(s, "\n");
+ seq_puts(s, " rx_pwr_off:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_PWR_MON_1,
+ BM1000_PCIE_PHY_RX_PWR_MON_1_RX_PWRSM_LANE_PWR_OFF);
+ seq_puts(s, "\n");
+ seq_puts(s, " agc_en:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_PWR_MON_0,
+ BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_AGC_EN);
+ seq_puts(s, "\n");
+ seq_puts(s, " dfe_en:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_PWR_MON_0,
+ BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_DFE_EN);
+ seq_puts(s, "\n");
+ seq_puts(s, " cdr_en:\t");
+ print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_PWR_MON_0,
+ BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_CDR_EN);
+ seq_puts(s, "\n");
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(bm1000_pcie_dbgfs_phy_mon);
+
+static void bm1000_pcie_tune_debugfs_populate(struct dw_pcie *pci)
+{
+ struct dentry *root_dir;
+
+ root_dir = debugfs_create_dir(dev_name(pci->dev), NULL);
+ if (!root_dir) {
+ dev_warn(pci->dev, "%s: failed to create debugfs dir\n",
+ __func__);
+ return;
+ }
+ if (!debugfs_create_file("phy_mon", S_IRUGO, root_dir, pci,
+ &bm1000_pcie_dbgfs_phy_mon_fops))
+ dev_warn(pci->dev, "%s: failed to create phy_mon debugfs file\n",
+ __func__);
+}
+
+#else
+
+static void bm1000_pcie_tune_debugfs_populate(struct dw_pcie *pci)
+{
+}
+
+#endif /* CONFIG_DEBUG_FS */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PCIe controller driver for Baikal Electronics SoCs
+ *
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ */
+
+#ifndef _PCIE_BAIKAL_H
+#define _PCIE_BAIKAL_H
+
+void bm1000_pcie_phy_enable(struct dw_pcie *pci);
+void bm1000_pcie_phy_disable(struct dw_pcie *pci);
+
+void bm1000_pcie_tune(struct dw_pcie *pci);
+
+#endif /* _PCIE_BAIKAL_H */
source "drivers/phy/ti/Kconfig"
source "drivers/phy/intel/Kconfig"
source "drivers/phy/xilinx/Kconfig"
+source "drivers/phy/baikal/Kconfig"
endmenu
sunplus/ \
tegra/ \
ti/ \
+ baikal/ \
xilinx/
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only
+
+config USB_PHY_BAIKAL
+ tristate "Baikal USB PHY driver"
+ depends on USB_SUPPORT
+ select GENERIC_PHY
+ select USB_PHY
+ help
+ Enable this to support the USB PHY on Baikal SoCs.
+ This driver controls both the USB2 PHY and the USB3 PHY.
\ No newline at end of file
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_USB_PHY_BAIKAL) += baikal-usb-phy.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Baikal USB PHY driver
+ *
+ * Copyright (C) 2022-2023 Baikal Electronics, JSC
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <dt-bindings/phy/phy.h>
+
+struct phy_baikal_desc {
+ struct phy *phy;
+ int index;
+ bool enable;
+};
+
+struct phy_baikal_priv {
+ struct phy_baikal_desc **phys;
+ int nphys;
+ struct clk_bulk_data *clocks;
+ unsigned int nclocks;
+};
+
+#ifdef CONFIG_ACPI
+static int phy_baikal_acpi_get_info(struct fwnode_handle *fwnode,
+ bool *is_usb3, const char **ref_name)
+{
+ struct acpi_device *adev = to_acpi_device_node(fwnode);
+ struct acpi_device *ref_adev;
+ struct device *ref_dev;
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+ int ret = 0;
+
+ *is_usb3 = fwnode_property_read_bool(fwnode, "usb3");
+ status = acpi_evaluate_object_typed(adev->handle, "CTRL", NULL,
+ &buffer, ACPI_TYPE_PACKAGE);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&adev->dev, "failed to get CTRL data\n");
+ return -ENODEV;
+ }
+
+ obj = buffer.pointer;
+ if (obj->package.count != 1) {
+ dev_err(&adev->dev, "invalid CTRL data\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ obj = &obj->package.elements[0];
+ if (obj->type != ACPI_TYPE_LOCAL_REFERENCE || !obj->reference.handle) {
+ dev_err(&adev->dev, "invalid CTRL reference\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ref_adev = acpi_fetch_acpi_dev(obj->reference.handle);
+ if (!ref_adev) {
+ dev_err(&adev->dev, "failed to process CTRL reference\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ ref_dev = bus_find_device_by_fwnode(&platform_bus_type,
+ acpi_fwnode_handle(ref_adev));
+ if (!ref_dev) {
+ dev_err(&adev->dev, "failed to get referenced device\n");
+ ret = -ENODEV;
+ goto err;
+ }
+ *ref_name = dev_name(ref_dev);
+
+err:
+ acpi_os_free(buffer.pointer);
+ return ret;
+}
+
+static int phy_baikal_acpi_add(struct device *dev)
+{
+ struct phy_baikal_priv *priv = dev_get_drvdata(dev);
+ struct fwnode_handle *child;
+ const char **ref_name;
+ bool *is_usb3;
+ int count = 0, i, ret;
+
+ ref_name = kcalloc(priv->nphys, sizeof(*ref_name), GFP_KERNEL);
+ if (!ref_name)
+ return -ENOMEM;
+
+ is_usb3 = kcalloc(priv->nphys, sizeof(*is_usb3), GFP_KERNEL);
+ if (!is_usb3) {
+ kfree(ref_name);
+ return -ENOMEM;
+ }
+
+ device_for_each_child_node(dev, child) {
+ ret = phy_baikal_acpi_get_info(child, &is_usb3[count],
+ &ref_name[count]);
+ if (ret)
+ goto err;
+
+ ret = phy_create_lookup(priv->phys[count]->phy,
+ is_usb3[count] ? "usb3-phy" : "usb2-phy",
+ ref_name[count]);
+ if (ret)
+ goto err;
+
+ ++count;
+ }
+
+err:
+ if (ret) {
+ for (i = 0; i < count; ++i)
+ phy_remove_lookup(priv->phys[i]->phy,
+ is_usb3[i] ? "usb3-phy" : "usb2-phy",
+ ref_name[i]);
+ }
+
+ kfree(ref_name);
+ kfree(is_usb3);
+ return ret;
+}
+
+static void phy_baikal_acpi_remove(struct device *dev)
+{
+ struct phy_baikal_priv *priv = dev_get_drvdata(dev);
+ struct fwnode_handle *child;
+ const char *ref_name;
+ bool is_usb3;
+ int i, ret;
+
+ device_for_each_child_node(dev, child) {
+ ret = phy_baikal_acpi_get_info(child, &is_usb3, &ref_name);
+ if (ret) {
+ ++i;
+ continue;
+ }
+
+ phy_remove_lookup(priv->phys[i++]->phy,
+ is_usb3 ? "usb3-phy" : "usb2-phy",
+ ref_name);
+ }
+}
+
+static void phy_baikal_acpi_init_clk(struct phy_baikal_desc *desc)
+{
+ struct device *dev = desc->phy->dev.parent;
+ struct phy_baikal_priv *priv = dev_get_drvdata(dev);
+ struct fwnode_handle *child;
+ int i = 0;
+
+ device_for_each_child_node(dev, child) {
+ if (i++ == desc->index) {
+ priv->clocks[2 * desc->index].clk =
+ devm_clk_get_optional(&to_acpi_device_node(child)->dev, "phy0_clk");
+ priv->clocks[2 * desc->index + 1].clk =
+ devm_clk_get_optional(&to_acpi_device_node(child)->dev, "phy1_clk");
+ break;
+ }
+ }
+}
+#else /* CONFIG_ACPI */
+static inline int phy_baikal_acpi_add(struct device *dev)
+{
+ return 0;
+}
+
+static inline void phy_baikal_acpi_remove(struct device *dev)
+{
+}
+
+static inline void phy_baikal_acpi_init_clk(struct phy_baikal_desc *desc)
+{
+}
+#endif
+
+static int phy_baikal_init(struct phy *phy)
+{
+ struct phy_baikal_priv *priv = dev_get_drvdata(phy->dev.parent);
+ struct phy_baikal_desc *desc = phy_get_drvdata(phy);
+ int n = 2 * desc->index;
+
+ if (!acpi_disabled)
+ phy_baikal_acpi_init_clk(desc);
+
+ if (desc->enable) {
+ clk_prepare_enable(priv->clocks[n + 0].clk);
+ clk_prepare_enable(priv->clocks[n + 1].clk);
+ return 0;
+ } else {
+ clk_disable_unprepare(priv->clocks[n + 0].clk);
+ clk_disable_unprepare(priv->clocks[n + 1].clk);
+ return -1;
+ }
+}
+
+static const struct phy_ops phy_baikal_ops = {
+ .init = phy_baikal_init,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *phy_baikal_xlate(struct device *dev, struct of_phandle_args *args)
+{
+ int i;
+ struct phy_baikal_priv *priv = dev_get_drvdata(dev);
+
+ for (i = 0; i < priv->nphys; i++) {
+ if (priv->phys[i]->index == args->args[0])
+ break;
+ }
+
+ return priv->phys[i]->phy;
+}
+
+static int phy_baikal_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fwnode_handle *child;
+ struct phy *phy;
+ struct phy_baikal_priv *priv;
+ struct phy_baikal_desc *phy_desc;
+ int index;
+ int i = 0;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->nphys = device_get_child_node_count(dev);
+ priv->phys = devm_kcalloc(dev, priv->nphys, sizeof(*priv->phys), GFP_KERNEL);
+ if (!priv->phys)
+ return -ENOMEM;
+
+ if (acpi_disabled) {
+ priv->nclocks = devm_clk_bulk_get_all(dev, &priv->clocks);
+ } else {
+ priv->nclocks = 2 * priv->nphys;
+ priv->clocks = devm_kcalloc(dev, priv->nclocks,
+ sizeof(*priv->clocks), GFP_KERNEL);
+ if (!priv->clocks)
+ return -ENOMEM;
+ }
+
+ dev_set_drvdata(dev, priv);
+ device_for_each_child_node(dev, child) {
+ fwnode_property_read_u32(child, "reg", &index);
+ phy = devm_phy_create(dev, NULL, &phy_baikal_ops);
+ if (!phy)
+ return -ENOMEM;
+
+ phy_desc = devm_kzalloc(dev, sizeof(*phy_desc), GFP_KERNEL);
+ if (!phy_desc)
+ return -ENOMEM;
+
+ phy_desc->phy = phy;
+ phy_desc->index = index;
+ phy_desc->enable = fwnode_property_read_bool(child, "enable");
+ priv->phys[i++] = phy_desc;
+ phy_set_drvdata(phy, phy_desc);
+ }
+
+ if (acpi_disabled)
+ return PTR_ERR_OR_ZERO(devm_of_phy_provider_register(dev, phy_baikal_xlate));
+ else
+ return phy_baikal_acpi_add(dev);
+}
+
+static int phy_baikal_remove(struct platform_device *pdev)
+{
+ if (!acpi_disabled)
+ phy_baikal_acpi_remove(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id phy_baikal_table[] = {
+ { .compatible = "baikal,bm1000-usb-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_baikal_table);
+
+static struct platform_driver phy_baikal_driver = {
+ .probe = phy_baikal_probe,
+ .remove = phy_baikal_remove,
+ .driver = {
+ .name = "baikal,bm1000-usb-phy",
+ .of_match_table = phy_baikal_table,
+ },
+};
+module_platform_driver(phy_baikal_driver);
+
+MODULE_DESCRIPTION("Baikal USB PHY driver");
+MODULE_LICENSE("GPL v2");
It is part of the SPI Engine framework that is used in some Analog Devices
reference designs for FPGAs.
+config SPI_BAIKAL_ESPI
+ tristate "Baikal eSPI controller"
+
config SPI_BCM2835
tristate "BCM2835 SPI controller"
depends on GPIOLIB
obj-$(CONFIG_SPI_ATH79) += spi-ath79.o
obj-$(CONFIG_SPI_AU1550) += spi-au1550.o
obj-$(CONFIG_SPI_AXI_SPI_ENGINE) += spi-axi-spi-engine.o
+obj-$(CONFIG_SPI_BAIKAL_ESPI) += spi-baikal-espi.o
obj-$(CONFIG_SPI_BCM2835) += spi-bcm2835.o
obj-$(CONFIG_SPI_BCM2835AUX) += spi-bcm2835aux.o
obj-$(CONFIG_SPI_BCM63XX) += spi-bcm63xx.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Baikal Electronics eSPI controller driver
+ * Copyright (C) 2020-2022 Baikal Electronics, JSC
+ */
+
+#include <linux/acpi.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/highmem.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/property.h>
+#include <linux/io.h>
+
+#define ESPI_FIFO_LEN 256
+#define ESPI_DUMMY_DATA 0xff
+
+struct espi_data {
+ struct spi_master *master;
+ /* hardware */
+ int irq;
+ void __iomem *regs;
+ struct clk *clk;
+ /* transfer */
+ uint32_t rxlen;
+ uint32_t txlen;
+ uint8_t *tx;
+ uint8_t *rx;
+};
+
+#define ESPI_CR1 0x00 /* control 1 */
+#define ESPI_CR2 0x04 /* control 2 */
+#define ESPI_TX_FIFO 0x0c /* tx fifo */
+#define ESPI_TX_FBCAR 0x14 /* tx byte count */
+#define ESPI_TX_FAETR 0x18 /* tx almost empty threshold */
+#define ESPI_RX_FIFO 0x1c /* rx fifo */
+#define ESPI_RX_FBCAR 0x24 /* rx byte count */
+#define ESPI_RX_FAFTR 0x28 /* rx almost full threshold */
+#define ESPI_ISR 0x30 /* irq status */
+#define ESPI_IMR 0x34 /* irq mask */
+#define ESPI_IVR 0x38 /* irq vector */
+
+typedef union {
+ uint32_t val;
+ struct {
+ uint32_t scr :1; /* reset */
+ uint32_t sce :1; /* enable */
+ uint32_t mss :1; /* select - master/slave */
+ uint32_t cph :1; /* clock phase */
+ uint32_t cpo :1; /* clock polarity */
+ } bits;
+} espi_cr1_t;
+
+#define ESPI_CR1_SCR_RESET 0
+#define ESPI_CR1_SCR_NORESET 1
+#define ESPI_CR1_SCE_CORE_DISABLE 0
+#define ESPI_CR1_SCE_CORE_ENABLE 1
+#define ESPI_CR1_MSS_MODE_MASTER 0
+#define ESPI_CR1_MSS_MODE_SLAVE 1
+#define ESPI_CR1_CPH_CLKPHASE_ODD 0
+#define ESPI_CR1_CPH_CLKPHASE_EVEN 1
+#define ESPI_CR1_CPO_CLKPOLAR_LOW 0
+#define ESPI_CR1_CPO_CLKPOLAR_HIGH 1
+
+typedef union {
+ uint32_t val;
+ struct {
+ uint32_t sso :3; /* slave select (ss0-ss7) */
+ uint32_t srd :1; /* read disable */
+ uint32_t sri :1; /* read first byte ignore */
+ uint32_t mlb :1; /* least sign git first */
+ uint32_t mte :1; /* master transfer enable */
+ } bits;
+} espi_cr2_t;
+
+#define ESPI_CR2_SRD_RX_DISABLE 0
+#define ESPI_CR2_SRD_RX_ENABLE 1
+#define ESPI_CR2_SRI_FIRST_RESIEV 0
+#define ESPI_CR2_SRI_FIRST_IGNORE 1
+#define ESPI_CR2_MLB_MSB 0
+#define ESPI_CR2_MLB_LSB 1
+#define ESPI_CR2_MTE_TX_DISABLE 0
+#define ESPI_CR2_MTE_TX_ENABLE 1 /* self clear */
+
+#define ESPI_RX_FAFTR_DISABLE 0xff
+#define ESPI_TX_FAETR_DISABLE 0x0
+#define ESPI_ISR_CLEAR_ALL 0xff /* clear by writing "1" */
+#define ESPI_IMR_DISABLE_ALL 0x0
+#define ESPI_IMR_ENABLE_ALL 0xff
+
+/* isr, imr, ivr */
+typedef union {
+ uint32_t val;
+ struct {
+ uint32_t tx_underrun :1;
+ uint32_t tx_overrun :1;
+ uint32_t rx_underrun :1;
+ uint32_t rx_overrun :1;
+ uint32_t tx_almost_empty :1;
+ uint32_t rx_almost_full :1;
+ uint32_t tx_done_master :1;
+ uint32_t tx_done_slave :1;
+ uint32_t alert :8;
+ uint32_t rx_crc_error :1;
+ } bits;
+} espi_irq_t;
+
+#define ESPI_IRQ_DISABLE 0
+#define ESPI_IRQ_ENABLE 1
+
+typedef union {
+ uint32_t val;
+ struct {
+ uint32_t ese :1; /* enable */
+ uint32_t rms :1; /* reset# direction */
+ uint32_t raa :1; /* reset# assertion active */
+ uint32_t cre :1; /* crc receive enable */
+ uint32_t crf :1; /* crc receive byte placed in fifo */
+ uint32_t wsr :1; /* wait state response */
+ uint32_t sdl :2; /* spi data lines (single, dual, quad) */
+ } bits;
+} espi_cr3_t;
+
+static inline uint32_t espi_readl(struct espi_data *priv, uint32_t offset)
+{
+ return __raw_readl(priv->regs + offset);
+}
+static inline void espi_writel(struct espi_data *priv, uint32_t offset, uint32_t val)
+{
+ __raw_writel(val, priv->regs + offset);
+}
+
+static void espi_set_cs (struct spi_device *spi, bool enable);
+static int espi_setup (struct spi_device *spi);
+static void espi_writer (struct espi_data *priv);
+static void espi_reader (struct espi_data *priv);
+static void espi_reset (struct espi_data *priv);
+static void espi_enable_tx(struct espi_data *priv);
+static void espi_enable_rx(struct espi_data *priv);
+
+static void espi_reset(struct espi_data *priv)
+{
+ espi_cr1_t cr1;
+ cr1.val = espi_readl(priv, ESPI_CR1);
+ cr1.bits.scr = ESPI_CR1_SCR_RESET;
+ espi_writel(priv, ESPI_CR1, cr1.val);
+}
+
+static void espi_enable_rx(struct espi_data *priv)
+{
+ espi_cr2_t cr2;
+ cr2.val = espi_readl(priv, ESPI_CR2);
+ cr2.bits.srd = ESPI_CR2_SRD_RX_ENABLE;
+ espi_writel(priv, ESPI_CR2, cr2.val);
+}
+
+static void espi_enable_tx(struct espi_data *priv)
+{
+ espi_cr2_t cr2;
+ int cnt = espi_readl(priv, ESPI_TX_FBCAR);
+ cr2.val = espi_readl(priv, ESPI_CR2);
+
+ if (cnt && cr2.bits.mte != ESPI_CR2_MTE_TX_ENABLE) {
+ cr2.bits.mte = ESPI_CR2_MTE_TX_ENABLE;
+ espi_writel(priv, ESPI_CR2, cr2.val);
+ }
+}
+
+static void espi_set_cs(struct spi_device *spi, bool enable)
+{
+ struct espi_data *priv;
+ espi_cr2_t cr2;
+ priv = spi_master_get_devdata(spi->master);
+ cr2.val = espi_readl(priv, ESPI_CR2);
+ cr2.bits.sso = spi->chip_select;
+ espi_writel(priv, ESPI_CR2, cr2.val);
+}
+
+static void espi_writer(struct espi_data *priv)
+{
+ volatile uint8_t data;
+ uint32_t have_tx = espi_readl(priv, ESPI_TX_FBCAR);
+ uint32_t have_rx = espi_readl(priv, ESPI_RX_FBCAR);
+ uint32_t free = ESPI_FIFO_LEN - have_tx - have_rx;
+ uint32_t cnt = min(free, priv->txlen);
+
+ if (!cnt) {
+ return;
+ }
+
+ priv->txlen -= cnt;
+
+ /* fifo */
+ while (cnt--) {
+ if (priv->tx) {
+ data = *priv->tx++;
+ } else {
+ data = ESPI_DUMMY_DATA;
+ }
+ espi_writel(priv, ESPI_TX_FIFO, data);
+ }
+
+ espi_enable_tx(priv);
+}
+
+static void espi_reader(struct espi_data *priv)
+{
+ volatile uint8_t data;
+ uint32_t have = espi_readl(priv, ESPI_RX_FBCAR);
+ uint32_t cnt = min(have, priv->rxlen);
+
+ if (!cnt) {
+ return;
+ }
+
+ priv->rxlen -= cnt;
+
+ /* fifo */
+ while (cnt--) {
+ data = espi_readl(priv, ESPI_RX_FIFO);
+ if (priv->rx) {
+ *priv->rx++ = data;
+ }
+ }
+}
+
+static irqreturn_t espi_irq_handler(int irq, void *dev)
+{
+ espi_irq_t status;
+ struct spi_master *master = dev;
+ struct espi_data *priv = spi_master_get_devdata(master);
+ int ret = -1;
+
+ status.val = espi_readl(priv, ESPI_IVR);
+ espi_writel(priv, ESPI_ISR, status.val);
+
+ if (!status.val) {
+ dev_err(&master->dev, "irq_none\n");
+ return IRQ_NONE;
+ }
+
+ /* errors */
+ if (status.bits.tx_underrun) {
+ dev_err(&master->dev, "tx_underrun\n");
+ goto exit;
+ }
+ if (status.bits.tx_overrun) {
+ dev_err(&master->dev, "tx_overrun\n");
+ goto exit;
+ }
+ if (status.bits.rx_underrun) {
+ dev_err(&master->dev, "rx_underrun\n");
+ goto exit;
+ }
+ if (status.bits.rx_overrun) {
+ dev_err(&master->dev, "rx_overrun\n");
+ goto exit;
+ }
+ if (status.bits.rx_crc_error) {
+ dev_err(&master->dev, "rx_crc_error\n");
+ goto exit;
+ }
+ if (status.bits.alert) {
+ dev_err(&master->dev, "alert\n");
+ goto exit;
+ }
+
+ /* transfer */
+ espi_reader(priv);
+ espi_writer(priv);
+ ret = 0;
+
+exit:
+ if (ret) {
+ master->cur_msg->status = -EIO;
+ priv->txlen = 0;
+ priv->rxlen = 0;
+ espi_reset(priv);
+ }
+
+ /* tx done */
+ if (priv->txlen == 0) {
+ espi_irq_t mask;
+ mask.val = espi_readl(priv, ESPI_IMR);
+ mask.bits.tx_almost_empty = ESPI_IRQ_DISABLE;
+ espi_writel(priv, ESPI_IMR, mask.val);
+ }
+
+ /* rx_done */
+ if ((priv->txlen == 0) && (priv->rxlen == 0)) {
+ espi_writel(priv, ESPI_IMR, ESPI_IMR_DISABLE_ALL);
+ espi_writel(priv, ESPI_ISR, ESPI_ISR_CLEAR_ALL);
+ spi_finalize_current_transfer(master);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int espi_transfer_one(struct spi_master *master,
+ struct spi_device *spi,
+ struct spi_transfer *transfer)
+{
+ struct espi_data *priv = spi_master_get_devdata(spi->master);
+
+ priv->tx = (void*)transfer->tx_buf;
+ priv->rx = (void*)transfer->rx_buf;
+ priv->txlen = transfer->len;
+ priv->rxlen = transfer->len;
+
+ /* clk */
+ if (transfer->speed_hz && (transfer->speed_hz != spi->max_speed_hz)) {
+ clk_set_rate(priv->clk, transfer->speed_hz);
+ }
+
+ /* transfer */
+ espi_writer(priv);
+ espi_writel(priv, ESPI_IMR, ESPI_IMR_ENABLE_ALL);
+
+ return 1;
+}
+
+static int espi_setup(struct spi_device *spi)
+{
+ struct espi_data *priv = spi_master_get_devdata(spi->master);
+ espi_cr1_t cr1;
+ espi_cr2_t cr2;
+
+ espi_reset(priv);
+
+ /* irq */
+ espi_writel(priv, ESPI_IMR, ESPI_IMR_DISABLE_ALL);
+ espi_writel(priv, ESPI_ISR, ESPI_ISR_CLEAR_ALL);
+
+ /* control 1 */
+ cr1.val = espi_readl(priv, ESPI_CR1);
+ cr1.bits.scr = ESPI_CR1_SCR_NORESET;
+ cr1.bits.sce = ESPI_CR1_SCE_CORE_ENABLE;
+ cr1.bits.mss = ESPI_CR1_MSS_MODE_MASTER;
+ cr1.bits.cph = spi->mode & SPI_CPHA ? ESPI_CR1_CPH_CLKPHASE_EVEN :
+ ESPI_CR1_CPH_CLKPHASE_ODD;
+ cr1.bits.cpo = spi->mode & SPI_CPOL ? ESPI_CR1_CPO_CLKPOLAR_HIGH :
+ ESPI_CR1_CPO_CLKPOLAR_LOW;
+ espi_writel(priv, ESPI_CR1, cr1.val);
+
+ /* control 2 */
+ cr2.val = espi_readl(priv, ESPI_CR2);
+ cr2.bits.srd = ESPI_CR2_SRD_RX_DISABLE;
+ cr2.bits.mte = ESPI_CR2_MTE_TX_DISABLE;
+ cr2.bits.sri = ESPI_CR2_SRI_FIRST_RESIEV;
+ cr2.bits.mlb = spi->mode & SPI_LSB_FIRST ? ESPI_CR2_MLB_LSB :
+ ESPI_CR2_MLB_MSB;
+ espi_writel(priv, ESPI_CR2, cr2.val);
+
+ /* threshold */
+ espi_writel(priv, ESPI_TX_FAETR, ESPI_FIFO_LEN / 2);
+ espi_writel(priv, ESPI_RX_FAFTR, ESPI_FIFO_LEN / 2);
+
+ /* full duplex */
+ espi_enable_rx(priv);
+
+ /* clk */
+ if (spi->max_speed_hz != clk_get_rate(priv->clk)) {
+ clk_set_rate(priv->clk, spi->max_speed_hz);
+ spi->max_speed_hz = clk_get_rate(priv->clk);
+ }
+
+ return 0;
+}
+
+static int espi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &(pdev->dev);
+ struct espi_data *priv;
+ struct resource *mem;
+ int err;
+ struct spi_master *master;
+
+ /* init */
+ priv = devm_kzalloc(dev, sizeof(struct espi_data), GFP_KERNEL);
+ if (!priv) {
+ dev_err(dev, "no memory\n");
+ return -ENOMEM;
+ }
+ platform_set_drvdata(pdev, priv);
+
+ /* reg */
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ return -EINVAL;
+ }
+
+ priv->regs = devm_ioremap_resource(dev, mem);
+ if (IS_ERR(priv->regs)) {
+ dev_err(dev, "region map failed\n");
+ return PTR_ERR(priv->regs);
+ }
+
+ /* irq */
+ priv->irq = platform_get_irq(pdev, 0);
+ if (priv->irq < 0) {
+ dev_err(&pdev->dev, "no irq resource\n");
+ return priv->irq;
+ }
+
+ /* clk */
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk)) {
+ dev_err(dev, "could not get spi clock\n");
+ return PTR_ERR(priv->clk);
+ }
+ err = clk_prepare_enable(priv->clk);
+ if (err) {
+ dev_err(dev, "could not prepare spi clock\n");
+ return err;
+ }
+
+ /* master */
+ master = spi_alloc_master(dev, 0);
+ if (!master) {
+ return -ENOMEM;
+ }
+ priv->master = master;
+ master->num_chipselect = 8;
+ master->use_gpio_descriptors = true;
+ master->mode_bits = SPI_CPHA | SPI_CPOL;
+ master->dev.of_node = dev->of_node;
+ master->dev.fwnode = dev->fwnode;
+ master->transfer_one = espi_transfer_one;
+ master->set_cs = espi_set_cs;
+ master->setup = espi_setup;
+ master->bus_num = of_alias_get_id(master->dev.of_node, "spi");
+ master->dev.of_node = dev->of_node;
+ master->dev.fwnode = dev->fwnode;
+ master->flags = SPI_MASTER_GPIO_SS;
+
+ err = devm_request_irq(dev, priv->irq, espi_irq_handler,
+ IRQF_SHARED, pdev->name, master);
+ if (err) {
+ dev_err(&pdev->dev, "unable to request irq %d\n", priv->irq);
+ return err;
+ }
+
+ spi_master_set_devdata(master, priv);
+
+ err = devm_spi_register_master(dev, master);
+ if (err) {
+ dev_err(&master->dev, "problem registering spi master\n");
+ spi_master_put(master);
+ return err;
+ }
+
+ return 0;
+}
+
+static int espi_remove(struct platform_device *pdev)
+{
+ struct espi_data *priv = platform_get_drvdata(pdev);
+ espi_reset(priv);
+ clk_disable_unprepare(priv->clk);
+ return 0;
+}
+
+static const struct of_device_id baikal_espi_of_match[] = {
+ { .compatible = "baikal,bm1000-espi" },
+ { .compatible = "baikal,bs1000-espi" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, baikal_espi_of_match);
+
+static struct platform_driver baikal_espi_driver = {
+ .probe = espi_probe,
+ .remove = espi_remove,
+ .driver = {
+ .name = "baikal-espi",
+ .of_match_table = baikal_espi_of_match
+ }
+};
+module_platform_driver(baikal_espi_driver);
+
+MODULE_DESCRIPTION("Baikal eSPI controller driver");
+MODULE_LICENSE("GPL v2");
.quirks = DW_UART_QUIRK_SKIP_SET_RATE,
};
+static const struct dw8250_platform_data dw8250_baikal_bm1000_data = {
+ .usr_reg = DW_UART_USR,
+ .quirks = DW_UART_QUIRK_SKIP_SET_RATE,
+};
+
static const struct of_device_id dw8250_of_match[] = {
{ .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb },
+ { .compatible = "baikal,bm1000-uart", .data = &dw8250_baikal_bm1000_data },
{ .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data },
{ .compatible = "marvell,armada-38x-uart", .data = &dw8250_armada_38x_data },
{ .compatible = "renesas,rzn1-uart", .data = &dw8250_renesas_rzn1_data },
#include <sound/hdmi-codec.h>
+struct drm_bridge;
struct drm_display_info;
struct drm_display_mode;
struct drm_encoder;
void dw_hdmi_resume(struct dw_hdmi *hdmi);
+struct drm_bridge *dw_hdmi_get_bridge(struct dw_hdmi *hdmi);
+
void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense);
int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
int drm_panel_get_modes(struct drm_panel *panel, struct drm_connector *connector);
+struct drm_panel *fwnode_drm_find_panel(const struct fwnode_handle *fwnode);
+
#if defined(CONFIG_OF) && defined(CONFIG_DRM_PANEL)
struct drm_panel *of_drm_find_panel(const struct device_node *np);
int of_drm_get_panel_orientation(const struct device_node *np,
struct dw_dma_chip {
struct device *dev;
int id;
- int irq;
+ int *irq;
+ int irq_num;
void __iomem *regs;
struct clk *clk;
struct dw_dma *dw;
extern const struct pci_ecam_ops al_pcie_ops; /* Amazon Annapurna Labs PCIe */
extern const struct pci_ecam_ops tegra194_pcie_ops; /* Tegra194 PCIe */
extern const struct pci_ecam_ops loongson_pci_ecam_ops; /* Loongson PCIe */
+extern const struct pci_ecam_ops baikal_m_pcie_ecam_ops; /* Baikal-M Synopsys DesignWare PCIe */
+extern const struct pci_ecam_ops baikal_s_pcie_ecam_ops; /* Baikal-S Synopsys DesignWare PCIe */
#endif
#if IS_ENABLED(CONFIG_PCI_HOST_COMMON)
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/export.h>
+#include <linux/property.h>
#include <sound/core.h>
#include <sound/hdaudio.h>
#include <sound/hda_register.h>
snd_hdac_chip_writew(bus, RIRBWP, AZX_RIRBWP_RST);
/* set N=1, get RIRB response interrupt for new entry */
snd_hdac_chip_writew(bus, RINTCNT, 1);
- /* enable rirb dma and response irq */
- snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN);
+
+ if (bus->polling_mode &&
+ device_property_read_bool(bus->dev, "broken-response-irq"))
+ /* enable rirb dma without response irq */
+ snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN);
+ else
+ /* enable rirb dma and response irq */
+ snd_hdac_chip_writeb(bus, RIRBCTL,
+ AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN);
+
/* Accept unsolicited responses */
snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL);
spin_unlock_irq(&bus->reg_lock);
spin_lock_irq(&bus->reg_lock);
+ if (device_property_read_bool(bus->dev, "increment-codec-address"))
+ val = val + 0x10000000;
+
bus->last_cmd[azx_command_addr(val)] = val;
/* add command to corb */
To compile this driver as a module, choose M here: the module
will be called snd-hda-tegra.
+config SND_HDA_BAIKAL
+ tristate "Baikal HD Audio"
+ depends on ARCH_BAIKAL
+ select SND_HDA
+ select SND_HDA_ALIGNED_MMIO
+ help
+ Say Y here to support the HDA controller present in Baikal
+ SoCs
+
+ This options enables support for the HD Audio controller
+ present in Baikal SoCs.
+
if SND_HDA
config SND_HDA_HWDEP
# SPDX-License-Identifier: GPL-2.0
snd-hda-intel-objs := hda_intel.o
snd-hda-tegra-objs := hda_tegra.o
+snd-hda-baikal-objs := hda_baikal.o
snd-hda-codec-y := hda_bind.o hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o
snd-hda-codec-y += hda_controller.o
# when built in kernel
obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o
obj-$(CONFIG_SND_HDA_TEGRA) += snd-hda-tegra.o
+obj-$(CONFIG_SND_HDA_BAIKAL) += snd-hda-baikal.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+* Implementation of primary ALSA driver code base for Baikal-M HDA controller.
+*
+* /sound/pci/hda/hda_baikal.c
+*
+* Copyright (C) 2021-2022 Baikal Electronics, JSC
+* Author: Danila Sharikov <Danila.Sharikov@baikalelectronics.com>
+*
+* Based on:
+* hda_intel.c, Copyright(c) 2004 Intel Corporation. All rights reserved.
+* hda_tegra.c
+*/
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clocksource.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/string.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/hda_codec.h>
+
+#include "hda_controller.h"
+
+#ifdef CONFIG_PM
+static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
+module_param(power_save, bint, 0644);
+MODULE_PARM_DESC(power_save,
+ "Automatic power-saving timeout (in seconds, 0 = disable).");
+#else
+#define power_save 0
+#endif
+
+/* max number of SDs */
+#define NUM_CAPTURE_SD 4
+#define NUM_PLAYBACK_SD 4
+
+struct hda_baikal {
+ struct azx chip;
+ struct device *dev;
+ void __iomem *regs;
+ struct work_struct probe_work;
+ struct work_struct irq_pending_work;
+ unsigned int irq_pending_warned:1;
+};
+
+static void hda_baikal_probe_work(struct work_struct *work);
+
+static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev);
+static const struct hda_controller_ops hda_baikal_ops;
+
+/* calculate runtime delay from LPIB */
+static int azx_get_delay_from_lpib(struct azx *chip, struct azx_dev *azx_dev,
+ unsigned int pos)
+{
+ struct snd_pcm_substream *substream = azx_dev->core.substream;
+ int stream = substream->stream;
+ unsigned int lpib_pos = azx_get_pos_lpib(chip, azx_dev);
+ int delay;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ delay = pos - lpib_pos;
+ else
+ delay = lpib_pos - pos;
+ if (delay < 0) {
+ if (delay >= azx_dev->core.delay_negative_threshold)
+ delay = 0;
+ else
+ delay += azx_dev->core.bufsize;
+ }
+
+ if (delay >= azx_dev->core.period_bytes) {
+ dev_info(chip->card->dev,
+ "Unstable LPIB (%d >= %d); disabling LPIB delay counting\n",
+ delay, azx_dev->core.period_bytes);
+ delay = 0;
+ chip->driver_caps &= ~AZX_DCAPS_COUNT_LPIB_DELAY;
+ chip->get_delay[stream] = NULL;
+ }
+
+ return bytes_to_frames(substream->runtime, delay);
+}
+
+/* called from IRQ */
+static int azx_position_check(struct azx *chip, struct azx_dev *azx_dev)
+{
+ struct hda_baikal *hda = container_of(chip, struct hda_baikal, chip);
+ int ok;
+
+ ok = azx_position_ok(chip, azx_dev);
+
+ if (ok == 1) {
+ azx_dev->irq_pending = 0;
+ return ok;
+ } else if (ok == 0) {
+ /* bogus IRQ, process it later */
+ azx_dev->irq_pending = 1;
+ schedule_work(&hda->irq_pending_work);
+ }
+ return 0;
+}
+
+/*
+ * Check whether the current DMA position is acceptable for updating
+ * periods. Returns non-zero if it's OK.
+ *
+ * Many HD-audio controllers appear pretty inaccurate about
+ * the update-IRQ timing. The IRQ is issued before actually the
+ * data is processed. So, we need to process it afterwords in a
+ * workqueue.
+ */
+static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
+{
+ struct snd_pcm_substream *substream = azx_dev->core.substream;
+ int stream = substream->stream;
+ u32 wallclk;
+ unsigned int pos;
+
+ wallclk = azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk;
+ if (wallclk < (azx_dev->core.period_wallclk * 2) / 3)
+ return -1; /* bogus (too early) interrupt */
+
+ if (chip->get_position[stream])
+ pos = chip->get_position[stream](chip, azx_dev);
+ else { /* use the position buffer as default */
+ pos = azx_get_pos_posbuf(chip, azx_dev);
+ if (!pos || pos == (u32)-1) {
+ dev_info(chip->card->dev,
+ "Invalid position buffer, using LPIB read method instead.\n");
+ chip->get_position[stream] = azx_get_pos_lpib;
+ if (chip->get_position[0] == azx_get_pos_lpib &&
+ chip->get_position[1] == azx_get_pos_lpib)
+ azx_bus(chip)->use_posbuf = false;
+ pos = azx_get_pos_lpib(chip, azx_dev);
+ chip->get_delay[stream] = NULL;
+ } else {
+ chip->get_position[stream] = azx_get_pos_posbuf;
+ if (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)
+ chip->get_delay[stream] = azx_get_delay_from_lpib;
+ }
+ }
+
+ if (pos >= azx_dev->core.bufsize)
+ pos = 0;
+
+ if (WARN_ONCE(!azx_dev->core.period_bytes,
+ "hda-baikal: zero azx_dev->period_bytes"))
+ return -1; /* this shouldn't happen! */
+ if (wallclk < (azx_dev->core.period_wallclk * 5) / 4 &&
+ pos % azx_dev->core.period_bytes > azx_dev->core.period_bytes / 2)
+ /* NG - it's below the first next period boundary */
+ return chip->bdl_pos_adj ? 0 : -1;
+ azx_dev->core.start_wallclk += wallclk;
+ return 1; /* OK, it's fine */
+}
+
+/* The work for pending PCM period updates */
+static void azx_irq_pending_work(struct work_struct *work)
+{
+ struct hda_baikal *hda = container_of(work, struct hda_baikal, irq_pending_work);
+ struct azx *chip = &hda->chip;
+ struct hdac_bus *bus = azx_bus(chip);
+ struct hdac_stream *s;
+ int pending, ok;
+
+ if (!hda->irq_pending_warned) {
+ dev_info(chip->card->dev,
+ "IRQ timing workaround is activated for card #%d. Suggest a bigger bdl_pos_adj.\n",
+ chip->card->number);
+ hda->irq_pending_warned = 1;
+ }
+
+ for (;;) {
+ pending = 0;
+ spin_lock_irq(&bus->reg_lock);
+ list_for_each_entry(s, &bus->stream_list, list) {
+ struct azx_dev *azx_dev = stream_to_azx_dev(s);
+ if (!azx_dev->irq_pending ||
+ !s->substream ||
+ !s->running)
+ continue;
+ ok = azx_position_ok(chip, azx_dev);
+ if (ok > 0) {
+ azx_dev->irq_pending = 0;
+ spin_unlock(&bus->reg_lock);
+ snd_pcm_period_elapsed(s->substream);
+ spin_lock(&bus->reg_lock);
+ } else if (ok < 0) {
+ pending = 0; /* too early */
+ } else
+ pending++;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+ if (!pending)
+ return;
+ msleep(1);
+ }
+}
+
+/* clear irq_pending flags and assure no on-going workq */
+static void azx_clear_irq_pending(struct azx *chip)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ struct hdac_stream *s;
+
+ spin_lock_irq(&bus->reg_lock);
+ list_for_each_entry(s, &bus->stream_list, list) {
+ struct azx_dev *azx_dev = stream_to_azx_dev(s);
+ azx_dev->irq_pending = 0;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+}
+
+static int hda_baikal_dev_disconnect(struct snd_device *device)
+{
+ struct azx *chip = device->device_data;
+
+ chip->bus.shutdown = 1;
+ return 0;
+}
+
+static int hda_baikal_dev_free(struct snd_device *device)
+{
+ struct azx *chip = device->device_data;
+ struct hda_baikal *hda = container_of(chip, struct hda_baikal, chip);
+
+ cancel_work_sync(&hda->probe_work);
+ if (azx_bus(chip)->chip_init) {
+ azx_clear_irq_pending(chip);
+ azx_stop_all_streams(chip);
+ azx_stop_chip(chip);
+ }
+
+ azx_free_stream_pages(chip);
+ azx_free_streams(chip);
+ snd_hdac_bus_exit(azx_bus(chip));
+
+ return 0;
+}
+
+static int hda_baikal_init_chip(struct azx *chip, struct platform_device *pdev)
+{
+ struct hda_baikal *hda = container_of(chip, struct hda_baikal, chip);
+ struct hdac_bus *bus = azx_bus(chip);
+ struct device *dev = hda->dev;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hda->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(hda->regs))
+ return PTR_ERR(hda->regs);
+
+ bus->remap_addr = hda->regs;
+ bus->addr = res->start;
+
+ return 0;
+}
+
+static int hda_baikal_first_init(struct azx *chip, struct platform_device *pdev)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ struct snd_card *card = chip->card;
+ int err;
+ unsigned short gcap;
+ int irq_id = platform_get_irq(pdev, 0);
+ const char *sname, *drv_name = "Baikal-HDA";
+
+ err = hda_baikal_init_chip(chip, pdev);
+ if (err)
+ return err;
+
+ err = devm_request_irq(chip->card->dev, irq_id, azx_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, chip);
+ if (err) {
+ dev_err(chip->card->dev,
+ "unable to request IRQ %d, disabling device\n",
+ irq_id);
+ return err;
+ }
+ bus->irq = irq_id;
+
+ synchronize_irq(bus->irq);
+
+ gcap = azx_readw(chip, GCAP);
+ dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap);
+
+ /*
+ * Read number of streams from GCAP register instead of using
+ * hardcoded value
+ */
+ chip->capture_streams = (gcap >> 8) & 0x0f;
+ chip->playback_streams = (gcap >> 12) & 0x0f;
+ if (!chip->playback_streams && !chip->capture_streams) {
+ /* gcap didn't give any info, switching to old method */
+ chip->playback_streams = NUM_PLAYBACK_SD;
+ chip->capture_streams = NUM_CAPTURE_SD;
+ }
+ chip->capture_index_offset = 0;
+ chip->playback_index_offset = chip->capture_streams;
+ chip->num_streams = chip->playback_streams + chip->capture_streams;
+
+ /* initialize streams */
+ err = azx_init_streams(chip);
+ if (err < 0) {
+ dev_err(card->dev, "failed to initialize streams: %d\n", err);
+ return err;
+ }
+
+ err = azx_alloc_stream_pages(chip);
+ if (err < 0) {
+ dev_err(card->dev, "failed to allocate stream pages: %d\n",
+ err);
+ return err;
+ }
+
+ /* initialize chip */
+ azx_init_chip(chip, 1);
+
+ /* codec detection */
+ if (!bus->codec_mask) {
+ dev_err(card->dev, "no codecs found!\n");
+ return -ENODEV;
+ }
+
+ /* driver name */
+ strncpy(card->driver, drv_name, sizeof(card->driver));
+ /* shortname for card */
+ if (device_property_read_string(&pdev->dev, "baikal,model", &sname))
+ sname = drv_name;
+ if (strlen(sname) > sizeof(card->shortname))
+ dev_info(card->dev, "truncating shortname for card\n");
+ strncpy(card->shortname, sname, sizeof(card->shortname));
+
+ /* longname for card */
+ snprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx irq %i",
+ card->shortname, bus->addr, bus->irq);
+
+ return 0;
+}
+
+static int hda_baikal_create(struct snd_card *card,
+ unsigned int driver_caps,
+ struct hda_baikal *hda)
+{
+ static struct snd_device_ops ops = {
+ .dev_disconnect = hda_baikal_dev_disconnect,
+ .dev_free = hda_baikal_dev_free,
+ };
+ struct azx *chip;
+ int err;
+
+ chip = &hda->chip;
+
+ mutex_init(&chip->open_mutex);
+ chip->card = card;
+ chip->ops = &hda_baikal_ops;
+ chip->driver_caps = driver_caps;
+ chip->driver_type = driver_caps & 0xff;
+ chip->dev_index = 0;
+ INIT_LIST_HEAD(&chip->pcm_list);
+ INIT_WORK(&hda->irq_pending_work, azx_irq_pending_work);
+
+ chip->codec_probe_mask = 0x3; /* two codecs */
+
+ chip->single_cmd = false;
+ chip->snoop = true;
+
+ chip->get_position[0] = chip->get_position[1] = azx_get_pos_lpib;
+ chip->get_delay[0] = chip->get_delay[1] = azx_get_delay_from_lpib;
+
+ INIT_WORK(&hda->probe_work, hda_baikal_probe_work);
+
+ err = azx_bus_init(chip, NULL);
+ if (err < 0)
+ return err;
+
+ //chip->bus.needs_damn_long_delay = 1;
+ chip->bus.core.aligned_mmio = 1;
+
+ /* force polling mode, because RIRB interrupts don't working */
+ if (device_property_read_bool(hda->dev, "force-polling-mode"))
+ chip->bus.core.polling_mode = 1;
+ else
+ chip->bus.core.polling_mode = 0;
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0) {
+ dev_err(card->dev, "Error creating device\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int hda_baikal_probe(struct platform_device *pdev)
+{
+ const unsigned int driver_flags = AZX_DCAPS_PM_RUNTIME |
+ AZX_DCAPS_NO_64BIT |
+ AZX_DCAPS_4K_BDLE_BOUNDARY |
+ AZX_DCAPS_COUNT_LPIB_DELAY;
+ struct snd_card *card;
+ struct azx *chip;
+ struct hda_baikal *hda;
+ int err;
+
+ hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL);
+ if (!hda)
+ return -ENOMEM;
+ hda->dev = &pdev->dev;
+ chip = &hda->chip;
+
+ err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+ THIS_MODULE, 0, &card);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Error creating card!\n");
+ return err;
+ }
+
+ err = hda_baikal_create(card, driver_flags, hda);
+ if (err < 0)
+ goto out_free;
+ card->private_data = chip;
+
+ dev_set_drvdata(&pdev->dev, card);
+
+ pm_runtime_enable(hda->dev);
+ if (!azx_has_pm_runtime(chip))
+ pm_runtime_forbid(hda->dev);
+
+ schedule_work(&hda->probe_work);
+
+ return 0;
+
+out_free:
+ snd_card_free(card);
+ return err;
+}
+
+/* Probe the given codec address */
+static int baikal_probe_codec(struct azx *chip, int addr)
+{
+ unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) |
+ (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID;
+ struct hdac_bus *bus = azx_bus(chip);
+ int err;
+ unsigned int res = -1;
+
+ mutex_lock(&bus->cmd_mutex);
+ chip->probing = 1;
+ bus->ops->command(bus, cmd);
+ err = bus->ops->get_response(bus, addr, &res);
+ chip->probing = 0;
+ mutex_unlock(&bus->cmd_mutex);
+ if (err < 0 || res == -1)
+ return -EIO;
+ dev_dbg(chip->card->dev, "codec #%d probed OK\n", addr);
+ return 0;
+}
+
+/* Probe codecs */
+static int azx_baikal_probe_codecs(struct azx *chip, unsigned int max_slots)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ int c, codecs, err;
+
+ int probe_retry;
+
+ codecs = 0;
+ if (!max_slots)
+ max_slots = AZX_DEFAULT_CODECS;
+
+ for (c = 0; c < max_slots; c++) {
+ if ((bus->codec_mask & (1 << c)) & chip->codec_probe_mask) {
+ for (probe_retry = 0; probe_retry < 100; probe_retry++) {
+ if (baikal_probe_codec(chip, c) < 0) {
+ azx_stop_chip(chip);
+ azx_init_chip(chip, true);
+ continue;
+ } else {
+ dev_warn(chip->card->dev,
+ "Codec #%d probe success; retry count = %d\n",
+ c, probe_retry);
+ break;
+ }
+ bus->codec_mask &= ~(1 << c);
+ dev_warn(chip->card->dev,
+ "Codec #%d probe error; disabling it...\n", c);
+ }
+ }
+ }
+
+ /* Then create codec instances */
+ for (c = 0; c < max_slots; c++) {
+ if ((bus->codec_mask & (1 << c)) & chip->codec_probe_mask) {
+ struct hda_codec *codec;
+ err = snd_hda_codec_new(&chip->bus, chip->card, c, &codec);
+ if (err < 0)
+ continue;
+ codec->jackpoll_interval = chip->jackpoll_interval;
+ codec->beep_mode = chip->beep_mode;
+ codecs++;
+ }
+ }
+ if (!codecs) {
+ dev_err(chip->card->dev, "no codecs initialized\n");
+ return -ENXIO;
+ }
+ return 0;
+}
+
+static void hda_baikal_probe_work(struct work_struct *work)
+{
+ struct hda_baikal *hda = container_of(work, struct hda_baikal,
+ probe_work);
+ struct azx *chip = &hda->chip;
+ struct hdac_bus *bus = azx_bus(chip);
+ struct platform_device *pdev = to_platform_device(hda->dev);
+ int max_slots;
+ int err;
+
+ pm_runtime_get_sync(hda->dev);
+ err = hda_baikal_first_init(chip, pdev);
+ if (err < 0)
+ goto out_free;
+
+ switch (bus->codec_mask) {
+ case 0x1:
+ max_slots = 1;
+ break;
+ case 0x2:
+ max_slots = 2;
+ break;
+ case 0x3:
+ max_slots = 2;
+ break;
+ }
+
+ /* create codec instances */
+ if (device_property_read_bool(bus->dev, "cyclic-codec-probe"))
+ err = azx_baikal_probe_codecs(chip, max_slots);
+ else
+ err = azx_probe_codecs(chip, max_slots);
+
+ if (err < 0)
+ goto out_free;
+
+ err = azx_codec_configure(chip);
+ if (err < 0)
+ goto out_free;
+
+ err = snd_card_register(chip->card);
+ if (err < 0)
+ goto out_free;
+
+ chip->running = 1;
+
+ snd_hda_set_power_save(&chip->bus, power_save * 1000);
+
+ out_free:
+ pm_runtime_put(hda->dev);
+ return; /* no error return from async probe */
+}
+
+static int hda_baikal_remove(struct platform_device *pdev)
+{
+ int ret;
+
+ ret = snd_card_free(dev_get_drvdata(&pdev->dev));
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static void hda_baikal_shutdown(struct platform_device *pdev)
+{
+ struct snd_card *card = dev_get_drvdata(&pdev->dev);
+ struct azx *chip;
+
+ if (!card)
+ return;
+ chip = card->private_data;
+ if (chip && chip->running)
+ azx_stop_chip(chip);
+}
+
+static const struct hda_controller_ops hda_baikal_ops = {
+ .position_check = azx_position_check,
+};
+
+static const struct of_device_id hda_baikal_match[] = {
+ { .compatible = "baikal,bm1000-hda" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, hda_baikal_match);
+
+static struct platform_driver baikal_platform_hda = {
+ .driver = {
+ .name = "baikal-hda",
+ .of_match_table = hda_baikal_match,
+ },
+ .probe = hda_baikal_probe,
+ .remove = hda_baikal_remove,
+ .shutdown = hda_baikal_shutdown,
+};
+module_platform_driver(baikal_platform_hda);
+
+MODULE_DESCRIPTION("Baikal HDA bus driver");
+MODULE_LICENSE("GPL");
source "sound/soc/apple/Kconfig"
source "sound/soc/atmel/Kconfig"
source "sound/soc/au1x/Kconfig"
+source "sound/soc/baikal/Kconfig"
source "sound/soc/bcm/Kconfig"
source "sound/soc/cirrus/Kconfig"
source "sound/soc/dwc/Kconfig"
obj-$(CONFIG_SND_SOC) += amd/
obj-$(CONFIG_SND_SOC) += atmel/
obj-$(CONFIG_SND_SOC) += au1x/
+obj-$(CONFIG_SND_SOC) += baikal/
obj-$(CONFIG_SND_SOC) += bcm/
obj-$(CONFIG_SND_SOC) += cirrus/
obj-$(CONFIG_SND_SOC) += dwc/
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_BAIKAL_I2S
+ tristate "Baikal SoC I2S Device Driver"
+ depends on HAVE_CLK
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select SND_SIMPLE_CARD_UTILS if ACPI
+ help
+ Say Y or M if you want to add support for I2S driver for
+ Baikal SoC I2S device. The device supports up to
+ a maximum of 8 channels each for play and record.
+
+config SND_BAIKAL_PIO_PCM
+ bool "PCM PIO extension for Baikal SoC I2S driver"
+ depends on SND_BAIKAL_I2S
+ help
+ Say Y or N if you want to add a custom ALSA extension that registers
+ a PCM and uses PIO to transfer data.
+
+ This functionality is specially suited for I2S devices that don't have
+ DMA support.
+
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SND_BAIKAL_I2S) += baikal_i2s.o
+obj-$(CONFIG_SND_BAIKAL_I2S) += baikal_snd.o
+
+baikal_i2s-y := baikal-i2s.o
+baikal_snd-y := baikal-snd.o
+
+ifneq ($(CONFIG_ACPI),)
+baikal_snd-objs += baikal-snd-simple-acpi.o
+endif
+
+baikal_i2s-$(CONFIG_SND_BAIKAL_PIO_PCM) += baikal-pio-pcm.o
--- /dev/null
+/*
+ * ALSA SoC Synopsys I2S Audio Layer
+ *
+ * sound/soc/baikal/baikal-i2s.c
+ *
+ * Copyright (C) 2010 ST Microelectronics
+ * Rajeev Kumar <rajeevkumar.linux@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <sound/designware_i2s.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+#include "local.h"
+
+static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val)
+{
+ writel(val, io_base + reg);
+}
+
+static inline u32 i2s_read_reg(void __iomem *io_base, int reg)
+{
+ return readl(io_base + reg);
+}
+
+static inline void i2s_disable_channels(struct dw_i2s_dev *dev, u32 stream)
+{
+ u32 i = 0;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ for (i = 0; i < 4; i++)
+ i2s_write_reg(dev->i2s_base, TER(i), 0);
+ } else {
+ for (i = 0; i < 4; i++)
+ i2s_write_reg(dev->i2s_base, RER(i), 0);
+ }
+}
+
+static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream)
+{
+ u32 i = 0;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ for (i = 0; i < 4; i++)
+ i2s_read_reg(dev->i2s_base, TOR(i));
+ } else {
+ for (i = 0; i < 4; i++)
+ i2s_read_reg(dev->i2s_base, ROR(i));
+ }
+}
+
+static inline void i2s_disable_irqs(struct dw_i2s_dev *dev, u32 stream,
+ int chan_nr)
+{
+ u32 i, irq;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ for (i = 0; i < (chan_nr / 2); i++) {
+ irq = i2s_read_reg(dev->i2s_base, IMR(i));
+ i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30);
+ }
+ } else {
+ for (i = 0; i < (chan_nr / 2); i++) {
+ irq = i2s_read_reg(dev->i2s_base, IMR(i));
+ i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03);
+ }
+ }
+}
+
+static inline void i2s_enable_irqs(struct dw_i2s_dev *dev, u32 stream,
+ int chan_nr)
+{
+ u32 i, irq;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ for (i = 0; i < (chan_nr / 2); i++) {
+ irq = i2s_read_reg(dev->i2s_base, IMR(i));
+ i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30);
+ }
+ } else {
+ for (i = 0; i < (chan_nr / 2); i++) {
+ irq = i2s_read_reg(dev->i2s_base, IMR(i));
+ i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03);
+ }
+ }
+}
+
+static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
+{
+ struct dw_i2s_dev *dev = dev_id;
+ bool irq_valid = false;
+ u32 isr[4];
+ int i;
+
+ for (i = 0; i < 4; i++)
+ isr[i] = i2s_read_reg(dev->i2s_base, ISR(i));
+
+ i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK);
+ i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE);
+
+ for (i = 0; i < 4; i++) {
+ /*
+ * Check if TX fifo is empty. If empty fill FIFO with samples
+ * NOTE: Only two channels supported
+ */
+ if ((isr[i] & ISR_TXFE) && (i == 0) && dev->use_pio) {
+ dw_pcm_push_tx(dev);
+ irq_valid = true;
+ }
+
+ /*
+ * Data available. Retrieve samples from FIFO
+ * NOTE: Only two channels supported
+ */
+ if ((isr[i] & ISR_RXDA) && (i == 0) && dev->use_pio) {
+ dw_pcm_pop_rx(dev);
+ irq_valid = true;
+ }
+
+ /* Error Handling: TX */
+ if (isr[i] & ISR_TXFO) {
+ dev_err(dev->dev, "TX overrun (ch_id=%d)\n", i);
+ irq_valid = true;
+ }
+
+ /* Error Handling: RX
+ if (isr[i] & ISR_RXFO) {
+ dev_err(dev->dev, "RX overrun (ch_id=%d)\n", i);
+ irq_valid = true;
+ }
+ */
+ }
+
+ if (irq_valid)
+ return IRQ_HANDLED;
+ else
+ return IRQ_NONE;
+}
+
+static void i2s_start(struct dw_i2s_dev *dev,
+ struct snd_pcm_substream *substream)
+{
+ struct i2s_clk_config_data *config = &dev->config;
+
+ i2s_write_reg(dev->i2s_base, IER, 1);
+ i2s_enable_irqs(dev, substream->stream, config->chan_nr);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ i2s_write_reg(dev->i2s_base, ITER, 1);
+ else
+ i2s_write_reg(dev->i2s_base, IRER, 1);
+
+ i2s_write_reg(dev->i2s_base, CER, 1);
+}
+
+static void i2s_stop(struct dw_i2s_dev *dev,
+ struct snd_pcm_substream *substream)
+{
+
+ i2s_clear_irqs(dev, substream->stream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ i2s_write_reg(dev->i2s_base, ITER, 0);
+ else
+ i2s_write_reg(dev->i2s_base, IRER, 0);
+
+ i2s_disable_irqs(dev, substream->stream, 8);
+
+ if (!dev->active) {
+ i2s_write_reg(dev->i2s_base, CER, 0);
+ i2s_write_reg(dev->i2s_base, IER, 0);
+ }
+}
+
+static int dw_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+ union dw_i2s_snd_dma_data *dma_data = NULL;
+
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+
+ if (!(dev->capability & DWC_I2S_RECORD) &&
+ (substream->stream == SNDRV_PCM_STREAM_CAPTURE))
+ return -EINVAL;
+
+ if (!(dev->capability & DWC_I2S_PLAY) &&
+ (substream->stream == SNDRV_PCM_STREAM_PLAYBACK))
+ return -EINVAL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = &dev->play_dma_data;
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ dma_data = &dev->capture_dma_data;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data);
+ snd_soc_dai_set_sysclk(codec_dai, 0, dev->sysclk, SND_SOC_CLOCK_IN);
+
+ return 0;
+}
+
+static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
+{
+ u32 ch_reg;
+ struct i2s_clk_config_data *config = &dev->config;
+
+ i2s_disable_channels(dev, stream);
+
+ for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) {
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ i2s_write_reg(dev->i2s_base, TCR(ch_reg),
+ dev->xfer_resolution);
+ i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
+ dev->fifo_th - 1);
+ i2s_write_reg(dev->i2s_base, TER(ch_reg), 1);
+ } else {
+ i2s_write_reg(dev->i2s_base, RCR(ch_reg),
+ dev->xfer_resolution);
+ i2s_write_reg(dev->i2s_base, RFCR(ch_reg),
+ dev->fifo_th - 1);
+ i2s_write_reg(dev->i2s_base, RER(ch_reg), 1);
+ }
+
+ }
+}
+
+static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct i2s_clk_config_data *config = &dev->config;
+
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+
+ int ret;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ config->data_width = 16;
+ dev->ccr = 0x00;
+ dev->xfer_resolution = 0x02;
+ break;
+
+ case SNDRV_PCM_FORMAT_S24_LE:
+ config->data_width = 24;
+ dev->ccr = 0x08;
+ dev->xfer_resolution = 0x04;
+ break;
+
+ case SNDRV_PCM_FORMAT_S32_LE:
+ config->data_width = 32;
+ dev->ccr = 0x10;
+ dev->xfer_resolution = 0x05;
+ break;
+
+ default:
+ dev_err(dev->dev, "designware-i2s: unsupported PCM fmt");
+ return -EINVAL;
+ }
+
+ config->chan_nr = params_channels(params);
+
+ switch (config->chan_nr) {
+ case EIGHT_CHANNEL_SUPPORT:
+ case SIX_CHANNEL_SUPPORT:
+ case FOUR_CHANNEL_SUPPORT:
+ case TWO_CHANNEL_SUPPORT:
+ break;
+ default:
+ dev_err(dev->dev, "channel not supported\n");
+ return -EINVAL;
+ }
+
+ dw_i2s_config(dev, substream->stream);
+
+ i2s_write_reg(dev->i2s_base, CCR, dev->ccr);
+
+ config->sample_rate = params_rate(params);
+
+ if (dev->capability & DW_I2S_MASTER) {
+ if (dev->i2s_clk_cfg) {
+ ret = dev->i2s_clk_cfg(config);
+ if (ret < 0) {
+ dev_err(dev->dev, "runtime audio clk config fail\n");
+ return ret;
+ }
+ } else {
+ u32 bitclk = config->sample_rate *
+ config->data_width * 2;
+
+ ret = clk_set_rate(dev->clk, bitclk);
+ if (ret) {
+ dev_err(dev->dev, "Can't set I2S clock rate: %d\n",
+ ret);
+ return ret;
+ }
+ }
+ }
+
+ snd_soc_dai_set_pll(codec_dai, 0, 0, dev->sysclk, params_rate(params) * 256);
+
+ return 0;
+}
+
+static void dw_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+}
+
+static int dw_i2s_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ i2s_write_reg(dev->i2s_base, TXFFR, 1);
+ else
+ i2s_write_reg(dev->i2s_base, RXFFR, 1);
+
+ return 0;
+}
+
+static int dw_i2s_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ dev->active++;
+ i2s_start(dev, substream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ dev->active--;
+ i2s_stop(dev, substream);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int dw_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+ int ret = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FC:
+ if (dev->capability & DW_I2S_SLAVE)
+ ret = 0;
+ else
+ ret = -EINVAL;
+ break;
+ case SND_SOC_DAIFMT_BP_FP:
+ if (dev->capability & DW_I2S_MASTER)
+ ret = 0;
+ else
+ ret = -EINVAL;
+ break;
+ case SND_SOC_DAIFMT_BC_FP:
+ case SND_SOC_DAIFMT_BP_FC:
+ ret = -EINVAL;
+ break;
+ default:
+ dev_dbg(dev->dev, "dwc : Invalid clock provider format\n");
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static const struct snd_soc_dai_ops dw_i2s_dai_ops = {
+ .startup = dw_i2s_startup,
+ .shutdown = dw_i2s_shutdown,
+ .hw_params = dw_i2s_hw_params,
+ .prepare = dw_i2s_prepare,
+ .trigger = dw_i2s_trigger,
+ .set_fmt = dw_i2s_set_fmt,
+};
+
+#ifdef CONFIG_PM
+static int dw_i2s_runtime_suspend(struct device *dev)
+{
+ struct dw_i2s_dev *dw_dev = dev_get_drvdata(dev);
+
+ if (dw_dev->capability & DW_I2S_MASTER)
+ clk_disable(dw_dev->clk);
+ return 0;
+}
+
+static int dw_i2s_runtime_resume(struct device *dev)
+{
+ struct dw_i2s_dev *dw_dev = dev_get_drvdata(dev);
+ int ret;
+
+ if (dw_dev->capability & DW_I2S_MASTER) {
+ ret = clk_enable(dw_dev->clk);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int dw_i2s_suspend(struct snd_soc_component *component)
+{
+ struct dw_i2s_dev *dev = snd_soc_component_get_drvdata(component);
+
+ if (dev->capability & DW_I2S_MASTER)
+ clk_disable(dev->clk);
+ return 0;
+}
+
+static int dw_i2s_resume(struct snd_soc_component *component)
+{
+ struct dw_i2s_dev *dev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_dai *dai;
+ int stream, ret;
+
+ if (dev->capability & DW_I2S_MASTER) {
+ ret = clk_enable(dev->clk);
+ if (ret)
+ return ret;
+ }
+
+ for_each_component_dais(component, dai) {
+ for_each_pcm_streams(stream)
+ if (snd_soc_dai_stream_active(dai, stream))
+ dw_i2s_config(dev, stream);
+ }
+
+ return 0;
+}
+
+#else
+#define dw_i2s_suspend NULL
+#define dw_i2s_resume NULL
+#endif
+
+static const struct snd_soc_component_driver dw_i2s_component = {
+ .name = "dw-i2s",
+ .suspend = dw_i2s_suspend,
+ .resume = dw_i2s_resume,
+ .legacy_dai_naming = 1,
+};
+
+/*
+ * The following tables allow a direct lookup of various parameters
+ * defined in the I2S block's configuration in terms of sound system
+ * parameters. Each table is sized to the number of entries possible
+ * according to the number of configuration bits describing an I2S
+ * block parameter.
+ */
+
+/* Maximum bit resolution of a channel - not uniformly spaced */
+static const u32 fifo_width[COMP_MAX_WORDSIZE] = {
+ 12, 16, 20, 24, 32, 0, 0, 0
+};
+
+/* Width of (DMA) bus */
+static const u32 bus_widths[COMP_MAX_DATA_WIDTH] = {
+ DMA_SLAVE_BUSWIDTH_1_BYTE,
+ DMA_SLAVE_BUSWIDTH_2_BYTES,
+ DMA_SLAVE_BUSWIDTH_4_BYTES,
+ DMA_SLAVE_BUSWIDTH_UNDEFINED
+};
+
+/* PCM format to support channel resolution */
+static const u32 formats[COMP_MAX_WORDSIZE] = {
+ SNDRV_PCM_FMTBIT_S16_LE,
+ SNDRV_PCM_FMTBIT_S16_LE,
+ SNDRV_PCM_FMTBIT_S24_LE,
+ SNDRV_PCM_FMTBIT_S24_LE,
+ SNDRV_PCM_FMTBIT_S32_LE,
+ 0,
+ 0,
+ 0
+};
+
+static int dw_configure_dai(struct dw_i2s_dev *dev,
+ struct snd_soc_dai_driver *dw_i2s_dai,
+ unsigned int rates)
+{
+ /*
+ * Read component parameter registers to extract
+ * the I2S block's configuration.
+ */
+ u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
+ u32 comp2 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp2);
+ u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
+ u32 idx;
+
+ if (dev->capability & DWC_I2S_RECORD &&
+ dev->quirks & DW_I2S_QUIRK_COMP_PARAM1)
+ comp1 = comp1 & ~BIT(5);
+
+ if (dev->capability & DWC_I2S_PLAY &&
+ dev->quirks & DW_I2S_QUIRK_COMP_PARAM1)
+ comp1 = comp1 & ~BIT(6);
+
+ if (COMP1_TX_ENABLED(comp1)) {
+ dev_dbg(dev->dev, " designware: play supported\n");
+ idx = COMP1_TX_WORDSIZE_0(comp1);
+ if (WARN_ON(idx >= ARRAY_SIZE(formats)))
+ return -EINVAL;
+ if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE)
+ idx = 1;
+ dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM;
+ dw_i2s_dai->playback.channels_max =
+ 1 << (COMP1_TX_CHANNELS(comp1) + 1);
+ dw_i2s_dai->playback.formats = formats[idx];
+ dw_i2s_dai->playback.rates = rates;
+ }
+
+ if (COMP1_RX_ENABLED(comp1)) {
+ dev_dbg(dev->dev, "designware: record supported\n");
+ idx = COMP2_RX_WORDSIZE_0(comp2);
+ if (WARN_ON(idx >= ARRAY_SIZE(formats)))
+ return -EINVAL;
+ if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE)
+ idx = 1;
+ dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM;
+ dw_i2s_dai->capture.channels_max =
+ 1 << (COMP1_RX_CHANNELS(comp1) + 1);
+ dw_i2s_dai->capture.formats = formats[idx];
+ dw_i2s_dai->capture.rates = rates;
+ }
+
+ if (COMP1_MODE_EN(comp1)) {
+ dev_dbg(dev->dev, "designware: i2s master mode supported\n");
+ dev->capability |= DW_I2S_MASTER;
+ } else {
+ dev_dbg(dev->dev, "designware: i2s slave mode supported\n");
+ dev->capability |= DW_I2S_SLAVE;
+ }
+
+ dev->fifo_th = fifo_depth / 2;
+ return 0;
+}
+
+static int dw_configure_dai_by_pd(struct dw_i2s_dev *dev,
+ struct snd_soc_dai_driver *dw_i2s_dai,
+ struct resource *res,
+ const struct i2s_platform_data *pdata)
+{
+ u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
+ u32 idx = COMP1_APB_DATA_WIDTH(comp1);
+ int ret;
+
+ if (WARN_ON(idx >= ARRAY_SIZE(bus_widths)))
+ return -EINVAL;
+
+ ret = dw_configure_dai(dev, dw_i2s_dai, pdata->snd_rates);
+ if (ret < 0)
+ return ret;
+
+ if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE)
+ idx = 1;
+ /* Set DMA slaves info */
+ dev->play_dma_data.pd.data = pdata->play_dma_data;
+ dev->capture_dma_data.pd.data = pdata->capture_dma_data;
+ dev->play_dma_data.pd.addr = res->start + I2S_TXDMA;
+ dev->capture_dma_data.pd.addr = res->start + I2S_RXDMA;
+ dev->play_dma_data.pd.max_burst = 16;
+ dev->capture_dma_data.pd.max_burst = 16;
+ dev->play_dma_data.pd.addr_width = bus_widths[idx];
+ dev->capture_dma_data.pd.addr_width = bus_widths[idx];
+ dev->play_dma_data.pd.filter = pdata->filter;
+ dev->capture_dma_data.pd.filter = pdata->filter;
+
+ return 0;
+}
+
+static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev,
+ struct snd_soc_dai_driver *dw_i2s_dai,
+ struct resource *res)
+{
+ u32 comp1 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_1);
+ u32 comp2 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_2);
+ u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
+ u32 idx = COMP1_APB_DATA_WIDTH(comp1);
+ u32 idx2;
+ int ret;
+
+ if (WARN_ON(idx >= ARRAY_SIZE(bus_widths)))
+ return -EINVAL;
+
+ ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_192000);
+ if (ret < 0)
+ return ret;
+
+ if (COMP1_TX_ENABLED(comp1)) {
+ idx2 = COMP1_TX_WORDSIZE_0(comp1);
+
+ dev->capability |= DWC_I2S_PLAY;
+ dev->play_dma_data.dt.addr = res->start + I2S_TXDMA;
+ dev->play_dma_data.dt.addr_width = bus_widths[idx];
+ dev->play_dma_data.dt.fifo_size = fifo_depth *
+ (fifo_width[idx2]) >> 8;
+ dev->play_dma_data.dt.maxburst = 16;
+ }
+ if (COMP1_RX_ENABLED(comp1)) {
+ idx2 = COMP2_RX_WORDSIZE_0(comp2);
+
+ dev->capability |= DWC_I2S_RECORD;
+ dev->capture_dma_data.dt.addr = res->start + I2S_RXDMA;
+ dev->capture_dma_data.dt.addr_width = bus_widths[idx];
+ dev->capture_dma_data.dt.fifo_size = fifo_depth *
+ (fifo_width[idx2] >> 8);
+ dev->capture_dma_data.dt.maxburst = 16;
+ }
+
+ return 0;
+
+}
+
+static int dw_i2s_probe(struct platform_device *pdev)
+{
+ const struct i2s_platform_data *pdata = pdev->dev.platform_data;
+ struct dw_i2s_dev *dev;
+ struct resource *res;
+ int ret, irq, irq_num;
+ struct snd_soc_dai_driver *dw_i2s_dai;
+ const char *clk_id;
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ dw_i2s_dai = devm_kzalloc(&pdev->dev, sizeof(*dw_i2s_dai), GFP_KERNEL);
+ if (!dw_i2s_dai)
+ return -ENOMEM;
+
+ dw_i2s_dai->ops = &dw_i2s_dai_ops;
+
+ dev->i2s_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(dev->i2s_base))
+ return PTR_ERR(dev->i2s_base);
+
+ dev->dev = &pdev->dev;
+
+ irq_num = platform_irq_count(pdev);
+ if (irq_num < 0)
+ return irq_num; /* -EPROBE_DEFER */
+
+ if (!irq_num)
+ dev_err(&pdev->dev, "No irq found on device\n");
+
+ irq_num--;
+ do {
+ irq = platform_get_irq(pdev, irq_num);
+ if (irq >= 0) {
+ printk(KERN_INFO "%s Registered IRQ number %d %d\n",
+ __func__, irq, irq_num);
+ ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler,
+ 0, pdev->name, dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request irq\n");
+ return ret;
+ }
+ }
+ } while (irq_num--);
+
+ dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
+ dev->i2s_reg_comp2 = I2S_COMP_PARAM_2;
+ if (pdata) {
+ dev->capability = pdata->cap;
+ clk_id = NULL;
+ dev->quirks = pdata->quirks;
+ if (dev->quirks & DW_I2S_QUIRK_COMP_REG_OFFSET) {
+ dev->i2s_reg_comp1 = pdata->i2s_reg_comp1;
+ dev->i2s_reg_comp2 = pdata->i2s_reg_comp2;
+ }
+ ret = dw_configure_dai_by_pd(dev, dw_i2s_dai, res, pdata);
+ } else {
+ clk_id = "i2sclk";
+ ret = dw_configure_dai_by_dt(dev, dw_i2s_dai, res);
+ }
+ if (ret < 0)
+ return ret;
+
+ if (dev->capability & DW_I2S_MASTER) {
+ if (pdata) {
+ dev->i2s_clk_cfg = pdata->i2s_clk_cfg;
+ if (!dev->i2s_clk_cfg) {
+ dev_err(&pdev->dev, "no clock configure method\n");
+ return -ENODEV;
+ }
+ }
+ dev->clk = devm_clk_get(&pdev->dev, clk_id);
+
+ if (IS_ERR(dev->clk))
+ return PTR_ERR(dev->clk);
+
+ ret = clk_prepare_enable(dev->clk);
+ if (ret < 0)
+ return ret;
+ }
+
+ dev_set_drvdata(&pdev->dev, dev);
+ ret = devm_snd_soc_register_component(&pdev->dev, &dw_i2s_component,
+ dw_i2s_dai, 1);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "not able to register dai\n");
+ goto err_clk_disable;
+ }
+
+ if (!pdata) {
+ if (irq >= 0) {
+ ret = dw_pcm_register(pdev);
+ dev->use_pio = true;
+ } else {
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
+ 0);
+ dev->use_pio = false;
+ }
+
+ if (ret) {
+ dev_err(&pdev->dev, "could not register pcm: %d\n",
+ ret);
+ goto err_clk_disable;
+ }
+ }
+
+ ret = device_property_read_u32(&pdev->dev, "system-clock-frequency",
+ &dev->sysclk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "'system-clock-frequency': missing or invalid!\n");
+ return -EINVAL;
+ }
+
+ pm_runtime_enable(&pdev->dev);
+ return 0;
+
+err_clk_disable:
+ if (dev->capability & DW_I2S_MASTER)
+ clk_disable_unprepare(dev->clk);
+ return ret;
+}
+
+static int dw_i2s_remove(struct platform_device *pdev)
+{
+ struct dw_i2s_dev *dev = dev_get_drvdata(&pdev->dev);
+
+ if (dev->capability & DW_I2S_MASTER)
+ clk_disable_unprepare(dev->clk);
+
+ pm_runtime_disable(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id dw_i2s_of_match[] = {
+ { .compatible = "snps,designware-i2s", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, dw_i2s_of_match);
+#endif
+
+static const struct dev_pm_ops dwc_pm_ops = {
+ SET_RUNTIME_PM_OPS(dw_i2s_runtime_suspend, dw_i2s_runtime_resume, NULL)
+};
+
+static struct platform_driver dw_i2s_driver = {
+ .probe = dw_i2s_probe,
+ .remove = dw_i2s_remove,
+ .driver = {
+ .name = "designware-i2s",
+ .of_match_table = of_match_ptr(dw_i2s_of_match),
+ .pm = &dwc_pm_ops,
+ },
+};
+
+module_platform_driver(dw_i2s_driver);
+
+MODULE_AUTHOR("Rajeev Kumar <rajeevkumar.linux@gmail.com>");
+MODULE_DESCRIPTION("DESIGNWARE I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:designware_i2s");
--- /dev/null
+/*
+ * ALSA SoC Synopsys PIO PCM for I2S driver
+ *
+ * sound/soc/baikal/baikal-pio-pcm.c
+ *
+ * Copyright (C) 2016 Synopsys
+ * Jose Abreu <joabreu@synopsys.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/io.h>
+#include <linux/rcupdate.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "local.h"
+
+#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN)
+#define PERIOD_BYTES_MIN 4096
+#define PERIODS_MIN 2
+
+#define dw_pcm_tx_fn(sample_bits) \
+static unsigned int dw_pcm_tx_##sample_bits(struct dw_i2s_dev *dev, \
+ struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \
+ bool *period_elapsed) \
+{ \
+ const u##sample_bits (*p)[2] = (void *)runtime->dma_area; \
+ unsigned int period_pos = tx_ptr % runtime->period_size; \
+ int i; \
+\
+ for (i = 0; i < dev->fifo_th; i++) { \
+ iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \
+ iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \
+ period_pos++; \
+ if (++tx_ptr >= runtime->buffer_size) \
+ tx_ptr = 0; \
+ } \
+ *period_elapsed = period_pos >= runtime->period_size; \
+ return tx_ptr; \
+}
+
+#define dw_pcm_rx_fn(sample_bits) \
+static unsigned int dw_pcm_rx_##sample_bits(struct dw_i2s_dev *dev, \
+ struct snd_pcm_runtime *runtime, unsigned int rx_ptr, \
+ bool *period_elapsed) \
+{ \
+ u##sample_bits (*p)[2] = (void *)runtime->dma_area; \
+ unsigned int period_pos = rx_ptr % runtime->period_size; \
+ int i; \
+\
+ for (i = 0; i < dev->fifo_th; i++) { \
+ p[rx_ptr][0] = ioread32(dev->i2s_base + LRBR_LTHR(0)); \
+ p[rx_ptr][1] = ioread32(dev->i2s_base + RRBR_RTHR(0)); \
+ period_pos++; \
+ if (++rx_ptr >= runtime->buffer_size) \
+ rx_ptr = 0; \
+ } \
+ *period_elapsed = period_pos >= runtime->period_size; \
+ return rx_ptr; \
+}
+
+dw_pcm_tx_fn(16);
+dw_pcm_tx_fn(32);
+dw_pcm_rx_fn(16);
+dw_pcm_rx_fn(32);
+
+#undef dw_pcm_tx_fn
+#undef dw_pcm_rx_fn
+
+static const struct snd_pcm_hardware dw_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+ .rates = SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 32000,
+ .rate_max = 48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = BUFFER_BYTES_MAX,
+ .period_bytes_min = PERIOD_BYTES_MIN,
+ .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+ .periods_min = PERIODS_MIN,
+ .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+ .fifo_size = 16,
+};
+
+static void dw_pcm_transfer(struct dw_i2s_dev *dev, bool push)
+{
+ struct snd_pcm_substream *substream;
+ bool active, period_elapsed;
+
+ rcu_read_lock();
+ if (push)
+ substream = rcu_dereference(dev->tx_substream);
+ else
+ substream = rcu_dereference(dev->rx_substream);
+ active = substream && snd_pcm_running(substream);
+ if (active) {
+ unsigned int ptr;
+ unsigned int new_ptr;
+
+ if (push) {
+ ptr = READ_ONCE(dev->tx_ptr);
+ new_ptr = dev->tx_fn(dev, substream->runtime, ptr,
+ &period_elapsed);
+ cmpxchg(&dev->tx_ptr, ptr, new_ptr);
+ } else {
+ ptr = READ_ONCE(dev->rx_ptr);
+ new_ptr = dev->rx_fn(dev, substream->runtime, ptr,
+ &period_elapsed);
+ cmpxchg(&dev->rx_ptr, ptr, new_ptr);
+ }
+
+ if (period_elapsed)
+ snd_pcm_period_elapsed(substream);
+ }
+ rcu_read_unlock();
+}
+
+void dw_pcm_push_tx(struct dw_i2s_dev *dev)
+{
+ dw_pcm_transfer(dev, true);
+}
+
+void dw_pcm_pop_rx(struct dw_i2s_dev *dev)
+{
+ dw_pcm_transfer(dev, false);
+}
+
+static int dw_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+
+ snd_soc_set_runtime_hwparams(substream, &dw_pcm_hardware);
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ runtime->private_data = dev;
+
+ return 0;
+}
+
+static int dw_pcm_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ synchronize_rcu();
+ return 0;
+}
+
+static int dw_pcm_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct dw_i2s_dev *dev = runtime->private_data;
+
+ switch (params_channels(hw_params)) {
+ case 2:
+ break;
+ default:
+ dev_err(dev->dev, "invalid channels number\n");
+ return -EINVAL;
+ }
+
+ switch (params_format(hw_params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ dev->tx_fn = dw_pcm_tx_16;
+ dev->rx_fn = dw_pcm_rx_16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S32_LE:
+ dev->tx_fn = dw_pcm_tx_32;
+ dev->rx_fn = dw_pcm_rx_32;
+ break;
+ default:
+ dev_err(dev->dev, "invalid format\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int dw_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct dw_i2s_dev *dev = runtime->private_data;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ WRITE_ONCE(dev->tx_ptr, 0);
+ rcu_assign_pointer(dev->tx_substream, substream);
+ } else {
+ WRITE_ONCE(dev->rx_ptr, 0);
+ rcu_assign_pointer(dev->rx_substream, substream);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rcu_assign_pointer(dev->tx_substream, NULL);
+ else
+ rcu_assign_pointer(dev->rx_substream, NULL);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t dw_pcm_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct dw_i2s_dev *dev = runtime->private_data;
+ snd_pcm_uframes_t pos;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ pos = READ_ONCE(dev->tx_ptr);
+ else
+ pos = READ_ONCE(dev->rx_ptr);
+
+ return pos < runtime->buffer_size ? pos : 0;
+}
+
+static int dw_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ size_t size = dw_pcm_hardware.buffer_bytes_max;
+
+ snd_pcm_set_managed_buffer_all(rtd->pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ NULL, size, size);
+ return 0;
+}
+
+static const struct snd_soc_component_driver dw_pcm_component = {
+ .open = dw_pcm_open,
+ .close = dw_pcm_close,
+ .hw_params = dw_pcm_hw_params,
+ .trigger = dw_pcm_trigger,
+ .pointer = dw_pcm_pointer,
+ .pcm_construct = dw_pcm_new,
+};
+
+int dw_pcm_register(struct platform_device *pdev)
+{
+ return devm_snd_soc_register_component(&pdev->dev, &dw_pcm_component,
+ NULL, 0);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Baikal ASoC simple sound card support for ACPI
+ *
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ * Author: Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
+ */
+
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/simple_card_utils.h>
+#include <sound/soc.h>
+
+static struct snd_soc_dapm_widget baikal_widgets[] = {
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_HP("Headphones", NULL),
+ SND_SOC_DAPM_SPK("AUX Out", NULL)
+};
+
+static struct snd_soc_dapm_route baikal_routes[] = {
+ { .sink = "Headphones", .source = "RHP" },
+ { .sink = "Headphones", .source = "LHP" },
+ { .sink = "AUX Out", .source = "AUXOUT1" },
+ { .sink = "AUX Out", .source = "AUXOUT2" },
+ { .sink = "L2", .source = "Mic Jack" },
+ { .sink = "R2", .source = "Mic Jack" },
+ { .sink = "LAUX", .source = "Line In" },
+ { .sink = "RAUX", .source = "Line In" }
+};
+
+static int baikal_snd_soc_card_probe(struct snd_soc_card *card)
+{
+ struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ ret = asoc_simple_init_hp(card, &priv->hp_jack, NULL);
+ if (ret)
+ return ret;
+
+ return asoc_simple_init_mic(card, &priv->mic_jack, NULL);
+}
+
+static const struct snd_soc_ops baikal_snd_soc_ops = {
+ .startup = asoc_simple_startup,
+ .shutdown = asoc_simple_shutdown,
+ .hw_params = asoc_simple_hw_params
+};
+
+static int baikal_simple_card_parse(struct asoc_simple_priv *priv)
+{
+ struct device *dev = priv->snd_card.dev, *tmp;
+ struct snd_soc_dai_link *dai_link = priv->snd_card.dai_link;
+ struct fwnode_reference_args args;
+ int ret;
+
+ ret = fwnode_property_get_reference_args(dev->fwnode, "baikal,cpu-dai",
+ NULL, 0, 0, &args);
+ if (ret) {
+ dev_err(dev, "'baikal,cpu': missing or invalid!\n");
+ return -EINVAL;
+ }
+
+ tmp = bus_find_device_by_fwnode(&platform_bus_type, args.fwnode);
+ if (IS_ERR_OR_NULL(tmp))
+ return -ENODEV;
+ dai_link->cpus->name = dev_name(tmp);
+ dai_link->platforms->name = dev_name(tmp);
+ priv->dai_props->cpu_dai->sysclk = 100000000;
+
+ ret = fwnode_property_get_reference_args(dev->fwnode, "baikal,audio-codec",
+ NULL, 0, 0, &args);
+ if (ret) {
+ dev_err(dev, "'baikal,codec': missing or invalid!\n");
+ return -EINVAL;
+ }
+
+ tmp = bus_find_device_by_fwnode(&i2c_bus_type, args.fwnode);
+ if (IS_ERR_OR_NULL(tmp))
+ return -ENODEV;
+ dai_link->codecs->name = dev_name(tmp);
+
+ ret = device_property_read_string(dev, "baikal,codec-name",
+ &dai_link->codecs->dai_name);
+ if (ret) {
+ dev_err(dev, "'baikal,codec-name': missing or invalid!\n");
+ return -EINVAL;
+ }
+
+ dai_link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM;
+ dai_link->init = asoc_simple_dai_init;
+ dai_link->ops = &baikal_snd_soc_ops;
+
+ return asoc_simple_set_dailink_name(dev, dai_link, "%s-%s",
+ dai_link->cpus->name,
+ dai_link->codecs->dai_name);
+}
+
+static int baikal_simple_card_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct asoc_simple_priv *priv;
+ struct snd_soc_card *card;
+ struct link_info *li;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ card = &priv->snd_card;
+ card->owner = THIS_MODULE;
+ card->dev = dev;
+ card->driver_name = "baikal-simple-card";
+ card->name = "MITX-Sound-Card";
+ card->dapm_widgets = baikal_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(baikal_widgets);
+ card->dapm_routes = baikal_routes;
+ card->num_dapm_routes = ARRAY_SIZE(baikal_routes);
+ card->probe = baikal_snd_soc_card_probe;
+
+ li = kzalloc(sizeof(*li), GFP_KERNEL);
+ if (!li)
+ return -ENOMEM;
+ li->num[0].cpus = 1;
+ li->num[0].codecs = 1;
+ li->num[0].platforms = 1;
+ li->link = 1;
+
+ ret = asoc_simple_init_priv(priv, li);
+ kfree(li);
+ if (ret)
+ return ret;
+
+ ret = baikal_simple_card_parse(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "parse error\n");
+ return ret;
+ }
+
+ snd_soc_card_set_drvdata(card, priv);
+ asoc_simple_debug_info(priv);
+
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static const struct of_device_id baikal_simple_card_of_match[] = {
+ { .compatible = "baikal,simple-audio-card" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, baikal_simple_card_of_match);
+
+static struct platform_driver baikal_simple_card_driver = {
+ .driver = {
+ .name = "baikal-asoc-simple-card",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = baikal_simple_card_of_match
+ },
+ .probe = baikal_simple_card_probe
+};
+
+module_platform_driver(baikal_simple_card_driver);
+
+MODULE_ALIAS("platform:baikal-asoc-simple-card");
+MODULE_AUTHOR("Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal ASoC Simple Sound Card");
+MODULE_LICENSE("GPL");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2022 Baikal Electronics, JSC
+ * Author: Danila Sharikov <Danila.Sharikov@baikalelectronics.ru>
+ * Author: Ekaterina Skachko <Ekaterina.Skachko@baikalelectronics.ru>
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include "local.h"
+
+static struct snd_soc_dai_link snd_baikal_dai = {
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+};
+
+static struct snd_soc_card snd_baikal_card = {
+ .name = "Baikal-Snd-Card",
+ .owner = THIS_MODULE,
+ .dai_link = &snd_baikal_dai,
+ .num_links = 1,
+};
+
+static int snd_soc_baikal_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fwnode_handle *codec_node, *baikal_node;
+ struct snd_soc_dai_link_component *comp;
+ int err;
+
+ comp = devm_kzalloc(dev, 3 * sizeof(*comp), GFP_KERNEL);
+ if (!comp)
+ return -ENOMEM;
+
+ baikal_node = fwnode_find_reference(dev->fwnode, "baikal,cpu-dai", 0);
+ if (IS_ERR(baikal_node)) {
+ dev_err(dev,
+ "'baikal,cpu-dai': missing or invalid!\n");
+ return -EINVAL;
+ }
+
+ codec_node = fwnode_find_reference(dev->fwnode, "baikal,audio-codec", 0);
+ if (IS_ERR(codec_node)) {
+ dev_err(dev,
+ "'baikal,audio-codec': missing or invalid!\n");
+ return -EINVAL;
+ }
+
+ snd_baikal_card.dev = dev;
+ snd_baikal_dai.cpus = &comp[0];
+ snd_baikal_dai.codecs = &comp[1];
+ snd_baikal_dai.platforms = &comp[2];
+
+ err = device_property_read_string(dev, "baikal,dai-name",
+ &snd_baikal_dai.name);
+ if (err) {
+ dev_err(dev,
+ "'baikal,dai-name': missing or invalid!\n");
+ return -EINVAL;
+ }
+
+ err = device_property_read_string(dev, "baikal,stream-name",
+ &snd_baikal_dai.stream_name);
+ if (err) {
+ dev_err(dev,
+ "'baikal,stream-name': missing or invalid!\n");
+ return -EINVAL;
+ }
+
+ err = device_property_read_string(dev, "baikal,codec-name",
+ &snd_baikal_dai.codecs->dai_name);
+ if (err) {
+ dev_err(dev,
+ "'baikal,codec-name': missing or invalid!\n");
+ return -EINVAL;
+ }
+
+ snd_baikal_dai.num_cpus = 1;
+ snd_baikal_dai.num_codecs = 1;
+ snd_baikal_dai.num_platforms = 1;
+
+ if (is_of_node(baikal_node) && is_of_node(codec_node)) {
+ snd_baikal_dai.cpus->of_node = to_of_node(baikal_node);
+ snd_baikal_dai.codecs->of_node = to_of_node(codec_node);
+ snd_baikal_dai.platforms->of_node = to_of_node(baikal_node);
+ snd_soc_of_parse_card_name(&snd_baikal_card, "baikal,card-name");
+ } else {
+ struct device *tmp;
+
+ tmp = bus_find_device_by_fwnode(&platform_bus_type, baikal_node);
+ if (IS_ERR_OR_NULL(tmp))
+ return -ENODEV;
+ snd_baikal_dai.cpus->name = dev_name(tmp);
+ snd_baikal_dai.platforms->name = dev_name(tmp);
+
+ tmp = bus_find_device_by_fwnode(&i2c_bus_type, codec_node);
+ if (IS_ERR_OR_NULL(tmp))
+ return -ENODEV;
+ snd_baikal_dai.codecs->name = dev_name(tmp);
+ }
+
+ platform_set_drvdata(pdev, &snd_baikal_card);
+ snd_soc_card_set_drvdata(&snd_baikal_card, NULL);
+
+ err = snd_soc_register_card(&snd_baikal_card);
+ if (err)
+ dev_err(dev,
+ "snd_soc_register_card failed (%d)!\n", err);
+
+ return err;
+}
+
+static int snd_soc_baikal_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *snd_baikal_card = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_card(snd_baikal_card);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id snd_soc_baikal_of_match[] = {
+ { .compatible = "baikal,snd-soc-baikal", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, snd_soc_baikal_of_match);
+#endif
+
+
+static struct platform_driver snd_soc_baikal_driver = {
+ .probe = snd_soc_baikal_probe,
+ .remove = snd_soc_baikal_remove,
+ .driver = {
+ .name = "snd-soc-baikal",
+ .of_match_table = of_match_ptr(snd_soc_baikal_of_match),
+ },
+};
+
+module_platform_driver(snd_soc_baikal_driver);
+
+MODULE_AUTHOR("Danila Sharikov <Danila.Sharikov@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal-M SoC Sound Driver");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * Copyright (ST) 2012 Rajeev Kumar (rajeevkumar.linux@gmail.com)
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef __DESIGNWARE_LOCAL_H
+#define __DESIGNWARE_LOCAL_H
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/designware_i2s.h>
+
+/* common register for all channel */
+#define IER 0x000
+#define IRER 0x004
+#define ITER 0x008
+#define CER 0x00C
+#define CCR 0x010
+#define RXFFR 0x014
+#define TXFFR 0x018
+
+/* Interrupt status register fields */
+#define ISR_TXFO BIT(5)
+#define ISR_TXFE BIT(4)
+#define ISR_RXFO BIT(1)
+#define ISR_RXDA BIT(0)
+
+/* I2STxRxRegisters for all channels */
+#define LRBR_LTHR(x) (0x40 * x + 0x020)
+#define RRBR_RTHR(x) (0x40 * x + 0x024)
+#define RER(x) (0x40 * x + 0x028)
+#define TER(x) (0x40 * x + 0x02C)
+#define RCR(x) (0x40 * x + 0x030)
+#define TCR(x) (0x40 * x + 0x034)
+#define ISR(x) (0x40 * x + 0x038)
+#define IMR(x) (0x40 * x + 0x03C)
+#define ROR(x) (0x40 * x + 0x040)
+#define TOR(x) (0x40 * x + 0x044)
+#define RFCR(x) (0x40 * x + 0x048)
+#define TFCR(x) (0x40 * x + 0x04C)
+#define RFF(x) (0x40 * x + 0x050)
+#define TFF(x) (0x40 * x + 0x054)
+
+/* I2SCOMPRegisters */
+#define I2S_COMP_PARAM_2 0x01F0
+#define I2S_COMP_PARAM_1 0x01F4
+#define I2S_COMP_VERSION 0x01F8
+#define I2S_COMP_TYPE 0x01FC
+
+/*
+ * Component parameter register fields - define the I2S block's
+ * configuration.
+ */
+#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25)
+#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22)
+#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19)
+#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16)
+#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9)
+#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7)
+#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6)
+#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5)
+#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4)
+#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2)
+#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0)
+
+#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10)
+#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7)
+#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3)
+#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0)
+
+/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */
+#define COMP_MAX_WORDSIZE (1 << 3)
+#define COMP_MAX_DATA_WIDTH (1 << 2)
+
+#define MAX_CHANNEL_NUM 8
+#define MIN_CHANNEL_NUM 2
+
+union dw_i2s_snd_dma_data {
+ struct i2s_dma_data pd;
+ struct snd_dmaengine_dai_dma_data dt;
+};
+
+struct dw_i2s_dev {
+ void __iomem *i2s_base;
+ struct clk *clk;
+ int active;
+ unsigned int capability;
+ unsigned int quirks;
+ unsigned int i2s_reg_comp1;
+ unsigned int i2s_reg_comp2;
+ unsigned int sysclk;
+ struct device *dev;
+ u32 ccr;
+ u32 xfer_resolution;
+ u32 fifo_th;
+
+ /* data related to DMA transfers b/w i2s and DMAC */
+ union dw_i2s_snd_dma_data play_dma_data;
+ union dw_i2s_snd_dma_data capture_dma_data;
+ struct i2s_clk_config_data config;
+ int (*i2s_clk_cfg)(struct i2s_clk_config_data *config);
+
+ /* data related to PIO transfers */
+ bool use_pio;
+ struct snd_pcm_substream __rcu *tx_substream;
+ struct snd_pcm_substream __rcu *rx_substream;
+ unsigned int (*tx_fn)(struct dw_i2s_dev *dev,
+ struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
+ bool *period_elapsed);
+ unsigned int (*rx_fn)(struct dw_i2s_dev *dev,
+ struct snd_pcm_runtime *runtime, unsigned int rx_ptr,
+ bool *period_elapsed);
+ unsigned int tx_ptr;
+ unsigned int rx_ptr;
+};
+
+#if IS_ENABLED(CONFIG_SND_BAIKAL_PIO_PCM)
+void dw_pcm_push_tx(struct dw_i2s_dev *dev);
+void dw_pcm_pop_rx(struct dw_i2s_dev *dev);
+int dw_pcm_register(struct platform_device *pdev);
+#else
+static inline void dw_pcm_push_tx(struct dw_i2s_dev *dev) { }
+static inline void dw_pcm_pop_rx(struct dw_i2s_dev *dev) { }
+static inline int dw_pcm_register(struct platform_device *pdev)
+{
+ return -EINVAL;
+}
+#endif
+
+#endif
struct nau8822_pll *pll_param = &nau8822->pll;
int ret, fs;
+ /* Force PLL calculation
if (freq_in == pll_param->freq_in &&
freq_out == pll_param->freq_out)
return 0;
+ */
if (freq_out == 0) {
dev_dbg(component->dev, "PLL disabled\n");
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
return 0;
}
+static int aic3x_set_pll(struct snd_soc_dai *dai, int pll_id, int source,
+ unsigned int freq_in, unsigned int freq_out)
+{
+ return 0;
+}
+
static int aic3x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
{
.set_sysclk = aic3x_set_dai_sysclk,
.set_fmt = aic3x_set_dai_fmt,
.set_tdm_slot = aic3x_set_dai_tdm_slot,
+ .set_pll = aic3x_set_pll,
.no_capture_mute = 1,
};
static void aic3x_configure_ocmv(struct device *dev, struct aic3x_priv *aic3x)
{
- struct device_node *np = dev->of_node;
u32 value;
int dvdd, avdd;
- if (np && !of_property_read_u32(np, "ai3x-ocmv", &value)) {
+ if (!device_property_read_u32(dev, "ai3x-ocmv", &value)) {
/* OCMV setting is forced by DT */
if (value <= 3) {
aic3x->ocmv = value;
struct aic3x_pdata *pdata = dev->platform_data;
struct aic3x_priv *aic3x;
struct aic3x_setup_data *ai3x_setup;
- struct device_node *np = dev->of_node;
+ struct gpio_desc *desc;
int ret, i;
u32 value;
aic3x->gpio_reset = pdata->gpio_reset;
aic3x->setup = pdata->setup;
aic3x->micbias_vg = pdata->micbias_vg;
- } else if (np) {
+ } else if (fwnode_device_is_available(dev->fwnode)) {
ai3x_setup = devm_kzalloc(dev, sizeof(*ai3x_setup), GFP_KERNEL);
if (!ai3x_setup)
return -ENOMEM;
- ret = of_get_named_gpio(np, "reset-gpios", 0);
- if (ret >= 0) {
- aic3x->gpio_reset = ret;
- } else {
- ret = of_get_named_gpio(np, "gpio-reset", 0);
- if (ret > 0) {
+ desc = fwnode_gpiod_get_index(dev->fwnode, "reset", 0, 0, "tlv320aic3x reset");
+ if (!IS_ERR_OR_NULL(desc)) {
+ aic3x->gpio_reset = desc_to_gpio(desc);
+ } else if (is_of_node(dev->fwnode) && dev->of_node) {
+ aic3x->gpio_reset = of_get_named_gpio(dev->of_node, "gpio-reset", 0);
+ if (aic3x->gpio_reset > 0) {
+ ret = gpio_request(aic3x->gpio_reset, "tlv320aic3x reset");
+ if (ret != 0)
+ goto err;
dev_warn(dev, "Using deprecated property \"gpio-reset\", please update your DT");
- aic3x->gpio_reset = ret;
} else {
aic3x->gpio_reset = -1;
}
+ } else {
+ aic3x->gpio_reset = -1;
}
- if (of_property_read_u32_array(np, "ai3x-gpio-func",
+ if (device_property_read_u32_array(dev, "ai3x-gpio-func",
ai3x_setup->gpio_func, 2) >= 0) {
aic3x->setup = ai3x_setup;
}
- if (!of_property_read_u32(np, "ai3x-micbias-vg", &value)) {
+ if (!device_property_read_u32(dev, "ai3x-micbias-vg", &value)) {
switch (value) {
case 1 :
aic3x->micbias_vg = AIC3X_MICBIAS_2_0V;
if (gpio_is_valid(aic3x->gpio_reset) &&
!aic3x_is_shared_reset(aic3x)) {
- ret = gpio_request(aic3x->gpio_reset, "tlv320aic3x reset");
- if (ret != 0)
- goto err;
gpio_direction_output(aic3x->gpio_reset, 0);
+
+ udelay(100);
+ gpio_set_value(aic3x->gpio_reset, 1);
}
for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++)
irq_valid = true;
}
- /* Error Handling: TX */
+ /* Error Handling: TX
if (isr[i] & ISR_RXFO) {
dev_err(dev->dev, "RX overrun (ch_id=%d)\n", i);
irq_valid = true;
}
+ */
}
if (irq_valid)
struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
union dw_i2s_snd_dma_data *dma_data = NULL;
+
if (!(dev->capability & DWC_I2S_RECORD) &&
(substream->stream == SNDRV_PCM_STREAM_CAPTURE))
return -EINVAL;
dma_data = &dev->capture_dma_data;
snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data);
+ snd_soc_dai_set_sysclk(codec_dai, 0, 12000000, SND_SOC_CLOCK_IN);
return 0;
}
struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
int ret = 0;
- switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
- case SND_SOC_DAIFMT_BC_FC:
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
if (dev->capability & DW_I2S_SLAVE)
ret = 0;
else
ret = -EINVAL;
break;
- case SND_SOC_DAIFMT_BP_FP:
+ case SND_SOC_DAIFMT_CBS_CFS:
if (dev->capability & DW_I2S_MASTER)
ret = 0;
else
ret = -EINVAL;
break;
- case SND_SOC_DAIFMT_BC_FP:
- case SND_SOC_DAIFMT_BP_FC:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ case SND_SOC_DAIFMT_CBS_CFM:
ret = -EINVAL;
break;
default:
- dev_dbg(dev->dev, "dwc : Invalid clock provider format\n");
+ dev_dbg(dev->dev, "dwc : Invalid master/slave format\n");
ret = -EINVAL;
break;
}
.set_fmt = dw_i2s_set_fmt,
};
+static const struct snd_soc_component_driver dw_i2s_component = {
+ .name = "dw-i2s",
+};
+
#ifdef CONFIG_PM
static int dw_i2s_runtime_suspend(struct device *dev)
{
static int dw_i2s_runtime_resume(struct device *dev)
{
struct dw_i2s_dev *dw_dev = dev_get_drvdata(dev);
- int ret;
- if (dw_dev->capability & DW_I2S_MASTER) {
- ret = clk_enable(dw_dev->clk);
- if (ret)
- return ret;
- }
+ if (dw_dev->capability & DW_I2S_MASTER)
+ clk_enable(dw_dev->clk);
return 0;
}
-static int dw_i2s_suspend(struct snd_soc_component *component)
+static int dw_i2s_suspend(struct snd_soc_dai *dai)
{
- struct dw_i2s_dev *dev = snd_soc_component_get_drvdata(component);
+ struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
if (dev->capability & DW_I2S_MASTER)
clk_disable(dev->clk);
return 0;
}
-static int dw_i2s_resume(struct snd_soc_component *component)
+static int dw_i2s_resume(struct snd_soc_dai *dai)
{
- struct dw_i2s_dev *dev = snd_soc_component_get_drvdata(component);
- struct snd_soc_dai *dai;
- int stream, ret;
-
- if (dev->capability & DW_I2S_MASTER) {
- ret = clk_enable(dev->clk);
- if (ret)
- return ret;
- }
+ struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
- for_each_component_dais(component, dai) {
- for_each_pcm_streams(stream)
- if (snd_soc_dai_stream_active(dai, stream))
- dw_i2s_config(dev, stream);
- }
+ if (dev->capability & DW_I2S_MASTER)
+ clk_enable(dev->clk);
+ if (dai->playback_active)
+ dw_i2s_config(dev, SNDRV_PCM_STREAM_PLAYBACK);
+ if (dai->capture_active)
+ dw_i2s_config(dev, SNDRV_PCM_STREAM_CAPTURE);
return 0;
}
#define dw_i2s_resume NULL
#endif
-static const struct snd_soc_component_driver dw_i2s_component = {
- .name = "dw-i2s",
- .suspend = dw_i2s_suspend,
- .resume = dw_i2s_resume,
- .legacy_dai_naming = 1,
-};
-
/*
* The following tables allow a direct lookup of various parameters
* defined in the I2S block's configuration in terms of sound system
}
+
static int dw_i2s_probe(struct platform_device *pdev)
{
const struct i2s_platform_data *pdata = pdev->dev.platform_data;
struct dw_i2s_dev *dev;
struct resource *res;
- int ret, irq;
+ int ret, irq, irq_num;
struct snd_soc_dai_driver *dw_i2s_dai;
const char *clk_id;
return -ENOMEM;
dw_i2s_dai->ops = &dw_i2s_dai_ops;
+ dw_i2s_dai->suspend = dw_i2s_suspend;
+ dw_i2s_dai->resume = dw_i2s_resume;
- dev->i2s_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dev->i2s_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(dev->i2s_base))
return PTR_ERR(dev->i2s_base);
dev->dev = &pdev->dev;
- irq = platform_get_irq_optional(pdev, 0);
- if (irq >= 0) {
- ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0,
- pdev->name, dev);
- if (ret < 0) {
- dev_err(&pdev->dev, "failed to request irq\n");
- return ret;
+ irq_num = platform_irq_count(pdev);
+ if (irq_num < 0)
+ return irq_num; /* -EPROBE_DEFER */
+
+ if (!irq_num)
+ dev_err(&pdev->dev, "No irq found on device\n");
+
+ irq_num--;
+ do {
+ irq = platform_get_irq(pdev, irq_num);
+ if (irq >= 0) {
+ printk(KERN_INFO "%s Registered IRQ number %d %d\n",
+ __func__, irq, irq_num);
+ ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler,
+ 0, pdev->name, dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request irq\n");
+ return ret;
+ }
}
- }
+ } while (irq_num--);
dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
dev->i2s_reg_comp2 = I2S_COMP_PARAM_2;
}
pm_runtime_enable(&pdev->dev);
+
return 0;
+
err_clk_disable:
if (dev->capability & DW_I2S_MASTER)
clk_disable_unprepare(dev->clk);