bpi-r3-mini:update phy driver

This commit is contained in:
padavanonly
2025-04-07 23:08:05 +08:00
parent dc6faff864
commit b754a01b54
10 changed files with 3987 additions and 894 deletions

View File

@@ -244,7 +244,9 @@ $(eval $(call KernelPackage,phy-realtek))
define KernelPackage/phy-air-en8811h
SUBMENU:=$(NETWORK_DEVICES_MENU)
TITLE:=Airoha EN8811H PHY Driver
KCONFIG:=CONFIG_AIR_EN8811H_PHY
KCONFIG:= \
CONFIG_AIROHA_EN8811H_PHY=y \
CONFIG_AIROHA_EN8811H_PHY_DEBUGFS=y
DEPENDS:=+kmod-libphy
FILES:=$(LINUX_DIR)/drivers/net/phy/air_en8811h.ko
AUTOLOAD:=$(call AutoLoad,20,air_en8811h,1)

View File

@@ -1305,6 +1305,7 @@ endef
$(eval $(call KernelPackage,usb-net-qmi-wwan))
define KernelPackage/usb-net-qmi-wwan-quectel-bpi
TITLE:=QMI WWAN driver
KCONFIG:=CONFIG_USB_NET_QMI_WWAN
@@ -1314,7 +1315,7 @@ define KernelPackage/usb-net-qmi-wwan-quectel-bpi
endef
define KernelPackage/usb-net-qmi-wwan-quectel-bpi/description
QMI WWAN driver for Quectel bpi version
QMI WWAN driver for Quectel bpi version
endef
$(eval $(call KernelPackage,usb-net-qmi-wwan-quectel-bpi))

View File

@@ -0,0 +1,204 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*************************************************
* FILE NAME: air_en8811h.h
* PURPOSE:
* EN8811H PHY Driver for Linux
* NOTES:
*
* Copyright (C) 2023 Airoha Technology Corp.
*************************************************/
#ifndef __EN8811H_H
#define __EN8811H_H
#define EN8811H_MD32_DM "EthMD32.dm.bin"
#define EN8811H_MD32_DSP "EthMD32.DSP.bin"
#define EN8811H_IVY "ivypram.bin"
#define EN8811H_PHY_ID1 0x03a2
#define EN8811H_PHY_ID2 0xa411
#define EN8811H_PHY_ID ((EN8811H_PHY_ID1 << 16) | EN8811H_PHY_ID2)
#define EN8811H_PHY_READY 0x02
#define EN8811H_PHY_IVY_READY 0xABC
#define MAX_RETRY 25
#define EN8811H_TX_POL_NORMAL 0x1
#define EN8811H_TX_POL_REVERSE 0x0
#define EN8811H_RX_POL_NORMAL (0x0 << 1)
#define EN8811H_RX_POL_REVERSE (0x1 << 1)
/***************************************************************
* The following led_cfg example is for reference only.
* LED0 Link 2500/Blink 2500 TxRx (GPIO5) <-> BASE_T_LED0,
* LED1 Link 1000/Blink 1000 TxRx (GPIO4) <-> BASE_T_LED1,
* LED2 Link 100 /Blink 100 TxRx (GPIO3) <-> BASE_T_LED2,
***************************************************************/
/* User-defined.B */
#define AIR_LED0_ON (LED_ON_EVT_LINK_2500M)
#define AIR_LED0_BLK (LED_BLK_EVT_2500M_TX_ACT | LED_BLK_EVT_2500M_RX_ACT)
#define AIR_LED1_ON (LED_ON_EVT_LINK_1000M)
#define AIR_LED1_BLK (LED_BLK_EVT_1000M_TX_ACT | LED_BLK_EVT_1000M_RX_ACT)
#define AIR_LED2_ON (LED_ON_EVT_LINK_100M)
#define AIR_LED2_BLK (LED_BLK_EVT_100M_TX_ACT | LED_BLK_EVT_100M_RX_ACT)
/* User-defined.E */
/* 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 EN8811H_DRIVER_VERSION "v1.3.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 (0x1ff)
/* LED ON Event Option.B */
#define LED_ON_EVT_LINK_2500M (1 << 8)
#define LED_ON_EVT_FORCE (1 << 6)
#define LED_ON_EVT_LINK_DOWN (1 << 3)
#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 (0xfff)
/* LED Blinking Event Option.B*/
#define LED_BLK_EVT_2500M_RX_ACT (1 << 11)
#define LED_BLK_EVT_2500M_TX_ACT (1 << 10)
#define LED_BLK_EVT_FORCE (1 << 9)
#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 EN8811H_LED_COUNT 3
#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_DUR (0x022)
#define LED_ON_DUR_MASK (0xffff)
#define LED_BLK_DUR (0x023)
#define LED_BLK_DUR_MASK (0xffff)
#define UNIT_LED_BLINK_DURATION 780
#define GET_BIT(val, bit) ((val & BIT(bit)) >> bit)
#define INVALID_DATA 0xffff
#define PBUS_INVALID_DATA 0xffffffff
/* 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
/* Registers on BUCKPBUS */
#define EN8811H_2P5G_LPA 0x3b30
#define EN8811H_2P5G_LPA_2P5G BIT(0)
#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)
#define EN8811H_LOOP 0x800
#define NUM_ASI_REGS 5
struct air_cable_test_rsl {
int status[4];
unsigned int length[4];
};
struct en8811h_priv {
struct dentry *debugfs_root;
unsigned int dm_crc32;
unsigned int dsp_crc32;
unsigned int ivy_crc32;
int pol;
int surge;
int cko;
struct kobject *cable_kobj;
int running_status;
int pair[4];
int an;
int link;
int speed;
int duplex;
int pause;
int asym_pause;
u16 on_crtl[3];
u16 blk_crtl[3];
u32 firmware_version;
bool mcu_needs_restart;
bool mcu_load;
int debug;
int phy_handle;
int init_stage;
int need_an;
int count;
};
struct air_base_t_led_cfg {
u16 en;
u16 gpio;
u16 pol;
u16 on_cfg;
u16 blk_cfg;
};
enum air_init_stage {
AIR_INIT_START,
AIR_INIT_CONFIG,
AIR_INIT_FW_LOADING,
AIR_INIT_FW_READY,
AIR_INIT_SUCESS,
AIR_INIT_FW_FAIL,
AIR_INIT_FAIL,
AIR_INIT_LAST
};
enum air_led_gpio {
AIR_LED2_GPIO3 = 3,
AIR_LED1_GPIO4,
AIR_LED0_GPIO5,
AIR_LED_LAST
};
enum air_base_t_led {
AIR_BASE_T_LED0,
AIR_BASE_T_LED1,
AIR_BASE_T_LED2,
AIR_BASE_T_LED3
};
enum air_led_blk_dur {
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
};
enum air_led_polarity {
AIR_ACTIVE_LOW,
AIR_ACTIVE_HIGH,
};
enum air_led_mode {
AIR_LED_MODE_DISABLE,
AIR_LED_MODE_USER_DEFINE,
AIR_LED_MODE_LAST
};
#endif /* End of __EN8811H_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,229 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*************************************************
* FILE NAME: air_en8811h_api.h
* PURPOSE:
* EN8811H PHY Driver for Linux
* NOTES:
*
* Copyright (C) 2023 Airoha Technology Corp.
*************************************************/
#ifndef __EN8811H_API_H
#define __EN8811H_API_H
#include <linux/version.h>
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
#define phydev_mdio_bus(_dev) (_dev->bus)
#define phydev_addr(_dev) (_dev->addr)
#define phydev_dev(_dev) (&_dev->dev)
#define phydev_kobj(_dev) (&_dev->dev.kobj)
#else
#define phydev_mdio_bus(_dev) (_dev->mdio.bus)
#define phydev_addr(_dev) (_dev->mdio.addr)
#define phydev_dev(_dev) (&_dev->mdio.dev)
#define phydev_kobj(_dev) (&_dev->mdio.dev.kobj)
#endif
#define DEBUGFS_COUNTER "counter"
#define DEBUGFS_DRIVER_INFO "drvinfo"
#define DEBUGFS_PORT_MODE "port_mode"
#define DEBUGFS_BUCKPBUS_OP "buckpbus_op"
#define DEBUGFS_PBUS_OP "pbus_op"
#define DEBUGFS_POLARITY "polarity"
#define DEBUGFS_LINK_STATUS "link_status"
#define DEBUGFS_DBG_REG_SHOW "dbg_regs_show"
#define DEBUGFS_TEMPERATURE "temp"
#define DEBUGFS_LP_SPEED "lp_speed"
#define DEBUGFS_MII_CL22_OP "cl22_op"
#define DEBUGFS_MII_CL45_OP "cl45_op"
#define DEBUGFS_CABLE_DIAG "cable_diag"
#define DEBUGFS_LED_MODE "led_mode"
#define DEBUGFS_TX_COMP "tx_comp"
#define CMD_MAX_LENGTH 128
/* bits range: for example AIR_BITS_RANGE(16, 4) = 0x0F0000 */
#ifndef AIR_BITS_RANGE
#define AIR_BITS_RANGE(offset, range) GENMASK((offset) + (range) - 1U, (offset))
#endif /* End of AIR_BITS_RANGE */
/* bits offset right: for example AIR_BITS_OFF_R(0x1234, 8, 4) = 0x2 */
#ifndef AIR_BITS_OFF_R
#define AIR_BITS_OFF_R(val, offset, range) (((val) >> (offset)) & GENMASK((range) - 1U, 0))
#endif /* End of AIR_BITS_OFF_R */
/* bits offset left: for example AIR_BITS_OFF_L(0x1234, 8, 4) = 0x400 */
#ifndef AIR_BITS_OFF_L
#define AIR_BITS_OFF_L(val, offset, range) (((val) & GENMASK((range) - 1U, 0)) << (offset))
#endif /* End of AIR_BITS_OFF_L */
#define AIR_EN8811H_SET_VALUE(__out__, __val__, __offset__, __length__) \
{ \
(__out__) &= ~AIR_BITS_RANGE((__offset__), (__length__)); \
(__out__) |= AIR_BITS_OFF_L((__val__), (__offset__), (__length__)); \
}
#define CTL1000_PORT_TYPE (0x0400)
#define CTL1000_TEST_NORMAL (0x0000)
#define CTL1000_TEST_TM1 (0x2000)
#define CTL1000_TEST_TM2 (0x4000)
#define CTL1000_TEST_TM3 (0x6000)
#define CTL1000_TEST_TM4 (0x8000)
#define MMD_DEV_VSPEC1 (0x1e)
enum air_port_mode {
AIR_PORT_MODE_FORCE_100,
AIR_PORT_MODE_FORCE_1000,
AIR_PORT_MODE_FORCE_2500,
AIR_PORT_MODE_AUTONEGO,
AIR_PORT_MODE_POWER_DOWN,
AIR_PORT_MODE_POWER_UP,
AIR_PORT_MODE_SSC_DISABLE,
AIR_PORT_MODE_SSC_ENABLE,
AIR_PORT_MODE_LAST = 0xFF,
};
enum air_polarity {
AIR_POL_TX_REV_RX_NOR,
AIR_POL_TX_NOR_RX_NOR,
AIR_POL_TX_REV_RX_REV,
AIR_POL_TX_NOR_RX_REV,
AIR_POL_TX_NOR_RX_LAST = 0xff,
};
/* Link mode bit indices */
enum air_link_mode_bit {
AIR_LINK_MODE_10baseT_Half_BIT = 0,
AIR_LINK_MODE_10baseT_Full_BIT = 1,
AIR_LINK_MODE_100baseT_Half_BIT = 2,
AIR_LINK_MODE_100baseT_Full_BIT = 3,
AIR_LINK_MODE_1000baseT_Full_BIT = 4,
AIR_LINK_MODE_2500baseT_Full_BIT = 5,
};
enum air_led_force {
AIR_LED_NORMAL = 0,
AIR_LED_FORCE_OFF,
AIR_LED_FORCE_ON,
AIR_LED_FORCE_LAST = 0xff,
};
enum air_para {
AIR_PARA_PRIV,
AIR_PARA_PHYDEV,
AIR_PARA_LAST = 0xff
};
enum air_port_cable_status {
AIR_PORT_CABLE_STATUS_ERROR,
AIR_PORT_CABLE_STATUS_OPEN,
AIR_PORT_CABLE_STATUS_SHORT,
AIR_PORT_CABLE_STATUS_NORMAL,
AIR_PORT_CABLE_STATUS_LAST = 0xff
};
enum air_surge {
AIR_SURGE_0R,
AIR_SURGE_5R,
AIR_SURGE_LAST = 0xff
};
enum air_port_cable_test_pair {
AIR_PORT_CABLE_TEST_PAIR_A,
AIR_PORT_CABLE_TEST_PAIR_B,
AIR_PORT_CABLE_TEST_PAIR_C,
AIR_PORT_CABLE_TEST_PAIR_D,
AIR_PORT_CABLE_TEST_PAIR_ALL,
AIR_PORT_CABLE_TEST_PAIR_LAST
};
enum air_cko {
AIR_CKO_DIS,
AIR_CKO_EN,
AIR_CKO_LAST = 0xff
};
enum air_tx_comp_mode {
AIR_TX_COMP_MODE_100M_PAIR_A,
AIR_TX_COMP_MODE_100M_PAIR_B,
AIR_TX_COMP_MODE_100M_PAIR_A_DISCRETE,
AIR_TX_COMP_MODE_100M_PAIR_B_DISCRETE,
AIR_TX_COMP_MODE_1000M_TM1,
AIR_TX_COMP_MODE_1000M_TM2,
AIR_TX_COMP_MODE_1000M_TM3,
AIR_TX_COMP_MODE_1000M_TM4_TD,
AIR_TX_COMP_MODE_1000M_TM4_CM_PAIR_A,
AIR_TX_COMP_MODE_1000M_TM4_CM_PAIR_B,
AIR_TX_COMP_MODE_1000M_TM4_CM_PAIR_C,
AIR_TX_COMP_MODE_1000M_TM4_CM_PAIR_D,
AIR_TX_COMP_MODE_2500M_TM1,
AIR_TX_COMP_MODE_2500M_TM2,
AIR_TX_COMP_MODE_2500M_TM3,
AIR_TX_COMP_MODE_2500M_TM4_TONE_1,
AIR_TX_COMP_MODE_2500M_TM4_TONE_2,
AIR_TX_COMP_MODE_2500M_TM4_TONE_3,
AIR_TX_COMP_MODE_2500M_TM4_TONE_4,
AIR_TX_COMP_MODE_2500M_TM4_TONE_5,
AIR_TX_COMP_MODE_2500M_TM5,
AIR_TX_COMP_MODE_2500M_TM6,
AIR_TX_COMP_MODE_LAST = 0xFF,
};
struct trrg_param_s {
unsigned int TrRG_LSB :5;
unsigned int Reserved_21 :3;
unsigned int TrRG_MSB :5;
unsigned int Reserved_29 :3;
unsigned int Reserved_0 :1;
unsigned int DATA_ADDR :6;
unsigned int NODE_ADDR :4;
unsigned int CH_ADDR :2;
unsigned int WR_RD_CTRL :1;
unsigned int Reserved_14 :1;
unsigned int PKT_XMT_STA :1;
};
union trrgdesc_s {
struct trrg_param_s param;
unsigned short Raw[2];
unsigned int DescVal;
};
struct trrg_s {
union trrgdesc_s TrRGDesc;
unsigned int RgMask;
};
struct hal_tr_data_s {
unsigned short data_lo;
unsigned char data_hi;
};
#ifndef unlikely
# define unlikely(x) (x)
#endif
int air_pbus_reg_write(struct phy_device *phydev,
unsigned int pbus_address, unsigned int pbus_data);
int air_mii_cl22_write(struct mii_bus *ebus, int addr,
unsigned int phy_register, unsigned int write_data);
int air_mii_cl22_read(struct mii_bus *ebus,
int addr, unsigned int phy_register);
int __air_mii_cl45_read(struct phy_device *phydev, int devad, u16 reg);
int __air_mii_cl45_write(struct phy_device *phydev,
int devad, u16 reg, u16 write_data);
int air_mii_cl45_read(struct phy_device *phydev, int devad, u16 reg);
int air_mii_cl45_write(struct phy_device *phydev,
int devad, u16 reg, u16 write_data);
unsigned int air_buckpbus_reg_read(struct phy_device *phydev,
unsigned int pbus_address);
int air_buckpbus_reg_write(struct phy_device *phydev,
unsigned int pbus_address, unsigned int pbus_data);
int en8811h_of_init(struct phy_device *phydev);
int air_surge_protect_cfg(struct phy_device *phydev);
int air_ref_clk_speed(struct phy_device *phydev, int para);
int air_cko_cfg(struct phy_device *phydev);
int airoha_control_flag(struct phy_device *phydev, int mask, int val);
#ifdef CONFIG_AIROHA_EN8811H_PHY_DEBUGFS
int airphy_debugfs_init(struct phy_device *phydev);
void airphy_debugfs_remove(struct phy_device *phydev);
#endif /*CONFIG_AIROHA_EN8811H_PHY_DEBUGFS*/
#endif /* End of __EN8811H_API_H */

