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:
David Wu
2022-01-18 21:01:53 +08:00
committed by Tao Huang
parent 39c1334ee8
commit f5f864ab51
4 changed files with 402 additions and 0 deletions
+106
View File
@@ -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);
+5
View File
@@ -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
+1
View File
@@ -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
+290
View File
@@ -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");