mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 01:55:51 +02:00
Merge branch 'introduce-airoha-an8801r-series-gigabit-ethernet-phy-driver'
Louis-Alexis Eyraud says:
====================
Introduce Airoha AN8801R series Gigabit Ethernet PHY driver
This series introduces the Airoha AN8801R Gigabit Ethernet PHY initial
support.
The Airoha AN8801R is a low power single-port Ethernet PHY Transceiver
with Single-port serdes interface for 1000Base-X/RGMII.
This chip is compliant with 10Base-T, 100Base-TX and 1000Base-T IEEE
802.3(u,ab) and supports:
- Energy Efficient Ethernet (802.3az)
- Full Duplex Control Flow (802.3x)
- auto-negotiation
- crossover detect and autocorrection,
- Wake-on-LAN with Magic Packet
- Jumbo Frame up to 9 Kilobytes.
This PHY also supports up to three user-configurable LEDs, which are
usually used for LAN Activity, 100M, 1000M indication.
The series provides the devicetree binding and the driver that have been
written by AngeloGioacchino Del Regno, based on downstream
implementation ([1]). The driver allows setting up PHY LEDs, 10/100M,
1000M speeds, and Wake on LAN and PHY interrupts.
Since v2, the series also adds the air_phy_lib library, which goal is to
share common code between air_en8811h and air_an8801 drivers, and its use
in them. The first shared functions are the existing BuckPbus register
accessors and air_phy_read/write_page functions coming from air_en8811h
driver.
The series is based on net-next kernel tree (sha1: 90d03ee2c5) and
I have tested it on Mediatek Genio 720-EVK board (that integrates an
Airoha AN8801RIN/A Ethernet PHY) with early board hardware enablement
patches.
[1]: https://gitlab.com/mediatek/aiot/bsp/linux/-/blob/mtk-v6.6/drivers/net/phy/an8801.c
====================
Link: https://patch.msgid.link/20260526-add-airoha-an8801-support-v5-0-01aea8dee69b@collabora.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
56edbedb74
120
Documentation/devicetree/bindings/net/airoha,an8801.yaml
Normal file
120
Documentation/devicetree/bindings/net/airoha,an8801.yaml
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/net/airoha,an8801.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Airoha AN8801R Series PHY
|
||||
|
||||
maintainers:
|
||||
- AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
|
||||
|
||||
description:
|
||||
The Airoha AN8801R is a low power single-port Ethernet PHY Transceiver
|
||||
with Single-port serdes interface for 1000Base-X/RGMII; this chip is
|
||||
compliant with 10Base-T, 100Base-TX and 1000Base-T IEEE 802.3(u,ab)
|
||||
and supports Energy Efficient Ethernet (802.3az), Full Duplex Control
|
||||
Flow (802.3x), auto-negotiation, crossover detect and autocorrection,
|
||||
Wake-on-LAN with Magic Packet, and Jumbo Frame up to 9 Kilobytes.
|
||||
This PHY also supports up to three user-configurable LEDs, which are
|
||||
usually used for LAN Activity, 100M, 1000M indication.
|
||||
|
||||
allOf:
|
||||
- $ref: ethernet-phy.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ethernet-phy-idc0ff.0421
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
leds:
|
||||
type: object
|
||||
description:
|
||||
Describes the LEDs associated to the PHY
|
||||
|
||||
properties:
|
||||
"#address-cells":
|
||||
const: 1
|
||||
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
patternProperties:
|
||||
"^led@[0-2]$":
|
||||
type: object
|
||||
description: PHY LEDs
|
||||
$ref: /schemas/leds/common.yaml#
|
||||
|
||||
properties:
|
||||
reg:
|
||||
enum: [0, 1, 2]
|
||||
|
||||
function-enumerator:
|
||||
enum: [0, 1, 2]
|
||||
description: |
|
||||
Specifies a function for offloading LED functionality to the PHY:
|
||||
0 - No offloading
|
||||
1 - Link Availability
|
||||
2 - Network Activity
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
wakeup-source:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
Enable Wake-on-LAN support
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/leds/common.h>
|
||||
|
||||
mdio {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
ethernet-phy@0 {
|
||||
compatible = "ethernet-phy-idc0ff.0421";
|
||||
reg = <0>;
|
||||
|
||||
leds {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led@0 {
|
||||
reg = <0>;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
function = LED_FUNCTION_LAN;
|
||||
default-state = "keep";
|
||||
};
|
||||
|
||||
led@1 {
|
||||
reg = <1>;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
function = LED_FUNCTION_LAN;
|
||||
function-enumerator = <1>;
|
||||
default-state = "keep";
|
||||
};
|
||||
|
||||
led@2 {
|
||||
reg = <2>;
|
||||
color = <LED_COLOR_ID_YELLOW>;
|
||||
function = LED_FUNCTION_LAN;
|
||||
function-enumerator = <2>;
|
||||
default-state = "keep";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -90,12 +90,24 @@ config AS21XXX_PHY
|
|||
AS21210PB1 that all register with the PHY ID 0x7500 0x7500
|
||||
before the firmware is loaded.
|
||||
|
||||
config AIR_AN8801_PHY
|
||||
tristate "Airoha AN8801 Gigabit PHY"
|
||||
select AIR_NET_PHYLIB
|
||||
help
|
||||
Currently supports the Airoha AN8801R PHY.
|
||||
|
||||
config AIR_EN8811H_PHY
|
||||
tristate "Airoha EN8811H 2.5 Gigabit PHY"
|
||||
select AIR_NET_PHYLIB
|
||||
select PHY_COMMON_PROPS
|
||||
help
|
||||
Currently supports the Airoha EN8811H PHY.
|
||||
|
||||
config AIR_NET_PHYLIB
|
||||
tristate
|
||||
help
|
||||
Airoha Ethernet PHY common library
|
||||
|
||||
config AMD_PHY
|
||||
tristate "AMD and Altima PHYs"
|
||||
help
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m)
|
|||
|
||||
obj-$(CONFIG_ADIN_PHY) += adin.o
|
||||
obj-$(CONFIG_ADIN1100_PHY) += adin1100.o
|
||||
obj-$(CONFIG_AIR_AN8801_PHY) += air_an8801.o
|
||||
obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o
|
||||
obj-$(CONFIG_AIR_NET_PHYLIB) += air_phy_lib.o
|
||||
obj-$(CONFIG_AMD_PHY) += amd.o
|
||||
obj-$(CONFIG_AMCC_QT2025_PHY) += qt2025.o
|
||||
obj-$(CONFIG_AQUANTIA_PHY) += aquantia/
|
||||
|
|
|
|||
1156
drivers/net/phy/air_an8801.c
Normal file
1156
drivers/net/phy/air_an8801.c
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -21,6 +21,8 @@
|
|||
#include <linux/wordpart.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
#include "air_phy_lib.h"
|
||||
|
||||
#define EN8811H_PHY_ID 0x03a2a411
|
||||
#define AN8811HB_PHY_ID 0xc0ff04a0
|
||||
|
||||
|
|
@ -40,23 +42,6 @@
|
|||
#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
|
||||
|
|
@ -244,193 +229,6 @@ static const unsigned long en8811h_led_trig = BIT(TRIGGER_NETDEV_FULL_DUPLEX) |
|
|||
BIT(TRIGGER_NETDEV_RX) |
|
||||
BIT(TRIGGER_NETDEV_TX);
|
||||
|
||||
static int air_phy_read_page(struct phy_device *phydev)
|
||||
{
|
||||
return __phy_read(phydev, AIR_EXT_PAGE_ACCESS);
|
||||
}
|
||||
|
||||
static int air_phy_write_page(struct phy_device *phydev, int page)
|
||||
{
|
||||
return __phy_write(phydev, AIR_EXT_PAGE_ACCESS, page);
|
||||
}
|
||||
|
||||
static int __air_buckpbus_reg_write(struct phy_device *phydev,
|
||||
u32 pbus_address, u32 pbus_data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH,
|
||||
upper_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,
|
||||
lower_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH,
|
||||
upper_16_bits(pbus_data));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW,
|
||||
lower_16_bits(pbus_data));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int air_buckpbus_reg_write(struct phy_device *phydev,
|
||||
u32 pbus_address, u32 pbus_data)
|
||||
{
|
||||
int saved_page;
|
||||
int ret = 0;
|
||||
|
||||
saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
|
||||
|
||||
if (saved_page >= 0) {
|
||||
ret = __air_buckpbus_reg_write(phydev, pbus_address,
|
||||
pbus_data);
|
||||
if (ret < 0)
|
||||
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
|
||||
pbus_address, ret);
|
||||
}
|
||||
|
||||
return 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;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH,
|
||||
upper_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW,
|
||||
lower_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH);
|
||||
if (pbus_data_high < 0)
|
||||
return pbus_data_high;
|
||||
|
||||
pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW);
|
||||
if (pbus_data_low < 0)
|
||||
return pbus_data_low;
|
||||
|
||||
*pbus_data = pbus_data_low | (pbus_data_high << 16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int air_buckpbus_reg_read(struct phy_device *phydev,
|
||||
u32 pbus_address, u32 *pbus_data)
|
||||
{
|
||||
int saved_page;
|
||||
int ret = 0;
|
||||
|
||||
saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
|
||||
|
||||
if (saved_page >= 0) {
|
||||
ret = __air_buckpbus_reg_read(phydev, pbus_address, pbus_data);
|
||||
if (ret < 0)
|
||||
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
|
||||
pbus_address, ret);
|
||||
}
|
||||
|
||||
return 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;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH,
|
||||
upper_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW,
|
||||
lower_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH);
|
||||
if (pbus_data_high < 0)
|
||||
return pbus_data_high;
|
||||
|
||||
pbus_data_low = __phy_read(phydev, 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, AIR_BPBUS_WR_ADDR_HIGH,
|
||||
upper_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,
|
||||
lower_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH,
|
||||
upper_16_bits(pbus_data_new));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW,
|
||||
lower_16_bits(pbus_data_new));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int air_buckpbus_reg_modify(struct phy_device *phydev,
|
||||
u32 pbus_address, u32 mask, u32 set)
|
||||
{
|
||||
int saved_page;
|
||||
int ret = 0;
|
||||
|
||||
saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
|
||||
|
||||
if (saved_page >= 0) {
|
||||
ret = __air_buckpbus_reg_modify(phydev, pbus_address, mask,
|
||||
set);
|
||||
if (ret < 0)
|
||||
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
|
||||
pbus_address, ret);
|
||||
}
|
||||
|
||||
return phy_restore_page(phydev, saved_page, ret);
|
||||
}
|
||||
|
||||
static int __air_write_buf(struct phy_device *phydev, u32 address,
|
||||
const struct firmware *fw)
|
||||
{
|
||||
|
|
@ -489,8 +287,8 @@ static int en8811h_wait_mcu_ready(struct phy_device *phydev)
|
|||
{
|
||||
int ret, reg_value;
|
||||
|
||||
ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
|
||||
EN8811H_FW_CTRL_1_FINISH);
|
||||
ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
|
||||
EN8811H_FW_CTRL_1_FINISH);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
@ -515,28 +313,29 @@ static int an8811hb_check_crc(struct phy_device *phydev, u32 set1,
|
|||
int ret;
|
||||
|
||||
/* Configure CRC */
|
||||
ret = air_buckpbus_reg_modify(phydev, set1,
|
||||
AN8811HB_CRC_RD_EN,
|
||||
AN8811HB_CRC_RD_EN);
|
||||
ret = air_phy_buckpbus_reg_modify(phydev, set1,
|
||||
AN8811HB_CRC_RD_EN,
|
||||
AN8811HB_CRC_RD_EN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
air_buckpbus_reg_read(phydev, set1, &pbus_value);
|
||||
air_phy_buckpbus_reg_read(phydev, set1, &pbus_value);
|
||||
|
||||
do {
|
||||
msleep(300);
|
||||
air_buckpbus_reg_read(phydev, mon2, &pbus_value);
|
||||
air_phy_buckpbus_reg_read(phydev, mon2, &pbus_value);
|
||||
|
||||
/* We do not know what errors this check is supposed
|
||||
* catch or what to do about a failure. So print the
|
||||
* result and continue like the vendor driver does.
|
||||
*/
|
||||
if (pbus_value & AN8811HB_CRC_ST) {
|
||||
air_buckpbus_reg_read(phydev, mon3, &pbus_value);
|
||||
air_phy_buckpbus_reg_read(phydev, mon3, &pbus_value);
|
||||
phydev_dbg(phydev, "CRC Check %s!\n",
|
||||
pbus_value & AN8811HB_CRC_CHECK_PASS ?
|
||||
"PASS" : "FAIL");
|
||||
return air_buckpbus_reg_modify(phydev, set1,
|
||||
AN8811HB_CRC_RD_EN, 0);
|
||||
return air_phy_buckpbus_reg_modify(phydev, set1,
|
||||
AN8811HB_CRC_RD_EN,
|
||||
0);
|
||||
}
|
||||
} while (--retry);
|
||||
|
||||
|
|
@ -548,8 +347,8 @@ static void en8811h_print_fw_version(struct phy_device *phydev)
|
|||
{
|
||||
struct en8811h_priv *priv = phydev->priv;
|
||||
|
||||
air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION,
|
||||
&priv->firmware_version);
|
||||
air_phy_buckpbus_reg_read(phydev, EN8811H_FW_VERSION,
|
||||
&priv->firmware_version);
|
||||
phydev_info(phydev, "MD32 firmware version: %08x\n",
|
||||
priv->firmware_version);
|
||||
}
|
||||
|
|
@ -574,8 +373,8 @@ static int an8811hb_load_firmware(struct phy_device *phydev)
|
|||
{
|
||||
int ret;
|
||||
|
||||
ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
|
||||
EN8811H_FW_CTRL_1_START);
|
||||
ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
|
||||
EN8811H_FW_CTRL_1_START);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -616,14 +415,14 @@ static int en8811h_load_firmware(struct phy_device *phydev)
|
|||
if (ret < 0)
|
||||
goto en8811h_load_firmware_rel1;
|
||||
|
||||
ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
|
||||
EN8811H_FW_CTRL_1_START);
|
||||
ret = air_phy_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);
|
||||
ret = air_phy_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;
|
||||
|
||||
|
|
@ -635,8 +434,8 @@ static int en8811h_load_firmware(struct phy_device *phydev)
|
|||
if (ret < 0)
|
||||
goto en8811h_load_firmware_out;
|
||||
|
||||
ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
|
||||
EN8811H_FW_CTRL_2_LOADING, 0);
|
||||
ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
|
||||
EN8811H_FW_CTRL_2_LOADING, 0);
|
||||
if (ret < 0)
|
||||
goto en8811h_load_firmware_out;
|
||||
|
||||
|
|
@ -662,8 +461,8 @@ static int en8811h_restart_mcu(struct phy_device *phydev)
|
|||
{
|
||||
int ret;
|
||||
|
||||
ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
|
||||
EN8811H_FW_CTRL_1_START);
|
||||
ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
|
||||
EN8811H_FW_CTRL_1_START);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -957,7 +756,7 @@ static unsigned long an8811hb_clk_recalc_rate(struct clk_hw *hw,
|
|||
u32 pbus_value;
|
||||
int ret;
|
||||
|
||||
ret = air_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value);
|
||||
ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -969,9 +768,9 @@ static int an8811hb_clk_enable(struct clk_hw *hw)
|
|||
struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
|
||||
struct phy_device *phydev = priv->phydev;
|
||||
|
||||
return air_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV,
|
||||
AN8811HB_CLK_DRV_CKO_MASK,
|
||||
AN8811HB_CLK_DRV_CKO_MASK);
|
||||
return air_phy_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV,
|
||||
AN8811HB_CLK_DRV_CKO_MASK,
|
||||
AN8811HB_CLK_DRV_CKO_MASK);
|
||||
}
|
||||
|
||||
static void an8811hb_clk_disable(struct clk_hw *hw)
|
||||
|
|
@ -979,8 +778,8 @@ static void an8811hb_clk_disable(struct clk_hw *hw)
|
|||
struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
|
||||
struct phy_device *phydev = priv->phydev;
|
||||
|
||||
air_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV,
|
||||
AN8811HB_CLK_DRV_CKO_MASK, 0);
|
||||
air_phy_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV,
|
||||
AN8811HB_CLK_DRV_CKO_MASK, 0);
|
||||
}
|
||||
|
||||
static int an8811hb_clk_is_enabled(struct clk_hw *hw)
|
||||
|
|
@ -990,7 +789,7 @@ static int an8811hb_clk_is_enabled(struct clk_hw *hw)
|
|||
u32 pbus_value;
|
||||
int ret;
|
||||
|
||||
ret = air_buckpbus_reg_read(phydev, AN8811HB_CLK_DRV, &pbus_value);
|
||||
ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_CLK_DRV, &pbus_value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -1056,7 +855,7 @@ static unsigned long en8811h_clk_recalc_rate(struct clk_hw *hw,
|
|||
u32 pbus_value;
|
||||
int ret;
|
||||
|
||||
ret = air_buckpbus_reg_read(phydev, EN8811H_HWTRAP1, &pbus_value);
|
||||
ret = air_phy_buckpbus_reg_read(phydev, EN8811H_HWTRAP1, &pbus_value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -1068,9 +867,9 @@ static int en8811h_clk_enable(struct clk_hw *hw)
|
|||
struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
|
||||
struct phy_device *phydev = priv->phydev;
|
||||
|
||||
return air_buckpbus_reg_modify(phydev, EN8811H_CLK_CGM,
|
||||
EN8811H_CLK_CGM_CKO,
|
||||
EN8811H_CLK_CGM_CKO);
|
||||
return air_phy_buckpbus_reg_modify(phydev, EN8811H_CLK_CGM,
|
||||
EN8811H_CLK_CGM_CKO,
|
||||
EN8811H_CLK_CGM_CKO);
|
||||
}
|
||||
|
||||
static void en8811h_clk_disable(struct clk_hw *hw)
|
||||
|
|
@ -1078,8 +877,8 @@ static void en8811h_clk_disable(struct clk_hw *hw)
|
|||
struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
|
||||
struct phy_device *phydev = priv->phydev;
|
||||
|
||||
air_buckpbus_reg_modify(phydev, EN8811H_CLK_CGM,
|
||||
EN8811H_CLK_CGM_CKO, 0);
|
||||
air_phy_buckpbus_reg_modify(phydev, EN8811H_CLK_CGM,
|
||||
EN8811H_CLK_CGM_CKO, 0);
|
||||
}
|
||||
|
||||
static int en8811h_clk_is_enabled(struct clk_hw *hw)
|
||||
|
|
@ -1089,7 +888,7 @@ static int en8811h_clk_is_enabled(struct clk_hw *hw)
|
|||
u32 pbus_value;
|
||||
int ret;
|
||||
|
||||
ret = air_buckpbus_reg_read(phydev, EN8811H_CLK_CGM, &pbus_value);
|
||||
ret = air_phy_buckpbus_reg_read(phydev, EN8811H_CLK_CGM, &pbus_value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -1200,9 +999,9 @@ static int an8811hb_probe(struct phy_device *phydev)
|
|||
return ret;
|
||||
|
||||
/* Configure led gpio pins as output */
|
||||
ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT,
|
||||
AN8811HB_GPIO_OUTPUT_345,
|
||||
AN8811HB_GPIO_OUTPUT_345);
|
||||
ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT,
|
||||
AN8811HB_GPIO_OUTPUT_345,
|
||||
AN8811HB_GPIO_OUTPUT_345);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -1241,9 +1040,9 @@ static int en8811h_probe(struct phy_device *phydev)
|
|||
return ret;
|
||||
|
||||
/* Configure led gpio pins as output */
|
||||
ret = air_buckpbus_reg_modify(phydev, EN8811H_GPIO_OUTPUT,
|
||||
EN8811H_GPIO_OUTPUT_345,
|
||||
EN8811H_GPIO_OUTPUT_345);
|
||||
ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_GPIO_OUTPUT,
|
||||
EN8811H_GPIO_OUTPUT_345,
|
||||
EN8811H_GPIO_OUTPUT_345);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -1263,9 +1062,9 @@ static int an8811hb_config_serdes_polarity(struct phy_device *phydev)
|
|||
return ret;
|
||||
if (pol == PHY_POL_NORMAL)
|
||||
pbus_value |= AN8811HB_RX_POLARITY_NORMAL;
|
||||
ret = air_buckpbus_reg_modify(phydev, AN8811HB_RX_POLARITY,
|
||||
AN8811HB_RX_POLARITY_NORMAL,
|
||||
pbus_value);
|
||||
ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_RX_POLARITY,
|
||||
AN8811HB_RX_POLARITY_NORMAL,
|
||||
pbus_value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -1276,9 +1075,9 @@ static int an8811hb_config_serdes_polarity(struct phy_device *phydev)
|
|||
pbus_value = 0;
|
||||
if (pol == PHY_POL_NORMAL)
|
||||
pbus_value |= AN8811HB_TX_POLARITY_NORMAL;
|
||||
return air_buckpbus_reg_modify(phydev, AN8811HB_TX_POLARITY,
|
||||
AN8811HB_TX_POLARITY_NORMAL,
|
||||
pbus_value);
|
||||
return air_phy_buckpbus_reg_modify(phydev, AN8811HB_TX_POLARITY,
|
||||
AN8811HB_TX_POLARITY_NORMAL,
|
||||
pbus_value);
|
||||
}
|
||||
|
||||
static int en8811h_config_serdes_polarity(struct phy_device *phydev)
|
||||
|
|
@ -1312,9 +1111,10 @@ static int en8811h_config_serdes_polarity(struct phy_device *phydev)
|
|||
if (pol == PHY_POL_NORMAL)
|
||||
pbus_value |= EN8811H_POLARITY_TX_NORMAL;
|
||||
|
||||
return air_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
|
||||
EN8811H_POLARITY_RX_REVERSE |
|
||||
EN8811H_POLARITY_TX_NORMAL, pbus_value);
|
||||
return air_phy_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
|
||||
EN8811H_POLARITY_RX_REVERSE |
|
||||
EN8811H_POLARITY_TX_NORMAL,
|
||||
pbus_value);
|
||||
}
|
||||
|
||||
static int an8811hb_config_init(struct phy_device *phydev)
|
||||
|
|
@ -1466,8 +1266,8 @@ static int en8811h_read_status(struct phy_device *phydev)
|
|||
val & MDIO_AN_10GBT_STAT_LP2_5G);
|
||||
} else {
|
||||
/* Get link partner 2.5GBASE-T ability from vendor register */
|
||||
ret = air_buckpbus_reg_read(phydev, EN8811H_2P5G_LPA,
|
||||
&pbus_value);
|
||||
ret = air_phy_buckpbus_reg_read(phydev, EN8811H_2P5G_LPA,
|
||||
&pbus_value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
|
||||
|
|
|
|||
213
drivers/net/phy/air_phy_lib.c
Normal file
213
drivers/net/phy/air_phy_lib.c
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Airoha Ethernet PHY common library
|
||||
*
|
||||
* Copyright (C) 2026 Airoha Technology Corp.
|
||||
* Copyright (C) 2026 Collabora Ltd.
|
||||
* Louis-Alexis Eyraud <louisalexis.eyraud@collabora.com>
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/wordpart.h>
|
||||
|
||||
#include "air_phy_lib.h"
|
||||
|
||||
#define AIR_EXT_PAGE_ACCESS 0x1f
|
||||
|
||||
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;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH,
|
||||
upper_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW,
|
||||
lower_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH);
|
||||
if (pbus_data_high < 0)
|
||||
return pbus_data_high;
|
||||
|
||||
pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW);
|
||||
if (pbus_data_low < 0)
|
||||
return pbus_data_low;
|
||||
|
||||
*pbus_data = pbus_data_low | (pbus_data_high << 16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __air_buckpbus_reg_write(struct phy_device *phydev,
|
||||
u32 pbus_address, u32 pbus_data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH,
|
||||
upper_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,
|
||||
lower_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH,
|
||||
upper_16_bits(pbus_data));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW,
|
||||
lower_16_bits(pbus_data));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH,
|
||||
upper_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW,
|
||||
lower_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH);
|
||||
if (pbus_data_high < 0)
|
||||
return pbus_data_high;
|
||||
|
||||
pbus_data_low = __phy_read(phydev, 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, AIR_BPBUS_WR_ADDR_HIGH,
|
||||
upper_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,
|
||||
lower_16_bits(pbus_address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH,
|
||||
upper_16_bits(pbus_data_new));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW,
|
||||
lower_16_bits(pbus_data_new));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address,
|
||||
u32 *pbus_data)
|
||||
{
|
||||
int saved_page;
|
||||
int ret = 0;
|
||||
|
||||
saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
|
||||
|
||||
if (saved_page >= 0) {
|
||||
ret = __air_buckpbus_reg_read(phydev, pbus_address, pbus_data);
|
||||
if (ret < 0)
|
||||
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
|
||||
pbus_address, ret);
|
||||
}
|
||||
|
||||
return phy_restore_page(phydev, saved_page, ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_read);
|
||||
|
||||
int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address,
|
||||
u32 pbus_data)
|
||||
{
|
||||
int saved_page;
|
||||
int ret = 0;
|
||||
|
||||
saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
|
||||
|
||||
if (saved_page >= 0) {
|
||||
ret = __air_buckpbus_reg_write(phydev, pbus_address,
|
||||
pbus_data);
|
||||
if (ret < 0)
|
||||
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
|
||||
pbus_address, ret);
|
||||
}
|
||||
|
||||
return phy_restore_page(phydev, saved_page, ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_write);
|
||||
|
||||
int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,
|
||||
u32 mask, u32 set)
|
||||
{
|
||||
int saved_page;
|
||||
int ret = 0;
|
||||
|
||||
saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
|
||||
|
||||
if (saved_page >= 0) {
|
||||
ret = __air_buckpbus_reg_modify(phydev, pbus_address, mask,
|
||||
set);
|
||||
if (ret < 0)
|
||||
phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
|
||||
pbus_address, ret);
|
||||
}
|
||||
|
||||
return phy_restore_page(phydev, saved_page, ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_modify);
|
||||
|
||||
int air_phy_read_page(struct phy_device *phydev)
|
||||
{
|
||||
return __phy_read(phydev, AIR_EXT_PAGE_ACCESS);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(air_phy_read_page);
|
||||
|
||||
int air_phy_write_page(struct phy_device *phydev, int page)
|
||||
{
|
||||
return __phy_write(phydev, AIR_EXT_PAGE_ACCESS, page);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(air_phy_write_page);
|
||||
|
||||
MODULE_DESCRIPTION("Airoha PHY Library");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Louis-Alexis Eyraud");
|
||||
39
drivers/net/phy/air_phy_lib.h
Normal file
39
drivers/net/phy/air_phy_lib.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Copyright (C) 2026 Airoha Technology Corp.
|
||||
* Copyright (C) 2026 Collabora Ltd.
|
||||
* Louis-Alexis Eyraud <louisalexis.eyraud@collabora.com>
|
||||
*/
|
||||
|
||||
#ifndef __AIR_PHY_LIB_H
|
||||
#define __AIR_PHY_LIB_H
|
||||
|
||||
#include <linux/phy.h>
|
||||
|
||||
#define AIR_PHY_PAGE_STANDARD 0x0000
|
||||
#define AIR_PHY_PAGE_EXTENDED_1 0x0001
|
||||
#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
|
||||
|
||||
int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,
|
||||
u32 mask, u32 set);
|
||||
int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address,
|
||||
u32 *pbus_data);
|
||||
int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address,
|
||||
u32 pbus_data);
|
||||
int air_phy_read_page(struct phy_device *phydev);
|
||||
int air_phy_write_page(struct phy_device *phydev, int page);
|
||||
|
||||
#endif /* __AIR_PHY_LIB_H */
|
||||
Loading…
Reference in New Issue
Block a user