View File

@@ -0,0 +1,967 @@
// SPDX-License-Identifier: GPL-2.0
/*************************************************
* FILE NAME: air_en8811h_main.c
* PURPOSE:
* EN8811H PHY Driver for Linux
* NOTES:
*
* Copyright (C) 2023 Airoha Technology Corp.
*************************************************/
/* INCLUDE FILE DECLARATIONS
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mii.h>
#include <linux/phy.h>
#include <linux/delay.h>
#include <linux/ethtool.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/crc32.h>
#include <linux/debugfs.h>
#include "air_en8811h_api.h"
#include "air_en8811h.h"
MODULE_DESCRIPTION("Airoha EN8811H PHY Drivers");
MODULE_AUTHOR("Airoha");
MODULE_LICENSE("GPL");
/**************************
* GPIO5 <-> BASE_T_LED0,
* GPIO4 <-> BASE_T_LED1,
* GPIO3 <-> BASE_T_LED2,
**************************/
/* User-defined.B */
/* #define AIR_MD32_FW_CHECK */
/* #define AIR_IVY_LOAD */
#ifdef AIR_IVY_LOAD
/* #define AIR_IVY_CHECK */
#endif
#define AIR_LED_SUPPORT
#ifdef AIR_LED_SUPPORT
static const struct air_base_t_led_cfg led_cfg[3] = {
/********************************************************************
*Enable, GPIO, LED Polarity, LED ON, LED Blink
*********************************************************************/
{1, AIR_LED0_GPIO5, AIR_ACTIVE_HIGH, AIR_LED0_ON, AIR_LED0_BLK},
{1, AIR_LED1_GPIO4, AIR_ACTIVE_HIGH, AIR_LED1_ON, AIR_LED1_BLK},
{1, AIR_LED2_GPIO3, AIR_ACTIVE_HIGH, AIR_LED2_ON, AIR_LED2_BLK},
};
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
***********************************************************/
#ifdef AIR_MD32_FW_CHECK
static void air_mdio_read_buf(struct phy_device *phydev, unsigned long address,
const struct firmware *fw, unsigned int *crc32)
{
unsigned int offset;
int ret = 0;
unsigned int pbus_data_low, pbus_data_high;
struct device *dev = phydev_dev(phydev);
struct mii_bus *mbus = phydev_mdio_bus(phydev);
int addr = phydev_addr(phydev);
char *buf = kmalloc(fw->size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
memset(buf, '\0', fw->size);
/* page 4 */
ret |= air_mii_cl22_write(mbus, addr, 0x1F, 4);
/* address increment*/
ret |= air_mii_cl22_write(mbus, addr, 0x10, 0x8000);
ret |= air_mii_cl22_write(mbus, addr,
0x15, (unsigned int)((address >> 16) & 0xffff));
ret |= air_mii_cl22_write(mbus, addr,
0x16, (unsigned int)(address & 0xffff));
for (offset = 0; offset < fw->size; offset += 4) {
pbus_data_high = air_mii_cl22_read(mbus, addr, 0x17);
pbus_data_low = air_mii_cl22_read(mbus, addr, 0x18);
buf[offset + 0] = pbus_data_low & 0xff;
buf[offset + 1] = (pbus_data_low & 0xff00) >> 8;
buf[offset + 2] = pbus_data_high & 0xff;
buf[offset + 3] = (pbus_data_high & 0xff00) >> 8;
}
msleep(100);
*crc32 = ~crc32(~0, buf, fw->size);
ret |= air_mii_cl22_write(mbus, addr, 0x1F, 0);
kfree(buf);
if (ret) {
dev_info(dev, "%s 0x%lx FAIL(ret:%d)\n",
__func__, address, ret);
}
}
#endif
static int air_mdio_write_buf(struct phy_device *phydev,
unsigned long address, const struct firmware *fw)
{
unsigned int write_data, offset;
int ret = 0;
struct device *dev = phydev_dev(phydev);
struct mii_bus *mbus = phydev_mdio_bus(phydev);
int addr = phydev_addr(phydev);
/* page 4 */
ret = air_mii_cl22_write(mbus, addr, 0x1F, 4);
if (ret < 0) {
dev_err(dev, "air_mii_cl22_write, ret: %d\n", ret);
return ret;
}
/* address increment*/
ret = air_mii_cl22_write(mbus, addr, 0x10, 0x8000);
if (ret < 0) {
dev_err(dev, "air_mii_cl22_write, ret: %d\n", ret);
return ret;
}
ret = air_mii_cl22_write(mbus, addr, 0x11,
(u32)((address >> 16) & 0xffff));
if (ret < 0) {
dev_err(dev, "air_mii_cl22_write, ret: %d\n", ret);
return ret;
}
ret = air_mii_cl22_write(mbus, addr, 0x12, (u32)(address & 0xffff));
if (ret < 0) {
dev_err(dev, "air_mii_cl22_write, ret: %d\n", ret);
return ret;
}
for (offset = 0; offset < fw->size; offset += 4) {
write_data = (fw->data[offset + 3] << 8) | fw->data[offset + 2];
ret = air_mii_cl22_write(mbus, addr, 0x13, write_data);
if (ret < 0) {
dev_err(dev, "air_mii_cl22_write, ret: %d\n", ret);
return ret;
}
write_data = (fw->data[offset + 1] << 8) | fw->data[offset];
ret = air_mii_cl22_write(mbus, addr, 0x14, write_data);
if (ret < 0) {
dev_err(dev, "air_mii_cl22_write, ret: %d\n", ret);
return ret;
}
}
ret = air_mii_cl22_write(mbus, addr, 0x1F, 0);
if (ret < 0) {
dev_err(dev, "air_mii_cl22_write, ret: %d\n", ret);
return ret;
}
return 0;
}
#ifdef AIR_IVY_LOAD
static int modify_reg_bits(struct phy_device *phydev,
unsigned int reg, unsigned int mask, unsigned int set)
{
unsigned int write_data;
int ret;
write_data = air_buckpbus_reg_read(phydev, reg);
write_data &= ~mask;
write_data |= set;
ret = air_buckpbus_reg_write(phydev, reg, write_data);
if (ret < 0)
return ret;
return 0;
}
static int air_mdio_load_ivy(struct phy_device *phydev,
unsigned long address, const struct firmware *fw)
{
unsigned int write_data = 0, offset, read_data;
int ret = 0, retry;
#ifdef AIR_IVY_CHECK
int error = 0;
#endif
struct device *dev = phydev_dev(phydev);
ret = air_buckpbus_reg_write(phydev,
0xcf924, 0x12);
if (ret < 0)
return ret;
write_data = air_buckpbus_reg_read(phydev, 0xcfa28);
write_data |= BIT(0);
ret = air_buckpbus_reg_write(phydev,
0xcfa28, write_data);
write_data = air_buckpbus_reg_read(phydev, 0xcfa28);
if (ret < 0)
return ret;
msleep(100);
for (offset = 0; offset < fw->size; offset += 4) {
write_data = (fw->data[offset + 3] << 24) | (fw->data[offset + 2] << 16);
write_data |= ((fw->data[offset + 1] << 8) | fw->data[offset]);
ret = air_buckpbus_reg_write(phydev,
address, write_data);
#ifdef AIR_IVY_CHECK
read_data = air_buckpbus_reg_read(phydev, address);
if (write_data != read_data) {
dev_info(dev, "%x: write_data(0x%x) != read_data(0x%x)\n",
address, write_data, read_data);
error++;
}
#endif
address += 1;
}
#ifdef AIR_IVY_CHECK
if (error)
dev_err(dev, "Check ivy Fail(%d)\n", error);
else
dev_err(dev, "Check ivy Pass\n");
#endif
ret = modify_reg_bits(phydev, 0xCFA28, BIT(0), 0);
if (ret < 0)
return ret;
ret = modify_reg_bits(phydev, 0xCFA28, 0, BIT(16));
if (ret < 0)
return ret;
ret |= air_buckpbus_reg_write(phydev,
0xDC065, 0x80);
ret |= air_buckpbus_reg_write(phydev,
0xDC064, 0x0);
if (ret < 0)
return ret;
retry = 5;
do {
msleep(300);
ret = air_buckpbus_reg_write(phydev,
0xDC064, 0x0);
if (ret < 0)
return ret;
write_data = air_buckpbus_reg_read(phydev, 0xDC065);
if (write_data == 0x80)
break;
if (!retry)
dev_err(dev, "0xDC065 is not ready.(0x%x)\n", write_data);
} while (retry--);
ret = modify_reg_bits(phydev, 0xCFA28, BIT(16), 0);
if (ret < 0)
return ret;
ret = modify_reg_bits(phydev, 0xCFA28, 0, BIT(24));
if (ret < 0)
return ret;
retry = 10;
do {
msleep(300);
write_data = air_buckpbus_reg_read(phydev, 0xCFA38);
if (write_data == EN8811H_PHY_IVY_READY) {
dev_info(dev, "IVY ready!\n");
break;
}
if (!retry)
dev_err(dev, "IVY is not ready.(0x%x)\n", write_data);
} while (retry--);
return 0;
}
#endif
static int en8811h_load_firmware(struct phy_device *phydev)
{
struct device *dev = phydev_dev(phydev);
const struct firmware *fw;
const char *firmware;
int ret = 0;
u32 pbus_value = 0;
#ifdef AIR_MD32_FW_CHECK
unsigned int d_crc32 = 0, crc32 = 0;
int retry = 0;
#endif
struct en8811h_priv *priv = phydev->priv;
priv->init_stage = AIR_INIT_FW_LOADING;
#ifdef AIR_IVY_LOAD
firmware = EN8811H_IVY;
ret = request_firmware_direct(&fw, firmware, dev);
if (ret < 0) {
dev_err(dev,
"failed to load firmware %s, ret: %d\n", firmware, ret);
return ret;
}
priv->ivy_crc32 = ~crc32(~0, fw->data, fw->size);
dev_info(dev, "%s: crc32=0x%x\n",
firmware, ~crc32(~0, fw->data, fw->size));
/* Download ivy */
ret = air_mdio_load_ivy(phydev, 0xd4000, fw);
release_firmware(fw);
if (ret < 0) {
dev_err(dev,
"air_mdio_write_buf 0xd4000 fail, ret: %d\n", ret);
goto release;
}
#endif
ret = air_buckpbus_reg_write(phydev,
0x0f0018, 0x0);
if (ret < 0)
return ret;
pbus_value = air_buckpbus_reg_read(phydev, 0x800000);
pbus_value |= BIT(11);
ret = air_buckpbus_reg_write(phydev,
0x800000, pbus_value);
if (ret < 0)
return ret;
firmware = EN8811H_MD32_DM;
ret = request_firmware_direct(&fw, firmware, dev);
if (ret < 0) {
dev_err(dev,
"failed to load firmware %s, ret: %d\n", firmware, ret);
return ret;
}
priv->dm_crc32 = ~crc32(~0, fw->data, fw->size);
dev_info(dev, "%s: crc32=0x%x\n",
firmware, ~crc32(~0, fw->data, fw->size));
/* Download DM */
ret = air_mdio_write_buf(phydev, 0x00000000, fw);
release_firmware(fw);
if (ret < 0) {
dev_err(dev,
"air_mdio_write_buf 0x00000000 fail, ret: %d\n", ret);
goto release;
}
firmware = EN8811H_MD32_DSP;
ret = request_firmware_direct(&fw, firmware, dev);
if (ret < 0) {
dev_info(dev,
"failed to load firmware %s, ret: %d\n", firmware, ret);
return ret;
}
priv->dsp_crc32 = ~crc32(~0, fw->data, fw->size);
dev_info(dev, "%s: crc32=0x%x\n",
firmware, ~crc32(~0, fw->data, fw->size));
/* Download PM */
ret = air_mdio_write_buf(phydev, 0x00100000, fw);
if (ret < 0) {
dev_err(dev,
"air_mdio_write_buf 0x00100000 fail , ret: %d\n", ret);
goto release;
}
pbus_value = air_buckpbus_reg_read(phydev, 0x800000);
pbus_value &= ~BIT(11);
ret = air_buckpbus_reg_write(phydev, 0x800000, pbus_value);
if (ret < 0)
goto release;
#ifdef AIR_MD32_FW_CHECK
crc32 = ~crc32(~0, fw->data, fw->size);
/* Check PM */
air_mdio_read_buf(phydev, 0x100000, fw, &d_crc32);
if (d_crc32 == crc32)
dev_info(dev, "0x00100000 Check Sum Pass.\n");
else {
dev_info(dev, "0x00100000 Check Sum Fail.\n");
dev_info(dev, "CRC32 0x%x != Caculated CRC32 0x%x\n",
crc32, d_crc32);
}
release_firmware(fw);
retry = MAX_RETRY;
do {
ret = air_buckpbus_reg_write(phydev, 0x0f0018, 0x01);
if (ret < 0)
return ret;
msleep(100);
pbus_value = air_buckpbus_reg_read(phydev, 0x0f0018);
retry--;
if (retry == 0) {
dev_err(dev,
"Release Software Reset fail , ret: %d\n",
pbus_value);
goto release;
}
} while (pbus_value != 0x1);
dev_info(dev,
"Release Software Reset successful.\n");
#else
release_firmware(fw);
ret = air_buckpbus_reg_write(phydev, 0x0f0018, 0x01);
if (ret < 0)
return ret;
#endif
return 0;
release:
release_firmware(fw);
return ret;
}
static int en8811h_init_up(struct phy_device *phydev)
{
int ret = 0, retry, reg_value;
struct device *dev = phydev_dev(phydev);
unsigned int pbus_value;
struct en8811h_priv *priv = phydev->priv;
dev_info(dev, "%s start\n", __func__);
ret = en8811h_load_firmware(phydev);
if (ret < 0) {
dev_err(dev, "EN8811H load firmware fail.\n");
return ret;
}
retry = MAX_RETRY;
do {
mdelay(300);
reg_value = air_mii_cl45_read(phydev, 0x1e, 0x8009);
if (reg_value == EN8811H_PHY_READY) {
dev_info(dev, "EN8811H PHY ready!\n");
priv->init_stage = AIR_INIT_FW_READY;
break;
}
if (retry == 0) {
dev_err(dev, "MD32 FW is not ready.(Status 0x%x)\n", reg_value);
pbus_value = air_buckpbus_reg_read(phydev, 0x3b3c);
dev_err(dev,
"Check MD32 FW Version(0x3b3c) : %08x\n", pbus_value);
dev_err(dev,
"%s fail!\n", __func__);
priv->init_stage = AIR_INIT_FW_FAIL;
}
} while (retry--);
return 0;
}
#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 (polar == AIR_ACTIVE_HIGH)
on_evt |= LED_ON_POL;
else
on_evt &= ~LED_ON_POL;
ret = air_mii_cl45_write(phydev, 0x1f,
LED_ON_CTRL(entity), on_evt | LED_ON_EN);
if (ret < 0)
return ret;
ret = air_mii_cl45_write(phydev, 0x1f, LED_BLK_CTRL(entity), blk_evt);
if (ret < 0)
return ret;
return 0;
}
static int airoha_led_set_mode(struct phy_device *phydev, u8 mode)
{
u16 cl45_data;
int err = 0;
struct device *dev = phydev_dev(phydev);
cl45_data = air_mii_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:
dev_err(dev, "LED mode%d is not supported!\n", mode);
return -EINVAL;
}
err = air_mii_cl45_write(phydev, 0x1f, LED_BCR, cl45_data);
if (err < 0)
return err;
return 0;
}
static int airoha_led_set_state(struct phy_device *phydev, u8 entity, u8 state)
{
u16 cl45_data = 0;
int err;
cl45_data = air_mii_cl45_read(phydev, 0x1f, LED_ON_CTRL(entity));
if (state == 1)
cl45_data |= LED_ON_EN;
else
cl45_data &= ~LED_ON_EN;
err = air_mii_cl45_write(phydev, 0x1f, LED_ON_CTRL(entity), cl45_data);
if (err < 0)
return err;
return 0;
}
static int en8811h_led_init(struct phy_device *phydev)
{
unsigned long led_gpio = 0, reg_value = 0;
u16 cl45_data = led_dur;
int ret = 0, id;
struct device *dev = phydev_dev(phydev);
struct en8811h_priv *priv = phydev->priv;
ret = air_mii_cl45_write(phydev, 0x1f, LED_BLK_DUR, cl45_data);
if (ret < 0)
return ret;
cl45_data >>= 1;
ret = air_mii_cl45_write(phydev, 0x1f, LED_ON_DUR, cl45_data);
if (ret < 0)
return ret;
ret = airoha_led_set_mode(phydev, AIR_LED_MODE_USER_DEFINE);
if (ret != 0) {
dev_err(dev, "led_set_mode fail(ret:%d)!\n", ret);
return ret;
}
for (id = 0; id < EN8811H_LED_COUNT; id++) {
/* LED0 <-> GPIO5, LED1 <-> GPIO4, LED0 <-> GPIO3 */
if (led_cfg[id].gpio != (id + (AIR_LED0_GPIO5 - (2 * id)))) {
dev_err(dev, "LED%d uses incorrect GPIO%d !\n",
id, led_cfg[id].gpio);
return -EINVAL;
}
ret = airoha_led_set_state(phydev, id, led_cfg[id].en);
if (ret != 0) {
dev_err(dev, "led_set_state fail(ret:%d)!\n", ret);
return ret;
}
if (led_cfg[id].en == 1) {
led_gpio |= BIT(led_cfg[id].gpio);
ret = airoha_led_set_usr_def(phydev, id,
led_cfg[id].pol, led_cfg[id].on_cfg,
led_cfg[id].blk_cfg);
if (ret != 0) {
dev_err(dev, "led_set_usr_def fail!\n");
return ret;
}
}
priv->on_crtl[id] = air_mii_cl45_read(phydev, 0x1f,
LED_ON_CTRL(id));
priv->blk_crtl[id] = air_mii_cl45_read(phydev, 0x1f,
LED_BLK_CTRL(id));
}
reg_value = air_buckpbus_reg_read(phydev, 0xcf8b8) | led_gpio;
ret = air_buckpbus_reg_write(phydev, 0xcf8b8, reg_value);
if (ret < 0)
return ret;
dev_info(dev, "LED initialize OK !\n");
return 0;
}
#endif /* AIR_LED_SUPPORT */
#if (KERNEL_VERSION(4, 5, 0) < LINUX_VERSION_CODE)
static int en8811h_get_features(struct phy_device *phydev)
{
int ret;
struct device *dev = phydev_dev(phydev);
dev_dbg(dev, "%s()\n", __func__);
ret = air_pbus_reg_write(phydev, 0xcf928, 0x0);
if (ret < 0)
return ret;
ret = genphy_read_abilities(phydev);
if (ret)
return ret;
/* EN8811H supports 100M/1G/2.5G speed. */
linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
phydev->supported);
linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
phydev->supported);
linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
phydev->supported);
linkmode_clear_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
phydev->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
phydev->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
phydev->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
phydev->supported);
return 0;
}
#endif
static int en8811h_probe(struct phy_device *phydev)
{
int ret = 0;
int pid1 = 0, pid2 = 0;
u32 pbus_value = 0;
struct device *dev = phydev_dev(phydev);
struct mii_bus *mbus = phydev_mdio_bus(phydev);
int addr = phydev_addr(phydev);
struct en8811h_priv *priv;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
phydev->priv = priv;
ret = air_pbus_reg_write(phydev, 0xcf928, 0x0);
if (ret < 0)
goto priv_free;
pid1 = air_mii_cl22_read(mbus, addr, MII_PHYSID1);
pid2 = air_mii_cl22_read(mbus, addr, MII_PHYSID2);
dev_info(dev, "PHY = %x - %x\n", pid1, pid2);
if ((pid1 != EN8811H_PHY_ID1) || (pid2 != EN8811H_PHY_ID2)) {
dev_err(dev, "EN8811H dose not exist!!\n");
goto priv_free;
}
priv->init_stage = AIR_INIT_START;
ret = air_buckpbus_reg_write(phydev, 0x1e00d0, 0xf);
ret |= air_buckpbus_reg_write(phydev, 0x1e0228, 0xf0);
if (ret < 0)
goto priv_free;
priv->mcu_needs_restart = false;
pbus_value = air_buckpbus_reg_read(phydev, 0xcf914);
dev_info(dev, "Bootmode: %s\n",
(GET_BIT(pbus_value, 24) ? "Flash" : "Download Code"));
ret = en8811h_of_init(phydev);
if (ret < 0)
goto priv_free;
#ifdef CONFIG_AIROHA_EN8811H_PHY_DEBUGFS
ret = airphy_debugfs_init(phydev);
if (ret < 0) {
dev_err(dev, "air_debug_procfs_init fail. (ret=%d)\n", ret);
airphy_debugfs_remove(phydev);
goto priv_free;
}
#endif /* CONFIG_AIROHA_EN8811H_PHY_DEBUGFS */
if (priv->phy_handle) {
dev_info(dev, "EN8811H Probe OK! (%s)\n", EN8811H_DRIVER_VERSION);
} else {
ret = en8811h_init_up(phydev);
if (ret < 0)
goto priv_free;
priv->init_stage = AIR_INIT_CONFIG;
ret = air_mii_cl45_write(phydev, 0x1e, 0x800c, 0x0);
ret |= air_mii_cl45_write(phydev, 0x1e, 0x800d, 0x0);
ret |= air_mii_cl45_write(phydev, 0x1e, 0x800e, 0x1101);
ret |= air_mii_cl45_write(phydev, 0x1e, 0x800f, 0x0002);
if (ret < 0)
goto priv_free;
pbus_value = air_buckpbus_reg_read(phydev, 0xca0f8);
pbus_value &= ~0x3;
#if defined(CONFIG_OF)
pbus_value |= priv->pol;
#else
pbus_value |= (EN8811H_RX_POL_NORMAL | EN8811H_TX_POL_NORMAL);
#endif
ret = air_buckpbus_reg_write(phydev, 0xca0f8, pbus_value);
if (ret < 0)
goto priv_free;
pbus_value = air_buckpbus_reg_read(phydev, 0xca0f8);
dev_info(dev, "Tx, Rx Polarity : %08x\n", pbus_value);
priv->firmware_version = air_buckpbus_reg_read(phydev, 0x3b3c);
dev_info(dev, "MD32 FW Version : %08x\n", priv->firmware_version);
ret = air_surge_protect_cfg(phydev);
if (ret < 0) {
dev_err(dev,
"air_surge_protect_cfg fail. (ret=%d)\n", ret);
goto priv_free;
}
ret = air_cko_cfg(phydev);
if (ret < 0) {
dev_err(dev,
"air_cko_cfg fail. (ret=%d)\n", ret);
goto priv_free;
}
#if defined(AIR_LED_SUPPORT)
ret = en8811h_led_init(phydev);
if (ret < 0) {
dev_err(dev, "en8811h_led_init fail. (ret=%d)\n", ret);
goto priv_free;
}
#endif
priv->init_stage = AIR_INIT_SUCESS;
dev_info(dev, "EN8811H initialize OK! (%s)\n", EN8811H_DRIVER_VERSION);
}
return 0;
priv_free:
kfree(priv);
return ret;
}
void en8811h_remove(struct phy_device *phydev)
{
struct en8811h_priv *priv = phydev->priv;
struct device *dev = phydev_dev(phydev);
dev_dbg(dev, "%s: start\n", __func__);
if (priv) {
dev_info(dev, "%s: airphy_debugfs_remove\n", __func__);
#ifdef CONFIG_AIROHA_EN8811H_PHY_DEBUGFS
airphy_debugfs_remove(phydev);
#endif /*CONFIG_AIROHA_EN8811H_PHY_DEBUGFS*/
kfree(priv);
}
}
static int en8811h_restart_up(struct phy_device *phydev)
{
int ret, retry, reg_value;
u32 pbus_value;
struct device *dev = phydev_dev(phydev);
struct en8811h_priv *priv = phydev->priv;
dev_info(dev, "%s start\n", __func__);
ret = air_mii_cl45_write(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;
ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
EN8811H_FW_CTRL_1_FINISH);
if (ret < 0)
return ret;
retry = MAX_RETRY;
do {
mdelay(300);
reg_value = air_mii_cl45_read(phydev, 0x1e, 0x8009);
if (reg_value == EN8811H_PHY_READY) {
priv->init_stage = AIR_INIT_FW_READY;
dev_info(dev, "EN8811H PHY ready!\n");
break;
}
if (retry == 0) {
dev_err(dev, "MD32 FW is not ready.(Status 0x%x)\n", reg_value);
pbus_value = air_buckpbus_reg_read(phydev, 0x3b3c);
dev_err(dev,
"Check MD32 FW Version(0x3b3c) : %08x\n", pbus_value);
dev_err(dev,
"%s fail!\n", __func__);
priv->init_stage = AIR_INIT_FW_FAIL;
}
} while (retry--);
return 0;
}
static int en8811h_config_init(struct phy_device *phydev)
{
int ret = 0;
u32 pbus_value = 0;
struct device *dev = phydev_dev(phydev);
struct en8811h_priv *priv = phydev->priv;
ret = air_buckpbus_reg_write(phydev, 0x1e00d0, 0xf);
ret |= air_buckpbus_reg_write(phydev, 0x1e0228, 0xf0);
if (ret < 0)
return ret;
/* If restart happened in .probe(), no need to restart now */
if (priv->mcu_needs_restart) {
ret = en8811h_restart_up(phydev);
if (ret < 0)
goto priv_free;
} else {
ret = en8811h_init_up(phydev);
if (ret < 0)
goto priv_free;
/* Next calls to .config_init() mcu needs to restart */
priv->mcu_needs_restart = true;
}
priv->init_stage = AIR_INIT_CONFIG;
ret = air_mii_cl45_write(phydev, 0x1e, 0x800c, 0x0);
ret |= air_mii_cl45_write(phydev, 0x1e, 0x800d, 0x0);
ret |= air_mii_cl45_write(phydev, 0x1e, 0x800e, 0x1101);
ret |= air_mii_cl45_write(phydev, 0x1e, 0x800f, 0x0002);
if (ret < 0)
goto priv_free;
/* Serdes polarity */
pbus_value = air_buckpbus_reg_read(phydev, 0xca0f8);
pbus_value &= ~0x3;
#if defined(CONFIG_OF)
pbus_value |= priv->pol;
#else
pbus_value |= (EN8811H_RX_POL_NORMAL | EN8811H_TX_POL_NORMAL);
#endif
ret = air_buckpbus_reg_write(phydev, 0xca0f8, pbus_value);
if (ret < 0)
goto priv_free;
pbus_value = air_buckpbus_reg_read(phydev, 0xca0f8);
dev_info(dev, "Tx, Rx Polarity : %08x\n", pbus_value);
priv->firmware_version = air_buckpbus_reg_read(phydev, 0x3b3c);
dev_info(dev, "MD32 FW Version : %08x\n", priv->firmware_version);
ret = air_surge_protect_cfg(phydev);
if (ret < 0) {
dev_err(dev,
"air_surge_protect_cfg fail. (ret=%d)\n", ret);
goto priv_free;
}
ret = air_cko_cfg(phydev);
if (ret < 0) {
dev_err(dev,
"air_cko_cfg fail. (ret=%d)\n", ret);
goto priv_free;
}
#if defined(AIR_LED_SUPPORT)
ret = en8811h_led_init(phydev);
if (ret < 0) {
dev_err(dev, "en8811h_led_init fail. (ret=%d)\n", ret);
goto priv_free;
}
#endif
priv->init_stage = AIR_INIT_SUCESS;
dev_info(dev, "EN8811H initialize OK! (%s)\n", EN8811H_DRIVER_VERSION);
return 0;
priv_free:
kfree(priv);
return ret;
}
static int en8811h_get_rate_matching(struct phy_device *phydev,
phy_interface_t iface)
{
return RATE_MATCH_PAUSE;
}
static int en8811h_config_aneg(struct phy_device *phydev)
{
bool changed = false;
int err, val;
val = 0;
if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
phydev->advertising))
val |= MDIO_AN_10GBT_CTRL_ADV2_5G;
err = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
MDIO_AN_10GBT_CTRL_ADV2_5G, val);
if (err < 0)
return err;
if (err > 0)
changed = true;
return __genphy_config_aneg(phydev, changed);
}
static int en8811h_update_link(struct phy_device *phydev)
{
int status = 0, bmcr;
struct mii_bus *mbus = phydev_mdio_bus(phydev);
int addr = phydev_addr(phydev);
bmcr = air_mii_cl22_read(mbus, addr, MII_BMCR);
if (bmcr < 0)
return bmcr;
/* Autoneg is being started, therefore disregard BMSR value and
* report link as down.
*/
if (bmcr & BMCR_ANRESTART)
goto done;
status = air_mii_cl22_read(mbus, addr, MII_BMSR);
if (status < 0)
return status;
done:
phydev->link = status & BMSR_LSTATUS ? 1 : 0;
phydev->autoneg_complete = status & BMSR_ANEGCOMPLETE ? 1 : 0;
return 0;
}
static int en8811h_read_status(struct phy_device *phydev)
{
struct en8811h_priv *priv = phydev->priv;
u32 pbus_value;
int old_link = phydev->link, ret;
ret = en8811h_update_link(phydev);
if (ret)
return ret;
/* why bother the PHY if nothing can have changed */
if (old_link && phydev->link)
return 0;
phydev->speed = SPEED_UNKNOWN;
phydev->duplex = DUPLEX_UNKNOWN;
phydev->pause = 0;
phydev->asym_pause = 0;
phydev->rate_matching = RATE_MATCH_PAUSE;
ret = genphy_read_lpa(phydev);
if (ret < 0)
return ret;
/* Get link partner 2.5GBASE-T ability from vendor register */
pbus_value = air_buckpbus_reg_read(phydev, EN8811H_2P5G_LPA);
if (ret < 0)
return ret;
linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
phydev->lp_advertising,
pbus_value & EN8811H_2P5G_LPA_2P5G);
phydev->duplex = DUPLEX_FULL;
if (phydev->autoneg_complete)
phy_resolve_aneg_pause(phydev);
if (!phydev->link)
return 0;
ret = air_ref_clk_speed(phydev, AIR_PARA_PHYDEV);
if (ret < 0)
return ret;
/* Firmware before version 24011202 has no vendor register 2P5G_LPA.
* Assume link partner advertised it if connected at 2500Mbps.
*/
if (priv->firmware_version < 0x24011202) {
linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
phydev->lp_advertising,
phydev->speed == SPEED_2500);
}
if (phydev->speed <= SPEED_1000)
phydev->pause = 1;
return 0;
}
static struct phy_driver en8811h_driver[] = {
{
.phy_id = EN8811H_PHY_ID,
.name = "Airoha EN8811H",
.phy_id_mask = 0x0ffffff0,
.probe = en8811h_probe,
.remove = en8811h_remove,
#if (KERNEL_VERSION(4, 5, 0) < LINUX_VERSION_CODE)
.get_features = en8811h_get_features,
.read_mmd = __air_mii_cl45_read,
.write_mmd = __air_mii_cl45_write,
#endif
.config_init = en8811h_config_init,
.read_status = en8811h_read_status,
.get_rate_matching = en8811h_get_rate_matching,
.config_aneg = en8811h_config_aneg,
.resume = genphy_resume,
.suspend = genphy_suspend,
} };
int __init en8811h_phy_driver_register(void)
{
int ret;
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
ret = phy_driver_register(en8811h_driver);
#else
ret = phy_driver_register(en8811h_driver, THIS_MODULE);
#endif
if (!ret)
return 0;
phy_driver_unregister(en8811h_driver);
return ret;
}
void __exit en8811h_phy_driver_unregister(void)
{
phy_driver_unregister(en8811h_driver);
}
module_init(en8811h_phy_driver_register);
module_exit(en8811h_phy_driver_unregister);

