From 8f49486c1a63575d62297647322e86d4cb371b8a Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Sat, 15 Jun 2019 18:03:15 +0200 Subject: [PATCH 1/2] uboot_helper: Add Allwinner H6 based Tanix TX6 STB --- scripts/uboot_helper | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/uboot_helper b/scripts/uboot_helper index 7a020d74d6..61624ca90b 100755 --- a/scripts/uboot_helper +++ b/scripts/uboot_helper @@ -95,6 +95,10 @@ devices = \ 'dtb': 'sun50i-h6-pine-h64.dtb', 'config': 'pine_h64_defconfig' }, + 'tanix-tx6' : { + 'dtb' : 'sun50i-h6-tanix-tx6.dtb', + 'config' : 'eachlink_h6_mini_defconfig' + }, }, }, 'Amlogic': { From cebeaa2927408834c4ba2e8317b1304d01e4e0c5 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Thu, 27 Jun 2019 17:20:39 +0200 Subject: [PATCH 2/2] Allwinner: Add support for Tanix TX6 --- .../devices/H6/patches/linux/11-pwm.patch | 344 ++++ .../H6/patches/linux/12-ac200-nodes.patch | 103 ++ .../H6/patches/linux/13-Tanix-TX6.patch | 185 +++ .../u-boot/002-orange-pi-3-support.patch | 1 + .../devices/H6/patches/u-boot/006-DDR3.patch | 1402 +++++++++++++++++ .../H6/patches/u-boot/007-ethernet-hack.patch | 51 + projects/Allwinner/linux/linux.aarch64.conf | 6 +- projects/Allwinner/linux/linux.arm.conf | 2 + .../Allwinner/patches/linux/0014-AC200.patch | 708 +++++++++ 9 files changed, 2800 insertions(+), 2 deletions(-) create mode 100644 projects/Allwinner/devices/H6/patches/linux/11-pwm.patch create mode 100644 projects/Allwinner/devices/H6/patches/linux/12-ac200-nodes.patch create mode 100644 projects/Allwinner/devices/H6/patches/linux/13-Tanix-TX6.patch create mode 100644 projects/Allwinner/devices/H6/patches/u-boot/006-DDR3.patch create mode 100644 projects/Allwinner/devices/H6/patches/u-boot/007-ethernet-hack.patch create mode 100644 projects/Allwinner/patches/linux/0014-AC200.patch diff --git a/projects/Allwinner/devices/H6/patches/linux/11-pwm.patch b/projects/Allwinner/devices/H6/patches/linux/11-pwm.patch new file mode 100644 index 0000000000..cc156dca07 --- /dev/null +++ b/projects/Allwinner/devices/H6/patches/linux/11-pwm.patch @@ -0,0 +1,344 @@ +From 0e014d5d469b49173f89897c898ee4d350f2b183 Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Fri, 26 Jul 2019 17:31:20 +0200 +Subject: [PATCH 1/5] pwm: sun4i: Add a quirk for reset line + +H6 PWM core needs deasserted reset line in order to work. + +Add a quirk for it. + +Signed-off-by: Jernej Skrabec +--- + drivers/pwm/pwm-sun4i.c | 27 +++++++++++++++++++++++++-- + 1 file changed, 25 insertions(+), 2 deletions(-) + +diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c +index de78c824bbfd..1b7be8fbde86 100644 +--- a/drivers/pwm/pwm-sun4i.c ++++ b/drivers/pwm/pwm-sun4i.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -72,12 +73,14 @@ static const u32 prescaler_table[] = { + + struct sun4i_pwm_data { + bool has_prescaler_bypass; ++ bool has_reset; + unsigned int npwm; + }; + + struct sun4i_pwm_chip { + struct pwm_chip chip; + struct clk *clk; ++ struct reset_control *rst; + void __iomem *base; + spinlock_t ctrl_lock; + const struct sun4i_pwm_data *data; +@@ -371,6 +374,14 @@ static int sun4i_pwm_probe(struct platform_device *pdev) + if (IS_ERR(pwm->clk)) + return PTR_ERR(pwm->clk); + ++ if (pwm->data->has_reset) { ++ pwm->rst = devm_reset_control_get(&pdev->dev, NULL); ++ if (IS_ERR(pwm->rst)) ++ return PTR_ERR(pwm->rst); ++ ++ reset_control_deassert(pwm->rst); ++ } ++ + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &sun4i_pwm_ops; + pwm->chip.base = -1; +@@ -383,19 +394,31 @@ static int sun4i_pwm_probe(struct platform_device *pdev) + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); +- return ret; ++ goto err_pwm_add; + } + + platform_set_drvdata(pdev, pwm); + + return 0; ++ ++err_pwm_add: ++ reset_control_assert(pwm->rst); ++ ++ return ret; + } + + static int sun4i_pwm_remove(struct platform_device *pdev) + { + struct sun4i_pwm_chip *pwm = platform_get_drvdata(pdev); ++ int ret; ++ ++ ret = pwmchip_remove(&pwm->chip); ++ if (ret) ++ return ret; + +- return pwmchip_remove(&pwm->chip); ++ reset_control_assert(pwm->rst); ++ ++ return 0; + } + + static struct platform_driver sun4i_pwm_driver = { +-- +2.22.1 + + +From 56755aaa0610275f5348ac840c76b4e2b8572281 Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Fri, 26 Jul 2019 17:42:06 +0200 +Subject: [PATCH 2/5] pwm: sun4i: Add a quirk for bus clock + +H6 PWM core needs bus clock to be enabled in order to work. + +Add a quirk for it. + +Signed-off-by: Jernej Skrabec +--- + drivers/pwm/pwm-sun4i.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c +index 1b7be8fbde86..7d3ac3f2dc3f 100644 +--- a/drivers/pwm/pwm-sun4i.c ++++ b/drivers/pwm/pwm-sun4i.c +@@ -72,6 +72,7 @@ static const u32 prescaler_table[] = { + }; + + struct sun4i_pwm_data { ++ bool has_bus_clock; + bool has_prescaler_bypass; + bool has_reset; + unsigned int npwm; +@@ -79,6 +80,7 @@ struct sun4i_pwm_data { + + struct sun4i_pwm_chip { + struct pwm_chip chip; ++ struct clk *bus_clk; + struct clk *clk; + struct reset_control *rst; + void __iomem *base; +@@ -382,6 +384,16 @@ static int sun4i_pwm_probe(struct platform_device *pdev) + reset_control_deassert(pwm->rst); + } + ++ if (pwm->data->has_bus_clock) { ++ pwm->bus_clk = devm_clk_get(&pdev->dev, "bus"); ++ if (IS_ERR(pwm->bus_clk)) { ++ ret = PTR_ERR(pwm->bus_clk); ++ goto err_bus; ++ } ++ ++ clk_prepare_enable(pwm->bus_clk); ++ } ++ + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &sun4i_pwm_ops; + pwm->chip.base = -1; +@@ -402,6 +414,8 @@ static int sun4i_pwm_probe(struct platform_device *pdev) + return 0; + + err_pwm_add: ++ clk_disable_unprepare(pwm->bus_clk); ++err_bus: + reset_control_assert(pwm->rst); + + return ret; +@@ -416,6 +430,7 @@ static int sun4i_pwm_remove(struct platform_device *pdev) + if (ret) + return ret; + ++ clk_disable_unprepare(pwm->bus_clk); + reset_control_assert(pwm->rst); + + return 0; +-- +2.22.1 + + +From 68c1b688e1e57ce22dfc6a72ad2b6f403953823a Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Fri, 26 Jul 2019 17:45:40 +0200 +Subject: [PATCH 3/5] pwm: sun4i: Add support for H6 PWM + +Now that sun4i PWM driver supports deasserting reset line and enabling +bus clock, support for H6 PWM can be added. + +Note that while H6 PWM has two channels, only first one is wired to +output pin. Second channel is used as a clock source to companion AC200 +chip which is bundled into same package. + +Signed-off-by: Jernej Skrabec +--- + drivers/pwm/pwm-sun4i.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c +index 7d3ac3f2dc3f..9e0eca79ff88 100644 +--- a/drivers/pwm/pwm-sun4i.c ++++ b/drivers/pwm/pwm-sun4i.c +@@ -331,6 +331,13 @@ static const struct sun4i_pwm_data sun4i_pwm_single_bypass = { + .npwm = 1, + }; + ++static const struct sun4i_pwm_data sun50i_pwm_dual_bypass_clk_rst = { ++ .has_bus_clock = true, ++ .has_prescaler_bypass = true, ++ .has_reset = true, ++ .npwm = 2, ++}; ++ + static const struct of_device_id sun4i_pwm_dt_ids[] = { + { + .compatible = "allwinner,sun4i-a10-pwm", +@@ -347,6 +354,9 @@ static const struct of_device_id sun4i_pwm_dt_ids[] = { + }, { + .compatible = "allwinner,sun8i-h3-pwm", + .data = &sun4i_pwm_single_bypass, ++ }, { ++ .compatible = "allwinner,sun50i-h6-pwm", ++ .data = &sun50i_pwm_dual_bypass_clk_rst, + }, { + /* sentinel */ + }, +-- +2.22.1 + + +From b78fc7d807a6d4b715cad9b87c134a3cb6289f62 Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Fri, 26 Jul 2019 17:53:36 +0200 +Subject: [PATCH 4/5] pwm: sun4i: Add support to output source clock directly + +PWM core has an option to bypass whole logic and output unchanged source +clock as PWM output. This is achieved by enabling bypass bit. + +Note that when bypass is enabled, no other setting has any meaning, not +even enable bit. + +This mode of operation is needed to achieve high enough frequency to +serve as clock source for AC200 chip, which is integrated into same +package as H6 SoC. + +Signed-off-by: Jernej Skrabec +--- + drivers/pwm/pwm-sun4i.c | 31 ++++++++++++++++++++++++++++++- + 1 file changed, 30 insertions(+), 1 deletion(-) + +diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c +index 9e0eca79ff88..848cff26f385 100644 +--- a/drivers/pwm/pwm-sun4i.c ++++ b/drivers/pwm/pwm-sun4i.c +@@ -120,6 +120,19 @@ static void sun4i_pwm_get_state(struct pwm_chip *chip, + + val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + ++ /* ++ * PWM chapter in H6 manual has a diagram which explains that if bypass ++ * bit is set, no other setting has any meaning. Even more, experiment ++ * proved that also enable bit is ignored in this case. ++ */ ++ if (val & BIT_CH(PWM_BYPASS, pwm->hwpwm)) { ++ state->period = DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC, clk_rate); ++ state->duty_cycle = state->period / 2; ++ state->polarity = PWM_POLARITY_NORMAL; ++ state->enabled = true; ++ return; ++ } ++ + if ((PWM_REG_PRESCAL(val, pwm->hwpwm) == PWM_PRESCAL_MASK) && + sun4i_pwm->data->has_prescaler_bypass) + prescaler = 1; +@@ -211,7 +224,8 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + { + struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); + struct pwm_state cstate; +- u32 ctrl; ++ u32 ctrl, clk_rate; ++ bool bypass; + int ret; + unsigned int delay_us; + unsigned long now; +@@ -226,6 +240,16 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + } + } + ++ /* ++ * Although it would make much more sense to check for bypass in ++ * sun4i_pwm_calculate(), value of bypass bit also depends on "enabled". ++ * Period is allowed to be rounded up or down. ++ */ ++ clk_rate = clk_get_rate(sun4i_pwm->clk); ++ bypass = (state->period == NSEC_PER_SEC / clk_rate || ++ state->period == DIV_ROUND_UP(NSEC_PER_SEC, clk_rate)) && ++ state->enabled; ++ + spin_lock(&sun4i_pwm->ctrl_lock); + ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + +@@ -273,6 +297,11 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + } + ++ if (bypass) ++ ctrl |= BIT_CH(PWM_BYPASS, pwm->hwpwm); ++ else ++ ctrl &= ~BIT_CH(PWM_BYPASS, pwm->hwpwm); ++ + sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG); + + spin_unlock(&sun4i_pwm->ctrl_lock); +-- +2.22.1 + + +From e5fa4d2acf30ccac7f3817c299a2fa6f20c23c50 Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Fri, 26 Jul 2019 18:02:50 +0200 +Subject: [PATCH 5/5] arm64: dts: allwinner: h6: Add PWM node + +Allwinner H6 PWM is similar to that in A20 except that it has additional +bus clock and reset line. + +Note that first PWM channel is connected to output pin and second +channel is used internally, as a clock source to AC200 co-packaged chip. +This means that any combination of these two channels can be used and +thus it doesn't make sense to add pinctrl nodes at this point. + +Signed-off-by: Jernej Skrabec +--- + arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi +index e8bed58e7246..c1abd805cfdc 100644 +--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi ++++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi +@@ -229,6 +229,16 @@ + status = "disabled"; + }; + ++ pwm: pwm@300a000 { ++ compatible = "allwinner,sun50i-h6-pwm"; ++ reg = <0x0300a000 0x400>; ++ clocks = <&osc24M>, <&ccu CLK_BUS_PWM>; ++ clock-names = "pwm", "bus"; ++ resets = <&ccu RST_BUS_PWM>; ++ #pwm-cells = <3>; ++ status = "disabled"; ++ }; ++ + pio: pinctrl@300b000 { + compatible = "allwinner,sun50i-h6-pinctrl"; + reg = <0x0300b000 0x400>; +-- +2.22.1 + diff --git a/projects/Allwinner/devices/H6/patches/linux/12-ac200-nodes.patch b/projects/Allwinner/devices/H6/patches/linux/12-ac200-nodes.patch new file mode 100644 index 0000000000..dc1d4c6ba7 --- /dev/null +++ b/projects/Allwinner/devices/H6/patches/linux/12-ac200-nodes.patch @@ -0,0 +1,103 @@ +From fa11b2ef7b8359f70d010d3b76b7dfd90250dc5c Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Fri, 16 Aug 2019 16:40:20 +0200 +Subject: [PATCH] arm64: dts: allwinner: h6: Add AC200 EPHY related nodes + +Signed-off-by: Jernej Skrabec +--- + arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 51 ++++++++++++++++++++ + 1 file changed, 51 insertions(+) + +diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi +index 8eec8685a50b..5eeb7da7a0ab 100644 +--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi ++++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi +@@ -17,6 +17,16 @@ + #address-cells = <1>; + #size-cells = <1>; + ++ ac200_pwm_clk: ac200_clk { ++ compatible = "pwm-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <24000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwm1_pin>; ++ pwms = <&pwm 1 42 0>; ++ status = "disabled"; ++ }; ++ + cpus { + #address-cells = <1>; + #size-cells = <0>; +@@ -218,6 +228,12 @@ + sid: efuse@3006000 { + compatible = "allwinner,sun50i-h6-sid"; + reg = <0x03006000 0x400>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ ephy_calib: ephy_calib@2c { ++ reg = <0x2c 0x2>; ++ }; + }; + + watchdog: watchdog@30090a0 { +@@ -268,6 +284,11 @@ + drive-strength = <40>; + }; + ++ i2c3_pins: i2c3-pins { ++ pins = "PB17", "PB18"; ++ function = "i2c3"; ++ }; ++ + hdmi_pins: hdmi-pins { + pins = "PH8", "PH9", "PH10"; + function = "hdmi"; +@@ -290,6 +311,11 @@ + bias-pull-up; + }; + ++ pwm1_pin: pwm1-pin { ++ pins = "PB19"; ++ function = "pwm1"; ++ }; ++ + mmc2_pins: mmc2-pins { + pins = "PC1", "PC4", "PC5", "PC6", + "PC7", "PC8", "PC9", "PC10", +@@ -408,6 +434,31 @@ + status = "disabled"; + }; + ++ i2c3: i2c@5002c00 { ++ compatible = "allwinner,sun6i-a31-i2c"; ++ reg = <0x05002c00 0x400>; ++ interrupts = ; ++ clocks = <&ccu CLK_BUS_I2C3>; ++ resets = <&ccu RST_BUS_I2C3>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c3_pins>; ++ status = "disabled"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ac200: mfd@10 { ++ compatible = "x-powers,ac200"; ++ reg = <0x10>; ++ clocks = <&ac200_pwm_clk>; ++ ++ ac200_ephy: phy { ++ compatible = "x-powers,ac200-ephy"; ++ nvmem-cells = <&ephy_calib>; ++ nvmem-cell-names = "ephy_calib"; ++ }; ++ }; ++ }; ++ + emac: ethernet@5020000 { + compatible = "allwinner,sun50i-h6-emac", + "allwinner,sun50i-a64-emac"; +-- +2.22.1 + diff --git a/projects/Allwinner/devices/H6/patches/linux/13-Tanix-TX6.patch b/projects/Allwinner/devices/H6/patches/linux/13-Tanix-TX6.patch new file mode 100644 index 0000000000..b5ac1bf8c6 --- /dev/null +++ b/projects/Allwinner/devices/H6/patches/linux/13-Tanix-TX6.patch @@ -0,0 +1,185 @@ +From 13300ba386795eceeaf47fc199d5e8683dcd2ff8 Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Mon, 24 Jun 2019 12:17:58 +0200 +Subject: [PATCH 1/2] Tanix TX6 DT + +Signed-off-by: Jernej Skrabec +--- + arch/arm64/boot/dts/allwinner/Makefile | 1 + + .../dts/allwinner/sun50i-h6-tanix-tx6.dts | 133 ++++++++++++++++++ + arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 7 + + 3 files changed, 141 insertions(+) + create mode 100644 arch/arm64/boot/dts/allwinner/sun50i-h6-tanix-tx6.dts + +diff --git a/arch/arm64/boot/dts/allwinner/Makefile b/arch/arm64/boot/dts/allwinner/Makefile +index f6db0611cb85..395fe76f6819 100644 +--- a/arch/arm64/boot/dts/allwinner/Makefile ++++ b/arch/arm64/boot/dts/allwinner/Makefile +@@ -25,3 +25,4 @@ dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h6-orangepi-3.dtb + dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h6-orangepi-lite2.dtb + dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h6-orangepi-one-plus.dtb + dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h6-pine-h64.dtb ++dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h6-tanix-tx6.dtb +diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6-tanix-tx6.dts b/arch/arm64/boot/dts/allwinner/sun50i-h6-tanix-tx6.dts +new file mode 100644 +index 000000000000..c90af0b29f28 +--- /dev/null ++++ b/arch/arm64/boot/dts/allwinner/sun50i-h6-tanix-tx6.dts +@@ -0,0 +1,136 @@ ++// SPDX-License-Identifier: (GPL-2.0+ or MIT) ++/* ++ * Copyright (c) 2019 Jernej Skrabec ++ */ ++ ++/dts-v1/; ++ ++#include "sun50i-h6.dtsi" ++ ++#include ++ ++/ { ++ model = "Tanix TX6"; ++ compatible = "tanix,tanix-tx6", "allwinner,sun50i-h6"; ++ ++ aliases { ++ ethernet0 = &emac; ++ serial0 = &uart0; ++ }; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++ ++ connector { ++ compatible = "hdmi-connector"; ++ type = "a"; ++ ddc-en-gpios = <&pio 7 2 GPIO_ACTIVE_HIGH>; /* PH2 */ ++ ++ port { ++ hdmi_con_in: endpoint { ++ remote-endpoint = <&hdmi_out_con>; ++ }; ++ }; ++ }; ++ ++ reg_vcc3v3: vcc3v3 { ++ compatible = "regulator-fixed"; ++ regulator-name = "vcc3v3"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ }; ++}; ++ ++&ac200_pwm_clk { ++ status = "okay"; ++}; ++ ++&de { ++ status = "okay"; ++}; ++ ++&dwc3 { ++ status = "okay"; ++}; ++ ++&emac { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&ext_rmii_pins>; ++ phy-mode = "rmii"; ++ phy-handle = <&ext_rmii_phy>; ++ status = "okay"; ++}; ++ ++&ehci0 { ++ status = "okay"; ++}; ++ ++&ehci3 { ++ status = "okay"; ++}; ++ ++&hdmi { ++ status = "okay"; ++}; ++ ++&hdmi_out { ++ hdmi_out_con: endpoint { ++ remote-endpoint = <&hdmi_con_in>; ++ }; ++}; ++ ++&i2c3 { ++ status = "okay"; ++}; ++ ++&mdio { ++ ext_rmii_phy: ethernet-phy@1 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <1>; ++ }; ++}; ++ ++&mmc0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&mmc0_pins>; ++ vmmc-supply = <®_vcc3v3>; ++ cd-gpios = <&pio 5 6 GPIO_ACTIVE_LOW>; ++ bus-width = <4>; ++ status = "okay"; ++}; ++ ++&ohci0 { ++ status = "okay"; ++}; ++ ++&ohci3 { ++ status = "okay"; ++}; ++ ++&pwm { ++ status = "okay"; ++}; ++ ++&r_ir { ++ status = "okay"; ++}; ++ ++&uart0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart0_ph_pins>; ++ status = "okay"; ++}; ++ ++&usb2otg { ++ dr_mode = "host"; ++ status = "okay"; ++}; ++ ++&usb2phy { ++ status = "okay"; ++}; ++ ++&usb3phy { ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi +index 7628a7c83096..305e093c910f 100644 +--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi ++++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi +@@ -251,6 +251,13 @@ + drive-strength = <40>; + }; + ++ ext_rmii_pins: rmii_pins { ++ pins = "PA0", "PA1", "PA2", "PA3", "PA4", ++ "PA5", "PA6", "PA7", "PA8", "PA9"; ++ function = "emac"; ++ drive-strength = <40>; ++ }; ++ + hdmi_pins: hdmi-pins { + pins = "PH8", "PH9", "PH10"; + function = "hdmi"; +-- +2.22.0 + diff --git a/projects/Allwinner/devices/H6/patches/u-boot/002-orange-pi-3-support.patch b/projects/Allwinner/devices/H6/patches/u-boot/002-orange-pi-3-support.patch index 86093d396a..76f4470662 100644 --- a/projects/Allwinner/devices/H6/patches/u-boot/002-orange-pi-3-support.patch +++ b/projects/Allwinner/devices/H6/patches/u-boot/002-orange-pi-3-support.patch @@ -353,6 +353,7 @@ index 0000000000..9a9cd28142 +CONFIG_ARCH_SUNXI=y +CONFIG_SPL=y +CONFIG_MACH_SUN50I_H6=y ++CONFIG_SUNXI_DRAM_H6_LPDDR3=y +CONFIG_MMC0_CD_PIN="PF6" +# CONFIG_PSCI_RESET is not set +CONFIG_MMC_SUNXI_SLOT_EXTRA=2 diff --git a/projects/Allwinner/devices/H6/patches/u-boot/006-DDR3.patch b/projects/Allwinner/devices/H6/patches/u-boot/006-DDR3.patch new file mode 100644 index 0000000000..35dd541dd4 --- /dev/null +++ b/projects/Allwinner/devices/H6/patches/u-boot/006-DDR3.patch @@ -0,0 +1,1402 @@ +From 53bc816d563278a4b35c724f1f950aecfb4b7810 Mon Sep 17 00:00:00 2001 +From: Andre Przywara +Date: Sat, 17 Nov 2018 11:53:02 +0000 +Subject: [PATCH 1/7] sunxi: H6: DRAM: avoid memcpy() on MMIO registers + +Using memcpy() is, however tempting, not a good idea: It depends on the +specific implementation of memcpy, also lacks barriers. In this +particular case the first registers were written using 64-bit writes, +and the last register using four separate single-byte writes. + +Replace the memcpy with a proper loop using the writel() accessor. + +Signed-off-by: Andre Przywara +--- + arch/arm/mach-sunxi/dram_sun50i_h6.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c +index 5da90a2835..e2f141eb9b 100644 +--- a/arch/arm/mach-sunxi/dram_sun50i_h6.c ++++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c +@@ -182,6 +182,7 @@ static void mctl_set_timing_lpddr3(struct dram_para *para) + (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE; + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY0_BASE; ++ int i; + + u8 tccd = 2; + u8 tfaw = max(ns_to_t(50), 4); +@@ -237,8 +238,9 @@ static void mctl_set_timing_lpddr3(struct dram_para *para) + u8 twr2rd = tcwl + 4 + 1 + twtr; + u8 trd2wr = tcl + 4 + (tcksrea >> 1) - tcwl + 1; + +- /* set mode register */ +- memcpy(mctl_phy->mr, mr_lpddr3, sizeof(mr_lpddr3)); ++ /* set mode registers */ ++ for (i = 0; i < ARRAY_SIZE(mr_lpddr3); i++) ++ writel(mr_lpddr3[i], &mctl_phy->mr[i]); + + /* set DRAM timing */ + writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | tras, +-- +2.22.0 + + +From 304faf14253a8d548633c1fcd772912d15cc6643 Mon Sep 17 00:00:00 2001 +From: Andre Przywara +Date: Sun, 25 Nov 2018 22:24:48 +0000 +Subject: [PATCH 2/7] sunxi: H6: DRAM: follow recommended PHY init algorithm + +The DRAM controller manual suggests to first program the PHY +initialisation parameters to the PHY_PIR register, and then set bit 0 to +trigger the initialisation. This is also used in boot0. + +Follow this recommendation by setting bit 0 in a separate step. + +Signed-off-by: Andre Przywara +--- + arch/arm/mach-sunxi/dram_sun50i_h6.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c +index e2f141eb9b..7a8b724f08 100644 +--- a/arch/arm/mach-sunxi/dram_sun50i_h6.c ++++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c +@@ -75,12 +75,14 @@ static void mctl_core_init(struct dram_para *para) + mctl_channel_init(para); + } + ++/* PHY initialisation */ + static void mctl_phy_pir_init(u32 val) + { + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY0_BASE; + +- writel(val | BIT(0), &mctl_phy->pir); ++ writel(val, &mctl_phy->pir); ++ writel(val | BIT(0), &mctl_phy->pir); /* Start initialisation. */ + mctl_await_completion(&mctl_phy->pgsr[0], BIT(0), BIT(0)); + } + +-- +2.22.0 + + +From 6eae304eb8a96a504738774a996a4afa1284b2b5 Mon Sep 17 00:00:00 2001 +From: Andre Przywara +Date: Wed, 21 Nov 2018 22:09:40 +0000 +Subject: [PATCH 3/7] sunxi: H6: move LPDDR3 timing definition into separate + file + +Currently the H6 DRAM driver only supports one kind of LPDDR3 DRAM. +Split the timing parameters for LPDDR3-1333 into a separate file, to +allow selecting an alternative later at compile time (as the sunxi-dw +driver does). + +Signed-off-by: Andre Przywara +--- + .../include/asm/arch-sunxi/dram_sun50i_h6.h | 28 ++++ + arch/arm/mach-sunxi/Kconfig | 10 +- + arch/arm/mach-sunxi/Makefile | 1 + + arch/arm/mach-sunxi/dram_sun50i_h6.c | 150 +----------------- + arch/arm/mach-sunxi/dram_timings/Makefile | 1 + + .../mach-sunxi/dram_timings/h6_lpddr3_1333.c | 132 +++++++++++++++ + configs/orangepi_one_plus_defconfig | 1 + + configs/pine_h64_defconfig | 1 + + 8 files changed, 176 insertions(+), 148 deletions(-) + create mode 100644 arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c + +diff --git a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h +index eeb4da5c3f..b28ae583c9 100644 +--- a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h ++++ b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h +@@ -287,6 +287,32 @@ check_member(sunxi_mctl_phy_reg, dx[3].reserved_0xf0, 0xaf0); + #define DCR_DDR4 (4 << 0) + #define DCR_DDR8BANK BIT(3) + ++ ++/* ++ * The delay parameters below allow to allegedly specify delay times of some ++ * unknown unit for each individual bit trace in each of the four data bytes ++ * the 32-bit wide access consists of. Also three control signals can be ++ * adjusted individually. ++ */ ++#define NR_OF_BYTE_LANES (32 / BITS_PER_BYTE) ++/* The eight data lines (DQn) plus DM, DQS, DQS/DM/DQ Output Enable and DQSN */ ++#define WR_LINES_PER_BYTE_LANE (BITS_PER_BYTE + 4) ++/* ++ * The eight data lines (DQn) plus DM, DQS, DQS/DM/DQ Output Enable, DQSN, ++ * Termination and Power down ++ */ ++#define RD_LINES_PER_BYTE_LANE (BITS_PER_BYTE + 6) ++struct dram_para { ++ u32 clk; ++ enum sunxi_dram_type type; ++ u8 cols; ++ u8 rows; ++ u8 ranks; ++ const u8 dx_read_delays[NR_OF_BYTE_LANES][RD_LINES_PER_BYTE_LANE]; ++ const u8 dx_write_delays[NR_OF_BYTE_LANES][WR_LINES_PER_BYTE_LANE]; ++}; ++ ++ + static inline int ns_to_t(int nanoseconds) + { + const unsigned int ctrl_freq = CONFIG_DRAM_CLK / 2; +@@ -294,4 +320,6 @@ static inline int ns_to_t(int nanoseconds) + return DIV_ROUND_UP(ctrl_freq * nanoseconds, 1000); + } + ++void mctl_set_timing_params(struct dram_para *para); ++ + #endif /* _SUNXI_DRAM_SUN50I_H6_H */ +diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig +index 1669e62a6d..e01cb6a09a 100644 +--- a/arch/arm/mach-sunxi/Kconfig ++++ b/arch/arm/mach-sunxi/Kconfig +@@ -340,7 +340,7 @@ config ARM_BOOT_HOOK_RMR + This allows both the SPL and the U-Boot proper to be entered in + either mode and switch to AArch64 if needed. + +-if SUNXI_DRAM_DW ++if SUNXI_DRAM_DW || DRAM_SUN50I_H6 + config SUNXI_DRAM_DDR3 + bool + +@@ -370,6 +370,14 @@ config SUNXI_DRAM_LPDDR3_STOCK + This option is the LPDDR3 timing used by the stock boot0 by + Allwinner. + ++config SUNXI_DRAM_H6_LPDDR3 ++ bool "LPDDR3-1333 on the H6 DRAM controller" ++ select SUNXI_DRAM_LPDDR3 ++ depends on DRAM_SUN50I_H6 ++ ---help--- ++ This option is the LPDDR3 timing used by the stock boot0 by ++ Allwinner. ++ + config SUNXI_DRAM_DDR2_V3S + bool "DDR2 found in V3s chip" + select SUNXI_DRAM_DDR2 +diff --git a/arch/arm/mach-sunxi/Makefile b/arch/arm/mach-sunxi/Makefile +index 43a93e3085..d129f33479 100644 +--- a/arch/arm/mach-sunxi/Makefile ++++ b/arch/arm/mach-sunxi/Makefile +@@ -39,4 +39,5 @@ obj-$(CONFIG_SPL_SPI_SUNXI) += spl_spi_sunxi.o + obj-$(CONFIG_SUNXI_DRAM_DW) += dram_sunxi_dw.o + obj-$(CONFIG_SUNXI_DRAM_DW) += dram_timings/ + obj-$(CONFIG_DRAM_SUN50I_H6) += dram_sun50i_h6.o ++obj-$(CONFIG_DRAM_SUN50I_H6) += dram_timings/ + endif +diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c +index 7a8b724f08..697b8af4ce 100644 +--- a/arch/arm/mach-sunxi/dram_sun50i_h6.c ++++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c +@@ -32,33 +32,8 @@ + * similar PHY is ZynqMP. + */ + +-/* +- * The delay parameters below allow to allegedly specify delay times of some +- * unknown unit for each individual bit trace in each of the four data bytes +- * the 32-bit wide access consists of. Also three control signals can be +- * adjusted individually. +- */ +-#define NR_OF_BYTE_LANES (32 / BITS_PER_BYTE) +-/* The eight data lines (DQn) plus DM, DQS, DQS/DM/DQ Output Enable and DQSN */ +-#define WR_LINES_PER_BYTE_LANE (BITS_PER_BYTE + 4) +-/* +- * The eight data lines (DQn) plus DM, DQS, DQS/DM/DQ Output Enable, DQSN, +- * Termination and Power down +- */ +-#define RD_LINES_PER_BYTE_LANE (BITS_PER_BYTE + 6) +-struct dram_para { +- u32 clk; +- enum sunxi_dram_type type; +- u8 cols; +- u8 rows; +- u8 ranks; +- const u8 dx_read_delays[NR_OF_BYTE_LANES][RD_LINES_PER_BYTE_LANE]; +- const u8 dx_write_delays[NR_OF_BYTE_LANES][WR_LINES_PER_BYTE_LANE]; +-}; +- + static void mctl_sys_init(struct dram_para *para); + static void mctl_com_init(struct dram_para *para); +-static void mctl_set_timing_lpddr3(struct dram_para *para); + static void mctl_channel_init(struct dram_para *para); + + static void mctl_core_init(struct dram_para *para) +@@ -67,7 +42,7 @@ static void mctl_core_init(struct dram_para *para) + mctl_com_init(para); + switch (para->type) { + case SUNXI_DRAM_TYPE_LPDDR3: +- mctl_set_timing_lpddr3(para); ++ mctl_set_timing_params(para); + break; + default: + panic("Unsupported DRAM type!"); +@@ -171,127 +146,6 @@ static void mctl_set_master_priority(void) + MBUS_CONF(HDCP2, true, HIGH, 2, 100, 64, 32); + } + +-static u32 mr_lpddr3[12] = { +- 0x00000000, 0x00000043, 0x0000001a, 0x00000001, +- 0x00000000, 0x00000000, 0x00000048, 0x00000000, +- 0x00000000, 0x00000000, 0x00000000, 0x00000003, +-}; +- +-/* TODO: flexible timing */ +-static void mctl_set_timing_lpddr3(struct dram_para *para) +-{ +- struct sunxi_mctl_ctl_reg * const mctl_ctl = +- (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE; +- struct sunxi_mctl_phy_reg * const mctl_phy = +- (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY0_BASE; +- int i; +- +- u8 tccd = 2; +- u8 tfaw = max(ns_to_t(50), 4); +- u8 trrd = max(ns_to_t(10), 2); +- u8 trcd = max(ns_to_t(24), 2); +- u8 trc = ns_to_t(70); +- u8 txp = max(ns_to_t(8), 2); +- u8 twtr = max(ns_to_t(8), 2); +- u8 trtp = max(ns_to_t(8), 2); +- u8 twr = max(ns_to_t(15), 2); +- u8 trp = ns_to_t(18); +- u8 tras = ns_to_t(42); +- u8 twtr_sa = ns_to_t(5); +- u8 tcksrea = ns_to_t(11); +- u16 trefi = ns_to_t(3900) / 32; +- u16 trfc = ns_to_t(210); +- u16 txsr = ns_to_t(220); +- +- if (CONFIG_DRAM_CLK % 400 == 0) { +- /* Round up these parameters */ +- twtr_sa++; +- tcksrea++; +- } +- +- u8 tmrw = 5; +- u8 tmrd = 5; +- u8 tmod = 12; +- u8 tcke = 3; +- u8 tcksrx = 5; +- u8 tcksre = 5; +- u8 tckesr = 5; +- u8 trasmax = CONFIG_DRAM_CLK / 60; +- u8 txs = 4; +- u8 txsdll = 4; +- u8 txsabort = 4; +- u8 txsfast = 4; +- +- u8 tcl = 5; /* CL 10 */ +- u8 tcwl = 3; /* CWL 6 */ +- u8 t_rdata_en = twtr_sa + 8; +- +- u32 tdinit0 = (200 * CONFIG_DRAM_CLK) + 1; /* 200us */ +- u32 tdinit1 = (100 * CONFIG_DRAM_CLK) / 1000 + 1; /* 100ns */ +- u32 tdinit2 = (11 * CONFIG_DRAM_CLK) + 1; /* 11us */ +- u32 tdinit3 = (1 * CONFIG_DRAM_CLK) + 1; /* 1us */ +- +- u8 twtp = tcwl + 4 + twr + 1; +- /* +- * The code below for twr2rd and trd2wr follows the IP core's +- * document from ZynqMP and i.MX7. The BSP has both number +- * substracted by 2. +- */ +- u8 twr2rd = tcwl + 4 + 1 + twtr; +- u8 trd2wr = tcl + 4 + (tcksrea >> 1) - tcwl + 1; +- +- /* set mode registers */ +- for (i = 0; i < ARRAY_SIZE(mr_lpddr3); i++) +- writel(mr_lpddr3[i], &mctl_phy->mr[i]); +- +- /* set DRAM timing */ +- writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | tras, +- &mctl_ctl->dramtmg[0]); +- writel((txp << 16) | (trtp << 8) | trc, &mctl_ctl->dramtmg[1]); +- writel((tcwl << 24) | (tcl << 16) | (trd2wr << 8) | twr2rd, +- &mctl_ctl->dramtmg[2]); +- writel((tmrw << 20) | (tmrd << 12) | tmod, &mctl_ctl->dramtmg[3]); +- writel((trcd << 24) | (tccd << 16) | (trrd << 8) | trp, +- &mctl_ctl->dramtmg[4]); +- writel((tcksrx << 24) | (tcksre << 16) | (tckesr << 8) | tcke, +- &mctl_ctl->dramtmg[5]); +- /* Value suggested by ZynqMP manual and used by libdram */ +- writel((txp + 2) | 0x02020000, &mctl_ctl->dramtmg[6]); +- writel((txsfast << 24) | (txsabort << 16) | (txsdll << 8) | txs, +- &mctl_ctl->dramtmg[8]); +- writel(txsr, &mctl_ctl->dramtmg[14]); +- +- clrsetbits_le32(&mctl_ctl->init[0], (3 << 30), (1 << 30)); +- writel(0, &mctl_ctl->dfimisc); +- clrsetbits_le32(&mctl_ctl->rankctl, 0xff0, 0x660); +- +- /* +- * Set timing registers of the PHY. +- * Note: the PHY is clocked 2x from the DRAM frequency. +- */ +- writel((trrd << 25) | (tras << 17) | (trp << 9) | (trtp << 1), +- &mctl_phy->dtpr[0]); +- writel((tfaw << 17) | 0x28000400 | (tmrd << 1), &mctl_phy->dtpr[1]); +- writel(((txs << 6) - 1) | (tcke << 17), &mctl_phy->dtpr[2]); +- writel(((txsdll << 22) - (0x1 << 16)) | twtr_sa | (tcksrea << 8), +- &mctl_phy->dtpr[3]); +- writel((txp << 1) | (trfc << 17) | 0x800, &mctl_phy->dtpr[4]); +- writel((trc << 17) | (trcd << 9) | (twtr << 1), &mctl_phy->dtpr[5]); +- writel(0x0505, &mctl_phy->dtpr[6]); +- +- /* Configure DFI timing */ +- writel(tcl | 0x2000200 | (t_rdata_en << 16) | 0x808000, +- &mctl_ctl->dfitmg0); +- writel(0x040201, &mctl_ctl->dfitmg1); +- +- /* Configure PHY timing */ +- writel(tdinit0 | (tdinit1 << 20), &mctl_phy->ptr[3]); +- writel(tdinit2 | (tdinit3 << 18), &mctl_phy->ptr[4]); +- +- /* set refresh timing */ +- writel((trefi << 16) | trfc, &mctl_ctl->rfshtmg); +-} +- + static void mctl_sys_init(struct dram_para *para) + { + struct sunxi_ccm_reg * const ccm = +@@ -735,12 +589,14 @@ unsigned long sunxi_dram_init(void) + (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE; + struct dram_para para = { + .clk = CONFIG_DRAM_CLK, ++#ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3 + .type = SUNXI_DRAM_TYPE_LPDDR3, + .ranks = 2, + .cols = 11, + .rows = 14, + .dx_read_delays = SUN50I_H6_DX_READ_DELAYS, + .dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS, ++#endif + }; + + unsigned long size; +diff --git a/arch/arm/mach-sunxi/dram_timings/Makefile b/arch/arm/mach-sunxi/dram_timings/Makefile +index 278a8a14cc..c3e74362eb 100644 +--- a/arch/arm/mach-sunxi/dram_timings/Makefile ++++ b/arch/arm/mach-sunxi/dram_timings/Makefile +@@ -1,3 +1,4 @@ + obj-$(CONFIG_SUNXI_DRAM_DDR3_1333) += ddr3_1333.o + obj-$(CONFIG_SUNXI_DRAM_LPDDR3_STOCK) += lpddr3_stock.o + obj-$(CONFIG_SUNXI_DRAM_DDR2_V3S) += ddr2_v3s.o ++obj-$(CONFIG_SUNXI_DRAM_H6_LPDDR3) += h6_lpddr3_1333.o +diff --git a/arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c b/arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c +new file mode 100644 +index 0000000000..1000860113 +--- /dev/null ++++ b/arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c +@@ -0,0 +1,132 @@ ++/* ++ * sun50i H6 LPDDR3 timings ++ * ++ * (C) Copyright 2017 Icenowy Zheng ++ * ++ * SPDX-License-Identifier: GPL-2.0+ ++ */ ++ ++#include ++#include ++#include ++ ++static u32 mr_lpddr3[12] = { ++ 0x00000000, 0x00000043, 0x0000001a, 0x00000001, ++ 0x00000000, 0x00000000, 0x00000048, 0x00000000, ++ 0x00000000, 0x00000000, 0x00000000, 0x00000003, ++}; ++ ++/* TODO: flexible timing */ ++void mctl_set_timing_params(struct dram_para *para) ++{ ++ struct sunxi_mctl_ctl_reg * const mctl_ctl = ++ (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE; ++ struct sunxi_mctl_phy_reg * const mctl_phy = ++ (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY0_BASE; ++ int i; ++ ++ u8 tccd = 2; ++ u8 tfaw = max(ns_to_t(50), 4); ++ u8 trrd = max(ns_to_t(10), 2); ++ u8 trcd = max(ns_to_t(24), 2); ++ u8 trc = ns_to_t(70); ++ u8 txp = max(ns_to_t(8), 2); ++ u8 twtr = max(ns_to_t(8), 2); ++ u8 trtp = max(ns_to_t(8), 2); ++ u8 twr = max(ns_to_t(15), 2); ++ u8 trp = ns_to_t(18); ++ u8 tras = ns_to_t(42); ++ u8 twtr_sa = ns_to_t(5); ++ u8 tcksrea = ns_to_t(11); ++ u16 trefi = ns_to_t(3900) / 32; ++ u16 trfc = ns_to_t(210); ++ u16 txsr = ns_to_t(220); ++ ++ if (CONFIG_DRAM_CLK % 400 == 0) { ++ /* Round up these parameters */ ++ twtr_sa++; ++ tcksrea++; ++ } ++ ++ u8 tmrw = 5; ++ u8 tmrd = 5; ++ u8 tmod = 12; ++ u8 tcke = 3; ++ u8 tcksrx = 5; ++ u8 tcksre = 5; ++ u8 tckesr = 5; ++ u8 trasmax = CONFIG_DRAM_CLK / 60; ++ u8 txs = 4; ++ u8 txsdll = 4; ++ u8 txsabort = 4; ++ u8 txsfast = 4; ++ ++ u8 tcl = 5; /* CL 10 */ ++ u8 tcwl = 3; /* CWL 6 */ ++ u8 t_rdata_en = twtr_sa + 8; ++ ++ u32 tdinit0 = (200 * CONFIG_DRAM_CLK) + 1; /* 200us */ ++ u32 tdinit1 = (100 * CONFIG_DRAM_CLK) / 1000 + 1; /* 100ns */ ++ u32 tdinit2 = (11 * CONFIG_DRAM_CLK) + 1; /* 11us */ ++ u32 tdinit3 = (1 * CONFIG_DRAM_CLK) + 1; /* 1us */ ++ ++ u8 twtp = tcwl + 4 + twr + 1; ++ /* ++ * The code below for twr2rd and trd2wr follows the IP core's ++ * document from ZynqMP and i.MX7. The BSP has both number ++ * substracted by 2. ++ */ ++ u8 twr2rd = tcwl + 4 + 1 + twtr; ++ u8 trd2wr = tcl + 4 + (tcksrea >> 1) - tcwl + 1; ++ ++ /* set mode registers */ ++ for (i = 0; i < ARRAY_SIZE(mr_lpddr3); i++) ++ writel(mr_lpddr3[i], &mctl_phy->mr[i]); ++ ++ /* set DRAM timing */ ++ writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | tras, ++ &mctl_ctl->dramtmg[0]); ++ writel((txp << 16) | (trtp << 8) | trc, &mctl_ctl->dramtmg[1]); ++ writel((tcwl << 24) | (tcl << 16) | (trd2wr << 8) | twr2rd, ++ &mctl_ctl->dramtmg[2]); ++ writel((tmrw << 20) | (tmrd << 12) | tmod, &mctl_ctl->dramtmg[3]); ++ writel((trcd << 24) | (tccd << 16) | (trrd << 8) | trp, ++ &mctl_ctl->dramtmg[4]); ++ writel((tcksrx << 24) | (tcksre << 16) | (tckesr << 8) | tcke, ++ &mctl_ctl->dramtmg[5]); ++ /* Value suggested by ZynqMP manual and used by libdram */ ++ writel((txp + 2) | 0x02020000, &mctl_ctl->dramtmg[6]); ++ writel((txsfast << 24) | (txsabort << 16) | (txsdll << 8) | txs, ++ &mctl_ctl->dramtmg[8]); ++ writel(txsr, &mctl_ctl->dramtmg[14]); ++ ++ clrsetbits_le32(&mctl_ctl->init[0], (3 << 30), (1 << 30)); ++ writel(0, &mctl_ctl->dfimisc); ++ clrsetbits_le32(&mctl_ctl->rankctl, 0xff0, 0x660); ++ ++ /* ++ * Set timing registers of the PHY. ++ * Note: the PHY is clocked 2x from the DRAM frequency. ++ */ ++ writel((trrd << 25) | (tras << 17) | (trp << 9) | (trtp << 1), ++ &mctl_phy->dtpr[0]); ++ writel((tfaw << 17) | 0x28000400 | (tmrd << 1), &mctl_phy->dtpr[1]); ++ writel(((txs << 6) - 1) | (tcke << 17), &mctl_phy->dtpr[2]); ++ writel(((txsdll << 22) - (0x1 << 16)) | twtr_sa | (tcksrea << 8), ++ &mctl_phy->dtpr[3]); ++ writel((txp << 1) | (trfc << 17) | 0x800, &mctl_phy->dtpr[4]); ++ writel((trc << 17) | (trcd << 9) | (twtr << 1), &mctl_phy->dtpr[5]); ++ writel(0x0505, &mctl_phy->dtpr[6]); ++ ++ /* Configure DFI timing */ ++ writel(tcl | 0x2000200 | (t_rdata_en << 16) | 0x808000, ++ &mctl_ctl->dfitmg0); ++ writel(0x040201, &mctl_ctl->dfitmg1); ++ ++ /* Configure PHY timing */ ++ writel(tdinit0 | (tdinit1 << 20), &mctl_phy->ptr[3]); ++ writel(tdinit2 | (tdinit3 << 18), &mctl_phy->ptr[4]); ++ ++ /* set refresh timing */ ++ writel((trefi << 16) | trfc, &mctl_ctl->rfshtmg); ++} +diff --git a/configs/beelink_gs1_defconfig b/configs/beelink_gs1_defconfig +index f5de850d08b9f190a476610242d90e5181345ef0..eec4edd4c2dd60bae1c1ca864072dac8fce64996 100644 +--- a/configs/beelink_gs1_defconfig ++++ b/configs/beelink_gs1_defconfig +@@ -3,6 +3,7 @@ CONFIG_ARCH_SUNXI=y + CONFIG_NR_DRAM_BANKS=1 + CONFIG_SPL=y + CONFIG_MACH_SUN50I_H6=y ++CONFIG_SUNXI_DRAM_H6_LPDDR3=y + CONFIG_MMC0_CD_PIN="PF6" + CONFIG_MMC_SUNXI_SLOT_EXTRA=2 + # CONFIG_PSCI_RESET is not set +diff --git a/configs/orangepi_one_plus_defconfig b/configs/orangepi_one_plus_defconfig +index 65537c422f..e4b9f3a1c8 100644 +--- a/configs/orangepi_one_plus_defconfig ++++ b/configs/orangepi_one_plus_defconfig +@@ -3,6 +3,7 @@ CONFIG_ARCH_SUNXI=y + CONFIG_NR_DRAM_BANKS=1 + CONFIG_SPL=y + CONFIG_MACH_SUN50I_H6=y ++CONFIG_SUNXI_DRAM_H6_LPDDR3=y + CONFIG_MMC0_CD_PIN="PF6" + # CONFIG_PSCI_RESET is not set + # CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set +diff --git a/configs/pine_h64_defconfig b/configs/pine_h64_defconfig +index 5ac89b462c..d88a6b1fd8 100644 +--- a/configs/pine_h64_defconfig ++++ b/configs/pine_h64_defconfig +@@ -3,6 +3,7 @@ CONFIG_ARCH_SUNXI=y + CONFIG_NR_DRAM_BANKS=1 + CONFIG_SPL=y + CONFIG_MACH_SUN50I_H6=y ++CONFIG_SUNXI_DRAM_H6_LPDDR3=y + CONFIG_MMC0_CD_PIN="PF6" + CONFIG_MMC_SUNXI_SLOT_EXTRA=2 + # CONFIG_PSCI_RESET is not set +-- +2.22.0 + + +From e61d239ef76aa2e16762a269e9b51472ae3443be Mon Sep 17 00:00:00 2001 +From: Andre Przywara +Date: Thu, 22 Nov 2018 01:40:23 +0000 +Subject: [PATCH 4/7] sunxi: H6: Add DDR3 support to DRAM controller driver + +At the moment the H6 DRAM driver only supports LPDDR3 DRAM. + +Extend the driver to cover DDR3 DRAM as well. + +The changes are partly motivated by looking at the ZynqMP register +documentation, partly by looking at register dumps after boot0/libdram +has initialised the controller. + +Many thanks to Jernej for contributing some fixes! + +Signed-off-by: Andre Przywara +--- + .../include/asm/arch-sunxi/dram_sun50i_h6.h | 7 ++ + arch/arm/mach-sunxi/dram_sun50i_h6.c | 71 +++++++++++++------ + 2 files changed, 57 insertions(+), 21 deletions(-) + +diff --git a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h +index b28ae583c9..8b8085611f 100644 +--- a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h ++++ b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h +@@ -9,6 +9,8 @@ + #ifndef _SUNXI_DRAM_SUN50I_H6_H + #define _SUNXI_DRAM_SUN50I_H6_H + ++#include ++ + enum sunxi_dram_type { + SUNXI_DRAM_TYPE_DDR3 = 3, + SUNXI_DRAM_TYPE_DDR4, +@@ -16,6 +18,11 @@ enum sunxi_dram_type { + SUNXI_DRAM_TYPE_LPDDR3, + }; + ++static inline bool sunxi_dram_is_lpddr(int type) ++{ ++ return type >= SUNXI_DRAM_TYPE_LPDDR2; ++} ++ + /* + * The following information is mainly retrieved by disassembly and some FPGA + * test code of sun50iw3 platform. +diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c +index 697b8af4ce..0436265bdb 100644 +--- a/arch/arm/mach-sunxi/dram_sun50i_h6.c ++++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c +@@ -42,6 +42,7 @@ static void mctl_core_init(struct dram_para *para) + mctl_com_init(para); + switch (para->type) { + case SUNXI_DRAM_TYPE_LPDDR3: ++ case SUNXI_DRAM_TYPE_DDR3: + mctl_set_timing_params(para); + break; + default: +@@ -302,22 +303,37 @@ static void mctl_com_init(struct dram_para *para) + reg_val = 0x3f00; + clrsetbits_le32(&mctl_com->unk_0x008, 0x3f00, reg_val); + +- /* TODO: half DQ, non-LPDDR3 types */ +- writel(MSTR_DEVICETYPE_LPDDR3 | MSTR_BUSWIDTH_FULL | +- MSTR_BURST_LENGTH(8) | MSTR_ACTIVE_RANKS(para->ranks) | +- 0x80000000, &mctl_ctl->mstr); +- writel(DCR_LPDDR3 | DCR_DDR8BANK | 0x400, &mctl_phy->dcr); ++ /* TODO: half DQ, DDR4 */ ++ reg_val = MSTR_BUSWIDTH_FULL | MSTR_BURST_LENGTH(8) | ++ MSTR_ACTIVE_RANKS(para->ranks); ++ if (para->type == SUNXI_DRAM_TYPE_LPDDR3) ++ reg_val |= MSTR_DEVICETYPE_LPDDR3; ++ if (para->type == SUNXI_DRAM_TYPE_DDR3) ++ reg_val |= MSTR_DEVICETYPE_DDR3 | MSTR_2TMODE; ++ writel(reg_val | BIT(31), &mctl_ctl->mstr); ++ ++ if (para->type == SUNXI_DRAM_TYPE_LPDDR3) ++ reg_val = DCR_LPDDR3 | DCR_DDR8BANK; ++ if (para->type == SUNXI_DRAM_TYPE_DDR3) ++ reg_val = DCR_DDR3 | DCR_DDR8BANK | BIT(28); /* 2T mode */ ++ writel(reg_val | 0x400, &mctl_phy->dcr); + + if (para->ranks == 2) + writel(0x0303, &mctl_ctl->odtmap); + else + writel(0x0201, &mctl_ctl->odtmap); + +- /* TODO: non-LPDDR3 types */ +- tmp = para->clk * 7 / 2000; +- reg_val = 0x0400; +- reg_val |= (tmp + 7) << 24; +- reg_val |= (((para->clk < 400) ? 3 : 4) - tmp) << 16; ++ /* TODO: DDR4 */ ++ if (para->type == SUNXI_DRAM_TYPE_LPDDR3) { ++ tmp = para->clk * 7 / 2000; ++ reg_val = 0x0400; ++ reg_val |= (tmp + 7) << 24; ++ reg_val |= (((para->clk < 400) ? 3 : 4) - tmp) << 16; ++ } else if (para->type == SUNXI_DRAM_TYPE_DDR3) { ++ reg_val = 0x06000400; /* TODO?: Use CL - CWL value in [7:0] */ ++ } else if (para->type == SUNXI_DRAM_TYPE_DDR4) { ++ panic("DDR4 not yet supported\n"); ++ } + writel(reg_val, &mctl_ctl->odtcfg); + + /* TODO: half DQ */ +@@ -372,6 +388,9 @@ static void mctl_bit_delay_set(struct dram_para *para) + setbits_le32(&mctl_phy->pgcr[0], BIT(26)); + udelay(1); + ++ if (para->type != SUNXI_DRAM_TYPE_LPDDR3) ++ return; ++ + for (i = 1; i < 14; i++) { + val = readl(&mctl_phy->acbdlr[i]); + val += 0x0a0a0a0a; +@@ -419,7 +438,8 @@ static void mctl_channel_init(struct dram_para *para) + else + clrsetbits_le32(&mctl_phy->dtcr[1], 0x30000, 0x10000); + +- clrbits_le32(&mctl_phy->dtcr[1], BIT(1)); ++ if (sunxi_dram_is_lpddr(para->type)) ++ clrbits_le32(&mctl_phy->dtcr[1], BIT(1)); + if (para->ranks == 2) { + writel(0x00010001, &mctl_phy->rankidr); + writel(0x20000, &mctl_phy->odtcr); +@@ -428,8 +448,11 @@ static void mctl_channel_init(struct dram_para *para) + writel(0x10000, &mctl_phy->odtcr); + } + +- /* TODO: non-LPDDR3 types */ +- clrsetbits_le32(&mctl_phy->dtcr[0], 0xF0000000, 0x10000040); ++ /* set bits [3:0] to 1? 0 not valid in ZynqMP d/s */ ++ if (para->type == SUNXI_DRAM_TYPE_LPDDR3) ++ clrsetbits_le32(&mctl_phy->dtcr[0], 0xF0000000, 0x10000040); ++ else ++ clrsetbits_le32(&mctl_phy->dtcr[0], 0xF0000000, 0x10000000); + if (para->clk <= 792) { + if (para->clk <= 672) { + if (para->clk <= 600) +@@ -459,12 +482,13 @@ static void mctl_channel_init(struct dram_para *para) + writel(0x06060606, &mctl_phy->acbdlr[i]); + } + +- /* TODO: non-LPDDR3 types */ +- mctl_phy_pir_init(PIR_ZCAL | PIR_DCAL | PIR_PHYRST | PIR_DRAMINIT | +- PIR_QSGATE | PIR_RDDSKW | PIR_WRDSKW | PIR_RDEYE | +- PIR_WREYE); ++ val = PIR_ZCAL | PIR_DCAL | PIR_PHYRST | PIR_DRAMINIT | PIR_QSGATE | ++ PIR_RDDSKW | PIR_WRDSKW | PIR_RDEYE | PIR_WREYE; ++ if (para->type == SUNXI_DRAM_TYPE_DDR3) ++ val |= PIR_DRAMRST | PIR_WL; ++ mctl_phy_pir_init(val); + +- /* TODO: non-LPDDR3 types */ ++ /* TODO: DDR4 types ? */ + for (i = 0; i < 4; i++) + writel(0x00000909, &mctl_phy->dx[i].gcr[5]); + +@@ -520,7 +544,8 @@ static void mctl_channel_init(struct dram_para *para) + panic("Error while initializing DRAM PHY!\n"); + } + +- clrsetbits_le32(&mctl_phy->dsgcr, 0xc0, 0x40); ++ if (sunxi_dram_is_lpddr(para->type)) ++ clrsetbits_le32(&mctl_phy->dsgcr, 0xc0, 0x40); + clrbits_le32(&mctl_phy->pgcr[1], 0x40); + clrbits_le32(&mctl_ctl->dfimisc, BIT(0)); + writel(1, &mctl_ctl->swctl); +@@ -589,11 +614,15 @@ unsigned long sunxi_dram_init(void) + (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE; + struct dram_para para = { + .clk = CONFIG_DRAM_CLK, +-#ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3 +- .type = SUNXI_DRAM_TYPE_LPDDR3, + .ranks = 2, + .cols = 11, + .rows = 14, ++#ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3 ++ .type = SUNXI_DRAM_TYPE_LPDDR3, ++ .dx_read_delays = SUN50I_H6_DX_READ_DELAYS, ++ .dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS, ++#elif defined(CONFIG_SUNXI_DRAM_H6_DDR3_1333) ++ .type = SUNXI_DRAM_TYPE_DDR3, + .dx_read_delays = SUN50I_H6_DX_READ_DELAYS, + .dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS, + #endif +-- +2.22.0 + + +From 521305e3a8908ccd9cbc0d3ff317092bc88454c3 Mon Sep 17 00:00:00 2001 +From: Andre Przywara +Date: Sun, 16 Jun 2019 23:24:29 +0100 +Subject: [PATCH 5/7] sunxi: H6: Add DDR3-1333 timings + +Add a routine to program the timing parameters for DDR3-1333 DRAM chips +connected to the H6 DRAM controller. + +The values were gathered from doing back-calculations from a register +dump, trying to match them up with the official JEDEC DDDR3 spec. +If in doubt, the register dump values were taken for now, but the JEDEC +recommendation were added as a comment. + +Many thanks to Jernej for contributing fixes! + +Signed-off-by: Andre Przywara +--- + arch/arm/mach-sunxi/Kconfig | 8 + + arch/arm/mach-sunxi/dram_timings/Makefile | 1 + + .../mach-sunxi/dram_timings/h6_ddr3_1333.c | 144 ++++++++++++++++++ + 3 files changed, 153 insertions(+) + create mode 100644 arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c + +diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig +index e01cb6a09a..12fa3ad811 100644 +--- a/arch/arm/mach-sunxi/Kconfig ++++ b/arch/arm/mach-sunxi/Kconfig +@@ -378,6 +378,14 @@ config SUNXI_DRAM_H6_LPDDR3 + This option is the LPDDR3 timing used by the stock boot0 by + Allwinner. + ++config SUNXI_DRAM_H6_DDR3_1333 ++ bool "DDR3-1333 boot0 timings on the H6 DRAM controller" ++ select SUNXI_DRAM_DDR3 ++ depends on DRAM_SUN50I_H6 ++ ---help--- ++ This option is the DDR3 timing used by the boot0 on H6 TV boxes ++ which use a DDR3-1333 timing. ++ + config SUNXI_DRAM_DDR2_V3S + bool "DDR2 found in V3s chip" + select SUNXI_DRAM_DDR2 +diff --git a/arch/arm/mach-sunxi/dram_timings/Makefile b/arch/arm/mach-sunxi/dram_timings/Makefile +index c3e74362eb..45f2e5a6b9 100644 +--- a/arch/arm/mach-sunxi/dram_timings/Makefile ++++ b/arch/arm/mach-sunxi/dram_timings/Makefile +@@ -2,3 +2,4 @@ obj-$(CONFIG_SUNXI_DRAM_DDR3_1333) += ddr3_1333.o + obj-$(CONFIG_SUNXI_DRAM_LPDDR3_STOCK) += lpddr3_stock.o + obj-$(CONFIG_SUNXI_DRAM_DDR2_V3S) += ddr2_v3s.o + obj-$(CONFIG_SUNXI_DRAM_H6_LPDDR3) += h6_lpddr3_1333.o ++obj-$(CONFIG_SUNXI_DRAM_H6_DDR3_1333) += h6_ddr3_1333.o +diff --git a/arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c b/arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c +new file mode 100644 +index 0000000000..12de4db310 +--- /dev/null ++++ b/arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c +@@ -0,0 +1,144 @@ ++/* ++ * sun50i H6 DDR3-1333 timings, as programmed by Allwinner's boot0 ++ * for some TV boxes with the H6 and DDR3 memory. ++ * ++ * The chips are probably able to be driven by a faster clock, but boot0 ++ * uses a more conservative timing (as usual). ++ * ++ * (C) Copyright 2018,2019 Arm Ltd. ++ * based on previous work by: ++ * (C) Copyright 2017 Icenowy Zheng ++ * ++ * References used: ++ * - JEDEC DDR3 SDRAM standard: JESD79-3F.pdf ++ * - Samsung K4B2G0446D datasheet ++ * - ZynqMP UG1087 register DDRC/PHY documentation ++ * ++ * Many thanks to Jernej Skrabec for contributing some fixes! ++ * ++ * SPDX-License-Identifier: GPL-2.0+ ++ */ ++ ++#include ++#include ++#include ++ ++/* ++ * Only the first four are used for DDR3(?) ++ * MR0: BL8, seq. read burst, no test, fast exit (DLL on), no DLL reset, ++ * CAS latency (CL): 11, write recovery (WR): 12 ++ * MR1: DLL enabled, output strength RZQ/6, Rtt_norm RZQ/2, ++ * write levelling disabled, TDQS disabled, output buffer enabled ++ * MR2: manual full array self refresh, dynamic ODT off, ++ * CAS write latency (CWL): 8 ++ */ ++static u32 mr_ddr3[7] = { ++ 0x00001c70, 0x00000040, 0x00000018, 0x00000000, ++ 0x00000000, 0x00000400, 0x00000848, ++}; ++ ++/* TODO: flexible timing */ ++void mctl_set_timing_params(struct dram_para *para) ++{ ++ struct sunxi_mctl_ctl_reg * const mctl_ctl = ++ (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE; ++ struct sunxi_mctl_phy_reg * const mctl_phy = ++ (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY0_BASE; ++ int i; ++ ++ u8 tccd = 2; /* JEDEC: 4nCK */ ++ u8 tfaw = ns_to_t(50); /* JEDEC: 40 ns */ ++ u8 trrd = max(ns_to_t(10), 2); /* JEDEC: max(7.5 ns, 4nCK) */ ++ u8 trcd = ns_to_t(15); /* JEDEC: 13.75 ns */ ++ u8 trc = ns_to_t(53); /* JEDEC: 48.75 ns */ ++ u8 txp = max(ns_to_t(8), 2); /* JEDEC: max(6 ns, 3nCK) */ ++ u8 twtr = max(ns_to_t(8), 2); /* JEDEC: max(7.5 ns, 4nCK) */ ++ u8 trtp = max(ns_to_t(8), 2); /* JEDEC: max(7.5 ns, 4nCK) */ ++ u8 twr = max(ns_to_t(15), 2); /* ? */ ++ u8 trp = ns_to_t(15); /* JEDEC: >= 13.75 ns */ ++ u8 tras = ns_to_t(38); /* JEDEC >= 35 ns, <= 9*trefi */ ++ u8 twtr_sa = 2; /* ? */ ++ u8 tcksrea = 4; /* ? */ ++ u16 trefi = ns_to_t(7800) / 32; /* JEDEC: 7.8us@Tcase <= 85C */ ++ u16 trfc = ns_to_t(350); /* JEDEC: 160 ns for 2Gb */ ++ u16 txsr = 4; /* ? */ ++ ++ u8 tmrw = 0; /* ? */ ++ u8 tmrd = 4; ++ u8 tmod = 12; ++ u8 tcke = 3; ++ u8 tcksrx = 5; ++ u8 tcksre = 5; ++ u8 tckesr = tcke + 1; ++ u8 trasmax = 24; /* JEDEC: tREFI * 9 */ ++ u8 txs = ns_to_t(360) / 32; /* JEDEC: max(5nCK,tRFC+10ns) */ ++ u8 txsdll = 4; /* JEDEC: 512 nCK */ ++ u8 txsabort = 4; /* ? */ ++ u8 txsfast = 4; /* ? */ ++ u8 tcl = 6; /* JEDEC: 11 / 2 => 6 */ ++ u8 tcwl = 4; /* JEDEC: 8 */ ++ u8 t_rdata_en = 7; /* ? */ ++ ++ u32 tdinit0 = (500 * CONFIG_DRAM_CLK) + 1; /* 500us */ ++ u32 tdinit1 = (360 * CONFIG_DRAM_CLK) / 1000 + 1; ++ u32 tdinit2 = (200 * CONFIG_DRAM_CLK) + 1; ++ u32 tdinit3 = (1 * CONFIG_DRAM_CLK) + 1; /* 1us */ ++ ++ u8 twtp = tcwl + 2 + twr; /* (WL + BL / 2 + tWR) / 2 */ ++ u8 twr2rd = tcwl + 2 + twtr; /* (WL + BL / 2 + tWTR) / 2 */ ++ u8 trd2wr = 5; /* (RL + BL / 2 + 2 - WL) / 2 */ ++ ++ if (tcl + 1 >= trtp + trp) ++ trtp = tcl + 2 - trp; ++ ++ /* set mode registers */ ++ for (i = 0; i < ARRAY_SIZE(mr_ddr3); i++) ++ writel(mr_ddr3[i], &mctl_phy->mr[i]); ++ ++ /* set DRAM timing */ ++ writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | tras, ++ &mctl_ctl->dramtmg[0]); ++ writel((txp << 16) | (trtp << 8) | trc, &mctl_ctl->dramtmg[1]); ++ writel((tcwl << 24) | (tcl << 16) | (trd2wr << 8) | twr2rd, ++ &mctl_ctl->dramtmg[2]); ++ writel((tmrw << 20) | (tmrd << 12) | tmod, &mctl_ctl->dramtmg[3]); ++ writel((trcd << 24) | (tccd << 16) | (trrd << 8) | trp, ++ &mctl_ctl->dramtmg[4]); ++ writel((tcksrx << 24) | (tcksre << 16) | (tckesr << 8) | tcke, ++ &mctl_ctl->dramtmg[5]); ++ /* Value suggested by ZynqMP manual and used by libdram */ ++ writel((txp + 2) | 0x02020000, &mctl_ctl->dramtmg[6]); ++ writel((txsfast << 24) | (txsabort << 16) | (txsdll << 8) | txs, ++ &mctl_ctl->dramtmg[8]); ++ writel(txsr, &mctl_ctl->dramtmg[14]); ++ ++ clrsetbits_le32(&mctl_ctl->init[0], (3 << 30), (1 << 30)); ++ writel(0, &mctl_ctl->dfimisc); ++ clrsetbits_le32(&mctl_ctl->rankctl, 0xff0, 0x660); ++ ++ /* ++ * Set timing registers of the PHY. ++ * Note: the PHY is clocked 2x from the DRAM frequency. ++ */ ++ writel((trrd << 25) | (tras << 17) | (trp << 9) | (trtp << 1), ++ &mctl_phy->dtpr[0]); ++ writel((tfaw << 17) | 0x28000400 | (tmrd << 1), &mctl_phy->dtpr[1]); ++ writel(((txs << 6) - 1) | (tcke << 17), &mctl_phy->dtpr[2]); ++ writel(((txsdll << 22) - (0x1 << 16)) | twtr_sa | (tcksrea << 8), ++ &mctl_phy->dtpr[3]); ++ writel((txp << 1) | (trfc << 17) | 0x800, &mctl_phy->dtpr[4]); ++ writel((trc << 17) | (trcd << 9) | (twtr << 1), &mctl_phy->dtpr[5]); ++ writel(0x0505, &mctl_phy->dtpr[6]); ++ ++ /* Configure DFI timing */ ++ writel(tcl | 0x2000200 | (t_rdata_en << 16) | 0x808000, ++ &mctl_ctl->dfitmg0); ++ writel(0x040201, &mctl_ctl->dfitmg1); ++ ++ /* Configure PHY timing. Zynq uses different registers. */ ++ writel(tdinit0 | (tdinit1 << 20), &mctl_phy->ptr[3]); ++ writel(tdinit2 | (tdinit3 << 18), &mctl_phy->ptr[4]); ++ ++ /* set refresh timing */ ++ writel((trefi << 16) | trfc, &mctl_ctl->rfshtmg); ++} +-- +2.22.0 + + +From e507bb6f7125e463560ff6b98dcae39e11ec5a2a Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Mon, 19 Nov 2018 00:53:37 +0000 +Subject: [PATCH 6/7] sunxi: H6: Add DDR3 DRAM delay values + +Add some basic line delay values to be used with DDR3 DRAM chips on +some H6 TV boxes. +Taken from a register dump after boot0 initialised the DRAM. +Put them as the default delay values for DDR3 DRAM until we know better. + +Signed-off-by: Jernej Skrabec +Signed-off-by: Andre Przywara +--- + arch/arm/mach-sunxi/dram_sun50i_h6.c | 23 +++++++++++++++++------ + 1 file changed, 17 insertions(+), 6 deletions(-) + +diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c +index 0436265bdb..5fe53bf463 100644 +--- a/arch/arm/mach-sunxi/dram_sun50i_h6.c ++++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c +@@ -597,17 +597,28 @@ unsigned long mctl_calc_size(struct dram_para *para) + return (1ULL << (para->cols + para->rows + 3)) * 4 * para->ranks; + } + +-#define SUN50I_H6_DX_WRITE_DELAYS \ ++#define SUN50I_H6_LPDDR3_DX_WRITE_DELAYS \ + {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0 }, \ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }} +-#define SUN50I_H6_DX_READ_DELAYS \ ++#define SUN50I_H6_LPDDR3_DX_READ_DELAYS \ + {{ 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0 }, \ + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0 }, \ + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0 }, \ + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0 }} + ++#define SUN50I_H6_DDR3_DX_WRITE_DELAYS \ ++ {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ ++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ ++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ ++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }} ++#define SUN50I_H6_DDR3_DX_READ_DELAYS \ ++ {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ ++ { 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0 }, \ ++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ ++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }} ++ + unsigned long sunxi_dram_init(void) + { + struct sunxi_mctl_com_reg * const mctl_com = +@@ -619,12 +630,12 @@ unsigned long sunxi_dram_init(void) + .rows = 14, + #ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3 + .type = SUNXI_DRAM_TYPE_LPDDR3, +- .dx_read_delays = SUN50I_H6_DX_READ_DELAYS, +- .dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS, ++ .dx_read_delays = SUN50I_H6_LPDDR3_DX_READ_DELAYS, ++ .dx_write_delays = SUN50I_H6_LPDDR3_DX_WRITE_DELAYS, + #elif defined(CONFIG_SUNXI_DRAM_H6_DDR3_1333) + .type = SUNXI_DRAM_TYPE_DDR3, +- .dx_read_delays = SUN50I_H6_DX_READ_DELAYS, +- .dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS, ++ .dx_read_delays = SUN50I_H6_DDR3_DX_READ_DELAYS, ++ .dx_write_delays = SUN50I_H6_DDR3_DX_WRITE_DELAYS, + #endif + }; + +-- +2.22.0 + + +From ed8d12f7e9b86a20221030ba8609e05308813c5e Mon Sep 17 00:00:00 2001 +From: Andre Przywara +Date: Fri, 16 Nov 2018 01:38:32 +0000 +Subject: [PATCH 7/7] sunxi: H6: Add basic Eachlink H6 Mini support + +The Eachlink H6 Mini is a modestly priced TV box, using the Allwinner H6 +SoC. It comes with 4GB of DRAM (3GB usable) and 32GB of eMMC in the +typical TV box enclosure. +This adds a basic device tree and defconfig for it. + +It contrast to the other supported H6 boards the H6 Mini uses DDR3 DRAM +chips (not LPDDR3), which require a different DRAM controller setup. + +Signed-off-by: Andre Przywara +--- + arch/arm/dts/Makefile | 1 + + arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts | 116 ++++++++++++++++++++ + configs/eachlink_h6_mini_defconfig | 17 +++ + 3 files changed, 134 insertions(+) + create mode 100644 arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts + create mode 100644 configs/eachlink_h6_mini_defconfig + +diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile +index 528fb909d5..c463aca190 100644 +--- a/arch/arm/dts/Makefile ++++ b/arch/arm/dts/Makefile +@@ -507,6 +507,7 @@ dtb-$(CONFIG_MACH_SUN50I_H5) += \ + sun50i-h5-orangepi-prime.dtb \ + sun50i-h5-orangepi-zero-plus2.dtb + dtb-$(CONFIG_MACH_SUN50I_H6) += \ ++ sun50i-h6-eachlink-h6-mini.dtb \ + sun50i-h6-orangepi-3.dtb \ + sun50i-h6-orangepi-lite2.dtb \ + sun50i-h6-orangepi-one-plus.dtb \ +diff --git a/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts b/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts +new file mode 100644 +index 0000000000..c217955a39 +--- /dev/null ++++ b/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts +@@ -0,0 +1,116 @@ ++// SPDX-License-Identifier: (GPL-2.0+ or MIT) ++/* ++ * Copyright (c) 2018 Arm Ltd. ++ * based on work by: ++ * Copyright (c) 2017 Icenowy Zheng ++ */ ++ ++/dts-v1/; ++ ++#include "sun50i-h6.dtsi" ++ ++#include ++ ++/ { ++ model = "Eachlink H6 Mini"; ++ compatible = "eachlink,h6-mini", "allwinner,sun50i-h6"; ++ ++ aliases { ++ serial0 = &uart0; ++ }; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++ ++ connector { ++ compatible = "hdmi-connector"; ++ type = "a"; ++ ++ port { ++ hdmi_con_in: endpoint { ++ remote-endpoint = <&hdmi_out_con>; ++ }; ++ }; ++ }; ++ ++ reg_vcc3v3: vcc3v3 { ++ compatible = "regulator-fixed"; ++ regulator-name = "vcc3v3"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ }; ++ ++ reg_vcc5v: vcc5v { ++ /* board wide 5V supply directly from the DC jack */ ++ compatible = "regulator-fixed"; ++ regulator-name = "vcc-5v"; ++ regulator-min-microvolt = <5000000>; ++ regulator-max-microvolt = <5000000>; ++ regulator-always-on; ++ }; ++}; ++ ++&de { ++ status = "okay"; ++}; ++ ++&hdmi { ++ status = "okay"; ++}; ++ ++&hdmi_out { ++ hdmi_out_con: endpoint { ++ remote-endpoint = <&hdmi_con_in>; ++ }; ++}; ++ ++&ehci0 { ++ phys = <&usb2phy 0>; ++ status = "okay"; ++}; ++ ++&ehci3 { ++ status = "okay"; ++}; ++ ++&mmc0 { ++ vmmc-supply = <®_vcc3v3>; ++ cd-gpios = <&pio 5 6 GPIO_ACTIVE_LOW>; ++ bus-width = <4>; ++ status = "okay"; ++}; ++ ++&mmc2 { ++ vmmc-supply = <®_vcc3v3>; ++ vqmmc-supply = <®_vcc3v3>; ++ non-removable; ++ cap-mmc-hw-reset; ++ bus-width = <8>; ++ status = "okay"; ++}; ++ ++&ohci0 { ++ phys = <&usb2phy 0>; ++ status = "okay"; ++}; ++ ++&ohci3 { ++ status = "okay"; ++}; ++ ++&uart0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart0_ph_pins>; ++ status = "okay"; ++}; ++ ++&usb2otg { ++ dr_mode = "host"; ++ status = "okay"; ++}; ++ ++&usb2phy { ++ usb0_vbus-supply = <®_vcc5v>; ++ status = "okay"; ++}; +diff --git a/configs/eachlink_h6_mini_defconfig b/configs/eachlink_h6_mini_defconfig +new file mode 100644 +index 0000000000..d471a24dd5 +--- /dev/null ++++ b/configs/eachlink_h6_mini_defconfig +@@ -0,0 +1,16 @@ ++CONFIG_ARM=y ++CONFIG_ARCH_SUNXI=y ++CONFIG_SPL=y ++CONFIG_MACH_SUN50I_H6=y ++CONFIG_DRAM_CLK=648 ++CONFIG_SUNXI_DRAM_H6_DDR3_1333=y ++CONFIG_MMC0_CD_PIN="PF6" ++# CONFIG_PSCI_RESET is not set ++CONFIG_MMC_SUNXI_SLOT_EXTRA=2 ++CONFIG_NR_DRAM_BANKS=1 ++# CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set ++CONFIG_SPL_TEXT_BASE=0x20060 ++# CONFIG_CMD_FLASH is not set ++# CONFIG_SPL_DOS_PARTITION is not set ++# CONFIG_SPL_EFI_PARTITION is not set ++CONFIG_DEFAULT_DEVICE_TREE="sun50i-h6-eachlink-h6-mini" +-- +2.22.0 + +From 0229ee3784c97944165f7469d5e45b8ee3f6b226 Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Sat, 29 Jun 2019 17:30:40 +0200 +Subject: [PATCH] sunxi: h6: dram: Add support for half DQ + +Signed-off-by: Jernej Skrabec +--- + .../include/asm/arch-sunxi/dram_sun50i_h6.h | 1 + + arch/arm/mach-sunxi/dram_sun50i_h6.c | 74 ++++++++++++------- + 2 files changed, 50 insertions(+), 25 deletions(-) + +diff --git a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h +index 8b8085611f..4812ee4eeb 100644 +--- a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h ++++ b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h +@@ -315,6 +315,7 @@ struct dram_para { + u8 cols; + u8 rows; + u8 ranks; ++ u8 bus_full_width; + const u8 dx_read_delays[NR_OF_BYTE_LANES][RD_LINES_PER_BYTE_LANE]; + const u8 dx_write_delays[NR_OF_BYTE_LANES][WR_LINES_PER_BYTE_LANE]; + }; +diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c +index 5fe53bf463..bdb227fcc3 100644 +--- a/arch/arm/mach-sunxi/dram_sun50i_h6.c ++++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c +@@ -201,6 +201,9 @@ static void mctl_set_addrmap(struct dram_para *para) + u8 rows = para->rows; + u8 ranks = para->ranks; + ++ if (!para->bus_full_width) ++ cols -= 1; ++ + /* Ranks */ + if (ranks == 2) + mctl_ctl->addrmap[0] = rows + cols - 3; +@@ -213,6 +216,10 @@ static void mctl_set_addrmap(struct dram_para *para) + /* Columns */ + mctl_ctl->addrmap[2] = 0; + switch (cols) { ++ case 7: ++ mctl_ctl->addrmap[3] = 0x1F1F1F00; ++ mctl_ctl->addrmap[4] = 0x1F1F; ++ break; + case 8: + mctl_ctl->addrmap[3] = 0x1F1F0000; + mctl_ctl->addrmap[4] = 0x1F1F; +@@ -303,13 +310,16 @@ static void mctl_com_init(struct dram_para *para) + reg_val = 0x3f00; + clrsetbits_le32(&mctl_com->unk_0x008, 0x3f00, reg_val); + +- /* TODO: half DQ, DDR4 */ +- reg_val = MSTR_BUSWIDTH_FULL | MSTR_BURST_LENGTH(8) | +- MSTR_ACTIVE_RANKS(para->ranks); ++ /* TODO: DDR4 */ ++ reg_val = MSTR_BURST_LENGTH(8) | MSTR_ACTIVE_RANKS(para->ranks); + if (para->type == SUNXI_DRAM_TYPE_LPDDR3) + reg_val |= MSTR_DEVICETYPE_LPDDR3; + if (para->type == SUNXI_DRAM_TYPE_DDR3) + reg_val |= MSTR_DEVICETYPE_DDR3 | MSTR_2TMODE; ++ if (para->bus_full_width) ++ reg_val |= MSTR_BUSWIDTH_FULL; ++ else ++ reg_val |= MSTR_BUSWIDTH_HALF; + writel(reg_val | BIT(31), &mctl_ctl->mstr); + + if (para->type == SUNXI_DRAM_TYPE_LPDDR3) +@@ -336,7 +346,10 @@ static void mctl_com_init(struct dram_para *para) + } + writel(reg_val, &mctl_ctl->odtcfg); + +- /* TODO: half DQ */ ++ if (!para->bus_full_width) { ++ writel(0x0, &mctl_phy->dx[2].gcr[0]); ++ writel(0x0, &mctl_phy->dx[3].gcr[0]); ++ } + } + + static void mctl_bit_delay_set(struct dram_para *para) +@@ -517,22 +530,31 @@ static void mctl_channel_init(struct dram_para *para) + + if (readl(&mctl_phy->pgsr[0]) & 0x400000) + { +- /* +- * Detect single rank. +- * TODO: also detect half DQ. +- */ ++ /* Check for single rank and optionally half DQ. */ + if ((readl(&mctl_phy->dx[0].rsr[0]) & 0x3) == 2 && +- (readl(&mctl_phy->dx[1].rsr[0]) & 0x3) == 2 && +- (readl(&mctl_phy->dx[2].rsr[0]) & 0x3) == 2 && +- (readl(&mctl_phy->dx[3].rsr[0]) & 0x3) == 2) { ++ (readl(&mctl_phy->dx[1].rsr[0]) & 0x3) == 2) { + para->ranks = 1; ++ ++ if ((readl(&mctl_phy->dx[2].rsr[0]) & 0x3) != 2 || ++ (readl(&mctl_phy->dx[3].rsr[0]) & 0x3) != 2) ++ para->bus_full_width = 0; ++ + /* Restart DRAM initialization from scratch. */ + mctl_core_init(para); + return; + } +- else { +- panic("This DRAM setup is currently not supported.\n"); ++ ++ /* Check for dual rank and half DQ */ ++ if ((readl(&mctl_phy->dx[0].rsr[0]) & 0x3) == 0 && ++ (readl(&mctl_phy->dx[1].rsr[0]) & 0x3) == 0) { ++ para->bus_full_width = 0; ++ ++ /* Restart DRAM initialization from scratch. */ ++ mctl_core_init(para); ++ return; + } ++ ++ panic("This DRAM setup is currently not supported.\n"); + } + + if (readl(&mctl_phy->pgsr[0]) & 0xff00000) { +@@ -560,11 +582,8 @@ static void mctl_channel_init(struct dram_para *para) + + static void mctl_auto_detect_dram_size(struct dram_para *para) + { +- /* TODO: non-LPDDR3, half DQ */ +- /* +- * Detect rank number by the code in mctl_channel_init. Furtherly +- * when DQ detection is available it will also be executed there. +- */ ++ /* TODO: non-(LP)DDR3 */ ++ /* Detect rank number and half DQ by the code in mctl_channel_init. */ + mctl_core_init(para); + + /* detect row address bits */ +@@ -573,8 +592,9 @@ static void mctl_auto_detect_dram_size(struct dram_para *para) + mctl_core_init(para); + + for (para->rows = 13; para->rows < 18; para->rows++) { +- /* 8 banks, 8 bit per byte and 32 bit width */ +- if (mctl_mem_matches((1 << (para->rows + para->cols + 5)))) ++ /* 8 banks, 8 bit per byte and 16/32 bit width */ ++ if (mctl_mem_matches((1 << (para->rows + para->cols + ++ 4 + para->bus_full_width)))) + break; + } + +@@ -583,18 +603,21 @@ static void mctl_auto_detect_dram_size(struct dram_para *para) + mctl_core_init(para); + + for (para->cols = 8; para->cols < 11; para->cols++) { +- /* 8 bits per byte and 32 bit width */ +- if (mctl_mem_matches(1 << (para->cols + 2))) ++ /* 8 bits per byte and 16/32 bit width */ ++ if (mctl_mem_matches(1 << (para->cols + 1 + ++ para->bus_full_width))) + break; + } + } + + unsigned long mctl_calc_size(struct dram_para *para) + { +- /* TODO: non-LPDDR3, half DQ */ ++ u8 width = para->bus_full_width ? 4 : 2; ++ ++ /* TODO: non-(LP)DDR3 */ + +- /* 8 banks, 32-bit (4 byte) data width */ +- return (1ULL << (para->cols + para->rows + 3)) * 4 * para->ranks; ++ /* 8 banks */ ++ return (1ULL << (para->cols + para->rows + 3)) * width * para->ranks; + } + + #define SUN50I_H6_LPDDR3_DX_WRITE_DELAYS \ +@@ -628,6 +651,7 @@ unsigned long sunxi_dram_init(void) + .ranks = 2, + .cols = 11, + .rows = 14, ++ .bus_full_width = 1, + #ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3 + .type = SUNXI_DRAM_TYPE_LPDDR3, + .dx_read_delays = SUN50I_H6_LPDDR3_DX_READ_DELAYS, +-- +2.22.0 + diff --git a/projects/Allwinner/devices/H6/patches/u-boot/007-ethernet-hack.patch b/projects/Allwinner/devices/H6/patches/u-boot/007-ethernet-hack.patch new file mode 100644 index 0000000000..ba7e838575 --- /dev/null +++ b/projects/Allwinner/devices/H6/patches/u-boot/007-ethernet-hack.patch @@ -0,0 +1,51 @@ +From 9e077d6ebaa5a76b0c91cbe6aabb66106e95caba Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Thu, 11 Jul 2019 23:28:58 +0200 +Subject: [PATCH] ethernet hack + +--- + arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts b/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts +index c217955a39..5c0099f1a7 100644 +--- a/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts ++++ b/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts +@@ -16,6 +16,7 @@ + compatible = "eachlink,h6-mini", "allwinner,sun50i-h6"; + + aliases { ++ ethernet0 = &emac; + serial0 = &uart0; + }; + +@@ -55,6 +56,12 @@ + status = "okay"; + }; + ++&emac { ++ phy-mode = "rmii"; ++ phy-handle = <&ext_rmii_phy>; ++ status = "okay"; ++}; ++ + &hdmi { + status = "okay"; + }; +@@ -74,6 +81,13 @@ + status = "okay"; + }; + ++&mdio { ++ ext_rmii_phy: ethernet-phy@1 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <1>; ++ }; ++}; ++ + &mmc0 { + vmmc-supply = <®_vcc3v3>; + cd-gpios = <&pio 5 6 GPIO_ACTIVE_LOW>; +-- +2.22.0 + diff --git a/projects/Allwinner/linux/linux.aarch64.conf b/projects/Allwinner/linux/linux.aarch64.conf index 2e23739b5f..f53104c3cb 100644 --- a/projects/Allwinner/linux/linux.aarch64.conf +++ b/projects/Allwinner/linux/linux.aarch64.conf @@ -1796,6 +1796,7 @@ CONFIG_SWPHY=y # # MII PHY device drivers # +CONFIG_AC200_PHY=y # CONFIG_AMD_PHY is not set # CONFIG_AQUANTIA_PHY is not set # CONFIG_AX88796B_PHY is not set @@ -2761,6 +2762,7 @@ CONFIG_MFD_CORE=y # CONFIG_MFD_BCM590XX is not set # CONFIG_MFD_BD9571MWV is not set # CONFIG_MFD_AC100 is not set +CONFIG_MFD_AC200=y CONFIG_MFD_AXP20X=y CONFIG_MFD_AXP20X_I2C=y CONFIG_MFD_AXP20X_RSB=y @@ -4571,7 +4573,7 @@ CONFIG_COMMON_CLK_SCPI=y # CONFIG_COMMON_CLK_S2MPS11 is not set # CONFIG_CLK_QORIQ is not set # CONFIG_COMMON_CLK_XGENE is not set -# CONFIG_COMMON_CLK_PWM is not set +CONFIG_COMMON_CLK_PWM=y # CONFIG_COMMON_CLK_VC5 is not set # CONFIG_COMMON_CLK_FIXED_MMIO is not set # CONFIG_CLK_SUNXI is not set @@ -5123,7 +5125,7 @@ CONFIG_PWM=y CONFIG_PWM_SYSFS=y # CONFIG_PWM_FSL_FTM is not set # CONFIG_PWM_PCA9685 is not set -# CONFIG_PWM_SUN4I is not set +CONFIG_PWM_SUN4I=y # # IRQ chip support diff --git a/projects/Allwinner/linux/linux.arm.conf b/projects/Allwinner/linux/linux.arm.conf index 4a7d3c7058..37695336c7 100644 --- a/projects/Allwinner/linux/linux.arm.conf +++ b/projects/Allwinner/linux/linux.arm.conf @@ -1704,6 +1704,7 @@ CONFIG_SWPHY=y # # MII PHY device drivers # +# CONFIG_AC200_PHY is not set # CONFIG_AMD_PHY is not set # CONFIG_AQUANTIA_PHY is not set # CONFIG_AX88796B_PHY is not set @@ -2587,6 +2588,7 @@ CONFIG_MFD_SUN4I_GPADC=y # CONFIG_MFD_BCM590XX is not set # CONFIG_MFD_BD9571MWV is not set # CONFIG_MFD_AC100 is not set +# CONFIG_MFD_AC200 is not set CONFIG_MFD_AXP20X=y CONFIG_MFD_AXP20X_I2C=y CONFIG_MFD_AXP20X_RSB=y diff --git a/projects/Allwinner/patches/linux/0014-AC200.patch b/projects/Allwinner/patches/linux/0014-AC200.patch new file mode 100644 index 0000000000..d2a564cb9a --- /dev/null +++ b/projects/Allwinner/patches/linux/0014-AC200.patch @@ -0,0 +1,708 @@ +From fe3aef9d51c82c286ff33d867b59d0fae9a6dddd Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Fri, 16 Aug 2019 16:38:21 +0200 +Subject: [PATCH 1/2] mfd: Add support for AC200 + +Signed-off-by: Jernej Skrabec +--- + drivers/mfd/Kconfig | 9 ++ + drivers/mfd/Makefile | 1 + + drivers/mfd/ac200.c | 150 +++++++++++++++++++++++++++ + include/linux/mfd/ac200.h | 209 ++++++++++++++++++++++++++++++++++++++ + 4 files changed, 369 insertions(+) + create mode 100644 drivers/mfd/ac200.c + create mode 100644 include/linux/mfd/ac200.h + +diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig +index 4a07afe50b35..576db86dfa79 100644 +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -178,6 +178,15 @@ config MFD_AC100 + This driver include only the core APIs. You have to select individual + components like codecs or RTC under the corresponding menus. + ++config MFD_AC200 ++ tristate "X-Powers AC200" ++ select MFD_CORE ++ depends on I2C ++ help ++ If you say Y here you get support for the X-Powers AC200 IC. ++ This driver include only the core APIs. You have to select individual ++ components like Ethernet PHY or RTC under the corresponding menus. ++ + config MFD_AXP20X + tristate + select MFD_CORE +diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile +index 7b6a6aa4fe42..7981edcbff4a 100644 +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -143,6 +143,7 @@ obj-$(CONFIG_MFD_DA9052_SPI) += da9052-spi.o + obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o + + obj-$(CONFIG_MFD_AC100) += ac100.o ++obj-$(CONFIG_MFD_AC200) += ac200.o + obj-$(CONFIG_MFD_AXP20X) += axp20x.o + obj-$(CONFIG_MFD_AXP20X_I2C) += axp20x-i2c.o + obj-$(CONFIG_MFD_AXP20X_RSB) += axp20x-rsb.o +diff --git a/drivers/mfd/ac200.c b/drivers/mfd/ac200.c +new file mode 100644 +index 000000000000..3c95be216cf0 +--- /dev/null ++++ b/drivers/mfd/ac200.c +@@ -0,0 +1,150 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * MFD core driver for X-Powers' AC200 IC ++ * ++ * The AC200 is a chip which is co-packaged with Allwinner H6 SoC and ++ * includes analog audio codec, analog TV encoder, ethernet PHY, eFuse ++ * and RTC. ++ * ++ * Copyright (c) 2019 Jernej Skrabec ++ * ++ * Based on AC100 driver with following copyrights: ++ * Copyright (2016) Chen-Yu Tsai ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static const struct regmap_range_cfg ac200_range_cfg[] = { ++ { ++ .range_min = AC200_SYS_VERSION, ++ .range_max = AC200_IC_CHARA1, ++ .selector_reg = AC200_TWI_REG_ADDR_H, ++ .selector_mask = 0xff, ++ .selector_shift = 0, ++ .window_start = 0, ++ .window_len = 256, ++ } ++}; ++ ++static const struct regmap_config ac200_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 16, ++ .ranges = ac200_range_cfg, ++ .num_ranges = ARRAY_SIZE(ac200_range_cfg), ++ .max_register = AC200_IC_CHARA1, ++}; ++ ++static struct mfd_cell ac200_cells[] = { ++ { ++ .name = "ac200-codec", ++ .of_compatible = "x-powers,ac200-codec", ++ }, { ++ .name = "ac200-efuse", ++ .of_compatible = "x-powers,ac200-efuse", ++ }, { ++ .name = "ac200-ephy", ++ .of_compatible = "x-powers,ac200-ephy", ++ }, { ++ .name = "ac200-rtc", ++ .of_compatible = "x-powers,ac200-rtc", ++ }, { ++ .name = "ac200-tve", ++ .of_compatible = "x-powers,ac200-tve", ++ }, ++}; ++ ++static int ac200_i2c_probe(struct i2c_client *i2c, ++ const struct i2c_device_id *id) ++{ ++ struct device *dev = &i2c->dev; ++ struct ac200_dev *ac200; ++ int ret; ++ ++ ac200 = devm_kzalloc(dev, sizeof(*ac200), GFP_KERNEL); ++ if (!ac200) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(i2c, ac200); ++ ++ ac200->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(ac200->clk)) { ++ ret = PTR_ERR(ac200->clk); ++ dev_err(dev, "Can't obtain the clock: %d\n", ret); ++ return ret; ++ } ++ ++ ac200->regmap = devm_regmap_init_i2c(i2c, &ac200_regmap_config); ++ if (IS_ERR(ac200->regmap)) { ++ ret = PTR_ERR(ac200->regmap); ++ dev_err(dev, "Regmap init failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(ac200->clk); ++ if (ret) ++ return ret; ++ ++ ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0); ++ if (ret) ++ goto err; ++ ++ ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 1); ++ if (ret) ++ goto err; ++ ++ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, ac200_cells, ++ ARRAY_SIZE(ac200_cells), NULL, 0, NULL); ++ if (ret) { ++ dev_err(dev, "Failed to add MFD devices: %d\n", ret); ++ goto err; ++ } ++ ++ return 0; ++ ++err: ++ clk_disable_unprepare(ac200->clk); ++ return ret; ++} ++ ++static int ac200_i2c_remove(struct i2c_client *i2c) ++{ ++ struct ac200_dev *ac200 = i2c_get_clientdata(i2c); ++ ++ regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0); ++ ++ clk_disable_unprepare(ac200->clk); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id ac200_ids[] = { ++ { "ac200", }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(i2c, ac200_ids); ++ ++static const struct of_device_id ac200_of_match[] = { ++ { .compatible = "x-powers,ac200" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, ac200_of_match); ++ ++static struct i2c_driver ac200_i2c_driver = { ++ .driver = { ++ .name = "ac200", ++ .of_match_table = of_match_ptr(ac200_of_match), ++ }, ++ .probe = ac200_i2c_probe, ++ .remove = ac200_i2c_remove, ++ .id_table = ac200_ids, ++}; ++module_i2c_driver(ac200_i2c_driver); ++ ++MODULE_DESCRIPTION("MFD core driver for AC200"); ++MODULE_AUTHOR("Jernej Skrabec "); ++MODULE_LICENSE("GPL v2"); +diff --git a/include/linux/mfd/ac200.h b/include/linux/mfd/ac200.h +new file mode 100644 +index 000000000000..48a21d5c354a +--- /dev/null ++++ b/include/linux/mfd/ac200.h +@@ -0,0 +1,209 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * AC200 register list ++ * ++ * Copyright (C) 2019 Jernej Skrabec ++ */ ++ ++#ifndef __LINUX_MFD_AC200_H ++#define __LINUX_MFD_AC200_H ++ ++#include ++#include ++ ++/* interface registers (can be accessed from any page) */ ++#define AC200_TWI_CHANGE_TO_RSB 0x3E ++#define AC200_TWI_PAD_DELAY 0xC4 ++#define AC200_TWI_REG_ADDR_H 0xFE ++ ++/* General registers */ ++#define AC200_SYS_VERSION 0x0000 ++#define AC200_SYS_CONTROL 0x0002 ++#define AC200_SYS_IRQ_ENABLE 0x0004 ++#define AC200_SYS_IRQ_STATUS 0x0006 ++#define AC200_SYS_CLK_CTL 0x0008 ++#define AC200_SYS_DLDO_OSC_CTL 0x000A ++#define AC200_SYS_PLL_CTL0 0x000C ++#define AC200_SYS_PLL_CTL1 0x000E ++#define AC200_SYS_AUDIO_CTL0 0x0010 ++#define AC200_SYS_AUDIO_CTL1 0x0012 ++#define AC200_SYS_EPHY_CTL0 0x0014 ++#define AC200_SYS_EPHY_CTL1 0x0016 ++#define AC200_SYS_TVE_CTL0 0x0018 ++#define AC200_SYS_TVE_CTL1 0x001A ++ ++/* Audio Codec registers */ ++#define AC200_AC_SYS_CLK_CTL 0x2000 ++#define AC200_SYS_MOD_RST 0x2002 ++#define AC200_SYS_SAMP_CTL 0x2004 ++#define AC200_I2S_CTL 0x2100 ++#define AC200_I2S_CLK 0x2102 ++#define AC200_I2S_FMT0 0x2104 ++#define AC200_I2S_FMT1 0x2108 ++#define AC200_I2S_MIX_SRC 0x2114 ++#define AC200_I2S_MIX_GAIN 0x2116 ++#define AC200_I2S_DACDAT_DVC 0x2118 ++#define AC200_I2S_ADCDAT_DVC 0x211A ++#define AC200_AC_DAC_DPC 0x2200 ++#define AC200_AC_DAC_MIX_SRC 0x2202 ++#define AC200_AC_DAC_MIX_GAIN 0x2204 ++#define AC200_DACA_OMIXER_CTRL 0x2220 ++#define AC200_OMIXER_SR 0x2222 ++#define AC200_LINEOUT_CTRL 0x2224 ++#define AC200_AC_ADC_DPC 0x2300 ++#define AC200_MBIAS_CTRL 0x2310 ++#define AC200_ADC_MIC_CTRL 0x2320 ++#define AC200_ADCMIXER_SR 0x2322 ++#define AC200_ANALOG_TUNING0 0x232A ++#define AC200_ANALOG_TUNING1 0x232C ++#define AC200_AC_AGC_SEL 0x2480 ++#define AC200_ADC_DAPLCTRL 0x2500 ++#define AC200_ADC_DAPRCTRL 0x2502 ++#define AC200_ADC_DAPLSTA 0x2504 ++#define AC200_ADC_DAPRSTA 0x2506 ++#define AC200_ADC_DAPLTL 0x2508 ++#define AC200_ADC_DAPRTL 0x250A ++#define AC200_ADC_DAPLHAC 0x250C ++#define AC200_ADC_DAPLLAC 0x250E ++#define AC200_ADC_DAPRHAC 0x2510 ++#define AC200_ADC_DAPRLAC 0x2512 ++#define AC200_ADC_DAPLDT 0x2514 ++#define AC200_ADC_DAPLAT 0x2516 ++#define AC200_ADC_DAPRDT 0x2518 ++#define AC200_ADC_DAPRAT 0x251A ++#define AC200_ADC_DAPNTH 0x251C ++#define AC200_ADC_DAPLHNAC 0x251E ++#define AC200_ADC_DAPLLNAC 0x2520 ++#define AC200_ADC_DAPRHNAC 0x2522 ++#define AC200_ADC_DAPRLNAC 0x2524 ++#define AC200_AC_DAPHHPFC 0x2526 ++#define AC200_AC_DAPLHPFC 0x2528 ++#define AC200_AC_DAPOPT 0x252A ++#define AC200_AC_DAC_DAPCTRL 0x3000 ++#define AC200_AC_DRC_HHPFC 0x3002 ++#define AC200_AC_DRC_LHPFC 0x3004 ++#define AC200_AC_DRC_CTRL 0x3006 ++#define AC200_AC_DRC_LPFHAT 0x3008 ++#define AC200_AC_DRC_LPFLAT 0x300A ++#define AC200_AC_DRC_RPFHAT 0x300C ++#define AC200_AC_DRC_RPFLAT 0x300E ++#define AC200_AC_DRC_LPFHRT 0x3010 ++#define AC200_AC_DRC_LPFLRT 0x3012 ++#define AC200_AC_DRC_RPFHRT 0x3014 ++#define AC200_AC_DRC_RPFLRT 0x3016 ++#define AC200_AC_DRC_LRMSHAT 0x3018 ++#define AC200_AC_DRC_LRMSLAT 0x301A ++#define AC200_AC_DRC_RRMSHAT 0x301C ++#define AC200_AC_DRC_RRMSLAT 0x301E ++#define AC200_AC_DRC_HCT 0x3020 ++#define AC200_AC_DRC_LCT 0x3022 ++#define AC200_AC_DRC_HKC 0x3024 ++#define AC200_AC_DRC_LKC 0x3026 ++#define AC200_AC_DRC_HOPC 0x3028 ++#define AC200_AC_DRC_LOPC 0x302A ++#define AC200_AC_DRC_HLT 0x302C ++#define AC200_AC_DRC_LLT 0x302E ++#define AC200_AC_DRC_HKI 0x3030 ++#define AC200_AC_DRC_LKI 0x3032 ++#define AC200_AC_DRC_HOPL 0x3034 ++#define AC200_AC_DRC_LOPL 0x3036 ++#define AC200_AC_DRC_HET 0x3038 ++#define AC200_AC_DRC_LET 0x303A ++#define AC200_AC_DRC_HKE 0x303C ++#define AC200_AC_DRC_LKE 0x303E ++#define AC200_AC_DRC_HOPE 0x3040 ++#define AC200_AC_DRC_LOPE 0x3042 ++#define AC200_AC_DRC_HKN 0x3044 ++#define AC200_AC_DRC_LKN 0x3046 ++#define AC200_AC_DRC_SFHAT 0x3048 ++#define AC200_AC_DRC_SFLAT 0x304A ++#define AC200_AC_DRC_SFHRT 0x304C ++#define AC200_AC_DRC_SFLRT 0x304E ++#define AC200_AC_DRC_MXGHS 0x3050 ++#define AC200_AC_DRC_MXGLS 0x3052 ++#define AC200_AC_DRC_MNGHS 0x3054 ++#define AC200_AC_DRC_MNGLS 0x3056 ++#define AC200_AC_DRC_EPSHC 0x3058 ++#define AC200_AC_DRC_EPSLC 0x305A ++#define AC200_AC_DRC_HPFHGAIN 0x305E ++#define AC200_AC_DRC_HPFLGAIN 0x3060 ++#define AC200_AC_DRC_BISTCR 0x3100 ++#define AC200_AC_DRC_BISTST 0x3102 ++ ++/* TVE registers */ ++#define AC200_TVE_CTL0 0x4000 ++#define AC200_TVE_CTL1 0x4002 ++#define AC200_TVE_MOD0 0x4004 ++#define AC200_TVE_MOD1 0x4006 ++#define AC200_TVE_DAC_CFG0 0x4008 ++#define AC200_TVE_DAC_CFG1 0x400A ++#define AC200_TVE_YC_DELAY 0x400C ++#define AC200_TVE_YC_FILTER 0x400E ++#define AC200_TVE_BURST_FRQ0 0x4010 ++#define AC200_TVE_BURST_FRQ1 0x4012 ++#define AC200_TVE_FRONT_PORCH 0x4014 ++#define AC200_TVE_BACK_PORCH 0x4016 ++#define AC200_TVE_TOTAL_LINE 0x401C ++#define AC200_TVE_FIRST_ACTIVE 0x401E ++#define AC200_TVE_BLACK_LEVEL 0x4020 ++#define AC200_TVE_BLANK_LEVEL 0x4022 ++#define AC200_TVE_PLUG_EN 0x4030 ++#define AC200_TVE_PLUG_IRQ_EN 0x4032 ++#define AC200_TVE_PLUG_IRQ_STA 0x4034 ++#define AC200_TVE_PLUG_STA 0x4038 ++#define AC200_TVE_PLUG_DEBOUNCE 0x4040 ++#define AC200_TVE_DAC_TEST 0x4042 ++#define AC200_TVE_PLUG_PULSE_LEVEL 0x40F4 ++#define AC200_TVE_PLUG_PULSE_START 0x40F8 ++#define AC200_TVE_PLUG_PULSE_PERIOD 0x40FA ++#define AC200_TVE_IF_CTL 0x5000 ++#define AC200_TVE_IF_TIM0 0x5008 ++#define AC200_TVE_IF_TIM1 0x500A ++#define AC200_TVE_IF_TIM2 0x500C ++#define AC200_TVE_IF_TIM3 0x500E ++#define AC200_TVE_IF_SYNC0 0x5010 ++#define AC200_TVE_IF_SYNC1 0x5012 ++#define AC200_TVE_IF_SYNC2 0x5014 ++#define AC200_TVE_IF_TIM4 0x5016 ++#define AC200_TVE_IF_STATUS 0x5018 ++ ++/* EPHY registers */ ++#define AC200_EPHY_CTL 0x6000 ++#define AC200_EPHY_BIST 0x6002 ++ ++/* eFuse registers (0x8000 - 0x9FFF, layout unknown) */ ++ ++/* RTC registers */ ++#define AC200_LOSC_CTRL0 0xA000 ++#define AC200_LOSC_CTRL1 0xA002 ++#define AC200_LOSC_AUTO_SWT_STA 0xA004 ++#define AC200_INTOSC_CLK_PRESCAL 0xA008 ++#define AC200_RTC_YY_MM_DD0 0xA010 ++#define AC200_RTC_YY_MM_DD1 0xA012 ++#define AC200_RTC_HH_MM_SS0 0xA014 ++#define AC200_RTC_HH_MM_SS1 0xA016 ++#define AC200_ALARM0_CUR_VLU0 0xA024 ++#define AC200_ALARM0_CUR_VLU1 0xA026 ++#define AC200_ALARM0_ENABLE 0xA028 ++#define AC200_ALARM0_IRQ_EN 0xA02C ++#define AC200_ALARM0_IRQ_STA 0xA030 ++#define AC200_ALARM1_WK_HH_MM_SS0 0xA040 ++#define AC200_ALARM1_WK_HH_MM_SS1 0xA042 ++#define AC200_ALARM1_ENABLE 0xA044 ++#define AC200_ALARM1_IRQ_EN 0xA048 ++#define AC200_ALARM1_IRQ_STA 0xA04C ++#define AC200_ALARM_CONFIG 0xA050 ++#define AC200_LOSC_OUT_GATING 0xA060 ++#define AC200_GP_DATA(x) (0xA100 + (x) * 2) ++#define AC200_RTC_DEB 0xA170 ++#define AC200_GPL_HOLD_OUTPUT 0xA180 ++#define AC200_VDD_RTC 0xA190 ++#define AC200_IC_CHARA0 0xA1F0 ++#define AC200_IC_CHARA1 0xA1F2 ++ ++struct ac200_dev { ++ struct clk *clk; ++ struct regmap *regmap; ++}; ++ ++#endif /* __LINUX_MFD_AC200_H */ +-- +2.22.1 + + +From d570d779f96ce5abc1f215eb11bbe0401b539f4b Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Fri, 16 Aug 2019 16:38:57 +0200 +Subject: [PATCH 2/2] net: phy: Add support for AC200 EPHY + +Signed-off-by: Jernej Skrabec +--- + drivers/net/phy/Kconfig | 7 ++ + drivers/net/phy/Makefile | 1 + + drivers/net/phy/ac200.c | 234 +++++++++++++++++++++++++++++++++++++++ + 3 files changed, 242 insertions(+) + create mode 100644 drivers/net/phy/ac200.c + +diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig +index 48ca213c0ada..37012117fc7a 100644 +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -257,6 +257,13 @@ config SFP + depends on HWMON || HWMON=n + select MDIO_I2C + ++config AC200_PHY ++ tristate "AC200 EPHY" ++ depends on NVMEM ++ depends on OF ++ help ++ Fast ethernet PHY as found in X-Powers AC200 multi-function device. ++ + config AMD_PHY + tristate "AMD PHYs" + ---help--- +diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile +index ba07c27e4208..8fab8dfbe94e 100644 +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -47,6 +47,7 @@ obj-$(CONFIG_SFP) += sfp.o + sfp-obj-$(CONFIG_SFP) += sfp-bus.o + obj-y += $(sfp-obj-y) $(sfp-obj-m) + ++obj-$(CONFIG_AC200_PHY) += ac200.o + obj-$(CONFIG_AMD_PHY) += amd.o + aquantia-objs += aquantia_main.o + ifdef CONFIG_HWMON +diff --git a/drivers/net/phy/ac200.c b/drivers/net/phy/ac200.c +new file mode 100644 +index 000000000000..e36af123db43 +--- /dev/null ++++ b/drivers/net/phy/ac200.c +@@ -0,0 +1,234 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/** ++ * Driver for AC200 Ethernet PHY ++ * ++ * Copyright (c) 2019 Jernej Skrabec ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define AC200_EPHY_ID 0x00441400 ++#define AC200_EPHY_ID_MASK 0x0ffffff0 ++ ++/* macros for system ephy control 0 register */ ++#define AC200_EPHY_RESET_INVALID BIT(0) ++#define AC200_EPHY_SYSCLK_GATING BIT(1) ++ ++/* macros for system ephy control 1 register */ ++#define AC200_EPHY_E_EPHY_MII_IO_EN BIT(0) ++#define AC200_EPHY_E_LNK_LED_IO_EN BIT(1) ++#define AC200_EPHY_E_SPD_LED_IO_EN BIT(2) ++#define AC200_EPHY_E_DPX_LED_IO_EN BIT(3) ++ ++/* macros for ephy control register */ ++#define AC200_EPHY_SHUTDOWN BIT(0) ++#define AC200_EPHY_LED_POL BIT(1) ++#define AC200_EPHY_CLK_SEL BIT(2) ++#define AC200_EPHY_ADDR(x) (((x) & 0x1F) << 4) ++#define AC200_EPHY_XMII_SEL BIT(11) ++#define AC200_EPHY_CALIB(x) (((x) & 0xF) << 12) ++ ++struct ac200_ephy_dev { ++ struct phy_driver *ephy; ++ struct regmap *regmap; ++}; ++ ++static char *ac200_phy_name = "AC200 EPHY"; ++ ++static void disable_intelligent_ieee(struct phy_device *phydev) ++{ ++ unsigned int value; ++ ++ phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */ ++ value = phy_read(phydev, 0x17); ++ value &= ~BIT(3); /* disable IEEE */ ++ phy_write(phydev, 0x17, value); ++ phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */ ++} ++ ++static void disable_802_3az_ieee(struct phy_device *phydev) ++{ ++ unsigned int value; ++ ++ phy_write(phydev, 0xd, 0x7); ++ phy_write(phydev, 0xe, 0x3c); ++ phy_write(phydev, 0xd, BIT(14) | 0x7); ++ value = phy_read(phydev, 0xe); ++ value &= ~BIT(1); ++ phy_write(phydev, 0xd, 0x7); ++ phy_write(phydev, 0xe, 0x3c); ++ phy_write(phydev, 0xd, BIT(14) | 0x7); ++ phy_write(phydev, 0xe, value); ++ ++ phy_write(phydev, 0x1f, 0x0200); /* switch to page 2 */ ++ phy_write(phydev, 0x18, 0x0000); ++} ++ ++static int ac200_ephy_config_init(struct phy_device *phydev) ++{ ++ const struct ac200_ephy_dev *priv = phydev->drv->driver_data; ++ unsigned int value; ++ int ret; ++ ++ phy_write(phydev, 0x1f, 0x0100); /* Switch to Page 1 */ ++ phy_write(phydev, 0x12, 0x4824); /* Disable APS */ ++ ++ phy_write(phydev, 0x1f, 0x0200); /* Switch to Page 2 */ ++ phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */ ++ ++ phy_write(phydev, 0x1f, 0x0600); /* Switch to Page 6 */ ++ phy_write(phydev, 0x14, 0x708f); /* PHYAFE TX optimization */ ++ phy_write(phydev, 0x13, 0xF000); /* PHYAFE RX optimization */ ++ phy_write(phydev, 0x15, 0x1530); ++ ++ phy_write(phydev, 0x1f, 0x0800); /* Switch to Page 6 */ ++ phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */ ++ ++ disable_intelligent_ieee(phydev); /* Disable Intelligent IEEE */ ++ disable_802_3az_ieee(phydev); /* Disable 802.3az IEEE */ ++ phy_write(phydev, 0x1f, 0x0000); /* Switch to Page 0 */ ++ ++ value = (phydev->interface == PHY_INTERFACE_MODE_RMII) ? ++ AC200_EPHY_XMII_SEL : 0; ++ ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL, ++ AC200_EPHY_XMII_SEL, value); ++ if (ret) ++ return ret; ++ ++ /* FIXME: This is probably H6 specific */ ++ value = phy_read(phydev, 0x13); ++ value |= BIT(12); ++ phy_write(phydev, 0x13, value); ++ ++ return 0; ++} ++ ++static const struct mdio_device_id __maybe_unused ac200_ephy_phy_tbl[] = { ++ { AC200_EPHY_ID, AC200_EPHY_ID_MASK }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(mdio, ac200_ephy_phy_tbl); ++ ++static int ac200_ephy_probe(struct platform_device *pdev) ++{ ++ struct ac200_dev *ac200 = dev_get_drvdata(pdev->dev.parent); ++ struct device *dev = &pdev->dev; ++ struct ac200_ephy_dev *priv; ++ struct nvmem_cell *calcell; ++ struct phy_driver *ephy; ++ u16 *caldata, calib; ++ size_t callen; ++ int ret; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ ephy = devm_kzalloc(dev, sizeof(*ephy), GFP_KERNEL); ++ if (!ephy) ++ return -ENOMEM; ++ ++ calcell = devm_nvmem_cell_get(dev, "ephy_calib"); ++ if (IS_ERR(calcell)) { ++ dev_err(dev, "Unable to find calibration data!\n"); ++ return PTR_ERR(calcell); ++ } ++ ++ caldata = nvmem_cell_read(calcell, &callen); ++ if (IS_ERR(caldata)) { ++ dev_err(dev, "Unable to read calibration data!\n"); ++ return PTR_ERR(caldata); ++ } ++ ++ if (callen != 2) { ++ dev_err(dev, "Calibration data has wrong length: 2 != %lu\n", ++ callen); ++ kfree(caldata); ++ return -EINVAL; ++ } ++ ++ calib = *caldata + 3; ++ kfree(caldata); ++ ++ ephy->phy_id = AC200_EPHY_ID; ++ ephy->phy_id_mask = AC200_EPHY_ID_MASK; ++ ephy->name = ac200_phy_name; ++ ephy->driver_data = priv; ++ ephy->soft_reset = genphy_soft_reset; ++ ephy->config_init = ac200_ephy_config_init; ++ ephy->suspend = genphy_suspend; ++ ephy->resume = genphy_resume; ++ ++ priv->ephy = ephy; ++ priv->regmap = ac200->regmap; ++ platform_set_drvdata(pdev, priv); ++ ++ ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL0, ++ AC200_EPHY_RESET_INVALID | ++ AC200_EPHY_SYSCLK_GATING); ++ if (ret) ++ return ret; ++ ++ ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL1, ++ AC200_EPHY_E_EPHY_MII_IO_EN | ++ AC200_EPHY_E_LNK_LED_IO_EN | ++ AC200_EPHY_E_SPD_LED_IO_EN | ++ AC200_EPHY_E_DPX_LED_IO_EN); ++ if (ret) ++ return ret; ++ ++ ret = regmap_write(ac200->regmap, AC200_EPHY_CTL, ++ AC200_EPHY_LED_POL | ++ AC200_EPHY_CLK_SEL | ++ AC200_EPHY_ADDR(1) | ++ AC200_EPHY_CALIB(calib)); ++ if (ret) ++ return ret; ++ ++ ret = phy_driver_register(priv->ephy, THIS_MODULE); ++ if (ret) { ++ dev_err(dev, "Unable to register phy\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ac200_ephy_remove(struct platform_device *pdev) ++{ ++ struct ac200_ephy_dev *priv = platform_get_drvdata(pdev); ++ ++ phy_driver_unregister(priv->ephy); ++ ++ regmap_write(priv->regmap, AC200_EPHY_CTL, AC200_EPHY_SHUTDOWN); ++ regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1, 0); ++ regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0, 0); ++ ++ return 0; ++} ++ ++static const struct of_device_id ac200_ephy_match[] = { ++ { .compatible = "x-powers,ac200-ephy" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, ac200_ephy_match); ++ ++static struct platform_driver ac200_ephy_driver = { ++ .probe = ac200_ephy_probe, ++ .remove = ac200_ephy_remove, ++ .driver = { ++ .name = "ac200-ephy", ++ .of_match_table = ac200_ephy_match, ++ }, ++}; ++module_platform_driver(ac200_ephy_driver); ++ ++MODULE_AUTHOR("Jernej Skrabec "); ++MODULE_DESCRIPTION("AC200 Ethernet PHY driver"); ++MODULE_LICENSE("GPL"); +-- +2.22.1 +