From a3d45cfc3120ed4365896105595cda13e954aacc Mon Sep 17 00:00:00 2001 From: chraac Date: Fri, 10 May 2024 23:39:33 +0800 Subject: [PATCH] add gmac to edge --- config/kernel/linux-sunxi64-current.config | 2 - config/kernel/linux-sunxi64-edge.config | 4 + ...-sun50i-h618-orangepi-zero2w-add-dtb.patch | 12 +- ...-sun50i-h618-orangepi-zero2w-add-dtb.patch | 35 +- .../drv-gmac-sun50i-h616-gmac-driver.patch | 4638 +++++++++++++++++ .../drv-pwm-sun50i-h616-enhance-pwm.patch | 1392 +++++ patch/kernel/archive/sunxi-6.7/series.armbian | 2 + patch/kernel/archive/sunxi-6.7/series.conf | 2 + 8 files changed, 6061 insertions(+), 26 deletions(-) create mode 100644 patch/kernel/archive/sunxi-6.7/patches.armbian/drv-gmac-sun50i-h616-gmac-driver.patch create mode 100644 patch/kernel/archive/sunxi-6.7/patches.armbian/drv-pwm-sun50i-h616-enhance-pwm.patch diff --git a/config/kernel/linux-sunxi64-current.config b/config/kernel/linux-sunxi64-current.config index 505498606bdd..14a5bba52bc3 100644 --- a/config/kernel/linux-sunxi64-current.config +++ b/config/kernel/linux-sunxi64-current.config @@ -21,7 +21,6 @@ CONFIG_PAHOLE_VERSION=125 CONFIG_IRQ_WORK=y CONFIG_BUILDTIME_TABLE_SORT=y CONFIG_THREAD_INFO_IN_TASK=y -CONFIG_DEBUG_INFO=y # # General setup @@ -2439,7 +2438,6 @@ CONFIG_NET_VENDOR_ALACRITECH=y CONFIG_NET_VENDOR_ALLWINNER=y CONFIG_SUN4I_EMAC=y CONFIG_SUNXI_GMAC=m -CONFIG_PWM_SUNXI_ENHANCE=y # CONFIG_ALTERA_TSE is not set CONFIG_NET_VENDOR_AMAZON=y CONFIG_NET_VENDOR_AMD=y diff --git a/config/kernel/linux-sunxi64-edge.config b/config/kernel/linux-sunxi64-edge.config index f23b8baec173..b51e6c0ee4f5 100644 --- a/config/kernel/linux-sunxi64-edge.config +++ b/config/kernel/linux-sunxi64-edge.config @@ -2446,6 +2446,7 @@ CONFIG_MDIO=m CONFIG_NET_VENDOR_ALACRITECH=y CONFIG_NET_VENDOR_ALLWINNER=y CONFIG_SUN4I_EMAC=y +CONFIG_SUNXI_GMAC=m # CONFIG_ALTERA_TSE is not set CONFIG_NET_VENDOR_AMAZON=y CONFIG_NET_VENDOR_AMD=y @@ -2552,6 +2553,7 @@ CONFIG_FIXED_PHY=y # MII PHY device drivers # CONFIG_AC200_PHY=m +CONFIG_AC200_PHY_SUNXI=m CONFIG_AMD_PHY=m CONFIG_ADIN_PHY=m CONFIG_ADIN1100_PHY=m @@ -4059,6 +4061,7 @@ CONFIG_MFD_SUN4I_GPADC=y CONFIG_MFD_BD9571MWV=m CONFIG_MFD_AC100=y CONFIG_MFD_AC200=m +CONFIG_MFD_AC200_SUNXI=m CONFIG_MFD_AXP20X=y CONFIG_MFD_AXP20X_I2C=y CONFIG_MFD_AXP20X_RSB=y @@ -7533,6 +7536,7 @@ CONFIG_PWM_CLK=m CONFIG_PWM_NTXEC=m CONFIG_PWM_PCA9685=m CONFIG_PWM_SUN4I=m +CONFIG_PWM_SUNXI_ENHANCE=y CONFIG_PWM_XILINX=m # diff --git a/patch/kernel/archive/sunxi-6.6/patches.armbian/arm64-dts-sun50i-h618-orangepi-zero2w-add-dtb.patch b/patch/kernel/archive/sunxi-6.6/patches.armbian/arm64-dts-sun50i-h618-orangepi-zero2w-add-dtb.patch index 5a3f8f52becc..91c3b595dd4f 100644 --- a/patch/kernel/archive/sunxi-6.6/patches.armbian/arm64-dts-sun50i-h618-orangepi-zero2w-add-dtb.patch +++ b/patch/kernel/archive/sunxi-6.6/patches.armbian/arm64-dts-sun50i-h618-orangepi-zero2w-add-dtb.patch @@ -12,16 +12,6 @@ add log for thermal zone reference the sun50i-h618-cpu-dvfs.dtsi in zero2w remove i2c bind - -sync overlays - -Revert "sync overlays" - -This reverts commit 71cea923579a633fa0770da4db38a4861100777c. - -try fix ac200 - -remove unused node in dts --- arch/arm64/boot/dts/allwinner/Makefile | 1 + .../allwinner/sun50i-h618-orangepi-zero2w.dts | 444 ++++++++++++++++++ @@ -450,9 +440,9 @@ index 000000000000..3d02ab9ab14f + /omit-if-no-ref/ + i2c4_ph_pins: i2c4-ph-pins { + pins = "PH6", "PH7"; ++ + function = "i2c4"; + }; -+ + /omit-if-no-ref/ + uart2_pi_pins: uart2-pi-pins { + pins = "PI5", "PI6"; diff --git a/patch/kernel/archive/sunxi-6.7/patches.armbian/arm64-dts-sun50i-h618-orangepi-zero2w-add-dtb.patch b/patch/kernel/archive/sunxi-6.7/patches.armbian/arm64-dts-sun50i-h618-orangepi-zero2w-add-dtb.patch index eb3509e4e478..91c3b595dd4f 100644 --- a/patch/kernel/archive/sunxi-6.7/patches.armbian/arm64-dts-sun50i-h618-orangepi-zero2w-add-dtb.patch +++ b/patch/kernel/archive/sunxi-6.7/patches.armbian/arm64-dts-sun50i-h618-orangepi-zero2w-add-dtb.patch @@ -1,4 +1,4 @@ -From 09a088f5c8e28c394a74730481adf9cec434fc93 Mon Sep 17 00:00:00 2001 +From d60a776c242b74a48fcdcb7bb4185cc803194231 Mon Sep 17 00:00:00 2001 From: chraac Date: Fri, 15 Mar 2024 12:30:26 +0800 Subject: [PATCH] orangepi-zero2w add dtb @@ -14,12 +14,12 @@ reference the sun50i-h618-cpu-dvfs.dtsi in zero2w remove i2c bind --- arch/arm64/boot/dts/allwinner/Makefile | 1 + - .../allwinner/sun50i-h618-orangepi-zero2w.dts | 435 ++++++++++++++++++ - 2 files changed, 436 insertions(+) + .../allwinner/sun50i-h618-orangepi-zero2w.dts | 444 ++++++++++++++++++ + 2 files changed, 445 insertions(+) create mode 100644 arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts diff --git a/arch/arm64/boot/dts/allwinner/Makefile b/arch/arm64/boot/dts/allwinner/Makefile -index 8b504ee408e78..a957365edc1aa 100644 +index 8b504ee408e7..a957365edc1a 100644 --- a/arch/arm64/boot/dts/allwinner/Makefile +++ b/arch/arm64/boot/dts/allwinner/Makefile @@ -56,5 +56,6 @@ dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h616-bigtreetech-cb1-sd.dtb @@ -31,10 +31,10 @@ index 8b504ee408e78..a957365edc1aa 100644 subdir-y := $(dts-dirs) overlay diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts b/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts new file mode 100644 -index 0000000000000..c09cc24a8b279 +index 000000000000..3d02ab9ab14f --- /dev/null +++ b/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts -@@ -0,0 +1,435 @@ +@@ -0,0 +1,444 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (C) 2023 Arm Ltd. @@ -328,16 +328,25 @@ index 0000000000000..c09cc24a8b279 + pinctrl-names = "default"; + pinctrl-0 = <&i2c3_pa_pins>; + -+ ac200_x: mfd@10 { ++ ac200: mfd@10 { + compatible = "x-powers,ac200"; + reg = <0x10>; + clocks = <&ac200_pwm_clk>; + // ephy id -+ nvmem-cells = <&ephy_calibration>; -+ nvmem-cell-names = "calibration"; -+ -+ ac200_ephy: phy { -+ compatible = "x-powers,ac200-ephy"; ++ interrupt-parent = <&pio>; ++ interrupts = <1 20 IRQ_TYPE_LEVEL_LOW>; ++ interrupt-controller; ++ #interrupt-cells = <1>; ++ nvmem-cells = <&ac200_bg>; ++ nvmem-cell-names = "bandgap"; ++ ++ ac200_ephy_ctl: syscon { ++ compatible = "x-powers,ac200-ephy-ctl"; ++ #clock-cells = <0>; ++ #reset-cells = <0>; ++ phy-mode = "rmii"; ++ nvmem-cells = <&ephy_calibration>; ++ nvmem-cell-names = "calibration"; + status = "okay"; + }; + }; @@ -431,9 +440,9 @@ index 0000000000000..c09cc24a8b279 + /omit-if-no-ref/ + i2c4_ph_pins: i2c4-ph-pins { + pins = "PH6", "PH7"; ++ + function = "i2c4"; + }; -+ + /omit-if-no-ref/ + uart2_pi_pins: uart2-pi-pins { + pins = "PI5", "PI6"; diff --git a/patch/kernel/archive/sunxi-6.7/patches.armbian/drv-gmac-sun50i-h616-gmac-driver.patch b/patch/kernel/archive/sunxi-6.7/patches.armbian/drv-gmac-sun50i-h616-gmac-driver.patch new file mode 100644 index 000000000000..4eecd857b522 --- /dev/null +++ b/patch/kernel/archive/sunxi-6.7/patches.armbian/drv-gmac-sun50i-h616-gmac-driver.patch @@ -0,0 +1,4638 @@ +From 68b0307cc9a0d55384466da693846c1eac4f2ae5 Mon Sep 17 00:00:00 2001 +From: chraac +Date: Wed, 1 May 2024 14:32:00 +0800 +Subject: [PATCH] add gmac driver + +commit: +97b476246bc79756423cf6c1d4907606c9633d4b +--- + .../arm64/boot/dts/allwinner/sun50i-h616.dtsi | 25 +- + .../allwinner/sun50i-h618-orangepi-zero2w.dts | 41 +- + drivers/gpio/gpiolib-of.c | 29 +- + drivers/mfd/Kconfig | 10 + + drivers/mfd/Makefile | 1 + + drivers/mfd/sunxi-ac200.c | 288 +++ + drivers/net/ethernet/allwinner/Kconfig | 8 + + drivers/net/ethernet/allwinner/Makefile | 2 + + drivers/net/ethernet/allwinner/sunxi-gmac.c | 2219 +++++++++++++++++ + drivers/net/ethernet/allwinner/sunxi-gmac.h | 270 ++ + .../net/ethernet/allwinner/sunxi_gmac_ops.c | 768 ++++++ + drivers/net/phy/Kconfig | 8 + + drivers/net/phy/Makefile | 1 + + drivers/net/phy/sunxi-ephy.c | 518 ++++ + include/linux/mfd/ac200.h | 213 ++ + include/linux/of_gpio.h | 18 + + 16 files changed, 4373 insertions(+), 46 deletions(-) + create mode 100644 drivers/mfd/sunxi-ac200.c + create mode 100644 drivers/net/ethernet/allwinner/sunxi-gmac.c + create mode 100644 drivers/net/ethernet/allwinner/sunxi-gmac.h + create mode 100644 drivers/net/ethernet/allwinner/sunxi_gmac_ops.c + create mode 100644 drivers/net/phy/sunxi-ephy.c + create mode 100644 include/linux/mfd/ac200.h + +diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi +index 3a2a1c4f327a..90e55a6aef1d 100644 +--- a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi ++++ b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi +@@ -209,7 +209,7 @@ video-codec@1c0e000 { + + syscon: syscon@3000000 { + compatible = "allwinner,sun50i-h616-system-control"; +- reg = <0x03000000 0x1000>; ++ reg = <0x03000000 0x30>,<0x03000038 0x0fc8>; + #address-cells = <1>; + #size-cells = <1>; + ranges; +@@ -708,19 +708,28 @@ mdio0: mdio { + }; + + emac1: ethernet@5030000 { +- compatible = "allwinner,sun50i-h616-emac"; +- syscon = <&syscon 1>; +- reg = <0x05030000 0x10000>; ++ compatible = "allwinner,sunxi-gmac"; ++ reg = <0x05030000 0x10000>, ++ <0x03000034 0x4>; ++ reg-names = "gmac1_reg","ephy_reg"; + interrupts = ; +- interrupt-names = "macirq"; ++ interrupt-names = "gmacirq"; + resets = <&ccu RST_BUS_EMAC1>; + reset-names = "stmmaceth"; +- clocks = <&ccu CLK_BUS_EMAC1>; +- clock-names = "stmmaceth"; ++ clocks = <&ccu CLK_BUS_EMAC1>,<&ccu CLK_EMAC_25M>; ++ clock-names = "bus-emac1","emac-25m"; ++ pinctrl-0 = <&rmii_pins>; ++ pinctrl-names = "default"; ++ tx-delay = <7>; ++ rx-delay = <31>; ++ phy-rst; ++ gmac-power0; ++ gmac-power1; ++ gmac-power2; + status = "disabled"; + + mdio1: mdio { +- compatible = "snps,dwmac-mdio"; ++ compatible = "ethernet-phy-ieee802.3-c22"; + #address-cells = <1>; + #size-cells = <0>; + }; +diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts b/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts +index fab468861eef..fb068dc7c0db 100644 +--- a/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts ++++ b/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts +@@ -200,19 +200,23 @@ &mmc1 { + }; + + &emac0 { ++ status = "disabled"; ++}; ++ ++&emac1 { + pinctrl-names = "default"; +- pinctrl-0 = <&ext_rgmii_pins>; +- phy-handle = <&ext_rgmii_phy>; +- allwinner,tx-delay-ps = <700>; +- phy-mode = "rgmii-rxid"; ++ pinctrl-0 = <&rmii_pins>; ++ phy-mode = "rmii"; ++ phy-handle = <&rmii_phy>; + phy-supply = <®_dldo1>; ++ allwinner,rx-delay-ps = <3100>; ++ allwinner,tx-delay-ps = <700>; + status = "okay"; + }; + +-&mdio0 { +- ext_rgmii_phy: ethernet-phy@1 { ++&mdio1 { ++ rmii_phy: ethernet-phy@1 { + compatible = "ethernet-phy-ieee802.3-c22"; +- motorcomm,clk-out-frequency-hz = <125000000>; + reg = <1>; + }; + }; +@@ -313,25 +317,16 @@ &i2c3 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c3_pa_pins>; + +- ac200: mfd@10 { +- compatible = "x-powers,ac200"; ++ ac200_x: mfd@10 { ++ compatible = "x-powers,ac200-sunxi"; + reg = <0x10>; + clocks = <&ac200_pwm_clk>; + // ephy id +- interrupt-parent = <&pio>; +- interrupts = <1 20 IRQ_TYPE_LEVEL_LOW>; +- interrupt-controller; +- #interrupt-cells = <1>; +- nvmem-cells = <&ac200_bg>; +- nvmem-cell-names = "bandgap"; +- +- ac200_ephy_ctl: syscon { +- compatible = "x-powers,ac200-ephy-ctl"; +- #clock-cells = <0>; +- #reset-cells = <0>; +- phy-mode = "rmii"; +- nvmem-cells = <&ephy_calibration>; +- nvmem-cell-names = "calibration"; ++ nvmem-cells = <&ephy_calibration>; ++ nvmem-cell-names = "calibration"; ++ ++ ac200_ephy: phy { ++ compatible = "x-powers,ac200-ephy-sunxi"; + status = "okay"; + }; + }; +diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c +index d9525d95e818..6842f90f9efe 100644 +--- a/drivers/gpio/gpiolib-of.c ++++ b/drivers/gpio/gpiolib-of.c +@@ -25,21 +25,6 @@ + #include "gpiolib.h" + #include "gpiolib-of.h" + +-/* +- * This is Linux-specific flags. By default controllers' and Linux' mapping +- * match, but GPIO controllers are free to translate their own flags to +- * Linux-specific in their .xlate callback. Though, 1:1 mapping is recommended. +- */ +-enum of_gpio_flags { +- OF_GPIO_ACTIVE_LOW = 0x1, +- OF_GPIO_SINGLE_ENDED = 0x2, +- OF_GPIO_OPEN_DRAIN = 0x4, +- OF_GPIO_TRANSITORY = 0x8, +- OF_GPIO_PULL_UP = 0x10, +- OF_GPIO_PULL_DOWN = 0x20, +- OF_GPIO_PULL_DISABLE = 0x40, +-}; +- + /** + * of_gpio_named_count() - Count GPIOs for a device + * @np: device node to count GPIOs for +@@ -398,6 +383,20 @@ static struct gpio_desc *of_get_named_gpiod_flags(const struct device_node *np, + return desc; + } + ++int of_get_named_gpio_flags(const struct device_node *np, const char *list_name, ++ int index, enum of_gpio_flags *flags) ++{ ++ struct gpio_desc *desc; ++ ++ desc = of_get_named_gpiod_flags(np, list_name, index, flags); ++ ++ if (IS_ERR(desc)) ++ return PTR_ERR(desc); ++ else ++ return desc_to_gpio(desc); ++} ++EXPORT_SYMBOL_GPL(of_get_named_gpio_flags); ++ + /** + * of_get_named_gpio() - Get a GPIO number to use with GPIO API + * @np: device node to get GPIO from +diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig +index caed5e75d11f..e532334e024f 100644 +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -203,6 +203,16 @@ config MFD_AC200 + This driver include only the core APIs. You have to select individual + components like Ethernet PHY or codec under the corresponding menus. + ++config MFD_AC200_SUNXI ++ tristate "X-Powers AC200 (Sunxi)" ++ select MFD_CORE ++ depends on I2C ++ depends on PWM_SUNXI_ENHANCE ++ 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 0c3b4aaf4eb7..bc180cf44009 100644 +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -141,6 +141,7 @@ obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o + + obj-$(CONFIG_MFD_AC100) += ac100.o + obj-$(CONFIG_MFD_AC200) += ac200.o ++obj-$(CONFIG_MFD_AC200_SUNXI) += sunxi-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/sunxi-ac200.c b/drivers/mfd/sunxi-ac200.c +new file mode 100644 +index 000000000000..7c5b0e0523cf +--- /dev/null ++++ b/drivers/mfd/sunxi-ac200.c +@@ -0,0 +1,288 @@ ++// 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) 2020 Jernej Skrabec ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SUNXI_AC300_KEY (0x1 << 8) ++ ++/* Interrupts */ ++#define AC200_IRQ_RTC 0 ++#define AC200_IRQ_EPHY 1 ++#define AC200_IRQ_TVE 2 ++ ++/* IRQ enable register */ ++#define AC200_SYS_IRQ_ENABLE_OUT_EN BIT(15) ++#define AC200_SYS_IRQ_ENABLE_RTC BIT(12) ++#define AC200_SYS_IRQ_ENABLE_EPHY BIT(8) ++#define AC200_SYS_IRQ_ENABLE_TVE BIT(4) ++ ++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 = { ++ .name = "ac200", ++ .reg_bits = 8, ++ .val_bits = 16, ++ .ranges = ac200_range_cfg, ++ .num_ranges = ARRAY_SIZE(ac200_range_cfg), ++ .max_register = AC200_IC_CHARA1, ++}; ++ ++static const struct regmap_irq ac200_regmap_irqs[] = { ++ REGMAP_IRQ_REG(AC200_IRQ_RTC, 0, AC200_SYS_IRQ_ENABLE_RTC), ++ REGMAP_IRQ_REG(AC200_IRQ_EPHY, 0, AC200_SYS_IRQ_ENABLE_EPHY), ++ REGMAP_IRQ_REG(AC200_IRQ_TVE, 0, AC200_SYS_IRQ_ENABLE_TVE), ++}; ++ ++static const struct regmap_irq_chip ac200_regmap_irq_chip = { ++ .name = "ac200_irq_chip", ++ .status_base = AC200_SYS_IRQ_STATUS, ++ .mask_base = AC200_SYS_IRQ_ENABLE, ++ .irqs = ac200_regmap_irqs, ++ .num_irqs = ARRAY_SIZE(ac200_regmap_irqs), ++ .num_regs = 1, ++}; ++ ++static const struct resource ephy_resource[] = { ++ DEFINE_RES_IRQ(AC200_IRQ_EPHY), ++}; ++ ++static const struct mfd_cell ac200_cells[] = { ++ { ++ .name = "ac200-ephy-sunxi", ++ .num_resources = ARRAY_SIZE(ephy_resource), ++ .resources = ephy_resource, ++ .of_compatible = "x-powers,ac200-ephy-sunxi", ++ }, ++ { ++ .name = "acx00-codec", ++ .of_compatible = "x-powers,ac200-codec-sunxi", ++ }, ++}; ++ ++atomic_t ac200_en; ++ ++int ac200_enable(void) ++{ ++ return atomic_read(&ac200_en); ++} ++ ++EXPORT_SYMBOL(ac200_enable); ++ ++static uint16_t ephy_caldata = 0; ++ ++static int sun50i_ephy_get_calibrate(struct device *dev) ++{ ++ struct nvmem_cell *calcell; ++ uint16_t *caldata; ++ size_t callen; ++ int ret = 0; ++ ++ calcell = devm_nvmem_cell_get(dev, "calibration"); ++ if (IS_ERR(calcell)) { ++ dev_err_probe(dev, PTR_ERR(calcell), ++ "Failed to get calibration nvmem cell (%pe)\n", ++ calcell); ++ ++ if (PTR_ERR(calcell) == -EPROBE_DEFER) ++ return -EPROBE_DEFER; ++ goto out; ++ } ++ ++ caldata = nvmem_cell_read(calcell, &callen); ++ if (IS_ERR(caldata)) { ++ ret = PTR_ERR(caldata); ++ dev_err(dev, "Failed to read calibration data (%pe)\n", ++ caldata); ++ goto out; ++ } ++ ++ ephy_caldata = *caldata; ++ kfree(caldata); ++out: ++ return ret; ++} ++ ++uint16_t sun50i_ephy_calibrate_value(void) ++{ ++ return ephy_caldata; ++} ++ ++EXPORT_SYMBOL(sun50i_ephy_calibrate_value); ++ ++static int ac200_i2c_probe(struct i2c_client *i2c) ++{ ++ struct device *dev = &i2c->dev; ++ struct ac200_dev *ac200; ++ uint32_t ephy_cal; ++ int ret; ++ ++ // 24Mhz clock for both ac200 and ac300 devices ++ ac200 = devm_kzalloc(dev, sizeof(*ac200), GFP_KERNEL); ++ if (!ac200) ++ return -ENOMEM; ++ ++ ac200->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(ac200->clk)) { ++ dev_err(dev, "Can't obtain the clock!\n"); ++ return PTR_ERR(ac200->clk); ++ } ++ ++ ret = clk_prepare_enable(ac200->clk); ++ if (ret) { ++ dev_err(dev, "rclk_prepare_enable failed! \n"); ++ return ret; ++ } ++ ++ ret = sun50i_ephy_get_calibrate(dev); ++ if (ret) { ++ dev_err(dev, "sun50i get ephy id failed\n"); ++ return ret; ++ } ++ ephy_cal = sun50i_ephy_calibrate_value(); ++ ++ if (ephy_cal & SUNXI_AC300_KEY) { ++ pr_warn("it's ac300, skip the ac200 init!\n"); ++ return -EINVAL; ++ } else { ++ pr_warn("it's ac200, ac200 init start!\n"); ++ } ++ ++ i2c_set_clientdata(i2c, ac200); ++ ++ 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; ++ } ++ ++ /* do a reset to put chip in a known state */ ++ ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0); ++ if (ret) ++ { ++ dev_err(dev, "AC200_SYS_CONTROL 0 failed! \n"); ++ return ret; ++ } ++ atomic_set(&ac200_en, 0); ++ ++ ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 1); ++ if (ret) ++ { ++ dev_err(dev, "AC200_SYS_CONTROL 1 failed! \n"); ++ return ret; ++ } ++ atomic_set(&ac200_en, 1); ++ ++ 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); ++ return ret; ++ } ++ else ++ { ++ dev_err(dev, "add MFD devices success! \n"); ++ } ++ ++ return 0; ++} ++ ++static void ac200_i2c_remove(struct i2c_client *i2c) ++{ ++ struct ac200_dev *ac200 = i2c_get_clientdata(i2c); ++ ++ regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0); ++ ++ mfd_remove_devices(&i2c->dev); ++}static void ac200_i2c_shutdown(struct i2c_client *i2c) ++{ ++ struct ac200_dev *ac200 = i2c_get_clientdata(i2c); ++ ++ regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0); ++} ++ ++static int ac200_i2c_suspend(struct device *dev) ++{ ++ struct ac200_dev *ac200 = dev_get_drvdata(dev); ++ ++ if (!IS_ERR_OR_NULL(ac200->clk)) ++ clk_disable_unprepare(ac200->clk); ++ ++ atomic_set(&ac200_en, 0); ++ return 0; ++} ++ ++static int ac200_i2c_resume(struct device *dev) ++{ ++ struct ac200_dev *ac200 = dev_get_drvdata(dev); ++ ++ if (!IS_ERR_OR_NULL(ac200->clk)) ++ clk_prepare_enable(ac200->clk); ++ ++ atomic_set(&ac200_en, 0); ++ msleep(40); ++ regmap_write(ac200->regmap, AC200_SYS_CONTROL, 1); ++ atomic_set(&ac200_en, 1); ++ return 0; ++} ++ ++static const struct dev_pm_ops ac200_core_pm_ops = { ++ .suspend_late = ac200_i2c_suspend, ++ .resume_early = ac200_i2c_resume, ++}; ++ ++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-sunxi" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, ac200_of_match); ++ ++static struct i2c_driver ac200_i2c_driver = { ++ .driver = { ++ .name = "ac200-sunxi", ++ .of_match_table = of_match_ptr(ac200_of_match), ++ .pm = &ac200_core_pm_ops, ++ }, ++ .probe = ac200_i2c_probe, ++ .remove = ac200_i2c_remove, ++ .shutdown = ac200_i2c_shutdown, ++ .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/drivers/net/ethernet/allwinner/Kconfig b/drivers/net/ethernet/allwinner/Kconfig +index 3e81059f8693..36d808810ca4 100644 +--- a/drivers/net/ethernet/allwinner/Kconfig ++++ b/drivers/net/ethernet/allwinner/Kconfig +@@ -34,4 +34,12 @@ config SUN4I_EMAC + To compile this driver as a module, choose M here. The module + will be called sun4i-emac. + ++config SUNXI_GMAC ++ tristate "Allwinner GMAC support" ++ depends on ARCH_SUNXI ++ depends on OF ++ depends on AC200_PHY_SUNXI ++ select CRC32 ++ select MII ++ + endif # NET_VENDOR_ALLWINNER +diff --git a/drivers/net/ethernet/allwinner/Makefile b/drivers/net/ethernet/allwinner/Makefile +index ddd5a5079e8a..56b9c434a5b8 100644 +--- a/drivers/net/ethernet/allwinner/Makefile ++++ b/drivers/net/ethernet/allwinner/Makefile +@@ -4,3 +4,5 @@ + # + + obj-$(CONFIG_SUN4I_EMAC) += sun4i-emac.o ++sunxi_gmac-objs := sunxi_gmac_ops.o sunxi-gmac.o ++obj-$(CONFIG_SUNXI_GMAC) += sunxi_gmac.o +diff --git a/drivers/net/ethernet/allwinner/sunxi-gmac.c b/drivers/net/ethernet/allwinner/sunxi-gmac.c +new file mode 100644 +index 000000000000..d31b86fc1e8c +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/sunxi-gmac.c +@@ -0,0 +1,2219 @@ ++/* ++ * linux/drivers/net/ethernet/allwinner/sunxi_gmac.c ++ * ++ * Copyright © 2016-2018, fuzhaoke ++ * Author: fuzhaoke ++ * ++ * This file is provided under a dual BSD/GPL license. When using or ++ * redistributing this file, you may do so under either license. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "sunxi-gmac.h" ++#include ++#include ++#include ++ ++ ++int sunxi_get_soc_chipid(unsigned char *chipid); ++ ++#define DMA_DESC_RX 256 ++#define DMA_DESC_TX 256 ++#define BUDGET (dma_desc_rx / 4) ++#define TX_THRESH (dma_desc_tx / 4) ++ ++#define HASH_TABLE_SIZE 64 ++#define MAX_BUF_SZ (SZ_2K - 1) ++ ++#define POWER_CHAN_NUM 3 ++ ++#undef PKT_DEBUG ++#undef DESC_PRINT ++ ++#define circ_cnt(head, tail, size) (((head) > (tail)) ? ((head) - (tail)) : ((head) - (tail)) & ((size) - 1)) ++ ++#define circ_space(head, tail, size) circ_cnt((tail), ((head) + 1), (size)) ++ ++#define circ_inc(n, s) (((n) + 1) % (s)) ++ ++#define GETH_MAC_ADDRESS "01:02:03:04:05:06" ++static char *mac_str = GETH_MAC_ADDRESS; ++module_param(mac_str, charp, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(mac_str, "MAC Address String.(xx:xx:xx:xx:xx:xx)"); ++ ++static int rxmode = 1; ++module_param(rxmode, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(rxmode, "DMA threshold control value"); ++ ++static int txmode = 1; ++module_param(txmode, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(txmode, "DMA threshold control value"); ++ ++static int pause = 0x400; ++module_param(pause, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(pause, "Flow Control Pause Time"); ++ ++#define TX_TIMEO 5000 ++static int watchdog = TX_TIMEO; ++module_param(watchdog, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(watchdog, "Transmit timeout in milliseconds"); ++ ++static int dma_desc_rx = DMA_DESC_RX; ++module_param(dma_desc_rx, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(watchdog, "The number of receive's descriptors"); ++ ++static int dma_desc_tx = DMA_DESC_TX; ++module_param(dma_desc_tx, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(watchdog, "The number of transmit's descriptors"); ++ ++/* - 0: Flow Off ++ * - 1: Rx Flow ++ * - 2: Tx Flow ++ * - 3: Rx & Tx Flow ++ */ ++static int flow_ctrl; ++module_param(flow_ctrl, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(flow_ctrl, "Flow control [0: off, 1: rx, 2: tx, 3: both]"); ++ ++static unsigned long tx_delay; ++module_param(tx_delay, ulong, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(tx_delay, "Adjust transmit clock delay, value: 0~7"); ++ ++static unsigned long rx_delay; ++module_param(rx_delay, ulong, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(rx_delay, "Adjust receive clock delay, value: 0~31"); ++ ++/* whether using ephy_clk */ ++static int g_use_ephy_clk; ++static int g_phy_addr; ++ ++struct geth_priv { ++ struct dma_desc *dma_tx; ++ struct sk_buff **tx_sk; ++ unsigned int tx_clean; ++ unsigned int tx_dirty; ++ dma_addr_t dma_tx_phy; ++ ++ struct reset_control *ephy_rst; ++ ++ unsigned long buf_sz; ++ ++ struct dma_desc *dma_rx; ++ struct sk_buff **rx_sk; ++ unsigned int rx_clean; ++ unsigned int rx_dirty; ++ dma_addr_t dma_rx_phy; ++ ++ struct net_device *ndev; ++ struct device *dev; ++ struct napi_struct napi; ++ ++ struct geth_extra_stats xstats; ++ ++ struct mii_bus *mii; ++ int link; ++ int speed; ++ int duplex; ++#define INT_PHY 0 ++#define EXT_PHY 1 ++ int phy_ext; ++ int phy_interface; ++ ++ void __iomem *base; ++ void __iomem *base_phy; ++ struct clk *geth_clk; ++ struct clk *ephy_clk; ++ struct pinctrl *pinctrl; ++ ++ struct regulator *gmac_power[POWER_CHAN_NUM]; ++ bool is_suspend; ++ int phyrst; ++ u8 rst_active_low; ++ /* definition spinlock */ ++ spinlock_t lock; ++ spinlock_t tx_lock; ++ ++ /* resume work */ ++ struct work_struct eth_work; ++}; ++ ++static u64 geth_dma_mask = DMA_BIT_MASK(32); ++ ++void sunxi_udelay(int n) ++{ ++ udelay(n); ++} ++ ++static int geth_stop(struct net_device *ndev); ++static int geth_open(struct net_device *ndev); ++static void geth_tx_complete(struct geth_priv *priv); ++static void geth_rx_refill(struct net_device *ndev); ++ ++#ifdef CONFIG_GETH_ATTRS ++static ssize_t adjust_bgs_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ int value = 0; ++ u32 efuse_value; ++ struct net_device *ndev = to_net_dev(dev); ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ if (priv->phy_ext == INT_PHY) { ++ value = readl(priv->base_phy) >> 28; ++ if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0) ++ pr_err("get PHY efuse fail!\n"); ++ else ++#if defined(CONFIG_ARCH_SUN50IW2) ++ value = value - ((efuse_value >> 24) & 0x0F); ++#else ++ pr_warn("miss config come from efuse!\n"); ++#endif ++ } ++ ++ return sprintf(buf, "bgs: %d\n", value); ++} ++ ++static ssize_t adjust_bgs_write(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ unsigned int out = 0; ++ struct net_device *ndev = to_net_dev(dev); ++ struct geth_priv *priv = netdev_priv(ndev); ++ u32 clk_value = readl(priv->base_phy); ++ u32 efuse_value; ++ ++ out = simple_strtoul(buf, NULL, 10); ++ ++ if (priv->phy_ext == INT_PHY) { ++ clk_value &= ~(0xF << 28); ++ if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0) ++ pr_err("get PHY efuse fail!\n"); ++ else ++#if defined(CONFIG_ARCH_SUN50IW2) ++ clk_value |= (((efuse_value >> 24) & 0x0F) + out) << 28; ++#else ++ pr_warn("miss config come from efuse!\n"); ++#endif ++ } ++ ++ writel(clk_value, priv->base_phy); ++ ++ return count; ++} ++ ++static struct device_attribute adjust_reg[] = { ++ __ATTR(adjust_bgs, 0664, adjust_bgs_show, adjust_bgs_write), ++}; ++ ++static int geth_create_attrs(struct net_device *ndev) ++{ ++ int j, ret; ++ ++ for (j = 0; j < ARRAY_SIZE(adjust_reg); j++) { ++ ret = device_create_file(&ndev->dev, &adjust_reg[j]); ++ if (ret) ++ goto sysfs_failed; ++ } ++ goto succeed; ++ ++sysfs_failed: ++ while (j--) ++ device_remove_file(&ndev->dev, &adjust_reg[j]); ++succeed: ++ return ret; ++} ++#endif ++ ++#ifdef DEBUG ++static void desc_print(struct dma_desc *desc, int size) ++{ ++#ifdef DESC_PRINT ++ int i; ++ ++ for (i = 0; i < size; i++) { ++ u32 *x = (u32 *)(desc + i); ++ ++ pr_info("\t%d [0x%08lx]: %08x %08x %08x %08x\n", ++ i, (unsigned long)(&desc[i]), ++ x[0], x[1], x[2], x[3]); ++ } ++ pr_info("\n"); ++#endif ++} ++#endif ++ ++static ssize_t gphy_test_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ ++ if (!dev) { ++ pr_err("Argment is invalid\n"); ++ return 0; ++ } ++ ++ if (!ndev) { ++ pr_err("Net device is null\n"); ++ return 0; ++ } ++ ++ return sprintf(buf, "Usage:\necho [0/1/2/3/4] > gphy_test\n" ++ "0 - Normal Mode\n" ++ "1 - Transmit Jitter Test\n" ++ "2 - Transmit Jitter Test(MASTER mode)\n" ++ "3 - Transmit Jitter Test(SLAVE mode)\n" ++ "4 - Transmit Distortion Test\n\n"); ++} ++ ++static ssize_t gphy_test_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct geth_priv *priv = netdev_priv(ndev); ++ u16 value = 0; ++ int ret = 0; ++ u16 data = 0; ++ ++ if (!dev) { ++ pr_err("Argument is invalid\n"); ++ return count; ++ } ++ ++ if (!ndev) { ++ pr_err("Net device is null\n"); ++ return count; ++ } ++ ++ data = sunxi_mdio_read(priv->base, g_phy_addr, MII_CTRL1000); ++ ++ ret = kstrtou16(buf, 0, &value); ++ if (ret) ++ return ret; ++ ++ if (value >= 0 && value <= 4) { ++ data &= ~(0x7 << 13); ++ data |= value << 13; ++ sunxi_mdio_write(priv->base, g_phy_addr, MII_CTRL1000, data); ++ pr_info("Set MII_CTRL1000(0x09) Reg: 0x%x\n", data); ++ } else { ++ pr_info("unknown value (%d)\n", value); ++ } ++ ++ return count; ++} ++ ++static DEVICE_ATTR(gphy_test, 0664, gphy_test_show, gphy_test_store); ++ ++static struct mii_reg_dump mii_reg; ++ ++static ssize_t mii_read_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev; ++ struct geth_priv *priv; ++ ++ if (!dev) { ++ pr_err("Argment is invalid\n"); ++ return 0; ++ } ++ ++ ndev = dev_get_drvdata(dev); ++ if (!ndev) { ++ pr_err("Net device is null\n"); ++ return 0; ++ } ++ ++ priv = netdev_priv(ndev); ++ if (!priv) { ++ pr_err("geth_priv is null\n"); ++ return 0; ++ } ++ ++ if (!netif_running(ndev)) { ++ pr_warn("eth is down!\n"); ++ return 0; ++ } ++ ++ mii_reg.value = sunxi_mdio_read(priv->base, mii_reg.addr, mii_reg.reg); ++ return sprintf(buf, "ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n", ++ mii_reg.addr, mii_reg.reg, mii_reg.value); ++} ++ ++static ssize_t mii_read_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev; ++ struct geth_priv *priv; ++ int ret = 0; ++ u16 reg, addr; ++ char *ptr; ++ ++ ptr = (char *)buf; ++ ++ if (!dev) { ++ pr_err("Argment is invalid\n"); ++ return count; ++ } ++ ++ ndev = dev_get_drvdata(dev); ++ if (!ndev) { ++ pr_err("Net device is null\n"); ++ return count; ++ } ++ ++ priv = netdev_priv(ndev); ++ if (!priv) { ++ pr_err("geth_priv is null\n"); ++ return count; ++ } ++ ++ if (!netif_running(ndev)) { ++ pr_warn("eth is down!\n"); ++ return count; ++ } ++ ++ ret = sunxi_parse_read_str(ptr, &addr, ®); ++ if (ret) ++ return ret; ++ ++ mii_reg.addr = addr; ++ mii_reg.reg = reg; ++ ++ return count; ++} ++ ++static DEVICE_ATTR(mii_read, 0664, mii_read_show, mii_read_store); ++ ++static ssize_t mii_write_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev; ++ struct geth_priv *priv; ++ u16 bef_val, aft_val; ++ ++ if (!dev) { ++ pr_err("Argment is invalid\n"); ++ return 0; ++ } ++ ++ ndev = dev_get_drvdata(dev); ++ if (!ndev) { ++ pr_err("Net device is null\n"); ++ return 0; ++ } ++ ++ priv = netdev_priv(ndev); ++ if (!priv) { ++ pr_err("geth_priv is null\n"); ++ return 0; ++ } ++ ++ if (!netif_running(ndev)) { ++ pr_warn("eth is down!\n"); ++ return 0; ++ } ++ ++ bef_val = sunxi_mdio_read(priv->base, mii_reg.addr, mii_reg.reg); ++ sunxi_mdio_write(priv->base, mii_reg.addr, mii_reg.reg, mii_reg.value); ++ aft_val = sunxi_mdio_read(priv->base, mii_reg.addr, mii_reg.reg); ++ return sprintf(buf, "before ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n" ++ "after ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n", ++ mii_reg.addr, mii_reg.reg, bef_val, ++ mii_reg.addr, mii_reg.reg, aft_val); ++} ++ ++static ssize_t mii_write_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev; ++ struct geth_priv *priv; ++ int ret = 0; ++ u16 reg, addr, val; ++ char *ptr; ++ ++ ptr = (char *)buf; ++ ++ if (!dev) { ++ pr_err("Argment is invalid\n"); ++ return count; ++ } ++ ++ ndev = dev_get_drvdata(dev); ++ if (!ndev) { ++ pr_err("Net device is null\n"); ++ return count; ++ } ++ ++ priv = netdev_priv(ndev); ++ if (!priv) { ++ pr_err("geth_priv is null\n"); ++ return count; ++ } ++ ++ if (!netif_running(ndev)) { ++ pr_warn("eth is down!\n"); ++ return count; ++ } ++ ++ ret = sunxi_parse_write_str(ptr, &addr, ®, &val); ++ if (ret) ++ return ret; ++ ++ mii_reg.reg = reg; ++ mii_reg.addr = addr; ++ mii_reg.value = val; ++ ++ return count; ++} ++ ++static DEVICE_ATTR(mii_write, 0664, mii_write_show, mii_write_store); ++ ++static int geth_power_on(struct geth_priv *priv) ++{ ++ int value; ++ int i; ++ ++ value = readl(priv->base_phy); ++ if (priv->phy_ext == INT_PHY) { ++ value |= (1 << 15); ++ value &= ~(1 << 16); ++ value |= (3 << 17); ++ } else { ++ value &= ~(1 << 15); ++ ++ for (i = 0; i < POWER_CHAN_NUM; i++) { ++ if (IS_ERR_OR_NULL(priv->gmac_power[i])) ++ continue; ++ if (regulator_enable(priv->gmac_power[i]) != 0) { ++ pr_err("gmac-power%d enable error\n", i); ++ return -EINVAL; ++ } ++ } ++ } ++ ++ writel(value, priv->base_phy); ++ ++ return 0; ++} ++ ++static void geth_power_off(struct geth_priv *priv) ++{ ++ int value; ++ int i; ++ ++ if (priv->phy_ext == INT_PHY) { ++ value = readl(priv->base_phy); ++ value |= (1 << 16); ++ writel(value, priv->base_phy); ++ } else { ++ for (i = 0; i < POWER_CHAN_NUM; i++) { ++ if (IS_ERR_OR_NULL(priv->gmac_power[i])) ++ continue; ++ regulator_disable(priv->gmac_power[i]); ++ } ++ } ++} ++ ++/* PHY interface operations */ ++static int geth_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg) ++{ ++ struct net_device *ndev = bus->priv; ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ return (int)sunxi_mdio_read(priv->base, phyaddr, phyreg); ++} ++ ++static int geth_mdio_write(struct mii_bus *bus, int phyaddr, int phyreg, u16 data) ++{ ++ struct net_device *ndev = bus->priv; ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ sunxi_mdio_write(priv->base, phyaddr, phyreg, data); ++ ++ return 0; ++} ++ ++static int geth_mdio_reset(struct mii_bus *bus) ++{ ++ struct net_device *ndev = bus->priv; ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ return sunxi_mdio_reset(priv->base); ++} ++ ++static void geth_adjust_link(struct net_device *ndev) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ struct phy_device *phydev = ndev->phydev; ++ unsigned long flags; ++ int new_state = 0; ++ ++ if (!phydev) ++ return; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ if (phydev->link) { ++ /* Now we make sure that we can be in full duplex mode. ++ * If not, we operate in half-duplex mode. ++ */ ++ if (phydev->duplex != priv->duplex) { ++ new_state = 1; ++ priv->duplex = phydev->duplex; ++ } ++ /* Flow Control operation */ ++ if (phydev->pause) ++ sunxi_flow_ctrl(priv->base, phydev->duplex, flow_ctrl, pause); ++ ++ if (phydev->speed != priv->speed) { ++ new_state = 1; ++ priv->speed = phydev->speed; ++ } ++ ++ if (priv->link == 0) { ++ new_state = 1; ++ priv->link = phydev->link; ++ } ++ ++ if (new_state) ++ sunxi_set_link_mode(priv->base, priv->duplex, priv->speed); ++ ++#ifdef LOOPBACK_DEBUG ++ phydev->state = PHY_FORCING; ++#endif ++ ++ } else if (priv->link != phydev->link) { ++ new_state = 1; ++ priv->link = 0; ++ priv->speed = 0; ++ priv->duplex = -1; ++ } ++ ++ if (new_state) ++ phy_print_status(phydev); ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++} ++ ++static int geth_phy_init(struct net_device *ndev) ++{ ++ int value; ++ struct mii_bus *new_bus; ++ struct geth_priv *priv = netdev_priv(ndev); ++ struct phy_device *phydev = ndev->phydev; ++ u32 supported = 0, advertising = 0; ++ ++ /* Fixup the phy interface type */ ++ if (priv->phy_ext == INT_PHY) { ++ priv->phy_interface = PHY_INTERFACE_MODE_MII; ++ } else { ++ /* If config gpio to reset the phy device, we should reset it */ ++ if (gpio_is_valid(priv->phyrst)) { ++ gpio_direction_output(priv->phyrst, priv->rst_active_low); ++ msleep(50); ++ gpio_direction_output(priv->phyrst, !priv->rst_active_low); ++ msleep(50); ++ } ++ } ++ ++ new_bus = mdiobus_alloc(); ++ if (!new_bus) { ++ netdev_err(ndev, "Failed to alloc new mdio bus\n"); ++ return -ENOMEM; ++ } ++ ++ new_bus->name = dev_name(priv->dev); ++ new_bus->read = &geth_mdio_read; ++ new_bus->write = &geth_mdio_write; ++ new_bus->reset = &geth_mdio_reset; ++ snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x", new_bus->name, 0); ++ ++ new_bus->parent = priv->dev; ++ new_bus->priv = ndev; ++ ++ if (mdiobus_register(new_bus)) { ++ pr_err("%s: Cannot register as MDIO bus\n", new_bus->name); ++ goto reg_fail; ++ } ++ ++ priv->mii = new_bus; ++ ++ { ++ int addr; ++ for (addr = 0; addr < PHY_MAX_ADDR; addr++) { ++ struct phy_device *phydev_tmp = mdiobus_get_phy(new_bus, addr); ++ ++ if (IS_ERR_OR_NULL(phydev_tmp) || phydev_tmp->phy_id == 0xffff) { ++ if (!IS_ERR_OR_NULL(phydev_tmp)) ++ phy_device_remove(phydev_tmp); ++ phydev_tmp = mdiobus_scan_c22(new_bus, addr); ++ } ++ ++ if (IS_ERR_OR_NULL(phydev_tmp) || phydev_tmp->phy_id == 0xffff || ++ phydev_tmp->phy_id == 0x00 || phydev_tmp->phy_id == AC300_ID) ++ continue; ++ ++ if (phydev_tmp->phy_id == EPHY_ID || phydev_tmp->phy_id == IP101G_ID) { ++ phydev = phydev_tmp; ++ g_phy_addr = addr; ++ break; ++ } ++ } ++ } ++ ++ if (!phydev) { ++ netdev_err(ndev, "No PHY found!\n"); ++ goto err; ++ } ++ ++ phy_write(phydev, MII_BMCR, BMCR_RESET); ++ while (BMCR_RESET & phy_read(phydev, MII_BMCR)) ++ msleep(30); ++ ++ value = phy_read(phydev, MII_BMCR); ++ phy_write(phydev, MII_BMCR, (value & ~BMCR_PDOWN)); ++ ++ phydev->irq = PHY_POLL; ++ ++ value = phy_connect_direct(ndev, phydev, &geth_adjust_link, priv->phy_interface); ++ if (value) { ++ netdev_err(ndev, "Could not attach to PHY\n"); ++ goto err; ++ } else { ++ netdev_info(ndev, "%s: Type(%d) PHY ID %08x at %d IRQ %s (%s)\n", ++ ndev->name, phydev->interface, phydev->phy_id, ++ phydev->mdio.addr, "poll", dev_name(&phydev->mdio.dev)); ++ } ++ ++#if 0 ++ phydev->supported &= PHY_GBIT_FEATURES; //kandy 000002cf ++ phydev->advertising = phydev->supported; ++#else ++ ++ ethtool_convert_link_mode_to_legacy_u32(&supported, phydev->supported); ++ ++ advertising = supported &= *PHY_BASIC_FEATURES; // kandy 000002cf ++ ++ ethtool_convert_legacy_u32_to_link_mode(phydev->supported, supported); ++ ethtool_convert_legacy_u32_to_link_mode(phydev->advertising, advertising); ++#endif ++ ++ if (priv->phy_ext == INT_PHY) { ++ /* EPHY Initial */ ++ phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */ ++ phy_write(phydev, 0x12, 0x4824); /* Disable APS */ ++ phy_write(phydev, 0x1f, 0x0200); /* switchto page 2 */ ++ phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */ ++ phy_write(phydev, 0x1f, 0x0600); /* switchto page 6 */ ++ phy_write(phydev, 0x14, 0x708F); /* PHYAFE TX optimization */ ++ phy_write(phydev, 0x19, 0x0000); ++ phy_write(phydev, 0x13, 0xf000); /* PHYAFE RX optimization */ ++ phy_write(phydev, 0x15, 0x1530); ++ phy_write(phydev, 0x1f, 0x0800); /* switch to page 8 */ ++ phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */ ++ phy_write(phydev, 0x1f, 0x0100); /* switchto page 1 */ ++ /* reg 0x17 bit3,set 0 to disable iEEE */ ++ phy_write(phydev, 0x17, phy_read(phydev, 0x17) & (~(1<<3))); ++ phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */ ++ } ++ ++ return 0; ++ ++err: ++ mdiobus_unregister(new_bus); ++reg_fail: ++ mdiobus_free(new_bus); ++ ++ return -EINVAL; ++} ++ ++static int geth_phy_release(struct net_device *ndev) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ struct phy_device *phydev = ndev->phydev; ++ int value = 0; ++ ++ /* Stop and disconnect the PHY */ ++ if (phydev) ++ phy_stop(phydev); ++ ++ priv->link = PHY_DOWN; ++ priv->speed = 0; ++ priv->duplex = -1; ++ ++ if (phydev) { ++ value = phy_read(phydev, MII_BMCR); ++ phy_write(phydev, MII_BMCR, (value | BMCR_PDOWN)); ++ phy_disconnect(phydev); ++ ndev->phydev = NULL; ++ } ++ ++#if 1 // defined(CONFIG_SUNXI_EPHY) ++ gmac_ephy_shutdown(); /* Turn off the clock of PHY for low power consumption */ ++#endif ++ ++ if (priv->mii) { ++ mdiobus_unregister(priv->mii); ++ priv->mii->priv = NULL; ++ mdiobus_free(priv->mii); ++ priv->mii = NULL; ++ } ++ ++ return 0; ++} ++ ++static void geth_rx_refill(struct net_device *ndev) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ struct dma_desc *desc; ++ struct sk_buff *sk = NULL; ++ dma_addr_t paddr; ++ ++ while (circ_space(priv->rx_clean, priv->rx_dirty, dma_desc_rx) > 0) { ++ int entry = priv->rx_clean; ++ ++ /* Find the dirty's desc and clean it */ ++ desc = priv->dma_rx + entry; ++ ++ if (priv->rx_sk[entry] == NULL) { ++ sk = netdev_alloc_skb_ip_align(ndev, priv->buf_sz); ++ ++ if (unlikely(sk == NULL)) ++ break; ++ ++ priv->rx_sk[entry] = sk; ++ paddr = dma_map_single(priv->dev, sk->data, ++ priv->buf_sz, DMA_FROM_DEVICE); ++ desc_buf_set(desc, paddr, priv->buf_sz); ++ } ++ ++ /* sync memery */ ++ wmb(); ++ desc_set_own(desc); ++ priv->rx_clean = circ_inc(priv->rx_clean, dma_desc_rx); ++ } ++} ++ ++/* geth_dma_desc_init - initialize the RX/TX descriptor list ++ * @ndev: net device structure ++ * Description: initialize the list for dma. ++ */ ++static int geth_dma_desc_init(struct net_device *ndev) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ unsigned int buf_sz; ++ ++ priv->rx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_rx, GFP_KERNEL); ++ if (!priv->rx_sk) ++ return -ENOMEM; ++ ++ priv->tx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_tx, GFP_KERNEL); ++ if (!priv->tx_sk) ++ goto tx_sk_err; ++ ++ /* Set the size of buffer depend on the MTU & max buf size */ ++ buf_sz = MAX_BUF_SZ; ++ ++ priv->dma_tx = dma_alloc_coherent(priv->dev, ++ dma_desc_tx * sizeof(struct dma_desc), ++ &priv->dma_tx_phy, ++ GFP_KERNEL); ++ if (!priv->dma_tx) ++ goto dma_tx_err; ++ ++ priv->dma_rx = dma_alloc_coherent(priv->dev, ++ dma_desc_rx * sizeof(struct dma_desc), ++ &priv->dma_rx_phy, ++ GFP_KERNEL); ++ if (!priv->dma_rx) ++ goto dma_rx_err; ++ ++ priv->buf_sz = buf_sz; ++ ++ return 0; ++ ++dma_rx_err: ++ dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc), priv->dma_tx, priv->dma_tx_phy); ++dma_tx_err: ++ kfree(priv->tx_sk); ++tx_sk_err: ++ kfree(priv->rx_sk); ++ ++ return -ENOMEM; ++} ++ ++static void geth_free_rx_sk(struct geth_priv *priv) ++{ ++ int i; ++ ++ for (i = 0; i < dma_desc_rx; i++) { ++ if (priv->rx_sk[i] != NULL) { ++ struct dma_desc *desc = priv->dma_rx + i; ++ ++ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc), desc_buf_get_len(desc), DMA_FROM_DEVICE); ++ dev_kfree_skb_any(priv->rx_sk[i]); ++ priv->rx_sk[i] = NULL; ++ } ++ } ++} ++ ++static void geth_free_tx_sk(struct geth_priv *priv) ++{ ++ int i; ++ ++ for (i = 0; i < dma_desc_tx; i++) { ++ if (priv->tx_sk[i] != NULL) { ++ struct dma_desc *desc = priv->dma_tx + i; ++ ++ if (desc_buf_get_addr(desc)) ++ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc), desc_buf_get_len(desc), DMA_TO_DEVICE); ++ dev_kfree_skb_any(priv->tx_sk[i]); ++ priv->tx_sk[i] = NULL; ++ } ++ } ++} ++ ++static void geth_free_dma_desc(struct geth_priv *priv) ++{ ++ /* Free the region of consistent memory previously allocated for the DMA */ ++ dma_free_coherent(priv->dev, dma_desc_tx * sizeof(struct dma_desc), priv->dma_tx, priv->dma_tx_phy); ++ dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc), priv->dma_rx, priv->dma_rx_phy); ++ ++ kfree(priv->rx_sk); ++ kfree(priv->tx_sk); ++} ++ ++#ifdef CONFIG_PM ++static int geth_select_gpio_state(struct pinctrl *pctrl, char *name) ++{ ++ int ret = 0; ++ struct pinctrl_state *pctrl_state = NULL; ++ ++ pctrl_state = pinctrl_lookup_state(pctrl, name); ++ if (IS_ERR(pctrl_state)) { ++ pr_err("gmac pinctrl_lookup_state(%s) failed! return %p\n", name, pctrl_state); ++ return -EINVAL; ++ } ++ ++ ret = pinctrl_select_state(pctrl, pctrl_state); ++ if (ret < 0) ++ pr_err("gmac pinctrl_select_state(%s) failed! return %d\n", name, ret); ++ ++ return ret; ++} ++ ++static int geth_suspend(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ cancel_work_sync(&priv->eth_work); ++ ++ if (!ndev || !netif_running(ndev)) ++ return 0; ++ ++ spin_lock(&priv->lock); ++ netif_device_detach(ndev); ++ spin_unlock(&priv->lock); ++ ++ geth_stop(ndev); ++ ++ if (priv->phy_ext == EXT_PHY) ++ geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_SLEEP); ++ ++ return 0; ++} ++ ++static void geth_resume_work(struct work_struct *work) ++{ ++ struct geth_priv *priv = container_of(work, struct geth_priv, eth_work); ++ struct net_device *ndev = priv->ndev; ++ ++ if (!netif_running(ndev)) ++ return; ++ ++ if (priv->phy_ext == EXT_PHY) ++ geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_DEFAULT); ++ ++ geth_open(ndev); ++ ++#if 1 //defined(CONFIG_SUNXI_EPHY) ++ if (!ephy_is_enable()) { ++ pr_info("[geth_resume] ephy is not enable, waiting...\n"); ++ msleep(2000); ++ if (!ephy_is_enable()) { ++ netdev_err(ndev, "Wait for ephy resume timeout.\n"); ++ return; ++ } ++ } ++#endif ++ ++ spin_lock(&priv->lock); ++ netif_device_attach(ndev); ++ spin_unlock(&priv->lock); ++ ++} ++ ++static void geth_resume(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ schedule_work(&priv->eth_work); ++} ++ ++ ++ ++static int ucc_geth_suspend(struct platform_device *ofdev, pm_message_t state) ++{ ++ struct net_device *ndev = platform_get_drvdata(ofdev); ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ cancel_work_sync(&priv->eth_work); ++ ++ if (!ndev || !netif_running(ndev)) ++ return 0; ++ ++ spin_lock(&priv->lock); ++ netif_device_detach(ndev); ++ spin_unlock(&priv->lock); ++ ++ geth_stop(ndev); ++ ++ if (priv->phy_ext == EXT_PHY) ++ geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_SLEEP); ++ ++ ++ return 0; ++} ++ ++static int ucc_geth_resume(struct platform_device *ofdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(ofdev); ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ schedule_work(&priv->eth_work); ++ ++ ++ return 0; ++} ++ ++static int geth_freeze(struct device *dev) ++{ ++ return 0; ++} ++ ++static int geth_restore(struct device *dev) ++{ ++ return 0; ++} ++ ++static const struct dev_pm_ops geth_pm_ops = { ++ .complete = geth_resume, ++ .prepare = geth_suspend, ++ .suspend = NULL, ++ .resume = NULL, ++ .freeze = geth_freeze, ++ .restore = geth_restore, ++}; ++#else ++static const struct dev_pm_ops geth_pm_ops; ++#endif /* CONFIG_PM */ ++ ++/*#define sunxi_get_soc_chipid(x) {}*/ ++static void geth_chip_hwaddr(u8 *addr) ++{ ++#define MD5_SIZE 16 ++#define CHIP_SIZE 16 ++ ++ struct crypto_ahash *tfm; ++ struct ahash_request *req; ++ struct scatterlist sg; ++ u8 result[MD5_SIZE]; ++ u8 chipid[CHIP_SIZE]; ++ int i = 0; ++ int ret = -1; ++ ++ memset(chipid, 0, sizeof(chipid)); ++ memset(result, 0, sizeof(result)); ++ ++ sunxi_get_soc_chipid((u8 *)chipid); ++ ++ tfm = crypto_alloc_ahash("md5", 0, CRYPTO_ALG_ASYNC); ++ if (IS_ERR(tfm)) { ++ pr_err("Failed to alloc md5\n"); ++ return; ++ } ++ ++ req = ahash_request_alloc(tfm, GFP_KERNEL); ++ if (!req) ++ goto out; ++ ++ ahash_request_set_callback(req, 0, NULL, NULL); ++ ++ ret = crypto_ahash_init(req); ++ if (ret) { ++ pr_err("crypto_ahash_init() failed\n"); ++ goto out; ++ } ++ ++ sg_init_one(&sg, chipid, sizeof(chipid)); ++ ahash_request_set_crypt(req, &sg, result, sizeof(chipid)); ++ ret = crypto_ahash_update(req); ++ if (ret) { ++ pr_err("crypto_ahash_update() failed for id\n"); ++ goto out; ++ } ++ ++ ret = crypto_ahash_final(req); ++ if (ret) { ++ pr_err("crypto_ahash_final() failed for result\n"); ++ goto out; ++ } ++ ++ ahash_request_free(req); ++ ++ /* Choose md5 result's [0][2][4][6][8][10] byte as mac address */ ++ for (i = 0; i < ETH_ALEN; i++) ++ addr[i] = result[2 * i]; ++ addr[0] &= 0xfe; /* clear multicast bit */ ++ addr[0] |= 0x02; /* set local assignment bit (IEEE802) */ ++ ++out: ++ crypto_free_ahash(tfm); ++} ++ ++static void geth_check_addr(struct net_device *ndev, unsigned char *mac) ++{ ++ int i; ++ char *p = mac; ++ u8 addr[ETH_ALEN]; ++ ++ if (!is_valid_ether_addr(ndev->dev_addr)) { ++ for (i = 0; i < ETH_ALEN; i++, p++) ++ addr[i] = simple_strtoul(p, &p, 16); ++ ++ if (!is_valid_ether_addr(addr)) ++ geth_chip_hwaddr(addr); ++ ++ if (!is_valid_ether_addr(addr)) { ++ eth_random_addr(addr); ++ pr_warn("%s: Use random mac address\n", ndev->name); ++ } ++ } ++ ++ eth_hw_addr_set(ndev, addr); ++} ++ ++static void geth_clk_enable(struct geth_priv *priv) ++{ ++ int phy_interface = 0; ++ u32 clk_value; ++ u32 efuse_value; ++ ++ if (clk_prepare_enable(priv->geth_clk)) ++ pr_err("try to enable geth_clk failed!\n"); ++ ++ if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk) && !IS_ERR_OR_NULL(priv->ephy_clk)) { ++ if (clk_prepare_enable(priv->ephy_clk)) ++ pr_err("try to enable ephy_clk failed!\n"); ++ } ++ ++ phy_interface = priv->phy_interface; ++ ++ clk_value = readl(priv->base_phy); ++ if (phy_interface == PHY_INTERFACE_MODE_RGMII) ++ clk_value |= 0x00000004; ++ else ++ clk_value &= (~0x00000004); ++ ++ clk_value &= (~0x00002003); ++ if (phy_interface == PHY_INTERFACE_MODE_RGMII || phy_interface == PHY_INTERFACE_MODE_GMII) ++ clk_value |= 0x00000002; ++ else if (phy_interface == PHY_INTERFACE_MODE_RMII) ++ clk_value |= 0x00002001; ++ ++ if (priv->phy_ext == INT_PHY) { ++// if (0 != sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value)) ++// pr_err("get PHY efuse fail!\n"); ++// else ++// #if defined(CONFIG_ARCH_SUN50IW2) ++// clk_value |= (((efuse_value >> 24) & 0x0F) + 3) << 28; ++// #else ++// pr_warn("miss config come from efuse!\n"); ++// #endif ++ } ++ ++ /* Adjust Tx/Rx clock delay */ ++ clk_value &= ~(0x07 << 10); ++ clk_value |= ((tx_delay & 0x07) << 10); ++ clk_value &= ~(0x1F << 5); ++ clk_value |= ((rx_delay & 0x1F) << 5); ++ ++ writel(clk_value, priv->base_phy); ++} ++ ++static void geth_clk_disable(struct geth_priv *priv) ++{ ++ if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk) && !IS_ERR_OR_NULL(priv->ephy_clk)) ++ clk_disable_unprepare(priv->ephy_clk); ++ ++ clk_disable_unprepare(priv->geth_clk); ++} ++ ++static void geth_tx_err(struct geth_priv *priv) ++{ ++ netif_stop_queue(priv->ndev); ++ ++ sunxi_stop_tx(priv->base); ++ ++ geth_free_tx_sk(priv); ++ memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc)); ++ desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx); ++ priv->tx_dirty = 0; ++ priv->tx_clean = 0; ++ sunxi_start_tx(priv->base, priv->dma_tx_phy); ++ ++ priv->ndev->stats.tx_errors++; ++ netif_wake_queue(priv->ndev); ++} ++ ++static inline void geth_schedule(struct geth_priv *priv) ++{ ++ if (likely(napi_schedule_prep(&priv->napi))) { ++ sunxi_int_disable(priv->base); ++ __napi_schedule(&priv->napi); ++ } ++} ++ ++static irqreturn_t geth_interrupt(int irq, void *dev_id) ++{ ++ struct net_device *ndev = (struct net_device *)dev_id; ++ struct geth_priv *priv = netdev_priv(ndev); ++ int status; ++ ++ if (unlikely(!ndev)) { ++ pr_err("%s: invalid ndev pointer\n", __func__); ++ return IRQ_NONE; ++ } ++ ++ status = sunxi_int_status(priv->base, (void *)(&priv->xstats)); ++ ++ if (likely(status == handle_tx_rx)) ++ geth_schedule(priv); ++ else if (unlikely(status == tx_hard_error_bump_tc)) ++ netdev_info(ndev, "Do nothing for bump tc\n"); ++ else if (unlikely(status == tx_hard_error)) ++ geth_tx_err(priv); ++ else ++ netdev_info(ndev, "Do nothing.....\n"); ++ ++ return IRQ_HANDLED; ++} ++ ++static int geth_open(struct net_device *ndev) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ int ret = 0; ++ ++ ret = geth_dma_desc_init(ndev); ++ if (ret) { ++ ret = -EINVAL; ++ return ret; ++ } ++ ++ ret = geth_power_on(priv); ++ if (ret) { ++ netdev_err(ndev, "Power on is failed\n"); ++ ret = -EINVAL; ++ goto power_err; ++ } ++ ++ geth_clk_enable(priv); ++ ++ ret = geth_phy_init(ndev); ++ if (ret) { ++ netdev_err(ndev, "phy init again...\n"); ++ ret = geth_phy_init(ndev); ++ if (ret) { ++ netdev_err(ndev, "phy init failed\n"); ++ ret = -EINVAL; ++ goto phy_err; ++ } ++ } ++ ++ ret = sunxi_mac_reset((void *)priv->base, &sunxi_udelay, 10000); ++ if (ret) { ++ netdev_err(ndev, "Initialize hardware error\n"); ++ goto mac_err; ++ } ++ ++ sunxi_mac_init(priv->base, txmode, rxmode); ++ sunxi_set_umac(priv->base, ndev->dev_addr, 0); ++ ++ memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc)); ++ memset(priv->dma_rx, 0, dma_desc_rx * sizeof(struct dma_desc)); ++ ++ desc_init_chain(priv->dma_rx, (unsigned long)priv->dma_rx_phy, dma_desc_rx); ++ desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx); ++ ++ priv->rx_clean = 0; ++ priv->rx_dirty = 0; ++ priv->tx_clean = 0; ++ priv->tx_dirty = 0; ++ geth_rx_refill(ndev); ++ ++ /* Extra statistics */ ++ memset(&priv->xstats, 0, sizeof(struct geth_extra_stats)); ++ ++ if (ndev->phydev) ++ phy_start(ndev->phydev); ++ ++ sunxi_start_rx(priv->base, (unsigned long)((struct dma_desc *)priv->dma_rx_phy + priv->rx_dirty)); ++ sunxi_start_tx(priv->base, (unsigned long)((struct dma_desc *)priv->dma_tx_phy + priv->tx_clean)); ++ ++ /* Enable the Rx/Tx */ ++ sunxi_mac_enable(priv->base); ++ ++ netif_carrier_on(ndev); ++ ++ napi_enable(&priv->napi); ++ netif_start_queue(ndev); ++ ++ return 0; ++ ++mac_err: ++ geth_phy_release(ndev); ++phy_err: ++ geth_clk_disable(priv); ++ ++ geth_power_off(priv); ++ ++power_err: ++ geth_free_dma_desc(priv); ++ ++ return ret; ++} ++ ++static int geth_stop(struct net_device *ndev) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ netif_stop_queue(ndev); ++ napi_disable(&priv->napi); ++ ++ netif_carrier_off(ndev); ++ ++ /* Release PHY resources */ ++ geth_phy_release(ndev); ++ ++ /* Disable Rx/Tx */ ++ sunxi_mac_disable(priv->base); ++ ++ geth_clk_disable(priv); ++ geth_power_off(priv); ++ ++ netif_tx_lock_bh(ndev); ++ /* Release the DMA TX/RX socket buffers */ ++ geth_free_rx_sk(priv); ++ geth_free_tx_sk(priv); ++ netif_tx_unlock_bh(ndev); ++ ++ /* Ensure that hareware have been stopped */ ++ geth_free_dma_desc(priv); ++ ++ return 0; ++} ++ ++static void geth_tx_complete(struct geth_priv *priv) ++{ ++ unsigned int entry = 0; ++ struct sk_buff *skb = NULL; ++ struct dma_desc *desc = NULL; ++ int tx_stat; ++ ++ spin_lock(&priv->tx_lock); ++ while (circ_cnt(priv->tx_dirty, priv->tx_clean, dma_desc_tx) > 0) { ++ entry = priv->tx_clean; ++ desc = priv->dma_tx + entry; ++ ++ /* Check if the descriptor is owned by the DMA. */ ++ if (desc_get_own(desc)) ++ break; ++ ++ /* Verify tx error by looking at the last segment */ ++ if (desc_get_tx_ls(desc)) { ++ tx_stat = desc_get_tx_status(desc, (void *)(&priv->xstats)); ++ ++ if (likely(!tx_stat)) ++ priv->ndev->stats.tx_packets++; ++ else ++ priv->ndev->stats.tx_errors++; ++ } ++ ++ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc), desc_buf_get_len(desc), DMA_TO_DEVICE); ++ ++ skb = priv->tx_sk[entry]; ++ priv->tx_sk[entry] = NULL; ++ desc_init(desc); ++ ++ /* Find next dirty desc */ ++ priv->tx_clean = circ_inc(entry, dma_desc_tx); ++ ++ if (unlikely(skb == NULL)) ++ continue; ++ ++ dev_kfree_skb(skb); ++ } ++ ++ if (unlikely(netif_queue_stopped(priv->ndev)) && circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) > TX_THRESH) { ++ netif_wake_queue(priv->ndev); ++ } ++ spin_unlock(&priv->tx_lock); ++} ++ ++static netdev_tx_t geth_xmit(struct sk_buff *skb, struct net_device *ndev) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ unsigned int entry; ++ struct dma_desc *desc, *first; ++ unsigned int len, tmp_len = 0; ++ int i, csum_insert; ++ int nfrags = skb_shinfo(skb)->nr_frags; ++ dma_addr_t paddr; ++ ++ spin_lock(&priv->tx_lock); ++ if (unlikely(circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) < (nfrags + 1))) { ++ if (!netif_queue_stopped(ndev)) { ++ netdev_err(ndev, "%s: BUG! Tx Ring full when queue awake\n", __func__); ++ netif_stop_queue(ndev); ++ } ++ spin_unlock(&priv->tx_lock); ++ ++ return NETDEV_TX_BUSY; ++ } ++ ++ csum_insert = (skb->ip_summed == CHECKSUM_PARTIAL); ++ entry = priv->tx_dirty; ++ first = priv->dma_tx + entry; ++ desc = priv->dma_tx + entry; ++ ++ len = skb_headlen(skb); ++ priv->tx_sk[entry] = skb; ++ ++#ifdef PKT_DEBUG ++ printk("======TX PKT DATA: ============\n"); ++ /* dump the packet */ ++ print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE, ++ 16, 1, skb->data, 64, true); ++#endif ++ ++ /* Every desc max size is 2K */ ++ while (len != 0) { ++ desc = priv->dma_tx + entry; ++ tmp_len = ((len > MAX_BUF_SZ) ? MAX_BUF_SZ : len); ++ ++ paddr = dma_map_single(priv->dev, skb->data, tmp_len, DMA_TO_DEVICE); ++ if (dma_mapping_error(priv->dev, paddr)) { ++ dev_kfree_skb(skb); ++ return -EIO; ++ } ++ desc_buf_set(desc, paddr, tmp_len); ++ /* Don't set the first's own bit, here */ ++ if (first != desc) { ++ priv->tx_sk[entry] = NULL; ++ desc_set_own(desc); ++ } ++ ++ entry = circ_inc(entry, dma_desc_tx); ++ len -= tmp_len; ++ } ++ ++ for (i = 0; i < nfrags; i++) { ++ const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; ++ ++ len = skb_frag_size(frag); ++ desc = priv->dma_tx + entry; ++ paddr = skb_frag_dma_map(priv->dev, frag, 0, len, DMA_TO_DEVICE); ++ if (dma_mapping_error(priv->dev, paddr)) { ++ dev_kfree_skb(skb); ++ return -EIO; ++ } ++ ++ desc_buf_set(desc, paddr, len); ++ desc_set_own(desc); ++ priv->tx_sk[entry] = NULL; ++ entry = circ_inc(entry, dma_desc_tx); ++ } ++ ++ ndev->stats.tx_bytes += skb->len; ++ priv->tx_dirty = entry; ++ desc_tx_close(first, desc, csum_insert); ++ ++ desc_set_own(first); ++ spin_unlock(&priv->tx_lock); ++ ++ if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) <= (MAX_SKB_FRAGS + 1)) { ++ netif_stop_queue(ndev); ++ if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) > TX_THRESH) ++ netif_wake_queue(ndev); ++ } ++ ++#ifdef DEBUG ++ printk("=======TX Descriptor DMA: 0x%08llx\n", priv->dma_tx_phy); ++ printk("Tx pointor: dirty: %d, clean: %d\n", priv->tx_dirty, priv->tx_clean); ++ desc_print(priv->dma_tx, dma_desc_tx); ++#endif ++ sunxi_tx_poll(priv->base); ++ geth_tx_complete(priv); ++ ++ return NETDEV_TX_OK; ++} ++ ++static int geth_rx(struct geth_priv *priv, int limit) ++{ ++ unsigned int rxcount = 0; ++ unsigned int entry; ++ struct dma_desc *desc; ++ struct sk_buff *skb; ++ int status; ++ int frame_len; ++ ++ while (rxcount < limit) { ++ entry = priv->rx_dirty; ++ desc = priv->dma_rx + entry; ++ ++ if (desc_get_own(desc)) ++ break; ++ ++ rxcount++; ++ priv->rx_dirty = circ_inc(priv->rx_dirty, dma_desc_rx); ++ ++ /* Get length & status from hardware */ ++ frame_len = desc_rx_frame_len(desc); ++ status = desc_get_rx_status(desc, (void *)(&priv->xstats)); ++ ++ netdev_dbg(priv->ndev, "Rx frame size %d, status: %d\n", frame_len, status); ++ ++ skb = priv->rx_sk[entry]; ++ if (unlikely(!skb)) { ++ netdev_err(priv->ndev, "Skb is null\n"); ++ priv->ndev->stats.rx_dropped++; ++ break; ++ } ++ ++#ifdef PKT_DEBUG ++ printk("======RX PKT DATA: ============\n"); ++ /* dump the packet */ ++ print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE, ++ 16, 1, skb->data, 64, true); ++#endif ++ ++ if (status == discard_frame) { ++ netdev_dbg(priv->ndev, "Get error pkt\n"); ++ priv->ndev->stats.rx_errors++; ++ continue; ++ } ++ ++ if (unlikely(status != llc_snap)) ++ frame_len -= ETH_FCS_LEN; ++ ++ priv->rx_sk[entry] = NULL; ++ ++ skb_put(skb, frame_len); ++ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc), desc_buf_get_len(desc), DMA_FROM_DEVICE); ++ ++ skb->protocol = eth_type_trans(skb, priv->ndev); ++ ++ skb->ip_summed = CHECKSUM_UNNECESSARY; ++ napi_gro_receive(&priv->napi, skb); ++ ++ priv->ndev->stats.rx_packets++; ++ priv->ndev->stats.rx_bytes += frame_len; ++ } ++ ++#ifdef DEBUG ++ if (rxcount > 0) { ++ printk("======RX Descriptor DMA: 0x%08llx=\n", priv->dma_rx_phy); ++ printk("RX pointor: dirty: %d, clean: %d\n", priv->rx_dirty, priv->rx_clean); ++ desc_print(priv->dma_rx, dma_desc_rx); ++ } ++#endif ++ geth_rx_refill(priv->ndev); ++ ++ return rxcount; ++} ++ ++static int geth_poll(struct napi_struct *napi, int budget) ++{ ++ struct geth_priv *priv = container_of(napi, struct geth_priv, napi); ++ int work_done = 0; ++ ++ geth_tx_complete(priv); ++ work_done = geth_rx(priv, budget); ++ ++ if (work_done < budget) { ++ napi_complete(napi); ++ sunxi_int_enable(priv->base); ++ } ++ ++ return work_done; ++} ++ ++static int geth_change_mtu(struct net_device *ndev, int new_mtu) ++{ ++ int max_mtu; ++ ++ if (netif_running(ndev)) { ++ pr_err("%s: must be stopped to change its MTU\n", ndev->name); ++ return -EBUSY; ++ } ++ ++ max_mtu = SKB_MAX_HEAD(NET_SKB_PAD + NET_IP_ALIGN); ++ ++ if ((new_mtu < 46) || (new_mtu > max_mtu)) { ++ pr_err("%s: invalid MTU, max MTU is: %d\n", ndev->name, max_mtu); ++ return -EINVAL; ++ } ++ ++ ndev->mtu = new_mtu; ++ netdev_update_features(ndev); ++ ++ return 0; ++} ++ ++static netdev_features_t geth_fix_features(struct net_device *ndev, netdev_features_t features) ++{ ++ return features; ++} ++ ++static void geth_set_rx_mode(struct net_device *ndev) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ unsigned int value = 0; ++ ++ pr_debug("%s: # mcasts %d, # unicast %d\n", ++ __func__, netdev_mc_count(ndev), netdev_uc_count(ndev)); ++ ++ spin_lock(&priv->lock); ++ if (ndev->flags & IFF_PROMISC) { ++ value = GETH_FRAME_FILTER_PR; ++ } else if ((netdev_mc_count(ndev) > HASH_TABLE_SIZE) || ++ (ndev->flags & IFF_ALLMULTI)) { ++ value = GETH_FRAME_FILTER_PM; /* pass all multi */ ++ sunxi_hash_filter(priv->base, ~0UL, ~0UL); ++ } else if (!netdev_mc_empty(ndev)) { ++ u32 mc_filter[2]; ++ struct netdev_hw_addr *ha; ++ ++ /* Hash filter for multicast */ ++ value = GETH_FRAME_FILTER_HMC; ++ ++ memset(mc_filter, 0, sizeof(mc_filter)); ++ netdev_for_each_mc_addr(ha, ndev) { ++ /* The upper 6 bits of the calculated CRC are used to ++ * index the contens of the hash table ++ */ ++ int bit_nr = bitrev32(~crc32_le(~0, ha->addr, 6)) >> 26; ++ /* The most significant bit determines the register to ++ * use (H/L) while the other 5 bits determine the bit ++ * within the register. ++ */ ++ mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31); ++ } ++ sunxi_hash_filter(priv->base, mc_filter[0], mc_filter[1]); ++ } ++ ++ /* Handle multiple unicast addresses (perfect filtering)*/ ++ if (netdev_uc_count(ndev) > 16) { ++ /* Switch to promiscuous mode is more than 8 addrs are required */ ++ value |= GETH_FRAME_FILTER_PR; ++ } else { ++ int reg = 1; ++ struct netdev_hw_addr *ha; ++ ++ netdev_for_each_uc_addr(ha, ndev) { ++ sunxi_set_umac(priv->base, ha->addr, reg); ++ reg++; ++ } ++ } ++ ++#ifdef FRAME_FILTER_DEBUG ++ /* Enable Receive all mode (to debug filtering_fail errors) */ ++ value |= GETH_FRAME_FILTER_RA; ++#endif ++ sunxi_set_filter(priv->base, value); ++ spin_unlock(&priv->lock); ++} ++ ++static void geth_tx_timeout(struct net_device *ndev, unsigned int txqueue) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ geth_tx_err(priv); ++} ++ ++static int geth_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd) ++{ ++ if (!netif_running(ndev)) ++ return -EINVAL; ++ ++ if (!ndev->phydev) ++ return -EINVAL; ++ ++ return phy_mii_ioctl(ndev->phydev, rq, cmd); ++} ++ ++/* Configuration changes (passed on by ifconfig) */ ++static int geth_config(struct net_device *ndev, struct ifmap *map) ++{ ++ if (ndev->flags & IFF_UP) /* can't act on a running interface */ ++ return -EBUSY; ++ ++ /* Don't allow changing the I/O address */ ++ if (map->base_addr != ndev->base_addr) { ++ pr_warn("%s: can't change I/O address\n", ndev->name); ++ return -EOPNOTSUPP; ++ } ++ ++ /* Don't allow changing the IRQ */ ++ if (map->irq != ndev->irq) { ++ pr_warn("%s: can't change IRQ number %d\n", ndev->name, ndev->irq); ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int geth_set_mac_address(struct net_device *ndev, void *p) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ struct sockaddr *addr = p; ++ ++ if (!is_valid_ether_addr(addr->sa_data)) ++ return -EADDRNOTAVAIL; ++ ++ eth_hw_addr_set(ndev, addr->sa_data); ++ ++ sunxi_set_umac(priv->base, ndev->dev_addr, 0); ++ ++ return 0; ++} ++ ++int geth_set_features(struct net_device *ndev, netdev_features_t features) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ if (features & NETIF_F_LOOPBACK && netif_running(ndev)) ++ sunxi_mac_loopback(priv->base, 1); ++ else ++ sunxi_mac_loopback(priv->base, 0); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_NET_POLL_CONTROLLER ++/* Polling receive - used by NETCONSOLE and other diagnostic tools ++ * to allow network I/O with interrupts disabled. ++ */ ++static void geth_poll_controller(struct net_device *dev) ++{ ++ disable_irq(dev->irq); ++ geth_interrupt(dev->irq, dev); ++ enable_irq(dev->irq); ++} ++#endif ++ ++static const struct net_device_ops geth_netdev_ops = { ++ .ndo_init = NULL, ++ .ndo_open = geth_open, ++ .ndo_start_xmit = geth_xmit, ++ .ndo_stop = geth_stop, ++ .ndo_change_mtu = geth_change_mtu, ++ .ndo_fix_features = geth_fix_features, ++ .ndo_set_rx_mode = geth_set_rx_mode, ++ .ndo_tx_timeout = geth_tx_timeout, ++ .ndo_do_ioctl = geth_ioctl, ++ .ndo_set_config = geth_config, ++#ifdef CONFIG_NET_POLL_CONTROLLER ++ .ndo_poll_controller = geth_poll_controller, ++#endif ++ .ndo_set_mac_address = geth_set_mac_address, ++ .ndo_set_features = geth_set_features, ++}; ++ ++static int geth_check_if_running(struct net_device *ndev) ++{ ++ if (!netif_running(ndev)) ++ return -EBUSY; ++ return 0; ++} ++ ++static int geth_get_sset_count(struct net_device *netdev, int sset) ++{ ++ int len; ++ ++ switch (sset) { ++ case ETH_SS_STATS: ++ len = 0; ++ return len; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++#if 0 ++static int geth_ethtool_getsettings(struct net_device *ndev, ++ struct ethtool_cmd *cmd) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ struct phy_device *phy = ndev->phydev; ++ int rc; ++ ++ if (phy == NULL) { ++ netdev_err(ndev, "%s: %s: PHY is not registered\n", ++ __func__, ndev->name); ++ return -ENODEV; ++ } ++ ++ if (!netif_running(ndev)) { ++ pr_err("%s: interface is disabled: we cannot track " ++ "link speed / duplex setting\n", ndev->name); ++ return -EBUSY; ++ } ++ ++ cmd->transceiver = XCVR_INTERNAL; ++ spin_lock_irq(&priv->lock); ++ rc = phy_ethtool_gset(phy, cmd); ++ spin_unlock_irq(&priv->lock); ++ ++ return rc; ++} ++ ++static int geth_ethtool_setsettings(struct net_device *ndev, ++ struct ethtool_cmd *cmd) ++{ ++ struct geth_priv *priv = netdev_priv(ndev); ++ struct phy_device *phy = ndev->phydev; ++ int rc; ++ ++ spin_lock(&priv->lock); ++ rc = phy_ethtool_sset(phy, cmd); ++ spin_unlock(&priv->lock); ++ ++ return rc; ++} ++#endif ++ ++static void geth_ethtool_getdrvinfo(struct net_device *ndev, ++ struct ethtool_drvinfo *info) ++{ ++ strlcpy(info->driver, "sunxi_geth", sizeof(info->driver)); ++ ++#define DRV_MODULE_VERSION "SUNXI Gbgit driver V1.1" ++ ++ strcpy(info->version, DRV_MODULE_VERSION); ++ info->fw_version[0] = '\0'; ++} ++ ++static const struct ethtool_ops geth_ethtool_ops = { ++ .begin = geth_check_if_running, ++ // .get_settings = geth_ethtool_getsettings, ++ // .set_settings = geth_ethtool_setsettings, ++ .get_link_ksettings = phy_ethtool_get_link_ksettings, ++ .set_link_ksettings = phy_ethtool_set_link_ksettings, ++ .get_link = ethtool_op_get_link, ++ .get_pauseparam = NULL, ++ .set_pauseparam = NULL, ++ .get_ethtool_stats = NULL, ++ .get_strings = NULL, ++ .get_wol = NULL, ++ .set_wol = NULL, ++ .get_sset_count = geth_get_sset_count, ++ .get_drvinfo = geth_ethtool_getdrvinfo, ++}; ++ ++struct gpio_config { ++ u32 gpio; ++ u32 mul_sel; ++ u32 pull; ++ u32 drv_level; ++ u32 data; ++}; ++ ++/* config hardware resource */ ++static int geth_hw_init(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct geth_priv *priv = netdev_priv(ndev); ++ struct device_node *np = pdev->dev.of_node; ++ int ret = 0; ++ struct resource *res; ++ u32 value; ++ struct gpio_config cfg; ++ const char *gmac_power; ++ char power[20]; ++ int i; ++ ++// #ifdef CONFIG_SUNXI_EXT_PHY ++ priv->phy_ext = EXT_PHY; ++// #else ++// priv->phy_ext = INT_PHY; ++// #endif ++ ++ /* config memery resource */ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (unlikely(!res)) { ++ pr_err("%s: ERROR: get gmac memory failed", __func__); ++ return -ENODEV; ++ } ++ ++ priv->base = devm_ioremap_resource(&pdev->dev, res); ++ if (!priv->base) { ++ pr_err("%s: ERROR: gmac memory mapping failed", __func__); ++ return -ENOMEM; ++ } ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); ++ if (unlikely(!res)) { ++ pr_err("%s: ERROR: get phy memory failed", __func__); ++ ret = -ENODEV; ++ goto mem_err; ++ } ++ ++ priv->base_phy = devm_ioremap_resource(&pdev->dev, res); ++ if (unlikely(!priv->base_phy)) { ++ pr_err("%s: ERROR: phy memory mapping failed", __func__); ++ ret = -ENOMEM; ++ goto mem_err; ++ } ++ ++ /* config IRQ */ ++ ndev->irq = platform_get_irq_byname(pdev, "gmacirq"); ++ if (ndev->irq == -ENXIO) { ++ pr_err("%s: ERROR: MAC IRQ not found\n", __func__); ++ ret = -ENXIO; ++ goto irq_err; ++ } ++ ++ ret = request_irq(ndev->irq, geth_interrupt, IRQF_SHARED, dev_name(&pdev->dev), ndev); ++ if (unlikely(ret < 0)) { ++ pr_err("Could not request irq %d, error: %d\n", ndev->irq, ret); ++ goto irq_err; ++ } ++ ++ /* config clock */ ++ priv->geth_clk = of_clk_get_by_name(np, "bus-emac1"); ++ if (unlikely(!priv->geth_clk || IS_ERR(priv->geth_clk))) { ++ pr_err("Get gmac clock failed!\n"); ++ ret = -EINVAL; ++ goto clk_err; ++ } ++ ++ if (INT_PHY == priv->phy_ext) { ++ priv->ephy_clk = of_clk_get_by_name(np, "emac-25m"); ++ if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) { ++ pr_err("Get ephy clock failed!\n"); ++ ret = -EINVAL; ++ goto clk_err; ++ } ++ } ++#if 1 // defined(CONFIG_ARCH_SUN8IW12) || defined(CONFIG_ARCH_SUN50IW9) ++ else { ++ if (!of_property_read_u32(np, "use_ephy25m", &g_use_ephy_clk) ++ && g_use_ephy_clk) { ++ priv->ephy_clk = of_clk_get_by_name(np, "ephy"); ++ if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) { ++ pr_err("Get ephy clk failed!\n"); ++ ret = -EINVAL; ++ goto clk_err; ++ } ++ } ++ } ++#endif ++ ++ /* config power regulator */ ++ if (EXT_PHY == priv->phy_ext) { ++ for (i = 0; i < POWER_CHAN_NUM; i++) { ++ snprintf(power, 15, "gmac-power%d", i); ++ ret = of_property_read_string(np, power, &gmac_power); ++ if (ret) { ++ priv->gmac_power[i] = NULL; ++ pr_info("gmac-power%d: NULL\n", i); ++ continue; ++ } ++ priv->gmac_power[i] = regulator_get(NULL, gmac_power); ++ if (IS_ERR(priv->gmac_power[i])) { ++ pr_err("gmac-power%d get error!\n", i); ++ ret = -EINVAL; ++ goto clk_err; ++ } ++ } ++ } ++ ++ // /* config other parameters */ ++ // priv->phy_interface = of_get_phy_mode(np); ++ // if (priv->phy_interface != PHY_INTERFACE_MODE_MII && ++ // priv->phy_interface != PHY_INTERFACE_MODE_RGMII && ++ // priv->phy_interface != PHY_INTERFACE_MODE_RMII) { ++ // pr_err("Not support phy type!\n"); ++ // priv->phy_interface = PHY_INTERFACE_MODE_MII; ++ // } ++ ++ priv->phy_interface = PHY_INTERFACE_MODE_RMII; ++ ++ if (!of_property_read_u32(np, "tx-delay", &value)) ++ tx_delay = value; ++ ++ if (!of_property_read_u32(np, "rx-delay", &value)) ++ rx_delay = value; ++ ++ /* config pinctrl */ ++ if (EXT_PHY == priv->phy_ext) { ++ priv->phyrst = of_get_named_gpio_flags(np, "phy-rst", 0, (enum of_gpio_flags *)&cfg); ++ priv->rst_active_low = (cfg.data == OF_GPIO_ACTIVE_LOW) ? 1 : 0; ++ ++ if (gpio_is_valid(priv->phyrst)) { ++ if (gpio_request(priv->phyrst, "phy-rst") < 0) { ++ pr_err("gmac gpio request fail!\n"); ++ ret = -EINVAL; ++ goto pin_err; ++ } ++ } ++ ++ priv->pinctrl = devm_pinctrl_get_select_default(&pdev->dev); ++ if (IS_ERR_OR_NULL(priv->pinctrl)) { ++ pr_err("gmac pinctrl error!\n"); ++ priv->pinctrl = NULL; ++ ret = -EINVAL; ++ goto pin_err; ++ } ++ } ++ ++ priv->ephy_rst = devm_reset_control_get_optional(&pdev->dev, "stmmaceth"); ++ reset_control_assert(priv->ephy_rst); ++ reset_control_deassert(priv->ephy_rst); ++ ++ msleep(800); ++ ++ return 0; ++ ++pin_err: ++ if (EXT_PHY == priv->phy_ext) { ++ for (i = 0; i < POWER_CHAN_NUM; i++) { ++ if (IS_ERR_OR_NULL(priv->gmac_power[i])) ++ continue; ++ regulator_put(priv->gmac_power[i]); ++ } ++ } ++clk_err: ++ free_irq(ndev->irq, ndev); ++irq_err: ++ devm_iounmap(&pdev->dev, priv->base_phy); ++mem_err: ++ devm_iounmap(&pdev->dev, priv->base); ++ ++ return ret; ++} ++ ++static void geth_hw_release(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct geth_priv *priv = netdev_priv(ndev); ++ int i; ++ ++ devm_iounmap(&pdev->dev, (priv->base_phy)); ++ devm_iounmap(&pdev->dev, priv->base); ++ free_irq(ndev->irq, ndev); ++ if (priv->geth_clk) ++ clk_put(priv->geth_clk); ++ ++ if (EXT_PHY == priv->phy_ext) { ++ for (i = 0; i < POWER_CHAN_NUM; i++) { ++ if (IS_ERR_OR_NULL(priv->gmac_power[i])) ++ continue; ++ regulator_put(priv->gmac_power[i]); ++ } ++ ++ if (!IS_ERR_OR_NULL(priv->pinctrl)) ++ devm_pinctrl_put(priv->pinctrl); ++ ++ if (gpio_is_valid(priv->phyrst)) ++ gpio_free(priv->phyrst); ++ } ++ ++ if (!IS_ERR_OR_NULL(priv->ephy_clk)) ++ clk_put(priv->ephy_clk); ++} ++ ++/** ++ * geth_probe ++ * @pdev: platform device pointer ++ * Description: the driver is initialized through platform_device. ++ */ ++static int geth_probe(struct platform_device *pdev) ++{ ++ int ret = 0; ++ struct net_device *ndev = NULL; ++ struct geth_priv *priv; ++ ++#ifdef CONFIG_OF ++ pdev->dev.dma_mask = &geth_dma_mask; ++ pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); ++#endif ++ ++ ndev = alloc_etherdev(sizeof(struct geth_priv)); ++ if (!ndev) { ++ dev_err(&pdev->dev, "could not allocate device.\n"); ++ return -ENOMEM; ++ } ++ SET_NETDEV_DEV(ndev, &pdev->dev); ++ ++ priv = netdev_priv(ndev); ++ platform_set_drvdata(pdev, ndev); ++ ++ /* Must set private data to pdev, before call it */ ++ ret = geth_hw_init(pdev); ++ if (0 != ret) { ++ pr_err("geth_hw_init fail!\n"); ++ goto hw_err; ++ } ++ ++ /* setup the netdevice, fill the field of netdevice */ ++ ether_setup(ndev); ++ ndev->netdev_ops = &geth_netdev_ops; ++ netdev_set_default_ethtool_ops(ndev, &geth_ethtool_ops); ++ ndev->base_addr = (unsigned long)priv->base; ++ ++ priv->ndev = ndev; ++ priv->dev = &pdev->dev; ++ ++ /* TODO: support the VLAN frames */ ++ ndev->hw_features = NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_IP_CSUM | ++ NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM; ++ ++ ndev->features |= ndev->hw_features; ++ ndev->hw_features |= NETIF_F_LOOPBACK; ++ ndev->priv_flags |= IFF_UNICAST_FLT; ++ ++ ndev->watchdog_timeo = msecs_to_jiffies(watchdog); ++ ++ netif_napi_add(ndev, &priv->napi, geth_poll); ++ ++ spin_lock_init(&priv->lock); ++ spin_lock_init(&priv->tx_lock); ++ ++ /* The last val is mdc clock ratio */ ++ sunxi_geth_register((void *)ndev->base_addr, HW_VERSION, 0x03); ++ ++ ret = register_netdev(ndev); ++ if (ret) { ++ netif_napi_del(&priv->napi); ++ pr_err("Error: Register %s failed\n", ndev->name); ++ goto reg_err; ++ } ++ ++ /* Before open the device, the mac address should be set */ ++ geth_check_addr(ndev, mac_str); ++ ++#ifdef CONFIG_GETH_ATTRS ++ geth_create_attrs(ndev); ++#endif ++ device_create_file(&pdev->dev, &dev_attr_gphy_test); ++ device_create_file(&pdev->dev, &dev_attr_mii_read); ++ device_create_file(&pdev->dev, &dev_attr_mii_write); ++ ++ device_enable_async_suspend(&pdev->dev); ++ ++#ifdef CONFIG_PM ++ INIT_WORK(&priv->eth_work, geth_resume_work); ++#endif ++ ++ return 0; ++ ++reg_err: ++ geth_hw_release(pdev); ++hw_err: ++ platform_set_drvdata(pdev, NULL); ++ free_netdev(ndev); ++ ++ return ret; ++} ++ ++static int geth_remove(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct geth_priv *priv = netdev_priv(ndev); ++ ++ device_remove_file(&pdev->dev, &dev_attr_gphy_test); ++ device_remove_file(&pdev->dev, &dev_attr_mii_read); ++ device_remove_file(&pdev->dev, &dev_attr_mii_write); ++ ++ netif_napi_del(&priv->napi); ++ unregister_netdev(ndev); ++ geth_hw_release(pdev); ++ platform_set_drvdata(pdev, NULL); ++ free_netdev(ndev); ++ ++ return 0; ++} ++ ++static const struct of_device_id geth_of_match[] = { ++ {.compatible = "allwinner,sunxi-gmac",}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, geth_of_match); ++ ++static struct platform_driver geth_driver = { ++ .probe = geth_probe, ++ .remove = geth_remove, ++ .suspend = ucc_geth_suspend, ++ .resume = ucc_geth_resume, ++ .driver = { ++ .name = "sunxi-gmac", ++ .owner = THIS_MODULE, ++ .pm = &geth_pm_ops, ++ .of_match_table = geth_of_match, ++ }, ++}; ++module_platform_driver(geth_driver); ++ ++#ifndef MODULE ++static int __init set_mac_addr(char *str) ++{ ++ char *p = str; ++ ++ if (str && strlen(str)) ++ memcpy(mac_str, p, 18); ++ ++ return 0; ++} ++__setup("mac_addr=", set_mac_addr); ++#endif ++ ++MODULE_DESCRIPTION("Allwinner Gigabit Ethernet driver"); ++MODULE_AUTHOR("fuzhaoke "); ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/net/ethernet/allwinner/sunxi-gmac.h b/drivers/net/ethernet/allwinner/sunxi-gmac.h +new file mode 100644 +index 000000000000..0ba8977d28f4 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/sunxi-gmac.h +@@ -0,0 +1,270 @@ ++/* ++ * linux/drivers/net/ethernet/allwinner/sunxi_gmac.h ++ * ++ * Copyright © 2016-2018, fuzhaoke ++ * Author: fuzhaoke ++ * ++ * This file is provided under a dual BSD/GPL license. When using or ++ * redistributing this file, you may do so under either license. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++#ifndef __SUNXI_GETH_H__ ++#define __SUNXI_GETH_H__ ++ ++#include ++#include ++#include ++#include ++#include ++ ++/* GETH_FRAME_FILTER register value */ ++#define GETH_FRAME_FILTER_PR 0x00000001 /* Promiscuous Mode */ ++#define GETH_FRAME_FILTER_HUC 0x00000002 /* Hash Unicast */ ++#define GETH_FRAME_FILTER_HMC 0x00000004 /* Hash Multicast */ ++#define GETH_FRAME_FILTER_DAIF 0x00000008 /* DA Inverse Filtering */ ++#define GETH_FRAME_FILTER_PM 0x00000010 /* Pass all multicast */ ++#define GETH_FRAME_FILTER_DBF 0x00000020 /* Disable Broadcast frames */ ++#define GETH_FRAME_FILTER_SAIF 0x00000100 /* Inverse Filtering */ ++#define GETH_FRAME_FILTER_SAF 0x00000200 /* Source Address Filter */ ++#define GETH_FRAME_FILTER_HPF 0x00000400 /* Hash or perfect Filter */ ++#define GETH_FRAME_FILTER_RA 0x80000000 /* Receive all mode */ ++ ++/* Default tx descriptor */ ++#define TX_SINGLE_DESC0 0x80000000 ++#define TX_SINGLE_DESC1 0x63000000 ++ ++/* Default rx descriptor */ ++#define RX_SINGLE_DESC0 0x80000000 ++#define RX_SINGLE_DESC1 0x83000000 ++ ++#define EPHY_ID 0x00441400 ++#define AC300_ID 0xc0000000 ++#define EPHY_ID_RTL8211F 0x001CC916 ++#define IP101G_ID 0x02430c54 ++ ++typedef union { ++ struct { ++ /* TDES0 */ ++ unsigned int deferred:1; /* Deferred bit (only half-duplex) */ ++ unsigned int under_err:1; /* Underflow error */ ++ unsigned int ex_deferral:1; /* Excessive deferral */ ++ unsigned int coll_cnt:4; /* Collision count */ ++ unsigned int vlan_tag:1; /* VLAN Frame */ ++ unsigned int ex_coll:1; /* Excessive collision */ ++ unsigned int late_coll:1; /* Late collision */ ++ unsigned int no_carr:1; /* No carrier */ ++ unsigned int loss_carr:1; /* Loss of collision */ ++ unsigned int ipdat_err:1; /* IP payload error */ ++ unsigned int frm_flu:1; /* Frame flushed */ ++ unsigned int jab_timeout:1; /* Jabber timeout */ ++ unsigned int err_sum:1; /* Error summary */ ++ unsigned int iphead_err:1; /* IP header error */ ++ unsigned int ttss:1; /* Transmit time stamp status */ ++ unsigned int reserved0:13; ++ unsigned int own:1; /* Own bit. CPU:0, DMA:1 */ ++ } tx; ++ ++ /* bits 5 7 0 | Frame status ++ * ---------------------------------------------------------- ++ * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octects) ++ * 1 0 0 | IPv4/6 No CSUM errorS. ++ * 1 0 1 | IPv4/6 CSUM PAYLOAD error ++ * 1 1 0 | IPv4/6 CSUM IP HR error ++ * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS ++ * 0 0 1 | IPv4/6 unsupported IP PAYLOAD ++ * 0 1 1 | COE bypassed.. no IPv4/6 frame ++ * 0 1 0 | Reserved. ++ */ ++ struct { ++ /* RDES0 */ ++ unsigned int chsum_err:1; /* Payload checksum error */ ++ unsigned int crc_err:1; /* CRC error */ ++ unsigned int dribbling:1; /* Dribble bit error */ ++ unsigned int mii_err:1; /* Received error (bit3) */ ++ unsigned int recv_wt:1; /* Received watchdog timeout */ ++ unsigned int frm_type:1; /* Frame type */ ++ unsigned int late_coll:1; /* Late Collision */ ++ unsigned int ipch_err:1; /* IPv header checksum error (bit7) */ ++ unsigned int last_desc:1; /* Laset descriptor */ ++ unsigned int first_desc:1; /* First descriptor */ ++ unsigned int vlan_tag:1; /* VLAN Tag */ ++ unsigned int over_err:1; /* Overflow error (bit11) */ ++ unsigned int len_err:1; /* Length error */ ++ unsigned int sou_filter:1; /* Source address filter fail */ ++ unsigned int desc_err:1; /* Descriptor error */ ++ unsigned int err_sum:1; /* Error summary (bit15) */ ++ unsigned int frm_len:14; /* Frame length */ ++ unsigned int des_filter:1; /* Destination address filter fail */ ++ unsigned int own:1; /* Own bit. CPU:0, DMA:1 */ ++ #define RX_PKT_OK 0x7FFFB77C ++ #define RX_LEN 0x3FFF0000 ++ } rx; ++ ++ unsigned int all; ++} desc0_u; ++ ++typedef union { ++ struct { ++ /* TDES1 */ ++ unsigned int buf1_size:11; /* Transmit buffer1 size */ ++ unsigned int buf2_size:11; /* Transmit buffer2 size */ ++ unsigned int ttse:1; /* Transmit time stamp enable */ ++ unsigned int dis_pad:1; /* Disable pad (bit23) */ ++ unsigned int adr_chain:1; /* Second address chained */ ++ unsigned int end_ring:1; /* Transmit end of ring */ ++ unsigned int crc_dis:1; /* Disable CRC */ ++ unsigned int cic:2; /* Checksum insertion control (bit27:28) */ ++ unsigned int first_sg:1; /* First Segment */ ++ unsigned int last_seg:1; /* Last Segment */ ++ unsigned int interrupt:1; /* Interrupt on completion */ ++ } tx; ++ ++ struct { ++ /* RDES1 */ ++ unsigned int buf1_size:11; /* Received buffer1 size */ ++ unsigned int buf2_size:11; /* Received buffer2 size */ ++ unsigned int reserved1:2; ++ unsigned int adr_chain:1; /* Second address chained */ ++ unsigned int end_ring:1; /* Received end of ring */ ++ unsigned int reserved2:5; ++ unsigned int dis_ic:1; /* Disable interrupt on completion */ ++ } rx; ++ ++ unsigned int all; ++} desc1_u; ++ ++typedef struct dma_desc { ++ desc0_u desc0; ++ desc1_u desc1; ++ /* The address of buffers */ ++ unsigned int desc2; ++ /* Next desc's address */ ++ unsigned int desc3; ++} __attribute__((packed)) dma_desc_t; ++ ++enum rx_frame_status { /* IPC status */ ++ good_frame = 0, ++ discard_frame = 1, ++ csum_none = 2, ++ llc_snap = 4, ++}; ++ ++enum tx_dma_irq_status { ++ tx_hard_error = 1, ++ tx_hard_error_bump_tc = 2, ++ handle_tx_rx = 3, ++}; ++ ++struct geth_extra_stats { ++ /* Transmit errors */ ++ unsigned long tx_underflow; ++ unsigned long tx_carrier; ++ unsigned long tx_losscarrier; ++ unsigned long vlan_tag; ++ unsigned long tx_deferred; ++ unsigned long tx_vlan; ++ unsigned long tx_jabber; ++ unsigned long tx_frame_flushed; ++ unsigned long tx_payload_error; ++ unsigned long tx_ip_header_error; ++ ++ /* Receive errors */ ++ unsigned long rx_desc; ++ unsigned long sa_filter_fail; ++ unsigned long overflow_error; ++ unsigned long ipc_csum_error; ++ unsigned long rx_collision; ++ unsigned long rx_crc; ++ unsigned long dribbling_bit; ++ unsigned long rx_length; ++ unsigned long rx_mii; ++ unsigned long rx_multicast; ++ unsigned long rx_gmac_overflow; ++ unsigned long rx_watchdog; ++ unsigned long da_rx_filter_fail; ++ unsigned long sa_rx_filter_fail; ++ unsigned long rx_missed_cntr; ++ unsigned long rx_overflow_cntr; ++ unsigned long rx_vlan; ++ ++ /* Tx/Rx IRQ errors */ ++ unsigned long tx_undeflow_irq; ++ unsigned long tx_process_stopped_irq; ++ unsigned long tx_jabber_irq; ++ unsigned long rx_overflow_irq; ++ unsigned long rx_buf_unav_irq; ++ unsigned long rx_process_stopped_irq; ++ unsigned long rx_watchdog_irq; ++ unsigned long tx_early_irq; ++ unsigned long fatal_bus_error_irq; ++ ++ /* Extra info */ ++ unsigned long threshold; ++ unsigned long tx_pkt_n; ++ unsigned long rx_pkt_n; ++ unsigned long poll_n; ++ unsigned long sched_timer_n; ++ unsigned long normal_irq_n; ++}; ++ ++struct mii_reg_dump { ++ u32 addr; ++ u16 reg; ++ u16 value; ++}; ++ ++int sunxi_mdio_read(void *, int, int); ++int sunxi_mdio_write(void *, int, int, unsigned short); ++int sunxi_mdio_reset(void *); ++void sunxi_set_link_mode(void *iobase, int duplex, int speed); ++void sunxi_int_disable(void *); ++int sunxi_int_status(void *, struct geth_extra_stats *x); ++int sunxi_mac_init(void *, int txmode, int rxmode); ++void sunxi_set_umac(void *, unsigned char *, int); ++void sunxi_mac_enable(void *); ++void sunxi_mac_disable(void *); ++void sunxi_tx_poll(void *); ++void sunxi_int_enable(void *); ++void sunxi_start_rx(void *, unsigned long); ++void sunxi_start_tx(void *, unsigned long); ++void sunxi_stop_tx(void *); ++void sunxi_stop_rx(void *); ++void sunxi_hash_filter(void *iobase, unsigned long low, unsigned long high); ++void sunxi_set_filter(void *iobase, unsigned long flags); ++void sunxi_flow_ctrl(void *iobase, int duplex, int fc, int pause); ++void sunxi_mac_loopback(void *iobase, int enable); ++ ++void desc_buf_set(struct dma_desc *p, unsigned long paddr, int size); ++void desc_set_own(struct dma_desc *p); ++void desc_init_chain(struct dma_desc *p, unsigned long paddr, unsigned int size); ++void desc_tx_close(struct dma_desc *first, struct dma_desc *end, int csum_insert); ++void desc_init(struct dma_desc *p); ++int desc_get_tx_status(struct dma_desc *desc, struct geth_extra_stats *x); ++int desc_buf_get_len(struct dma_desc *desc); ++int desc_buf_get_addr(struct dma_desc *desc); ++int desc_get_rx_status(struct dma_desc *desc, struct geth_extra_stats *x); ++int desc_get_own(struct dma_desc *desc); ++int desc_get_tx_ls(struct dma_desc *desc); ++int desc_rx_frame_len(struct dma_desc *desc); ++ ++int sunxi_mac_reset(void *iobase, void (*mdelay)(int), int n); ++int sunxi_geth_register(void *iobase, int version, unsigned int div); ++ ++int sunxi_parse_read_str(char *str, u16 *addr, u16 *reg); ++int sunxi_parse_write_str(char *str, u16 *addr, u16 *reg, u16 *val); ++extern int ephy_is_enable(void); ++extern int gmac_ephy_shutdown(void); ++ ++#if defined(CONFIG_ARCH_SUN8IW3) \ ++ || defined(CONFIG_ARCH_SUN9IW1) \ ++ || defined(CONFIG_ARCH_SUN7I) ++#define HW_VERSION 0 ++#else ++#define HW_VERSION 1 ++#endif ++ ++#endif +diff --git a/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c b/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c +new file mode 100644 +index 000000000000..926516835023 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c +@@ -0,0 +1,768 @@ ++/* ++ * linux/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c ++ * ++ * Copyright © 2016-2018, fuzhaoke ++ * Author: fuzhaoke ++ * ++ * This file is provided under a dual BSD/GPL license. When using or ++ * redistributing this file, you may do so under either license. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++#include ++#include ++#include ++#include ++#include "sunxi-gmac.h" ++ ++/****************************************************************************** ++ * sun8iw6 operations ++ *****************************************************************************/ ++#define GETH_BASIC_CTL0 0x00 ++#define GETH_BASIC_CTL1 0x04 ++#define GETH_INT_STA 0x08 ++#define GETH_INT_EN 0x0C ++#define GETH_TX_CTL0 0x10 ++#define GETH_TX_CTL1 0x14 ++#define GETH_TX_FLOW_CTL 0x1C ++#define GETH_TX_DESC_LIST 0x20 ++#define GETH_RX_CTL0 0x24 ++#define GETH_RX_CTL1 0x28 ++#define GETH_RX_DESC_LIST 0x34 ++#define GETH_RX_FRM_FLT 0x38 ++#define GETH_RX_HASH0 0x40 ++#define GETH_RX_HASH1 0x44 ++#define GETH_MDIO_ADDR 0x48 ++#define GETH_MDIO_DATA 0x4C ++#define GETH_ADDR_HI(reg) (0x50 + ((reg) << 3)) ++#define GETH_ADDR_LO(reg) (0x54 + ((reg) << 3)) ++#define GETH_TX_DMA_STA 0xB0 ++#define GETH_TX_CUR_DESC 0xB4 ++#define GETH_TX_CUR_BUF 0xB8 ++#define GETH_RX_DMA_STA 0xC0 ++#define GETH_RX_CUR_DESC 0xC4 ++#define GETH_RX_CUR_BUF 0xC8 ++#define GETH_RGMII_STA 0xD0 ++ ++#define RGMII_IRQ 0x00000001 ++ ++#define CTL0_LM 0x02 ++#define CTL0_DM 0x01 ++#define CTL0_SPEED 0x04 ++ ++#define BURST_LEN 0x3F000000 ++#define RX_TX_PRI 0x02 ++#define SOFT_RST 0x01 ++ ++#define TX_FLUSH 0x01 ++#define TX_MD 0x02 ++#define TX_NEXT_FRM 0x04 ++#define TX_TH 0x0700 ++ ++#define RX_FLUSH 0x01 ++#define RX_MD 0x02 ++#define RX_RUNT_FRM 0x04 ++#define RX_TH 0x0030 ++ ++#define TX_INT 0x00001 ++#define TX_STOP_INT 0x00002 ++#define TX_UA_INT 0x00004 ++#define TX_TOUT_INT 0x00008 ++#define TX_UNF_INT 0x00010 ++#define TX_EARLY_INT 0x00020 ++#define RX_INT 0x00100 ++#define RX_UA_INT 0x00200 ++#define RX_STOP_INT 0x00400 ++#define RX_TOUT_INT 0x00800 ++#define RX_OVF_INT 0x01000 ++#define RX_EARLY_INT 0x02000 ++#define LINK_STA_INT 0x10000 ++ ++#define DISCARD_FRAME -1 ++#define GOOD_FRAME 0 ++#define CSUM_NONE 2 ++#define LLC_SNAP 4 ++ ++#define SF_DMA_MODE 1 ++ ++/* Flow Control defines */ ++#define FLOW_OFF 0 ++#define FLOW_RX 1 ++#define FLOW_TX 2 ++#define FLOW_AUTO (FLOW_TX | FLOW_RX) ++ ++#define HASH_TABLE_SIZE 64 ++#define PAUSE_TIME 0x200 ++#define GMAC_MAX_UNICAST_ADDRESSES 8 ++ ++/* PHY address */ ++#define PHY_ADDR 0x01 ++#define PHY_DM 0x0010 ++#define PHY_AUTO_NEG 0x0020 ++#define PHY_POWERDOWN 0x0080 ++#define PHY_NEG_EN 0x1000 ++ ++#define MII_BUSY 0x00000001 ++#define MII_WRITE 0x00000002 ++#define MII_PHY_MASK 0x0000FFC0 ++#define MII_CR_MASK 0x0000001C ++#define MII_CLK 0x00000008 ++/* bits 4 3 2 | AHB1 Clock | MDC Clock ++ * ------------------------------------------------------- ++ * 0 0 0 | 60 ~ 100 MHz | div-42 ++ * 0 0 1 | 100 ~ 150 MHz | div-62 ++ * 0 1 0 | 20 ~ 35 MHz | div-16 ++ * 0 1 1 | 35 ~ 60 MHz | div-26 ++ * 1 0 0 | 150 ~ 250 MHz | div-102 ++ * 1 0 1 | 250 ~ 300 MHz | div-124 ++ * 1 1 x | Reserved | ++ */ ++ ++enum csum_insertion { ++ cic_dis = 0, /* Checksum Insertion Control */ ++ cic_ip = 1, /* Only IP header */ ++ cic_no_pse = 2, /* IP header but not pseudoheader */ ++ cic_full = 3, /* IP header and pseudoheader */ ++}; ++ ++struct gethdev { ++ void *iobase; ++ unsigned int ver; ++ unsigned int mdc_div; ++}; ++ ++static struct gethdev hwdev; ++ ++/*************************************************************************** ++ * External interface ++ **************************************************************************/ ++/* Set a ring desc buffer */ ++void desc_init_chain(struct dma_desc *desc, unsigned long addr, unsigned int size) ++{ ++ /* In chained mode the desc3 points to the next element in the ring. ++ * The latest element has to point to the head. ++ */ ++ int i; ++ struct dma_desc *p = desc; ++ unsigned long dma_phy = addr; ++ ++ for (i = 0; i < (size - 1); i++) { ++ dma_phy += sizeof(struct dma_desc); ++ p->desc3 = (unsigned int)dma_phy; ++ /* Chain mode */ ++ p->desc1.all |= (1 << 24); ++ p++; ++ } ++ p->desc1.all |= (1 << 24); ++ p->desc3 = (unsigned int)addr; ++} ++ ++int sunxi_mdio_read(void *iobase, int phyaddr, int phyreg) ++{ ++ unsigned int value = 0; ++ ++ /* Mask the MDC_DIV_RATIO */ ++ value |= ((hwdev.mdc_div & 0x07) << 20); ++ value |= (((phyaddr << 12) & (0x0001F000)) | ++ ((phyreg << 4) & (0x000007F0)) | ++ MII_BUSY); ++ ++ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1) ++ ; ++ ++ writel(value, iobase + GETH_MDIO_ADDR); ++ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1) ++ ; ++ ++ return (int)readl(iobase + GETH_MDIO_DATA); ++} ++ ++int sunxi_mdio_write(void *iobase, int phyaddr, int phyreg, unsigned short data) ++{ ++ unsigned int value; ++ ++ value = ((0x07 << 20) & readl(iobase + GETH_MDIO_ADDR)) | ++ (hwdev.mdc_div << 20); ++ value |= (((phyaddr << 12) & (0x0001F000)) | ++ ((phyreg << 4) & (0x000007F0))) | ++ MII_WRITE | MII_BUSY; ++ ++ /* Wait until any existing MII operation is complete */ ++ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1) ++ ; ++ ++ /* Set the MII address register to write */ ++ writel(data, iobase + GETH_MDIO_DATA); ++ writel(value, iobase + GETH_MDIO_ADDR); ++ ++ /* Wait until any existing MII operation is complete */ ++ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1) ++ ; ++ ++ return 0; ++} ++ ++int sunxi_mdio_reset(void *iobase) ++{ ++ writel((4 << 2), iobase + GETH_MDIO_ADDR); ++ return 0; ++} ++ ++void sunxi_set_link_mode(void *iobase, int duplex, int speed) ++{ ++ unsigned int ctrl = readl(iobase + GETH_BASIC_CTL0); ++ ++ if (!duplex) ++ ctrl &= ~CTL0_DM; ++ else ++ ctrl |= CTL0_DM; ++ ++ switch (speed) { ++ case 1000: ++ ctrl &= ~0x0C; ++ break; ++ case 100: ++ case 10: ++ default: ++ ctrl |= 0x08; ++ if (speed == 100) ++ ctrl |= 0x04; ++ else ++ ctrl &= ~0x04; ++ break; ++ } ++ ++ writel(ctrl, iobase + GETH_BASIC_CTL0); ++} ++ ++void sunxi_mac_loopback(void *iobase, int enable) ++{ ++ int reg; ++ ++ reg = readl(iobase + GETH_BASIC_CTL0); ++ if (enable) ++ reg |= 0x02; ++ else ++ reg &= ~0x02; ++ writel(reg, iobase + GETH_BASIC_CTL0); ++} ++ ++void sunxi_flow_ctrl(void *iobase, int duplex, int fc, int pause) ++{ ++ unsigned int flow = 0; ++ ++ if (fc & FLOW_RX) { ++ flow = readl(iobase + GETH_RX_CTL0); ++ flow |= 0x10000; ++ writel(flow, iobase + GETH_RX_CTL0); ++ } ++ ++ if (fc & FLOW_TX) { ++ flow = readl(iobase + GETH_TX_FLOW_CTL); ++ flow |= 0x00001; ++ writel(flow, iobase + GETH_TX_FLOW_CTL); ++ } ++ ++ if (duplex) { ++ flow = readl(iobase + GETH_TX_FLOW_CTL); ++ flow |= (pause << 4); ++ writel(flow, iobase + GETH_TX_FLOW_CTL); ++ } ++} ++ ++int sunxi_int_status(void *iobase, struct geth_extra_stats *x) ++{ ++ int ret = 0; ++ /* read the status register (CSR5) */ ++ unsigned int intr_status; ++ ++ intr_status = readl(iobase + GETH_RGMII_STA); ++ if (intr_status & RGMII_IRQ) ++ readl(iobase + GETH_RGMII_STA); ++ ++ intr_status = readl(iobase + GETH_INT_STA); ++ ++ /* ABNORMAL interrupts */ ++ if (intr_status & TX_UNF_INT) { ++ ret = tx_hard_error_bump_tc; ++ x->tx_undeflow_irq++; ++ } ++ if (intr_status & TX_TOUT_INT) { ++ x->tx_jabber_irq++; ++ } ++ if (intr_status & RX_OVF_INT) { ++ x->rx_overflow_irq++; ++ } ++ if (intr_status & RX_UA_INT) { ++ x->rx_buf_unav_irq++; ++ } ++ if (intr_status & RX_STOP_INT) { ++ x->rx_process_stopped_irq++; ++ } ++ if (intr_status & RX_TOUT_INT) { ++ x->rx_watchdog_irq++; ++ } ++ if (intr_status & TX_EARLY_INT) { ++ x->tx_early_irq++; ++ } ++ if (intr_status & TX_STOP_INT) { ++ x->tx_process_stopped_irq++; ++ ret = tx_hard_error; ++ } ++ ++ /* TX/RX NORMAL interrupts */ ++ if (intr_status & (TX_INT | RX_INT | RX_EARLY_INT | TX_UA_INT)) { ++ x->normal_irq_n++; ++ if (intr_status & (TX_INT | RX_INT)) ++ ret = handle_tx_rx; ++ } ++ /* Clear the interrupt by writing a logic 1 to the CSR5[15-0] */ ++ writel(intr_status & 0x3FFF, iobase + GETH_INT_STA); ++ ++ return ret; ++} ++ ++void sunxi_start_rx(void *iobase, unsigned long rxbase) ++{ ++ unsigned int value; ++ ++ /* Write the base address of Rx descriptor lists into registers */ ++ writel(rxbase, iobase + GETH_RX_DESC_LIST); ++ ++ value = readl(iobase + GETH_RX_CTL1); ++ value |= 0x40000000; ++ writel(value, iobase + GETH_RX_CTL1); ++} ++ ++void sunxi_stop_rx(void *iobase) ++{ ++ unsigned int value; ++ ++ value = readl(iobase + GETH_RX_CTL1); ++ value &= ~0x40000000; ++ writel(value, iobase + GETH_RX_CTL1); ++} ++ ++void sunxi_start_tx(void *iobase, unsigned long txbase) ++{ ++ unsigned int value; ++ ++ /* Write the base address of Tx descriptor lists into registers */ ++ writel(txbase, iobase + GETH_TX_DESC_LIST); ++ ++ value = readl(iobase + GETH_TX_CTL1); ++ value |= 0x40000000; ++ writel(value, iobase + GETH_TX_CTL1); ++} ++ ++void sunxi_stop_tx(void *iobase) ++{ ++ unsigned int value = readl(iobase + GETH_TX_CTL1); ++ ++ value &= ~0x40000000; ++ writel(value, iobase + GETH_TX_CTL1); ++} ++ ++static int sunxi_dma_init(void *iobase) ++{ ++ unsigned int value; ++ ++ /* Burst should be 8 */ ++ value = (8 << 24); ++ ++#ifdef CONFIG_GMAC_DA ++ value |= RX_TX_PRI; /* Rx has priority over tx */ ++#endif ++ writel(value, iobase + GETH_BASIC_CTL1); ++ ++ /* Mask interrupts by writing to CSR7 */ ++ writel(RX_INT | TX_INT | TX_UNF_INT, iobase + GETH_INT_EN); ++ ++ return 0; ++} ++ ++int sunxi_mac_init(void *iobase, int txmode, int rxmode) ++{ ++ unsigned int value; ++ ++ sunxi_dma_init(iobase); ++ ++ /* Initialize the core component */ ++ value = readl(iobase + GETH_TX_CTL0); ++ value |= (1 << 30); /* Jabber Disable */ ++ writel(value, iobase + GETH_TX_CTL0); ++ ++ value = readl(iobase + GETH_RX_CTL0); ++ value |= (1 << 27); /* Enable CRC & IPv4 Header Checksum */ ++ value |= (1 << 28); /* Automatic Pad/CRC Stripping */ ++ value |= (1 << 29); /* Jumbo Frame Enable */ ++ writel(value, iobase + GETH_RX_CTL0); ++ ++ writel((hwdev.mdc_div << 20), iobase + GETH_MDIO_ADDR); /* MDC_DIV_RATIO */ ++ ++ /* Set the Rx&Tx mode */ ++ value = readl(iobase + GETH_TX_CTL1); ++ if (txmode == SF_DMA_MODE) { ++ /* Transmit COE type 2 cannot be done in cut-through mode. */ ++ value |= TX_MD; ++ /* Operating on second frame increase the performance ++ * especially when transmit store-and-forward is used. ++ */ ++ value |= TX_NEXT_FRM; ++ } else { ++ value &= ~TX_MD; ++ value &= ~TX_TH; ++ /* Set the transmit threshold */ ++ if (txmode <= 64) ++ value |= 0x00000000; ++ else if (txmode <= 128) ++ value |= 0x00000100; ++ else if (txmode <= 192) ++ value |= 0x00000200; ++ else ++ value |= 0x00000300; ++ } ++ writel(value, iobase + GETH_TX_CTL1); ++ ++ value = readl(iobase + GETH_RX_CTL1); ++ if (rxmode == SF_DMA_MODE) { ++ value |= RX_MD; ++ } else { ++ value &= ~RX_MD; ++ value &= ~RX_TH; ++ if (rxmode <= 32) ++ value |= 0x10; ++ else if (rxmode <= 64) ++ value |= 0x00; ++ else if (rxmode <= 96) ++ value |= 0x20; ++ else ++ value |= 0x30; ++ } ++ writel(value, iobase + GETH_RX_CTL1); ++ ++ return 0; ++} ++ ++void sunxi_hash_filter(void *iobase, unsigned long low, unsigned long high) ++{ ++ writel(high, iobase + GETH_RX_HASH0); ++ writel(low, iobase + GETH_RX_HASH1); ++} ++ ++void sunxi_set_filter(void *iobase, unsigned long flags) ++{ ++ int tmp_flags; ++ ++ tmp_flags = readl(iobase + GETH_RX_FRM_FLT); ++ ++ tmp_flags |= ((flags >> 31) | ++ ((flags >> 9) & 0x00000002) | ++ ((flags << 1) & 0x00000010) | ++ ((flags >> 3) & 0x00000060) | ++ ((flags << 7) & 0x00000300) | ++ ((flags << 6) & 0x00003000) | ++ ((flags << 12) & 0x00030000) | ++ (flags << 31)); ++ ++ writel(tmp_flags, iobase + GETH_RX_FRM_FLT); ++} ++ ++void sunxi_set_umac(void *iobase, unsigned char *addr, int index) ++{ ++ unsigned long data; ++ ++ data = (addr[5] << 8) | addr[4]; ++ writel(data, iobase + GETH_ADDR_HI(index)); ++ data = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0]; ++ writel(data, iobase + GETH_ADDR_LO(index)); ++} ++ ++void sunxi_mac_enable(void *iobase) ++{ ++ unsigned long value; ++ ++ value = readl(iobase + GETH_TX_CTL0); ++ value |= (1 << 31); ++ writel(value, iobase + GETH_TX_CTL0); ++ ++ value = readl(iobase + GETH_RX_CTL0); ++ value |= (1 << 31); ++ writel(value, iobase + GETH_RX_CTL0); ++} ++ ++void sunxi_mac_disable(void *iobase) ++{ ++ unsigned long value; ++ ++ value = readl(iobase + GETH_TX_CTL0); ++ value &= ~(1 << 31); ++ writel(value, iobase + GETH_TX_CTL0); ++ ++ value = readl(iobase + GETH_RX_CTL0); ++ value &= ~(1 << 31); ++ writel(value, iobase + GETH_RX_CTL0); ++} ++ ++void sunxi_tx_poll(void *iobase) ++{ ++ unsigned int value; ++ ++ value = readl(iobase + GETH_TX_CTL1); ++ writel(value | 0x80000000, iobase + GETH_TX_CTL1); ++} ++ ++void sunxi_rx_poll(void *iobase) ++{ ++ unsigned int value; ++ ++ value = readl(iobase + GETH_RX_CTL1); ++ writel(value | 0x80000000, iobase + GETH_RX_CTL1); ++} ++ ++void sunxi_int_enable(void *iobase) ++{ ++ writel(RX_INT | TX_INT | TX_UNF_INT, iobase + GETH_INT_EN); ++} ++ ++void sunxi_int_disable(void *iobase) ++{ ++ writel(0, iobase + GETH_INT_EN); ++} ++ ++void desc_buf_set(struct dma_desc *desc, unsigned long paddr, int size) ++{ ++ desc->desc1.all &= (~((1 << 11) - 1)); ++ desc->desc1.all |= (size & ((1 << 11) - 1)); ++ desc->desc2 = paddr; ++} ++ ++void desc_set_own(struct dma_desc *desc) ++{ ++ desc->desc0.all |= 0x80000000; ++} ++ ++void desc_tx_close(struct dma_desc *first, struct dma_desc *end, int csum_insert) ++{ ++ struct dma_desc *desc = first; ++ ++ first->desc1.tx.first_sg = 1; ++ end->desc1.tx.last_seg = 1; ++ end->desc1.tx.interrupt = 1; ++ ++ if (csum_insert) ++ do { ++ desc->desc1.tx.cic = 3; ++ desc++; ++ } while (desc <= end); ++} ++ ++void desc_init(struct dma_desc *desc) ++{ ++ desc->desc1.all = 0; ++ desc->desc2 = 0; ++ ++ desc->desc1.all |= (1 << 24); ++} ++ ++int desc_get_tx_status(struct dma_desc *desc, struct geth_extra_stats *x) ++{ ++ int ret = 0; ++ ++ if (desc->desc0.tx.under_err) { ++ x->tx_underflow++; ++ ret = -1; ++ } ++ if (desc->desc0.tx.no_carr) { ++ x->tx_carrier++; ++ ret = -1; ++ } ++ if (desc->desc0.tx.loss_carr) { ++ x->tx_losscarrier++; ++ ret = -1; ++ } ++ ++#if 0 ++ if ((desc->desc0.tx.ex_deferral) || ++ (desc->desc0.tx.ex_coll) || ++ (desc->desc0.tx.late_coll)) ++ stats->collisions += desc->desc0.tx.coll_cnt; ++#endif ++ ++ if (desc->desc0.tx.deferred) ++ x->tx_deferred++; ++ ++ return ret; ++} ++ ++int desc_buf_get_len(struct dma_desc *desc) ++{ ++ return (desc->desc1.all & ((1 << 11) - 1)); ++} ++ ++int desc_buf_get_addr(struct dma_desc *desc) ++{ ++ return desc->desc2; ++} ++ ++int desc_rx_frame_len(struct dma_desc *desc) ++{ ++ return desc->desc0.rx.frm_len; ++} ++ ++int desc_get_rx_status(struct dma_desc *desc, struct geth_extra_stats *x) ++{ ++ int ret = good_frame; ++ ++ if (desc->desc0.rx.last_desc == 0) { ++ return discard_frame; ++ } ++ ++ if (desc->desc0.rx.err_sum) { ++ if (desc->desc0.rx.desc_err) ++ x->rx_desc++; ++ ++ if (desc->desc0.rx.sou_filter) ++ x->sa_filter_fail++; ++ ++ if (desc->desc0.rx.over_err) ++ x->overflow_error++; ++ ++ if (desc->desc0.rx.ipch_err) ++ x->ipc_csum_error++; ++ ++ if (desc->desc0.rx.late_coll) ++ x->rx_collision++; ++ ++ if (desc->desc0.rx.crc_err) ++ x->rx_crc++; ++ ++ ret = discard_frame; ++ } ++ ++ if (desc->desc0.rx.len_err) { ++ ret = discard_frame; ++ } ++ if (desc->desc0.rx.mii_err) { ++ ret = discard_frame; ++ } ++ ++ return ret; ++} ++ ++int desc_get_own(struct dma_desc *desc) ++{ ++ return desc->desc0.all & 0x80000000; ++} ++ ++int desc_get_tx_ls(struct dma_desc *desc) ++{ ++ return desc->desc1.tx.last_seg; ++} ++ ++int sunxi_geth_register(void *iobase, int version, unsigned int div) ++{ ++ hwdev.ver = version; ++ hwdev.iobase = iobase; ++ hwdev.mdc_div = div; ++ ++ return 0; ++} ++ ++int sunxi_mac_reset(void *iobase, void (*delay)(int), int n) ++{ ++ unsigned int value; ++ ++ /* DMA SW reset */ ++ value = readl(iobase + GETH_BASIC_CTL1); ++ value |= SOFT_RST; ++ writel(value, iobase + GETH_BASIC_CTL1); ++ ++ delay(n); ++ ++ return !!(readl(iobase + GETH_BASIC_CTL1) & SOFT_RST); ++} ++ ++/** ++ * sunxi_parse_read_str - parse the input string for write attri. ++ * @str: string to be parsed, eg: "0x00 0x01". ++ * @addr: store the reg address. eg: 0x00. ++ * @reg: store the expect value. eg: 0x01. ++ * ++ * return 0 if success, otherwise failed. ++ */ ++int sunxi_parse_read_str(char *str, u16 *addr, u16 *reg) ++{ ++ char *ptr = str; ++ char *tstr = NULL; ++ int ret; ++ ++ /* ++ * Skip the leading whitespace, find the true split symbol. ++ * And it must be 'address value'. ++ */ ++ tstr = strim(str); ++ ptr = strchr(tstr, ' '); ++ if (!ptr) ++ return -EINVAL; ++ ++ /* ++ * Replaced split symbol with a %NUL-terminator temporary. ++ * Will be fixed at end. ++ */ ++ *ptr = '\0'; ++ ret = kstrtos16(tstr, 16, addr); ++ if (ret) ++ goto out; ++ ++ ret = kstrtos16(skip_spaces(ptr + 1), 16, reg); ++ ++out: ++ return ret; ++} ++ ++/** ++ * sunxi_parse_write_str - parse the input string for compare attri. ++ * @str: string to be parsed, eg: "0x00 0x11 0x11". ++ * @addr: store the address. eg: 0x00. ++ * @reg: store the reg. eg: 0x11. ++ * @val: store the value. eg: 0x11. ++ * ++ * return 0 if success, otherwise failed. ++ */ ++int sunxi_parse_write_str(char *str, u16 *addr, ++ u16 *reg, u16 *val) ++{ ++ u16 result_addr[3] = { 0 }; ++ char *ptr = str; ++ char *ptr2 = NULL; ++ int i, ret = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(result_addr); i++) { ++ ptr = skip_spaces(ptr); ++ ptr2 = strchr(ptr, ' '); ++ if (ptr2) ++ *ptr2 = '\0'; ++ ++ ret = kstrtou16(ptr, 16, &result_addr[i]); ++ if (!ptr2) ++ break; ++ ++ *ptr2 = ' '; ++ ++ if (ret) ++ break; ++ ++ ptr = ptr2 + 1; ++ } ++ ++ *addr = result_addr[0]; ++ *reg = result_addr[1]; ++ *val = result_addr[2]; ++ ++ return ret; ++} ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig +index 43edfd9d61dc..0283b463cad9 100644 +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -75,6 +75,14 @@ config AC200_PHY + help + Fast ethernet PHY as found in X-Powers AC200 multi-function device. + ++config AC200_PHY_SUNXI ++ tristate "AC200 EPHY(Sunxi)" ++ depends on NVMEM ++ depends on OF ++ depends on MFD_AC200_SUNXI ++ help ++ Fast ethernet PHY as found in X-Powers AC200(Sunxi) multi-function device. ++ + config AMD_PHY + tristate "AMD PHYs" + help +diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile +index e5a46da678ff..340df0674fcd 100644 +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -33,6 +33,7 @@ sfp-obj-$(CONFIG_SFP) += sfp-bus.o + obj-y += $(sfp-obj-y) $(sfp-obj-m) + + obj-$(CONFIG_AC200_PHY) += ac200-phy.o ++obj-$(CONFIG_AC200_PHY_SUNXI) += sunxi-ephy.o + obj-$(CONFIG_ADIN_PHY) += adin.o + obj-$(CONFIG_ADIN1100_PHY) += adin1100.o + obj-$(CONFIG_AMD_PHY) += amd.o +diff --git a/drivers/net/phy/sunxi-ephy.c b/drivers/net/phy/sunxi-ephy.c +new file mode 100644 +index 000000000000..92f5ba101ced +--- /dev/null ++++ b/drivers/net/phy/sunxi-ephy.c +@@ -0,0 +1,518 @@ ++/* ++ * Copyright © 2015-2016, Shuge ++ * Author: Sugar ++ * ++ * This file is provided under a dual BSD/GPL license. When using or ++ * redistributing this file, you may do so under either license. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++ ++#define EPHY_AC200 1 ++#define EPHY_AC300 2 ++ ++#define WAIT_MAX_COUNT 200 ++ ++struct ephy_res { ++ struct phy_device *ac300; ++ struct ac200_dev *ac200; ++ atomic_t ephy_en; ++}; ++ ++static struct ephy_res ac200_ephy; ++static struct ephy_res ac300_ephy; ++u8 ephy_type; ++ ++static int ac200_reg_read(struct ac200_dev *ac200, unsigned short reg) ++{ ++ unsigned int val; ++ int ret; ++ ++ ret = regmap_read(ac200->regmap, reg, &val); ++ ++ if (ret < 0) ++ return ret; ++ else ++ return val; ++} ++ ++static int ac200_reg_write(struct ac200_dev *ac200, unsigned short reg, unsigned short val) ++{ ++ return regmap_write(ac200->regmap, reg, val); ++} ++ ++int ephy_is_enable(void) ++{ ++ if (ephy_type == EPHY_AC200) ++ return atomic_read(&ac200_ephy.ephy_en); ++ else if (ephy_type == EPHY_AC300) ++ return atomic_read(&ac300_ephy.ephy_en); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ephy_is_enable); ++ ++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); /* read address 0 0x17 register */ ++ value &= ~(1 << 3); /* reg 0x17 bit 3, set 0 to 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, 0x1 << 14 | 0x7); ++ value = phy_read(phydev, 0xe); ++ value &= ~(0x1 << 1); ++ phy_write(phydev, 0xd, 0x7); ++ phy_write(phydev, 0xe, 0x3c); ++ phy_write(phydev, 0xd, 0x1 << 14 | 0x7); ++ phy_write(phydev, 0xe, value); ++ ++ phy_write(phydev, 0x1f, 0x0200); /* switch to page 2 */ ++ phy_write(phydev, 0x18, 0x0000); ++} ++ ++static void ephy_config_default(struct phy_device *phydev) ++{ ++ 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, 0x708b); /* PHYAFE TX optimization */ ++ phy_write(phydev, 0x13, 0xF000); /* PHYAFE RX optimization */ ++ phy_write(phydev, 0x15, 0x1530); ++ ++ phy_write(phydev, 0x1f, 0x0800); /* Switch to Page 8 */ ++ phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */ ++} ++ ++static void __maybe_unused ephy_config_fixed(struct phy_device *phydev) ++{ ++ 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, 0x7809); /*PHYAFE TX optimization */ ++ phy_write(phydev, 0x13, 0xf000); /*PHYAFE RX optimization */ ++ phy_write(phydev, 0x10, 0x5523); ++ phy_write(phydev, 0x15, 0x3533); ++ ++ phy_write(phydev, 0x1f, 0x0800); /*switch to Page 8 */ ++ phy_write(phydev, 0x1d, 0x0844); /*disable auto offset */ ++ phy_write(phydev, 0x18, 0x00bc); /*PHYAFE TRX optimization */ ++} ++ ++static void __maybe_unused ephy_config_cali(struct phy_device *phydev, u16 ephy_cali) ++{ ++ int value; ++ value = phy_read(phydev, 0x06); ++ value &= ~(0x0F << 12); ++ value |= (0x0F & (0x03 + ephy_cali)) << 12; ++ phy_write(phydev, 0x06, value); ++ ++ return; ++} ++ ++int ephy_config_init(struct phy_device *phydev) ++{ ++ int value; ++#if 1 //defined(CONFIG_ARCH_SUN50IW9) ++ if (ephy_type == EPHY_AC300) { ++ int ret; ++ u16 ephy_cali = 0; ++ ephy_cali = sun50i_ephy_calibrate_value(); ++ if (ret) { ++ pr_err("ephy cali efuse read fail!\n"); ++ return -1; ++ } ++ ephy_config_cali(ac300_ephy.ac300, ephy_cali); ++ /* ++ * BIT9: the flag of calibration value ++ * 0: Normal ++ * 1: Low level of calibration value ++ */ ++ if (ephy_cali & BIT(9)) { ++ pr_debug("ac300:ephy cali efuse read: fixed!\n"); ++ ephy_config_fixed(phydev); ++ } else { ++ pr_debug("ac300:ephy cali efuse read: default!\n"); ++ ephy_config_default(phydev); ++ } ++ } else { ++ pr_debug("ac200:ephy cali efuse read: default!\n"); ++ ephy_config_default(phydev); ++ } ++#else ++ ephy_config_default(phydev); ++#endif ++ ++#if 0 ++ /* Disable Auto Power Saving mode */ ++ phy_write(phydev, 0x1f, 0x0100); /* Switch to Page 1 */ ++ value = phy_read(phydev, 0x17); ++ value &= ~BIT(13); ++ phy_write(phydev, 0x17, value); ++#endif ++ 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 */ ++ ++#if 1 // def CONFIG_MFD_ACX00 ++ if (ephy_type == EPHY_AC200) { ++ value = ac200_reg_read(ac200_ephy.ac200, AC200_EPHY_CTL); ++ if (phydev->interface == PHY_INTERFACE_MODE_RMII) ++ value |= (1 << 11); ++ else ++ value &= (~(1 << 11)); ++ ac200_reg_write(ac200_ephy.ac200, AC200_EPHY_CTL, value | (1 << 11)); ++ } else ++#endif ++ if (ephy_type == EPHY_AC300) { ++ value = phy_read(ac300_ephy.ac300, 0x06); ++ if (phydev->interface == PHY_INTERFACE_MODE_RMII) ++ value |= (1 << 11); ++ else ++ value &= (~(1 << 11)); ++ phy_write(ac300_ephy.ac300, 0x06, value | (1 << 1)); /*LED_POL 1:Low active*/ ++ } ++ ++#if defined(CONFIG_ARCH_SUN50IW6) ++ value = phy_read(phydev, 0x13); ++ value |= 1 << 12; ++ phy_write(phydev, 0x13, value); ++#endif ++ ++ return 0; ++} ++EXPORT_SYMBOL(ephy_config_init); ++ ++static int ephy_probe(struct phy_device *phydev) ++{ ++ return 0; ++} ++ ++#if 0 ++static int ephy_ack_interrupt(struct phy_device *phydev) ++{ ++ int err = phy_read(phydev, IP101A_G_IRQ_CONF_STATUS); ++ ++ if (err < 0) ++ return err; ++ ++ return 0; ++} ++#endif ++ ++ ++static struct phy_driver sunxi_phy_driver = { ++ .phy_id = 0x00441400, ++ .name = "ephy", ++ .phy_id_mask = 0x0ffffff0, ++ .features = PHY_BASIC_FEATURES, //| SUPPORTED_Pause |SUPPORTED_Asym_Pause, ++#if 0 ++ .flags = PHY_HAS_INTERRUPT, ++ .ack_interrupt = ephy_ack_interrupt, ++#endif ++ .config_init = &ephy_config_init, ++ .config_aneg = &genphy_config_aneg, ++ .read_status = &genphy_read_status, ++ .suspend = genphy_suspend, ++ .resume = genphy_resume, ++ .probe = ephy_probe, ++}; ++ ++static void ac300_ephy_enable(struct ephy_res *priv) ++{ ++ phy_write(priv->ac300, 0x00, 0x1f83); /* release reset */ ++ ++ phy_write(priv->ac300, 0x00, 0x1fb7); /* clk gating (24MHz clock)*/ ++ ++ //phy_write(priv->ac300, 0x05, 0xa81f); ++ phy_write(priv->ac300, 0x05, 0xa819); ++ ++ phy_write(priv->ac300, 0x06, 0x00); ++ ++ msleep(1000); /* FIXME: fix some board compatible issues. */ ++ ++ atomic_set(&ac300_ephy.ephy_en, 1); ++} ++ ++static void ac300_ephy_disable(struct ephy_res *priv) ++{ ++ phy_write(priv->ac300, 0x00, 0x1f40); ++ phy_write(priv->ac300, 0x05, 0xa800); ++ ++ phy_write(priv->ac300, 0x06, 0x01); ++ ++ atomic_set(&ac300_ephy.ephy_en, 0); ++} ++ ++static int ac300_ephy_probe(struct phy_device *phydev) ++{ ++ ac300_ephy.ac300 = phydev; ++ ++ atomic_set(&ac300_ephy.ephy_en, 0); ++ ac300_ephy_enable(&ac300_ephy); ++ ++ ephy_type = EPHY_AC300; ++ ++ return 0; ++} ++ ++static void ac300_ephy_shutdown(struct phy_device *phydev) ++{ ++ ac300_ephy.ac300 = phydev; ++ ++ phy_write(ac300_ephy.ac300, 0x00, 0x1f40); ++ phy_write(ac300_ephy.ac300, 0x05, 0xa800); ++ phy_write(ac300_ephy.ac300, 0x06, 0x01); ++} ++ ++int gmac_ephy_shutdown(void) ++{ ++ if (ephy_type == EPHY_AC300) ++ ac300_ephy_disable(&ac300_ephy); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(gmac_ephy_shutdown); ++ ++static int ac300_ephy_suspend(struct phy_device *phydev) ++{ ++ ac300_ephy_disable(&ac300_ephy); ++ ++ return 0; ++} ++ ++static int ac300_ephy_resume(struct phy_device *phydev) ++{ ++ ac300_ephy_enable(&ac300_ephy); ++ ++ return 0; ++} ++ ++static struct phy_driver ac300_ephy_driver = { ++ .phy_id = 0xc0000000, ++ .name = "ac300", ++ .phy_id_mask = 0xffffffff, ++ .suspend = ac300_ephy_suspend, ++ .resume = ac300_ephy_resume, ++ .probe = ac300_ephy_probe, ++ // .shutdown = ac300_ephy_shutdown, ++}; ++ ++ ++static void ac200_ephy_enable(struct ephy_res *priv) ++{ ++#if 1 //ifdef CONFIG_MFD_ACX00 ++ int value; ++ unsigned char i = 0; ++#if 1 // defined(CONFIG_ARCH_SUN50IW6) || defined(CONFIG_ARCH_SUN50IW9) ++ u16 ephy_cali = 0; ++#endif ++ ++ if (!ac200_enable()) { ++ for (i = 0; i < WAIT_MAX_COUNT; i++) { ++ msleep(10); ++ if (ac200_enable()) ++ break; ++ } ++ if (i == WAIT_MAX_COUNT) { ++ pr_err("acx00 is no enable, and ac200_ephy_enable is fail\n"); ++ return; ++ } ++ } ++ ++ value = ac200_reg_read(priv->ac200, AC200_SYS_EPHY_CTL0); ++ value |= 0x03; ++ ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL0, value); ++ value = ac200_reg_read(priv->ac200, AC200_SYS_EPHY_CTL1); ++ ++ value |= 0x0f; ++ ++ ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL1, value); ++ ac200_reg_write(priv->ac200, AC200_EPHY_CTL, 0x06); ++ ++ /*for ephy */ ++ value = ac200_reg_read(priv->ac200, AC200_EPHY_CTL); ++ value &= ~(0xf << 12); ++ ++#if 1 // defined(CONFIG_ARCH_SUN50IW6) || defined(CONFIG_ARCH_SUN50IW9) ++ ++ ephy_cali = sun50i_ephy_calibrate_value(); ++ value |= (0x0F & (0x03 + ephy_cali)) << 12; ++#else ++ value |= (0x0F & (0x03 + ac200_reg_read(priv->ac200, AC200_SID))) << 12; ++#endif ++ ++ ac200_reg_write(priv->ac200, AC200_EPHY_CTL, value); ++ ++ atomic_set(&ac200_ephy.ephy_en, 1); ++#endif ++} ++ ++static void ac200_ephy_disable(struct ephy_res *priv) ++{ ++ int value; ++ ++ /* reset ephy */ ++ value = ac200_reg_read(priv->ac200, AC200_SYS_EPHY_CTL0); ++ value &= ~0x01; ++ ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL0, value); ++ ++ /* shutdown ephy */ ++ value = ac200_reg_read(priv->ac200, AC200_EPHY_CTL); ++ value |= 0x01; ++ ac200_reg_write(priv->ac200, AC200_EPHY_CTL, value); ++ ++ atomic_set(&priv->ephy_en, 0); ++} ++ ++static const struct platform_device_id ac200_ephy_id[] = { ++ { "ac200-ephy", 0}, ++ { }, ++}; ++MODULE_DEVICE_TABLE(platform, ac200_ephy_id); ++ ++static int ac200_ephy_probe(struct platform_device *pdev) ++{ ++ struct ac200_dev *ax = dev_get_drvdata(pdev->dev.parent); ++ ++ if (!ax) ++ return -ENODEV; ++ ++ ac200_ephy.ac200 = ax; ++ ephy_type = EPHY_AC200; ++ platform_set_drvdata(pdev, &ac200_ephy); ++ ++ atomic_set(&ac200_ephy.ephy_en, 0); ++ ac200_ephy_enable(&ac200_ephy); ++ ++ return 0; ++} ++ ++static int ac200_ephy_remove(struct platform_device *pdev) ++{ ++ ac200_ephy_disable(&ac200_ephy); ++ ++ return 0; ++} ++ ++static int ac200_ephy_suspend(struct device *dev) ++{ ++ ac200_ephy_disable(&ac200_ephy); ++ ++ return 0; ++} ++ ++static int ac200_ephy_resume(struct device *dev) ++{ ++ ac200_ephy_enable(&ac200_ephy); ++ ++ return 0; ++} ++ ++/* Suspend hook structures */ ++static const struct dev_pm_ops ac200_ephy_pm_ops = { ++ .suspend = ac200_ephy_suspend, ++ .resume = ac200_ephy_resume, ++}; ++ ++static struct platform_driver ac200_ephy_driver = { ++ .driver = { ++ .name = "ac200-ephy-sunxi", ++ .owner = THIS_MODULE, ++ .pm = &ac200_ephy_pm_ops, ++ }, ++ .probe = ac200_ephy_probe, ++ .remove = ac200_ephy_remove, ++ .id_table = ac200_ephy_id, ++}; ++ ++static int ephy_init(void) ++{ ++ int ret = 0; ++ ++ ret = platform_driver_register(&ac200_ephy_driver); ++ if (ret) ++ return ret; ++ ++ ret = phy_driver_register(&ac300_ephy_driver, THIS_MODULE); ++ if (ret) ++ goto ac300_ephy_error; ++ ++ ret = phy_driver_register(&sunxi_phy_driver, THIS_MODULE); ++ if (ret) ++ goto ephy_driver_error; ++ ++ return ret; ++ ++ephy_driver_error: ++ phy_driver_unregister(&ac300_ephy_driver); ++ ++ac300_ephy_error: ++ platform_driver_unregister(&ac200_ephy_driver); ++ ++ return ret; ++} ++ ++static void ephy_exit(void) ++{ ++ phy_driver_unregister(&sunxi_phy_driver); ++ phy_driver_unregister(&ac300_ephy_driver); ++ platform_driver_unregister(&ac200_ephy_driver); ++} ++ ++module_init(ephy_init); ++module_exit(ephy_exit); ++ ++static struct mdio_device_id __maybe_unused ephy_tbl[] = { ++ { 0x00441400, 0x0ffffff0 }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(mdio, ephy_tbl); ++ ++MODULE_DESCRIPTION("Allwinner EPHY drivers"); ++MODULE_AUTHOR("Sugar "); ++MODULE_LICENSE("GPL"); +diff --git a/include/linux/mfd/ac200.h b/include/linux/mfd/ac200.h +new file mode 100644 +index 000000000000..84d89cbfbd3d +--- /dev/null ++++ b/include/linux/mfd/ac200.h +@@ -0,0 +1,213 @@ ++/* 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; ++ struct regmap_irq_chip_data *regmap_irqc; ++}; ++ ++extern int ac200_enable(void); ++extern uint16_t sun50i_ephy_calibrate_value(void); ++ ++#endif /* __LINUX_MFD_AC200_H */ +diff --git a/include/linux/of_gpio.h b/include/linux/of_gpio.h +index d0f66a5e1b2a..71349cac1a67 100644 +--- a/include/linux/of_gpio.h ++++ b/include/linux/of_gpio.h +@@ -15,6 +15,21 @@ + #include /* FIXME: Shouldn't be here */ + #include + ++/* ++ * This is Linux-specific flags. By default controllers' and Linux' mapping ++ * match, but GPIO controllers are free to translate their own flags to ++ * Linux-specific in their .xlate callback. Though, 1:1 mapping is recommended. ++ */ ++enum of_gpio_flags { ++ OF_GPIO_ACTIVE_LOW = 0x1, ++ OF_GPIO_SINGLE_ENDED = 0x2, ++ OF_GPIO_OPEN_DRAIN = 0x4, ++ OF_GPIO_TRANSITORY = 0x8, ++ OF_GPIO_PULL_UP = 0x10, ++ OF_GPIO_PULL_DOWN = 0x20, ++ OF_GPIO_PULL_DISABLE = 0x40, ++}; ++ + struct device_node; + + #ifdef CONFIG_OF_GPIO +@@ -22,6 +37,9 @@ struct device_node; + extern int of_get_named_gpio(const struct device_node *np, + const char *list_name, int index); + ++extern int of_get_named_gpio_flags(const struct device_node *np, ++ const char *list_name, int index, enum of_gpio_flags *flags); ++ + #else /* CONFIG_OF_GPIO */ + + #include +-- +2.43.0.windows.1 + diff --git a/patch/kernel/archive/sunxi-6.7/patches.armbian/drv-pwm-sun50i-h616-enhance-pwm.patch b/patch/kernel/archive/sunxi-6.7/patches.armbian/drv-pwm-sun50i-h616-enhance-pwm.patch new file mode 100644 index 000000000000..ad55a6b6ea30 --- /dev/null +++ b/patch/kernel/archive/sunxi-6.7/patches.armbian/drv-pwm-sun50i-h616-enhance-pwm.patch @@ -0,0 +1,1392 @@ +From 9f41765427dd08070299557d7ddae89f6ae21483 Mon Sep 17 00:00:00 2001 +From: chraac +Date: Wed, 1 May 2024 14:24:51 +0800 +Subject: [PATCH 4/5] drivers: pwm: Add pwm-sunxi-enhance driver for h616 + +linux-orangepi commit: +c68ef342eba3673c7f1f5aa1ab819b06da1f60c6 +--- + .../allwinner/sun50i-h618-orangepi-zero2w.dts | 54 +- + drivers/pwm/Kconfig | 9 + + drivers/pwm/Makefile | 1 + + drivers/pwm/pwm-sunxi-enhance.c | 1193 +++++++++++++++++ + drivers/pwm/pwm-sunxi-enhance.h | 60 + + 5 files changed, 1316 insertions(+), 1 deletion(-) + create mode 100644 drivers/pwm/pwm-sunxi-enhance.c + create mode 100644 drivers/pwm/pwm-sunxi-enhance.h + +diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts b/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts +index e275ec942bbcd..fab468861eeff 100644 +--- a/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts ++++ b/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts +@@ -119,11 +119,39 @@ pwm: pwm@300a000 { + resets = <&ccu RST_BUS_PWM>; + pwm-number = <6>; + pwm-base = <0x0>; +- sunxi-pwms = <&pwm5>; ++ sunxi-pwms = <&pwm0>, <&pwm1>, <&pwm2>, <&pwm3>, <&pwm4>, <&pwm5>; + #pwm-cells = <3>; + status = "okay"; + }; + ++ pwm0: pwm0@0300a000 { ++ compatible = "allwinner,sunxi-pwm0"; ++ }; ++ ++ pwm1: pwm1@0300a000 { ++ compatible = "allwinner,sunxi-pwm1"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwm1_ph_pin>; ++ }; ++ ++ pwm2: pwm2@0300a000 { ++ compatible = "allwinner,sunxi-pwm2"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwm2_ph_pin>; ++ }; ++ ++ pwm3: pwm3@0300a000 { ++ compatible = "allwinner,sunxi-pwm3"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwm3_ph_pin>; ++ }; ++ ++ pwm4: pwm4@0300a000 { ++ compatible = "allwinner,sunxi-pwm4"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwm4_ph_pin>; ++ }; ++ + pwm5: pwm5@0300a000 { + compatible = "allwinner,sunxi-pwm5"; + pinctrl-names = "default"; +@@ -428,6 +456,30 @@ spi1_cs1_pin: spi1-cs1-pin { + function = "spi1"; + }; + ++ /omit-if-no-ref/ ++ pwm1_ph_pin: pwm1-ph-pin { ++ pins = "PH3"; ++ function = "pwm1"; ++ }; ++ ++ /omit-if-no-ref/ ++ pwm2_ph_pin: pwm2-ph-pin { ++ pins = "PH2"; ++ function = "pwm2"; ++ }; ++ ++ /omit-if-no-ref/ ++ pwm3_ph_pin: pwm3-ph-pin { ++ pins = "PH0"; ++ function = "pwm3"; ++ }; ++ ++ /omit-if-no-ref/ ++ pwm4_ph_pin: pwm4-ph-pin { ++ pins = "PH1"; ++ function = "pwm4"; ++ }; ++ + /omit-if-no-ref/ + pwm5_pin: pwm5-pin { + pins = "PA12"; +diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig +index 8ebcddf91f7b7..44adc2b7df0e4 100644 +--- a/drivers/pwm/Kconfig ++++ b/drivers/pwm/Kconfig +@@ -615,6 +615,15 @@ config PWM_SUN4I + To compile this driver as a module, choose M here: the module + will be called pwm-sun4i. + ++config PWM_SUNXI_ENHANCE ++ tristate "Sunxi Enhance PWM support" ++ depends on PWM && ARCH_SUNXI ++ help ++ Enhance PWM framework driver for sunxi. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called pwm-sunxi. ++ + config PWM_SUNPLUS + tristate "Sunplus PWM support" + depends on ARCH_SUNPLUS || COMPILE_TEST +diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile +index c822389c2a24c..9339710d41774 100644 +--- a/drivers/pwm/Makefile ++++ b/drivers/pwm/Makefile +@@ -57,6 +57,7 @@ obj-$(CONFIG_PWM_STM32) += pwm-stm32.o + obj-$(CONFIG_PWM_STM32_LP) += pwm-stm32-lp.o + obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o + obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o ++obj-$(CONFIG_PWM_SUNXI_ENHANCE) += pwm-sunxi-enhance.o + obj-$(CONFIG_PWM_SUNPLUS) += pwm-sunplus.o + obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o + obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o +diff --git a/drivers/pwm/pwm-sunxi-enhance.c b/drivers/pwm/pwm-sunxi-enhance.c +new file mode 100644 +index 0000000000000..68e22077ce34b +--- /dev/null ++++ b/drivers/pwm/pwm-sunxi-enhance.c +@@ -0,0 +1,1193 @@ ++/* ++ * Allwinnertech pulse-width-modulation controller driver ++ * ++ * Copyright (C) 2015 AllWinner ++ * ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pwm-sunxi-enhance.h" ++ ++#define PWM_NUM_MAX 4 ++#define PWM_BIND_NUM 2 ++#define PWM_PIN_STATE_ACTIVE "default" ++//#define PWM_PIN_STATE_SLEEP "sleep" ++ ++#define SETMASK(width, shift) ((width ? ((-1U) >> (32 - width)) : 0) << (shift)) ++#define CLRMASK(width, shift) (~(SETMASK(width, shift))) ++#define GET_BITS(shift, width, reg) \ ++ (((reg)&SETMASK(width, shift)) >> (shift)) ++#define SET_BITS(shift, width, reg, val) \ ++ (((reg)&CLRMASK(width, shift)) | (val << (shift))) ++ ++static int pwm_debug = 0; ++module_param(pwm_debug, int, 0644); ++MODULE_PARM_DESC(pwm_debug, "enable pwm debug"); ++ ++#define pwm_debug(fmt, ...) \ ++ if (pwm_debug) \ ++ printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__) ++ ++struct sunxi_pwm_config ++{ ++ unsigned int dead_time; ++ unsigned int bind_pwm; ++ ++ unsigned int clk_bypass_output; ++}; ++ ++struct sunxi_pwm_chip ++{ ++ struct pwm_chip chip; ++ void __iomem *base; ++ struct sunxi_pwm_config *config; ++ struct clk *bus_clk; ++ struct clk *clk; ++ struct reset_control *pwm_rst_clk; ++}; ++ ++static inline struct sunxi_pwm_chip *to_sunxi_pwm_chip(struct pwm_chip *chip) ++{ ++ return container_of(chip, struct sunxi_pwm_chip, chip); ++} ++ ++static inline u32 sunxi_pwm_readl(struct pwm_chip *chip, u32 offset) ++{ ++ struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); ++ u32 value = 0; ++ ++ value = readl(pc->base + offset); ++ ++ return value; ++} ++ ++static inline u32 sunxi_pwm_writel(struct pwm_chip *chip, u32 offset, u32 value) ++{ ++ struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); ++ ++ writel(value, pc->base + offset); ++ ++ return 0; ++} ++ ++static int sunxi_pwm_pin_set_state(struct device *dev, char *name) ++{ ++ struct pinctrl *pctl; ++ struct pinctrl_state *state; ++ int ret = -1; ++ ++ pctl = devm_pinctrl_get(dev); ++ if (IS_ERR(pctl)) ++ { ++ dev_err(dev, "pinctrl_get failed!\n"); ++ ret = PTR_ERR(pctl); ++ goto exit; ++ } ++ ++ state = pinctrl_lookup_state(pctl, name); ++ if (IS_ERR(state)) ++ { ++ dev_err(dev, "pinctrl_lookup_state(%s) failed!\n", name); ++ ret = PTR_ERR(state); ++ goto exit; ++ } ++ ++ ret = pinctrl_select_state(pctl, state); ++ if (ret < 0) ++ { ++ dev_err(dev, "pinctrl_select_state(%s) failed!\n", name); ++ goto exit; ++ } ++ ret = 0; ++ ++exit: ++ return ret; ++} ++ ++static int sunxi_pwm_get_config(struct platform_device *pdev, struct sunxi_pwm_config *config) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ int ret = 0; ++ ++ /* read register config */ ++ ret = of_property_read_u32(np, "bind_pwm", &config->bind_pwm); ++ if (ret < 0) ++ { ++ /*if there is no bind pwm,set 255, dual pwm invalid!*/ ++ config->bind_pwm = 255; ++ ret = 0; ++ } ++ ++ ret = of_property_read_u32(np, "dead_time", &config->dead_time); ++ if (ret < 0) ++ { ++ /*if there is bind pwm, but not set dead time,set bind pwm 255,dual pwm invalid!*/ ++ config->bind_pwm = 255; ++ ret = 0; ++ } ++ ++ ret = of_property_read_u32(np, "clk_bypass_output", &config->clk_bypass_output); ++ if (ret < 0) ++ { ++ /*if use pwm as the internal clock source!*/ ++ config->clk_bypass_output = 0; ++ ret = 0; ++ } ++ ++ of_node_put(np); ++ ++ return ret; ++} ++ ++static int sunxi_pwm_set_polarity_single(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity) ++{ ++ u32 temp; ++ unsigned int reg_offset, reg_shift, reg_width; ++ u32 sel = 0; ++ ++ sel = pwm->pwm - chip->base; ++ reg_offset = PWM_PCR_BASE + sel * 0x20; ++ reg_shift = PWM_ACT_STA_SHIFT; ++ reg_width = PWM_ACT_STA_WIDTH; ++ temp = sunxi_pwm_readl(chip, reg_offset); ++ if (polarity == PWM_POLARITY_NORMAL) ++ temp = SET_BITS(reg_shift, 1, temp, 1); ++ else ++ temp = SET_BITS(reg_shift, 1, temp, 0); ++ ++ sunxi_pwm_writel(chip, reg_offset, temp); ++ ++ return 0; ++} ++ ++static int sunxi_pwm_set_polarity_dual(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity, int bind_num) ++{ ++ u32 temp[2]; ++ unsigned int reg_offset[2], reg_shift[2], reg_width[2]; ++ u32 sel[2] = {0}; ++ ++ sel[0] = pwm->pwm - chip->base; ++ sel[1] = bind_num - chip->base; ++ /* config current pwm*/ ++ reg_offset[0] = PWM_PCR_BASE + sel[0] * 0x20; ++ reg_shift[0] = PWM_ACT_STA_SHIFT; ++ reg_width[0] = PWM_ACT_STA_WIDTH; ++ temp[0] = sunxi_pwm_readl(chip, reg_offset[0]); ++ if (polarity == PWM_POLARITY_NORMAL) ++ temp[0] = SET_BITS(reg_shift[0], 1, temp[0], 1); ++ else ++ temp[0] = SET_BITS(reg_shift[0], 1, temp[0], 0); ++ ++ /* config bind pwm*/ ++ reg_offset[1] = PWM_PCR_BASE + sel[1] * 0x20; ++ reg_shift[1] = PWM_ACT_STA_SHIFT; ++ reg_width[1] = PWM_ACT_STA_WIDTH; ++ temp[1] = sunxi_pwm_readl(chip, reg_offset[1]); ++ ++ /*bind pwm's polarity is reverse compare with the current pwm*/ ++ if (polarity == PWM_POLARITY_NORMAL) ++ temp[1] = SET_BITS(reg_shift[0], 1, temp[1], 0); ++ else ++ temp[1] = SET_BITS(reg_shift[0], 1, temp[1], 1); ++ ++ /*config register at the same time*/ ++ sunxi_pwm_writel(chip, reg_offset[0], temp[0]); ++ sunxi_pwm_writel(chip, reg_offset[1], temp[1]); ++ ++ return 0; ++} ++ ++static int sunxi_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity) ++{ ++ int bind_num; ++ struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); ++ ++ bind_num = pc->config[pwm->pwm - chip->base].bind_pwm; ++ if (bind_num == 255) ++ sunxi_pwm_set_polarity_single(chip, pwm, polarity); ++ else ++ sunxi_pwm_set_polarity_dual(chip, pwm, polarity, bind_num); ++ ++ return 0; ++} ++ ++static u32 get_pccr_reg_offset(u32 sel, u32 *reg_offset) ++{ ++ switch (sel) ++ { ++ case 0: ++ case 1: ++ *reg_offset = PWM_PCCR01; ++ break; ++ case 2: ++ case 3: ++ *reg_offset = PWM_PCCR23; ++ break; ++ case 4: ++ case 5: ++ *reg_offset = PWM_PCCR45; ++ break; ++ case 6: ++ case 7: ++ *reg_offset = PWM_PCCR67; ++ break; ++ case 8: ++ *reg_offset = PWM_PCCR8; ++ break; ++ default: ++ pr_err("%s:Not supported!\n", __func__); ++ break; ++ } ++ return 0; ++} ++ ++static u32 get_pdzcr_reg_offset(u32 sel, u32 *reg_offset) ++{ ++ switch (sel) ++ { ++ case 0: ++ case 1: ++ *reg_offset = PWM_PDZCR01; ++ break; ++ case 2: ++ case 3: ++ *reg_offset = PWM_PDZCR23; ++ break; ++ case 4: ++ case 5: ++ *reg_offset = PWM_PDZCR45; ++ break; ++ case 6: ++ case 7: ++ *reg_offset = PWM_PDZCR67; ++ break; ++ default: ++ pr_err("%s:Not supported!\n", __func__); ++ break; ++ } ++ return 0; ++} ++ ++#define PRESCALE_MAX 256 ++ ++static int sunxi_pwm_config_single(struct pwm_chip *chip, struct pwm_device *pwm, ++ int duty_ns, int period_ns) ++{ ++ unsigned int temp; ++ unsigned long long c = 0; ++ unsigned long entire_cycles = 256, active_cycles = 192; ++ struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); ++ unsigned int reg_offset, reg_shift, reg_width; ++ unsigned int reg_bypass_shift; ++ unsigned int reg_clk_src_shift, reg_clk_src_width; ++ unsigned int reg_div_m_shift, reg_div_m_width; ++ unsigned int pre_scal_id = 0, div_m = 0, prescale = 0; ++ u32 sel = 0; ++ u32 pre_scal[][2] = { ++ ++ /* reg_value clk_pre_div */ ++ {0, 1}, ++ {1, 2}, ++ {2, 4}, ++ {3, 8}, ++ {4, 16}, ++ {5, 32}, ++ {6, 64}, ++ {7, 128}, ++ {8, 256}, ++ }; ++ ++ sel = pwm->pwm - chip->base; ++ ++ get_pccr_reg_offset(sel, ®_offset); ++ if ((sel % 2) == 0) ++ reg_bypass_shift = 0x5; ++ else ++ reg_bypass_shift = 0x6; ++ /*src clk reg*/ ++ reg_clk_src_shift = PWM_CLK_SRC_SHIFT; ++ reg_clk_src_width = PWM_CLK_SRC_WIDTH; ++ ++ if (period_ns > 0 && period_ns <= 10) ++ { ++ /* if freq lt 100M, then direct output 100M clock,set by pass. */ ++ c = 100000000; ++ /*bypass reg*/ ++ temp = sunxi_pwm_readl(chip, reg_offset); ++ temp = SET_BITS(reg_bypass_shift, 1, temp, 1); ++ sunxi_pwm_writel(chip, reg_offset, temp); ++ ++ /*clk_src_reg*/ ++ temp = sunxi_pwm_readl(chip, reg_offset); ++ temp = SET_BITS(reg_clk_src_shift, reg_clk_src_width, temp, 1); ++ sunxi_pwm_writel(chip, reg_offset, temp); ++ ++ return 0; ++ } ++ else if (period_ns > 10 && period_ns <= 334) ++ { ++ /* if freq between 3M~100M, then select 100M as clock */ ++ c = 100000000; ++ /*set clk bypass_output reg to 1 when pwm is used as the internal clock source.*/ ++ if (pc->config[pwm->pwm - chip->base].clk_bypass_output == 1) { ++ temp = sunxi_pwm_readl(chip, reg_offset); ++ temp = SET_BITS(reg_bypass_shift, 1, temp, 1); ++ sunxi_pwm_writel(chip, reg_offset, temp); ++ } ++ /*clk_src_reg*/ ++ temp = sunxi_pwm_readl(chip, reg_offset); ++ temp = SET_BITS(reg_clk_src_shift, reg_clk_src_width, temp, 1); ++ sunxi_pwm_writel(chip, reg_offset, temp); ++ } ++ else if (period_ns > 334) ++ { ++ /* if freq < 3M, then select 24M clock */ ++ c = 24000000; ++ /*set clk bypass_output reg to 1 when pwm is used as the internal clock source.*/ ++ if (pc->config[pwm->pwm - chip->base].clk_bypass_output == 1) { ++ temp = sunxi_pwm_readl(chip, reg_offset); ++ temp = SET_BITS(reg_bypass_shift, 1, temp, 1); ++ sunxi_pwm_writel(chip, reg_offset, temp); ++ } ++ /*clk_src_reg*/ ++ temp = sunxi_pwm_readl(chip, reg_offset); ++ temp = SET_BITS(reg_clk_src_shift, reg_clk_src_width, temp, 0); ++ sunxi_pwm_writel(chip, reg_offset, temp); ++ } ++ pwm_debug("duty_ns=%d period_ns=%d c =%llu.\n", duty_ns, period_ns, c); ++ ++ c = c * period_ns; ++ do_div(c, 1000000000); ++ entire_cycles = (unsigned long)c; ++ ++ for (pre_scal_id = 0; pre_scal_id < 9; pre_scal_id++) ++ { ++ if (entire_cycles <= 65536) ++ break; ++ for (prescale = 0; prescale < PRESCALE_MAX + 1; prescale++) ++ { ++ entire_cycles = ((unsigned long)c / pre_scal[pre_scal_id][1]) / (prescale + 1); ++ if (entire_cycles <= 65536) ++ { ++ div_m = pre_scal[pre_scal_id][0]; ++ break; ++ } ++ } ++ } ++ ++ c = (unsigned long long)entire_cycles * duty_ns; ++ do_div(c, period_ns); ++ active_cycles = c; ++ if (entire_cycles == 0) ++ entire_cycles++; ++ ++ /* config clk div_m*/ ++ reg_div_m_shift = PWM_DIV_M_SHIFT; ++ reg_div_m_width = PWM_DIV_M_WIDTH; ++ temp = sunxi_pwm_readl(chip, reg_offset); ++ temp = SET_BITS(reg_div_m_shift, reg_div_m_width, temp, div_m); ++ sunxi_pwm_writel(chip, reg_offset, temp); ++ ++ /* config prescal */ ++ reg_offset = PWM_PCR_BASE + 0x20 * sel; ++ reg_shift = PWM_PRESCAL_SHIFT; ++ reg_width = PWM_PRESCAL_WIDTH; ++ temp = sunxi_pwm_readl(chip, reg_offset); ++ temp = SET_BITS(reg_shift, reg_width, temp, prescale); ++ sunxi_pwm_writel(chip, reg_offset, temp); ++ ++ /* config active cycles */ ++ reg_offset = PWM_PPR_BASE + 0x20 * sel; ++ reg_shift = PWM_ACT_CYCLES_SHIFT; ++ reg_width = PWM_ACT_CYCLES_WIDTH; ++ temp = sunxi_pwm_readl(chip, reg_offset); ++ temp = SET_BITS(reg_shift, reg_width, temp, active_cycles); ++ sunxi_pwm_writel(chip, reg_offset, temp); ++ ++ /* config period cycles */ ++ reg_offset = PWM_PPR_BASE + 0x20 * sel; ++ reg_shift = PWM_PERIOD_CYCLES_SHIFT; ++ reg_width = PWM_PERIOD_CYCLES_WIDTH; ++ temp = sunxi_pwm_readl(chip, reg_offset); ++ temp = SET_BITS(reg_shift, reg_width, temp, (entire_cycles - 1)); ++ ++ sunxi_pwm_writel(chip, reg_offset, temp); ++ ++ pwm_debug("active_cycles=%lu entire_cycles=%lu prescale=%u div_m=%u\n", ++ active_cycles, entire_cycles, prescale, div_m); ++ return 0; ++} ++ ++static int sunxi_pwm_config_dual(struct pwm_chip *chip, struct pwm_device *pwm, ++ int duty_ns, int period_ns, int bind_num) ++{ ++ u32 value[2] = {0}; ++ unsigned int temp; ++ unsigned long long c = 0, clk = 0, clk_temp = 0; ++ unsigned long entire_cycles = 256, active_cycles = 192; ++ unsigned int reg_offset[2], reg_shift[2], reg_width[2]; ++ unsigned int reg_bypass_shift; ++ unsigned int reg_dz_en_offset[2], reg_dz_en_shift[2], reg_dz_en_width[2]; ++ unsigned int pre_scal_id = 0, div_m = 0, prescale = 0; ++ int src_clk_sel = 0; ++ int i = 0; ++ unsigned int dead_time = 0, duty = 0; ++ u32 pre_scal[][2] = { ++ ++ /* reg_value clk_pre_div */ ++ {0, 1}, ++ {1, 2}, ++ {2, 4}, ++ {3, 8}, ++ {4, 16}, ++ {5, 32}, ++ {6, 64}, ++ {7, 128}, ++ {8, 256}, ++ }; ++ unsigned int pwm_index[2] = {0}; ++ struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); ++ ++ pwm_index[0] = pwm->pwm - chip->base; ++ pwm_index[1] = bind_num - chip->base; ++ ++ /* if duty time < dead time,it is wrong. */ ++ dead_time = pc->config[pwm_index[0]].dead_time; ++ duty = (unsigned int)duty_ns; ++ /* judge if the pwm eanble dead zone */ ++ get_pdzcr_reg_offset(pwm_index[0], ®_dz_en_offset[0]); ++ reg_dz_en_shift[0] = PWM_DZ_EN_SHIFT; ++ reg_dz_en_width[0] = PWM_DZ_EN_WIDTH; ++ ++ value[0] = sunxi_pwm_readl(chip, reg_dz_en_offset[0]); ++ value[0] = SET_BITS(reg_dz_en_shift[0], reg_dz_en_width[0], value[0], 1); ++ sunxi_pwm_writel(chip, reg_dz_en_offset[0], value[0]); ++ temp = sunxi_pwm_readl(chip, reg_dz_en_offset[0]); ++ temp &= (1u << reg_dz_en_shift[0]); ++ if (duty < dead_time || temp == 0) ++ { ++ pr_err("[PWM]duty time or dead zone error.\n"); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < PWM_BIND_NUM; i++) ++ { ++ if ((i % 2) == 0) ++ reg_bypass_shift = 0x5; ++ else ++ reg_bypass_shift = 0x6; ++ get_pccr_reg_offset(pwm_index[i], ®_offset[i]); ++ reg_shift[i] = reg_bypass_shift; ++ reg_width[i] = PWM_BYPASS_WIDTH; ++ } ++ ++ if (period_ns > 0 && period_ns <= 10) ++ { ++ /* if freq lt 100M, then direct output 100M clock,set by pass */ ++ clk = 100000000; ++ src_clk_sel = 1; ++ ++ /* config the two pwm bypass */ ++ for (i = 0; i < PWM_BIND_NUM; i++) ++ { ++ temp = sunxi_pwm_readl(chip, reg_offset[i]); ++ temp = SET_BITS(reg_shift[i], reg_width[i], temp, 1); ++ sunxi_pwm_writel(chip, reg_offset[i], temp); ++ ++ reg_shift[i] = PWM_CLK_SRC_SHIFT; ++ reg_width[i] = PWM_CLK_SRC_WIDTH; ++ temp = sunxi_pwm_readl(chip, reg_offset[i]); ++ temp = SET_BITS(reg_shift[i], reg_width[i], temp, 1); ++ sunxi_pwm_writel(chip, reg_offset[i], temp); ++ } ++ ++ return 0; ++ } ++ else if (period_ns > 10 && period_ns <= 334) ++ { ++ clk = 100000000; ++ src_clk_sel = 1; ++ } ++ else if (period_ns > 334) ++ { ++ /* if freq < 3M, then select 24M clock */ ++ clk = 24000000; ++ src_clk_sel = 0; ++ } ++ ++ for (i = 0; i < PWM_BIND_NUM; i++) ++ { ++ reg_shift[i] = PWM_CLK_SRC_SHIFT; ++ reg_width[i] = PWM_CLK_SRC_WIDTH; ++ ++ temp = sunxi_pwm_readl(chip, reg_offset[i]); ++ temp = SET_BITS(reg_shift[i], reg_width[i], temp, src_clk_sel); ++ sunxi_pwm_writel(chip, reg_offset[i], temp); ++ } ++ ++ c = clk; ++ c *= period_ns; ++ do_div(c, 1000000000); ++ entire_cycles = (unsigned long)c; ++ ++ /* get div_m and prescale,which satisfy: deat_val <= 256, entire <= 65536 */ ++ for (pre_scal_id = 0; pre_scal_id < 9; pre_scal_id++) ++ { ++ for (prescale = 0; prescale < PRESCALE_MAX + 1; prescale++) ++ { ++ entire_cycles = ((unsigned long)c / pre_scal[pre_scal_id][1]) / (prescale + 1); ++ clk_temp = clk; ++ do_div(clk_temp, pre_scal[pre_scal_id][1] * (prescale + 1)); ++ clk_temp *= dead_time; ++ do_div(clk_temp, 1000000000); ++ if (entire_cycles <= 65536 && clk_temp <= 256) ++ { ++ div_m = pre_scal[pre_scal_id][0]; ++ break; ++ } ++ } ++ if (entire_cycles <= 65536 && clk_temp <= 256) ++ break; ++ else ++ { ++ pr_err("%s:config dual err.entire_cycles=%lu, dead_zone_val=%llu", ++ __func__, entire_cycles, clk_temp); ++ return -EINVAL; ++ } ++ } ++ ++ c = (unsigned long long)entire_cycles * duty_ns; ++ do_div(c, period_ns); ++ active_cycles = c; ++ if (entire_cycles == 0) ++ entire_cycles++; ++ ++ /* config clk div_m*/ ++ for (i = 0; i < PWM_BIND_NUM; i++) ++ { ++ reg_shift[i] = PWM_DIV_M_SHIFT; ++ reg_width[i] = PWM_DIV_M_SHIFT; ++ temp = sunxi_pwm_readl(chip, reg_offset[i]); ++ temp = SET_BITS(reg_shift[i], reg_width[i], temp, div_m); ++ sunxi_pwm_writel(chip, reg_offset[i], temp); ++ } ++ ++ /* config prescal */ ++ for (i = 0; i < PWM_BIND_NUM; i++) ++ { ++ reg_offset[i] = PWM_PCR_BASE + 0x20 * pwm_index[i]; ++ reg_shift[i] = PWM_PRESCAL_SHIFT; ++ reg_width[i] = PWM_PRESCAL_WIDTH; ++ temp = sunxi_pwm_readl(chip, reg_offset[i]); ++ temp = SET_BITS(reg_shift[i], reg_width[i], temp, prescale); ++ sunxi_pwm_writel(chip, reg_offset[i], temp); ++ } ++ ++ /* config active cycles */ ++ for (i = 0; i < PWM_BIND_NUM; i++) ++ { ++ reg_offset[i] = PWM_PPR_BASE + 0x20 * pwm_index[i]; ++ reg_shift[i] = PWM_ACT_CYCLES_SHIFT; ++ reg_width[i] = PWM_ACT_CYCLES_WIDTH; ++ temp = sunxi_pwm_readl(chip, reg_offset[i]); ++ temp = SET_BITS(reg_shift[i], reg_width[i], temp, active_cycles); ++ sunxi_pwm_writel(chip, reg_offset[i], temp); ++ } ++ ++ /* config period cycles */ ++ for (i = 0; i < PWM_BIND_NUM; i++) ++ { ++ reg_offset[i] = PWM_PPR_BASE + 0x20 * pwm_index[i]; ++ reg_shift[i] = PWM_PERIOD_CYCLES_SHIFT; ++ reg_width[i] = PWM_PERIOD_CYCLES_WIDTH; ++ temp = sunxi_pwm_readl(chip, reg_offset[i]); ++ temp = SET_BITS(reg_shift[i], reg_width[i], temp, (entire_cycles - 1)); ++ sunxi_pwm_writel(chip, reg_offset[i], temp); ++ } ++ ++ pwm_debug("active_cycles=%lu entire_cycles=%lu prescale=%u div_m=%u\n", ++ active_cycles, entire_cycles, prescale, div_m); ++ ++ /* config dead zone, one config for two pwm */ ++ reg_offset[0] = reg_dz_en_offset[0]; ++ reg_shift[0] = PWM_PDZINTV_SHIFT; ++ reg_width[0] = PWM_PDZINTV_WIDTH; ++ temp = sunxi_pwm_readl(chip, reg_offset[0]); ++ temp = SET_BITS(reg_shift[0], reg_width[0], temp, (unsigned int)clk_temp); ++ sunxi_pwm_writel(chip, reg_offset[0], temp); ++ ++ return 0; ++} ++ ++static int sunxi_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, ++ int duty_ns, int period_ns) ++{ ++ int bind_num; ++ ++ struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); ++ ++ bind_num = pc->config[pwm->pwm - chip->base].bind_pwm; ++ if (bind_num == 255) { ++ sunxi_pwm_config_single(chip, pwm, duty_ns, period_ns); ++ } ++ else { ++ sunxi_pwm_config_dual(chip, pwm, duty_ns, period_ns, bind_num); ++ } ++ ++ return 0; ++} ++ ++static int sunxi_pwm_enable_single(struct pwm_chip *chip, struct pwm_device *pwm) ++{ ++ unsigned int value = 0, index = 0; ++ unsigned int reg_offset, reg_shift; ++ struct device_node *sub_np; ++ struct platform_device *pwm_pdevice; ++ int ret; ++ ++ index = pwm->pwm - chip->base; ++ sub_np = of_parse_phandle(chip->dev->of_node, "sunxi-pwms", index); ++ if (IS_ERR_OR_NULL(sub_np)) ++ { ++ pr_err("%s: can't parse \"sunxi-pwms\" property\n", __func__); ++ return -ENODEV; ++ } ++ pwm_pdevice = of_find_device_by_node(sub_np); ++ if (IS_ERR_OR_NULL(pwm_pdevice)) ++ { ++ pr_err("%s: can't parse pwm device\n", __func__); ++ return -ENODEV; ++ } ++ ret = sunxi_pwm_pin_set_state(&pwm_pdevice->dev, PWM_PIN_STATE_ACTIVE); ++ if (ret != 0) ++ return ret; ++ ++ /* enable clk for pwm controller */ ++ get_pccr_reg_offset(index, ®_offset); ++ reg_shift = PWM_CLK_GATING_SHIFT; ++ value = sunxi_pwm_readl(chip, reg_offset); ++ value = SET_BITS(reg_shift, 1, value, 1); ++ sunxi_pwm_writel(chip, reg_offset, value); ++ ++ /* enable pwm controller */ ++ reg_offset = PWM_PER; ++ reg_shift = index; ++ value = sunxi_pwm_readl(chip, reg_offset); ++ value = SET_BITS(reg_shift, 1, value, 1); ++ sunxi_pwm_writel(chip, reg_offset, value); ++ ++ return 0; ++} ++ ++static int sunxi_pwm_enable_dual(struct pwm_chip *chip, struct pwm_device *pwm, int bind_num) ++{ ++ u32 value[2] = {0}; ++ unsigned int reg_offset[2], reg_shift[2], reg_width[2]; ++ struct device_node *sub_np[2]; ++ struct platform_device *pwm_pdevice[2]; ++ int i = 0, ret = 0; ++ unsigned int pwm_index[2] = {0}; ++ ++ pwm_index[0] = pwm->pwm - chip->base; ++ pwm_index[1] = bind_num - chip->base; ++ ++ /*set current pwm pin state*/ ++ sub_np[0] = of_parse_phandle(chip->dev->of_node, "sunxi-pwms", pwm_index[0]); ++ if (IS_ERR_OR_NULL(sub_np[0])) ++ { ++ pr_err("%s: can't parse \"sunxi-pwms\" property\n", __func__); ++ return -ENODEV; ++ } ++ pwm_pdevice[0] = of_find_device_by_node(sub_np[0]); ++ if (IS_ERR_OR_NULL(pwm_pdevice[0])) ++ { ++ pr_err("%s: can't parse pwm device\n", __func__); ++ return -ENODEV; ++ } ++ ++ /*set bind pwm pin state*/ ++ sub_np[1] = of_parse_phandle(chip->dev->of_node, "sunxi-pwms", pwm_index[1]); ++ if (IS_ERR_OR_NULL(sub_np[1])) ++ { ++ pr_err("%s: can't parse \"sunxi-pwms\" property\n", __func__); ++ return -ENODEV; ++ } ++ pwm_pdevice[1] = of_find_device_by_node(sub_np[1]); ++ if (IS_ERR_OR_NULL(pwm_pdevice[1])) ++ { ++ pr_err("%s: can't parse pwm device\n", __func__); ++ return -ENODEV; ++ } ++ ++ ret = sunxi_pwm_pin_set_state(&pwm_pdevice[0]->dev, PWM_PIN_STATE_ACTIVE); ++ if (ret != 0) ++ return ret; ++ ret = sunxi_pwm_pin_set_state(&pwm_pdevice[1]->dev, PWM_PIN_STATE_ACTIVE); ++ if (ret != 0) ++ return ret; ++ ++ /* enable clk for pwm controller */ ++ for (i = 0; i < PWM_BIND_NUM; i++) ++ { ++ get_pccr_reg_offset(pwm_index[i], ®_offset[i]); ++ reg_shift[i] = PWM_CLK_GATING_SHIFT; ++ reg_width[i] = PWM_CLK_GATING_WIDTH; ++ value[i] = sunxi_pwm_readl(chip, reg_offset[i]); ++ value[i] = SET_BITS(reg_shift[i], reg_width[i], value[i], 1); ++ sunxi_pwm_writel(chip, reg_offset[i], value[i]); ++ } ++ ++ /* enable pwm controller */ ++ for (i = 0; i < PWM_BIND_NUM; i++) ++ { ++ reg_offset[i] = PWM_PER; ++ reg_shift[i] = pwm_index[i]; ++ reg_width[i] = 0x1; ++ value[i] = sunxi_pwm_readl(chip, reg_offset[i]); ++ value[i] = SET_BITS(reg_shift[i], reg_width[i], value[i], 1); ++ sunxi_pwm_writel(chip, reg_offset[i], value[i]); ++ } ++ ++ return 0; ++} ++ ++static int sunxi_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) ++{ ++ int bind_num; ++ int ret = 0; ++ struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); ++ ++ bind_num = pc->config[pwm->pwm - chip->base].bind_pwm; ++ if (bind_num == 255) ++ ret = sunxi_pwm_enable_single(chip, pwm); ++ else ++ ret = sunxi_pwm_enable_dual(chip, pwm, bind_num); ++ ++ sunxi_pwm_set_polarity(chip, pwm, PWM_POLARITY_NORMAL); ++ ++ return ret; ++} ++ ++static void sunxi_pwm_disable_single(struct pwm_chip *chip, struct pwm_device *pwm) ++{ ++ u32 value = 0, index = 0; ++ unsigned int reg_offset, reg_shift, reg_width; ++ struct device_node *sub_np; ++ struct platform_device *pwm_pdevice; ++ ++ index = pwm->pwm - chip->base; ++ sub_np = of_parse_phandle(chip->dev->of_node, "sunxi-pwms", index); ++ if (IS_ERR_OR_NULL(sub_np)) ++ { ++ pr_err("%s: can't parse \"sunxi-pwms\" property\n", __func__); ++ return; ++ } ++ pwm_pdevice = of_find_device_by_node(sub_np); ++ if (IS_ERR_OR_NULL(pwm_pdevice)) ++ { ++ pr_err("%s: can't parse pwm device\n", __func__); ++ return; ++ } ++ ++ /* disable pwm controller */ ++ reg_offset = PWM_PER; ++ reg_shift = index; ++ reg_width = 0x1; ++ value = sunxi_pwm_readl(chip, reg_offset); ++ value = SET_BITS(reg_shift, reg_width, value, 0); ++ sunxi_pwm_writel(chip, reg_offset, value); ++ ++ /* ++ * 0 , 1 --> 0 ++ * 2 , 3 --> 2 ++ * 4 , 5 --> 4 ++ * 6 , 7 --> 6 ++ */ ++ reg_shift &= ~(1); ++ ++ if (GET_BITS(reg_shift, 2, value) == 0) ++ { ++ /* disable clk for pwm controller. */ ++ get_pccr_reg_offset(index, ®_offset); ++ reg_shift = PWM_CLK_GATING_SHIFT; ++ reg_width = 0x1; ++ value = sunxi_pwm_readl(chip, reg_offset); ++ value = SET_BITS(reg_shift, reg_width, value, 0); ++ sunxi_pwm_writel(chip, reg_offset, value); ++ } ++ ++ // sunxi_pwm_pin_set_state(&pwm_pdevice->dev, PWM_PIN_STATE_SLEEP); ++} ++ ++static void sunxi_pwm_disable_dual(struct pwm_chip *chip, struct pwm_device *pwm, int bind_num) ++{ ++ u32 value[2] = {0}; ++ unsigned int reg_offset[2], reg_shift[2], reg_width[2]; ++ struct device_node *sub_np[2]; ++ struct platform_device *pwm_pdevice[2]; ++ int i = 0; ++ unsigned int pwm_index[2] = {0}; ++ ++ pwm_index[0] = pwm->pwm - chip->base; ++ pwm_index[1] = bind_num - chip->base; ++ ++ /* get current index pwm device */ ++ sub_np[0] = of_parse_phandle(chip->dev->of_node, "pwms", pwm_index[0]); ++ if (IS_ERR_OR_NULL(sub_np[0])) ++ { ++ pr_err("%s: can't parse \"pwms\" property\n", __func__); ++ return; ++ } ++ pwm_pdevice[0] = of_find_device_by_node(sub_np[0]); ++ if (IS_ERR_OR_NULL(pwm_pdevice[0])) ++ { ++ pr_err("%s: can't parse pwm device\n", __func__); ++ return; ++ } ++ /* get bind pwm device */ ++ sub_np[1] = of_parse_phandle(chip->dev->of_node, "pwms", pwm_index[1]); ++ if (IS_ERR_OR_NULL(sub_np[1])) ++ { ++ pr_err("%s: can't parse \"pwms\" property\n", __func__); ++ return; ++ } ++ pwm_pdevice[1] = of_find_device_by_node(sub_np[1]); ++ if (IS_ERR_OR_NULL(pwm_pdevice[1])) ++ { ++ pr_err("%s: can't parse pwm device\n", __func__); ++ return; ++ } ++ ++ /* disable pwm controller */ ++ for (i = 0; i < PWM_BIND_NUM; i++) ++ { ++ reg_offset[i] = PWM_PER; ++ reg_shift[i] = pwm_index[i]; ++ reg_width[i] = 0x1; ++ value[i] = sunxi_pwm_readl(chip, reg_offset[i]); ++ value[i] = SET_BITS(reg_shift[i], reg_width[i], value[i], 0); ++ sunxi_pwm_writel(chip, reg_offset[i], value[i]); ++ } ++ ++ /* disable pwm clk gating */ ++ for (i = 0; i < PWM_BIND_NUM; i++) ++ { ++ get_pccr_reg_offset(pwm_index[i], ®_offset[i]); ++ reg_shift[i] = PWM_CLK_GATING_SHIFT; ++ reg_width[i] = 0x1; ++ value[i] = sunxi_pwm_readl(chip, reg_offset[i]); ++ value[i] = SET_BITS(reg_shift[i], reg_width[i], value[i], 0); ++ sunxi_pwm_writel(chip, reg_offset[i], value[i]); ++ } ++ ++ /* disable pwm dead zone,one for the two pwm */ ++ get_pdzcr_reg_offset(pwm_index[0], ®_offset[0]); ++ reg_shift[0] = PWM_DZ_EN_SHIFT; ++ reg_width[0] = PWM_DZ_EN_WIDTH; ++ value[0] = sunxi_pwm_readl(chip, reg_offset[0]); ++ value[0] = SET_BITS(reg_shift[0], reg_width[0], value[0], 0); ++ sunxi_pwm_writel(chip, reg_offset[0], value[0]); ++ ++ /* config pin sleep */ ++ // sunxi_pwm_pin_set_state(&pwm_pdevice[0]->dev, PWM_PIN_STATE_SLEEP); ++ // sunxi_pwm_pin_set_state(&pwm_pdevice[1]->dev, PWM_PIN_STATE_SLEEP); ++} ++ ++static void sunxi_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) ++{ ++ int bind_num; ++ struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); ++ ++ bind_num = pc->config[pwm->pwm - chip->base].bind_pwm; ++ if (bind_num == 255) ++ sunxi_pwm_disable_single(chip, pwm); ++ else ++ sunxi_pwm_disable_dual(chip, pwm, bind_num); ++} ++ ++static int sunxi_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, ++ const struct pwm_state *state) ++{ ++ int err; ++ bool enabled = pwm->state.enabled; ++ ++ if (state->polarity != pwm->state.polarity) { ++ /* ++ * Changing the polarity of a running PWM is only allowed when ++ * the PWM driver implements ->apply(). ++ */ ++ if (enabled) { ++ sunxi_pwm_disable(chip, pwm); ++ ++ enabled = false; ++ } ++ ++ err = sunxi_pwm_set_polarity(chip, pwm, state->polarity); ++ if (err) ++ return err; ++ } ++ ++ ++ if (!state->enabled) { ++ if (enabled) { ++ sunxi_pwm_disable(chip, pwm); ++ } ++ ++ return 0; ++ } ++ ++ /* ++ * We cannot skip calling ->config even if state->period == ++ * pwm->state.period && state->duty_cycle == pwm->state.duty_cycle ++ * because we might have exited early in the last call to ++ * pwm_apply_state because of !state->enabled and so the two values in ++ * pwm->state might not be configured in hardware. ++ */ ++ err = sunxi_pwm_config(pwm->chip, pwm, state->duty_cycle, state->period); ++ if (err) { ++ return err; ++ } ++ ++ if (!enabled) ++ err = sunxi_pwm_enable(chip, pwm); ++ ++ return err; ++} ++ ++static const struct pwm_ops sunxi_pwm_ops = { ++ .apply = sunxi_pwm_apply, ++}; ++ ++static int sunxi_pwm_probe(struct platform_device *pdev) ++{ ++ int ret; ++ struct sunxi_pwm_chip *pwm; ++ struct device_node *np = pdev->dev.of_node; ++ int i; ++ struct platform_device *pwm_pdevice; ++ struct device_node *sub_np; ++ ++ pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); ++ if (!pwm) ++ { ++ dev_err(&pdev->dev, "failed to allocate memory!\n"); ++ return -ENOMEM; ++ } ++ ++ /* io map pwm base */ ++ pwm->base = (void __iomem *)of_iomap(pdev->dev.of_node, 0); ++ if (!pwm->base) ++ { ++ dev_err(&pdev->dev, "unable to map pwm registers\n"); ++ ret = -EINVAL; ++ goto err_iomap; ++ } ++ ++ /* read property pwm-number */ ++ ret = of_property_read_u32(np, "pwm-number", &pwm->chip.npwm); ++ if (ret < 0) ++ { ++ dev_err(&pdev->dev, "failed to get pwm number: %d, force to one!\n", ret); ++ /* force to one pwm if read property fail */ ++ pwm->chip.npwm = 1; ++ } ++ ++ /* read property pwm-base */ ++ ret = of_property_read_u32(np, "pwm-base", &pwm->chip.base); ++ if (ret < 0) ++ { ++ dev_err(&pdev->dev, "failed to get pwm-base: %d, force to -1 !\n", ret); ++ /* force to one pwm if read property fail */ ++ pwm->chip.base = -1; ++ } ++ pwm->chip.dev = &pdev->dev; ++ pwm->chip.ops = &sunxi_pwm_ops; ++ ++ /* add pwm chip to pwm-core */ ++ ret = pwmchip_add(&pwm->chip); ++ if (ret < 0) ++ { ++ dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); ++ goto err_add; ++ } ++ platform_set_drvdata(pdev, pwm); ++ ++ pwm->config = devm_kzalloc(&pdev->dev, sizeof(*pwm->config) * pwm->chip.npwm, GFP_KERNEL); ++ if (!pwm->config) ++ { ++ dev_err(&pdev->dev, "failed to allocate memory!\n"); ++ goto err_alloc; ++ } ++ ++ for (i = 0; i < pwm->chip.npwm; i++) ++ { ++ sub_np = of_parse_phandle(np, "sunxi-pwms", i); ++ if (IS_ERR_OR_NULL(sub_np)) ++ { ++ pr_err("%s: can't parse \"sunxi-pwms\" property\n", __func__); ++ return -EINVAL; ++ } ++ ++ pwm_pdevice = of_find_device_by_node(sub_np); ++ ret = sunxi_pwm_get_config(pwm_pdevice, &pwm->config[i]); ++ if (ret) ++ { ++ pr_err("Get config failed,exit!\n"); ++ goto err_get_config; ++ } ++ } ++ ++ pwm->clk = devm_clk_get_optional(&pdev->dev, "mod"); ++ if (IS_ERR(pwm->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(pwm->clk), ++ "get mod clock failed\n"); ++ ++ pwm->bus_clk = devm_clk_get_optional(&pdev->dev, "bus"); ++ if (IS_ERR(pwm->bus_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(pwm->bus_clk), ++ "get bus clock failed\n"); ++ ++ pwm->pwm_rst_clk = devm_reset_control_get_optional_shared(&pdev->dev, NULL); ++ if (IS_ERR(pwm->pwm_rst_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(pwm->pwm_rst_clk), ++ "get reset failed\n"); ++ ++ /* Deassert reset */ ++ ret = reset_control_deassert(pwm->pwm_rst_clk); ++ if (ret) ++ { ++ dev_err(&pdev->dev, "cannot deassert reset control: %pe\n", ++ ERR_PTR(ret)); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(pwm->clk); ++ if (ret) ++ { ++ dev_err(&pdev->dev, "cannot prepare and enable clk %pe\n", ++ ERR_PTR(ret)); ++ goto err_alloc; ++ } ++ ++ ret = clk_prepare_enable(pwm->bus_clk); ++ if (ret) ++ { ++ dev_err(&pdev->dev, "cannot prepare and enable bus_clk %pe\n", ++ ERR_PTR(ret)); ++ goto err_alloc; ++ } ++ ++ return 0; ++ ++err_get_config: ++err_alloc: ++ pwmchip_remove(&pwm->chip); ++err_add: ++ iounmap(pwm->base); ++err_iomap: ++ return ret; ++} ++ ++static int sunxi_pwm_remove(struct platform_device *pdev) ++{ ++ struct sunxi_pwm_chip *pwm = platform_get_drvdata(pdev); ++ clk_disable(pwm->clk); ++ clk_disable(pwm->bus_clk); ++ reset_control_assert(pwm->pwm_rst_clk); ++ pwmchip_remove(&pwm->chip); ++ ++ return 0; ++} ++ ++static int sunxi_pwm_suspend(struct platform_device *pdev, pm_message_t state) ++{ ++ return 0; ++} ++ ++static int sunxi_pwm_resume(struct platform_device *pdev) ++{ ++ return 0; ++} ++ ++#if !IS_ENABLED(CONFIG_OF) ++struct platform_device sunxi_pwm_device = { ++ .name = "sunxi_pwm", ++ .id = -1, ++}; ++#else ++static const struct of_device_id sunxi_pwm_match[] = { ++ { ++ .compatible = "allwinner,sun50i-h616-pwm", ++ }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, sunxi_pwm_match); ++#endif ++ ++static struct platform_driver sunxi_pwm_driver = { ++ .probe = sunxi_pwm_probe, ++ .remove = sunxi_pwm_remove, ++ .suspend = sunxi_pwm_suspend, ++ .resume = sunxi_pwm_resume, ++ .driver = { ++ .name = "sunxi_pwm", ++ .owner = THIS_MODULE, ++ .of_match_table = sunxi_pwm_match, ++ }, ++}; ++ ++static int __init pwm_module_init(void) ++{ ++ int ret = 0; ++ ++#if !IS_ENABLED(CONFIG_OF) ++ ret = platform_device_register(&sunxi_pwm_device); ++#endif ++ if (ret == 0) ++ { ++ ret = platform_driver_register(&sunxi_pwm_driver); ++ } ++ ++ return ret; ++} ++ ++static void __exit pwm_module_exit(void) ++{ ++ platform_driver_unregister(&sunxi_pwm_driver); ++#if !IS_ENABLED(CONFIG_OF) ++ platform_device_unregister(&sunxi_pwm_device); ++#endif ++} ++ ++subsys_initcall(pwm_module_init); ++module_exit(pwm_module_exit); ++ ++MODULE_AUTHOR("zengqi"); ++MODULE_AUTHOR("liuli"); ++MODULE_DESCRIPTION("pwm driver"); ++MODULE_LICENSE("GPL"); ++MODULE_ALIAS("platform:sunxi-pwm"); +diff --git a/drivers/pwm/pwm-sunxi-enhance.h b/drivers/pwm/pwm-sunxi-enhance.h +new file mode 100644 +index 0000000000000..e25e10bf5a3d5 +--- /dev/null ++++ b/drivers/pwm/pwm-sunxi-enhance.h +@@ -0,0 +1,60 @@ ++/* ++ * drivers/pwm/pwm-sunxi-new.h ++ * ++ * Allwinnertech pulse-width-modulation controller driver ++ * ++ * Copyright (C) 2018 AllWinner ++ * ++ * ++ * 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 __PWM_SUNXI_NEW__H__ ++#define __PWM_SUNXI_NEW__H__ ++ ++#define PWM_PIER (0x0000) ++#define PWM_PISR (0x0004) ++#define PWM_CIER (0x0010) ++#define PWM_CISR (0x0014) ++#define PWM_PCCR01 (0x0020) ++#define PWM_PCCR23 (0x0024) ++#define PWM_PCCR45 (0x0028) ++#define PWM_PCCR67 (0x002c) ++#define PWM_PDZCR01 (0x0030) ++#define PWM_PDZCR23 (0x0034) ++#define PWM_PDZCR45 (0x0038) ++#define PWM_PDZCR67 (0x003c) ++#define PWM_PER (0x0040) ++#define PWM_CER (0x0044) ++ ++#define PWM_PCR_BASE (0x0060 + 0x0000) ++#define PWM_PPR_BASE (0x0060 + 0x0004) ++#define PWM_PCNTR_BASE (0x0060 + 0x0008) ++#define PWM_CCR_BASE (0x0060 + 0x000c) ++#define PWM_CRLR_BASE (0x0060 + 0x0010) ++#define PWM_CFLR_BASE (0x0060 + 0x0014) ++#define PWM_PCCR8 (0x0300) ++ ++#define PWM_ACT_STA_SHIFT 0x8 ++#define PWM_ACT_STA_WIDTH 0x1 ++#define PWM_CLK_SRC_SHIFT 0x7 ++#define PWM_CLK_SRC_WIDTH 0x2 ++#define PWM_DIV_M_SHIFT 0x0 ++#define PWM_DIV_M_WIDTH 0x4 ++#define PWM_PRESCAL_SHIFT 0x0 ++#define PWM_PRESCAL_WIDTH 0x8 ++#define PWM_ACT_CYCLES_SHIFT 0x0 ++#define PWM_ACT_CYCLES_WIDTH 0x10 ++#define PWM_PERIOD_CYCLES_SHIFT 0x10 ++#define PWM_PERIOD_CYCLES_WIDTH 0x10 ++#define PWM_DZ_EN_SHIFT 0x0 ++#define PWM_DZ_EN_WIDTH 0x1 ++#define PWM_PDZINTV_SHIFT 0x8 ++#define PWM_PDZINTV_WIDTH 0x8 ++#define PWM_BYPASS_WIDTH 0x1 ++#define PWM_CLK_GATING_SHIFT 0x4 ++#define PWM_CLK_GATING_WIDTH 0x1 ++ ++#endif ++ +-- +GitLab diff --git a/patch/kernel/archive/sunxi-6.7/series.armbian b/patch/kernel/archive/sunxi-6.7/series.armbian index 8a228dbf7927..82541403fc49 100644 --- a/patch/kernel/archive/sunxi-6.7/series.armbian +++ b/patch/kernel/archive/sunxi-6.7/series.armbian @@ -201,3 +201,5 @@ patches.armbian/arm64-dts-allwinner-sun50i-h616-PG-12c-pins.patch patches.armbian/arm64-dts-allwinner-sun50i-h616-spi1-cs1-pin.patch patches.armbian/arm64-dts-sun50i-h618-add-overlay.patch + patches.armbian/drv-pwm-sun50i-h616-enhance-pwm.patch + patches.armbian/drv-gmac-sun50i-h616-gmac-driver.patch diff --git a/patch/kernel/archive/sunxi-6.7/series.conf b/patch/kernel/archive/sunxi-6.7/series.conf index 66437d031775..b160d0f86d6a 100644 --- a/patch/kernel/archive/sunxi-6.7/series.conf +++ b/patch/kernel/archive/sunxi-6.7/series.conf @@ -523,3 +523,5 @@ patches.armbian/arm64-dts-allwinner-sun50i-h616-PG-12c-pins.patch patches.armbian/arm64-dts-allwinner-sun50i-h616-spi1-cs1-pin.patch patches.armbian/arm64-dts-sun50i-h618-add-overlay.patch + patches.armbian/drv-pwm-sun50i-h616-enhance-pwm.patch + patches.armbian/drv-gmac-sun50i-h616-gmac-driver.patch