On PHY probe, save the firmware version major/minor version to use it later to validate if a command is supported in the loaded firmware. Signed-off-by: Christian Marangi --- drivers/net/phy/as21xxx.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c index 005277360656..a5344abde91a 100644 --- a/drivers/net/phy/as21xxx.c +++ b/drivers/net/phy/as21xxx.c @@ -183,6 +183,9 @@ struct as21xxx_led_pattern_info { }; struct as21xxx_priv { + u8 fw_major_ver; + u8 fw_minor_ver; + bool parity_status; /* Protect concurrent IPC access */ struct mutex ipc_lock; @@ -570,6 +573,7 @@ static int aeon_ipc_sync_parity(struct phy_device *phydev, static int aeon_ipc_get_fw_version(struct phy_device *phydev) { u16 ret_data[AEON_IPC_DATA_NUM_REGISTERS], data[1]; + struct as21xxx_priv *priv = phydev->priv; char fw_version[AEON_IPC_DATA_MAX + 1]; int ret; @@ -585,6 +589,17 @@ static int aeon_ipc_get_fw_version(struct phy_device *phydev) fw_version[ret] = '\0'; phydev_info(phydev, "Firmware Version: %s\n", fw_version); + /* + * Firmware is the format x.x.x + * Save in priv struct to check validate feature support + * in new firmware version. + */ + ret = sscanf(fw_version, "%hhu.%hhu", &priv->fw_major_ver, + &priv->fw_minor_ver); + if (ret < 2) { + priv->fw_major_ver = 0; + priv->fw_minor_ver = 0; + } return 0; } -- 2.51.0 Rework the msg send logic to support sending DBG command. These DBG command use a special way to send and receive data and use data in u8 size and are used to tweak advanced (and later introduced) feature of the PHY. Signed-off-by: Christian Marangi --- drivers/net/phy/as21xxx.c | 190 +++++++++++++++++++++++++++++++------- 1 file changed, 155 insertions(+), 35 deletions(-) diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c index a5344abde91a..2098fa6a2f63 100644 --- a/drivers/net/phy/as21xxx.c +++ b/drivers/net/phy/as21xxx.c @@ -59,6 +59,10 @@ #define IPC_CMD_SYS_CPU 0x2 /* SYS_CPU */ #define IPC_CMD_BULK_DATA 0xa /* Pass bulk data in ipc registers. */ #define IPC_CMD_BULK_WRITE 0xc /* Write bulk data to memory */ +#define IPC_CMD_DBG 0x16 +#define IPC_CMD_POLL 0x17 +#define IPC_CMD_WRITE_BUF 0x18 +#define IPC_CMD_READ_BUF 0x19 #define IPC_CMD_CFG_PARAM 0x1a /* Write config parameters to memory */ #define IPC_CMD_NG_TESTMODE 0x1b /* Set NG test mode and tone */ #define IPC_CMD_TEMP_MON 0x15 /* Temperature monitoring function */ @@ -115,6 +119,12 @@ /* Sub command of CMD_TEMP_MON */ #define IPC_CMD_TEMP_MON_GET 0x4 +/* Sub command of CMD_DBG */ +#define IPC_DBG_DPC 0x8b + +#define IPC_DATA_DBG_SEC GENMASK(15, 8) +#define IPC_DATA_DBG_CMD GENMASK(7, 0) + #define AS21XXX_MDIO_AN_C22 0xffe0 #define PHY_ID_AS21XXX 0x75009410 @@ -451,18 +461,9 @@ static int aeon_ipc_send_cmd(struct phy_device *phydev, return 0; } -/* If data is NULL, return 0 or negative error. - * If data not NULL, return number of Bytes received from IPC or - * a negative error. - */ -static int aeon_ipc_send_msg(struct phy_device *phydev, - u16 opcode, u16 *data, unsigned int data_len, - u16 *ret_data) +static int aeon_ipc_set_msg_data(struct phy_device *phydev, u16 *data, + unsigned int data_len) { - struct as21xxx_priv *priv = phydev->priv; - unsigned int ret_size; - u16 cmd, ret_sts; - int ret; int i; /* IPC have a max of 8 register to transfer data, @@ -475,46 +476,69 @@ static int aeon_ipc_send_msg(struct phy_device *phydev, phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i), data[i]); - cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, data_len) | - FIELD_PREP(AEON_IPC_CMD_OPCODE, opcode); - - mutex_lock(&priv->ipc_lock); - - ret = aeon_ipc_send_cmd(phydev, priv, cmd, &ret_sts); - if (ret) { - phydev_err(phydev, "failed to send ipc msg for %x: %d\n", - opcode, ret); - goto out; - } - - if (!data) - goto out; + return 0; +} - if ((ret_sts & AEON_IPC_STS_STATUS) == AEON_IPC_STS_STATUS_ERROR) { - ret = -EINVAL; - goto out; - } +static int +aeon_ipc_get_msg_ret_data(struct phy_device *phydev, u16 ret_sts, + u16 *ret_data) __must_hold(&priv->ipc_lock) +{ + unsigned int ret_size; + int ret; + int i; /* Prevent IPC from stack smashing the kernel. * We can't trust IPC to return a good value and we always * preallocate space for 16 Bytes. */ ret_size = FIELD_GET(AEON_IPC_STS_SIZE, ret_sts); - if (ret_size > AEON_IPC_DATA_MAX) { - ret = -EINVAL; - goto out; - } + if (ret_size > AEON_IPC_DATA_MAX) + return -EINVAL; /* Read data from IPC data register for ret_size value from IPC */ for (i = 0; i < DIV_ROUND_UP(ret_size, sizeof(u16)); i++) { ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i)); if (ret < 0) - goto out; + return ret; ret_data[i] = ret; } - ret = ret_size; + return ret_size; +} + +/* If data is NULL, return 0 or negative error. + * If data not NULL, return number of Bytes received from IPC or + * a negative error. + */ +static int aeon_ipc_send_msg(struct phy_device *phydev, + u16 opcode, u16 *data, unsigned int data_len, + u16 *ret_data) +{ + struct as21xxx_priv *priv = phydev->priv; + u16 cmd, ret_sts; + int ret; + + ret = aeon_ipc_set_msg_data(phydev, data, data_len); + if (ret) + return ret; + + cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, data_len) | + FIELD_PREP(AEON_IPC_CMD_OPCODE, opcode); + + mutex_lock(&priv->ipc_lock); + + ret = aeon_ipc_send_cmd(phydev, priv, cmd, &ret_sts); + if (ret) { + phydev_err(phydev, "failed to send ipc msg for %x: %d\n", + opcode, ret); + goto out; + } + + if (!data) + goto out; + + ret = aeon_ipc_get_msg_ret_data(phydev, ret_sts, ret_data); out: mutex_unlock(&priv->ipc_lock); @@ -604,6 +628,102 @@ static int aeon_ipc_get_fw_version(struct phy_device *phydev) return 0; } +static int aeon_ipc_poll(struct phy_device *phydev) +{ + struct as21xxx_priv *priv = phydev->priv; + u16 ret_sts; + u16 cmd; + int ret; + + cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, 0) | + FIELD_PREP(AEON_IPC_CMD_OPCODE, IPC_CMD_POLL); + + mutex_lock(&priv->ipc_lock); + + ret = aeon_ipc_send_cmd(phydev, phydev->priv, cmd, &ret_sts); + if (ret) + phydev_err(phydev, "Invalid IPC status on IPC poll: %x\n", + ret_sts); + + mutex_unlock(&priv->ipc_lock); + + return ret; +} + +static int aeon_ipc_dbg_cmd(struct phy_device *phydev, u16 dbg_sec, + u16 dbg_cmd, u16 msg_size) +{ + u16 data[3]; + + data[0] = FIELD_PREP(IPC_DATA_DBG_SEC, dbg_sec) | + FIELD_PREP(IPC_DATA_DBG_CMD, dbg_cmd); + data[1] = msg_size; + + return aeon_ipc_send_msg(phydev, IPC_CMD_DBG, data, + sizeof(data), NULL); +} + +static int aeon_ipc_dbg_read_buf(struct phy_device *phydev, u16 *buf) +{ + struct as21xxx_priv *priv = phydev->priv; + u16 cmd, ret_sts; + int ret; + + cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, 0) | + FIELD_PREP(AEON_IPC_CMD_OPCODE, IPC_CMD_READ_BUF); + + mutex_lock(&priv->ipc_lock); + + ret = aeon_ipc_send_cmd(phydev, phydev->priv, cmd, &ret_sts); + if (ret) + goto out; + + ret = aeon_ipc_get_msg_ret_data(phydev, ret_sts, buf); + +out: + mutex_unlock(&priv->ipc_lock); + + return ret; +} + +static int aeon_ipc_dbg_write_buf(struct phy_device *phydev, u8 *data, + u8 data_len) +{ + u16 msg_data[AEON_IPC_DATA_NUM_REGISTERS]; + struct as21xxx_priv *priv = phydev->priv; + u16 cmd, ret_sts; + int ret; + int i; + + /* Make sure we don't try to write more data than supported */ + if (data_len * 2 > AEON_IPC_DATA_MAX) + return -EINVAL; + + /* Pack u8 DBG data in u16 buffer */ + for (i = 0; i < data_len; i += 2) { + msg_data[i] = data[i]; + msg_data[i] |= data[i + 1] << 8; + } + + ret = aeon_ipc_set_msg_data(phydev, msg_data, data_len * 2); + if (ret) + return ret; + + cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, data_len) | + FIELD_PREP(AEON_IPC_CMD_OPCODE, IPC_CMD_WRITE_BUF); + + mutex_lock(&priv->ipc_lock); + + ret = aeon_ipc_send_cmd(phydev, priv, cmd, &ret_sts); + if (ret) + phydev_err(phydev, "failed to send IPC msg for %x: %d\n", + IPC_CMD_WRITE_BUF, ret); + + mutex_unlock(&priv->ipc_lock); + + return ret; +} + static int aeon_dpc_ra_enable(struct phy_device *phydev) { u16 data[2]; -- 2.51.0 The as21xxx control In-Band support with the DPC RateAdapation configuration. Tested on a Banana Pi R4 PRO (MT7988a) and on Airoha AN7581/AN7583 where In Band is controlled by disabling Autoneg on the PCS, where the PHY correctly transfer packet with In Band disabled on the PCS and DPC RA disabled on the PHY. It was also confirmed that with In Band enabled on the PCS and DPC RA enabled on the PHY also packets gets transmitted correctly. With this new information, fill in the .inband_caps() OP and set the .config_inband() to enable DPC RA when inband is enabled. Support for this feature is enabled only on PHY firmware >= 1.9 as on previous version the DPC RA could only be enabled and a PHY reset (and Firmware reloaded) was needed to change this at runtime. To keep compatibility with some HW configuration, we enable DPC RA by default for older firmware. This is needed as on Banana Pi R4 PRO, one of the 2 as21xxx PHY is attached to a Switch that requires the PHY in In Band mode with Rate Adaption. Signed-off-by: Christian Marangi --- drivers/net/phy/as21xxx.c | 111 +++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c index 2098fa6a2f63..60ec7af6e386 100644 --- a/drivers/net/phy/as21xxx.c +++ b/drivers/net/phy/as21xxx.c @@ -125,6 +125,10 @@ #define IPC_DATA_DBG_SEC GENMASK(15, 8) #define IPC_DATA_DBG_CMD GENMASK(7, 0) +/* DBG_DPC sub command */ +#define IPC_DPC_RA_SET_CFG 0x2 +#define IPC_DPC_RA_GET_CFG 0x7 + #define AS21XXX_MDIO_AN_C22 0xffe0 #define PHY_ID_AS21XXX 0x75009410 @@ -724,6 +728,52 @@ static int aeon_ipc_dbg_write_buf(struct phy_device *phydev, u8 *data, return ret; } +static int aeon_ipc_dpc_ra_set(struct phy_device *phydev, bool enable) +{ + u8 data[1]; + int ret; + + ret = aeon_ipc_dbg_cmd(phydev, IPC_DBG_DPC, + IPC_DPC_RA_SET_CFG, + sizeof(data)); + if (ret) + return ret; + + data[0] = enable; + + ret = aeon_ipc_dbg_write_buf(phydev, data, sizeof(data)); + if (ret) + return ret; + + ret = aeon_ipc_poll(phydev); + if (ret) + return ret; + + return 0; +} + +static int aeon_ipc_dpc_ra_get(struct phy_device *phydev, bool *enabled) +{ + u16 ret_data[AEON_IPC_DATA_NUM_REGISTERS]; + int ret; + + ret = aeon_ipc_dbg_cmd(phydev, IPC_DBG_DPC, + IPC_DPC_RA_GET_CFG, 0); + if (ret) + return ret; + + ret = aeon_ipc_poll(phydev); + if (ret) + return ret; + + ret = aeon_ipc_dbg_read_buf(phydev, ret_data); + if (ret < 0) + return ret; + + *enabled = !!ret_data[0]; + return 0; +} + static int aeon_dpc_ra_enable(struct phy_device *phydev) { u16 data[2]; @@ -765,7 +815,11 @@ static int as21xxx_probe(struct phy_device *phydev) if (ret) return ret; - return aeon_dpc_ra_enable(phydev); + /* Enable DPC Rate Adaption by default on older firmware */ + if (priv->fw_major_ver == 1 && priv->fw_minor_ver < 9) + return aeon_dpc_ra_enable(phydev); + + return 0; } static int as21xxx_read_link(struct phy_device *phydev, int *bmcr) @@ -1078,6 +1132,41 @@ static int as21xxx_match_phy_device(struct phy_device *phydev, return ret; } +static unsigned int as21xxx_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + struct as21xxx_priv *priv = phydev->priv; + + /* Configuring DPC Rate Adaption (to permit In Band support) + * is supported only from firmware version 1.9+ + */ + if (priv->fw_major_ver > 1 || priv->fw_minor_ver >= 9) + return LINK_INBAND_ENABLE | LINK_INBAND_DISABLE; + + /* On older firmware we enforce In Band by default + * for compatibility reason. + */ + return LINK_INBAND_ENABLE; +} + +static int as21xxx_config_inband(struct phy_device *phydev, + unsigned int modes) +{ + bool enabled; + int ret; + + ret = aeon_ipc_dpc_ra_get(phydev, &enabled); + if (ret) + return ret; + + /* Ignore if already in the desired mode */ + if ((modes == LINK_INBAND_ENABLE && enabled) || + (modes == LINK_INBAND_DISABLE && !enabled)) + return 0; + + return aeon_ipc_dpc_ra_set(phydev, modes == LINK_INBAND_ENABLE); +} + static struct phy_driver as21xxx_drivers[] = { { /* PHY expose in C45 as 0x7500 0x9410 @@ -1093,6 +1182,8 @@ static struct phy_driver as21xxx_drivers[] = { PHY_ID_MATCH_EXACT(PHY_ID_AS21011JB1), .name = "Aeonsemi AS21011JB1", .probe = as21xxx_probe, + .inband_caps = as21xxx_inband_caps, + .config_inband = as21xxx_config_inband, .match_phy_device = as21xxx_match_phy_device, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, @@ -1105,6 +1196,8 @@ static struct phy_driver as21xxx_drivers[] = { PHY_ID_MATCH_EXACT(PHY_ID_AS21011PB1), .name = "Aeonsemi AS21011PB1", .probe = as21xxx_probe, + .inband_caps = as21xxx_inband_caps, + .config_inband = as21xxx_config_inband, .match_phy_device = as21xxx_match_phy_device, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, @@ -1117,6 +1210,8 @@ static struct phy_driver as21xxx_drivers[] = { PHY_ID_MATCH_EXACT(PHY_ID_AS21010PB1), .name = "Aeonsemi AS21010PB1", .probe = as21xxx_probe, + .inband_caps = as21xxx_inband_caps, + .config_inband = as21xxx_config_inband, .match_phy_device = as21xxx_match_phy_device, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, @@ -1129,6 +1224,8 @@ static struct phy_driver as21xxx_drivers[] = { PHY_ID_MATCH_EXACT(PHY_ID_AS21010JB1), .name = "Aeonsemi AS21010JB1", .probe = as21xxx_probe, + .inband_caps = as21xxx_inband_caps, + .config_inband = as21xxx_config_inband, .match_phy_device = as21xxx_match_phy_device, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, @@ -1141,6 +1238,8 @@ static struct phy_driver as21xxx_drivers[] = { PHY_ID_MATCH_EXACT(PHY_ID_AS21210PB1), .name = "Aeonsemi AS21210PB1", .probe = as21xxx_probe, + .inband_caps = as21xxx_inband_caps, + .config_inband = as21xxx_config_inband, .match_phy_device = as21xxx_match_phy_device, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, @@ -1153,6 +1252,8 @@ static struct phy_driver as21xxx_drivers[] = { PHY_ID_MATCH_EXACT(PHY_ID_AS21510JB1), .name = "Aeonsemi AS21510JB1", .probe = as21xxx_probe, + .inband_caps = as21xxx_inband_caps, + .config_inband = as21xxx_config_inband, .match_phy_device = as21xxx_match_phy_device, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, @@ -1165,6 +1266,8 @@ static struct phy_driver as21xxx_drivers[] = { PHY_ID_MATCH_EXACT(PHY_ID_AS21510PB1), .name = "Aeonsemi AS21510PB1", .probe = as21xxx_probe, + .inband_caps = as21xxx_inband_caps, + .config_inband = as21xxx_config_inband, .match_phy_device = as21xxx_match_phy_device, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, @@ -1177,6 +1280,8 @@ static struct phy_driver as21xxx_drivers[] = { PHY_ID_MATCH_EXACT(PHY_ID_AS21511JB1), .name = "Aeonsemi AS21511JB1", .probe = as21xxx_probe, + .inband_caps = as21xxx_inband_caps, + .config_inband = as21xxx_config_inband, .match_phy_device = as21xxx_match_phy_device, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, @@ -1189,6 +1294,8 @@ static struct phy_driver as21xxx_drivers[] = { PHY_ID_MATCH_EXACT(PHY_ID_AS21210JB1), .name = "Aeonsemi AS21210JB1", .probe = as21xxx_probe, + .inband_caps = as21xxx_inband_caps, + .config_inband = as21xxx_config_inband, .match_phy_device = as21xxx_match_phy_device, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, @@ -1201,6 +1308,8 @@ static struct phy_driver as21xxx_drivers[] = { PHY_ID_MATCH_EXACT(PHY_ID_AS21511PB1), .name = "Aeonsemi AS21511PB1", .probe = as21xxx_probe, + .inband_caps = as21xxx_inband_caps, + .config_inband = as21xxx_config_inband, .match_phy_device = as21xxx_match_phy_device, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, -- 2.51.0