View File

@@ -0,0 +1,44 @@
From b5aa08e6dd4e06ffd3fb4de2ffc6af1e3da0bce4 Mon Sep 17 00:00:00 2001
From: Sam Shih <sam.shih@mediatek.com>
Date: Fri, 2 Jun 2023 13:06:00 +0800
Subject: [PATCH]
[backport-networking-drivers][999-1703-en8811h-2p5gphy-support.patch]
---
drivers/net/phy/Kconfig | 11 +
drivers/net/phy/Makefile | 2 +
2 files changed, 13 insertions(+)
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 170dd00cd..5eeccfee2 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -345,6 +345,17 @@ config SFP
depends on HWMON || HWMON=n
select MDIO_I2C
+config AIROHA_EN8811H_PHY
+ tristate "Drivers for Airoha EN8811H 2.5G Gigabit PHY"
+ ---help---
+ Currently supports the Airoha EN8811H PHY.
+
+config AIROHA_EN8811H_PHY_DEBUGFS
+ bool "EN8811H debugfs support"
+ depends on AIROHA_EN8811H_PHY
+ ---help---
+ Enable creation of debugfs files for the EN8811H drivers.
+
config ADIN_PHY
tristate "Analog Devices Industrial Ethernet PHYs"
help
diff -urN a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
--- a/drivers/net/phy/Makefile 2022-03-26 16:28:57.619384439 +0800
+++ b/drivers/net/phy/Makefile 2022-03-26 16:31:40.893705848 +0800
@@ -111,6 +111,8 @@
obj-$(CONFIG_TERANETICS_PHY) += teranetics.o
obj-$(CONFIG_VITESSE_PHY) += vitesse.o
obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
+air_en8811h-y := air_en8811h_main.o air_en8811h_api.o
+obj-$(CONFIG_AIROHA_EN8811H_PHY) += air_en8811h.o
obj-$(CONFIG_MT753X_GSW) += mtk/mt753x/
obj-$(CONFIG_RTL8367S_GSW) += rtk/

