Use the version sent to upstream U-Boot[1], which has much better
code quality and also works better (without packet loss).
The `en8811h_read_fw` func is adapted to use current read logic.
1. https://lore.kernel.org/u-boot/20250720122852.22563-1-lucienzx159@gmail.com/
Signed-off-by: Tianling Shen <cnsztl@immortalwrt.org>
Link: https://github.com/openwrt/openwrt/pull/20000
(cherry picked from commit b63a48b012)
Link: https://github.com/openwrt/openwrt/pull/20146
Signed-off-by: Robert Marko <robimarko@gmail.com>
1905 lines
58 KiB
Diff
1905 lines
58 KiB
Diff
From 70157a6148ad47734f1dc646b4157ca83cc5df9f Mon Sep 17 00:00:00 2001
|
|
From: Weijie Gao <weijie.gao@mediatek.com>
|
|
Date: Thu, 13 Jul 2023 16:34:48 +0800
|
|
Subject: [PATCH] net: phy: add support for Airoha ethernet PHY driver
|
|
|
|
This patch adds support for Airoha ethernet PHY driver.
|
|
|
|
If GMAC2 of your board connects to Airoha EN8801S, please change the eth
|
|
node as follow:
|
|
|
|
ð {
|
|
status = "okay";
|
|
mediatek,gmac-id = <1>;
|
|
mediatek,sgmiisys = <&sgmiisys1>;
|
|
phy-mode = "sgmii";
|
|
phy-handle = <&phy5>;
|
|
|
|
phy5: eth-phy@5 {
|
|
reg = <24>;
|
|
};
|
|
};
|
|
|
|
If GMAC2 of your board connects to Airoha EN8811H, please change the eth
|
|
node as follow:
|
|
|
|
ð {
|
|
status = "okay";
|
|
mediatek,gmac-id = <1>;
|
|
mediatek,sgmiisys = <&sgmiisys1>;
|
|
phy-mode = "2500base-x";
|
|
phy-handle = <&phy5>;
|
|
|
|
fixed-link {
|
|
speed = <2500>;
|
|
full-duplex;
|
|
};
|
|
|
|
phy5: eth-phy@5 {
|
|
reg = <15>;
|
|
};
|
|
};
|
|
|
|
Signed-off-by: Weijie Gao <weijie.gao@mediatek.com>
|
|
---
|
|
.../drivers/net/phy/Kconfig | 15 +
|
|
.../drivers/net/phy/Makefile | 2 +
|
|
.../drivers/net/phy/air_en8801s.c | 633 ++
|
|
.../drivers/net/phy/air_en8801s.h | 267 +
|
|
.../drivers/net/phy/air_en8811h.c | 649 ++
|
|
.../drivers/net/phy/air_en8811h.h | 160 +
|
|
.../drivers/net/phy/air_en8811h_fw.h | 9227 +++++++++++++++++
|
|
7 files changed, 10953 insertions(+)
|
|
create mode 100644 drivers/net/phy/air_en8801s.c
|
|
create mode 100644 drivers/net/phy/air_en8801s.h
|
|
create mode 100644 drivers/net/phy/air_en8811h.c
|
|
create mode 100644 drivers/net/phy/air_en8811h.h
|
|
create mode 100644 drivers/net/phy/air_en8811h_fw.h
|
|
|
|
--- a/drivers/net/phy/Kconfig
|
|
+++ b/drivers/net/phy/Kconfig
|
|
@@ -83,6 +83,37 @@ config PHY_ADIN
|
|
help
|
|
Add support for configuring RGMII on Analog Devices ADIN PHYs.
|
|
|
|
+menuconfig PHY_AIROHA
|
|
+ bool "Airoha Ethernet PHYs support"
|
|
+
|
|
+config PHY_AIROHA_EN8801S
|
|
+ bool "Airoha Ethernet EN8801S support"
|
|
+ depends on PHY_AIROHA
|
|
+ help
|
|
+ AIROHA EN8801S supported.
|
|
+
|
|
+config PHY_AIROHA_EN8811H
|
|
+ bool "Airoha Ethernet EN8811H support"
|
|
+ depends on PHY_AIROHA
|
|
+ help
|
|
+ AIROHA EN8811H supported.
|
|
+
|
|
+choice
|
|
+ prompt "Location of the Airoha PHY firmware"
|
|
+ default PHY_AIROHA_FW_IN_UBI
|
|
+ depends on PHY_AIROHA_EN8811H
|
|
+
|
|
+config PHY_AIROHA_FW_IN_MMC
|
|
+ bool "Airoha firmware in MMC boot1 partition"
|
|
+
|
|
+config PHY_AIROHA_FW_IN_UBI
|
|
+ bool "Airoha firmware in UBI volume en8811h-fw on NAND flash"
|
|
+
|
|
+config PHY_AIROHA_FW_IN_MTD
|
|
+ bool "Airoha firmware in MTD partition on raw flash"
|
|
+
|
|
+endchoice
|
|
+
|
|
menuconfig PHY_AQUANTIA
|
|
bool "Aquantia Ethernet PHYs support"
|
|
select PHY_GIGE
|
|
--- a/drivers/net/phy/Makefile
|
|
+++ b/drivers/net/phy/Makefile
|
|
@@ -11,6 +11,8 @@ obj-$(CONFIG_MV88E6352_SWITCH) += mv88e6
|
|
obj-$(CONFIG_PHYLIB) += phy.o
|
|
obj-$(CONFIG_PHYLIB_10G) += generic_10g.o
|
|
obj-$(CONFIG_PHY_ADIN) += adin.o
|
|
+obj-$(CONFIG_PHY_AIROHA_EN8801S) += air_en8801s.o
|
|
+obj-$(CONFIG_PHY_AIROHA_EN8811H) += air_en8811h.o
|
|
obj-$(CONFIG_PHY_AQUANTIA) += aquantia.o
|
|
obj-$(CONFIG_PHY_ATHEROS) += atheros.o
|
|
obj-$(CONFIG_PHY_BROADCOM) += broadcom.o
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/air_en8801s.c
|
|
@@ -0,0 +1,633 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*************************************************
|
|
+ * FILE NAME: air_en8801s.c
|
|
+ * PURPOSE:
|
|
+ * EN8801S PHY Driver for Uboot
|
|
+ * NOTES:
|
|
+ *
|
|
+ * Copyright (C) 2023 Airoha Technology Corp.
|
|
+ *************************************************/
|
|
+
|
|
+/* INCLUDE FILE DECLARATIONS
|
|
+ */
|
|
+#include <config.h>
|
|
+#include <phy.h>
|
|
+#include <errno.h>
|
|
+#include <version.h>
|
|
+#include "air_en8801s.h"
|
|
+
|
|
+#if AIR_UBOOT_REVISION > 0x202004
|
|
+#include <linux/delay.h>
|
|
+#endif
|
|
+
|
|
+static struct phy_device *s_phydev = 0;
|
|
+/******************************************************
|
|
+ * The following led_cfg example is for reference only.
|
|
+ * LED5 1000M/LINK/ACT (GPIO5) <-> BASE_T_LED0,
|
|
+ * LED6 10/100M/LINK/ACT (GPIO9) <-> BASE_T_LED1,
|
|
+ * LED4 100M/LINK/ACT (GPIO8) <-> BASE_T_LED2,
|
|
+ ******************************************************/
|
|
+/* User-defined.B */
|
|
+#define AIR_LED_SUPPORT
|
|
+#ifdef AIR_LED_SUPPORT
|
|
+static const AIR_BASE_T_LED_CFG_T led_cfg[4] =
|
|
+{
|
|
+ /*
|
|
+ * LED Enable, GPIO, LED Polarity, LED ON, LED Blink
|
|
+ */
|
|
+ {LED_ENABLE, 5, AIR_ACTIVE_LOW, BASE_T_LED0_ON_CFG, BASE_T_LED0_BLK_CFG}, /* BASE-T LED0 */
|
|
+ {LED_ENABLE, 9, AIR_ACTIVE_LOW, BASE_T_LED1_ON_CFG, BASE_T_LED1_BLK_CFG}, /* BASE-T LED1 */
|
|
+ {LED_ENABLE, 8, AIR_ACTIVE_LOW, BASE_T_LED2_ON_CFG, BASE_T_LED2_BLK_CFG}, /* BASE-T LED2 */
|
|
+ {LED_DISABLE, 1, AIR_ACTIVE_LOW, BASE_T_LED3_ON_CFG, BASE_T_LED3_BLK_CFG} /* BASE-T LED3 */
|
|
+};
|
|
+static const u16 led_dur = UNIT_LED_BLINK_DURATION << AIR_LED_BLK_DUR_64M;
|
|
+#endif
|
|
+/* User-defined.E */
|
|
+/************************************************************************
|
|
+ * F U N C T I O N S
|
|
+ ************************************************************************/
|
|
+/* Airoha MII read function */
|
|
+static int airoha_cl22_read(struct mii_dev *bus, int phy_addr, int phy_register)
|
|
+{
|
|
+ int read_data = bus->read(bus, phy_addr, MDIO_DEVAD_NONE, phy_register);
|
|
+
|
|
+ if (read_data < 0)
|
|
+ return -EIO;
|
|
+ return read_data;
|
|
+}
|
|
+
|
|
+/* Airoha MII write function */
|
|
+static int airoha_cl22_write(struct mii_dev *bus, int phy_addr, int phy_register, int write_data)
|
|
+{
|
|
+ int ret = bus->write(bus, phy_addr, MDIO_DEVAD_NONE, phy_register, write_data);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int airoha_cl45_write(struct phy_device *phydev, int devad, int reg, int val)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, devad);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, reg);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, val);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int airoha_cl45_read(struct phy_device *phydev, int devad, int reg)
|
|
+{
|
|
+ int read_data, ret;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, devad);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, reg);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ read_data = phy_read(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG);
|
|
+ if (read_data < 0)
|
|
+ return -EIO;
|
|
+ return read_data;
|
|
+}
|
|
+
|
|
+/* EN8801 PBUS write function */
|
|
+int airoha_pbus_write(struct mii_dev *bus, int pbus_addr, int pbus_reg, unsigned long pbus_data)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = airoha_cl22_write(bus, pbus_addr, 0x1F, (pbus_reg >> 6));
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl22_write(bus, pbus_addr, ((pbus_reg >> 2) & 0xf), (pbus_data & 0xFFFF));
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl22_write(bus, pbus_addr, 0x10, (pbus_data >> 16));
|
|
+ AIR_RTN_ERR(ret);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* EN8801 PBUS read function */
|
|
+unsigned long airoha_pbus_read(struct mii_dev *bus, int pbus_addr, int pbus_reg)
|
|
+{
|
|
+ unsigned long pbus_data;
|
|
+ unsigned int pbus_data_low, pbus_data_high;
|
|
+
|
|
+ airoha_cl22_write(bus, pbus_addr, 0x1F, (pbus_reg >> 6));
|
|
+ pbus_data_low = airoha_cl22_read(bus, pbus_addr, ((pbus_reg >> 2) & 0xf));
|
|
+ pbus_data_high = airoha_cl22_read(bus, pbus_addr, 0x10);
|
|
+ pbus_data = (pbus_data_high << 16) + pbus_data_low;
|
|
+ return pbus_data;
|
|
+}
|
|
+
|
|
+/* Airoha Token Ring Write function */
|
|
+static int airoha_tr_reg_write(struct phy_device *phydev, unsigned long tr_address, unsigned long tr_data)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, 0x52b5); /* page select */
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x11, (int)(tr_data & 0xffff));
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x12, (int)(tr_data >> 16));
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x10, (int)(tr_address | TrReg_WR));
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, 0x0); /* page resetore */
|
|
+ AIR_RTN_ERR(ret);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+int airoha_phy_process(void)
|
|
+{
|
|
+ int ret = 0, pbus_addr = EN8801S_PBUS_PHY_ID;
|
|
+ unsigned long pbus_data;
|
|
+ struct mii_dev *mbus;
|
|
+
|
|
+ mbus = s_phydev->bus;
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x19e0);
|
|
+ pbus_data |= BIT(0);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x19e0, pbus_data);
|
|
+ if(ret)
|
|
+ printf("error: airoha_pbus_write fail ret: %d\n", ret);
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x19e0);
|
|
+ pbus_data &= ~BIT(0);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x19e0, pbus_data);
|
|
+ if(ret)
|
|
+ printf("error: airoha_pbus_write fail ret: %d\n", ret);
|
|
+
|
|
+ if(ret)
|
|
+ printf("error: FCM regs reset fail, ret: %d\n", ret);
|
|
+ else
|
|
+ debug("FCM regs reset successful\n");
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+#ifdef AIR_LED_SUPPORT
|
|
+static int airoha_led_set_usr_def(struct phy_device *phydev, u8 entity, int polar,
|
|
+ u16 on_evt, u16 blk_evt)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ if (AIR_ACTIVE_HIGH == polar) {
|
|
+ on_evt |= LED_ON_POL;
|
|
+ } else {
|
|
+ on_evt &= ~LED_ON_POL;
|
|
+ }
|
|
+ ret = airoha_cl45_write(phydev, 0x1f, LED_ON_CTRL(entity), on_evt | LED_ON_EN);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1f, LED_BLK_CTRL(entity), blk_evt);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int airoha_led_set_mode(struct phy_device *phydev, u8 mode)
|
|
+{
|
|
+ u16 cl45_data;
|
|
+ int err = 0;
|
|
+
|
|
+ cl45_data = airoha_cl45_read(phydev, 0x1f, LED_BCR);
|
|
+ switch (mode) {
|
|
+ case AIR_LED_MODE_DISABLE:
|
|
+ cl45_data &= ~LED_BCR_EXT_CTRL;
|
|
+ cl45_data &= ~LED_BCR_MODE_MASK;
|
|
+ cl45_data |= LED_BCR_MODE_DISABLE;
|
|
+ break;
|
|
+ case AIR_LED_MODE_USER_DEFINE:
|
|
+ cl45_data |= LED_BCR_EXT_CTRL;
|
|
+ cl45_data |= LED_BCR_CLK_EN;
|
|
+ break;
|
|
+ default:
|
|
+ printf("LED mode%d is not supported!\n", mode);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ err = airoha_cl45_write(phydev, 0x1f, LED_BCR, cl45_data);
|
|
+ AIR_RTN_ERR(err);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int airoha_led_set_state(struct phy_device *phydev, u8 entity, u8 state)
|
|
+{
|
|
+ u16 cl45_data;
|
|
+ int err;
|
|
+
|
|
+ cl45_data = airoha_cl45_read(phydev, 0x1f, LED_ON_CTRL(entity));
|
|
+ if (LED_ENABLE == state) {
|
|
+ cl45_data |= LED_ON_EN;
|
|
+ } else {
|
|
+ cl45_data &= ~LED_ON_EN;
|
|
+ }
|
|
+
|
|
+ err = airoha_cl45_write(phydev, 0x1f, LED_ON_CTRL(entity), cl45_data);
|
|
+ AIR_RTN_ERR(err);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int en8801s_led_init(struct phy_device *phydev)
|
|
+{
|
|
+
|
|
+ unsigned long led_gpio = 0, reg_value = 0;
|
|
+ int ret = 0, led_id;
|
|
+ struct mii_dev *mbus = phydev->bus;
|
|
+ int gpio_led_rg[3] = {0x1870, 0x1874, 0x1878};
|
|
+ u16 cl45_data = led_dur;
|
|
+
|
|
+ ret = airoha_cl45_write(phydev, 0x1f, LED_BLK_DUR, cl45_data);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ cl45_data >>= 1;
|
|
+ ret = airoha_cl45_write(phydev, 0x1f, LED_ON_DUR, cl45_data);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_led_set_mode(phydev, AIR_LED_MODE_USER_DEFINE);
|
|
+ if (ret != 0) {
|
|
+ printf("LED fail to set mode, ret %d !\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ for(led_id = 0; led_id < EN8801S_LED_COUNT; led_id++) {
|
|
+ reg_value = 0;
|
|
+ ret = airoha_led_set_state(phydev, led_id, led_cfg[led_id].en);
|
|
+ if (ret != 0) {
|
|
+ printf("LED fail to set state, ret %d !\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ if (LED_ENABLE == led_cfg[led_id].en) {
|
|
+ if ( (led_cfg[led_id].gpio < 0) || led_cfg[led_id].gpio > 9) {
|
|
+ printf("GPIO%d is out of range!! GPIO number is 0~9.\n", led_cfg[led_id].gpio);
|
|
+ return -EIO;
|
|
+ }
|
|
+ led_gpio |= BIT(led_cfg[led_id].gpio);
|
|
+ reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, gpio_led_rg[led_cfg[led_id].gpio / 4]);
|
|
+ LED_SET_GPIO_SEL(led_cfg[led_id].gpio, led_id, reg_value);
|
|
+ debug("[Airoha] gpio%d, reg_value 0x%lx\n", led_cfg[led_id].gpio, reg_value);
|
|
+ ret = airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, gpio_led_rg[led_cfg[led_id].gpio / 4], reg_value);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_led_set_usr_def(phydev, led_id, led_cfg[led_id].pol, led_cfg[led_id].on_cfg, led_cfg[led_id].blk_cfg);
|
|
+ if (ret != 0) {
|
|
+ printf("LED fail to set usr def, ret %d !\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ reg_value = (airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1880) & ~led_gpio);
|
|
+ ret = airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1880, reg_value);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x186c, led_gpio);
|
|
+ AIR_RTN_ERR(ret);
|
|
+
|
|
+ printf("LED initialize OK !\n");
|
|
+ return 0;
|
|
+}
|
|
+#endif /* AIR_LED_SUPPORT */
|
|
+
|
|
+static int en8801s_config(struct phy_device *phydev)
|
|
+{
|
|
+ int reg_value = 0, ret = 0;
|
|
+ struct mii_dev *mbus = phydev->bus;
|
|
+ int retry, pbus_addr = EN8801S_PBUS_DEFAULT_ID;
|
|
+ int phy_addr = EN8801S_MDIO_PHY_ID;
|
|
+ unsigned long pbus_data = 0;
|
|
+ gephy_all_REG_LpiReg1Ch GPHY_RG_LPI_1C;
|
|
+ gephy_all_REG_dev1Eh_reg324h GPHY_RG_1E_324;
|
|
+ gephy_all_REG_dev1Eh_reg012h GPHY_RG_1E_012;
|
|
+ gephy_all_REG_dev1Eh_reg017h GPHY_RG_1E_017;
|
|
+
|
|
+ s_phydev = phydev;
|
|
+ retry = MAX_OUI_CHECK;
|
|
+ while (1) {
|
|
+ /* PHY OUI */
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, EN8801S_RG_ETHER_PHY_OUI);
|
|
+ if (EN8801S_PBUS_OUI == pbus_data) {
|
|
+ printf("PBUS addr 0x%x: Start initialized.\n", pbus_addr);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, EN8801S_RG_BUCK_CTL, 0x03);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ break;
|
|
+ } else
|
|
+ pbus_addr = EN8801S_PBUS_PHY_ID;
|
|
+
|
|
+ if (0 == --retry) {
|
|
+ printf("EN8801S Probe fail !\n");
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* SMI ADDR */
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, EN8801S_RG_SMI_ADDR);
|
|
+ pbus_data = (pbus_data & 0xffff0000) | (unsigned long)(pbus_addr << 8) | (unsigned long)(EN8801S_MDIO_DEFAULT_ID);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, EN8801S_RG_SMI_ADDR, pbus_data);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ mdelay(10);
|
|
+
|
|
+ pbus_data = (airoha_pbus_read(mbus, pbus_addr, EN8801S_RG_LTR_CTL) & (~0x3)) | BIT(2) ;
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, EN8801S_RG_LTR_CTL, pbus_data);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ mdelay(500);
|
|
+ pbus_data = (pbus_data & ~BIT(2)) | EN8801S_RX_POLARITY_NORMAL | EN8801S_TX_POLARITY_NORMAL;
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, EN8801S_RG_LTR_CTL, pbus_data);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ mdelay(500);
|
|
+ /* SMI ADDR */
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, EN8801S_RG_SMI_ADDR);
|
|
+ pbus_data = (pbus_data & 0xffff0000) | (unsigned long)(EN8801S_PBUS_PHY_ID << 8) | (unsigned long)(EN8801S_MDIO_PHY_ID);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, EN8801S_RG_SMI_ADDR, pbus_data);
|
|
+ pbus_addr = EN8801S_PBUS_PHY_ID;
|
|
+ AIR_RTN_ERR(ret);
|
|
+ mdelay(10);
|
|
+
|
|
+ /* Optimze 10M IoT */
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1690);
|
|
+ pbus_data |= (1 << 31);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x1690, pbus_data);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ /* set SGMII Base Page */
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c000c00);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x10, 0xD801);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0, 0x9140);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0A14, 0x0003);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c000c00);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ /* Set FCM control */
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x1404, 0x004b);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x140c, 0x0007);
|
|
+ AIR_RTN_ERR(ret);
|
|
+
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x142c, 0x05050505);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1440);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x1440, pbus_data & ~BIT(11));
|
|
+ AIR_RTN_ERR(ret);
|
|
+
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1408);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x1408, pbus_data | BIT(5));
|
|
+ AIR_RTN_ERR(ret);
|
|
+
|
|
+ /* Set GPHY Perfomance*/
|
|
+ /* Token Ring */
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_R1000DEC_15h, 0x0055A0);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_R1000DEC_17h, 0x07FF3F);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_PMA_00h, 0x00001E);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_PMA_01h, 0x6FB90A);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_PMA_17h, 0x060671);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_PMA_18h, 0x0E2F00);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_TR_26h, 0x444444);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_03h, 0x000000);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_06h, 0x2EBAEF);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_08h, 0x00000B);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_0Ch, 0x00504D);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_0Dh, 0x02314F);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_0Fh, 0x003028);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_10h, 0x005010);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_11h, 0x040001);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_13h, 0x018670);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_14h, 0x00024A);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_1Bh, 0x000072);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_1Ch, 0x003210);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ /* CL22 & CL45 */
|
|
+ ret = airoha_cl22_write(mbus, phy_addr, 0x1f, 0x03);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ GPHY_RG_LPI_1C.DATA = airoha_cl22_read(mbus, phy_addr, RgAddr_LPI_1Ch);
|
|
+ if (GPHY_RG_LPI_1C.DATA < 0)
|
|
+ return -EIO;
|
|
+ GPHY_RG_LPI_1C.DataBitField.smi_deton_th = 0x0C;
|
|
+ ret = airoha_cl22_write(mbus, phy_addr, RgAddr_LPI_1Ch, GPHY_RG_LPI_1C.DATA);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl22_write(mbus, phy_addr, RgAddr_LPI_1Ch, 0xC92);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl22_write(mbus, phy_addr, RgAddr_AUXILIARY_1Dh, 0x1);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl22_write(mbus, phy_addr, 0x1f, 0x0);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x120, 0x8014);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x122, 0xffff);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x123, 0xffff);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x144, 0x0200);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x14A, 0xEE20);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x189, 0x0110);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x19B, 0x0111);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x234, 0x0181);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x238, 0x0120);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x239, 0x0117);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x268, 0x07F4);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x2D1, 0x0733);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x323, 0x0011);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x324, 0x013F);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x326, 0x0037);
|
|
+ AIR_RTN_ERR(ret);
|
|
+
|
|
+ reg_value = airoha_cl45_read(phydev, 0x1E, 0x324);
|
|
+ if (reg_value < 0)
|
|
+ return -EIO;
|
|
+ GPHY_RG_1E_324.DATA = (int)reg_value;
|
|
+ GPHY_RG_1E_324.DataBitField.smi_det_deglitch_off = 0;
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x324, GPHY_RG_1E_324.DATA);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x19E, 0xC2);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x013, 0x0);
|
|
+ AIR_RTN_ERR(ret);
|
|
+
|
|
+ /* EFUSE */
|
|
+ airoha_pbus_write(mbus, pbus_addr, 0x1C08, 0x40000040);
|
|
+ retry = MAX_RETRY;
|
|
+ while (0 != retry) {
|
|
+ mdelay(1);
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1C08);
|
|
+ if ((pbus_data & (1 << 30)) == 0) {
|
|
+ break;
|
|
+ }
|
|
+ retry--;
|
|
+ }
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1C38); /* RAW#2 */
|
|
+ reg_value = airoha_cl45_read(phydev, 0x1E, 0x12);
|
|
+ if (reg_value < 0)
|
|
+ return -EIO;
|
|
+ GPHY_RG_1E_012.DATA = reg_value;
|
|
+ GPHY_RG_1E_012.DataBitField.da_tx_i2mpb_a_tbt = pbus_data & 0x03f;
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x12, GPHY_RG_1E_012.DATA);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ reg_value = airoha_cl45_read(phydev, 0x1E, 0x17);
|
|
+ if (reg_value < 0)
|
|
+ return -EIO;
|
|
+ GPHY_RG_1E_017.DataBitField.da_tx_i2mpb_b_tbt = (reg_value >> 8) & 0x03f;
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x17, GPHY_RG_1E_017.DATA);
|
|
+ AIR_RTN_ERR(ret);
|
|
+
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x1C08, 0x40400040);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ retry = MAX_RETRY;
|
|
+ while (0 != retry) {
|
|
+ mdelay(1);
|
|
+ reg_value = airoha_pbus_read(mbus, pbus_addr, 0x1C08);
|
|
+ if ((reg_value & (1 << 30)) == 0) {
|
|
+ break;
|
|
+ }
|
|
+ retry--;
|
|
+ }
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1C30); /* RAW#16 */
|
|
+ GPHY_RG_1E_324.DataBitField.smi_det_deglitch_off = (pbus_data >> 12) & 0x01;
|
|
+ ret = airoha_cl45_write(phydev, 0x1E, 0x324, GPHY_RG_1E_324.DATA);
|
|
+ AIR_RTN_ERR(ret);
|
|
+#ifdef AIR_LED_SUPPORT
|
|
+ ret = en8801s_led_init(phydev);
|
|
+ if (ret != 0){
|
|
+ printf("en8801s_led_init fail (ret:%d) !\n", ret);
|
|
+ }
|
|
+#endif
|
|
+ printf("EN8801S initialize OK ! (%s)\n", EN8801S_DRIVER_VERSION);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int en8801s_read_status(struct phy_device *phydev)
|
|
+{
|
|
+ int ret, pbus_addr = EN8801S_PBUS_PHY_ID;
|
|
+ struct mii_dev *mbus;
|
|
+ unsigned long pbus_data;
|
|
+
|
|
+ mbus = phydev->bus;
|
|
+ if (SPEED_10 == phydev->speed) {
|
|
+ /* set the bit for Optimze 10M IoT */
|
|
+ debug("[Airoha] SPEED_10 0x1694\n");
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1694);
|
|
+ pbus_data |= (1 << 31);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x1694, pbus_data);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ } else {
|
|
+ debug("[Airoha] SPEED_1000/100 0x1694\n");
|
|
+ /* clear the bit for other speeds */
|
|
+ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1694);
|
|
+ pbus_data &= ~(1 << 31);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x1694, pbus_data);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ }
|
|
+
|
|
+ airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c000c00);
|
|
+ if(SPEED_1000 == phydev->speed) {
|
|
+ debug("[Airoha] SPEED_1000\n");
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x10, 0xD801);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0, 0x9140);
|
|
+ AIR_RTN_ERR(ret);
|
|
+
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0A14, 0x0003);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c000c00);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ mdelay(2); /* delay 2 ms */
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x1404, 0x004b);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x140c, 0x0007);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ }
|
|
+ else if (SPEED_100 == phydev->speed) {
|
|
+ debug("[Airoha] SPEED_100\n");
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x10, 0xD401);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0, 0x9140);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0A14, 0x0007);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c11);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ mdelay(2); /* delay 2 ms */
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x1404, 0x0027);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x140c, 0x0007);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ }
|
|
+ else {
|
|
+ debug("[Airoha] SPEED_10\n");
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x10, 0xD001);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0, 0x9140);
|
|
+ AIR_RTN_ERR(ret);
|
|
+
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0A14, 0x000b);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c11);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ mdelay(2); /* delay 2 ms */
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x1404, 0x0047);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ ret = airoha_pbus_write(mbus, pbus_addr, 0x140c, 0x0007);
|
|
+ AIR_RTN_ERR(ret);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int en8801s_startup(struct phy_device *phydev)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = genphy_update_link(phydev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = genphy_parse_link(phydev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return en8801s_read_status(phydev);
|
|
+}
|
|
+#if AIR_UBOOT_REVISION > 0x202303
|
|
+U_BOOT_PHY_DRIVER(en8801s) = {
|
|
+ .name = "Airoha EN8801S",
|
|
+ .uid = EN8801S_PHY_ID,
|
|
+ .mask = 0x0ffffff0,
|
|
+ .features = PHY_GBIT_FEATURES,
|
|
+ .config = &en8801s_config,
|
|
+ .startup = &en8801s_startup,
|
|
+ .shutdown = &genphy_shutdown,
|
|
+};
|
|
+#else
|
|
+static struct phy_driver AIR_EN8801S_driver = {
|
|
+ .name = "Airoha EN8801S",
|
|
+ .uid = EN8801S_PHY_ID,
|
|
+ .mask = 0x0ffffff0,
|
|
+ .features = PHY_GBIT_FEATURES,
|
|
+ .config = &en8801s_config,
|
|
+ .startup = &en8801s_startup,
|
|
+ .shutdown = &genphy_shutdown,
|
|
+};
|
|
+
|
|
+int phy_air_en8801s_init(void)
|
|
+{
|
|
+ phy_register(&AIR_EN8801S_driver);
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/air_en8801s.h
|
|
@@ -0,0 +1,267 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*************************************************
|
|
+ * FILE NAME: air_en8801s.h
|
|
+ * PURPOSE:
|
|
+ * EN8801S PHY Driver for Uboot
|
|
+ * NOTES:
|
|
+ *
|
|
+ * Copyright (C) 2023 Airoha Technology Corp.
|
|
+ *************************************************/
|
|
+
|
|
+#ifndef __EN8801S_H
|
|
+#define __EN8801S_H
|
|
+
|
|
+/************************************************************************
|
|
+* D E F I N E S
|
|
+************************************************************************/
|
|
+#define AIR_UBOOT_REVISION ((((U_BOOT_VERSION_NUM / 1000) % 10) << 20) | \
|
|
+ (((U_BOOT_VERSION_NUM / 100) % 10) << 16) | \
|
|
+ (((U_BOOT_VERSION_NUM / 10) % 10) << 12) | \
|
|
+ ((U_BOOT_VERSION_NUM % 10) << 8) | \
|
|
+ (((U_BOOT_VERSION_NUM_PATCH / 10) % 10) << 4) | \
|
|
+ ((U_BOOT_VERSION_NUM_PATCH % 10) << 0))
|
|
+
|
|
+#define EN8801S_MDIO_DEFAULT_ID 0x1d
|
|
+#define EN8801S_PBUS_DEFAULT_ID (EN8801S_MDIO_DEFAULT_ID + 1)
|
|
+#define EN8801S_MDIO_PHY_ID 0x18 /* Range PHY_ADDRESS_RANGE .. 0x1e */
|
|
+#define EN8801S_PBUS_PHY_ID (EN8801S_MDIO_PHY_ID + 1)
|
|
+#define EN8801S_DRIVER_VERSION "v1.1.3"
|
|
+
|
|
+#define EN8801S_RG_ETHER_PHY_OUI 0x19a4
|
|
+#define EN8801S_RG_SMI_ADDR 0x19a8
|
|
+#define EN8801S_PBUS_OUI 0x17a5
|
|
+#define EN8801S_RG_BUCK_CTL 0x1a20
|
|
+#define EN8801S_RG_LTR_CTL 0x0cf8
|
|
+
|
|
+#define EN8801S_PHY_ID1 0x03a2
|
|
+#define EN8801S_PHY_ID2 0x9461
|
|
+#define EN8801S_PHY_ID (unsigned long)((EN8801S_PHY_ID1 << 16) | EN8801S_PHY_ID2)
|
|
+
|
|
+/*
|
|
+SFP Sample for verification
|
|
+Tx Reverse, Rx Reverse
|
|
+*/
|
|
+#define EN8801S_TX_POLARITY_NORMAL 0x0
|
|
+#define EN8801S_TX_POLARITY_REVERSE 0x1
|
|
+
|
|
+#define EN8801S_RX_POLARITY_NORMAL (0x1 << 1)
|
|
+#define EN8801S_RX_POLARITY_REVERSE (0x0 << 1)
|
|
+
|
|
+#ifndef BIT
|
|
+#define BIT(nr) (1UL << (nr))
|
|
+#endif
|
|
+
|
|
+#define MAX_RETRY 5
|
|
+#define MAX_OUI_CHECK 2
|
|
+
|
|
+/* CL45 MDIO control */
|
|
+#define MII_MMD_ACC_CTL_REG 0x0d
|
|
+#define MII_MMD_ADDR_DATA_REG 0x0e
|
|
+#define MMD_OP_MODE_DATA BIT(14)
|
|
+
|
|
+#define MAX_TRG_COUNTER 5
|
|
+
|
|
+/* TokenRing Reg Access */
|
|
+#define TrReg_PKT_XMT_STA 0x8000
|
|
+#define TrReg_WR 0x8000
|
|
+#define TrReg_RD 0xA000
|
|
+
|
|
+#define RgAddr_LPI_1Ch 0x1c
|
|
+#define RgAddr_AUXILIARY_1Dh 0x1d
|
|
+#define RgAddr_PMA_00h 0x0f80
|
|
+#define RgAddr_PMA_01h 0x0f82
|
|
+#define RgAddr_PMA_17h 0x0fae
|
|
+#define RgAddr_PMA_18h 0x0fb0
|
|
+#define RgAddr_DSPF_03h 0x1686
|
|
+#define RgAddr_DSPF_06h 0x168c
|
|
+#define RgAddr_DSPF_08h 0x1690
|
|
+#define RgAddr_DSPF_0Ch 0x1698
|
|
+#define RgAddr_DSPF_0Dh 0x169a
|
|
+#define RgAddr_DSPF_0Fh 0x169e
|
|
+#define RgAddr_DSPF_10h 0x16a0
|
|
+#define RgAddr_DSPF_11h 0x16a2
|
|
+#define RgAddr_DSPF_13h 0x16a6
|
|
+#define RgAddr_DSPF_14h 0x16a8
|
|
+#define RgAddr_DSPF_1Bh 0x16b6
|
|
+#define RgAddr_DSPF_1Ch 0x16b8
|
|
+#define RgAddr_TR_26h 0x0ecc
|
|
+#define RgAddr_R1000DEC_15h 0x03aa
|
|
+#define RgAddr_R1000DEC_17h 0x03ae
|
|
+
|
|
+/*
|
|
+The following led_cfg example is for reference only.
|
|
+LED5 1000M/LINK/ACT (GPIO5) <-> BASE_T_LED0,
|
|
+LED6 10/100M/LINK/ACT(GPIO9) <-> BASE_T_LED1,
|
|
+LED4 100M/LINK/ACT (GPIO8) <-> BASE_T_LED2,
|
|
+*/
|
|
+/* User-defined.B */
|
|
+#define BASE_T_LED0_ON_CFG (LED_ON_EVT_LINK_1000M)
|
|
+#define BASE_T_LED0_BLK_CFG (LED_BLK_EVT_1000M_TX_ACT | LED_BLK_EVT_1000M_RX_ACT)
|
|
+#define BASE_T_LED1_ON_CFG (LED_ON_EVT_LINK_100M | LED_ON_EVT_LINK_10M)
|
|
+#define BASE_T_LED1_BLK_CFG (LED_BLK_EVT_100M_TX_ACT | LED_BLK_EVT_100M_RX_ACT | \
|
|
+ LED_BLK_EVT_10M_TX_ACT | LED_BLK_EVT_10M_RX_ACT )
|
|
+#define BASE_T_LED2_ON_CFG (LED_ON_EVT_LINK_100M)
|
|
+#define BASE_T_LED2_BLK_CFG (LED_BLK_EVT_100M_TX_ACT | LED_BLK_EVT_100M_RX_ACT)
|
|
+#define BASE_T_LED3_ON_CFG (0x0)
|
|
+#define BASE_T_LED3_BLK_CFG (0x0)
|
|
+/* User-defined.E */
|
|
+
|
|
+#define EN8801S_LED_COUNT 4
|
|
+
|
|
+#define LED_BCR (0x021)
|
|
+#define LED_BCR_EXT_CTRL (1 << 15)
|
|
+#define LED_BCR_CLK_EN (1 << 3)
|
|
+#define LED_BCR_TIME_TEST (1 << 2)
|
|
+#define LED_BCR_MODE_MASK (3)
|
|
+#define LED_BCR_MODE_DISABLE (0)
|
|
+#define LED_ON_CTRL(i) (0x024 + ((i)*2))
|
|
+#define LED_ON_EN (1 << 15)
|
|
+#define LED_ON_POL (1 << 14)
|
|
+#define LED_ON_EVT_MASK (0x7f)
|
|
+/* LED ON Event Option.B */
|
|
+#define LED_ON_EVT_FORCE (1 << 6)
|
|
+#define LED_ON_EVT_LINK_DOWN (1 << 3)
|
|
+#define LED_ON_EVT_LINK_10M (1 << 2)
|
|
+#define LED_ON_EVT_LINK_100M (1 << 1)
|
|
+#define LED_ON_EVT_LINK_1000M (1 << 0)
|
|
+/* LED ON Event Option.E */
|
|
+#define LED_BLK_CTRL(i) (0x025 + ((i)*2))
|
|
+#define LED_BLK_EVT_MASK (0x3ff)
|
|
+/* LED Blinking Event Option.B*/
|
|
+#define LED_BLK_EVT_FORCE (1 << 9)
|
|
+#define LED_BLK_EVT_10M_RX_ACT (1 << 5)
|
|
+#define LED_BLK_EVT_10M_TX_ACT (1 << 4)
|
|
+#define LED_BLK_EVT_100M_RX_ACT (1 << 3)
|
|
+#define LED_BLK_EVT_100M_TX_ACT (1 << 2)
|
|
+#define LED_BLK_EVT_1000M_RX_ACT (1 << 1)
|
|
+#define LED_BLK_EVT_1000M_TX_ACT (1 << 0)
|
|
+/* LED Blinking Event Option.E*/
|
|
+#define LED_ON_DUR (0x022)
|
|
+#define LED_ON_DUR_MASK (0xffff)
|
|
+#define LED_BLK_DUR (0x023)
|
|
+#define LED_BLK_DUR_MASK (0xffff)
|
|
+
|
|
+#define LED_ENABLE 1
|
|
+#define LED_DISABLE 0
|
|
+
|
|
+#define UNIT_LED_BLINK_DURATION 1024
|
|
+
|
|
+#define AIR_RTN_ON_ERR(cond, err) \
|
|
+ do { if ((cond)) return (err); } while(0)
|
|
+
|
|
+#define AIR_RTN_ERR(err) AIR_RTN_ON_ERR(err < 0, err)
|
|
+
|
|
+#define LED_SET_EVT(reg, cod, result, bit) do \
|
|
+ { \
|
|
+ if(reg & cod) { \
|
|
+ result |= bit; \
|
|
+ } \
|
|
+ } while(0)
|
|
+
|
|
+#define LED_SET_GPIO_SEL(gpio, led, val) do \
|
|
+ { \
|
|
+ val |= (led << (8 * (gpio % 4))); \
|
|
+ } while(0)
|
|
+
|
|
+/* DATA TYPE DECLARATIONS
|
|
+ */
|
|
+typedef struct
|
|
+{
|
|
+ int DATA_Lo;
|
|
+ int DATA_Hi;
|
|
+}TR_DATA_T;
|
|
+
|
|
+typedef union
|
|
+{
|
|
+ struct
|
|
+ {
|
|
+ /* b[15:00] */
|
|
+ int smi_deton_wt : 3;
|
|
+ int smi_det_mdi_inv : 1;
|
|
+ int smi_detoff_wt : 3;
|
|
+ int smi_sigdet_debouncing_en : 1;
|
|
+ int smi_deton_th : 6;
|
|
+ int rsv_14 : 2;
|
|
+ } DataBitField;
|
|
+ int DATA;
|
|
+} gephy_all_REG_LpiReg1Ch, *Pgephy_all_REG_LpiReg1Ch;
|
|
+
|
|
+typedef union
|
|
+{
|
|
+ struct
|
|
+ {
|
|
+ /* b[15:00] */
|
|
+ int rg_smi_detcnt_max : 6;
|
|
+ int rsv_6 : 2;
|
|
+ int rg_smi_det_max_en : 1;
|
|
+ int smi_det_deglitch_off : 1;
|
|
+ int rsv_10 : 6;
|
|
+ } DataBitField;
|
|
+ int DATA;
|
|
+} gephy_all_REG_dev1Eh_reg324h, *Pgephy_all_REG_dev1Eh_reg324h;
|
|
+
|
|
+typedef union
|
|
+{
|
|
+ struct
|
|
+ {
|
|
+ /* b[15:00] */
|
|
+ int da_tx_i2mpb_a_tbt : 6;
|
|
+ int rsv_6 : 4;
|
|
+ int da_tx_i2mpb_a_gbe : 6;
|
|
+ } DataBitField;
|
|
+ int DATA;
|
|
+} gephy_all_REG_dev1Eh_reg012h, *Pgephy_all_REG_dev1Eh_reg012h;
|
|
+
|
|
+typedef union
|
|
+{
|
|
+ struct
|
|
+ {
|
|
+ /* b[15:00] */
|
|
+ int da_tx_i2mpb_b_tbt : 6;
|
|
+ int rsv_6 : 2;
|
|
+ int da_tx_i2mpb_b_gbe : 6;
|
|
+ int rsv_14 : 2;
|
|
+ } DataBitField;
|
|
+ int DATA;
|
|
+} gephy_all_REG_dev1Eh_reg017h, *Pgephy_all_REG_dev1Eh_reg017h;
|
|
+
|
|
+typedef struct AIR_BASE_T_LED_CFG_S
|
|
+{
|
|
+ u16 en;
|
|
+ u16 gpio;
|
|
+ u16 pol;
|
|
+ u16 on_cfg;
|
|
+ u16 blk_cfg;
|
|
+}AIR_BASE_T_LED_CFG_T;
|
|
+
|
|
+typedef enum
|
|
+{
|
|
+ AIR_LED_BLK_DUR_32M,
|
|
+ AIR_LED_BLK_DUR_64M,
|
|
+ AIR_LED_BLK_DUR_128M,
|
|
+ AIR_LED_BLK_DUR_256M,
|
|
+ AIR_LED_BLK_DUR_512M,
|
|
+ AIR_LED_BLK_DUR_1024M,
|
|
+ AIR_LED_BLK_DUR_LAST
|
|
+} AIR_LED_BLK_DUT_T;
|
|
+
|
|
+typedef enum
|
|
+{
|
|
+ AIR_ACTIVE_LOW,
|
|
+ AIR_ACTIVE_HIGH,
|
|
+} AIR_LED_POLARITY;
|
|
+typedef enum
|
|
+{
|
|
+ AIR_LED_MODE_DISABLE,
|
|
+ AIR_LED_MODE_USER_DEFINE,
|
|
+ AIR_LED_MODE_LAST
|
|
+} AIR_LED_MODE_T;
|
|
+
|
|
+/************************************************************************
|
|
+* F U N C T I O N P R O T O T Y P E S
|
|
+************************************************************************/
|
|
+
|
|
+unsigned long airoha_pbus_read(struct mii_dev *bus, int pbus_addr, int pbus_reg);
|
|
+int airoha_pbus_write(struct mii_dev *bus, int pbus_addr, int pbus_reg, unsigned long pbus_data);
|
|
+int airoha_phy_process(void);
|
|
+#endif /* __EN8801S_H */
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/air_en8811h.c
|
|
@@ -0,0 +1,886 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Driver for the Airoha EN8811H 2.5 Gigabit PHY.
|
|
+ *
|
|
+ * Limitations of the EN8811H:
|
|
+ * - Only full duplex supported
|
|
+ * - Forced speed (AN off) is not supported by hardware (100Mbps)
|
|
+ *
|
|
+ * Source originated from linux air_en8811h.c
|
|
+ *
|
|
+ * Copyright (C) 2025 Airoha Technology Corp.
|
|
+ */
|
|
+#include <phy.h>
|
|
+#include <errno.h>
|
|
+#include <log.h>
|
|
+#include <env.h>
|
|
+#include <malloc.h>
|
|
+#include <fs.h>
|
|
+#include <asm/unaligned.h>
|
|
+#include <linux/iopoll.h>
|
|
+#include <linux/bitops.h>
|
|
+#include <linux/compat.h>
|
|
+#include <dm/device_compat.h>
|
|
+#include <u-boot/crc.h>
|
|
+
|
|
+#ifdef CONFIG_PHY_AIROHA_FW_IN_UBI
|
|
+#include <ubi_uboot.h>
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_PHY_AIROHA_FW_IN_MMC
|
|
+#include <mmc.h>
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_PHY_AIROHA_FW_IN_MTD
|
|
+#include <mtd.h>
|
|
+#endif
|
|
+
|
|
+#define EN8811H_PHY_ID 0x03a2a411
|
|
+
|
|
+#define AIR_FW_ADDR_DM 0x00000000
|
|
+#define AIR_FW_ADDR_DSP 0x00100000
|
|
+
|
|
+#define EN8811H_MD32_DM_SIZE 0x4000
|
|
+#define EN8811H_MD32_DSP_SIZE 0x20000
|
|
+
|
|
+ #define EN8811H_FW_CTRL_1 0x0f0018
|
|
+ #define EN8811H_FW_CTRL_1_START 0x0
|
|
+ #define EN8811H_FW_CTRL_1_FINISH 0x1
|
|
+ #define EN8811H_FW_CTRL_2 0x800000
|
|
+ #define EN8811H_FW_CTRL_2_LOADING BIT(11)
|
|
+
|
|
+ /* MII Registers */
|
|
+ #define AIR_AUX_CTRL_STATUS 0x1d
|
|
+ #define AIR_AUX_CTRL_STATUS_SPEED_MASK GENMASK(4, 2)
|
|
+ #define AIR_AUX_CTRL_STATUS_SPEED_100 0x4
|
|
+ #define AIR_AUX_CTRL_STATUS_SPEED_1000 0x8
|
|
+ #define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc
|
|
+
|
|
+#define AIR_EXT_PAGE_ACCESS 0x1f
|
|
+#define AIR_PHY_PAGE_STANDARD 0x0000
|
|
+#define AIR_PHY_PAGE_EXTENDED_4 0x0004
|
|
+
|
|
+/* MII Registers Page 4*/
|
|
+#define AIR_BPBUS_MODE 0x10
|
|
+#define AIR_BPBUS_MODE_ADDR_FIXED 0x0000
|
|
+#define AIR_BPBUS_MODE_ADDR_INCR BIT(15)
|
|
+#define AIR_BPBUS_WR_ADDR_HIGH 0x11
|
|
+#define AIR_BPBUS_WR_ADDR_LOW 0x12
|
|
+#define AIR_BPBUS_WR_DATA_HIGH 0x13
|
|
+#define AIR_BPBUS_WR_DATA_LOW 0x14
|
|
+#define AIR_BPBUS_RD_ADDR_HIGH 0x15
|
|
+#define AIR_BPBUS_RD_ADDR_LOW 0x16
|
|
+#define AIR_BPBUS_RD_DATA_HIGH 0x17
|
|
+#define AIR_BPBUS_RD_DATA_LOW 0x18
|
|
+
|
|
+/* Registers on MDIO_MMD_VEND1 */
|
|
+#define EN8811H_PHY_FW_STATUS 0x8009
|
|
+#define EN8811H_PHY_READY 0x02
|
|
+
|
|
+/* Registers on MDIO_MMD_VEND2 */
|
|
+#define AIR_PHY_LED_BCR 0x021
|
|
+#define AIR_PHY_LED_BCR_MODE_MASK GENMASK(1, 0)
|
|
+#define AIR_PHY_LED_BCR_TIME_TEST BIT(2)
|
|
+#define AIR_PHY_LED_BCR_CLK_EN BIT(3)
|
|
+#define AIR_PHY_LED_BCR_EXT_CTRL BIT(15)
|
|
+
|
|
+#define AIR_PHY_LED_DUR_ON 0x022
|
|
+
|
|
+#define AIR_PHY_LED_DUR_BLINK 0x023
|
|
+
|
|
+#define AIR_PHY_LED_ON(i) (0x024 + ((i) * 2))
|
|
+#define AIR_PHY_LED_ON_MASK (GENMASK(6, 0) | BIT(8))
|
|
+#define AIR_PHY_LED_ON_LINK1000 BIT(0)
|
|
+#define AIR_PHY_LED_ON_LINK100 BIT(1)
|
|
+#define AIR_PHY_LED_ON_LINK10 BIT(2)
|
|
+#define AIR_PHY_LED_ON_LINKDOWN BIT(3)
|
|
+#define AIR_PHY_LED_ON_FDX BIT(4) /* Full duplex */
|
|
+#define AIR_PHY_LED_ON_HDX BIT(5) /* Half duplex */
|
|
+#define AIR_PHY_LED_ON_FORCE_ON BIT(6)
|
|
+#define AIR_PHY_LED_ON_LINK2500 BIT(8)
|
|
+#define AIR_PHY_LED_ON_POLARITY BIT(14)
|
|
+#define AIR_PHY_LED_ON_ENABLE BIT(15)
|
|
+
|
|
+#define AIR_PHY_LED_BLINK(i) (0x025 + ((i) * 2))
|
|
+#define AIR_PHY_LED_BLINK_1000TX BIT(0)
|
|
+#define AIR_PHY_LED_BLINK_1000RX BIT(1)
|
|
+#define AIR_PHY_LED_BLINK_100TX BIT(2)
|
|
+#define AIR_PHY_LED_BLINK_100RX BIT(3)
|
|
+#define AIR_PHY_LED_BLINK_10TX BIT(4)
|
|
+#define AIR_PHY_LED_BLINK_10RX BIT(5)
|
|
+#define AIR_PHY_LED_BLINK_COLLISION BIT(6)
|
|
+#define AIR_PHY_LED_BLINK_RX_CRC_ERR BIT(7)
|
|
+#define AIR_PHY_LED_BLINK_RX_IDLE_ERR BIT(8)
|
|
+#define AIR_PHY_LED_BLINK_FORCE_BLINK BIT(9)
|
|
+#define AIR_PHY_LED_BLINK_2500TX BIT(10)
|
|
+#define AIR_PHY_LED_BLINK_2500RX BIT(11)
|
|
+
|
|
+#define EN8811H_FW_VERSION 0x3b3c
|
|
+
|
|
+#define EN8811H_POLARITY 0xca0f8
|
|
+#define EN8811H_POLARITY_TX_NORMAL BIT(0)
|
|
+#define EN8811H_POLARITY_RX_REVERSE BIT(1)
|
|
+
|
|
+#define EN8811H_CLK_CGM 0xcf958
|
|
+#define EN8811H_CLK_CGM_CKO BIT(26)
|
|
+#define EN8811H_HWTRAP1 0xcf914
|
|
+#define EN8811H_HWTRAP1_CKO BIT(12)
|
|
+
|
|
+#define air_upper_16_bits(n) ((u16)((n) >> 16))
|
|
+#define air_lower_16_bits(n) ((u16)((n) & 0xffff))
|
|
+#define clear_bit(bit, bitmap) __clear_bit(bit, bitmap)
|
|
+
|
|
+/* Led definitions */
|
|
+#define EN8811H_LED_COUNT 3
|
|
+
|
|
+struct led {
|
|
+ unsigned long rules;
|
|
+ unsigned long state;
|
|
+};
|
|
+
|
|
+enum {
|
|
+ AIR_PHY_LED_STATE_FORCE_ON,
|
|
+ AIR_PHY_LED_STATE_FORCE_BLINK,
|
|
+};
|
|
+
|
|
+enum {
|
|
+ AIR_PHY_LED_DUR_BLINK_32MS,
|
|
+ AIR_PHY_LED_DUR_BLINK_64MS,
|
|
+ AIR_PHY_LED_DUR_BLINK_128MS,
|
|
+ AIR_PHY_LED_DUR_BLINK_256MS,
|
|
+ AIR_PHY_LED_DUR_BLINK_512MS,
|
|
+ AIR_PHY_LED_DUR_BLINK_1024MS,
|
|
+};
|
|
+
|
|
+enum {
|
|
+ AIR_LED_DISABLE,
|
|
+ AIR_LED_ENABLE,
|
|
+};
|
|
+
|
|
+enum {
|
|
+ AIR_ACTIVE_LOW,
|
|
+ AIR_ACTIVE_HIGH,
|
|
+};
|
|
+
|
|
+enum {
|
|
+ AIR_LED_MODE_DISABLE,
|
|
+ AIR_LED_MODE_USER_DEFINE,
|
|
+};
|
|
+
|
|
+/* Trigger specific enum */
|
|
+enum air_led_trigger_netdev_modes {
|
|
+ AIR_TRIGGER_NETDEV_LINK = 0,
|
|
+ AIR_TRIGGER_NETDEV_LINK_10,
|
|
+ AIR_TRIGGER_NETDEV_LINK_100,
|
|
+ AIR_TRIGGER_NETDEV_LINK_1000,
|
|
+ AIR_TRIGGER_NETDEV_LINK_2500,
|
|
+ AIR_TRIGGER_NETDEV_LINK_5000,
|
|
+ AIR_TRIGGER_NETDEV_LINK_10000,
|
|
+ AIR_TRIGGER_NETDEV_HALF_DUPLEX,
|
|
+ AIR_TRIGGER_NETDEV_FULL_DUPLEX,
|
|
+ AIR_TRIGGER_NETDEV_TX,
|
|
+ AIR_TRIGGER_NETDEV_RX,
|
|
+ AIR_TRIGGER_NETDEV_TX_ERR,
|
|
+ AIR_TRIGGER_NETDEV_RX_ERR,
|
|
+
|
|
+ /* Keep last */
|
|
+ __AIR_TRIGGER_NETDEV_MAX,
|
|
+};
|
|
+
|
|
+/* Default LED setup:
|
|
+ * GPIO5 <-> LED0 On: Link detected, blink Rx/Tx
|
|
+ * GPIO4 <-> LED1 On: Link detected at 2500 and 1000 Mbps
|
|
+ * GPIO3 <-> LED2 On: Link detected at 2500 and 100 Mbps
|
|
+ */
|
|
+#define AIR_DEFAULT_TRIGGER_LED0 (BIT(AIR_TRIGGER_NETDEV_LINK) | \
|
|
+ BIT(AIR_TRIGGER_NETDEV_RX) | \
|
|
+ BIT(AIR_TRIGGER_NETDEV_TX))
|
|
+#define AIR_DEFAULT_TRIGGER_LED1 (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | \
|
|
+ BIT(AIR_TRIGGER_NETDEV_LINK_1000))
|
|
+#define AIR_DEFAULT_TRIGGER_LED2 (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | \
|
|
+ BIT(AIR_TRIGGER_NETDEV_LINK_100))
|
|
+
|
|
+#define AIR_PHY_LED_DUR_UNIT 781
|
|
+#define AIR_PHY_LED_DUR (AIR_PHY_LED_DUR_UNIT << AIR_PHY_LED_DUR_BLINK_64MS)
|
|
+
|
|
+struct en8811h_priv {
|
|
+ int firmware_version;
|
|
+ bool mcu_needs_restart;
|
|
+ struct led led[EN8811H_LED_COUNT];
|
|
+};
|
|
+
|
|
+static int air_phy_read_page(struct phy_device *phydev)
|
|
+{
|
|
+ return phy_read(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS);
|
|
+}
|
|
+
|
|
+static int air_phy_write_page(struct phy_device *phydev, int page)
|
|
+{
|
|
+ return phy_write(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS, page);
|
|
+}
|
|
+
|
|
+int air_phy_select_page(struct phy_device *phydev, int page)
|
|
+{
|
|
+ int ret, oldpage;
|
|
+
|
|
+ oldpage = air_phy_read_page(phydev);
|
|
+ if (oldpage < 0)
|
|
+ return oldpage;
|
|
+
|
|
+ if (oldpage != page) {
|
|
+ ret = air_phy_write_page(phydev, page);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return oldpage;
|
|
+}
|
|
+
|
|
+int air_phy_restore_page(struct phy_device *phydev, int oldpage, int ret)
|
|
+{
|
|
+ int r;
|
|
+
|
|
+ if (oldpage < 0)
|
|
+ return oldpage;
|
|
+
|
|
+ r = air_phy_write_page(phydev, oldpage);
|
|
+ if (ret >= 0 && r < 0)
|
|
+ ret = r;
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int air_buckpbus_reg_write(struct phy_device *phydev,
|
|
+ u32 pbus_address, u32 pbus_data)
|
|
+{
|
|
+ int ret, saved_page;
|
|
+
|
|
+ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
|
|
+ if (saved_page < 0)
|
|
+ return saved_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
|
|
+ AIR_BPBUS_MODE_ADDR_FIXED);
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
|
|
+ air_upper_16_bits(pbus_address));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
|
|
+ air_lower_16_bits(pbus_address));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH,
|
|
+ air_upper_16_bits(pbus_data));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
|
|
+ air_lower_16_bits(pbus_data));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+restore_page:
|
|
+ if (ret < 0)
|
|
+ printf("%s 0x%08x failed: %d\n", __func__,
|
|
+ pbus_address, ret);
|
|
+
|
|
+ return air_phy_restore_page(phydev, saved_page, ret);
|
|
+}
|
|
+
|
|
+static int air_buckpbus_reg_read(struct phy_device *phydev,
|
|
+ u32 pbus_address, u32 *pbus_data)
|
|
+{
|
|
+ int pbus_data_low, pbus_data_high;
|
|
+ int ret = 0, saved_page;
|
|
+
|
|
+ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
|
|
+ if (saved_page < 0)
|
|
+ return saved_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
|
|
+ AIR_BPBUS_MODE_ADDR_FIXED);
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH,
|
|
+ air_upper_16_bits(pbus_address));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
|
|
+ air_lower_16_bits(pbus_address));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH);
|
|
+ if (pbus_data_high < 0) {
|
|
+ ret = pbus_data_high;
|
|
+ goto restore_page;
|
|
+ }
|
|
+
|
|
+ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW);
|
|
+ if (pbus_data_low < 0) {
|
|
+ ret = pbus_data_low;
|
|
+ goto restore_page;
|
|
+ }
|
|
+
|
|
+ *pbus_data = pbus_data_low | (pbus_data_high << 16);
|
|
+
|
|
+restore_page:
|
|
+ if (ret < 0)
|
|
+ printf("%s 0x%08x failed: %d\n", __func__,
|
|
+ pbus_address, ret);
|
|
+
|
|
+ return air_phy_restore_page(phydev, saved_page, ret);
|
|
+}
|
|
+
|
|
+static int air_buckpbus_reg_modify(struct phy_device *phydev,
|
|
+ u32 pbus_address, u32 mask, u32 set)
|
|
+{
|
|
+ int pbus_data_low, pbus_data_high;
|
|
+ u32 pbus_data_old, pbus_data_new;
|
|
+ int ret = 0, saved_page;
|
|
+
|
|
+ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
|
|
+ if (saved_page < 0)
|
|
+ return saved_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
|
|
+ AIR_BPBUS_MODE_ADDR_FIXED);
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH,
|
|
+ air_upper_16_bits(pbus_address));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
|
|
+ air_lower_16_bits(pbus_address));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH);
|
|
+ if (pbus_data_high < 0)
|
|
+ return pbus_data_high;
|
|
+
|
|
+ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW);
|
|
+ if (pbus_data_low < 0)
|
|
+ return pbus_data_low;
|
|
+
|
|
+ pbus_data_old = pbus_data_low | (pbus_data_high << 16);
|
|
+ pbus_data_new = (pbus_data_old & ~mask) | set;
|
|
+ if (pbus_data_new == pbus_data_old)
|
|
+ return 0;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
|
|
+ air_upper_16_bits(pbus_address));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
|
|
+ air_lower_16_bits(pbus_address));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH,
|
|
+ air_upper_16_bits(pbus_data_new));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
|
|
+ air_lower_16_bits(pbus_data_new));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+restore_page:
|
|
+ if (ret < 0)
|
|
+ printf("%s 0x%08x failed: %d\n", __func__,
|
|
+ pbus_address, ret);
|
|
+
|
|
+ return air_phy_restore_page(phydev, saved_page, ret);
|
|
+}
|
|
+
|
|
+static int air_write_buf(struct phy_device *phydev, unsigned long address,
|
|
+ unsigned long array_size, const unsigned char *buffer)
|
|
+{
|
|
+ unsigned int offset;
|
|
+ int ret, saved_page;
|
|
+ u16 val;
|
|
+
|
|
+ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
|
|
+ if (saved_page < 0)
|
|
+ return saved_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
|
|
+ AIR_BPBUS_MODE_ADDR_INCR);
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
|
|
+ air_upper_16_bits(address));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
|
|
+ air_lower_16_bits(address));
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ for (offset = 0; offset < array_size; offset += 4) {
|
|
+ val = get_unaligned_le16(&buffer[offset + 2]);
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, val);
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+
|
|
+ val = get_unaligned_le16(&buffer[offset]);
|
|
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, val);
|
|
+ if (ret < 0)
|
|
+ goto restore_page;
|
|
+ }
|
|
+
|
|
+restore_page:
|
|
+ if (ret < 0)
|
|
+ printf("%s 0x%08lx failed: %d\n", __func__,
|
|
+ address, ret);
|
|
+
|
|
+ return air_phy_restore_page(phydev, saved_page, ret);
|
|
+}
|
|
+
|
|
+static int en8811h_wait_mcu_ready(struct phy_device *phydev)
|
|
+{
|
|
+ int ret, reg_value;
|
|
+
|
|
+ /* Because of mdio-lock, may have to wait for multiple loads */
|
|
+ ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
|
|
+ EN8811H_PHY_FW_STATUS, reg_value,
|
|
+ reg_value == EN8811H_PHY_READY,
|
|
+ 20000, 7500000, true);
|
|
+ if (ret) {
|
|
+ printf("MCU not ready: 0x%x\n", reg_value);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+__weak int en8811h_read_fw(void **addr)
|
|
+{
|
|
+ u32 ca_crc32;
|
|
+ void *buffer;
|
|
+ int ret;
|
|
+
|
|
+ /* Allocate memory to store both DM and DSP firmware */
|
|
+ buffer = malloc(EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE);
|
|
+ if (!buffer) {
|
|
+ printf("Failed to allocate memory for firmware\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+#ifdef CONFIG_PHY_AIROHA_FW_IN_UBI
|
|
+ ret = ubi_volume_read("en8811h-fw", buffer, 0, EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE);
|
|
+ if (ret) {
|
|
+ printf("[Airoha] read firmware from UBI failed.\n");
|
|
+ free(buffer);
|
|
+ buffer = NULL;
|
|
+ return ret;
|
|
+ }
|
|
+#elif defined(CONFIG_PHY_AIROHA_FW_IN_MMC)
|
|
+ struct mmc *mmc = find_mmc_device(0);
|
|
+ if (!mmc) {
|
|
+ printf("[Airoha] opening MMC device failed.\n");
|
|
+ free(buffer);
|
|
+ buffer = NULL;
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ if (mmc_init(mmc)) {
|
|
+ printf("[Airoha] initializing MMC device failed.\n");
|
|
+ free(buffer);
|
|
+ buffer = NULL;
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ if (IS_SD(mmc)) {
|
|
+ printf("[Airoha] SD card is not supported.\n");
|
|
+ free(buffer);
|
|
+ buffer = NULL;
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ ret = mmc_set_part_conf(mmc, 1, 2, 2);
|
|
+ if (ret) {
|
|
+ printf("[Airoha] cannot access eMMC boot1 hw partition.\n");
|
|
+ free(buffer);
|
|
+ buffer = NULL;
|
|
+ return ret;
|
|
+ }
|
|
+ ret = blk_dread(mmc_get_blk_desc(mmc), 0, 0x120, buffer);
|
|
+ mmc_set_part_conf(mmc, 1, 1, 0);
|
|
+ if (ret != 0x120) {
|
|
+ printf("[Airoha] cannot read firmware from eMMC.\n");
|
|
+ free(buffer);
|
|
+ buffer = NULL;
|
|
+ return -EIO;
|
|
+ }
|
|
+#else
|
|
+#warning EN8811H firmware loading not implemented
|
|
+ free(buffer);
|
|
+ buffer = NULL;
|
|
+ return -EOPNOTSUPP;
|
|
+#endif
|
|
+
|
|
+ /* Calculate CRC32 of DM firmware for verification */
|
|
+ ca_crc32 = crc32(0, (unsigned char *)buffer, EN8811H_MD32_DM_SIZE);
|
|
+ debug("DM crc32: 0x%x\n", ca_crc32);
|
|
+
|
|
+ /* Calculate CRC32 of DSP firmware for verification */
|
|
+ ca_crc32 = crc32(0, (unsigned char *)buffer + EN8811H_MD32_DM_SIZE,
|
|
+ EN8811H_MD32_DSP_SIZE);
|
|
+ debug("DSP crc32: 0x%x\n", ca_crc32);
|
|
+
|
|
+ *addr = buffer;
|
|
+ debug("Found Airoha Firmware.\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int en8811h_load_firmware(struct phy_device *phydev)
|
|
+{
|
|
+ struct en8811h_priv *priv = phydev->priv;
|
|
+ void *buffer;
|
|
+ int ret;
|
|
+
|
|
+ ret = en8811h_read_fw(&buffer);
|
|
+ if (ret < 0)
|
|
+ goto en8811h_load_firmware_out;
|
|
+
|
|
+ ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
|
|
+ EN8811H_FW_CTRL_1_START);
|
|
+ if (ret < 0)
|
|
+ goto en8811h_load_firmware_out;
|
|
+
|
|
+ ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
|
|
+ EN8811H_FW_CTRL_2_LOADING,
|
|
+ EN8811H_FW_CTRL_2_LOADING);
|
|
+ if (ret < 0)
|
|
+ goto en8811h_load_firmware_out;
|
|
+
|
|
+ ret = air_write_buf(phydev, AIR_FW_ADDR_DM, EN8811H_MD32_DM_SIZE,
|
|
+ (unsigned char *)buffer);
|
|
+ if (ret < 0)
|
|
+ goto en8811h_load_firmware_out;
|
|
+
|
|
+ ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, EN8811H_MD32_DSP_SIZE,
|
|
+ (unsigned char *)buffer + EN8811H_MD32_DM_SIZE);
|
|
+ if (ret < 0)
|
|
+ goto en8811h_load_firmware_out;
|
|
+
|
|
+ ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
|
|
+ EN8811H_FW_CTRL_2_LOADING, 0);
|
|
+ if (ret < 0)
|
|
+ goto en8811h_load_firmware_out;
|
|
+
|
|
+ ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
|
|
+ EN8811H_FW_CTRL_1_FINISH);
|
|
+ if (ret < 0)
|
|
+ goto en8811h_load_firmware_out;
|
|
+
|
|
+ ret = en8811h_wait_mcu_ready(phydev);
|
|
+
|
|
+ air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION,
|
|
+ &priv->firmware_version);
|
|
+ printf("MD32 firmware version: %08x\n",
|
|
+ priv->firmware_version);
|
|
+
|
|
+en8811h_load_firmware_out:
|
|
+ free(buffer);
|
|
+ if (ret < 0)
|
|
+ printf("Firmware loading failed: %d\n", ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int en8811h_restart_mcu(struct phy_device *phydev)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_write_mmd(phydev, 0x1e, 0x8009, 0x0);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
|
|
+ EN8811H_FW_CTRL_1_START);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
|
|
+ EN8811H_FW_CTRL_1_FINISH);
|
|
+}
|
|
+
|
|
+static int air_led_hw_control_set(struct phy_device *phydev, u8 index,
|
|
+ unsigned long rules)
|
|
+{
|
|
+ struct en8811h_priv *priv = phydev->priv;
|
|
+ u16 on = 0, blink = 0;
|
|
+ int ret;
|
|
+
|
|
+ if (index >= EN8811H_LED_COUNT)
|
|
+ return -EINVAL;
|
|
+
|
|
+ priv->led[index].rules = rules;
|
|
+
|
|
+ if (rules & BIT(AIR_TRIGGER_NETDEV_FULL_DUPLEX))
|
|
+ on |= AIR_PHY_LED_ON_FDX;
|
|
+
|
|
+ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_10) | BIT(AIR_TRIGGER_NETDEV_LINK)))
|
|
+ on |= AIR_PHY_LED_ON_LINK10;
|
|
+
|
|
+ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_100) | BIT(AIR_TRIGGER_NETDEV_LINK)))
|
|
+ on |= AIR_PHY_LED_ON_LINK100;
|
|
+
|
|
+ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_1000) | BIT(AIR_TRIGGER_NETDEV_LINK)))
|
|
+ on |= AIR_PHY_LED_ON_LINK1000;
|
|
+
|
|
+ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | BIT(AIR_TRIGGER_NETDEV_LINK)))
|
|
+ on |= AIR_PHY_LED_ON_LINK2500;
|
|
+
|
|
+ if (rules & BIT(AIR_TRIGGER_NETDEV_RX)) {
|
|
+ blink |= AIR_PHY_LED_BLINK_10RX |
|
|
+ AIR_PHY_LED_BLINK_100RX |
|
|
+ AIR_PHY_LED_BLINK_1000RX |
|
|
+ AIR_PHY_LED_BLINK_2500RX;
|
|
+ }
|
|
+
|
|
+ if (rules & BIT(AIR_TRIGGER_NETDEV_TX)) {
|
|
+ blink |= AIR_PHY_LED_BLINK_10TX |
|
|
+ AIR_PHY_LED_BLINK_100TX |
|
|
+ AIR_PHY_LED_BLINK_1000TX |
|
|
+ AIR_PHY_LED_BLINK_2500TX;
|
|
+ }
|
|
+
|
|
+ if (blink || on) {
|
|
+ /* switch hw-control on, so led-on and led-blink are off */
|
|
+ clear_bit(AIR_PHY_LED_STATE_FORCE_ON,
|
|
+ &priv->led[index].state);
|
|
+ clear_bit(AIR_PHY_LED_STATE_FORCE_BLINK,
|
|
+ &priv->led[index].state);
|
|
+ } else {
|
|
+ priv->led[index].rules = 0;
|
|
+ }
|
|
+
|
|
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_ON(index),
|
|
+ AIR_PHY_LED_ON_MASK, on);
|
|
+
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BLINK(index),
|
|
+ blink);
|
|
+};
|
|
+
|
|
+static int air_led_init(struct phy_device *phydev, u8 index, u8 state, u8 pol)
|
|
+{
|
|
+ int val = 0;
|
|
+ int err;
|
|
+
|
|
+ if (index >= EN8811H_LED_COUNT)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (state == AIR_LED_ENABLE)
|
|
+ val |= AIR_PHY_LED_ON_ENABLE;
|
|
+ else
|
|
+ val &= ~AIR_PHY_LED_ON_ENABLE;
|
|
+
|
|
+ if (pol == AIR_ACTIVE_HIGH)
|
|
+ val |= AIR_PHY_LED_ON_POLARITY;
|
|
+ else
|
|
+ val &= ~AIR_PHY_LED_ON_POLARITY;
|
|
+
|
|
+ err = phy_write_mmd(phydev, 0x1f, AIR_PHY_LED_ON(index), val);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int air_leds_init(struct phy_device *phydev, int num, u16 dur, int mode)
|
|
+{
|
|
+ struct en8811h_priv *priv = phydev->priv;
|
|
+ int ret, i;
|
|
+
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_BLINK,
|
|
+ dur);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_ON,
|
|
+ dur >> 1);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ switch (mode) {
|
|
+ case AIR_LED_MODE_DISABLE:
|
|
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR,
|
|
+ AIR_PHY_LED_BCR_EXT_CTRL |
|
|
+ AIR_PHY_LED_BCR_MODE_MASK, 0);
|
|
+ break;
|
|
+ case AIR_LED_MODE_USER_DEFINE:
|
|
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR,
|
|
+ AIR_PHY_LED_BCR_EXT_CTRL |
|
|
+ AIR_PHY_LED_BCR_CLK_EN,
|
|
+ AIR_PHY_LED_BCR_EXT_CTRL |
|
|
+ AIR_PHY_LED_BCR_CLK_EN);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ break;
|
|
+ default:
|
|
+ printf("LED mode %d is not supported\n", mode);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < num; ++i) {
|
|
+ ret = air_led_init(phydev, i, AIR_LED_ENABLE, AIR_ACTIVE_HIGH);
|
|
+ if (ret < 0) {
|
|
+ printf("LED%d init failed: %d\n", i, ret);
|
|
+ return ret;
|
|
+ }
|
|
+ air_led_hw_control_set(phydev, i, priv->led[i].rules);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int en8811h_config(struct phy_device *phydev)
|
|
+{
|
|
+ struct en8811h_priv *priv = phydev->priv;
|
|
+ ofnode node = phy_get_ofnode(phydev);
|
|
+ u32 pbus_value = 0;
|
|
+ int ret = 0;
|
|
+
|
|
+ /* If restart happened in .probe(), no need to restart now */
|
|
+ if (priv->mcu_needs_restart) {
|
|
+ ret = en8811h_restart_mcu(phydev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ } else {
|
|
+ ret = en8811h_load_firmware(phydev);
|
|
+ if (ret) {
|
|
+ printf("Load firmware fail.\n");
|
|
+ return ret;
|
|
+ }
|
|
+ /* Next calls to .config() mcu needs to restart */
|
|
+ priv->mcu_needs_restart = true;
|
|
+ }
|
|
+
|
|
+ ret = phy_write_mmd(phydev, 0x1e, 0x800c, 0x0);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = phy_write_mmd(phydev, 0x1e, 0x800d, 0x0);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = phy_write_mmd(phydev, 0x1e, 0x800e, 0x1101);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = phy_write_mmd(phydev, 0x1e, 0x800f, 0x0002);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* Serdes polarity */
|
|
+ pbus_value = 0;
|
|
+ if (ofnode_read_bool(node, "airoha,pnswap-rx"))
|
|
+ pbus_value |= EN8811H_POLARITY_RX_REVERSE;
|
|
+ else
|
|
+ pbus_value &= ~EN8811H_POLARITY_RX_REVERSE;
|
|
+ if (ofnode_read_bool(node, "airoha,pnswap-tx"))
|
|
+ pbus_value &= ~EN8811H_POLARITY_TX_NORMAL;
|
|
+ else
|
|
+ pbus_value |= EN8811H_POLARITY_TX_NORMAL;
|
|
+ ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
|
|
+ EN8811H_POLARITY_RX_REVERSE |
|
|
+ EN8811H_POLARITY_TX_NORMAL, pbus_value);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR,
|
|
+ AIR_LED_MODE_USER_DEFINE);
|
|
+ if (ret < 0) {
|
|
+ printf("Failed to disable leds: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int en8811h_parse_status(struct phy_device *phydev)
|
|
+{
|
|
+ int ret = 0, reg_value;
|
|
+
|
|
+ phydev->duplex = DUPLEX_FULL;
|
|
+
|
|
+ reg_value = phy_read(phydev, MDIO_DEVAD_NONE, AIR_AUX_CTRL_STATUS);
|
|
+ if (reg_value < 0)
|
|
+ return reg_value;
|
|
+
|
|
+ switch (reg_value & AIR_AUX_CTRL_STATUS_SPEED_MASK) {
|
|
+ case AIR_AUX_CTRL_STATUS_SPEED_2500:
|
|
+ phydev->speed = SPEED_2500;
|
|
+ break;
|
|
+ case AIR_AUX_CTRL_STATUS_SPEED_1000:
|
|
+ phydev->speed = SPEED_1000;
|
|
+ break;
|
|
+ case AIR_AUX_CTRL_STATUS_SPEED_100:
|
|
+ phydev->speed = SPEED_100;
|
|
+ break;
|
|
+ default:
|
|
+ printf("Auto-neg error, defaulting to 100M/FD\n");
|
|
+ phydev->speed = SPEED_100;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int en8811h_startup(struct phy_device *phydev)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = genphy_update_link(phydev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return en8811h_parse_status(phydev);
|
|
+}
|
|
+
|
|
+static int en8811h_probe(struct phy_device *phydev)
|
|
+{
|
|
+ struct en8811h_priv *priv;
|
|
+
|
|
+ priv = malloc(sizeof(*priv));
|
|
+ if (!priv)
|
|
+ return -ENOMEM;
|
|
+ memset(priv, 0, sizeof(*priv));
|
|
+
|
|
+ priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0;
|
|
+ priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1;
|
|
+ priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2;
|
|
+
|
|
+ /* mcu has just restarted after firmware load */
|
|
+ priv->mcu_needs_restart = false;
|
|
+
|
|
+ phydev->priv = priv;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+U_BOOT_PHY_DRIVER(en8811h) = {
|
|
+ .name = "Airoha EN8811H",
|
|
+ .uid = EN8811H_PHY_ID,
|
|
+ .mask = 0x0ffffff0,
|
|
+ .config = &en8811h_config,
|
|
+ .probe = &en8811h_probe,
|
|
+ .startup = &en8811h_startup,
|
|
+ .shutdown = &genphy_shutdown,
|
|
+};
|