driver: rk630: Add RK630 MAC PHY support
Support RK630 ethernet PHY currently, this is a RMII PHY. Signed-off-by: David Wu <david.wu@rock-chips.com> Change-Id: I444f9c5abd1dc5fe8067b79ddee93a801d7a28b9
This commit is contained in:
@@ -13,11 +13,98 @@
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/mfd/rk630.h>
|
||||
|
||||
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;
|
||||
}
|
||||
udelay(20);
|
||||
|
||||
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;
|
||||
}
|
||||
udelay(20);
|
||||
|
||||
/* 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 mfd_cell rk630_devs[] = {
|
||||
{
|
||||
.name = "rk630-tve",
|
||||
.of_compatible = "rockchip,rk630-tve",
|
||||
},
|
||||
{
|
||||
.name = "rk630-macphy",
|
||||
.of_compatible = "rockchip,rk630-macphy",
|
||||
},
|
||||
};
|
||||
|
||||
static const struct regmap_range rk630_grf_readable_ranges[] = {
|
||||
@@ -79,6 +166,8 @@ EXPORT_SYMBOL_GPL(rk630_cru_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);
|
||||
@@ -102,6 +191,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);
|
||||
|
||||
@@ -260,6 +260,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
|
||||
|
||||
@@ -75,6 +75,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
|
||||
|
||||
@@ -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 <david.wu@rock-chips.com>
|
||||
*
|
||||
* 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 <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/of_irq.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/wakelock.h>
|
||||
|
||||
#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 <david.wu@rock-chips.com>");
|
||||
MODULE_DESCRIPTION("Rockchip RK630 Ethernet PHY driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
Reference in New Issue
Block a user