mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-26 17:01:14 +00:00
6f5c301eab
Backport merged upstream patch that adds support for firmware loader from NVMEM or attached filesystem for Aquantia PHYs. Refresh all kernel patches affected by this change. Also update the path for aquantia .ko that got moved to dedicated directory upstream. Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
2307 lines
66 KiB
Diff
2307 lines
66 KiB
Diff
From d2213db3f49bce8e7a87c8de05b9a091f78f654e Mon Sep 17 00:00:00 2001
|
|
From: Christian Marangi <ansuelsmth@gmail.com>
|
|
Date: Tue, 14 Nov 2023 15:08:41 +0100
|
|
Subject: [PATCH 1/3] net: phy: aquantia: move to separate directory
|
|
|
|
Move aquantia PHY driver to separate driectory in preparation for
|
|
firmware loading support to keep things tidy.
|
|
|
|
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
|
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
|
Signed-off-by: David S. Miller <davem@davemloft.net>
|
|
---
|
|
drivers/net/phy/Kconfig | 5 +----
|
|
drivers/net/phy/Makefile | 6 +-----
|
|
drivers/net/phy/aquantia/Kconfig | 5 +++++
|
|
drivers/net/phy/aquantia/Makefile | 6 ++++++
|
|
drivers/net/phy/{ => aquantia}/aquantia.h | 0
|
|
drivers/net/phy/{ => aquantia}/aquantia_hwmon.c | 0
|
|
drivers/net/phy/{ => aquantia}/aquantia_main.c | 0
|
|
7 files changed, 13 insertions(+), 9 deletions(-)
|
|
create mode 100644 drivers/net/phy/aquantia/Kconfig
|
|
create mode 100644 drivers/net/phy/aquantia/Makefile
|
|
rename drivers/net/phy/{ => aquantia}/aquantia.h (100%)
|
|
rename drivers/net/phy/{ => aquantia}/aquantia_hwmon.c (100%)
|
|
rename drivers/net/phy/{ => aquantia}/aquantia_main.c (100%)
|
|
|
|
--- a/drivers/net/phy/Kconfig
|
|
+++ b/drivers/net/phy/Kconfig
|
|
@@ -90,10 +90,7 @@ config ADIN1100_PHY
|
|
Currently supports the:
|
|
- ADIN1100 - Robust,Industrial, Low Power 10BASE-T1L Ethernet PHY
|
|
|
|
-config AQUANTIA_PHY
|
|
- tristate "Aquantia PHYs"
|
|
- help
|
|
- Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405
|
|
+source "drivers/net/phy/aquantia/Kconfig"
|
|
|
|
config AX88796B_PHY
|
|
tristate "Asix PHYs"
|
|
--- a/drivers/net/phy/Makefile
|
|
+++ b/drivers/net/phy/Makefile
|
|
@@ -33,11 +33,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m)
|
|
obj-$(CONFIG_ADIN_PHY) += adin.o
|
|
obj-$(CONFIG_ADIN1100_PHY) += adin1100.o
|
|
obj-$(CONFIG_AMD_PHY) += amd.o
|
|
-aquantia-objs += aquantia_main.o
|
|
-ifdef CONFIG_HWMON
|
|
-aquantia-objs += aquantia_hwmon.o
|
|
-endif
|
|
-obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o
|
|
+obj-$(CONFIG_AQUANTIA_PHY) += aquantia/
|
|
obj-$(CONFIG_AT803X_PHY) += at803x.o
|
|
obj-$(CONFIG_AX88796B_PHY) += ax88796b.o
|
|
obj-$(CONFIG_BCM54140_PHY) += bcm54140.o
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/aquantia/Kconfig
|
|
@@ -0,0 +1,5 @@
|
|
+# SPDX-License-Identifier: GPL-2.0-only
|
|
+config AQUANTIA_PHY
|
|
+ tristate "Aquantia PHYs"
|
|
+ help
|
|
+ Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/aquantia/Makefile
|
|
@@ -0,0 +1,6 @@
|
|
+# SPDX-License-Identifier: GPL-2.0
|
|
+aquantia-objs += aquantia_main.o
|
|
+ifdef CONFIG_HWMON
|
|
+aquantia-objs += aquantia_hwmon.o
|
|
+endif
|
|
+obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o
|
|
--- a/drivers/net/phy/aquantia.h
|
|
+++ /dev/null
|
|
@@ -1,16 +0,0 @@
|
|
-/* SPDX-License-Identifier: GPL-2.0 */
|
|
-/* HWMON driver for Aquantia PHY
|
|
- *
|
|
- * Author: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
|
|
- * Author: Andrew Lunn <andrew@lunn.ch>
|
|
- * Author: Heiner Kallweit <hkallweit1@gmail.com>
|
|
- */
|
|
-
|
|
-#include <linux/device.h>
|
|
-#include <linux/phy.h>
|
|
-
|
|
-#if IS_REACHABLE(CONFIG_HWMON)
|
|
-int aqr_hwmon_probe(struct phy_device *phydev);
|
|
-#else
|
|
-static inline int aqr_hwmon_probe(struct phy_device *phydev) { return 0; }
|
|
-#endif
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/aquantia/aquantia.h
|
|
@@ -0,0 +1,16 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/* HWMON driver for Aquantia PHY
|
|
+ *
|
|
+ * Author: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
|
|
+ * Author: Andrew Lunn <andrew@lunn.ch>
|
|
+ * Author: Heiner Kallweit <hkallweit1@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/device.h>
|
|
+#include <linux/phy.h>
|
|
+
|
|
+#if IS_REACHABLE(CONFIG_HWMON)
|
|
+int aqr_hwmon_probe(struct phy_device *phydev);
|
|
+#else
|
|
+static inline int aqr_hwmon_probe(struct phy_device *phydev) { return 0; }
|
|
+#endif
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/aquantia/aquantia_hwmon.c
|
|
@@ -0,0 +1,250 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/* HWMON driver for Aquantia PHY
|
|
+ *
|
|
+ * Author: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
|
|
+ * Author: Andrew Lunn <andrew@lunn.ch>
|
|
+ * Author: Heiner Kallweit <hkallweit1@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/phy.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/ctype.h>
|
|
+#include <linux/hwmon.h>
|
|
+
|
|
+#include "aquantia.h"
|
|
+
|
|
+/* Vendor specific 1, MDIO_MMD_VEND2 */
|
|
+#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL 0xc421
|
|
+#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL 0xc422
|
|
+#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN 0xc423
|
|
+#define VEND1_THERMAL_PROV_LOW_TEMP_WARN 0xc424
|
|
+#define VEND1_THERMAL_STAT1 0xc820
|
|
+#define VEND1_THERMAL_STAT2 0xc821
|
|
+#define VEND1_THERMAL_STAT2_VALID BIT(0)
|
|
+#define VEND1_GENERAL_STAT1 0xc830
|
|
+#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL BIT(14)
|
|
+#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL BIT(13)
|
|
+#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN BIT(12)
|
|
+#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN BIT(11)
|
|
+
|
|
+#if IS_REACHABLE(CONFIG_HWMON)
|
|
+
|
|
+static umode_t aqr_hwmon_is_visible(const void *data,
|
|
+ enum hwmon_sensor_types type,
|
|
+ u32 attr, int channel)
|
|
+{
|
|
+ if (type != hwmon_temp)
|
|
+ return 0;
|
|
+
|
|
+ switch (attr) {
|
|
+ case hwmon_temp_input:
|
|
+ case hwmon_temp_min_alarm:
|
|
+ case hwmon_temp_max_alarm:
|
|
+ case hwmon_temp_lcrit_alarm:
|
|
+ case hwmon_temp_crit_alarm:
|
|
+ return 0444;
|
|
+ case hwmon_temp_min:
|
|
+ case hwmon_temp_max:
|
|
+ case hwmon_temp_lcrit:
|
|
+ case hwmon_temp_crit:
|
|
+ return 0644;
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int aqr_hwmon_get(struct phy_device *phydev, int reg, long *value)
|
|
+{
|
|
+ int temp = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
|
|
+
|
|
+ if (temp < 0)
|
|
+ return temp;
|
|
+
|
|
+ /* 16 bit value is 2's complement with LSB = 1/256th degree Celsius */
|
|
+ *value = (s16)temp * 1000 / 256;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int aqr_hwmon_set(struct phy_device *phydev, int reg, long value)
|
|
+{
|
|
+ int temp;
|
|
+
|
|
+ if (value >= 128000 || value < -128000)
|
|
+ return -ERANGE;
|
|
+
|
|
+ temp = value * 256 / 1000;
|
|
+
|
|
+ /* temp is in s16 range and we're interested in lower 16 bits only */
|
|
+ return phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, (u16)temp);
|
|
+}
|
|
+
|
|
+static int aqr_hwmon_test_bit(struct phy_device *phydev, int reg, int bit)
|
|
+{
|
|
+ int val = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
|
|
+
|
|
+ if (val < 0)
|
|
+ return val;
|
|
+
|
|
+ return !!(val & bit);
|
|
+}
|
|
+
|
|
+static int aqr_hwmon_status1(struct phy_device *phydev, int bit, long *value)
|
|
+{
|
|
+ int val = aqr_hwmon_test_bit(phydev, VEND1_GENERAL_STAT1, bit);
|
|
+
|
|
+ if (val < 0)
|
|
+ return val;
|
|
+
|
|
+ *value = val;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int aqr_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
|
+ u32 attr, int channel, long *value)
|
|
+{
|
|
+ struct phy_device *phydev = dev_get_drvdata(dev);
|
|
+ int reg;
|
|
+
|
|
+ if (type != hwmon_temp)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ switch (attr) {
|
|
+ case hwmon_temp_input:
|
|
+ reg = aqr_hwmon_test_bit(phydev, VEND1_THERMAL_STAT2,
|
|
+ VEND1_THERMAL_STAT2_VALID);
|
|
+ if (reg < 0)
|
|
+ return reg;
|
|
+ if (!reg)
|
|
+ return -EBUSY;
|
|
+
|
|
+ return aqr_hwmon_get(phydev, VEND1_THERMAL_STAT1, value);
|
|
+
|
|
+ case hwmon_temp_lcrit:
|
|
+ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
|
|
+ value);
|
|
+ case hwmon_temp_min:
|
|
+ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
|
|
+ value);
|
|
+ case hwmon_temp_max:
|
|
+ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
|
|
+ value);
|
|
+ case hwmon_temp_crit:
|
|
+ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
|
|
+ value);
|
|
+ case hwmon_temp_lcrit_alarm:
|
|
+ return aqr_hwmon_status1(phydev,
|
|
+ VEND1_GENERAL_STAT1_LOW_TEMP_FAIL,
|
|
+ value);
|
|
+ case hwmon_temp_min_alarm:
|
|
+ return aqr_hwmon_status1(phydev,
|
|
+ VEND1_GENERAL_STAT1_LOW_TEMP_WARN,
|
|
+ value);
|
|
+ case hwmon_temp_max_alarm:
|
|
+ return aqr_hwmon_status1(phydev,
|
|
+ VEND1_GENERAL_STAT1_HIGH_TEMP_WARN,
|
|
+ value);
|
|
+ case hwmon_temp_crit_alarm:
|
|
+ return aqr_hwmon_status1(phydev,
|
|
+ VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL,
|
|
+ value);
|
|
+ default:
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int aqr_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
|
+ u32 attr, int channel, long value)
|
|
+{
|
|
+ struct phy_device *phydev = dev_get_drvdata(dev);
|
|
+
|
|
+ if (type != hwmon_temp)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ switch (attr) {
|
|
+ case hwmon_temp_lcrit:
|
|
+ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
|
|
+ value);
|
|
+ case hwmon_temp_min:
|
|
+ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
|
|
+ value);
|
|
+ case hwmon_temp_max:
|
|
+ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
|
|
+ value);
|
|
+ case hwmon_temp_crit:
|
|
+ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
|
|
+ value);
|
|
+ default:
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static const struct hwmon_ops aqr_hwmon_ops = {
|
|
+ .is_visible = aqr_hwmon_is_visible,
|
|
+ .read = aqr_hwmon_read,
|
|
+ .write = aqr_hwmon_write,
|
|
+};
|
|
+
|
|
+static u32 aqr_hwmon_chip_config[] = {
|
|
+ HWMON_C_REGISTER_TZ,
|
|
+ 0,
|
|
+};
|
|
+
|
|
+static const struct hwmon_channel_info aqr_hwmon_chip = {
|
|
+ .type = hwmon_chip,
|
|
+ .config = aqr_hwmon_chip_config,
|
|
+};
|
|
+
|
|
+static u32 aqr_hwmon_temp_config[] = {
|
|
+ HWMON_T_INPUT |
|
|
+ HWMON_T_MAX | HWMON_T_MIN |
|
|
+ HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM |
|
|
+ HWMON_T_CRIT | HWMON_T_LCRIT |
|
|
+ HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM,
|
|
+ 0,
|
|
+};
|
|
+
|
|
+static const struct hwmon_channel_info aqr_hwmon_temp = {
|
|
+ .type = hwmon_temp,
|
|
+ .config = aqr_hwmon_temp_config,
|
|
+};
|
|
+
|
|
+static const struct hwmon_channel_info *aqr_hwmon_info[] = {
|
|
+ &aqr_hwmon_chip,
|
|
+ &aqr_hwmon_temp,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+static const struct hwmon_chip_info aqr_hwmon_chip_info = {
|
|
+ .ops = &aqr_hwmon_ops,
|
|
+ .info = aqr_hwmon_info,
|
|
+};
|
|
+
|
|
+int aqr_hwmon_probe(struct phy_device *phydev)
|
|
+{
|
|
+ struct device *dev = &phydev->mdio.dev;
|
|
+ struct device *hwmon_dev;
|
|
+ char *hwmon_name;
|
|
+ int i, j;
|
|
+
|
|
+ hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
|
|
+ if (!hwmon_name)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ for (i = j = 0; hwmon_name[i]; i++) {
|
|
+ if (isalnum(hwmon_name[i])) {
|
|
+ if (i != j)
|
|
+ hwmon_name[j] = hwmon_name[i];
|
|
+ j++;
|
|
+ }
|
|
+ }
|
|
+ hwmon_name[j] = '\0';
|
|
+
|
|
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
|
|
+ phydev, &aqr_hwmon_chip_info, NULL);
|
|
+
|
|
+ return PTR_ERR_OR_ZERO(hwmon_dev);
|
|
+}
|
|
+
|
|
+#endif
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/aquantia/aquantia_main.c
|
|
@@ -0,0 +1,842 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Driver for Aquantia PHY
|
|
+ *
|
|
+ * Author: Shaohui Xie <Shaohui.Xie@freescale.com>
|
|
+ *
|
|
+ * Copyright 2015 Freescale Semiconductor, Inc.
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/bitfield.h>
|
|
+#include <linux/phy.h>
|
|
+
|
|
+#include "aquantia.h"
|
|
+
|
|
+#define PHY_ID_AQ1202 0x03a1b445
|
|
+#define PHY_ID_AQ2104 0x03a1b460
|
|
+#define PHY_ID_AQR105 0x03a1b4a2
|
|
+#define PHY_ID_AQR106 0x03a1b4d0
|
|
+#define PHY_ID_AQR107 0x03a1b4e0
|
|
+#define PHY_ID_AQCS109 0x03a1b5c2
|
|
+#define PHY_ID_AQR405 0x03a1b4b0
|
|
+#define PHY_ID_AQR113C 0x31c31c12
|
|
+
|
|
+#define MDIO_PHYXS_VEND_IF_STATUS 0xe812
|
|
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK GENMASK(7, 3)
|
|
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR 0
|
|
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX 1
|
|
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI 2
|
|
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII 3
|
|
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI 4
|
|
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII 6
|
|
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI 7
|
|
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII 10
|
|
+
|
|
+#define MDIO_AN_VEND_PROV 0xc400
|
|
+#define MDIO_AN_VEND_PROV_1000BASET_FULL BIT(15)
|
|
+#define MDIO_AN_VEND_PROV_1000BASET_HALF BIT(14)
|
|
+#define MDIO_AN_VEND_PROV_5000BASET_FULL BIT(11)
|
|
+#define MDIO_AN_VEND_PROV_2500BASET_FULL BIT(10)
|
|
+#define MDIO_AN_VEND_PROV_DOWNSHIFT_EN BIT(4)
|
|
+#define MDIO_AN_VEND_PROV_DOWNSHIFT_MASK GENMASK(3, 0)
|
|
+#define MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT 4
|
|
+
|
|
+#define MDIO_AN_TX_VEND_STATUS1 0xc800
|
|
+#define MDIO_AN_TX_VEND_STATUS1_RATE_MASK GENMASK(3, 1)
|
|
+#define MDIO_AN_TX_VEND_STATUS1_10BASET 0
|
|
+#define MDIO_AN_TX_VEND_STATUS1_100BASETX 1
|
|
+#define MDIO_AN_TX_VEND_STATUS1_1000BASET 2
|
|
+#define MDIO_AN_TX_VEND_STATUS1_10GBASET 3
|
|
+#define MDIO_AN_TX_VEND_STATUS1_2500BASET 4
|
|
+#define MDIO_AN_TX_VEND_STATUS1_5000BASET 5
|
|
+#define MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX BIT(0)
|
|
+
|
|
+#define MDIO_AN_TX_VEND_INT_STATUS1 0xcc00
|
|
+#define MDIO_AN_TX_VEND_INT_STATUS1_DOWNSHIFT BIT(1)
|
|
+
|
|
+#define MDIO_AN_TX_VEND_INT_STATUS2 0xcc01
|
|
+#define MDIO_AN_TX_VEND_INT_STATUS2_MASK BIT(0)
|
|
+
|
|
+#define MDIO_AN_TX_VEND_INT_MASK2 0xd401
|
|
+#define MDIO_AN_TX_VEND_INT_MASK2_LINK BIT(0)
|
|
+
|
|
+#define MDIO_AN_RX_LP_STAT1 0xe820
|
|
+#define MDIO_AN_RX_LP_STAT1_1000BASET_FULL BIT(15)
|
|
+#define MDIO_AN_RX_LP_STAT1_1000BASET_HALF BIT(14)
|
|
+#define MDIO_AN_RX_LP_STAT1_SHORT_REACH BIT(13)
|
|
+#define MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT BIT(12)
|
|
+#define MDIO_AN_RX_LP_STAT1_AQ_PHY BIT(2)
|
|
+
|
|
+#define MDIO_AN_RX_LP_STAT4 0xe823
|
|
+#define MDIO_AN_RX_LP_STAT4_FW_MAJOR GENMASK(15, 8)
|
|
+#define MDIO_AN_RX_LP_STAT4_FW_MINOR GENMASK(7, 0)
|
|
+
|
|
+#define MDIO_AN_RX_VEND_STAT3 0xe832
|
|
+#define MDIO_AN_RX_VEND_STAT3_AFR BIT(0)
|
|
+
|
|
+/* MDIO_MMD_C22EXT */
|
|
+#define MDIO_C22EXT_STAT_SGMII_RX_GOOD_FRAMES 0xd292
|
|
+#define MDIO_C22EXT_STAT_SGMII_RX_BAD_FRAMES 0xd294
|
|
+#define MDIO_C22EXT_STAT_SGMII_RX_FALSE_CARRIER 0xd297
|
|
+#define MDIO_C22EXT_STAT_SGMII_TX_GOOD_FRAMES 0xd313
|
|
+#define MDIO_C22EXT_STAT_SGMII_TX_BAD_FRAMES 0xd315
|
|
+#define MDIO_C22EXT_STAT_SGMII_TX_FALSE_CARRIER 0xd317
|
|
+#define MDIO_C22EXT_STAT_SGMII_TX_COLLISIONS 0xd318
|
|
+#define MDIO_C22EXT_STAT_SGMII_TX_LINE_COLLISIONS 0xd319
|
|
+#define MDIO_C22EXT_STAT_SGMII_TX_FRAME_ALIGN_ERR 0xd31a
|
|
+#define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES 0xd31b
|
|
+
|
|
+/* Vendor specific 1, MDIO_MMD_VEND1 */
|
|
+#define VEND1_GLOBAL_FW_ID 0x0020
|
|
+#define VEND1_GLOBAL_FW_ID_MAJOR GENMASK(15, 8)
|
|
+#define VEND1_GLOBAL_FW_ID_MINOR GENMASK(7, 0)
|
|
+
|
|
+#define VEND1_GLOBAL_GEN_STAT2 0xc831
|
|
+#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG BIT(15)
|
|
+
|
|
+/* The following registers all have similar layouts; first the registers... */
|
|
+#define VEND1_GLOBAL_CFG_10M 0x0310
|
|
+#define VEND1_GLOBAL_CFG_100M 0x031b
|
|
+#define VEND1_GLOBAL_CFG_1G 0x031c
|
|
+#define VEND1_GLOBAL_CFG_2_5G 0x031d
|
|
+#define VEND1_GLOBAL_CFG_5G 0x031e
|
|
+#define VEND1_GLOBAL_CFG_10G 0x031f
|
|
+/* ...and now the fields */
|
|
+#define VEND1_GLOBAL_CFG_RATE_ADAPT GENMASK(8, 7)
|
|
+#define VEND1_GLOBAL_CFG_RATE_ADAPT_NONE 0
|
|
+#define VEND1_GLOBAL_CFG_RATE_ADAPT_USX 1
|
|
+#define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE 2
|
|
+
|
|
+#define VEND1_GLOBAL_RSVD_STAT1 0xc885
|
|
+#define VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID GENMASK(7, 4)
|
|
+#define VEND1_GLOBAL_RSVD_STAT1_PROV_ID GENMASK(3, 0)
|
|
+
|
|
+#define VEND1_GLOBAL_RSVD_STAT9 0xc88d
|
|
+#define VEND1_GLOBAL_RSVD_STAT9_MODE GENMASK(7, 0)
|
|
+#define VEND1_GLOBAL_RSVD_STAT9_1000BT2 0x23
|
|
+
|
|
+#define VEND1_GLOBAL_INT_STD_STATUS 0xfc00
|
|
+#define VEND1_GLOBAL_INT_VEND_STATUS 0xfc01
|
|
+
|
|
+#define VEND1_GLOBAL_INT_STD_MASK 0xff00
|
|
+#define VEND1_GLOBAL_INT_STD_MASK_PMA1 BIT(15)
|
|
+#define VEND1_GLOBAL_INT_STD_MASK_PMA2 BIT(14)
|
|
+#define VEND1_GLOBAL_INT_STD_MASK_PCS1 BIT(13)
|
|
+#define VEND1_GLOBAL_INT_STD_MASK_PCS2 BIT(12)
|
|
+#define VEND1_GLOBAL_INT_STD_MASK_PCS3 BIT(11)
|
|
+#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS1 BIT(10)
|
|
+#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS2 BIT(9)
|
|
+#define VEND1_GLOBAL_INT_STD_MASK_AN1 BIT(8)
|
|
+#define VEND1_GLOBAL_INT_STD_MASK_AN2 BIT(7)
|
|
+#define VEND1_GLOBAL_INT_STD_MASK_GBE BIT(6)
|
|
+#define VEND1_GLOBAL_INT_STD_MASK_ALL BIT(0)
|
|
+
|
|
+#define VEND1_GLOBAL_INT_VEND_MASK 0xff01
|
|
+#define VEND1_GLOBAL_INT_VEND_MASK_PMA BIT(15)
|
|
+#define VEND1_GLOBAL_INT_VEND_MASK_PCS BIT(14)
|
|
+#define VEND1_GLOBAL_INT_VEND_MASK_PHY_XS BIT(13)
|
|
+#define VEND1_GLOBAL_INT_VEND_MASK_AN BIT(12)
|
|
+#define VEND1_GLOBAL_INT_VEND_MASK_GBE BIT(11)
|
|
+#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL1 BIT(2)
|
|
+#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2 BIT(1)
|
|
+#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 BIT(0)
|
|
+
|
|
+/* Sleep and timeout for checking if the Processor-Intensive
|
|
+ * MDIO operation is finished
|
|
+ */
|
|
+#define AQR107_OP_IN_PROG_SLEEP 1000
|
|
+#define AQR107_OP_IN_PROG_TIMEOUT 100000
|
|
+
|
|
+struct aqr107_hw_stat {
|
|
+ const char *name;
|
|
+ int reg;
|
|
+ int size;
|
|
+};
|
|
+
|
|
+#define SGMII_STAT(n, r, s) { n, MDIO_C22EXT_STAT_SGMII_ ## r, s }
|
|
+static const struct aqr107_hw_stat aqr107_hw_stats[] = {
|
|
+ SGMII_STAT("sgmii_rx_good_frames", RX_GOOD_FRAMES, 26),
|
|
+ SGMII_STAT("sgmii_rx_bad_frames", RX_BAD_FRAMES, 26),
|
|
+ SGMII_STAT("sgmii_rx_false_carrier_events", RX_FALSE_CARRIER, 8),
|
|
+ SGMII_STAT("sgmii_tx_good_frames", TX_GOOD_FRAMES, 26),
|
|
+ SGMII_STAT("sgmii_tx_bad_frames", TX_BAD_FRAMES, 26),
|
|
+ SGMII_STAT("sgmii_tx_false_carrier_events", TX_FALSE_CARRIER, 8),
|
|
+ SGMII_STAT("sgmii_tx_collisions", TX_COLLISIONS, 8),
|
|
+ SGMII_STAT("sgmii_tx_line_collisions", TX_LINE_COLLISIONS, 8),
|
|
+ SGMII_STAT("sgmii_tx_frame_alignment_err", TX_FRAME_ALIGN_ERR, 16),
|
|
+ SGMII_STAT("sgmii_tx_runt_frames", TX_RUNT_FRAMES, 22),
|
|
+};
|
|
+#define AQR107_SGMII_STAT_SZ ARRAY_SIZE(aqr107_hw_stats)
|
|
+
|
|
+struct aqr107_priv {
|
|
+ u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
|
|
+};
|
|
+
|
|
+static int aqr107_get_sset_count(struct phy_device *phydev)
|
|
+{
|
|
+ return AQR107_SGMII_STAT_SZ;
|
|
+}
|
|
+
|
|
+static void aqr107_get_strings(struct phy_device *phydev, u8 *data)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < AQR107_SGMII_STAT_SZ; i++)
|
|
+ strscpy(data + i * ETH_GSTRING_LEN, aqr107_hw_stats[i].name,
|
|
+ ETH_GSTRING_LEN);
|
|
+}
|
|
+
|
|
+static u64 aqr107_get_stat(struct phy_device *phydev, int index)
|
|
+{
|
|
+ const struct aqr107_hw_stat *stat = aqr107_hw_stats + index;
|
|
+ int len_l = min(stat->size, 16);
|
|
+ int len_h = stat->size - len_l;
|
|
+ u64 ret;
|
|
+ int val;
|
|
+
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg);
|
|
+ if (val < 0)
|
|
+ return U64_MAX;
|
|
+
|
|
+ ret = val & GENMASK(len_l - 1, 0);
|
|
+ if (len_h) {
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg + 1);
|
|
+ if (val < 0)
|
|
+ return U64_MAX;
|
|
+
|
|
+ ret += (val & GENMASK(len_h - 1, 0)) << 16;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void aqr107_get_stats(struct phy_device *phydev,
|
|
+ struct ethtool_stats *stats, u64 *data)
|
|
+{
|
|
+ struct aqr107_priv *priv = phydev->priv;
|
|
+ u64 val;
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < AQR107_SGMII_STAT_SZ; i++) {
|
|
+ val = aqr107_get_stat(phydev, i);
|
|
+ if (val == U64_MAX)
|
|
+ phydev_err(phydev, "Reading HW Statistics failed for %s\n",
|
|
+ aqr107_hw_stats[i].name);
|
|
+ else
|
|
+ priv->sgmii_stats[i] += val;
|
|
+
|
|
+ data[i] = priv->sgmii_stats[i];
|
|
+ }
|
|
+}
|
|
+
|
|
+static int aqr_config_aneg(struct phy_device *phydev)
|
|
+{
|
|
+ bool changed = false;
|
|
+ u16 reg;
|
|
+ int ret;
|
|
+
|
|
+ if (phydev->autoneg == AUTONEG_DISABLE)
|
|
+ return genphy_c45_pma_setup_forced(phydev);
|
|
+
|
|
+ ret = genphy_c45_an_config_aneg(phydev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ if (ret > 0)
|
|
+ changed = true;
|
|
+
|
|
+ /* Clause 45 has no standardized support for 1000BaseT, therefore
|
|
+ * use vendor registers for this mode.
|
|
+ */
|
|
+ reg = 0;
|
|
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
|
|
+ phydev->advertising))
|
|
+ reg |= MDIO_AN_VEND_PROV_1000BASET_FULL;
|
|
+
|
|
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
|
|
+ phydev->advertising))
|
|
+ reg |= MDIO_AN_VEND_PROV_1000BASET_HALF;
|
|
+
|
|
+ /* Handle the case when the 2.5G and 5G speeds are not advertised */
|
|
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
|
|
+ phydev->advertising))
|
|
+ reg |= MDIO_AN_VEND_PROV_2500BASET_FULL;
|
|
+
|
|
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
|
|
+ phydev->advertising))
|
|
+ reg |= MDIO_AN_VEND_PROV_5000BASET_FULL;
|
|
+
|
|
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
|
|
+ MDIO_AN_VEND_PROV_1000BASET_HALF |
|
|
+ MDIO_AN_VEND_PROV_1000BASET_FULL |
|
|
+ MDIO_AN_VEND_PROV_2500BASET_FULL |
|
|
+ MDIO_AN_VEND_PROV_5000BASET_FULL, reg);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ if (ret > 0)
|
|
+ changed = true;
|
|
+
|
|
+ return genphy_c45_check_and_restart_aneg(phydev, changed);
|
|
+}
|
|
+
|
|
+static int aqr_config_intr(struct phy_device *phydev)
|
|
+{
|
|
+ bool en = phydev->interrupts == PHY_INTERRUPT_ENABLED;
|
|
+ int err;
|
|
+
|
|
+ if (en) {
|
|
+ /* Clear any pending interrupts before enabling them */
|
|
+ err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ err = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_MASK2,
|
|
+ en ? MDIO_AN_TX_VEND_INT_MASK2_LINK : 0);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_STD_MASK,
|
|
+ en ? VEND1_GLOBAL_INT_STD_MASK_ALL : 0);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_VEND_MASK,
|
|
+ en ? VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 |
|
|
+ VEND1_GLOBAL_INT_VEND_MASK_AN : 0);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ if (!en) {
|
|
+ /* Clear any pending interrupts after we have disabled them */
|
|
+ err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static irqreturn_t aqr_handle_interrupt(struct phy_device *phydev)
|
|
+{
|
|
+ int irq_status;
|
|
+
|
|
+ irq_status = phy_read_mmd(phydev, MDIO_MMD_AN,
|
|
+ MDIO_AN_TX_VEND_INT_STATUS2);
|
|
+ if (irq_status < 0) {
|
|
+ phy_error(phydev);
|
|
+ return IRQ_NONE;
|
|
+ }
|
|
+
|
|
+ if (!(irq_status & MDIO_AN_TX_VEND_INT_STATUS2_MASK))
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ phy_trigger_machine(phydev);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static int aqr_read_status(struct phy_device *phydev)
|
|
+{
|
|
+ int val;
|
|
+
|
|
+ if (phydev->autoneg == AUTONEG_ENABLE) {
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
|
|
+ if (val < 0)
|
|
+ return val;
|
|
+
|
|
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
|
|
+ phydev->lp_advertising,
|
|
+ val & MDIO_AN_RX_LP_STAT1_1000BASET_FULL);
|
|
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
|
|
+ phydev->lp_advertising,
|
|
+ val & MDIO_AN_RX_LP_STAT1_1000BASET_HALF);
|
|
+ }
|
|
+
|
|
+ return genphy_c45_read_status(phydev);
|
|
+}
|
|
+
|
|
+static int aqr107_read_rate(struct phy_device *phydev)
|
|
+{
|
|
+ u32 config_reg;
|
|
+ int val;
|
|
+
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_STATUS1);
|
|
+ if (val < 0)
|
|
+ return val;
|
|
+
|
|
+ if (val & MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX)
|
|
+ phydev->duplex = DUPLEX_FULL;
|
|
+ else
|
|
+ phydev->duplex = DUPLEX_HALF;
|
|
+
|
|
+ switch (FIELD_GET(MDIO_AN_TX_VEND_STATUS1_RATE_MASK, val)) {
|
|
+ case MDIO_AN_TX_VEND_STATUS1_10BASET:
|
|
+ phydev->speed = SPEED_10;
|
|
+ config_reg = VEND1_GLOBAL_CFG_10M;
|
|
+ break;
|
|
+ case MDIO_AN_TX_VEND_STATUS1_100BASETX:
|
|
+ phydev->speed = SPEED_100;
|
|
+ config_reg = VEND1_GLOBAL_CFG_100M;
|
|
+ break;
|
|
+ case MDIO_AN_TX_VEND_STATUS1_1000BASET:
|
|
+ phydev->speed = SPEED_1000;
|
|
+ config_reg = VEND1_GLOBAL_CFG_1G;
|
|
+ break;
|
|
+ case MDIO_AN_TX_VEND_STATUS1_2500BASET:
|
|
+ phydev->speed = SPEED_2500;
|
|
+ config_reg = VEND1_GLOBAL_CFG_2_5G;
|
|
+ break;
|
|
+ case MDIO_AN_TX_VEND_STATUS1_5000BASET:
|
|
+ phydev->speed = SPEED_5000;
|
|
+ config_reg = VEND1_GLOBAL_CFG_5G;
|
|
+ break;
|
|
+ case MDIO_AN_TX_VEND_STATUS1_10GBASET:
|
|
+ phydev->speed = SPEED_10000;
|
|
+ config_reg = VEND1_GLOBAL_CFG_10G;
|
|
+ break;
|
|
+ default:
|
|
+ phydev->speed = SPEED_UNKNOWN;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, config_reg);
|
|
+ if (val < 0)
|
|
+ return val;
|
|
+
|
|
+ if (FIELD_GET(VEND1_GLOBAL_CFG_RATE_ADAPT, val) ==
|
|
+ VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE)
|
|
+ phydev->rate_matching = RATE_MATCH_PAUSE;
|
|
+ else
|
|
+ phydev->rate_matching = RATE_MATCH_NONE;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int aqr107_read_status(struct phy_device *phydev)
|
|
+{
|
|
+ int val, ret;
|
|
+
|
|
+ ret = aqr_read_status(phydev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (!phydev->link || phydev->autoneg == AUTONEG_DISABLE)
|
|
+ return 0;
|
|
+
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MDIO_PHYXS_VEND_IF_STATUS);
|
|
+ if (val < 0)
|
|
+ return val;
|
|
+
|
|
+ switch (FIELD_GET(MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK, val)) {
|
|
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR:
|
|
+ phydev->interface = PHY_INTERFACE_MODE_10GKR;
|
|
+ break;
|
|
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX:
|
|
+ phydev->interface = PHY_INTERFACE_MODE_1000BASEKX;
|
|
+ break;
|
|
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI:
|
|
+ phydev->interface = PHY_INTERFACE_MODE_10GBASER;
|
|
+ break;
|
|
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII:
|
|
+ phydev->interface = PHY_INTERFACE_MODE_USXGMII;
|
|
+ break;
|
|
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI:
|
|
+ phydev->interface = PHY_INTERFACE_MODE_XAUI;
|
|
+ break;
|
|
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII:
|
|
+ phydev->interface = PHY_INTERFACE_MODE_SGMII;
|
|
+ break;
|
|
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI:
|
|
+ phydev->interface = PHY_INTERFACE_MODE_RXAUI;
|
|
+ break;
|
|
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII:
|
|
+ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
|
|
+ break;
|
|
+ default:
|
|
+ phydev->interface = PHY_INTERFACE_MODE_NA;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* Read possibly downshifted rate from vendor register */
|
|
+ return aqr107_read_rate(phydev);
|
|
+}
|
|
+
|
|
+static int aqr107_get_downshift(struct phy_device *phydev, u8 *data)
|
|
+{
|
|
+ int val, cnt, enable;
|
|
+
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV);
|
|
+ if (val < 0)
|
|
+ return val;
|
|
+
|
|
+ enable = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_EN, val);
|
|
+ cnt = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
|
|
+
|
|
+ *data = enable && cnt ? cnt : DOWNSHIFT_DEV_DISABLE;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int aqr107_set_downshift(struct phy_device *phydev, u8 cnt)
|
|
+{
|
|
+ int val = 0;
|
|
+
|
|
+ if (!FIELD_FIT(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt))
|
|
+ return -E2BIG;
|
|
+
|
|
+ if (cnt != DOWNSHIFT_DEV_DISABLE) {
|
|
+ val = MDIO_AN_VEND_PROV_DOWNSHIFT_EN;
|
|
+ val |= FIELD_PREP(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt);
|
|
+ }
|
|
+
|
|
+ return phy_modify_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
|
|
+ MDIO_AN_VEND_PROV_DOWNSHIFT_EN |
|
|
+ MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
|
|
+}
|
|
+
|
|
+static int aqr107_get_tunable(struct phy_device *phydev,
|
|
+ struct ethtool_tunable *tuna, void *data)
|
|
+{
|
|
+ switch (tuna->id) {
|
|
+ case ETHTOOL_PHY_DOWNSHIFT:
|
|
+ return aqr107_get_downshift(phydev, data);
|
|
+ default:
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int aqr107_set_tunable(struct phy_device *phydev,
|
|
+ struct ethtool_tunable *tuna, const void *data)
|
|
+{
|
|
+ switch (tuna->id) {
|
|
+ case ETHTOOL_PHY_DOWNSHIFT:
|
|
+ return aqr107_set_downshift(phydev, *(const u8 *)data);
|
|
+ default:
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* If we configure settings whilst firmware is still initializing the chip,
|
|
+ * then these settings may be overwritten. Therefore make sure chip
|
|
+ * initialization has completed. Use presence of the firmware ID as
|
|
+ * indicator for initialization having completed.
|
|
+ * The chip also provides a "reset completed" bit, but it's cleared after
|
|
+ * read. Therefore function would time out if called again.
|
|
+ */
|
|
+static int aqr107_wait_reset_complete(struct phy_device *phydev)
|
|
+{
|
|
+ int val;
|
|
+
|
|
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
|
|
+ VEND1_GLOBAL_FW_ID, val, val != 0,
|
|
+ 20000, 2000000, false);
|
|
+}
|
|
+
|
|
+static void aqr107_chip_info(struct phy_device *phydev)
|
|
+{
|
|
+ u8 fw_major, fw_minor, build_id, prov_id;
|
|
+ int val;
|
|
+
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_FW_ID);
|
|
+ if (val < 0)
|
|
+ return;
|
|
+
|
|
+ fw_major = FIELD_GET(VEND1_GLOBAL_FW_ID_MAJOR, val);
|
|
+ fw_minor = FIELD_GET(VEND1_GLOBAL_FW_ID_MINOR, val);
|
|
+
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT1);
|
|
+ if (val < 0)
|
|
+ return;
|
|
+
|
|
+ build_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID, val);
|
|
+ prov_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_PROV_ID, val);
|
|
+
|
|
+ phydev_dbg(phydev, "FW %u.%u, Build %u, Provisioning %u\n",
|
|
+ fw_major, fw_minor, build_id, prov_id);
|
|
+}
|
|
+
|
|
+static int aqr107_config_init(struct phy_device *phydev)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ /* Check that the PHY interface type is compatible */
|
|
+ if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
|
|
+ phydev->interface != PHY_INTERFACE_MODE_1000BASEKX &&
|
|
+ phydev->interface != PHY_INTERFACE_MODE_2500BASEX &&
|
|
+ phydev->interface != PHY_INTERFACE_MODE_XGMII &&
|
|
+ phydev->interface != PHY_INTERFACE_MODE_USXGMII &&
|
|
+ phydev->interface != PHY_INTERFACE_MODE_10GKR &&
|
|
+ phydev->interface != PHY_INTERFACE_MODE_10GBASER &&
|
|
+ phydev->interface != PHY_INTERFACE_MODE_XAUI &&
|
|
+ phydev->interface != PHY_INTERFACE_MODE_RXAUI)
|
|
+ return -ENODEV;
|
|
+
|
|
+ WARN(phydev->interface == PHY_INTERFACE_MODE_XGMII,
|
|
+ "Your devicetree is out of date, please update it. The AQR107 family doesn't support XGMII, maybe you mean USXGMII.\n");
|
|
+
|
|
+ ret = aqr107_wait_reset_complete(phydev);
|
|
+ if (!ret)
|
|
+ aqr107_chip_info(phydev);
|
|
+
|
|
+ return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
|
|
+}
|
|
+
|
|
+static int aqcs109_config_init(struct phy_device *phydev)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ /* Check that the PHY interface type is compatible */
|
|
+ if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
|
|
+ phydev->interface != PHY_INTERFACE_MODE_2500BASEX)
|
|
+ return -ENODEV;
|
|
+
|
|
+ ret = aqr107_wait_reset_complete(phydev);
|
|
+ if (!ret)
|
|
+ aqr107_chip_info(phydev);
|
|
+
|
|
+ /* AQCS109 belongs to a chip family partially supporting 10G and 5G.
|
|
+ * PMA speed ability bits are the same for all members of the family,
|
|
+ * AQCS109 however supports speeds up to 2.5G only.
|
|
+ */
|
|
+ phy_set_max_speed(phydev, SPEED_2500);
|
|
+
|
|
+ return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
|
|
+}
|
|
+
|
|
+static void aqr107_link_change_notify(struct phy_device *phydev)
|
|
+{
|
|
+ u8 fw_major, fw_minor;
|
|
+ bool downshift, short_reach, afr;
|
|
+ int mode, val;
|
|
+
|
|
+ if (phydev->state != PHY_RUNNING || phydev->autoneg == AUTONEG_DISABLE)
|
|
+ return;
|
|
+
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
|
|
+ /* call failed or link partner is no Aquantia PHY */
|
|
+ if (val < 0 || !(val & MDIO_AN_RX_LP_STAT1_AQ_PHY))
|
|
+ return;
|
|
+
|
|
+ short_reach = val & MDIO_AN_RX_LP_STAT1_SHORT_REACH;
|
|
+ downshift = val & MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT;
|
|
+
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT4);
|
|
+ if (val < 0)
|
|
+ return;
|
|
+
|
|
+ fw_major = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MAJOR, val);
|
|
+ fw_minor = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MINOR, val);
|
|
+
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_VEND_STAT3);
|
|
+ if (val < 0)
|
|
+ return;
|
|
+
|
|
+ afr = val & MDIO_AN_RX_VEND_STAT3_AFR;
|
|
+
|
|
+ phydev_dbg(phydev, "Link partner is Aquantia PHY, FW %u.%u%s%s%s\n",
|
|
+ fw_major, fw_minor,
|
|
+ short_reach ? ", short reach mode" : "",
|
|
+ downshift ? ", fast-retrain downshift advertised" : "",
|
|
+ afr ? ", fast reframe advertised" : "");
|
|
+
|
|
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT9);
|
|
+ if (val < 0)
|
|
+ return;
|
|
+
|
|
+ mode = FIELD_GET(VEND1_GLOBAL_RSVD_STAT9_MODE, val);
|
|
+ if (mode == VEND1_GLOBAL_RSVD_STAT9_1000BT2)
|
|
+ phydev_info(phydev, "Aquantia 1000Base-T2 mode active\n");
|
|
+}
|
|
+
|
|
+static int aqr107_wait_processor_intensive_op(struct phy_device *phydev)
|
|
+{
|
|
+ int val, err;
|
|
+
|
|
+ /* The datasheet notes to wait at least 1ms after issuing a
|
|
+ * processor intensive operation before checking.
|
|
+ * We cannot use the 'sleep_before_read' parameter of read_poll_timeout
|
|
+ * because that just determines the maximum time slept, not the minimum.
|
|
+ */
|
|
+ usleep_range(1000, 5000);
|
|
+
|
|
+ err = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
|
|
+ VEND1_GLOBAL_GEN_STAT2, val,
|
|
+ !(val & VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG),
|
|
+ AQR107_OP_IN_PROG_SLEEP,
|
|
+ AQR107_OP_IN_PROG_TIMEOUT, false);
|
|
+ if (err) {
|
|
+ phydev_err(phydev, "timeout: processor-intensive MDIO operation\n");
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int aqr107_get_rate_matching(struct phy_device *phydev,
|
|
+ phy_interface_t iface)
|
|
+{
|
|
+ if (iface == PHY_INTERFACE_MODE_10GBASER ||
|
|
+ iface == PHY_INTERFACE_MODE_2500BASEX ||
|
|
+ iface == PHY_INTERFACE_MODE_NA)
|
|
+ return RATE_MATCH_PAUSE;
|
|
+ return RATE_MATCH_NONE;
|
|
+}
|
|
+
|
|
+static int aqr107_suspend(struct phy_device *phydev)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
|
|
+ MDIO_CTRL1_LPOWER);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ return aqr107_wait_processor_intensive_op(phydev);
|
|
+}
|
|
+
|
|
+static int aqr107_resume(struct phy_device *phydev)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
|
|
+ MDIO_CTRL1_LPOWER);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ return aqr107_wait_processor_intensive_op(phydev);
|
|
+}
|
|
+
|
|
+static int aqr107_probe(struct phy_device *phydev)
|
|
+{
|
|
+ phydev->priv = devm_kzalloc(&phydev->mdio.dev,
|
|
+ sizeof(struct aqr107_priv), GFP_KERNEL);
|
|
+ if (!phydev->priv)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ return aqr_hwmon_probe(phydev);
|
|
+}
|
|
+
|
|
+static struct phy_driver aqr_driver[] = {
|
|
+{
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_AQ1202),
|
|
+ .name = "Aquantia AQ1202",
|
|
+ .config_aneg = aqr_config_aneg,
|
|
+ .config_intr = aqr_config_intr,
|
|
+ .handle_interrupt = aqr_handle_interrupt,
|
|
+ .read_status = aqr_read_status,
|
|
+},
|
|
+{
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_AQ2104),
|
|
+ .name = "Aquantia AQ2104",
|
|
+ .config_aneg = aqr_config_aneg,
|
|
+ .config_intr = aqr_config_intr,
|
|
+ .handle_interrupt = aqr_handle_interrupt,
|
|
+ .read_status = aqr_read_status,
|
|
+},
|
|
+{
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR105),
|
|
+ .name = "Aquantia AQR105",
|
|
+ .config_aneg = aqr_config_aneg,
|
|
+ .config_intr = aqr_config_intr,
|
|
+ .handle_interrupt = aqr_handle_interrupt,
|
|
+ .read_status = aqr_read_status,
|
|
+ .suspend = aqr107_suspend,
|
|
+ .resume = aqr107_resume,
|
|
+},
|
|
+{
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR106),
|
|
+ .name = "Aquantia AQR106",
|
|
+ .config_aneg = aqr_config_aneg,
|
|
+ .config_intr = aqr_config_intr,
|
|
+ .handle_interrupt = aqr_handle_interrupt,
|
|
+ .read_status = aqr_read_status,
|
|
+},
|
|
+{
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR107),
|
|
+ .name = "Aquantia AQR107",
|
|
+ .probe = aqr107_probe,
|
|
+ .get_rate_matching = aqr107_get_rate_matching,
|
|
+ .config_init = aqr107_config_init,
|
|
+ .config_aneg = aqr_config_aneg,
|
|
+ .config_intr = aqr_config_intr,
|
|
+ .handle_interrupt = aqr_handle_interrupt,
|
|
+ .read_status = aqr107_read_status,
|
|
+ .get_tunable = aqr107_get_tunable,
|
|
+ .set_tunable = aqr107_set_tunable,
|
|
+ .suspend = aqr107_suspend,
|
|
+ .resume = aqr107_resume,
|
|
+ .get_sset_count = aqr107_get_sset_count,
|
|
+ .get_strings = aqr107_get_strings,
|
|
+ .get_stats = aqr107_get_stats,
|
|
+ .link_change_notify = aqr107_link_change_notify,
|
|
+},
|
|
+{
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_AQCS109),
|
|
+ .name = "Aquantia AQCS109",
|
|
+ .probe = aqr107_probe,
|
|
+ .get_rate_matching = aqr107_get_rate_matching,
|
|
+ .config_init = aqcs109_config_init,
|
|
+ .config_aneg = aqr_config_aneg,
|
|
+ .config_intr = aqr_config_intr,
|
|
+ .handle_interrupt = aqr_handle_interrupt,
|
|
+ .read_status = aqr107_read_status,
|
|
+ .get_tunable = aqr107_get_tunable,
|
|
+ .set_tunable = aqr107_set_tunable,
|
|
+ .suspend = aqr107_suspend,
|
|
+ .resume = aqr107_resume,
|
|
+ .get_sset_count = aqr107_get_sset_count,
|
|
+ .get_strings = aqr107_get_strings,
|
|
+ .get_stats = aqr107_get_stats,
|
|
+ .link_change_notify = aqr107_link_change_notify,
|
|
+},
|
|
+{
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR405),
|
|
+ .name = "Aquantia AQR405",
|
|
+ .config_aneg = aqr_config_aneg,
|
|
+ .config_intr = aqr_config_intr,
|
|
+ .handle_interrupt = aqr_handle_interrupt,
|
|
+ .read_status = aqr_read_status,
|
|
+},
|
|
+{
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR113C),
|
|
+ .name = "Aquantia AQR113C",
|
|
+ .probe = aqr107_probe,
|
|
+ .get_rate_matching = aqr107_get_rate_matching,
|
|
+ .config_init = aqr107_config_init,
|
|
+ .config_aneg = aqr_config_aneg,
|
|
+ .config_intr = aqr_config_intr,
|
|
+ .handle_interrupt = aqr_handle_interrupt,
|
|
+ .read_status = aqr107_read_status,
|
|
+ .get_tunable = aqr107_get_tunable,
|
|
+ .set_tunable = aqr107_set_tunable,
|
|
+ .suspend = aqr107_suspend,
|
|
+ .resume = aqr107_resume,
|
|
+ .get_sset_count = aqr107_get_sset_count,
|
|
+ .get_strings = aqr107_get_strings,
|
|
+ .get_stats = aqr107_get_stats,
|
|
+ .link_change_notify = aqr107_link_change_notify,
|
|
+},
|
|
+};
|
|
+
|
|
+module_phy_driver(aqr_driver);
|
|
+
|
|
+static struct mdio_device_id __maybe_unused aqr_tbl[] = {
|
|
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQ1202) },
|
|
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQ2104) },
|
|
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR105) },
|
|
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR106) },
|
|
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR107) },
|
|
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQCS109) },
|
|
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR405) },
|
|
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) },
|
|
+ { }
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(mdio, aqr_tbl);
|
|
+
|
|
+MODULE_DESCRIPTION("Aquantia PHY driver");
|
|
+MODULE_AUTHOR("Shaohui Xie <Shaohui.Xie@freescale.com>");
|
|
+MODULE_LICENSE("GPL v2");
|
|
--- a/drivers/net/phy/aquantia_hwmon.c
|
|
+++ /dev/null
|
|
@@ -1,250 +0,0 @@
|
|
-// SPDX-License-Identifier: GPL-2.0
|
|
-/* HWMON driver for Aquantia PHY
|
|
- *
|
|
- * Author: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
|
|
- * Author: Andrew Lunn <andrew@lunn.ch>
|
|
- * Author: Heiner Kallweit <hkallweit1@gmail.com>
|
|
- */
|
|
-
|
|
-#include <linux/phy.h>
|
|
-#include <linux/device.h>
|
|
-#include <linux/ctype.h>
|
|
-#include <linux/hwmon.h>
|
|
-
|
|
-#include "aquantia.h"
|
|
-
|
|
-/* Vendor specific 1, MDIO_MMD_VEND2 */
|
|
-#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL 0xc421
|
|
-#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL 0xc422
|
|
-#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN 0xc423
|
|
-#define VEND1_THERMAL_PROV_LOW_TEMP_WARN 0xc424
|
|
-#define VEND1_THERMAL_STAT1 0xc820
|
|
-#define VEND1_THERMAL_STAT2 0xc821
|
|
-#define VEND1_THERMAL_STAT2_VALID BIT(0)
|
|
-#define VEND1_GENERAL_STAT1 0xc830
|
|
-#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL BIT(14)
|
|
-#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL BIT(13)
|
|
-#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN BIT(12)
|
|
-#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN BIT(11)
|
|
-
|
|
-#if IS_REACHABLE(CONFIG_HWMON)
|
|
-
|
|
-static umode_t aqr_hwmon_is_visible(const void *data,
|
|
- enum hwmon_sensor_types type,
|
|
- u32 attr, int channel)
|
|
-{
|
|
- if (type != hwmon_temp)
|
|
- return 0;
|
|
-
|
|
- switch (attr) {
|
|
- case hwmon_temp_input:
|
|
- case hwmon_temp_min_alarm:
|
|
- case hwmon_temp_max_alarm:
|
|
- case hwmon_temp_lcrit_alarm:
|
|
- case hwmon_temp_crit_alarm:
|
|
- return 0444;
|
|
- case hwmon_temp_min:
|
|
- case hwmon_temp_max:
|
|
- case hwmon_temp_lcrit:
|
|
- case hwmon_temp_crit:
|
|
- return 0644;
|
|
- default:
|
|
- return 0;
|
|
- }
|
|
-}
|
|
-
|
|
-static int aqr_hwmon_get(struct phy_device *phydev, int reg, long *value)
|
|
-{
|
|
- int temp = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
|
|
-
|
|
- if (temp < 0)
|
|
- return temp;
|
|
-
|
|
- /* 16 bit value is 2's complement with LSB = 1/256th degree Celsius */
|
|
- *value = (s16)temp * 1000 / 256;
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int aqr_hwmon_set(struct phy_device *phydev, int reg, long value)
|
|
-{
|
|
- int temp;
|
|
-
|
|
- if (value >= 128000 || value < -128000)
|
|
- return -ERANGE;
|
|
-
|
|
- temp = value * 256 / 1000;
|
|
-
|
|
- /* temp is in s16 range and we're interested in lower 16 bits only */
|
|
- return phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, (u16)temp);
|
|
-}
|
|
-
|
|
-static int aqr_hwmon_test_bit(struct phy_device *phydev, int reg, int bit)
|
|
-{
|
|
- int val = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
|
|
-
|
|
- if (val < 0)
|
|
- return val;
|
|
-
|
|
- return !!(val & bit);
|
|
-}
|
|
-
|
|
-static int aqr_hwmon_status1(struct phy_device *phydev, int bit, long *value)
|
|
-{
|
|
- int val = aqr_hwmon_test_bit(phydev, VEND1_GENERAL_STAT1, bit);
|
|
-
|
|
- if (val < 0)
|
|
- return val;
|
|
-
|
|
- *value = val;
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int aqr_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
|
- u32 attr, int channel, long *value)
|
|
-{
|
|
- struct phy_device *phydev = dev_get_drvdata(dev);
|
|
- int reg;
|
|
-
|
|
- if (type != hwmon_temp)
|
|
- return -EOPNOTSUPP;
|
|
-
|
|
- switch (attr) {
|
|
- case hwmon_temp_input:
|
|
- reg = aqr_hwmon_test_bit(phydev, VEND1_THERMAL_STAT2,
|
|
- VEND1_THERMAL_STAT2_VALID);
|
|
- if (reg < 0)
|
|
- return reg;
|
|
- if (!reg)
|
|
- return -EBUSY;
|
|
-
|
|
- return aqr_hwmon_get(phydev, VEND1_THERMAL_STAT1, value);
|
|
-
|
|
- case hwmon_temp_lcrit:
|
|
- return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
|
|
- value);
|
|
- case hwmon_temp_min:
|
|
- return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
|
|
- value);
|
|
- case hwmon_temp_max:
|
|
- return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
|
|
- value);
|
|
- case hwmon_temp_crit:
|
|
- return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
|
|
- value);
|
|
- case hwmon_temp_lcrit_alarm:
|
|
- return aqr_hwmon_status1(phydev,
|
|
- VEND1_GENERAL_STAT1_LOW_TEMP_FAIL,
|
|
- value);
|
|
- case hwmon_temp_min_alarm:
|
|
- return aqr_hwmon_status1(phydev,
|
|
- VEND1_GENERAL_STAT1_LOW_TEMP_WARN,
|
|
- value);
|
|
- case hwmon_temp_max_alarm:
|
|
- return aqr_hwmon_status1(phydev,
|
|
- VEND1_GENERAL_STAT1_HIGH_TEMP_WARN,
|
|
- value);
|
|
- case hwmon_temp_crit_alarm:
|
|
- return aqr_hwmon_status1(phydev,
|
|
- VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL,
|
|
- value);
|
|
- default:
|
|
- return -EOPNOTSUPP;
|
|
- }
|
|
-}
|
|
-
|
|
-static int aqr_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
|
- u32 attr, int channel, long value)
|
|
-{
|
|
- struct phy_device *phydev = dev_get_drvdata(dev);
|
|
-
|
|
- if (type != hwmon_temp)
|
|
- return -EOPNOTSUPP;
|
|
-
|
|
- switch (attr) {
|
|
- case hwmon_temp_lcrit:
|
|
- return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
|
|
- value);
|
|
- case hwmon_temp_min:
|
|
- return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
|
|
- value);
|
|
- case hwmon_temp_max:
|
|
- return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
|
|
- value);
|
|
- case hwmon_temp_crit:
|
|
- return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
|
|
- value);
|
|
- default:
|
|
- return -EOPNOTSUPP;
|
|
- }
|
|
-}
|
|
-
|
|
-static const struct hwmon_ops aqr_hwmon_ops = {
|
|
- .is_visible = aqr_hwmon_is_visible,
|
|
- .read = aqr_hwmon_read,
|
|
- .write = aqr_hwmon_write,
|
|
-};
|
|
-
|
|
-static u32 aqr_hwmon_chip_config[] = {
|
|
- HWMON_C_REGISTER_TZ,
|
|
- 0,
|
|
-};
|
|
-
|
|
-static const struct hwmon_channel_info aqr_hwmon_chip = {
|
|
- .type = hwmon_chip,
|
|
- .config = aqr_hwmon_chip_config,
|
|
-};
|
|
-
|
|
-static u32 aqr_hwmon_temp_config[] = {
|
|
- HWMON_T_INPUT |
|
|
- HWMON_T_MAX | HWMON_T_MIN |
|
|
- HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM |
|
|
- HWMON_T_CRIT | HWMON_T_LCRIT |
|
|
- HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM,
|
|
- 0,
|
|
-};
|
|
-
|
|
-static const struct hwmon_channel_info aqr_hwmon_temp = {
|
|
- .type = hwmon_temp,
|
|
- .config = aqr_hwmon_temp_config,
|
|
-};
|
|
-
|
|
-static const struct hwmon_channel_info *aqr_hwmon_info[] = {
|
|
- &aqr_hwmon_chip,
|
|
- &aqr_hwmon_temp,
|
|
- NULL,
|
|
-};
|
|
-
|
|
-static const struct hwmon_chip_info aqr_hwmon_chip_info = {
|
|
- .ops = &aqr_hwmon_ops,
|
|
- .info = aqr_hwmon_info,
|
|
-};
|
|
-
|
|
-int aqr_hwmon_probe(struct phy_device *phydev)
|
|
-{
|
|
- struct device *dev = &phydev->mdio.dev;
|
|
- struct device *hwmon_dev;
|
|
- char *hwmon_name;
|
|
- int i, j;
|
|
-
|
|
- hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
|
|
- if (!hwmon_name)
|
|
- return -ENOMEM;
|
|
-
|
|
- for (i = j = 0; hwmon_name[i]; i++) {
|
|
- if (isalnum(hwmon_name[i])) {
|
|
- if (i != j)
|
|
- hwmon_name[j] = hwmon_name[i];
|
|
- j++;
|
|
- }
|
|
- }
|
|
- hwmon_name[j] = '\0';
|
|
-
|
|
- hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
|
|
- phydev, &aqr_hwmon_chip_info, NULL);
|
|
-
|
|
- return PTR_ERR_OR_ZERO(hwmon_dev);
|
|
-}
|
|
-
|
|
-#endif
|
|
--- a/drivers/net/phy/aquantia_main.c
|
|
+++ /dev/null
|
|
@@ -1,842 +0,0 @@
|
|
-// SPDX-License-Identifier: GPL-2.0
|
|
-/*
|
|
- * Driver for Aquantia PHY
|
|
- *
|
|
- * Author: Shaohui Xie <Shaohui.Xie@freescale.com>
|
|
- *
|
|
- * Copyright 2015 Freescale Semiconductor, Inc.
|
|
- */
|
|
-
|
|
-#include <linux/kernel.h>
|
|
-#include <linux/module.h>
|
|
-#include <linux/delay.h>
|
|
-#include <linux/bitfield.h>
|
|
-#include <linux/phy.h>
|
|
-
|
|
-#include "aquantia.h"
|
|
-
|
|
-#define PHY_ID_AQ1202 0x03a1b445
|
|
-#define PHY_ID_AQ2104 0x03a1b460
|
|
-#define PHY_ID_AQR105 0x03a1b4a2
|
|
-#define PHY_ID_AQR106 0x03a1b4d0
|
|
-#define PHY_ID_AQR107 0x03a1b4e0
|
|
-#define PHY_ID_AQCS109 0x03a1b5c2
|
|
-#define PHY_ID_AQR405 0x03a1b4b0
|
|
-#define PHY_ID_AQR113C 0x31c31c12
|
|
-
|
|
-#define MDIO_PHYXS_VEND_IF_STATUS 0xe812
|
|
-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK GENMASK(7, 3)
|
|
-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR 0
|
|
-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX 1
|
|
-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI 2
|
|
-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII 3
|
|
-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI 4
|
|
-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII 6
|
|
-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI 7
|
|
-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII 10
|
|
-
|
|
-#define MDIO_AN_VEND_PROV 0xc400
|
|
-#define MDIO_AN_VEND_PROV_1000BASET_FULL BIT(15)
|
|
-#define MDIO_AN_VEND_PROV_1000BASET_HALF BIT(14)
|
|
-#define MDIO_AN_VEND_PROV_5000BASET_FULL BIT(11)
|
|
-#define MDIO_AN_VEND_PROV_2500BASET_FULL BIT(10)
|
|
-#define MDIO_AN_VEND_PROV_DOWNSHIFT_EN BIT(4)
|
|
-#define MDIO_AN_VEND_PROV_DOWNSHIFT_MASK GENMASK(3, 0)
|
|
-#define MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT 4
|
|
-
|
|
-#define MDIO_AN_TX_VEND_STATUS1 0xc800
|
|
-#define MDIO_AN_TX_VEND_STATUS1_RATE_MASK GENMASK(3, 1)
|
|
-#define MDIO_AN_TX_VEND_STATUS1_10BASET 0
|
|
-#define MDIO_AN_TX_VEND_STATUS1_100BASETX 1
|
|
-#define MDIO_AN_TX_VEND_STATUS1_1000BASET 2
|
|
-#define MDIO_AN_TX_VEND_STATUS1_10GBASET 3
|
|
-#define MDIO_AN_TX_VEND_STATUS1_2500BASET 4
|
|
-#define MDIO_AN_TX_VEND_STATUS1_5000BASET 5
|
|
-#define MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX BIT(0)
|
|
-
|
|
-#define MDIO_AN_TX_VEND_INT_STATUS1 0xcc00
|
|
-#define MDIO_AN_TX_VEND_INT_STATUS1_DOWNSHIFT BIT(1)
|
|
-
|
|
-#define MDIO_AN_TX_VEND_INT_STATUS2 0xcc01
|
|
-#define MDIO_AN_TX_VEND_INT_STATUS2_MASK BIT(0)
|
|
-
|
|
-#define MDIO_AN_TX_VEND_INT_MASK2 0xd401
|
|
-#define MDIO_AN_TX_VEND_INT_MASK2_LINK BIT(0)
|
|
-
|
|
-#define MDIO_AN_RX_LP_STAT1 0xe820
|
|
-#define MDIO_AN_RX_LP_STAT1_1000BASET_FULL BIT(15)
|
|
-#define MDIO_AN_RX_LP_STAT1_1000BASET_HALF BIT(14)
|
|
-#define MDIO_AN_RX_LP_STAT1_SHORT_REACH BIT(13)
|
|
-#define MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT BIT(12)
|
|
-#define MDIO_AN_RX_LP_STAT1_AQ_PHY BIT(2)
|
|
-
|
|
-#define MDIO_AN_RX_LP_STAT4 0xe823
|
|
-#define MDIO_AN_RX_LP_STAT4_FW_MAJOR GENMASK(15, 8)
|
|
-#define MDIO_AN_RX_LP_STAT4_FW_MINOR GENMASK(7, 0)
|
|
-
|
|
-#define MDIO_AN_RX_VEND_STAT3 0xe832
|
|
-#define MDIO_AN_RX_VEND_STAT3_AFR BIT(0)
|
|
-
|
|
-/* MDIO_MMD_C22EXT */
|
|
-#define MDIO_C22EXT_STAT_SGMII_RX_GOOD_FRAMES 0xd292
|
|
-#define MDIO_C22EXT_STAT_SGMII_RX_BAD_FRAMES 0xd294
|
|
-#define MDIO_C22EXT_STAT_SGMII_RX_FALSE_CARRIER 0xd297
|
|
-#define MDIO_C22EXT_STAT_SGMII_TX_GOOD_FRAMES 0xd313
|
|
-#define MDIO_C22EXT_STAT_SGMII_TX_BAD_FRAMES 0xd315
|
|
-#define MDIO_C22EXT_STAT_SGMII_TX_FALSE_CARRIER 0xd317
|
|
-#define MDIO_C22EXT_STAT_SGMII_TX_COLLISIONS 0xd318
|
|
-#define MDIO_C22EXT_STAT_SGMII_TX_LINE_COLLISIONS 0xd319
|
|
-#define MDIO_C22EXT_STAT_SGMII_TX_FRAME_ALIGN_ERR 0xd31a
|
|
-#define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES 0xd31b
|
|
-
|
|
-/* Vendor specific 1, MDIO_MMD_VEND1 */
|
|
-#define VEND1_GLOBAL_FW_ID 0x0020
|
|
-#define VEND1_GLOBAL_FW_ID_MAJOR GENMASK(15, 8)
|
|
-#define VEND1_GLOBAL_FW_ID_MINOR GENMASK(7, 0)
|
|
-
|
|
-#define VEND1_GLOBAL_GEN_STAT2 0xc831
|
|
-#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG BIT(15)
|
|
-
|
|
-/* The following registers all have similar layouts; first the registers... */
|
|
-#define VEND1_GLOBAL_CFG_10M 0x0310
|
|
-#define VEND1_GLOBAL_CFG_100M 0x031b
|
|
-#define VEND1_GLOBAL_CFG_1G 0x031c
|
|
-#define VEND1_GLOBAL_CFG_2_5G 0x031d
|
|
-#define VEND1_GLOBAL_CFG_5G 0x031e
|
|
-#define VEND1_GLOBAL_CFG_10G 0x031f
|
|
-/* ...and now the fields */
|
|
-#define VEND1_GLOBAL_CFG_RATE_ADAPT GENMASK(8, 7)
|
|
-#define VEND1_GLOBAL_CFG_RATE_ADAPT_NONE 0
|
|
-#define VEND1_GLOBAL_CFG_RATE_ADAPT_USX 1
|
|
-#define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE 2
|
|
-
|
|
-#define VEND1_GLOBAL_RSVD_STAT1 0xc885
|
|
-#define VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID GENMASK(7, 4)
|
|
-#define VEND1_GLOBAL_RSVD_STAT1_PROV_ID GENMASK(3, 0)
|
|
-
|
|
-#define VEND1_GLOBAL_RSVD_STAT9 0xc88d
|
|
-#define VEND1_GLOBAL_RSVD_STAT9_MODE GENMASK(7, 0)
|
|
-#define VEND1_GLOBAL_RSVD_STAT9_1000BT2 0x23
|
|
-
|
|
-#define VEND1_GLOBAL_INT_STD_STATUS 0xfc00
|
|
-#define VEND1_GLOBAL_INT_VEND_STATUS 0xfc01
|
|
-
|
|
-#define VEND1_GLOBAL_INT_STD_MASK 0xff00
|
|
-#define VEND1_GLOBAL_INT_STD_MASK_PMA1 BIT(15)
|
|
-#define VEND1_GLOBAL_INT_STD_MASK_PMA2 BIT(14)
|
|
-#define VEND1_GLOBAL_INT_STD_MASK_PCS1 BIT(13)
|
|
-#define VEND1_GLOBAL_INT_STD_MASK_PCS2 BIT(12)
|
|
-#define VEND1_GLOBAL_INT_STD_MASK_PCS3 BIT(11)
|
|
-#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS1 BIT(10)
|
|
-#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS2 BIT(9)
|
|
-#define VEND1_GLOBAL_INT_STD_MASK_AN1 BIT(8)
|
|
-#define VEND1_GLOBAL_INT_STD_MASK_AN2 BIT(7)
|
|
-#define VEND1_GLOBAL_INT_STD_MASK_GBE BIT(6)
|
|
-#define VEND1_GLOBAL_INT_STD_MASK_ALL BIT(0)
|
|
-
|
|
-#define VEND1_GLOBAL_INT_VEND_MASK 0xff01
|
|
-#define VEND1_GLOBAL_INT_VEND_MASK_PMA BIT(15)
|
|
-#define VEND1_GLOBAL_INT_VEND_MASK_PCS BIT(14)
|
|
-#define VEND1_GLOBAL_INT_VEND_MASK_PHY_XS BIT(13)
|
|
-#define VEND1_GLOBAL_INT_VEND_MASK_AN BIT(12)
|
|
-#define VEND1_GLOBAL_INT_VEND_MASK_GBE BIT(11)
|
|
-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL1 BIT(2)
|
|
-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2 BIT(1)
|
|
-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 BIT(0)
|
|
-
|
|
-/* Sleep and timeout for checking if the Processor-Intensive
|
|
- * MDIO operation is finished
|
|
- */
|
|
-#define AQR107_OP_IN_PROG_SLEEP 1000
|
|
-#define AQR107_OP_IN_PROG_TIMEOUT 100000
|
|
-
|
|
-struct aqr107_hw_stat {
|
|
- const char *name;
|
|
- int reg;
|
|
- int size;
|
|
-};
|
|
-
|
|
-#define SGMII_STAT(n, r, s) { n, MDIO_C22EXT_STAT_SGMII_ ## r, s }
|
|
-static const struct aqr107_hw_stat aqr107_hw_stats[] = {
|
|
- SGMII_STAT("sgmii_rx_good_frames", RX_GOOD_FRAMES, 26),
|
|
- SGMII_STAT("sgmii_rx_bad_frames", RX_BAD_FRAMES, 26),
|
|
- SGMII_STAT("sgmii_rx_false_carrier_events", RX_FALSE_CARRIER, 8),
|
|
- SGMII_STAT("sgmii_tx_good_frames", TX_GOOD_FRAMES, 26),
|
|
- SGMII_STAT("sgmii_tx_bad_frames", TX_BAD_FRAMES, 26),
|
|
- SGMII_STAT("sgmii_tx_false_carrier_events", TX_FALSE_CARRIER, 8),
|
|
- SGMII_STAT("sgmii_tx_collisions", TX_COLLISIONS, 8),
|
|
- SGMII_STAT("sgmii_tx_line_collisions", TX_LINE_COLLISIONS, 8),
|
|
- SGMII_STAT("sgmii_tx_frame_alignment_err", TX_FRAME_ALIGN_ERR, 16),
|
|
- SGMII_STAT("sgmii_tx_runt_frames", TX_RUNT_FRAMES, 22),
|
|
-};
|
|
-#define AQR107_SGMII_STAT_SZ ARRAY_SIZE(aqr107_hw_stats)
|
|
-
|
|
-struct aqr107_priv {
|
|
- u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
|
|
-};
|
|
-
|
|
-static int aqr107_get_sset_count(struct phy_device *phydev)
|
|
-{
|
|
- return AQR107_SGMII_STAT_SZ;
|
|
-}
|
|
-
|
|
-static void aqr107_get_strings(struct phy_device *phydev, u8 *data)
|
|
-{
|
|
- int i;
|
|
-
|
|
- for (i = 0; i < AQR107_SGMII_STAT_SZ; i++)
|
|
- strscpy(data + i * ETH_GSTRING_LEN, aqr107_hw_stats[i].name,
|
|
- ETH_GSTRING_LEN);
|
|
-}
|
|
-
|
|
-static u64 aqr107_get_stat(struct phy_device *phydev, int index)
|
|
-{
|
|
- const struct aqr107_hw_stat *stat = aqr107_hw_stats + index;
|
|
- int len_l = min(stat->size, 16);
|
|
- int len_h = stat->size - len_l;
|
|
- u64 ret;
|
|
- int val;
|
|
-
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg);
|
|
- if (val < 0)
|
|
- return U64_MAX;
|
|
-
|
|
- ret = val & GENMASK(len_l - 1, 0);
|
|
- if (len_h) {
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg + 1);
|
|
- if (val < 0)
|
|
- return U64_MAX;
|
|
-
|
|
- ret += (val & GENMASK(len_h - 1, 0)) << 16;
|
|
- }
|
|
-
|
|
- return ret;
|
|
-}
|
|
-
|
|
-static void aqr107_get_stats(struct phy_device *phydev,
|
|
- struct ethtool_stats *stats, u64 *data)
|
|
-{
|
|
- struct aqr107_priv *priv = phydev->priv;
|
|
- u64 val;
|
|
- int i;
|
|
-
|
|
- for (i = 0; i < AQR107_SGMII_STAT_SZ; i++) {
|
|
- val = aqr107_get_stat(phydev, i);
|
|
- if (val == U64_MAX)
|
|
- phydev_err(phydev, "Reading HW Statistics failed for %s\n",
|
|
- aqr107_hw_stats[i].name);
|
|
- else
|
|
- priv->sgmii_stats[i] += val;
|
|
-
|
|
- data[i] = priv->sgmii_stats[i];
|
|
- }
|
|
-}
|
|
-
|
|
-static int aqr_config_aneg(struct phy_device *phydev)
|
|
-{
|
|
- bool changed = false;
|
|
- u16 reg;
|
|
- int ret;
|
|
-
|
|
- if (phydev->autoneg == AUTONEG_DISABLE)
|
|
- return genphy_c45_pma_setup_forced(phydev);
|
|
-
|
|
- ret = genphy_c45_an_config_aneg(phydev);
|
|
- if (ret < 0)
|
|
- return ret;
|
|
- if (ret > 0)
|
|
- changed = true;
|
|
-
|
|
- /* Clause 45 has no standardized support for 1000BaseT, therefore
|
|
- * use vendor registers for this mode.
|
|
- */
|
|
- reg = 0;
|
|
- if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
|
|
- phydev->advertising))
|
|
- reg |= MDIO_AN_VEND_PROV_1000BASET_FULL;
|
|
-
|
|
- if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
|
|
- phydev->advertising))
|
|
- reg |= MDIO_AN_VEND_PROV_1000BASET_HALF;
|
|
-
|
|
- /* Handle the case when the 2.5G and 5G speeds are not advertised */
|
|
- if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
|
|
- phydev->advertising))
|
|
- reg |= MDIO_AN_VEND_PROV_2500BASET_FULL;
|
|
-
|
|
- if (linkmode_test_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
|
|
- phydev->advertising))
|
|
- reg |= MDIO_AN_VEND_PROV_5000BASET_FULL;
|
|
-
|
|
- ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
|
|
- MDIO_AN_VEND_PROV_1000BASET_HALF |
|
|
- MDIO_AN_VEND_PROV_1000BASET_FULL |
|
|
- MDIO_AN_VEND_PROV_2500BASET_FULL |
|
|
- MDIO_AN_VEND_PROV_5000BASET_FULL, reg);
|
|
- if (ret < 0)
|
|
- return ret;
|
|
- if (ret > 0)
|
|
- changed = true;
|
|
-
|
|
- return genphy_c45_check_and_restart_aneg(phydev, changed);
|
|
-}
|
|
-
|
|
-static int aqr_config_intr(struct phy_device *phydev)
|
|
-{
|
|
- bool en = phydev->interrupts == PHY_INTERRUPT_ENABLED;
|
|
- int err;
|
|
-
|
|
- if (en) {
|
|
- /* Clear any pending interrupts before enabling them */
|
|
- err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- err = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_MASK2,
|
|
- en ? MDIO_AN_TX_VEND_INT_MASK2_LINK : 0);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_STD_MASK,
|
|
- en ? VEND1_GLOBAL_INT_STD_MASK_ALL : 0);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_VEND_MASK,
|
|
- en ? VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 |
|
|
- VEND1_GLOBAL_INT_VEND_MASK_AN : 0);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- if (!en) {
|
|
- /* Clear any pending interrupts after we have disabled them */
|
|
- err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static irqreturn_t aqr_handle_interrupt(struct phy_device *phydev)
|
|
-{
|
|
- int irq_status;
|
|
-
|
|
- irq_status = phy_read_mmd(phydev, MDIO_MMD_AN,
|
|
- MDIO_AN_TX_VEND_INT_STATUS2);
|
|
- if (irq_status < 0) {
|
|
- phy_error(phydev);
|
|
- return IRQ_NONE;
|
|
- }
|
|
-
|
|
- if (!(irq_status & MDIO_AN_TX_VEND_INT_STATUS2_MASK))
|
|
- return IRQ_NONE;
|
|
-
|
|
- phy_trigger_machine(phydev);
|
|
-
|
|
- return IRQ_HANDLED;
|
|
-}
|
|
-
|
|
-static int aqr_read_status(struct phy_device *phydev)
|
|
-{
|
|
- int val;
|
|
-
|
|
- if (phydev->autoneg == AUTONEG_ENABLE) {
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
|
|
- if (val < 0)
|
|
- return val;
|
|
-
|
|
- linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
|
|
- phydev->lp_advertising,
|
|
- val & MDIO_AN_RX_LP_STAT1_1000BASET_FULL);
|
|
- linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
|
|
- phydev->lp_advertising,
|
|
- val & MDIO_AN_RX_LP_STAT1_1000BASET_HALF);
|
|
- }
|
|
-
|
|
- return genphy_c45_read_status(phydev);
|
|
-}
|
|
-
|
|
-static int aqr107_read_rate(struct phy_device *phydev)
|
|
-{
|
|
- u32 config_reg;
|
|
- int val;
|
|
-
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_STATUS1);
|
|
- if (val < 0)
|
|
- return val;
|
|
-
|
|
- if (val & MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX)
|
|
- phydev->duplex = DUPLEX_FULL;
|
|
- else
|
|
- phydev->duplex = DUPLEX_HALF;
|
|
-
|
|
- switch (FIELD_GET(MDIO_AN_TX_VEND_STATUS1_RATE_MASK, val)) {
|
|
- case MDIO_AN_TX_VEND_STATUS1_10BASET:
|
|
- phydev->speed = SPEED_10;
|
|
- config_reg = VEND1_GLOBAL_CFG_10M;
|
|
- break;
|
|
- case MDIO_AN_TX_VEND_STATUS1_100BASETX:
|
|
- phydev->speed = SPEED_100;
|
|
- config_reg = VEND1_GLOBAL_CFG_100M;
|
|
- break;
|
|
- case MDIO_AN_TX_VEND_STATUS1_1000BASET:
|
|
- phydev->speed = SPEED_1000;
|
|
- config_reg = VEND1_GLOBAL_CFG_1G;
|
|
- break;
|
|
- case MDIO_AN_TX_VEND_STATUS1_2500BASET:
|
|
- phydev->speed = SPEED_2500;
|
|
- config_reg = VEND1_GLOBAL_CFG_2_5G;
|
|
- break;
|
|
- case MDIO_AN_TX_VEND_STATUS1_5000BASET:
|
|
- phydev->speed = SPEED_5000;
|
|
- config_reg = VEND1_GLOBAL_CFG_5G;
|
|
- break;
|
|
- case MDIO_AN_TX_VEND_STATUS1_10GBASET:
|
|
- phydev->speed = SPEED_10000;
|
|
- config_reg = VEND1_GLOBAL_CFG_10G;
|
|
- break;
|
|
- default:
|
|
- phydev->speed = SPEED_UNKNOWN;
|
|
- return 0;
|
|
- }
|
|
-
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_VEND1, config_reg);
|
|
- if (val < 0)
|
|
- return val;
|
|
-
|
|
- if (FIELD_GET(VEND1_GLOBAL_CFG_RATE_ADAPT, val) ==
|
|
- VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE)
|
|
- phydev->rate_matching = RATE_MATCH_PAUSE;
|
|
- else
|
|
- phydev->rate_matching = RATE_MATCH_NONE;
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int aqr107_read_status(struct phy_device *phydev)
|
|
-{
|
|
- int val, ret;
|
|
-
|
|
- ret = aqr_read_status(phydev);
|
|
- if (ret)
|
|
- return ret;
|
|
-
|
|
- if (!phydev->link || phydev->autoneg == AUTONEG_DISABLE)
|
|
- return 0;
|
|
-
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MDIO_PHYXS_VEND_IF_STATUS);
|
|
- if (val < 0)
|
|
- return val;
|
|
-
|
|
- switch (FIELD_GET(MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK, val)) {
|
|
- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR:
|
|
- phydev->interface = PHY_INTERFACE_MODE_10GKR;
|
|
- break;
|
|
- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX:
|
|
- phydev->interface = PHY_INTERFACE_MODE_1000BASEKX;
|
|
- break;
|
|
- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI:
|
|
- phydev->interface = PHY_INTERFACE_MODE_10GBASER;
|
|
- break;
|
|
- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII:
|
|
- phydev->interface = PHY_INTERFACE_MODE_USXGMII;
|
|
- break;
|
|
- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI:
|
|
- phydev->interface = PHY_INTERFACE_MODE_XAUI;
|
|
- break;
|
|
- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII:
|
|
- phydev->interface = PHY_INTERFACE_MODE_SGMII;
|
|
- break;
|
|
- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI:
|
|
- phydev->interface = PHY_INTERFACE_MODE_RXAUI;
|
|
- break;
|
|
- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII:
|
|
- phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
|
|
- break;
|
|
- default:
|
|
- phydev->interface = PHY_INTERFACE_MODE_NA;
|
|
- break;
|
|
- }
|
|
-
|
|
- /* Read possibly downshifted rate from vendor register */
|
|
- return aqr107_read_rate(phydev);
|
|
-}
|
|
-
|
|
-static int aqr107_get_downshift(struct phy_device *phydev, u8 *data)
|
|
-{
|
|
- int val, cnt, enable;
|
|
-
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV);
|
|
- if (val < 0)
|
|
- return val;
|
|
-
|
|
- enable = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_EN, val);
|
|
- cnt = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
|
|
-
|
|
- *data = enable && cnt ? cnt : DOWNSHIFT_DEV_DISABLE;
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int aqr107_set_downshift(struct phy_device *phydev, u8 cnt)
|
|
-{
|
|
- int val = 0;
|
|
-
|
|
- if (!FIELD_FIT(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt))
|
|
- return -E2BIG;
|
|
-
|
|
- if (cnt != DOWNSHIFT_DEV_DISABLE) {
|
|
- val = MDIO_AN_VEND_PROV_DOWNSHIFT_EN;
|
|
- val |= FIELD_PREP(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt);
|
|
- }
|
|
-
|
|
- return phy_modify_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
|
|
- MDIO_AN_VEND_PROV_DOWNSHIFT_EN |
|
|
- MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
|
|
-}
|
|
-
|
|
-static int aqr107_get_tunable(struct phy_device *phydev,
|
|
- struct ethtool_tunable *tuna, void *data)
|
|
-{
|
|
- switch (tuna->id) {
|
|
- case ETHTOOL_PHY_DOWNSHIFT:
|
|
- return aqr107_get_downshift(phydev, data);
|
|
- default:
|
|
- return -EOPNOTSUPP;
|
|
- }
|
|
-}
|
|
-
|
|
-static int aqr107_set_tunable(struct phy_device *phydev,
|
|
- struct ethtool_tunable *tuna, const void *data)
|
|
-{
|
|
- switch (tuna->id) {
|
|
- case ETHTOOL_PHY_DOWNSHIFT:
|
|
- return aqr107_set_downshift(phydev, *(const u8 *)data);
|
|
- default:
|
|
- return -EOPNOTSUPP;
|
|
- }
|
|
-}
|
|
-
|
|
-/* If we configure settings whilst firmware is still initializing the chip,
|
|
- * then these settings may be overwritten. Therefore make sure chip
|
|
- * initialization has completed. Use presence of the firmware ID as
|
|
- * indicator for initialization having completed.
|
|
- * The chip also provides a "reset completed" bit, but it's cleared after
|
|
- * read. Therefore function would time out if called again.
|
|
- */
|
|
-static int aqr107_wait_reset_complete(struct phy_device *phydev)
|
|
-{
|
|
- int val;
|
|
-
|
|
- return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
|
|
- VEND1_GLOBAL_FW_ID, val, val != 0,
|
|
- 20000, 2000000, false);
|
|
-}
|
|
-
|
|
-static void aqr107_chip_info(struct phy_device *phydev)
|
|
-{
|
|
- u8 fw_major, fw_minor, build_id, prov_id;
|
|
- int val;
|
|
-
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_FW_ID);
|
|
- if (val < 0)
|
|
- return;
|
|
-
|
|
- fw_major = FIELD_GET(VEND1_GLOBAL_FW_ID_MAJOR, val);
|
|
- fw_minor = FIELD_GET(VEND1_GLOBAL_FW_ID_MINOR, val);
|
|
-
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT1);
|
|
- if (val < 0)
|
|
- return;
|
|
-
|
|
- build_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID, val);
|
|
- prov_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_PROV_ID, val);
|
|
-
|
|
- phydev_dbg(phydev, "FW %u.%u, Build %u, Provisioning %u\n",
|
|
- fw_major, fw_minor, build_id, prov_id);
|
|
-}
|
|
-
|
|
-static int aqr107_config_init(struct phy_device *phydev)
|
|
-{
|
|
- int ret;
|
|
-
|
|
- /* Check that the PHY interface type is compatible */
|
|
- if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
|
|
- phydev->interface != PHY_INTERFACE_MODE_1000BASEKX &&
|
|
- phydev->interface != PHY_INTERFACE_MODE_2500BASEX &&
|
|
- phydev->interface != PHY_INTERFACE_MODE_XGMII &&
|
|
- phydev->interface != PHY_INTERFACE_MODE_USXGMII &&
|
|
- phydev->interface != PHY_INTERFACE_MODE_10GKR &&
|
|
- phydev->interface != PHY_INTERFACE_MODE_10GBASER &&
|
|
- phydev->interface != PHY_INTERFACE_MODE_XAUI &&
|
|
- phydev->interface != PHY_INTERFACE_MODE_RXAUI)
|
|
- return -ENODEV;
|
|
-
|
|
- WARN(phydev->interface == PHY_INTERFACE_MODE_XGMII,
|
|
- "Your devicetree is out of date, please update it. The AQR107 family doesn't support XGMII, maybe you mean USXGMII.\n");
|
|
-
|
|
- ret = aqr107_wait_reset_complete(phydev);
|
|
- if (!ret)
|
|
- aqr107_chip_info(phydev);
|
|
-
|
|
- return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
|
|
-}
|
|
-
|
|
-static int aqcs109_config_init(struct phy_device *phydev)
|
|
-{
|
|
- int ret;
|
|
-
|
|
- /* Check that the PHY interface type is compatible */
|
|
- if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
|
|
- phydev->interface != PHY_INTERFACE_MODE_2500BASEX)
|
|
- return -ENODEV;
|
|
-
|
|
- ret = aqr107_wait_reset_complete(phydev);
|
|
- if (!ret)
|
|
- aqr107_chip_info(phydev);
|
|
-
|
|
- /* AQCS109 belongs to a chip family partially supporting 10G and 5G.
|
|
- * PMA speed ability bits are the same for all members of the family,
|
|
- * AQCS109 however supports speeds up to 2.5G only.
|
|
- */
|
|
- phy_set_max_speed(phydev, SPEED_2500);
|
|
-
|
|
- return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
|
|
-}
|
|
-
|
|
-static void aqr107_link_change_notify(struct phy_device *phydev)
|
|
-{
|
|
- u8 fw_major, fw_minor;
|
|
- bool downshift, short_reach, afr;
|
|
- int mode, val;
|
|
-
|
|
- if (phydev->state != PHY_RUNNING || phydev->autoneg == AUTONEG_DISABLE)
|
|
- return;
|
|
-
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
|
|
- /* call failed or link partner is no Aquantia PHY */
|
|
- if (val < 0 || !(val & MDIO_AN_RX_LP_STAT1_AQ_PHY))
|
|
- return;
|
|
-
|
|
- short_reach = val & MDIO_AN_RX_LP_STAT1_SHORT_REACH;
|
|
- downshift = val & MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT;
|
|
-
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT4);
|
|
- if (val < 0)
|
|
- return;
|
|
-
|
|
- fw_major = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MAJOR, val);
|
|
- fw_minor = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MINOR, val);
|
|
-
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_VEND_STAT3);
|
|
- if (val < 0)
|
|
- return;
|
|
-
|
|
- afr = val & MDIO_AN_RX_VEND_STAT3_AFR;
|
|
-
|
|
- phydev_dbg(phydev, "Link partner is Aquantia PHY, FW %u.%u%s%s%s\n",
|
|
- fw_major, fw_minor,
|
|
- short_reach ? ", short reach mode" : "",
|
|
- downshift ? ", fast-retrain downshift advertised" : "",
|
|
- afr ? ", fast reframe advertised" : "");
|
|
-
|
|
- val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT9);
|
|
- if (val < 0)
|
|
- return;
|
|
-
|
|
- mode = FIELD_GET(VEND1_GLOBAL_RSVD_STAT9_MODE, val);
|
|
- if (mode == VEND1_GLOBAL_RSVD_STAT9_1000BT2)
|
|
- phydev_info(phydev, "Aquantia 1000Base-T2 mode active\n");
|
|
-}
|
|
-
|
|
-static int aqr107_wait_processor_intensive_op(struct phy_device *phydev)
|
|
-{
|
|
- int val, err;
|
|
-
|
|
- /* The datasheet notes to wait at least 1ms after issuing a
|
|
- * processor intensive operation before checking.
|
|
- * We cannot use the 'sleep_before_read' parameter of read_poll_timeout
|
|
- * because that just determines the maximum time slept, not the minimum.
|
|
- */
|
|
- usleep_range(1000, 5000);
|
|
-
|
|
- err = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
|
|
- VEND1_GLOBAL_GEN_STAT2, val,
|
|
- !(val & VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG),
|
|
- AQR107_OP_IN_PROG_SLEEP,
|
|
- AQR107_OP_IN_PROG_TIMEOUT, false);
|
|
- if (err) {
|
|
- phydev_err(phydev, "timeout: processor-intensive MDIO operation\n");
|
|
- return err;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int aqr107_get_rate_matching(struct phy_device *phydev,
|
|
- phy_interface_t iface)
|
|
-{
|
|
- if (iface == PHY_INTERFACE_MODE_10GBASER ||
|
|
- iface == PHY_INTERFACE_MODE_2500BASEX ||
|
|
- iface == PHY_INTERFACE_MODE_NA)
|
|
- return RATE_MATCH_PAUSE;
|
|
- return RATE_MATCH_NONE;
|
|
-}
|
|
-
|
|
-static int aqr107_suspend(struct phy_device *phydev)
|
|
-{
|
|
- int err;
|
|
-
|
|
- err = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
|
|
- MDIO_CTRL1_LPOWER);
|
|
- if (err)
|
|
- return err;
|
|
-
|
|
- return aqr107_wait_processor_intensive_op(phydev);
|
|
-}
|
|
-
|
|
-static int aqr107_resume(struct phy_device *phydev)
|
|
-{
|
|
- int err;
|
|
-
|
|
- err = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
|
|
- MDIO_CTRL1_LPOWER);
|
|
- if (err)
|
|
- return err;
|
|
-
|
|
- return aqr107_wait_processor_intensive_op(phydev);
|
|
-}
|
|
-
|
|
-static int aqr107_probe(struct phy_device *phydev)
|
|
-{
|
|
- phydev->priv = devm_kzalloc(&phydev->mdio.dev,
|
|
- sizeof(struct aqr107_priv), GFP_KERNEL);
|
|
- if (!phydev->priv)
|
|
- return -ENOMEM;
|
|
-
|
|
- return aqr_hwmon_probe(phydev);
|
|
-}
|
|
-
|
|
-static struct phy_driver aqr_driver[] = {
|
|
-{
|
|
- PHY_ID_MATCH_MODEL(PHY_ID_AQ1202),
|
|
- .name = "Aquantia AQ1202",
|
|
- .config_aneg = aqr_config_aneg,
|
|
- .config_intr = aqr_config_intr,
|
|
- .handle_interrupt = aqr_handle_interrupt,
|
|
- .read_status = aqr_read_status,
|
|
-},
|
|
-{
|
|
- PHY_ID_MATCH_MODEL(PHY_ID_AQ2104),
|
|
- .name = "Aquantia AQ2104",
|
|
- .config_aneg = aqr_config_aneg,
|
|
- .config_intr = aqr_config_intr,
|
|
- .handle_interrupt = aqr_handle_interrupt,
|
|
- .read_status = aqr_read_status,
|
|
-},
|
|
-{
|
|
- PHY_ID_MATCH_MODEL(PHY_ID_AQR105),
|
|
- .name = "Aquantia AQR105",
|
|
- .config_aneg = aqr_config_aneg,
|
|
- .config_intr = aqr_config_intr,
|
|
- .handle_interrupt = aqr_handle_interrupt,
|
|
- .read_status = aqr_read_status,
|
|
- .suspend = aqr107_suspend,
|
|
- .resume = aqr107_resume,
|
|
-},
|
|
-{
|
|
- PHY_ID_MATCH_MODEL(PHY_ID_AQR106),
|
|
- .name = "Aquantia AQR106",
|
|
- .config_aneg = aqr_config_aneg,
|
|
- .config_intr = aqr_config_intr,
|
|
- .handle_interrupt = aqr_handle_interrupt,
|
|
- .read_status = aqr_read_status,
|
|
-},
|
|
-{
|
|
- PHY_ID_MATCH_MODEL(PHY_ID_AQR107),
|
|
- .name = "Aquantia AQR107",
|
|
- .probe = aqr107_probe,
|
|
- .get_rate_matching = aqr107_get_rate_matching,
|
|
- .config_init = aqr107_config_init,
|
|
- .config_aneg = aqr_config_aneg,
|
|
- .config_intr = aqr_config_intr,
|
|
- .handle_interrupt = aqr_handle_interrupt,
|
|
- .read_status = aqr107_read_status,
|
|
- .get_tunable = aqr107_get_tunable,
|
|
- .set_tunable = aqr107_set_tunable,
|
|
- .suspend = aqr107_suspend,
|
|
- .resume = aqr107_resume,
|
|
- .get_sset_count = aqr107_get_sset_count,
|
|
- .get_strings = aqr107_get_strings,
|
|
- .get_stats = aqr107_get_stats,
|
|
- .link_change_notify = aqr107_link_change_notify,
|
|
-},
|
|
-{
|
|
- PHY_ID_MATCH_MODEL(PHY_ID_AQCS109),
|
|
- .name = "Aquantia AQCS109",
|
|
- .probe = aqr107_probe,
|
|
- .get_rate_matching = aqr107_get_rate_matching,
|
|
- .config_init = aqcs109_config_init,
|
|
- .config_aneg = aqr_config_aneg,
|
|
- .config_intr = aqr_config_intr,
|
|
- .handle_interrupt = aqr_handle_interrupt,
|
|
- .read_status = aqr107_read_status,
|
|
- .get_tunable = aqr107_get_tunable,
|
|
- .set_tunable = aqr107_set_tunable,
|
|
- .suspend = aqr107_suspend,
|
|
- .resume = aqr107_resume,
|
|
- .get_sset_count = aqr107_get_sset_count,
|
|
- .get_strings = aqr107_get_strings,
|
|
- .get_stats = aqr107_get_stats,
|
|
- .link_change_notify = aqr107_link_change_notify,
|
|
-},
|
|
-{
|
|
- PHY_ID_MATCH_MODEL(PHY_ID_AQR405),
|
|
- .name = "Aquantia AQR405",
|
|
- .config_aneg = aqr_config_aneg,
|
|
- .config_intr = aqr_config_intr,
|
|
- .handle_interrupt = aqr_handle_interrupt,
|
|
- .read_status = aqr_read_status,
|
|
-},
|
|
-{
|
|
- PHY_ID_MATCH_MODEL(PHY_ID_AQR113C),
|
|
- .name = "Aquantia AQR113C",
|
|
- .probe = aqr107_probe,
|
|
- .get_rate_matching = aqr107_get_rate_matching,
|
|
- .config_init = aqr107_config_init,
|
|
- .config_aneg = aqr_config_aneg,
|
|
- .config_intr = aqr_config_intr,
|
|
- .handle_interrupt = aqr_handle_interrupt,
|
|
- .read_status = aqr107_read_status,
|
|
- .get_tunable = aqr107_get_tunable,
|
|
- .set_tunable = aqr107_set_tunable,
|
|
- .suspend = aqr107_suspend,
|
|
- .resume = aqr107_resume,
|
|
- .get_sset_count = aqr107_get_sset_count,
|
|
- .get_strings = aqr107_get_strings,
|
|
- .get_stats = aqr107_get_stats,
|
|
- .link_change_notify = aqr107_link_change_notify,
|
|
-},
|
|
-};
|
|
-
|
|
-module_phy_driver(aqr_driver);
|
|
-
|
|
-static struct mdio_device_id __maybe_unused aqr_tbl[] = {
|
|
- { PHY_ID_MATCH_MODEL(PHY_ID_AQ1202) },
|
|
- { PHY_ID_MATCH_MODEL(PHY_ID_AQ2104) },
|
|
- { PHY_ID_MATCH_MODEL(PHY_ID_AQR105) },
|
|
- { PHY_ID_MATCH_MODEL(PHY_ID_AQR106) },
|
|
- { PHY_ID_MATCH_MODEL(PHY_ID_AQR107) },
|
|
- { PHY_ID_MATCH_MODEL(PHY_ID_AQCS109) },
|
|
- { PHY_ID_MATCH_MODEL(PHY_ID_AQR405) },
|
|
- { PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) },
|
|
- { }
|
|
-};
|
|
-
|
|
-MODULE_DEVICE_TABLE(mdio, aqr_tbl);
|
|
-
|
|
-MODULE_DESCRIPTION("Aquantia PHY driver");
|
|
-MODULE_AUTHOR("Shaohui Xie <Shaohui.Xie@freescale.com>");
|
|
-MODULE_LICENSE("GPL v2");
|