diff --git a/drivers/mfd/rk630.c b/drivers/mfd/rk630.c index 36d08407fef5..788c09ef3923 100644 --- a/drivers/mfd/rk630.c +++ b/drivers/mfd/rk630.c @@ -13,6 +13,89 @@ #include #include +static int rk630_macphy_enable(struct rk630 *rk630) +{ + u32 val; + int ret; + + /* IOMUX */ + val = 0xfffc5554; + ret = regmap_write(rk630->grf, GRF_REG(0x8), val); + if (ret != 0) { + dev_err(rk630->dev, "Could not write to GRF: %d\n", ret); + return ret; + } + + /* IOMUX */ + val = 0x00330021; + ret = regmap_write(rk630->grf, GRF_REG(0x10), val); + if (ret != 0) { + dev_err(rk630->dev, "Could not write to GRF: %d\n", ret); + return ret; + } + + /* reset */ + val = BIT(12 + 16) | BIT(12); + ret = regmap_write(rk630->cru, CRU_REG(0x50), val); + if (ret != 0) { + dev_err(rk630->dev, "Could not write to CRU: %d\n", ret); + return ret; + } + usleep_range(20, 30); + + val = BIT(12 + 16); + ret = regmap_write(rk630->cru, CRU_REG(0x50), val); + if (ret != 0) { + dev_err(rk630->dev, "Could not write to CRU: %d\n", ret); + return ret; + } + usleep_range(20, 30); + + /* power up && led*/ + val = BIT(1 + 16) | BIT(1) | BIT(2 + 16); + ret = regmap_write(rk630->grf, GRF_REG(0x408), val); + if (ret != 0) { + dev_err(rk630->dev, "Could not write to GRF: %d\n", ret); + return ret; + } + usleep_range(20000, 50000); + + /* mdio_sel: mdio */ + val = BIT(8 + 16) | BIT(8); + ret = regmap_write(rk630->grf, GRF_REG(0x400), val); + if (ret != 0) { + dev_err(rk630->dev, "Could not write to GRF: %d\n", ret); + return ret; + } + + /* mode sel: RMII && clock sel: 24M && BGS value: OTP && id */ + val = (2 << 14) | (0 << 12) | (0x1 << 8) | (6 << 5) | 1; + ret = regmap_write(rk630->grf, GRF_REG(0x404), val | 0xffff0000); + if (ret != 0) { + dev_err(rk630->dev, "Could not write to GRF: %d\n", ret); + return ret; + } + usleep_range(100, 150); + + return 0; +} + +static int rk630_macphy_disable(struct rk630 *rk630) +{ + u32 val; + int ret; + + /* GRF_SOC_CON2_CFG */ + val = BIT(2) | BIT(16 + 2); + ret = regmap_write(rk630->grf, GRF_REG(0x408), val); + if (ret != 0) { + dev_err(rk630->dev, "Could not write to GRF: %d\n", ret); + return ret; + } + + return 0; +} + static const struct regmap_irq rk630_irqs[] = { /* RTC INT_STS0 */ @@ -48,6 +131,10 @@ static const struct mfd_cell rk630_devs[] = { .name = "rk630-rtc", .of_compatible = "rockchip,rk630-rtc", }, + { + .name = "rk630-macphy", + .of_compatible = "rockchip,rk630-macphy", + }, }; static const struct regmap_range rk630_grf_readable_ranges[] = { @@ -128,6 +215,8 @@ const struct regmap_config rk630_rtc_regmap_config = { int rk630_core_probe(struct rk630 *rk630) { + bool macphy_enabled = false; + struct device_node *np; int ret; rk630->reset_gpio = devm_gpiod_get(rk630->dev, "reset", 0); @@ -166,6 +255,23 @@ int rk630_core_probe(struct rk630 *rk630) return ret; } + for_each_child_of_node(rk630->dev->of_node, np) { + if (!of_device_is_compatible(np, "rockchip,rk630-macphy")) + continue; + + if (!of_device_is_available(np)) { + continue; + } else { + macphy_enabled = true; + break; + } + } + + if (macphy_enabled) + rk630_macphy_enable(rk630); + else + rk630_macphy_disable(rk630); + return 0; } EXPORT_SYMBOL_GPL(rk630_core_probe); diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 2386871e1294..dc5e94ad2d68 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -410,6 +410,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" ---help--- diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index f21cda9d865e..12b0edd2d73b 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -80,6 +80,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 000000000000..9421446d47e9 --- /dev/null +++ b/drivers/net/phy/rk630phy.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0+ +/** + * + * Driver for ROCKCHIP RK630 Ethernet PHYs + * + * Copyright (c) 2020, Fuzhou Rockchip Electronics Co., Ltd + * + * David Wu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RK630_PHY_ID 0x00441400 + +/* PAGE 0 */ +#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_AFE_RX_CTRL 0x13 +#define REG_PAGE6_AFE_TX_CTRL 0x14 +#define REG_PAGE6_AFE_DRIVER2 0x15 + +/* PAGE 8 */ +#define REG_PAGE8_AFE_CTRL 0x18 + +struct rk630_phy_priv { + struct phy_device *phydev; + bool ieee; + int wol_irq; + struct wake_lock wol_wake_lock; +}; + +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_uaps(struct phy_device *phydev) +{ + u32 value; + + /* Switch to page 1 */ + phy_write(phydev, REG_PAGE_SEL, 0x0100); + value = phy_read(phydev, REG_PAGE1_UAPS_CONFIGURE); + value |= BIT(15); + phy_write(phydev, REG_PAGE1_UAPS_CONFIGURE, value); + /* Switch to page 0 */ + phy_write(phydev, REG_PAGE_SEL, 0x0000); +} + +static int rk630_phy_config_init(struct phy_device *phydev) +{ + u32 value; + + 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); + + /* Adjust tx level, bypass */ + value = phy_read(phydev, 0x1d); + value |= BIT(11); + phy_write(phydev, 0x1d, value); + /* switch to page6 */ + phy_write(phydev, REG_PAGE_SEL, 0x0600); + /* Enable tx level control */ + value = phy_read(phydev, REG_PAGE6_ADC_ANONTROL); + value &= ~BIT(6); + phy_write(phydev, REG_PAGE6_ADC_ANONTROL, value); + /* Set tx level */ + value = phy_read(phydev, REG_PAGE6_AFE_DRIVER2); + value &= ~GENMASK(15, 8); + value |= 0x121a; + phy_write(phydev, REG_PAGE6_AFE_DRIVER2, value); + + /* Switch to page 0 */ + phy_write(phydev, REG_PAGE_SEL, 0x0000); + + rk630_phy_ieee_set(phydev, true); + /* + * Ultra Auto-Power Saving Mode (UAPS) is designed to + * save power when cable is not plugged into PHY. + */ + rk630_phy_set_uaps(phydev); + + return 0; +} + +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)); + wake_lock_timeout(&priv->wol_wake_lock, msecs_to_jiffies(8000)); + 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) { + wake_lock_init(&priv->wol_wake_lock, + WAKE_LOCK_SUSPEND, "wol_wake_lock"); + ret = devm_request_threaded_irq(&phydev->mdio.dev, priv->wol_irq, + NULL, rk630_wol_irq_thread, + IRQF_TRIGGER_RISING | IRQF_SHARED | IRQF_ONESHOT, + "wol_irq", priv); + if (ret) { + wake_lock_destroy(&priv->wol_wake_lock); + 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; + + return 0; +} + +static void rk630_phy_remove(struct phy_device *phydev) +{ + struct rk630_phy_priv *priv = phydev->priv; + + if (priv->wol_irq > 0) + wake_lock_destroy(&priv->wol_wake_lock); +} + +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, + .probe = rk630_phy_probe, + .remove = rk630_phy_remove, + .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, rockchip_phy_tbl); + +module_phy_driver(rk630_phy_driver); + +MODULE_AUTHOR("David Wu "); +MODULE_DESCRIPTION("Rockchip RK630 Ethernet PHY driver"); +MODULE_LICENSE("GPL v2");