View File

@@ -1,892 +0,0 @@
diff -urN a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c
--- a/drivers/net/phy/air_en8811h.c 1970-01-01 08:00:00.000000000 +0800
+++ b/drivers/net/phy/air_en8811h.c 2023-06-14 14:02:38.474875084 +0800
@@ -0,0 +1,706 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/* FILE NAME: air_en8811h.c
+ * PURPOSE:
+ * EN8811H phy driver for Linux
+ * NOTES:
+ *
+ */
+
+/* INCLUDE FILE DECLARATIONS
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/delay.h>
+#include <linux/ethtool.h>
+#include <linux/delay.h>
+#include <linux/version.h>
+#include <linux/firmware.h>
+#include <linux/crc32.h>
+
+#include "air_en8811h.h"
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))
+#define phydev_mdio_bus(_dev) (_dev->bus)
+#define phydev_addr(_dev) (_dev->addr)
+#define phydev_dev(_dev) (&_dev->dev)
+#else
+#define phydev_mdio_bus(_dev) (_dev->mdio.bus)
+#define phydev_addr(_dev) (_dev->mdio.addr)
+#define phydev_dev(_dev) (&_dev->mdio.dev)
+#endif
+
+MODULE_DESCRIPTION("Airoha EN8811H PHY drivers");
+MODULE_AUTHOR("Airoha");
+MODULE_LICENSE("GPL");
+
+/*
+GPIO5 <-> BASE_T_LED0,
+GPIO4 <-> BASE_T_LED1,
+GPIO3 <-> BASE_T_LED2,
+*/
+/* User-defined.B */
+#define AIR_LED_SUPPORT
+#ifdef AIR_LED_SUPPORT
+static const AIR_BASE_T_LED_CFG_T led_cfg[3] =
+{
+ /*
+ * LED Enable, GPIO, LED Polarity, LED ON, LED Blink
+ */
+ {LED_ENABLE, AIR_LED0_GPIO5, AIR_ACTIVE_HIGH, BASE_T_LED0_ON_CFG, BASE_T_LED0_BLK_CFG}, /* BASE-T LED0 */
+ {LED_ENABLE, AIR_LED1_GPIO4, AIR_ACTIVE_HIGH, BASE_T_LED1_ON_CFG, BASE_T_LED1_BLK_CFG}, /* BASE-T LED1 */
+ {LED_ENABLE, AIR_LED2_GPIO3, AIR_ACTIVE_HIGH, BASE_T_LED2_ON_CFG, BASE_T_LED2_BLK_CFG}, /* BASE-T LED2 */
+};
+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
+************************************************************************/
+#if 0
+/* Airoha MII read function */
+static int air_mii_cl22_read(struct mii_bus *ebus, unsigned int phy_addr,unsigned int phy_register)
+{
+ int read_data;
+ read_data = mdiobus_read(ebus, phy_addr, phy_register);
+ return read_data;
+}
+#endif
+/* Airoha MII write function */
+static int air_mii_cl22_write(struct mii_bus *ebus, unsigned int phy_addr, unsigned int phy_register,unsigned int write_data)
+{
+ int ret = 0;
+ ret = mdiobus_write(ebus, phy_addr, phy_register, write_data);
+ return ret;
+}
+
+static int air_mii_cl45_read(struct phy_device *phydev, int devad, u16 reg)
+{
+ int ret = 0;
+ int data;
+ struct device *dev = phydev_dev(phydev);
+ ret = phy_write(phydev, MII_MMD_ACC_CTL_REG, devad);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return INVALID_DATA;
+ }
+ ret = phy_write(phydev, MII_MMD_ADDR_DATA_REG, reg);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return INVALID_DATA;
+ }
+ ret = phy_write(phydev, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return INVALID_DATA;
+ }
+ data = phy_read(phydev, MII_MMD_ADDR_DATA_REG);
+ return data;
+}
+
+static int air_mii_cl45_write(struct phy_device *phydev, int devad, u16 reg, u16 write_data)
+{
+ int ret = 0;
+ struct device *dev = phydev_dev(phydev);
+ ret = phy_write(phydev, MII_MMD_ACC_CTL_REG, devad);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, MII_MMD_ADDR_DATA_REG, reg);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, MII_MMD_ADDR_DATA_REG, write_data);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+/* Use default PBUS_PHY_ID */
+/* EN8811H PBUS write function */
+static int air_pbus_reg_write(struct phy_device *phydev, unsigned long pbus_address, unsigned long pbus_data)
+{
+ struct mii_bus *mbus = phydev_mdio_bus(phydev);
+ int addr = phydev_addr(phydev);
+ int ret = 0;
+ ret = air_mii_cl22_write(mbus, (addr + 8), 0x1F, (unsigned int)(pbus_address >> 6));
+ AIR_RTN_ERR(ret);
+ ret = air_mii_cl22_write(mbus, (addr + 8), (unsigned int)((pbus_address >> 2) & 0xf), (unsigned int)(pbus_data & 0xFFFF));
+ AIR_RTN_ERR(ret);
+ ret = air_mii_cl22_write(mbus, (addr + 8), 0x10, (unsigned int)(pbus_data >> 16));
+ AIR_RTN_ERR(ret);
+ return 0;
+}
+
+/* EN8811H BUCK write function */
+static int air_buckpbus_reg_write(struct phy_device *phydev, unsigned long pbus_address, unsigned int pbus_data)
+{
+ int ret = 0;
+ struct device *dev = phydev_dev(phydev);
+ ret = phy_write(phydev, 0x1F, (unsigned int)4); /* page 4 */
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, 0x10, (unsigned int)0);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, 0x11, (unsigned int)((pbus_address >> 16) & 0xffff));
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, 0x12, (unsigned int)(pbus_address & 0xffff));
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, 0x13, (unsigned int)((pbus_data >> 16) & 0xffff));
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, 0x14, (unsigned int)(pbus_data & 0xffff));
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, 0x1F, 0);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+/* EN8811H BUCK read function */
+static unsigned int air_buckpbus_reg_read(struct phy_device *phydev, unsigned long pbus_address)
+{
+ unsigned int pbus_data = 0, pbus_data_low, pbus_data_high;
+ int ret = 0;
+ struct device *dev = phydev_dev(phydev);
+ ret = phy_write(phydev, 0x1F, (unsigned int)4); /* page 4 */
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return PBUS_INVALID_DATA;
+ }
+ ret = phy_write(phydev, 0x10, (unsigned int)0);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return PBUS_INVALID_DATA;
+ }
+ ret = phy_write(phydev, 0x15, (unsigned int)((pbus_address >> 16) & 0xffff));
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return PBUS_INVALID_DATA;
+ }
+ ret = phy_write(phydev, 0x16, (unsigned int)(pbus_address & 0xffff));
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return PBUS_INVALID_DATA;
+ }
+
+ pbus_data_high = phy_read(phydev, 0x17);
+ pbus_data_low = phy_read(phydev, 0x18);
+ pbus_data = (pbus_data_high << 16) + pbus_data_low;
+ ret = phy_write(phydev, 0x1F, 0);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ return pbus_data;
+}
+
+static int MDIOWriteBuf(struct phy_device *phydev, unsigned long address, const struct firmware *fw)
+{
+ unsigned int write_data, offset ;
+ int ret = 0;
+ struct device *dev = phydev_dev(phydev);
+ ret = phy_write(phydev, 0x1F, (unsigned int)4); /* page 4 */
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, 0x10, (unsigned int)0x8000); /* address increment*/
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, 0x11, (unsigned int)((address >> 16) & 0xffff));
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ ret = phy_write(phydev, 0x12, (unsigned int)(address & 0xffff));
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+
+ for (offset = 0; offset < fw->size; offset += 4)
+ {
+ write_data = (fw->data[offset + 3] << 8) | fw->data[offset + 2];
+ ret = phy_write(phydev, 0x13, write_data);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ write_data = (fw->data[offset + 1] << 8) | fw->data[offset];
+ ret = phy_write(phydev, 0x14, write_data);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ }
+ ret = phy_write(phydev, 0x1F, (unsigned int)0);
+ if (ret < 0) {
+ dev_err(dev, "phy_write, ret: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int en8811h_load_firmware(struct phy_device *phydev)
+{
+ struct device *dev = phydev_dev(phydev);
+ const struct firmware *fw;
+ const char *firmware;
+ int ret = 0;
+ unsigned int crc32;
+ u32 pbus_value = 0;
+
+ ret = air_buckpbus_reg_write(phydev, 0x0f0018, 0x0);
+ AIR_RTN_ERR(ret);
+ pbus_value = air_buckpbus_reg_read(phydev, 0x800000);
+ pbus_value |= BIT(11);
+ ret = air_buckpbus_reg_write(phydev, 0x800000, pbus_value);
+ AIR_RTN_ERR(ret);
+ firmware = EN8811H_MD32_DM;
+ ret = request_firmware_direct(&fw, firmware, dev);
+ if (ret < 0) {
+ dev_err(dev, "failed to load firmware %s, ret: %d\n", firmware, ret);
+ return ret;
+ }
+ crc32 = ~crc32(~0, fw->data, fw->size);
+ dev_info(dev, "%s: crc32=0x%x\n", firmware, crc32);
+ /* Download DM */
+ ret = MDIOWriteBuf(phydev, 0x00000000, fw);
+ release_firmware(fw);
+ if (ret < 0) {
+ dev_err(dev, "MDIOWriteBuf 0x00000000 fail, ret: %d\n", ret);
+ return ret;
+ }
+
+ firmware = EN8811H_MD32_DSP;
+ ret = request_firmware_direct(&fw, firmware, dev);
+ if (ret < 0) {
+ dev_info(dev, "failed to load firmware %s, ret: %d\n", firmware, ret);
+ return ret;
+ }
+ crc32 = ~crc32(~0, fw->data, fw->size);
+ dev_info(dev, "%s: crc32=0x%x\n", firmware, crc32);
+ /* Download PM */
+ ret = MDIOWriteBuf(phydev, 0x00100000, fw);
+ release_firmware(fw);
+ if (ret < 0) {
+ dev_err(dev, "MDIOWriteBuf 0x00100000 fail , ret: %d\n", ret);
+ return ret;
+ }
+
+ pbus_value = air_buckpbus_reg_read(phydev, 0x800000);
+ pbus_value &= ~BIT(11);
+ ret = air_buckpbus_reg_write(phydev, 0x800000, pbus_value);
+ AIR_RTN_ERR(ret);
+ ret = air_buckpbus_reg_write(phydev, 0x0f0018, 0x01);
+ AIR_RTN_ERR(ret);
+ return 0;
+}
+
+#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 = air_mii_cl45_write(phydev, 0x1f, LED_ON_CTRL(entity), on_evt | LED_ON_EN);
+ AIR_RTN_ERR(ret);
+ ret = air_mii_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;
+ struct device *dev = phydev_dev(phydev);
+ cl45_data = air_mii_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:
+ dev_err(dev, "LED mode%d is not supported!\n", mode);
+ return -EINVAL;
+ }
+ err = air_mii_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 = 0;
+ int err;
+
+ cl45_data = air_mii_cl45_read(phydev, 0x1f, LED_ON_CTRL(entity));
+ if (LED_ENABLE == state) {
+ cl45_data |= LED_ON_EN;
+ } else {
+ cl45_data &= ~LED_ON_EN;
+ }
+
+ err = air_mii_cl45_write(phydev, 0x1f, LED_ON_CTRL(entity), cl45_data);
+ AIR_RTN_ERR(err);
+ return 0;
+}
+
+static int en8811h_led_init(struct phy_device *phydev)
+{
+
+ unsigned long led_gpio = 0, reg_value = 0;
+ u16 cl45_data = led_dur;
+ int ret = 0, led_id;
+ struct device *dev = phydev_dev(phydev);
+ ret = air_mii_cl45_write(phydev, 0x1f, LED_BLK_DUR, cl45_data);
+ AIR_RTN_ERR(ret);
+ cl45_data >>= 1;
+ ret = air_mii_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) {
+ dev_err(dev, "LED fail to set mode, ret %d !\n", ret);
+ return ret;
+ }
+ for(led_id = 0; led_id < EN8811H_LED_COUNT; led_id++)
+ {
+ /* LED0 <-> GPIO5, LED1 <-> GPIO4, LED0 <-> GPIO3 */
+ if (led_cfg[led_id].gpio != (led_id + (AIR_LED0_GPIO5 - (2 * led_id))))
+ {
+ dev_err(dev, "LED%d uses incorrect GPIO%d !\n", led_id, led_cfg[led_id].gpio);
+ return -EINVAL;
+ }
+ ret = airoha_led_set_state(phydev, led_id, led_cfg[led_id].en);
+ if (ret != 0)
+ {
+ dev_err(dev, "LED fail to set state, ret %d !\n", ret);
+ return ret;
+ }
+ if (LED_ENABLE == led_cfg[led_id].en)
+ {
+ led_gpio |= BIT(led_cfg[led_id].gpio);
+ 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)
+ {
+ dev_err(dev, "LED fail to set default, ret %d !\n", ret);
+ return ret;
+ }
+ }
+ }
+ reg_value = air_buckpbus_reg_read(phydev, 0xcf8b8) | led_gpio;
+ ret = air_buckpbus_reg_write(phydev, 0xcf8b8, reg_value);
+ AIR_RTN_ERR(ret);
+
+ dev_info(dev, "LED initialize OK !\n");
+ return 0;
+}
+#endif /* AIR_LED_SUPPORT */
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 5, 0))
+static int en8811h_get_features(struct phy_device *phydev)
+{
+ int ret;
+ struct device *dev = phydev_dev(phydev);
+ dev_info(dev, "%s()\n", __func__);
+ ret = air_pbus_reg_write(phydev, 0xcf928 , 0x0);
+ AIR_RTN_ERR(ret);
+ ret = genphy_read_abilities(phydev);
+ if (ret)
+ return ret;
+
+ /* EN8811H supports 100M/1G/2.5G speed. */
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+ phydev->supported);
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+ phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
+ phydev->supported);
+ return 0;
+}
+#endif
+static int en8811h_phy_probe(struct phy_device *phydev)
+{
+ int ret = 0;
+ int reg_value, pid1 = 0, pid2 = 0;
+ u32 pbus_value = 0, retry;
+ struct device *dev = phydev_dev(phydev);
+
+ ret = air_pbus_reg_write(phydev, 0xcf928 , 0x0);
+ AIR_RTN_ERR(ret);
+ pid1 = phy_read(phydev, MII_PHYSID1);
+ if (pid1 < 0)
+ return pid1;
+ pid2 = phy_read(phydev, MII_PHYSID2);
+ if (pid2 < 0)
+ return pid2;
+ dev_info(dev, "PHY = %x - %x\n", pid1, pid2);
+ if ((EN8811H_PHY_ID1 != pid1) || (EN8811H_PHY_ID2 != pid2))
+ {
+ dev_err(dev, "EN8811H dose not exist !\n");
+ return -ENODEV;
+ }
+ ret = en8811h_load_firmware(phydev);
+ if (ret)
+ {
+ dev_err(dev,"EN8811H load firmware fail.\n");
+ return ret;
+ }
+ retry = MAX_RETRY;
+ do {
+ mdelay(300);
+ reg_value = air_mii_cl45_read(phydev, 0x1e, 0x8009);
+ if (EN8811H_PHY_READY == reg_value)
+ {
+ dev_info(dev, "EN8811H PHY ready!\n");
+ break;
+ }
+ retry--;
+ } while (retry);
+ if (0 == retry)
+ {
+ dev_err(dev, "EN8811H PHY is not ready. (MD32 FW Status reg: 0x%x)\n", reg_value);
+ pbus_value = air_buckpbus_reg_read(phydev, 0x3b3c);
+ dev_err(dev, "Check MD32 FW Version(0x3b3c) : %08x\n", pbus_value);
+ dev_err(dev, "EN8811H initialize fail!\n");
+ return 0;
+ }
+ /* Mode selection*/
+ dev_info(dev, "EN8811H Mode 1 !\n");
+ ret = air_mii_cl45_write(phydev, 0x1e, 0x800c, 0x0);
+ AIR_RTN_ERR(ret);
+ ret = air_mii_cl45_write(phydev, 0x1e, 0x800d, 0x0);
+ AIR_RTN_ERR(ret);
+ ret = air_mii_cl45_write(phydev, 0x1e, 0x800e, 0x1101);
+ AIR_RTN_ERR(ret);
+ ret = air_mii_cl45_write(phydev, 0x1e, 0x800f, 0x0002);
+ AIR_RTN_ERR(ret);
+
+ /* Serdes polarity */
+ pbus_value = air_buckpbus_reg_read(phydev, 0xca0f8);
+ pbus_value = (pbus_value & 0xfffffffc) | EN8811H_RX_POLARITY_REVERSE | EN8811H_TX_POLARITY_NORMAL;
+ ret = air_buckpbus_reg_write(phydev, 0xca0f8, pbus_value);
+ AIR_RTN_ERR(ret);
+ pbus_value = air_buckpbus_reg_read(phydev, 0xca0f8);
+ dev_info(dev, "Tx, Rx Polarity(0xca0f8): %08x\n", pbus_value);
+ pbus_value = air_buckpbus_reg_read(phydev, 0x3b3c);
+ dev_info(dev, "MD32 FW Version(0x3b3c) : %08x\n", pbus_value);
+#if defined(AIR_LED_SUPPORT)
+ ret = en8811h_led_init(phydev);
+ if (ret < 0)
+ {
+ dev_err(dev, "en8811h_led_init fail. (ret=%d)\n", ret);
+ return ret;
+ }
+#endif
+ dev_info(dev, "EN8811H initialize OK! (%s)\n", EN8811H_DRIVER_VERSION);
+ return 0;
+}
+
+static int en8811h_get_autonego(struct phy_device *phydev, int *an)
+{
+ int reg;
+ reg = phy_read(phydev, MII_BMCR);
+ if (reg < 0)
+ return -EINVAL;
+ if (reg & BMCR_ANENABLE)
+ *an = AUTONEG_ENABLE;
+ else
+ *an = AUTONEG_DISABLE;
+ return 0;
+}
+
+static int en8811h_read_status(struct phy_device *phydev)
+{
+ int ret = 0, lpagb = 0, lpa = 0, common_adv_gb = 0, common_adv = 0, advgb = 0, adv = 0, reg = 0, an = AUTONEG_DISABLE, bmcr = 0;
+ int old_link = phydev->link;
+ u32 pbus_value = 0;
+ struct device *dev = phydev_dev(phydev);
+ ret = genphy_update_link(phydev);
+ if (ret)
+ {
+ dev_err(dev, "ret %d!\n", ret);
+ return ret;
+ }
+
+ if (old_link && phydev->link)
+ return 0;
+
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+
+ reg = phy_read(phydev, MII_BMSR);
+ if (reg < 0)
+ {
+ dev_err(dev, "MII_BMSR reg %d!\n", reg);
+ return reg;
+ }
+ reg = phy_read(phydev, MII_BMSR);
+ if (reg < 0)
+ {
+ dev_err(dev, "MII_BMSR reg %d!\n", reg);
+ return reg;
+ }
+ if(reg & BMSR_LSTATUS)
+ {
+ pbus_value = air_buckpbus_reg_read(phydev, 0x109D4);
+ if (0x10 & pbus_value) {
+ phydev->speed = SPEED_2500;
+ phydev->duplex = DUPLEX_FULL;
+ }
+ else
+ {
+ ret = en8811h_get_autonego(phydev, &an);
+ if ((AUTONEG_ENABLE == an) && (0 == ret))
+ {
+ dev_dbg(dev, "AN mode!\n");
+ dev_dbg(dev, "SPEED 1000/100!\n");
+ lpagb = phy_read(phydev, MII_STAT1000);
+ if (lpagb < 0 )
+ return lpagb;
+ advgb = phy_read(phydev, MII_CTRL1000);
+ if (adv < 0 )
+ return adv;
+ common_adv_gb = (lpagb & (advgb << 2));
+
+ lpa = phy_read(phydev, MII_LPA);
+ if (lpa < 0 )
+ return lpa;
+ adv = phy_read(phydev, MII_ADVERTISE);
+ if (adv < 0 )
+ return adv;
+ common_adv = (lpa & adv);
+
+ phydev->speed = SPEED_10;
+ phydev->duplex = DUPLEX_HALF;
+ if (common_adv_gb & (LPA_1000FULL | LPA_1000HALF))
+ {
+ phydev->speed = SPEED_1000;
+ if (common_adv_gb & LPA_1000FULL)
+
+ phydev->duplex = DUPLEX_FULL;
+ }
+ else if (common_adv & (LPA_100FULL | LPA_100HALF))
+ {
+ phydev->speed = SPEED_100;
+ if (common_adv & LPA_100FULL)
+ phydev->duplex = DUPLEX_FULL;
+ }
+ else
+ {
+ if (common_adv & LPA_10FULL)
+ phydev->duplex = DUPLEX_FULL;
+ }
+ }
+ else
+ {
+ dev_dbg(dev, "Force mode!\n");
+ bmcr = phy_read(phydev, MII_BMCR);
+
+ if (bmcr < 0)
+ return bmcr;
+
+ if (bmcr & BMCR_FULLDPLX)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+
+ if (bmcr & BMCR_SPEED1000)
+ phydev->speed = SPEED_1000;
+ else if (bmcr & BMCR_SPEED100)
+ phydev->speed = SPEED_100;
+ else
+ phydev->speed = SPEED_UNKNOWN;
+ }
+ }
+ }
+
+ return ret;
+}
+static struct phy_driver en8811h_driver[] = {
+{
+ .phy_id = EN8811H_PHY_ID,
+ .name = "Airoha EN8811H",
+ .phy_id_mask = 0x0ffffff0,
+ .probe = en8811h_phy_probe,
+ .read_status = en8811h_read_status,
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 5, 0))
+ .get_features = en8811h_get_features,
+ .read_mmd = air_mii_cl45_read,
+ .write_mmd = air_mii_cl45_write,
+#endif
+} };
+
+int __init en8811h_phy_driver_register(void)
+{
+ int ret;
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))
+ ret = phy_driver_register(en8811h_driver);
+#else
+ ret = phy_driver_register(en8811h_driver, THIS_MODULE);
+#endif
+ if (!ret)
+ return 0;
+
+ phy_driver_unregister(en8811h_driver);
+ return ret;
+}
+
+void __exit en8811h_phy_driver_unregister(void)
+{
+ phy_driver_unregister(en8811h_driver);
+}
+
+module_init(en8811h_phy_driver_register);
+module_exit(en8811h_phy_driver_unregister);
\ No newline at end of file
diff -urN a/drivers/net/phy/air_en8811h.h b/drivers/net/phy/air_en8811h.h
--- a/drivers/net/phy/air_en8811h.h 1970-01-01 08:00:00.000000000 +0800
+++ b/drivers/net/phy/air_en8811h.h 2023-06-14 14:02:38.474875084 +0800
@@ -0,0 +1,151 @@
+#ifndef __EN8811H_H
+#define __EN8811H_H
+
+#define EN8811H_MD32_DM "EthMD32.dm.bin"
+#define EN8811H_MD32_DSP "EthMD32.DSP.bin"
+
+#define EN8811H_PHY_ID1 0x03a2
+#define EN8811H_PHY_ID2 0xa411
+#define EN8811H_PHY_ID ((EN8811H_PHY_ID1 << 16) | EN8811H_PHY_ID2)
+#define EN8811H_PHY_READY 0x02
+#define MAX_RETRY 25
+
+#define EN8811H_TX_POLARITY_NORMAL 0x1
+#define EN8811H_TX_POLARITY_REVERSE 0x0
+
+#define EN8811H_RX_POLARITY_REVERSE (0x1 << 1)
+#define EN8811H_RX_POLARITY_NORMAL (0x0 << 1)
+
+
+/*
+The following led_cfg example is for reference only.
+LED0 Link 2500/Blink 2500 TxRx (GPIO5) <-> BASE_T_LED0,
+LED1 Link 1000/Blink 1000 TxRx (GPIO4) <-> BASE_T_LED1,
+LED2 Link 100 /Blink 100 TxRx (GPIO3) <-> BASE_T_LED2,
+*/
+/* User-defined.B */
+#define BASE_T_LED0_ON_CFG (LED_ON_EVT_LINK_2500M)
+#define BASE_T_LED0_BLK_CFG (LED_BLK_EVT_2500M_TX_ACT | LED_BLK_EVT_2500M_RX_ACT)
+#define BASE_T_LED1_ON_CFG (LED_ON_EVT_LINK_1000M)
+#define BASE_T_LED1_BLK_CFG (LED_BLK_EVT_1000M_TX_ACT | LED_BLK_EVT_1000M_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)
+/* User-defined.E */
+
+/* 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 EN8811H_DRIVER_VERSION "v1.2.1"
+
+#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 (0x1ff)
+/* LED ON Event Option.B */
+#define LED_ON_EVT_LINK_2500M (1 << 8)
+#define LED_ON_EVT_FORCE (1 << 6)
+#define LED_ON_EVT_LINK_DOWN (1 << 3)
+#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 (0xfff)
+/* LED Blinking Event Option.B*/
+#define LED_BLK_EVT_2500M_RX_ACT (1 << 11)
+#define LED_BLK_EVT_2500M_TX_ACT (1 << 10)
+#define LED_BLK_EVT_FORCE (1 << 9)
+#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_ENABLE 1
+#define LED_DISABLE 0
+
+#define EN8811H_LED_COUNT 3
+
+#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_DUR (0x022)
+#define LED_ON_DUR_MASK (0xffff)
+
+#define LED_BLK_DUR (0x023)
+#define LED_BLK_DUR_MASK (0xffff)
+
+#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)
+
+#define INVALID_DATA 0xffff
+#define PBUS_INVALID_DATA 0xffffffff
+
+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_LED2_GPIO3 = 3,
+ AIR_LED1_GPIO4,
+ AIR_LED0_GPIO5,
+ AIR_LED_LAST
+} AIR_LED_GPIO;
+
+typedef enum {
+ AIR_BASE_T_LED0,
+ AIR_BASE_T_LED1,
+ AIR_BASE_T_LED2,
+ AIR_BASE_T_LED3
+}AIR_BASE_T_LED;
+
+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;
+
+#endif /* End of __EN8811H_MD32_H */
diff -urN a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
--- a/drivers/net/phy/Kconfig 2022-03-26 16:28:40.211133235 +0800
+++ b/drivers/net/phy/Kconfig 2022-03-26 16:30:52.637025198 +0800
@@ -595,6 +595,11 @@
the Reduced Gigabit Media Independent Interface(RGMII) between
Ethernet physical media devices and the Gigabit Ethernet controller.
+config AIR_EN8811H_PHY
+ tristate "Drivers for Airoha EN8811H 2.5 Gigabit PHY"
+ ---help---
+ Currently supports the Airoha EN8811H PHY.
+
endif # PHYLIB
config MICREL_KS8995MA
diff -urN a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
--- a/drivers/net/phy/Makefile 2022-03-26 16:28:57.619384439 +0800
+++ b/drivers/net/phy/Makefile 2022-03-26 16:31:40.893705848 +0800
@@ -111,6 +111,7 @@
obj-$(CONFIG_TERANETICS_PHY) += teranetics.o
obj-$(CONFIG_VITESSE_PHY) += vitesse.o
obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
+obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o
obj-$(CONFIG_MT753X_GSW) += mtk/mt753x/
obj-$(CONFIG_RTL8367S_GSW) += rtk/