From 5d868ef8b05b5d05ac71dd68782c373ffee04c44 Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megi@xff.cz>
Date: Thu, 18 Jul 2024 17:35:08 +0200
Subject: [PATCH 448/463] net: phy: Add Rockchip RK630 PHY driver

This is downstream driver stripped from wakelocks.

Signed-off-by: Ondrej Jirman <megi@xff.cz>
---
 drivers/net/phy/Kconfig    |   5 +
 drivers/net/phy/Makefile   |   1 +
 drivers/net/phy/rk630phy.c | 422 +++++++++++++++++++++++++++++++++++++
 3 files changed, 428 insertions(+)
 create mode 100644 drivers/net/phy/rk630phy.c

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 7fddc8306d822..4ef9f9d3532a7 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -363,6 +363,11 @@ config ROCKCHIP_PHY
 	help
 	  Currently supports the integrated Ethernet PHY.
 
+config RK630_PHY
+	tristate "Driver for RK630 Ethernet PHYs"
+	help
+	  Currently supports the RK630 Ethernet PHY.
+
 config SMSC_PHY
 	tristate "SMSC PHYs"
 	select CRC16
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 202ed7f450da6..b58da22d0f5d9 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_QSEMI_PHY)		+= qsemi.o
 obj-$(CONFIG_REALTEK_PHY)	+= realtek.o
 obj-$(CONFIG_RENESAS_PHY)	+= uPD60620.o
 obj-$(CONFIG_ROCKCHIP_PHY)	+= rockchip.o
+obj-$(CONFIG_RK630_PHY)		+= rk630phy.o
 obj-$(CONFIG_SMSC_PHY)		+= smsc.o
 obj-$(CONFIG_STE10XP)		+= ste10Xp.o
 obj-$(CONFIG_TERANETICS_PHY)	+= teranetics.o
