From: Baikal Electronics Date: Wed, 5 Jul 2023 11:03:34 +0000 (+0300) Subject: SDK 6.1 X-Git-Tag: baikal/aarch64/sdk6.1 X-Git-Url: https://git.baikalelectronics.ru/sdk/?a=commitdiff_plain;h=b691a191ed2e98252b8f76f9540b7f54a109d5f6;p=kernel.git SDK 6.1 --- diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 76580b932e446..9acf5a43897ad 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -33,6 +33,26 @@ config ARCH_APPLE 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 GPIOLIB + select PINCTRL + select OF_GPIO + select I2C_BAIKAL_SMBUS + select GPIO_SYSFS + select GPIO_DWAPB + select GPIO_GENERIC + select STMMAC_ETH + select STMMAC_PLATFORM + select PHYLIB + select SPI_DESIGNWARE + select SPI_DW_MMIO + select DW_APB_TIMER + select DW_APB_TIMER_OF + select MULTIPLEXER + help + This enables support for Baikal Electronics SoC family + menuconfig ARCH_BCM bool "Broadcom SoC Support" @@ -354,4 +374,15 @@ config ARCH_ZYNQMP 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" diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile index 7b107fa7414bf..b02494b61441e 100644 --- a/arch/arm64/boot/dts/Makefile +++ b/arch/arm64/boot/dts/Makefile @@ -8,6 +8,7 @@ subdir-y += amlogic subdir-y += apm subdir-y += apple subdir-y += arm +subdir-y += baikal subdir-y += bitmain subdir-y += broadcom subdir-y += cavium diff --git a/arch/arm64/boot/dts/baikal/Makefile b/arch/arm64/boot/dts/baikal/Makefile new file mode 100644 index 0000000000000..17201158be656 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/Makefile @@ -0,0 +1,9 @@ +# 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 diff --git a/arch/arm64/boot/dts/baikal/bm1000-coresight.dtsi b/arch/arm64/boot/dts/baikal/bm1000-coresight.dtsi new file mode 100644 index 0000000000000..7c1bee14f0a05 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bm1000-coresight.dtsi @@ -0,0 +1,518 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device tree include file for BE-M1000 SoC Coresight subsystem + * Copyright (C) 2023 Baikal Electronics, JSC + */ + +#include + +#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 = ; + arm,trig-out-sigs = <6 7>; + arm,trig-out-types = ; + arm,cs-dev-assoc = <&stm0>; + }; + + trig-conns@3 { + reg = <3>; + arm,trig-out-sigs = <0 1>; + arm,trig-out-types = ; + 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 = ; + 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 = ; + 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 = ; + arm,trig-out-sigs = <0 1>; + arm,trig-out-types = ; + arm,cs-dev-assoc = <&etf0>; + }; + + trig-conns@1 { + reg = <1>; + arm,trig-in-sigs = <2 3>; + arm,trig-in-types = ; + arm,trig-out-sigs = <2 3>; + arm,trig-out-types = ; + arm,cs-dev-assoc = <&etf1>; + }; + + trig-conns@2 { + reg = <2>; + arm,trig-in-sigs = <4 5>; + arm,trig-in-types = ; + arm,trig-out-sigs = <4 5>; + arm,trig-out-types = ; + arm,cs-dev-assoc = <&etf2>; + }; + + trig-conns@3 { + reg = <3>; + arm,trig-in-sigs = <6 7>; + arm,trig-in-types = ; + arm,trig-out-sigs = <6 7>; + arm,trig-out-types = ; + 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 = ; + arm,trig-out-sigs = <0 1>; + arm,trig-out-types = ; + arm,cs-dev-assoc = <&etr>; + }; + + trig-conns@1 { + reg = <1>; + arm,trig-in-sigs = <2 3>; + arm,trig-in-types = ; + arm,trig-out-sigs = <2 3>; + arm,trig-out-types = ; + arm,cs-dev-assoc = <&etb>; + }; + + trig-conns@2 { + reg = <2>; + arm,trig-in-sigs = <4 5>; + arm,trig-in-types = ; + arm,trig-out-sigs = <4 5>; + arm,trig-out-types = ; + arm,cs-dev-assoc = <&etf>; + }; + + trig-conns@3 { + reg = <3>; + arm,trig-out-sigs = <6 7>; + arm,trig-out-types = ; + arm,cs-dev-assoc = <&tpiu>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/baikal/bm1000-dbm.dtsi b/arch/arm64/boot/dts/baikal/bm1000-dbm.dtsi new file mode 100644 index 0000000000000..c3d1e0ade3e80 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bm1000-dbm.dtsi @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device tree source for Baikal Electronics DBM boards + * Copyright (C) 2019-2023 Baikal Electronics, JSC + */ + +#include +#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 = ; + gpios = <&porta 18 GPIO_ACTIVE_LOW>; + debounce-interval = <50>; + }; + button-brightness-up { + label = "Brightness Up Button"; + linux,code = ; + gpios = <&porta 17 GPIO_ACTIVE_LOW>; + debounce-interval = <50>; + }; + button-brightness-toggle { + label = "Brightness Toggle Button"; + linux,code = ; + 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>; +}; diff --git a/arch/arm64/boot/dts/baikal/bm1000-dbm10.dts b/arch/arm64/boot/dts/baikal/bm1000-dbm10.dts new file mode 100644 index 0000000000000..e5751636f48f0 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bm1000-dbm10.dts @@ -0,0 +1,21 @@ +// 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>; + }; +}; diff --git a/arch/arm64/boot/dts/baikal/bm1000-dbm20.dts b/arch/arm64/boot/dts/baikal/bm1000-dbm20.dts new file mode 100644 index 0000000000000..12bcd1bd67ec1 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bm1000-dbm20.dts @@ -0,0 +1,21 @@ +// 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>; + }; +}; diff --git a/arch/arm64/boot/dts/baikal/bm1000-mbm.dtsi b/arch/arm64/boot/dts/baikal/bm1000-mbm.dtsi new file mode 100644 index 0000000000000..cdecc9ecaa3e6 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bm1000-mbm.dtsi @@ -0,0 +1,266 @@ +// 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"; +}; diff --git a/arch/arm64/boot/dts/baikal/bm1000-mbm10.dts b/arch/arm64/boot/dts/baikal/bm1000-mbm10.dts new file mode 100644 index 0000000000000..7dd80e5a24f6e --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bm1000-mbm10.dts @@ -0,0 +1,21 @@ +// 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>; + }; +}; diff --git a/arch/arm64/boot/dts/baikal/bm1000-mbm20.dts b/arch/arm64/boot/dts/baikal/bm1000-mbm20.dts new file mode 100644 index 0000000000000..e1b0f52004131 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bm1000-mbm20.dts @@ -0,0 +1,104 @@ +// 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 +#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 = ; + gpios = <&porta 18 GPIO_ACTIVE_LOW>; + debounce-interval = <50>; + }; + button-brightness-up { + label = "Brightness Up Button"; + linux,code = ; + gpios = <&porta 17 GPIO_ACTIVE_LOW>; + debounce-interval = <50>; + }; + button-brightness-toggle { + label = "Brightness Toggle Button"; + linux,code = ; + 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>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/baikal/bm1000-qemu-m.dts b/arch/arm64/boot/dts/baikal/bm1000-qemu-m.dts new file mode 100644 index 0000000000000..e87c0a62c4fe3 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bm1000-qemu-m.dts @@ -0,0 +1,161 @@ +// 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 */ + /*compatible = "auo,b133xtn01; /* 1366x768 */ + /*compatible = "auo,b101aw03"; /* 1024x600 */ + /*compatible = "innolux,g121x1-l03"; /* 1024x768 */ + /*compatible = "auo,b101ean01"; /* 1280x800 */ + + port { + lcd_panel: endpoint { + remote-endpoint = <&vdu_pads>; + }; + }; + }; + + /*panel_hdmi: panel { + compatible = "edt,et057090dhu"; + + port { + lcd_hdmi_panel: endpoint { + remote-endpoint = <&vdu_hdmi_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>; + }; + }; +}; diff --git a/arch/arm64/boot/dts/baikal/bm1000.dtsi b/arch/arm64/boot/dts/baikal/bm1000.dtsi new file mode 100644 index 0000000000000..493b7979591f6 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bm1000.dtsi @@ -0,0 +1,1409 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device tree include file for BE-M1000 SoC + * Copyright (C) 2017-2023 Baikal Electronics, JSC + */ + +#include +#include + +#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 = ; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = , + , + , + ; + }; + + 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>; + cmu-id = <0x28000000>; + 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>; + cmu-id = <0xc000000>; + 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>; + cmu-id = <0xa000000>; + 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>; + cmu-id = <0x26000000>; + 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>; + cmu-id = <0x20000000>; + 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>; + cmu-id = <0x20010000>; + 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>; + cmu-id = <0x2a000000>; + 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>; + cmu-id = <0x2c000000>; + 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>; + cmu-id = <0x30000000>; + 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>; + cmu-id = <0x30010000>; + 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 = , + , + , + , + , + , + , + , + , + , + ; + 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 = , + , + , + , + , + , + , + , + , + , + ; + 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 = , + , + , + , + , + , + , + , + , + , + ; + 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 = ; + }; + + ddr0: memory-controller@e200000 { + compatible = "baikal,bm1000-edac-mc"; + reg = <0x0 0x0e200000 0x0 0x10000>; + interrupts = , /* dfi_alert_err */ + , /* ecc_corrected_err */ + , /* ecc_uncorrected_err */ + , /* sbr_done */ + , /* ecc_corrected_err_fault */ + ; /* 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 = , /* dfi_alert_err */ + , /* ecc_corrected_err */ + , /* ecc_uncorrected_err */ + , /* sbr_done */ + , /* ecc_corrected_err_fault */ + ; /* 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 = ; + }; + }; + + spi0: spi@20210000 { + compatible = "snps,dw-apb-ssi"; + reg = <0x0 0x20210000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; + 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 = , /* rx_da */ + /* , rx_or */ + , /* tx_emp */ + ; /* 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + clocks = <&timer1_clk>; + clock-names = "timer"; + status = "disabled"; + }; + + timer2: timer@20290014 { + compatible = "snps,dw-apb-timer"; + reg = <0x0 0x20290014 0x0 0x14>; + interrupts = ; + clocks = <&timer2_clk>; + clock-names = "timer"; + status = "disabled"; + }; + + timer3: timer@20290028 { + compatible = "snps,dw-apb-timer"; + reg = <0x0 0x20290028 0x0 0x14>; + interrupts = ; + clocks = <&timer3_clk>; + clock-names = "timer"; + status = "disabled"; + }; + + timer4: timer@2029003c { + compatible = "snps,dw-apb-timer"; + reg = <0x0 0x2029003c 0x0 0x14>; + interrupts = ; + 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 = ; + 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 = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + + 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 = ; + 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 = , + ; + 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 = ; + 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 = , + , + ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = , + , + , + , + , + , + , + , + ; + 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 = ; + + 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 = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + 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 = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + 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 = ; + 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 = ; + 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 = , + ; + 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 = ; + 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 = ; + status = "disabled"; + }; + pvt_cluster1: pvt@c200000 { + compatible = "baikal,bm1000-pvt"; + reg = <0x0 0xc200000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_cluster2: pvt@a200000 { + compatible = "baikal,bm1000-pvt"; + reg = <0x0 0xa200000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_cluster3: pvt@26200000 { + compatible = "baikal,bm1000-pvt"; + reg = <0x0 0x26200000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_mali: pvt@2a060000 { + compatible = "baikal,bm1000-pvt"; + reg = <0x0 0x2a060000 0x0 0x1000>; + interrupts = ; + 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" diff --git a/arch/arm64/boot/dts/baikal/bs1000-clocks.dtsi b/arch/arm64/boot/dts/baikal/bs1000-clocks.dtsi new file mode 100644 index 0000000000000..23fb03bb8ec54 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bs1000-clocks.dtsi @@ -0,0 +1,625 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device tree include file for BE-S1000 SoC clocks + * Copyright (C) 2021-2022 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 = ; \ + 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 = ; \ + 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 = ; \ + 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 = ; \ + 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 = ; \ + 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 = ; \ + 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 = <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"; + }; + 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 = ; + #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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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_i2c1", // 7 + "scp_smbus_i2c2", // 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_i2c1", // 23 + "lsp_smbus_i2c2", // 24 + "lsp_smbus_i2c3", // 25 + "lsp_smbus_i2c4", // 26 + "lsp_smbus_i2c5", // 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 = ; + 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 + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/baikal/bs1000-coresight.dtsi b/arch/arm64/boot/dts/baikal/bs1000-coresight.dtsi new file mode 100644 index 0000000000000..9f80ee74da47c --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bs1000-coresight.dtsi @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device tree include file for BE-M1000 SoC Coresight subsystem + * Copyright (C) 2023 Baikal Electronics, JSC + */ + +#include + +#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>, <&cluster##cl##_cmu0 3>; \ + 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##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>, <&cluster##cl##_cmu0 3>; \ + clock-names = "apb_pclk", "atclk"; \ + \ + 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>, <&cluster##cl##_cmu0 3>; \ + clock-names = "apb_pclk", "atclk"; \ + \ + 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 = ; \ + arm,trig-out-sigs = <0 1>; \ + arm,trig-out-types = ; \ + 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 = ; + arm,trig-conn-name = "tsgen"; + }; + + trig-conns@1 { + reg = <1>; + arm,trig-in-sigs = <0 1 2>; + arm,trig-in-types = ; + arm,trig-out-sigs = <2 3>; + arm,trig-out-types = ; + 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 = ; + arm,trig-out-sigs = <5 6>; + arm,trig-out-types = ; + arm,cs-dev-assoc = <&etf>; + }; + + trig-conns@5 { + reg = <5>; + arm,trig-in-sigs = <8 9 10>; + arm,trig-in-types = ; + arm,trig-out-sigs = <7 8>; + arm,trig-out-types = ; + arm,cs-dev-assoc = <&etr>; + }; + + trig-conns@6 { + reg = <6>; + arm,trig-in-sigs = <11 12 13 14>; + arm,trig-in-types = ; + arm,trig-out-sigs = <9 10 11 12>; + arm,trig-out-types = ; + 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 = ; + arm,trig-conn-name = "cti_interrupt"; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/baikal/bs1000-dbs-mezzanine-espi.dtsi b/arch/arm64/boot/dts/baikal/bs1000-dbs-mezzanine-espi.dtsi new file mode 100644 index 0000000000000..0ef4c1c7d92f2 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bs1000-dbs-mezzanine-espi.dtsi @@ -0,0 +1,54 @@ +// 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"; + }; +}; diff --git a/arch/arm64/boot/dts/baikal/bs1000-dbs-mezzanine-qspi2.dtsi b/arch/arm64/boot/dts/baikal/bs1000-dbs-mezzanine-qspi2.dtsi new file mode 100644 index 0000000000000..929210b0692cf --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bs1000-dbs-mezzanine-qspi2.dtsi @@ -0,0 +1,32 @@ +// 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"; + }; +}; diff --git a/arch/arm64/boot/dts/baikal/bs1000-dbs-ov.dts b/arch/arm64/boot/dts/baikal/bs1000-dbs-ov.dts new file mode 100644 index 0000000000000..1232fc9817e22 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bs1000-dbs-ov.dts @@ -0,0 +1,79 @@ +// 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"; +}; diff --git a/arch/arm64/boot/dts/baikal/bs1000-dbs.dts b/arch/arm64/boot/dts/baikal/bs1000-dbs.dts new file mode 100644 index 0000000000000..2511abc00570e --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bs1000-dbs.dts @@ -0,0 +1,104 @@ +// 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"; +}; diff --git a/arch/arm64/boot/dts/baikal/bs1000-dbs.dtsi b/arch/arm64/boot/dts/baikal/bs1000-dbs.dtsi new file mode 100644 index 0000000000000..25b7001bd5383 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bs1000-dbs.dtsi @@ -0,0 +1,248 @@ +// 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"; +}; diff --git a/arch/arm64/boot/dts/baikal/bs1000-qemu-s.dts b/arch/arm64/boot/dts/baikal/bs1000-qemu-s.dts new file mode 100644 index 0000000000000..16ec325abf8d9 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bs1000-qemu-s.dts @@ -0,0 +1,123 @@ +// 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"; +}; diff --git a/arch/arm64/boot/dts/baikal/bs1000.dtsi b/arch/arm64/boot/dts/baikal/bs1000.dtsi new file mode 100644 index 0000000000000..abea7ad8fb9a7 --- /dev/null +++ b/arch/arm64/boot/dts/baikal/bs1000.dtsi @@ -0,0 +1,1168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device tree include file for BE-S1000 SoC + * Copyright (C) 2021-2023 Baikal Electronics, JSC + */ + +#include +#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 = ; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = , + , + , + ; + }; + + soc { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <2>; + ranges; + + ohci: usb@a00000 { + compatible = "generic-ohci"; + reg = <0x0 0xa00000 0x0 0x1000>; + interrupts = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + }; + }; + + i2c2: i2c@c90000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0xc90000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; + i2c-sda-hold-time-ns = <500>; + clock-frequency = <400000>; + clocks = <&i2c_clk>; + // clocks = <&cmu_sc_1 23>; // "lsp_smbus_i2c1" + clock-names = "ref"; + status = "disabled"; + }; + + i2c3: i2c@ca0000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0xca0000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; + i2c-sda-hold-time-ns = <500>; + clock-frequency = <400000>; + clocks = <&i2c_clk>; + // clocks = <&cmu_sc_1 24>; // "lsp_smbus_i2c2" + clock-names = "ref"; + status = "disabled"; + }; + + i2c4: i2c@cb0000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0xcb0000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; + i2c-sda-hold-time-ns = <500>; + clock-frequency = <400000>; + clocks = <&i2c_clk>; + // clocks = <&cmu_sc_1 25>; // "lsp_smbus_i2c3" + clock-names = "ref"; + status = "disabled"; + }; + + wdt: watchdog@ce0000 { + compatible = "snps,dw-wdt"; + reg = <0x0 0xce0000 0x0 0x1000>; + interrupts = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + 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 = ; + + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + 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 = , + ; + 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>; + status = "disabled"; + }; + + ddr0: memory-controller@53000000 { + compatible = "baikal,bs1000-edac-mc"; + reg = <0x0 0x53000000 0x0 0x10000>; + interrupts = , /* dfi_alert_err */ + , /* ecc_corrected_err */ + , /* ecc_uncorrected_err */ + , /* sbr_done */ + , /* ecc_corrected_err_fault */ + ; /* 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 = , /* dfi_alert_err */ + , /* ecc_corrected_err */ + , /* ecc_uncorrected_err */ + , /* sbr_done */ + , /* ecc_corrected_err_fault */ + ; /* 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 = , /* dfi_alert_err */ + , /* ecc_corrected_err */ + , /* ecc_uncorrected_err */ + , /* sbr_done */ + , /* ecc_corrected_err_fault */ + ; /* 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 = , /* dfi_alert_err */ + , /* ecc_corrected_err */ + , /* ecc_uncorrected_err */ + , /* sbr_done */ + , /* ecc_corrected_err_fault */ + ; /* 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 = , /* dfi_alert_err */ + , /* ecc_corrected_err */ + , /* ecc_uncorrected_err */ + , /* sbr_done */ + , /* ecc_corrected_err_fault */ + ; /* 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 = , /* dfi_alert_err */ + , /* ecc_corrected_err */ + , /* ecc_uncorrected_err */ + , /* sbr_done */ + , /* ecc_corrected_err_fault */ + ; /* 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 = ; + status = "disabled"; + }; + pvt_pcie1: pvt@3c030000 { + compatible = "baikal,bs1000-pvt"; + reg = <0x0 0x3c030000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_pcie2: pvt@44030000 { + compatible = "baikal,bs1000-pvt"; + reg = <0x0 0x44030000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_pcie3: pvt@48030000 { + compatible = "baikal,bs1000-pvt"; + reg = <0x0 0x48030000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_pcie4: pvt@4c030000 { + compatible = "baikal,bs1000-pvt"; + reg = <0x0 0x4c030000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_ddr0: pvt@50030000 { + compatible = "baikal,bs1000-pvt"; + reg = <0x0 0x50030000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_ddr1: pvt@54030000 { + compatible = "baikal,bs1000-pvt"; + reg = <0x0 0x54030000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_ddr2: pvt@58030000 { + compatible = "baikal,bs1000-pvt"; + reg = <0x0 0x58030000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_ddr3: pvt@60030000 { + compatible = "baikal,bs1000-pvt"; + reg = <0x0 0x60030000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_ddr4: pvt@64030000 { + compatible = "baikal,bs1000-pvt"; + reg = <0x0 0x64030000 0x0 0x1000>; + interrupts = ; + status = "disabled"; + }; + pvt_ddr5: pvt@68030000 { + compatible = "baikal,bs1000-pvt"; + reg = <0x0 0x68030000 0x0 0x1000>; + interrupts = ; + 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 = ; + 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 = ; + 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 = ; + }; + }; + + 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 = ; + }; + }; + + 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 = ; + }; + }; + + i2c5: i2c@cc0000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0xcc0000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; + i2c-sda-hold-time-ns = <500>; + clock-frequency = <400000>; + clocks = <&i2c_clk>; + // clocks = <&cmu_sc_1 26>; // "lsp_smbus_i2c4" + clock-names = "ref"; + }; + + i2c6: i2c@cd0000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0xcd0000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; + i2c-sda-hold-time-ns = <500>; + clock-frequency = <400000>; + clocks = <&i2c_clk>; + // clocks = <&cmu_sc_1 27>; // "lsp_smbus_i2c5" + clock-names = "ref"; + }; + + uart_s: serial@e00000 { + compatible = "snps,dw-apb-uart"; + reg = <0x0 0xe00000 0x0 0x100>; + interrupts = ; + 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" diff --git a/arch/arm64/configs/baikal_defconfig b/arch/arm64/configs/baikal_defconfig new file mode 100644 index 0000000000000..cdc30a1390861 --- /dev/null +++ b/arch/arm64/configs/baikal_defconfig @@ -0,0 +1,843 @@ +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_2067961 is not set +# CONFIG_ARM64_ERRATUM_2441009 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_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_TCINDEX=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_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_DWMAC_BAIKAL=y +CONFIG_STMMAC_ETH=y +CONFIG_MARVELL_PHY=y +CONFIG_MICREL_PHY=y +CONFIG_REALTEK_PHY=y +CONFIG_MDIO_BITBANG=y +CONFIG_MDIO_GPIO=m +CONFIG_MDIO_GPIO_BAIKAL=y +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_DESIGNWARE_SLAVE=y +CONFIG_I2C_DESIGNWARE_PLATFORM=y +CONFIG_SPI=y +CONFIG_SPI_BAIKAL_ESPI=y +CONFIG_SPI_DW_DMA=y +CONFIG_SPI_SPIDEV=y +CONFIG_GPIO_GENERIC_PLATFORM=y +CONFIG_GPIO_PCF857X=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_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_HDA=y +# CONFIG_SND_DESIGNWARE_I2S is not set +# CONFIG_SND_DESIGNWARE_PCM is not set +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_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_USER=y +CONFIG_LEDS_TRIGGERS=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_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 +CONFIG_SENSORS_BT1_PVT=y +CONFIG_SENSORS_BT1_PVT_ALARMS=n diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 0b6af3348e791..1df051fcdbb83 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -361,7 +361,7 @@ CONFIG_SMC91X=y CONFIG_SMSC911X=y CONFIG_SNI_AVE=y CONFIG_SNI_NETSEC=y -CONFIG_STMMAC_ETH=m +CONFIG_STMMAC_ETH=y CONFIG_DWMAC_TEGRA=m CONFIG_TI_K3_AM65_CPSW_NUSS=y CONFIG_QCOM_IPA=m diff --git a/drivers/acpi/pci_mcfg.c b/drivers/acpi/pci_mcfg.c index 860014b89b8eb..f627d0cb45663 100644 --- a/drivers/acpi/pci_mcfg.c +++ b/drivers/acpi/pci_mcfg.c @@ -171,6 +171,30 @@ static struct mcfg_fixup mcfg_quirks[] = { 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 diff --git a/drivers/ata/ahci_dwc.c b/drivers/ata/ahci_dwc.c index 8fb66860db318..a694e6c2dae4e 100644 --- a/drivers/ata/ahci_dwc.c +++ b/drivers/ata/ahci_dwc.c @@ -468,10 +468,15 @@ static struct ahci_dwc_plat_data ahci_bt1_plat = { .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); diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index e3ca0d058a256..5e86cc0f7a251 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -83,6 +83,7 @@ obj-y += analogbits/ 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/ diff --git a/drivers/clk/baikal/Makefile b/drivers/clk/baikal/Makefile new file mode 100644 index 0000000000000..dc94047aa9131 --- /dev/null +++ b/drivers/clk/baikal/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-y += clk-bm1000.o +obj-y += clk-bs1000.o diff --git a/drivers/clk/baikal/clk-bm1000.c b/drivers/clk/baikal/clk-bm1000.c new file mode 100644 index 0000000000000..278c264641df2 --- /dev/null +++ b/drivers/clk/baikal/clk-bm1000.c @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015-2023 Baikal Electronics, JSC + * Author: Ekaterina Skachko + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + uint32_t base; + unsigned int parent; + const char *name; + uint32_t is_clk_ch; +}; + +#define to_baikal_cmu(_hw) container_of(_hw, struct baikal_clk_cmu, hw) + +/* Pointer to the place on handling SMC CMU calls in monitor */ +#define BAIKAL_SMC_LCRU_ID 0x82000000 + +static int baikal_clk_enable(struct clk_hw *hw) +{ + struct arm_smccc_res res; + struct baikal_clk_cmu *pclk = to_baikal_cmu(hw); + uint32_t cmd; + + if (pclk->is_clk_ch) + cmd = CMU_CLK_CH_ENABLE; + else + cmd = CMU_PLL_ENABLE; + + /* If clock valid */ + arm_smccc_smc(BAIKAL_SMC_LCRU_ID, 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); + uint32_t cmd; + + if (pclk->is_clk_ch) + cmd = CMU_CLK_CH_DISABLE; + else + cmd = CMU_PLL_DISABLE; + + /* If clock valid */ + arm_smccc_smc(BAIKAL_SMC_LCRU_ID, 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); + uint32_t 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_LCRU_ID, 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); + uint32_t 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_LCRU_ID, 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); + uint32_t 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_LCRU_ID, 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); + uint32_t 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_LCRU_ID, 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 number, i = 0; + u32 rc, index; + u64 base; + struct property *prop; + const __be32 *p; + const char *clk_ch_name; + const char *parent_name; + + cmu = kmalloc(sizeof(struct baikal_clk_cmu *), GFP_KERNEL); + if (!cmu) { + pr_err("%s: could not allocate CMU clk\n", __func__); + kfree(cmu); + return -ENOMEM; + } + + of_property_read_string(node, "clock-output-names", &cmu->name); + of_property_read_u32(node, "clock-frequency", &cmu->parent); + if (of_property_read_u64(node, "reg", &base) == 0) + cmu->base = base; + else + of_property_read_u32(node, "cmu-id", &cmu->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(struct clk_onecell_data), GFP_KERNEL); + if (!clk_ch) { + pr_err("%s: could not allocate CMU clk channel\n", __func__); + 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) { + pr_err("%s: could not allocate CMU init structure \n", __func__); + 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(struct baikal_clk_cmu), 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[] = { "baikal_osc25" }; +const char *baikal_acpi_clk_osc27_str[] = { "baikal_osc27" }; + +static struct clk *baikal_acpi_clk_osc25; +static struct clk *baikal_acpi_clk_osc27; + +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, *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, size, i, index, 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); + + 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_cmu), 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].name = str; + cmu_ch[i].base = index; + cmu_ch[i].parent = cmu->base; + cmu_ch[i].is_clk_ch = 1; + 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 = "bm1000-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_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(baikal_acpi_clk_driver_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 "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bm1000-cmu"); diff --git a/drivers/clk/baikal/clk-bs1000.c b/drivers/clk/baikal/clk-bs1000.c new file mode 100644 index 0000000000000..967a9a52df913 --- /dev/null +++ b/drivers/clk/baikal/clk-bs1000.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021-2023 Baikal Electronics, JSC + * Author: Ekaterina Skachko + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BAIKAL_SMC_CLK (0x82000000 + 0x400) +#define BAIKAL_SMC_CLK_ROUND (BAIKAL_SMC_CLK + 0) +#define BAIKAL_SMC_CLK_SET (BAIKAL_SMC_CLK + 1) +#define BAIKAL_SMC_CLK_GET (BAIKAL_SMC_CLK + 2) +#define BAIKAL_SMC_CLK_ENABLE (BAIKAL_SMC_CLK + 3) +#define BAIKAL_SMC_CLK_DISABLE (BAIKAL_SMC_CLK + 4) +#define BAIKAL_SMC_CLK_IS_ENABLED (BAIKAL_SMC_CLK + 5) + +struct baikal_clk { + struct clk_hw hw; + uint32_t 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; + uint32_t 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 "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bs1000-cmu"); diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index 69a8742c0a7a3..de1917aca5ea1 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -32,6 +32,9 @@ static const struct of_device_id allowlist[] __initconst = { { .compatible = "arm,integrator-ap", }, { .compatible = "arm,integrator-cp", }, + { .compatible = "baikal,bm1000", }, + { .compatible = "baikal,bs1000", }, + { .compatible = "hisilicon,hi3660", }, { .compatible = "fsl,imx27", }, diff --git a/drivers/dma/dw/core.c b/drivers/dma/dw/core.c index 97ba3bfc10b13..39bf45553a2ea 100644 --- a/drivers/dma/dw/core.c +++ b/drivers/dma/dw/core.c @@ -1070,6 +1070,8 @@ int do_dma_probe(struct dw_dma_chip *chip) 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) @@ -1141,10 +1143,13 @@ int do_dma_probe(struct dw_dma_chip *chip) 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++) { @@ -1267,7 +1272,9 @@ int do_dma_probe(struct dw_dma_chip *chip) 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; @@ -1277,13 +1284,17 @@ int do_dma_remove(struct dw_dma_chip *chip) { 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, diff --git a/drivers/dma/dw/platform.c b/drivers/dma/dw/platform.c index 47f2292dba983..fcd4d086cf38a 100644 --- a/drivers/dma/dw/platform.c +++ b/drivers/dma/dw/platform.c @@ -29,6 +29,7 @@ static int dw_probe(struct platform_device *pdev) 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); @@ -43,9 +44,15 @@ static int dw_probe(struct platform_device *pdev) 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)) diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 34f5a092c99e7..e0738ba51430d 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -232,6 +232,7 @@ config DRM_SCHED source "drivers/gpu/drm/i2c/Kconfig" source "drivers/gpu/drm/arm/Kconfig" +source "drivers/gpu/drm/baikal/Kconfig" config DRM_RADEON tristate "ATI Radeon" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 0b283e46f28b8..db0456c3dd20f 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -148,3 +148,4 @@ obj-y += gud/ obj-$(CONFIG_DRM_HYPERV) += hyperv/ obj-y += solomon/ obj-$(CONFIG_DRM_SPRD) += sprd/ +obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal/ diff --git a/drivers/gpu/drm/baikal/Kconfig b/drivers/gpu/drm/baikal/Kconfig new file mode 100644 index 0000000000000..4a18055cd22fa --- /dev/null +++ b/drivers/gpu/drm/baikal/Kconfig @@ -0,0 +1,12 @@ +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. diff --git a/drivers/gpu/drm/baikal/Makefile b/drivers/gpu/drm/baikal/Makefile new file mode 100644 index 0000000000000..321d3be96b814 --- /dev/null +++ b/drivers/gpu/drm/baikal/Makefile @@ -0,0 +1,11 @@ +# 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 diff --git a/drivers/gpu/drm/baikal/baikal-hdmi.c b/drivers/gpu/drm/baikal/baikal-hdmi.c new file mode 100644 index 0000000000000..a532dc4d36346 --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal-hdmi.c @@ -0,0 +1,114 @@ +// 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 + * + */ + +#include +#include +#include + +#include + +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 dw_hdmi *hdmi; + hdmi = dw_hdmi_probe(pdev, &baikal_dw_hdmi_plat_data); + if (IS_ERR(hdmi)) { + return PTR_ERR(hdmi); + } else { + return 0; + } +} + +static int baikal_dw_hdmi_remove(struct platform_device *pdev) +{ + struct dw_hdmi *hdmi = platform_get_drvdata(pdev); + dw_hdmi_remove(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 "); +MODULE_DESCRIPTION("Baikal BE-M1000 SoC DesignWare HDMI 2.0 Tx + Gen2 PHY Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/baikal/baikal_vdu_backlight.c b/drivers/gpu/drm/baikal/baikal_vdu_backlight.c new file mode 100644 index 0000000000000..41ee86012fabc --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_backlight.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +/** + * baikal_vdu_backlight.c + * Implementation of backlight functions for + * Baikal Electronics BE-M1000 SoC's VDU + */ + +#include +#include +#include + +#include + +#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 + +int baikal_vdu_lvds_set_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t value) +{ + struct drm_encoder *encoder = connector->encoder; + + if (!encoder) + return -1; + + if (!strcmp(property->name, "scaling mode")) { + uint64_t curval; + switch (value) { + case DRM_MODE_SCALE_FULLSCREEN: + break; + case DRM_MODE_SCALE_NO_SCALE: + break; + case DRM_MODE_SCALE_ASPECT: + break; + default: + goto set_prop_error; + } + + if (drm_object_property_get_value(&connector->base, + property, + &curval)) + goto set_prop_error; + + if (curval == value) + goto set_prop_done; + + if (drm_object_property_set_value(&connector->base, + property, + value)) + goto set_prop_error; + + } else if (!strcmp(property->name, "backlight")) { + if (drm_object_property_set_value(&connector->base, + property, + value)) + goto set_prop_error; + else { + // TODO set backlight + } + } else if (!strcmp(property->name, "DPMS")) { + const struct drm_encoder_helper_funcs *hfuncs + = encoder->helper_private; + hfuncs->dpms(encoder, value); + } + +set_prop_done: + return 0; +set_prop_error: + return -1; +} + +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 device_node *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 = of_get_child_by_name(dev->of_node, "backlight"); + if (!node) + return 0; + + of_property_read_u32(node, "min-brightness-level", &min_brightness); + of_property_read_u32(node, "default-brightness-level", &dfl_brightness); + of_property_read_u32(node, "brightness-level-step", &brightness_step); + of_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: + of_node_put(node); + return ret; +} diff --git a/drivers/gpu/drm/baikal/baikal_vdu_connector.c b/drivers/gpu/drm/baikal/baikal_vdu_connector.c new file mode 100644 index 0000000000000..515eab5ade44a --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_connector.c @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2021 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + * Parts of this file were based on sources as follows: + * + * Copyright (c) 2006-2008 Intel Corporation + * Copyright (c) 2007 Dave Airlie + * Copyright (C) 2011 Texas Instruments + * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved. + */ + +/** + * baikal_vdu_connector.c + * Implementation of the connector functions for Baikal Electronics + * BE-M1000 SoC's VDU + */ + +#include +#include + +#include +#include +#include + +#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 void baikal_vdu_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static void baikal_vdu_connector_force(struct drm_connector *connector) +{ + struct baikal_vdu_private *priv = + connector_to_baikal_vdu_private(connector); + + 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 enum drm_connector_status baikal_vdu_connector_detect( + struct drm_connector *connector, bool force) +{ + struct baikal_vdu_private *priv = + connector_to_baikal_vdu_private(connector); + + return (priv->panel ? + connector_status_connected : + connector_status_disconnected); +} + +static int baikal_vdu_connector_helper_get_modes( + struct drm_connector *connector) +{ + struct baikal_vdu_private *priv = + connector_to_baikal_vdu_private(connector); + + if (!priv->panel) + return 0; + + return drm_panel_get_modes(priv->panel, connector); +} + +int baikal_vdu_lvds_set_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t value) +{ + struct drm_encoder *encoder = connector->encoder; + + if (!encoder) + return -1; + + if (!strcmp(property->name, "scaling mode")) { + uint64_t curval; + switch (value) { + case DRM_MODE_SCALE_FULLSCREEN: + break; + case DRM_MODE_SCALE_NO_SCALE: + break; + case DRM_MODE_SCALE_ASPECT: + break; + default: + goto set_prop_error; + } + + if (drm_object_property_get_value(&connector->base, + property, + &curval)) + goto set_prop_error; + + if (curval == value) + goto set_prop_done; + + if (drm_object_property_set_value(&connector->base, + property, + value)) + goto set_prop_error; + + } else if (!strcmp(property->name, "backlight")) { + if (drm_object_property_set_value(&connector->base, + property, + value)) + goto set_prop_error; + else { + // TBD set backlight + } + } else if (!strcmp(property->name, "DPMS")) { + const struct drm_encoder_helper_funcs *hfuncs + = encoder->helper_private; + hfuncs->dpms(encoder, value); + } + +set_prop_done: + return 0; +set_prop_error: + return -1; +} + +static void baikal_vdu_lvds_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + // TBD +} + +const struct drm_connector_funcs connector_funcs = { + //.dpms = drm_atomic_helper_connector_dpms, // TODO sort this out + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = baikal_vdu_connector_detect, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .set_property = baikal_vdu_lvds_set_property, + .destroy = baikal_vdu_connector_destroy, + .force = baikal_vdu_connector_force, +}; + +const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = baikal_vdu_connector_helper_get_modes, +}; + +static const struct drm_encoder_funcs encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static const struct drm_encoder_helper_funcs baikal_vdu_lvds_helper_funcs = { + .dpms = baikal_vdu_lvds_encoder_dpms, + //.prepare = TBD, + //.mode_set = TBD, + //.commit = TBD, +}; + +int baikal_vdu_lvds_connector_create(struct drm_device *drm) +{ + struct baikal_vdu_private *priv = drm_to_baikal_vdu_private(drm); + struct drm_connector *connector = &priv->connector; + struct drm_encoder *encoder = &priv->encoder; + + drm_connector_init(drm, connector, &connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_LVDS); + encoder->crtc = &priv->crtc; + encoder->possible_crtcs = BIT(drm_crtc_index(encoder->crtc)); + + drm_connector_attach_encoder(connector, encoder); + + drm_encoder_helper_add(encoder, &baikal_vdu_lvds_helper_funcs); + drm_connector_helper_add(connector, &connector_helper_funcs); + + drm_connector_register(connector); + + return 0; +} + +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_private *priv = drm_to_baikal_vdu_private(drm); + struct device *dev = drm->dev; + struct backlight_properties props; + struct input_handler *handler; + struct device_node *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 = of_get_child_by_name(dev->of_node, "backlight"); + if (!node) + return 0; + + of_property_read_u32(node, "min-brightness-level", &min_brightness); + of_property_read_u32(node, "default-brightness-level", &dfl_brightness); + of_property_read_u32(node, "brightness-level-step", &brightness_step); + of_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: + of_node_put(node); + return ret; +} diff --git a/drivers/gpu/drm/baikal/baikal_vdu_crtc.c b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c new file mode 100644 index 0000000000000..3d0576b667601 --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +/** + * baikal_vdu_crtc.c + * Implementation of the CRTC functions for Baikal Electronics BE-M1000 VDU driver + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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) { + of_property_read_string(priv->bridge->of_node, "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; +} diff --git a/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c new file mode 100644 index 0000000000000..1fda8c2173117 --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +#include +#include +#include +#include +#include + +#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); +} diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drm.h b/drivers/gpu/drm/baikal/baikal_vdu_drm.h new file mode 100644 index 0000000000000..70d7eb650c7d8 --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_drm.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +#ifndef __BAIKAL_VDU_DRM_H__ +#define __BAIKAL_VDU_DRM_H__ + +#include +#include +#include +#include +#include +#include + +/* Append new drm mode definition here, align with libdrm definition */ +#define DRM_MODE_SCALE_NO_SCALE 2 + +#define CRTC_HDMI 0 +#define CRTC_LVDS 1 + +#define connector_to_baikal_vdu(x) \ + container_of(x, struct baikal_vdu_private, connector) +#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; +}; + +/* 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); + +/* Connector Functions */ +int baikal_vdu_lvds_connector_create(struct drm_device *dev); + +/* 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__ */ diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drv.c b/drivers/gpu/drm/baikal/baikal_vdu_drv.c new file mode 100644 index 0000000000000..3c046c06b94d9 --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * Bugfixes by Alexey Sheplyakov + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "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_of_get_bridge(struct device *dev, + struct device_node *np, + u32 port, u32 endpoint) +{ + struct drm_bridge *bridge; + struct drm_panel *panel; + int ret; + + ret = drm_of_find_panel_or_bridge(np, port, endpoint, + &panel, &bridge); + 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; + + if (!(mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, priv->regs_name))) { + 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; + + if (!(priv->irq = platform_get_irq_byname(pdev, priv->irq_name))) { + 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_of_get_bridge(dev, dev->of_node, 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 = of_property_read_u32(pdev->dev.of_node, "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 "); +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"); diff --git a/drivers/gpu/drm/baikal/baikal_vdu_panel.c b/drivers/gpu/drm/baikal/baikal_vdu_panel.c new file mode 100644 index 0000000000000..2b69efc0bec6a --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_panel.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +#include +#include +#include +#include + +#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 enum drm_connector_status baikal_lvds_connector_detect( + struct drm_connector *connector, bool force) +{ + // TODO: refactor + return connector_status_connected; +} + +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); +} + +int baikal_lvds_set_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t value) +{ + struct drm_encoder *encoder = connector->encoder; + + if (!encoder) + return -1; + + if (!strcmp(property->name, "scaling mode")) { + uint64_t curval; + switch (value) { + case DRM_MODE_SCALE_FULLSCREEN: + break; + case DRM_MODE_SCALE_NO_SCALE: + break; + case DRM_MODE_SCALE_ASPECT: + break; + default: + goto set_prop_error; + } + + if (drm_object_property_get_value(&connector->base, + property, + &curval)) + goto set_prop_error; + + if (curval == value) + goto set_prop_done; + + if (drm_object_property_set_value(&connector->base, + property, + value)) + goto set_prop_error; + + } else if (!strcmp(property->name, "backlight")) { + if (drm_object_property_set_value(&connector->base, + property, + value)) + goto set_prop_error; + else { + // TBD set backlight + } + } else if (!strcmp(property->name, "DPMS")) { + const struct drm_encoder_helper_funcs *hfuncs + = encoder->helper_private; + hfuncs->dpms(encoder, value); + } + +set_prop_done: + return 0; +set_prop_error: + return -1; +} + +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, + .detect = baikal_lvds_connector_detect, + .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, + .set_property = baikal_lvds_set_property, +}; + +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_detach(struct drm_bridge *bridge) +{ + struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge); + + // TODO +} + +static void baikal_lvds_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge); + struct baikal_vdu_private *priv = panel_bridge->vdu; + + baikal_vdu_switch_on(priv); + 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); + struct baikal_vdu_private *priv = panel_bridge->vdu; + + drm_panel_unprepare(panel_bridge->panel); + baikal_vdu_switch_off(priv); +} + +static const struct drm_bridge_funcs baikal_lvds_bridge_funcs = { + .attach = baikal_lvds_bridge_attach, + .detach = baikal_lvds_bridge_detach, + .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); +} diff --git a/drivers/gpu/drm/baikal/baikal_vdu_plane.c b/drivers/gpu/drm/baikal/baikal_vdu_plane.c new file mode 100644 index 0000000000000..de704c496b351 --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_plane.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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, +// .prepare_fb = drm_gem_plane_helper_prepare_fb, // TODO +}; + +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; +} diff --git a/drivers/gpu/drm/baikal/baikal_vdu_regs.h b/drivers/gpu/drm/baikal/baikal_vdu_regs.h new file mode 100644 index 0000000000000..d48ea8d12051f --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_regs.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +#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__ */ diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig index 15fc182d05ef0..61a8542708c71 100644 --- a/drivers/gpu/drm/bridge/synopsys/Kconfig +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig @@ -51,3 +51,19 @@ config DRM_DW_MIPI_DSI 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. diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile index ce715562e9e52..b5285a4261d8b 100644 --- a/drivers/gpu/drm/bridge/synopsys/Makefile +++ b/drivers/gpu/drm/bridge/synopsys/Makefile @@ -1,6 +1,7 @@ # 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 diff --git a/drivers/gpu/drm/bridge/synopsys/baikal-hdmi-ahb-audio.c b/drivers/gpu/drm/bridge/synopsys/baikal-hdmi-ahb-audio.c new file mode 100644 index 0000000000000..c1c71a8d7abdb --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/baikal-hdmi-ahb-audio.c @@ -0,0 +1,683 @@ +// 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 + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Synopsis Designware HDMI AHB ALSA interface"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index d3bccc8176c51..b97db3ccbe7d0 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -419,7 +419,6 @@ config SENSORS_ATXP1 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 diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c index 21ab172774ec5..691aed96d80f1 100644 --- a/drivers/hwmon/bt1-pvt.c +++ b/drivers/hwmon/bt1-pvt.c @@ -66,25 +66,49 @@ static const struct pvt_sensor_info pvt_info[] = { * 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} + } } }; @@ -98,29 +122,93 @@ static const struct polynomial poly_N_to_temp = { * 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; + + arm_smccc_smc(BAIKAL_SMC_PVT_ID, 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; +} - old = readl_relaxed(reg); - writel((old & ~mask) | (data & mask), reg); +static uint32_t readl_pvt(uint32_t pvt_base, uint32_t offset) +{ + struct arm_smccc_res res; + + arm_smccc_smc(BAIKAL_SMC_PVT_ID, 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; } @@ -136,9 +224,8 @@ static inline void pvt_set_mode(struct pvt_hwmon *pvt, u32 mode) 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); } @@ -155,8 +242,8 @@ static inline void pvt_set_trim(struct pvt_hwmon *pvt, u32 trim) 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); } @@ -164,9 +251,9 @@ static inline void pvt_set_tout(struct pvt_hwmon *pvt, u32 tout) { 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); } /* @@ -213,7 +300,7 @@ static irqreturn_t pvt_soft_isr(int irq, void *data) * 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. @@ -236,14 +323,14 @@ static irqreturn_t pvt_soft_isr(int irq, void *data) */ 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); @@ -293,16 +380,16 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, 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; } @@ -313,17 +400,17 @@ static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, 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; } @@ -336,10 +423,10 @@ static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, 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. */ @@ -348,7 +435,7 @@ static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, 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); @@ -361,7 +448,7 @@ static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, 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); @@ -415,21 +502,27 @@ static irqreturn_t pvt_hard_isr(int irq, void *data) * 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); @@ -454,7 +547,7 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, { struct pvt_cache *cache = &pvt->cache[type]; unsigned long timeout; - u32 data; + long data; int ret; /* @@ -474,8 +567,8 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, * 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 @@ -486,21 +579,23 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, 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; } @@ -613,7 +708,7 @@ static int pvt_read_trim(struct pvt_hwmon *pvt, long *val) { 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; @@ -900,11 +995,56 @@ static int pvt_request_regs(struct pvt_hwmon *pvt) } 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) { @@ -915,24 +1055,23 @@ 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; @@ -957,21 +1096,21 @@ static int pvt_check_pwr(struct pvt_hwmon *pvt) * 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; } @@ -992,10 +1131,10 @@ static int pvt_init_iface(struct pvt_hwmon *pvt) * 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); @@ -1079,8 +1218,8 @@ static void pvt_disable_iface(void *data) 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); } @@ -1102,8 +1241,8 @@ static int pvt_enable_iface(struct pvt_hwmon *pvt) * 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; @@ -1135,15 +1274,15 @@ static int pvt_probe(struct platform_device *pdev) 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; @@ -1155,11 +1294,16 @@ static int pvt_probe(struct platform_device *pdev) 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); diff --git a/drivers/hwmon/bt1-pvt.h b/drivers/hwmon/bt1-pvt.h index 93b8dd5e7c944..9f48398dc5d7f 100644 --- a/drivers/hwmon/bt1-pvt.h +++ b/drivers/hwmon/bt1-pvt.h @@ -14,6 +14,43 @@ #include #include +#define BAIKAL_SMC_PVT_ID 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) @@ -98,7 +135,7 @@ #if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) # define PVT_TOUT_DEF 60000 #else -# define PVT_TOUT_DEF 0 +# define PVT_TOUT_DEF 1000 #endif /* @@ -209,6 +246,8 @@ struct pvt_hwmon { struct device *hwmon; void __iomem *regs; + int base; + enum chips type_cpu; int irq; struct clk_bulk_data clks[PVT_CLOCK_NUM]; @@ -239,9 +278,11 @@ struct pvt_poly_term { * @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__ */ diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index e50f9603d189e..facd3f5bd44fd 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -452,6 +452,9 @@ config I2C_AXXIA 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 diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index e73cdb1d2b5a8..71d4eb16382ee 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -45,6 +45,7 @@ ifeq ($(CONFIG_I2C_AT91_SLAVE_EXPERIMENTAL),y) 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 diff --git a/drivers/i2c/busses/i2c-baikal-smbus.c b/drivers/i2c/busses/i2c-baikal-smbus.c new file mode 100644 index 0000000000000..ed0550da583d5 --- /dev/null +++ b/drivers/i2c/busses/i2c-baikal-smbus.c @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SMBus controller driver for Baikal SoC + * + * Copyright (C) 2019-2022 Baikal Electronics, JSC + * Author: Georgy Vlasov + */ + +#include +#include +#include +#include +#include +#include + +/* Register definitions */ +#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 txedsize = 0; + + while (txedsize < data->block[0] + 1) { + unsigned xblocknum; + unsigned 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 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 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 == NULL) { + 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); + strlcpy(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 "); +MODULE_DESCRIPTION("Baikal SMBus driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 358ad56f65245..17d7bfd8ab91e 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -496,6 +496,22 @@ config VCPU_STALL_DETECTOR 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" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index ac9b3e757ba1d..93453052ed92b 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -62,3 +62,4 @@ obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o 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 diff --git a/drivers/misc/tp_bmc.c b/drivers/misc/tp_bmc.c new file mode 100644 index 0000000000000..268d373f45a56 --- /dev/null +++ b/drivers/misc/tp_bmc.c @@ -0,0 +1,770 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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"); diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index a7343d4bc50e7..4c34c21b3dab7 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -325,6 +325,36 @@ static void rk35xx_sdhci_reset(struct sdhci_host *host, u8 mask) 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, @@ -343,6 +373,13 @@ static const struct sdhci_ops sdhci_dwcmshc_rk35xx_ops = { .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, @@ -422,6 +459,10 @@ static void dwcmshc_rk35xx_postinit(struct sdhci_host *host, struct dwcmshc_priv } 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, diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 632341911b6e7..eb870f517d31e 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -4098,7 +4098,8 @@ static int sdhci_set_dma_mask(struct sdhci_host *host) 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", diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 5ce7cdcc192fd..18310a27a51ba 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -480,6 +480,7 @@ struct sdhci_host { #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 */ diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index ffaa240552598..570236c2241ff 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -127,6 +127,10 @@ static const struct flash_info winbond_nor_parts[] = { .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) }, diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig index e5c571fd232cc..4ecb67d4fcff7 100644 --- a/drivers/mux/Kconfig +++ b/drivers/mux/Kconfig @@ -58,4 +58,13 @@ config MUX_MMIO 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 diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile index 6e9fa47daf566..af1454c869def 100644 --- a/drivers/mux/Makefile +++ b/drivers/mux/Makefile @@ -8,9 +8,11 @@ mux-adg792a-objs := adg792a.o 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 diff --git a/drivers/mux/baikal.c b/drivers/mux/baikal.c new file mode 100644 index 0000000000000..361aa6dd15a6d --- /dev/null +++ b/drivers/mux/baikal.c @@ -0,0 +1,704 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Multiplexer driver for Baikal-S SoC low speed peripheral devices + * + * Copyright (C) 2022 Baikal Electronics, JSC + * + * Author: Aleksandr Efimov + */ + +#include +#include +#include +#include +#include +#include +#include + +#define BAIKAL_LSP_MUX_SMC_ID 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_LSP_MUX_SMC_ID, + 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 "); +MODULE_DESCRIPTION("Baikal-S SoC low speed peripheral mux driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig index 31ff351740342..1161bf74bdf88 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig @@ -66,6 +66,14 @@ config DWMAC_ANARION 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 diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile index d4e12e9ace4ff..ad138062e199e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Makefile +++ b/drivers/net/ethernet/stmicro/stmmac/Makefile @@ -14,6 +14,7 @@ stmmac-$(CONFIG_STMMAC_SELFTESTS) += stmmac_selftests.o # 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 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c new file mode 100644 index 0000000000000..b823281315cb7 --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Baikal Electronics DWMAC specific glue layer + * + * Copyright (C) 2015-2022 Baikal Electronics, JSC + * Authors: Dmitry Dunaev + * Alexey Sheplyakov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c index f5581db0ba9ba..0a32081c4fb07 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c @@ -16,7 +16,7 @@ #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; @@ -69,9 +69,10 @@ static void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi) 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; @@ -109,22 +110,25 @@ static void dwmac1000_dma_init(void __iomem *ioaddr, /* 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) { @@ -147,8 +151,8 @@ 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); @@ -174,9 +178,10 @@ static void dwmac1000_dma_operation_mode_rx(void __iomem *ioaddr, int mode, 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); @@ -207,8 +212,9 @@ static void dwmac1000_dma_operation_mode_tx(void __iomem *ioaddr, int mode, 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; @@ -217,8 +223,9 @@ static void dwmac1000_dump_dma_regs(void __iomem *ioaddr, u32 *reg_space) 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); @@ -262,12 +269,14 @@ static int dwmac1000_get_hw_feature(void __iomem *ioaddr, 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, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h new file mode 100644 index 0000000000000..df82d4ae8a80f --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h @@ -0,0 +1,26 @@ +/* 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__ */ diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index 62ce3abf0f196..3cb065f86ce83 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -222,6 +222,38 @@ config PCIE_ARTPEC6_EP 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 diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile index 8ba7b67f5e50a..b38abfaa749e2 100644 --- a/drivers/pci/controller/dwc/Makefile +++ b/drivers/pci/controller/dwc/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o 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 @@ -26,6 +27,9 @@ obj-$(CONFIG_PCIE_UNIPHIER) += pcie-uniphier.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 @@ -44,5 +48,6 @@ ifdef CONFIG_PCI_QUIRKS 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 diff --git a/drivers/pci/controller/dwc/pcie-baikal-acpi.c b/drivers/pci/controller/dwc/pcie-baikal-acpi.c new file mode 100644 index 0000000000000..85da9f8d4e0f8 --- /dev/null +++ b/drivers/pci/controller/dwc/pcie-baikal-acpi.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021-2023 Baikal Electronics, JSC + * Author: Aleksandr Efimov + */ + +#include + +#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 diff --git a/drivers/pci/controller/dwc/pcie-baikal-core.c b/drivers/pci/controller/dwc/pcie-baikal-core.c new file mode 100644 index 0000000000000..8a5d034d9817f --- /dev/null +++ b/drivers/pci/controller/dwc/pcie-baikal-core.c @@ -0,0 +1,2027 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PCIe controller driver for Baikal Electronics SoCs + * + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * Authors: Pavel Parkhomenko + * Aleksandr Efimov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 num; + struct regmap *gpr; + struct gpio_desc *reset_gpio; + 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 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 (bm->reset_gpio != NULL) { + 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; + } + } + + /* 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); + usleep_range(20000, 30000); + + if (bm->reset_gpio != NULL) { + /* Deassert PERST pin */ + gpiod_set_value_cansleep(bm->reset_gpio, 0); + } + + /* 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); +} + +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; + 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); + 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; + } + + /* TODO: GPIO */ + + 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); + + /* 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); diff --git a/drivers/pci/controller/dwc/pcie-baikal-tune.c b/drivers/pci/controller/dwc/pcie-baikal-tune.c new file mode 100644 index 0000000000000..e11763a25dfd5 --- /dev/null +++ b/drivers/pci/controller/dwc/pcie-baikal-tune.c @@ -0,0 +1,575 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BM1000 PCIe Controller & Phy Gen3 equalization parameters fine tune. + * + * Copyright (C) 2023 Baikal Electronics, JSC + */ + +#include +#include +#include +#include +#include + +#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; + struct device_node *np = dev->of_node; + 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) + of_property_read_u32(np, "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) + of_property_read_u32(np, "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) + of_property_read_u32(np, "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) + of_property_read_u32(np, "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) + of_property_read_u32(np, "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) + of_property_read_u32(np, "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) + of_property_read_u32(np, "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) + of_property_read_u32(np, "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 */ diff --git a/drivers/pci/controller/dwc/pcie-baikal.h b/drivers/pci/controller/dwc/pcie-baikal.h new file mode 100644 index 0000000000000..57834b6899eda --- /dev/null +++ b/drivers/pci/controller/dwc/pcie-baikal.h @@ -0,0 +1,16 @@ +// 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 */ diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 7bd00a11d074b..c5171e265ffed 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -96,5 +96,6 @@ source "drivers/phy/tegra/Kconfig" source "drivers/phy/ti/Kconfig" source "drivers/phy/intel/Kconfig" source "drivers/phy/xilinx/Kconfig" +source "drivers/phy/baikal/Kconfig" endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 54f312c10a408..88d1e2d90cfd4 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -34,4 +34,5 @@ obj-y += allwinner/ \ sunplus/ \ tegra/ \ ti/ \ + baikal/ \ xilinx/ diff --git a/drivers/phy/baikal/Kconfig b/drivers/phy/baikal/Kconfig new file mode 100644 index 0000000000000..b8b598d6019e1 --- /dev/null +++ b/drivers/phy/baikal/Kconfig @@ -0,0 +1,10 @@ +# 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 diff --git a/drivers/phy/baikal/Makefile b/drivers/phy/baikal/Makefile new file mode 100644 index 0000000000000..54fcd11988681 --- /dev/null +++ b/drivers/phy/baikal/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_USB_PHY_BAIKAL) += baikal-usb-phy.o diff --git a/drivers/phy/baikal/baikal-usb-phy.c b/drivers/phy/baikal/baikal-usb-phy.c new file mode 100644 index 0000000000000..57e05ceca7100 --- /dev/null +++ b/drivers/phy/baikal/baikal-usb-phy.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Baikal USB PHY driver + * + * Copyright (C) 2022-2023 Baikal Electronics, JSC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; +}; + +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 (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 device_node *child; + struct phy *phy; + struct phy_baikal_priv *priv; + struct phy_baikal_desc *phy_desc; + struct phy_provider *phy_provider; + int index; + int i = 0; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + priv->nphys = of_get_child_count(dev->of_node); + priv->phys = devm_kcalloc(dev, priv->nphys, sizeof(*priv->phys), GFP_KERNEL); + priv->nclocks = devm_clk_bulk_get_all(dev, &priv->clocks); + + dev_set_drvdata(dev, priv); + for_each_available_child_of_node(dev->of_node, child) { + of_property_read_u32(child, "reg", &index); + phy = devm_phy_create(dev, NULL, &phy_baikal_ops); + phy_desc = devm_kzalloc(dev, sizeof(*phy_desc), GFP_KERNEL); + phy_desc->phy = phy; + phy_desc->index = index; + phy_desc->enable = of_property_read_bool(child, "enable"); + priv->phys[i++] = phy_desc; + phy_set_drvdata(phy, phy_desc); + } + + phy_provider = devm_of_phy_provider_register(dev, phy_baikal_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +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, + .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"); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index d1bb62f7368b7..7e03efcf9981c 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -153,6 +153,9 @@ config SPI_AXI_SPI_ENGINE 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 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 4b34e855c8412..5668b16fd1e1f 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_SPI_AT91_USART) += spi-at91-usart.o 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 diff --git a/drivers/spi/spi-baikal-espi.c b/drivers/spi/spi-baikal-espi.c new file mode 100644 index 0000000000000..0abd91037c4de --- /dev/null +++ b/drivers/spi/spi-baikal-espi.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Baikal Electronics eSPI controller driver + * Copyright (C) 2020-2022 Baikal Electronics, JSC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 7db51781289ed..cc5520f016496 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -772,8 +772,14 @@ static const struct dw8250_platform_data dw8250_starfive_jh7100_data = { .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 }, diff --git a/include/linux/dma/dw.h b/include/linux/dma/dw.h index 9752f3745f764..9428e0e3fc98b 100644 --- a/include/linux/dma/dw.h +++ b/include/linux/dma/dw.h @@ -30,7 +30,8 @@ struct dw_dma; 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; diff --git a/include/linux/pci-ecam.h b/include/linux/pci-ecam.h index 6b1301e2498e9..d2126b26e878a 100644 --- a/include/linux/pci-ecam.h +++ b/include/linux/pci-ecam.h @@ -88,6 +88,8 @@ extern const struct pci_ecam_ops xgene_v2_pcie_ecam_ops; /* APM X-Gene PCIe v2.x 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) diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c index 9a60bfdb39bac..71a46932df0c8 100644 --- a/sound/hda/hdac_controller.c +++ b/sound/hda/hdac_controller.c @@ -11,6 +11,10 @@ #include #include "local.h" +#ifdef CONFIG_OF +#include +#endif + /* clear CORB read pointer properly */ static void azx_clear_corbrp(struct hdac_bus *bus) { @@ -78,8 +82,19 @@ void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus) 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 */ + +#ifdef CONFIG_OF + if (bus->polling_mode && of_property_read_bool(bus->dev->of_node, + "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); +#else snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN); +#endif + /* Accept unsolicited responses */ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL); spin_unlock_irq(&bus->reg_lock); @@ -146,6 +161,11 @@ int snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val) spin_lock_irq(&bus->reg_lock); +#ifdef CONFIG_OF + if (of_property_read_bool(bus->dev->of_node, + "increment-codec-address")) + val = val + 0x10000000; +#endif bus->last_cmd[azx_command_addr(val)] = val; /* add command to corb */ diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index a8e8cf98befa1..bb6116505bca9 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -42,6 +42,18 @@ config SND_HDA_TEGRA 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 diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index 00d3061044842..0f58b35cafb1b 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 snd-hda-intel-objs := hda_intel.o snd-hda-tegra-objs := hda_tegra.o +snd-hda-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 @@ -62,3 +63,4 @@ obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.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 diff --git a/sound/pci/hda/hda_baikal.c b/sound/pci/hda/hda_baikal.c new file mode 100644 index 0000000000000..c1fe5cfc35ae6 --- /dev/null +++ b/sound/pci/hda/hda_baikal.c @@ -0,0 +1,625 @@ +// 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 +* +* Based on: +* hda_intel.c, Copyright(c) 2004 Intel Corporation. All rights reserved. +* hda_tegra.c +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hda_controller.h" + +#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"; + struct device_node *np = pdev->dev.of_node; + + 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 */ + sname = of_get_property(np, "baikal,model", NULL); + if (!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 (of_property_read_bool(hda->dev->of_node, "cyclic-codec-probe")) + 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 (of_property_read_bool(bus->dev->of_node, "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"); diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 848fbae26c3bf..54c314bc4d6c6 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -71,6 +71,7 @@ source "sound/soc/amd/Kconfig" 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" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 507eaed1d6a13..a24c8174bdf91 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_SND_SOC) += adi/ 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/ diff --git a/sound/soc/baikal/Kconfig b/sound/soc/baikal/Kconfig new file mode 100644 index 0000000000000..99bcb910c7869 --- /dev/null +++ b/sound/soc/baikal/Kconfig @@ -0,0 +1,20 @@ +# 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 + 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. + diff --git a/sound/soc/baikal/Makefile b/sound/soc/baikal/Makefile new file mode 100644 index 0000000000000..8a70f93a1a442 --- /dev/null +++ b/sound/soc/baikal/Makefile @@ -0,0 +1,7 @@ +# 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 +baikal_i2s-$(CONFIG_SND_BAIKAL_PIO_PCM) += baikal-pio-pcm.o diff --git a/sound/soc/baikal/baikal-i2s.c b/sound/soc/baikal/baikal-i2s.c new file mode 100644 index 0000000000000..6fea68fcf6204 --- /dev/null +++ b/sound/soc/baikal/baikal-i2s.c @@ -0,0 +1,805 @@ +/* + * ALSA SoC Synopsys I2S Audio Layer + * + * sound/soc/baikal/baikal-i2s.c + * + * Copyright (C) 2010 ST Microelectronics + * Rajeev Kumar + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 device_node *np = pdev->dev.of_node; + 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 = of_property_read_u32(np, "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 "); +MODULE_DESCRIPTION("DESIGNWARE I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:designware_i2s"); diff --git a/sound/soc/baikal/baikal-pio-pcm.c b/sound/soc/baikal/baikal-pio-pcm.c new file mode 100644 index 0000000000000..de4ab3bed03d5 --- /dev/null +++ b/sound/soc/baikal/baikal-pio-pcm.c @@ -0,0 +1,266 @@ +/* + * ALSA SoC Synopsys PIO PCM for I2S driver + * + * sound/soc/baikal/baikal-pio-pcm.c + * + * Copyright (C) 2016 Synopsys + * Jose Abreu + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#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); +} diff --git a/sound/soc/baikal/baikal-snd.c b/sound/soc/baikal/baikal-snd.c new file mode 100644 index 0000000000000..e1bb12e6a018f --- /dev/null +++ b/sound/soc/baikal/baikal-snd.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2022 Baikal Electronics, JSC + * Author: Danila Sharikov + * Author: Ekaterina Skachko + */ + +#include +#include +#include +#include +#include + +#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_node *np = pdev->dev.of_node; + struct device_node *codec_np, *baikal_np; + struct snd_soc_dai_link_component *comp; + int err; + + comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL); + if (!comp) + return -ENOMEM; + + baikal_np = of_parse_phandle(np, "baikal,cpu-dai", 0); + if (!baikal_np) { + dev_err(&pdev->dev, + "'baikal,cpu-dai': missing or invalid!\n"); + return -EINVAL; + } + + codec_np = of_parse_phandle(np, "baikal,audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, + "'baikal,audio-codec': missing or invalid!\n"); + return -EINVAL; + } + + snd_baikal_card.dev = &pdev->dev; + snd_baikal_dai.cpus = &comp[0]; + snd_baikal_dai.codecs = &comp[1]; + snd_baikal_dai.platforms = &comp[2]; + + err = of_property_read_string(np, "baikal,dai-name", + &snd_baikal_dai.name); + if (err) { + dev_err(&pdev->dev, + "'baikal,dai-name': missing or invalid!\n"); + return -EINVAL; + } + + err = of_property_read_string(np, "baikal,stream-name", + &snd_baikal_dai.stream_name); + if (err) { + dev_err(&pdev->dev, + "'baikal,stream-name': missing or invalid!\n"); + return -EINVAL; + } + + err = of_property_read_string(np, "baikal,codec-name", + &snd_baikal_dai.codecs->dai_name); + if (err) { + dev_err(&pdev->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; + + snd_baikal_dai.cpus->of_node = baikal_np; + snd_baikal_dai.codecs->of_node = codec_np; + snd_baikal_dai.platforms->of_node = baikal_np; + + snd_soc_of_parse_card_name(&snd_baikal_card, "baikal,card-name"); + + 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(&pdev->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 "); +MODULE_DESCRIPTION("Baikal-M SoC Sound Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/baikal/local.h b/sound/soc/baikal/local.h new file mode 100644 index 0000000000000..56382e9e2646b --- /dev/null +++ b/sound/soc/baikal/local.h @@ -0,0 +1,136 @@ +/* + * 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 +#include +#include +#include +#include +#include + +/* 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 diff --git a/sound/soc/codecs/nau8822.c b/sound/soc/codecs/nau8822.c index 1aef281a99727..b5f93c0418b4d 100644 --- a/sound/soc/codecs/nau8822.c +++ b/sound/soc/codecs/nau8822.c @@ -726,9 +726,11 @@ static int nau8822_set_pll(struct snd_soc_dai *dai, int pll_id, int source, 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"); diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 08938801daec2..12976bc99e9e0 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1227,6 +1227,12 @@ static int aic3x_mute(struct snd_soc_dai *dai, int mute, int direction) 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) { @@ -1478,6 +1484,7 @@ static const struct snd_soc_dai_ops aic3x_dai_ops = { .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, }; @@ -1827,6 +1834,9 @@ int aic3x_probe(struct device *dev, struct regmap *regmap, kernel_ulong_t driver 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++) diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index 7f7dd07c63b2f..4788653a27bf6 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -136,11 +136,12 @@ static irqreturn_t i2s_irq_handler(int irq, void *dev_id) 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) @@ -189,6 +190,7 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream, 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; @@ -203,6 +205,7 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream, 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; } @@ -356,25 +359,25 @@ 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: + 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; } @@ -390,6 +393,10 @@ static const struct snd_soc_dai_ops dw_i2s_dai_ops = { .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) { @@ -403,43 +410,32 @@ 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; } @@ -448,13 +444,6 @@ static int dw_i2s_resume(struct snd_soc_component *component) #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 @@ -625,12 +614,13 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, } + 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; @@ -643,22 +633,37 @@ static int dw_i2s_probe(struct platform_device *pdev) 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; @@ -722,8 +727,10 @@ static int dw_i2s_probe(struct platform_device *pdev) } pm_runtime_enable(&pdev->dev); + return 0; + err_clk_disable: if (dev->capability & DW_I2S_MASTER) clk_disable_unprepare(dev->clk);