diff --git a/drivers/net/phy/rk630phy.c b/drivers/net/phy/rk630phy.c
new file mode 100644
index 0000000000000..20a244f928c85
--- /dev/null
+++ b/drivers/net/phy/rk630phy.c
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0+
+/**
+ *
+ * Driver for ROCKCHIP RK630 Ethernet PHYs
+ *
+ * Copyright (c) 2020, Rockchip Electronics Co., Ltd
+ *
+ * David Wu <david.wu@rock-chips.com>
+ *
+ */
+
+#include <linux/ethtool.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mfd/core.h>
+#include <linux/mii.h>
+#include <linux/netdevice.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_irq.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+
+#define RK630_PHY_ID				0x00441400
+
+/* PAGE 0 */
+#define REG_MMD_ACCESS_CONTROL			0x0d
+#define REG_MMD_ACCESS_DATA_ADDRESS		0x0e
+#define REG_INTERRUPT_STATUS			0X10
+#define REG_INTERRUPT_MASK			0X11
+#define REG_GLOBAL_CONFIGURATION		0X13
+#define REG_MAC_ADDRESS0			0x16
+#define REG_MAC_ADDRESS1			0x17
+#define REG_MAC_ADDRESS2			0x18
+
+#define REG_PAGE_SEL				0x1F
+
+/* PAGE 1 */
+#define REG_PAGE1_APS_CTRL			0x12
+#define REG_PAGE1_UAPS_CONFIGURE		0X13
+#define REG_PAGE1_EEE_CONFIGURE			0x17
+
+/* PAGE 2 */
+#define REG_PAGE2_AFE_CTRL			0x18
+
+/* PAGE 6 */
+#define REG_PAGE6_ADC_ANONTROL			0x10
+#define REG_PAGE6_GAIN_ANONTROL			0x12
+#define REG_PAGE6_AFE_RX_CTRL			0x13
+#define REG_PAGE6_AFE_TX_CTRL			0x14
+#define REG_PAGE6_AFE_DRIVER2			0x15
+#define REG_PAGE6_CP_CURRENT			0x17
+#define REG_PAGE6_ADC_OP_BIAS			0x18
+#define REG_PAGE6_RX_DECTOR			0x19
+#define REG_PAGE6_TX_MOS_DRV			0x1B
+#define REG_PAGE6_AFE_PDCW			0x1c
+
+/* PAGE 8 */
+#define REG_PAGE8_AFE_CTRL			0x18
+#define REG_PAGE8_AUTO_CAL			0x1d
+
+/*
+ * Fixed address:
+ * Addr: 1 --- RK630@S40
+ *       2 --- RV1106@T22
+ */
+#define PHY_ADDR_S40				1
+#define PHY_ADDR_T22				2
+
+#define T22_TX_LEVEL_100M			0x2d
+#define T22_TX_LEVEL_10M			0x32
+
+struct rk630_phy_priv {
+	struct phy_device *phydev;
+	bool ieee;
+	int wol_irq;
+	int tx_level_100M;
+	int tx_level_10M;
+};
+
+static void rk630_phy_t22_get_tx_level_from_efuse(struct phy_device *phydev)
+{
+	struct rk630_phy_priv *priv = phydev->priv;
+	unsigned int tx_level_100M = T22_TX_LEVEL_100M;
+	unsigned int tx_level_10M = T22_TX_LEVEL_10M;
+	unsigned char *efuse_buf;
+	struct nvmem_cell *cell;
+	size_t len;
+
+	cell = nvmem_cell_get(&phydev->mdio.dev, "txlevel");
+	if (IS_ERR(cell)) {
+		phydev_err(phydev, "failed to get txlevel cell: %ld, use default\n",
+			   PTR_ERR(cell));
+	} else {
+		efuse_buf = nvmem_cell_read(cell, &len);
+		nvmem_cell_put(cell);
+		if (!IS_ERR(efuse_buf)) {
+			if (len == 2 && efuse_buf[0] > 0 && efuse_buf[1] > 0) {
+				tx_level_100M = efuse_buf[1];
+				tx_level_10M = efuse_buf[0];
+			}
+			kfree(efuse_buf);
+		} else {
+			phydev_err(phydev, "failed to get efuse buf, use default\n");
+		}
+	}
+
+	priv->tx_level_100M = tx_level_100M;
+	priv->tx_level_10M = tx_level_10M;
+}
+
+static void rk630_phy_wol_enable(struct phy_device *phydev)
+{
+	struct net_device *ndev = phydev->attached_dev;
+	u32 value;
+
+	/* Switch to page 0 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0000);
+	phy_write(phydev, REG_MAC_ADDRESS0, ((u16)ndev->dev_addr[0] << 8) + ndev->dev_addr[1]);
+	phy_write(phydev, REG_MAC_ADDRESS1, ((u16)ndev->dev_addr[2] << 8) + ndev->dev_addr[3]);
+	phy_write(phydev, REG_MAC_ADDRESS2, ((u16)ndev->dev_addr[4] << 8) + ndev->dev_addr[5]);
+
+	value = phy_read(phydev, REG_GLOBAL_CONFIGURATION);
+	value |= BIT(8);
+	value &= ~BIT(7);
+	value |= BIT(10);
+	phy_write(phydev, REG_GLOBAL_CONFIGURATION, value);
+
+	value = phy_read(phydev, REG_INTERRUPT_MASK);
+	value |= BIT(14);
+	phy_write(phydev, REG_INTERRUPT_MASK, value);
+}
+
+static void rk630_phy_wol_disable(struct phy_device *phydev)
+{
+	u32 value;
+
+	/* Switch to page 0 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0000);
+	value = phy_read(phydev, REG_GLOBAL_CONFIGURATION);
+	value &= ~BIT(10);
+	phy_write(phydev, REG_GLOBAL_CONFIGURATION, value);
+}
+
+static void rk630_phy_ieee_set(struct phy_device *phydev, bool enable)
+{
+	u32 value;
+
+	/* Switch to page 1 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0100);
+	value = phy_read(phydev, REG_PAGE1_EEE_CONFIGURE);
+	if (enable)
+		value |= BIT(3);
+	else
+		value &= ~BIT(3);
+	phy_write(phydev, REG_PAGE1_EEE_CONFIGURE, value);
+	/* Switch to page 0 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0000);
+}
+
+static void rk630_phy_set_aps(struct phy_device *phydev, bool enable)
+{
+	u32 value;
+
+	/* Switch to page 1 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0100);
+	value = phy_read(phydev, REG_PAGE1_APS_CTRL);
+	if (enable)
+		value |= BIT(15);
+	else
+		value &= ~BIT(15);
+	phy_write(phydev, REG_PAGE1_APS_CTRL, value);
+	/* Switch to page 0 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0000);
+}
+
+static void rk630_phy_set_uaps(struct phy_device *phydev, bool enable)
+{
+	u32 value;
+
+	/* Switch to page 1 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0100);
+	value = phy_read(phydev, REG_PAGE1_UAPS_CONFIGURE);
+	if (enable)
+		value |= BIT(15);
+	else
+		value &= ~BIT(15);
+	phy_write(phydev, REG_PAGE1_UAPS_CONFIGURE, value);
+	/* Switch to page 0 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0000);
+}
+
+static void rk630_phy_s40_config_init(struct phy_device *phydev)
+{
+	phy_write(phydev, 0, phy_read(phydev, 0) & ~BIT(13));
+
+	/* Switch to page 1 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0100);
+	/* Disable APS */
+	phy_write(phydev, REG_PAGE1_APS_CTRL, 0x4824);
+	/* Switch to page 2 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0200);
+	/* PHYAFE TRX optimization */
+	phy_write(phydev, REG_PAGE2_AFE_CTRL, 0x0000);
+	/* Switch to page 6 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0600);
+	/* PHYAFE TX optimization */
+	phy_write(phydev, REG_PAGE6_AFE_TX_CTRL, 0x708f);
+	/* PHYAFE RX optimization */
+	phy_write(phydev, REG_PAGE6_AFE_RX_CTRL, 0xf000);
+	phy_write(phydev, REG_PAGE6_AFE_DRIVER2, 0x1530);
+
+	/* Switch to page 8 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0800);
+	/* PHYAFE TRX optimization */
+	phy_write(phydev, REG_PAGE8_AFE_CTRL, 0x00bc);
+
+	/* Switch to page 0 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0000);
+}
+
+static void rk630_phy_t22_config_init(struct phy_device *phydev)
+{
+	struct rk630_phy_priv *priv = phydev->priv;
+
+	/* Switch to page 1 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0100);
+	/* Enable offset clock */
+	phy_write(phydev, 0x10, 0xfbfe);
+	/* Disable APS */
+	phy_write(phydev, REG_PAGE1_APS_CTRL, 0x4824);
+	/* Switch to page 2 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0200);
+	/* PHYAFE TRX optimization */
+	phy_write(phydev, REG_PAGE2_AFE_CTRL, 0x0000);
+	/* Switch to page 6 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0600);
+	/* PHYAFE ADC optimization */
+	phy_write(phydev, REG_PAGE6_ADC_ANONTROL, 0x5540);
+	/* PHYAFE Gain optimization */
+	phy_write(phydev, REG_PAGE6_GAIN_ANONTROL, 0x0400);
+	/* PHYAFE EQ optimization */
+	phy_write(phydev, REG_PAGE6_AFE_TX_CTRL, 0x1088);
+
+	if (priv->tx_level_100M <= 0 || priv->tx_level_10M <= 0)
+		rk630_phy_t22_get_tx_level_from_efuse(phydev);
+
+	/* PHYAFE TX optimization */
+	phy_write(phydev, REG_PAGE6_AFE_DRIVER2,
+		  (priv->tx_level_100M << 8) | priv->tx_level_10M);
+	/* PHYAFE CP current optimization */
+	phy_write(phydev, REG_PAGE6_CP_CURRENT, 0x0575);
+	/* ADC OP BIAS optimization */
+	phy_write(phydev, REG_PAGE6_ADC_OP_BIAS, 0x0000);
+	/* Rx signal detctor level optimization */
+	phy_write(phydev, REG_PAGE6_RX_DECTOR, 0x0408);
+	/* PHYAFE PDCW optimization */
+	phy_write(phydev, REG_PAGE6_AFE_PDCW, 0x8880);
+	/* Add PHY Tx mos drive, reduce power noise/jitter */
+	phy_write(phydev, REG_PAGE6_TX_MOS_DRV, 0x888e);
+
+	/* Switch to page 8 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0800);
+	/* Disable auto-cal */
+	phy_write(phydev, REG_PAGE8_AUTO_CAL, 0x0844);
+	/* Reatart offset calibration */
+	phy_write(phydev, 0x13, 0xc096);
+
+	/* Switch to page 0 */
+	phy_write(phydev, REG_PAGE_SEL, 0x0000);
+
+	/* Disable eee mode advertised */
+	phy_write(phydev, REG_MMD_ACCESS_CONTROL, 0x0007);
+	phy_write(phydev, REG_MMD_ACCESS_DATA_ADDRESS, 0x003c);
+	phy_write(phydev, REG_MMD_ACCESS_CONTROL, 0x4007);
+	phy_write(phydev, REG_MMD_ACCESS_DATA_ADDRESS, 0x0000);
+}
+
+static int rk630_phy_config_init(struct phy_device *phydev)
+{
+	pr_err("conf init\n");
+
+	switch (phydev->mdio.addr) {
+	case PHY_ADDR_S40:
+		rk630_phy_s40_config_init(phydev);
+		/*
+		 * Ultra Auto-Power Saving Mode (UAPS) is designed to
+		 * save power when cable is not plugged into PHY.
+		 */
+		rk630_phy_set_uaps(phydev, true);
+		break;
+	case PHY_ADDR_T22:
+		rk630_phy_t22_config_init(phydev);
+		rk630_phy_set_aps(phydev, false);
+		rk630_phy_set_uaps(phydev, false);
+		break;
+	default:
+		phydev_err(phydev, "Unsupported address for current phy: %d\n",
+			   phydev->mdio.addr);
+		return -EINVAL;
+	}
+
+	rk630_phy_ieee_set(phydev, true);
+
+	return 0;
+}
+
+static void rk630_link_change_notify(struct phy_device *phydev)
+{
+	unsigned int val;
+
+	if (phydev->state == PHY_RUNNING || phydev->state == PHY_NOLINK) {
+		/* Switch to page 6 */
+		phy_write(phydev, REG_PAGE_SEL, 0x0600);
+		val = phy_read(phydev, REG_PAGE6_AFE_TX_CTRL);
+		val &= ~GENMASK(14, 13);
+		if (phydev->speed == SPEED_10 && phydev->link)
+			val |= BIT(13);
+		phy_write(phydev, REG_PAGE6_AFE_TX_CTRL, val);
+		/* Switch to page 0 */
+		phy_write(phydev, REG_PAGE_SEL, 0x0000);
+	}
+}
+
+static irqreturn_t rk630_wol_irq_thread(int irq, void *dev_id)
+{
+	struct rk630_phy_priv *priv = (struct rk630_phy_priv *)dev_id;
+
+	phy_write(priv->phydev, REG_INTERRUPT_STATUS, BIT(14));
+	return IRQ_HANDLED;
+}
+
+static int rk630_phy_probe(struct phy_device *phydev)
+{
+	struct rk630_phy_priv *priv;
+	int ret;
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	priv->wol_irq = of_irq_get_byname(phydev->mdio.dev.of_node, "wol_irq");
+	if (priv->wol_irq == -EPROBE_DEFER)
+		return priv->wol_irq;
+
+	if (priv->wol_irq > 0) {
+		ret = devm_request_threaded_irq(&phydev->mdio.dev, priv->wol_irq,
+						NULL, rk630_wol_irq_thread,
+						IRQF_TRIGGER_FALLING | IRQF_SHARED | IRQF_ONESHOT,
+						"wol_irq", priv);
+		if (ret) {
+			phydev_err(phydev, "request wol_irq failed: %d\n", ret);
+			return ret;
+		}
+		disable_irq(priv->wol_irq);
+		enable_irq_wake(priv->wol_irq);
+	}
+
+	priv->phydev = phydev;
+
+	pr_err("== rk630 probed\n");
+
+	return 0;
+}
+
+static int rk630_phy_suspend(struct phy_device *phydev)
+{
+	struct rk630_phy_priv *priv = phydev->priv;
+
+	if (priv->wol_irq > 0) {
+		rk630_phy_wol_enable(phydev);
+		phy_write(phydev, REG_INTERRUPT_MASK, BIT(14));
+		enable_irq(priv->wol_irq);
+	}
+	return genphy_suspend(phydev);
+}
+
+static int rk630_phy_resume(struct phy_device *phydev)
+{
+	struct rk630_phy_priv *priv = phydev->priv;
+
+	if (priv->wol_irq > 0) {
+		rk630_phy_wol_disable(phydev);
+		phy_write(phydev, REG_INTERRUPT_MASK, 0);
+		disable_irq(priv->wol_irq);
+	}
+
+	return genphy_resume(phydev);
+}
+
+static struct phy_driver rk630_phy_driver[] = {
+	{
+		.phy_id			= RK630_PHY_ID,
+		.phy_id_mask		= 0xffffffff,
+		.name			= "RK630 PHY",
+		.features		= PHY_BASIC_FEATURES,
+		.flags			= 0,
+		.link_change_notify	= rk630_link_change_notify,
+		.probe			= rk630_phy_probe,
+		.soft_reset		= genphy_soft_reset,
+		.config_init		= rk630_phy_config_init,
+		.config_aneg		= genphy_config_aneg,
+		.read_status		= genphy_read_status,
+		.suspend		= rk630_phy_suspend,
+		.resume			= rk630_phy_resume,
+	},
+};
+
+static struct mdio_device_id __maybe_unused rk630_phy_tbl[] = {
+	{ RK630_PHY_ID, 0xffffffff },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, rk630_phy_tbl);
+
+module_phy_driver(rk630_phy_driver);
+
+MODULE_AUTHOR("David Wu <david.wu@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip RK630 Ethernet PHY driver");
+MODULE_LICENSE("GPL v2");
-- 
2.46.0

