blob: d033f7245954512d0d8ceb824eb591ca5905555a [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/of.h>
//xf.li 2022/9/9 modify for LPSD mode start
#include <linux/of_gpio.h>
#include <linux/gpio/consumer.h>
//xf.li 2022/9/9 modify for LPSD mode end
#include <linux/delay.h>
#define PHY_ID_88Q2220_B0 0x002b0b21
#define PHY_ID_88Q2220_B1 0x002b0b22
#define Q2110_PMA_PMD_CTRL (MII_ADDR_C45 | 0x10000)
/* 1 = PMA/PMD reset, 0 = Normal */
#define Q2110_PMA_PMD_RST BIT(15)
#define Q2110_T1_PMA_PMD_CTRL (MII_ADDR_C45 | 0x10834)
/* 1 = PHY as master, 0 = PHY as slave */
#define Q2110_T1_MASTER_SLAVE_CFG BIT(14)
/* 0 = 100BASE-T1, 1 = 1000BASE-T1 */
#define Q2110_T1_LINK_TYPE BIT(0)
#define Q2110_RST_CTRL (MII_ADDR_C45 | 0x038000)
/* software reset of T-unit */
#define Q2110_RGMII_SW_RESET BIT(15)
#define Q2110_PCS_CTRL (MII_ADDR_C45 | 0x030900)
#define Q2110_LOOPBACK BIT(14)
#define Q2110_T1_AN_STATUS (MII_ADDR_C45 | 0x070201)
/* 1 = Link Up, 0 = Link Down */
#define Q2110_T1_LINK_STATUS BIT(2)
#define Q2110_COM_PORT_CTRL (MII_ADDR_C45 | 0x4A001)
/* Add delay on TX_CLK */
#define Q2110_RGMII_TX_TIMING_CTRL BIT(15)
/* Add delay on RX_CLK */
#define Q2110_RGMII_RX_TIMING_CTRL BIT(14)
/* NAD_WAKEUP_PHY1 : GPIO7 */
#define NAD_WAKEUP_PHY1 275
/* NAD_RESET_PHY1 : GPIO26 */
#define NAD_RESET_PHY1 294
/* PHY_POWER_SUPPLY : GPIO178 */
#define PHY_POWER_SUPPLY 446
#define Q2220_LPSD_CTRL_1 (MII_ADDR_C45 | 0x038021)
#define Q2220_LPSD_DISABLE_REMOTE_WAKE_UP BIT(15) | BIT(11)
#define Q2220_LPSD_STATUS (MII_ADDR_C45 | 0x038020)
#define Q2220_LPSD_POWER_DOWN BIT(0)
int if_suspend = 0;
/* Set and/or override some configuration registers based on the
* marvell,88q2110 property stored in the of_node for the phydev.
* marvell,88q2110 = <speed master>,...;
* speed: 1000Mbps or 100Mbps.
* master: 1-master, 0-slave.
*/
static int q2110_dts_init(struct phy_device *phydev)
{
const __be32 *paddr;
u32 speed;
u32 master;
int val, len;
if (!phydev->mdio.dev.of_node)
return 0;
paddr = of_get_property(phydev->mdio.dev.of_node,
"marvell,88q2220", &len);
if (!paddr)
return 0;
speed = be32_to_cpup(paddr);
master = be32_to_cpup(paddr + 1);
val = phy_read(phydev, Q2110_T1_PMA_PMD_CTRL);
if (val < 0)
return val;
val &= ~(Q2110_T1_MASTER_SLAVE_CFG | Q2110_T1_LINK_TYPE);
if (speed == SPEED_1000)
val |= Q2110_T1_LINK_TYPE;
if (master)
val |= Q2110_T1_MASTER_SLAVE_CFG;
val = phy_write(phydev, Q2110_T1_PMA_PMD_CTRL, val);
if (val < 0)
return val;
/*
phy_write(phydev, (MII_ADDR_C45 | 0x030900), 0x8000);
phy_write(phydev, (MII_ADDR_C45 | 0x03FFE4), 0x000C);
*/
printk("q2110 dts init ok!!\n");
return 0;
}
static int q2110_timing_init(struct phy_device *phydev)
{
int val;
if (phy_interface_is_rgmii(phydev)) {
val = phy_read(phydev, Q2110_COM_PORT_CTRL);
if (val < 0)
return val;
val &= ~(Q2110_RGMII_TX_TIMING_CTRL |
Q2110_RGMII_RX_TIMING_CTRL);
if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
val |= (Q2110_RGMII_TX_TIMING_CTRL |
Q2110_RGMII_RX_TIMING_CTRL);
else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
val |= Q2110_RGMII_RX_TIMING_CTRL;
else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
val |= Q2110_RGMII_TX_TIMING_CTRL;
val = phy_write(phydev, Q2110_COM_PORT_CTRL, val);
if (val < 0)
return val;
}
val = phy_read(phydev, Q2110_RST_CTRL);
if (val < 0)
return val;
val |= Q2110_RGMII_SW_RESET;
val = phy_write(phydev, Q2110_RST_CTRL, val);
if (val < 0)
return val;
val = phy_read(phydev, Q2110_RST_CTRL);
if (val < 0)
return val;
val &= ~Q2110_RGMII_SW_RESET;
val = phy_write(phydev, Q2110_RST_CTRL, val);
if (val < 0)
return val;
return 0;
}
static int q2220_b0_config_init(struct phy_device *phydev)
{
int i=0,mrvlId;
phydev->supported =
SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full;
phydev->advertising =
SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full;
phydev->state = PHY_NOLINK;
phydev->autoneg = AUTONEG_DISABLE;
printk("PHY_DEVICE is B0");
phy_write(phydev, MII_ADDR_C45 | 0x038033, 0x6801);
phy_write(phydev, MII_ADDR_C45 | 0x070200, 0x0000U);
phy_write(phydev, MII_ADDR_C45 | 0x010000, 0x840);
phy_write(phydev, MII_ADDR_C45 | 0x03FE1B, 0x48);
phy_write(phydev, MII_ADDR_C45 | 0x01FFE4, 0x6B6);
phy_write(phydev, MII_ADDR_C45 | 0x010000, 0x0);
phy_write(phydev, MII_ADDR_C45 | 0x030000, 0x0);
while (i < 5) {
mrvlId= phy_read(phydev, MII_ADDR_C45 | 0x03FFE4);
if (mrvlId == 0x06BA) {
break;
}
i++;
msleep(1);
}
phy_write(phydev, MII_ADDR_C45 | 0x078032, 0x2020);
phy_write(phydev, MII_ADDR_C45 | 0x078031, 0xA28);
phy_write(phydev, MII_ADDR_C45 | 0x078031, 0xC28);
phy_write(phydev, MII_ADDR_C45 | 0x03FFDB, 0xFC10);
phy_write(phydev, MII_ADDR_C45 | 0x03FE1B, 0x58);
//cheng.c modify for ethernet can not link up with golden device
phy_write(phydev, MII_ADDR_C45 | 0x03803A, 0xDA44);
phy_write(phydev, MII_ADDR_C45 | 0x038039, 0x2C0B);
phy_write(phydev, MII_ADDR_C45 | 0x038027, 0x1);//TC10 sleep/wakeup capability support
q2110_timing_init(phydev);
q2110_dts_init(phydev);
//softResetGe
phy_write(phydev, (MII_ADDR_C45 | 0x03FE1B), 0x48);
phy_write(phydev, (MII_ADDR_C45 | 0x030900), 0x8000);
phy_write(phydev, (MII_ADDR_C45 | 0x03FFE4), 0x000C);
phy_write(phydev, (MII_ADDR_C45 | 0x03FE1B), 0x58);
#if 0
//FE/100BT1
phy_write(phydev, (MII_ADDR_C45 | 0x030900), 0x8000);
phy_write(phydev, (MII_ADDR_C45 | 0x03FFE4), 0x000C);
#endif
printk("phy init ok\n");
return 0;
}
static int q2220_b1_config_init(struct phy_device *phydev)
{
phydev->supported =
SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full;
phydev->advertising =
SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full;
phydev->state = PHY_NOLINK;
phydev->autoneg = AUTONEG_DISABLE;
printk("PHY_DEVICE is B1");
/* set power management state breakpoint (internal state) */
phy_write(phydev, MII_ADDR_C45 | 0x03FFE4, 0x0007);
/* disable ANEG */
phy_write(phydev, MII_ADDR_C45 | 0x070200, 0x0000U);
/* prepare to write init code */
/* enable TXDAC setting overwrite bit */
phy_write(phydev, MII_ADDR_C45 | 0x03ffe3, 0x7000);
/* set IEEE power down */
phy_write(phydev, MII_ADDR_C45 | 0x010000, 0x0840);
/* wait 3 ms to get to ON_TBGB State and send out WUP if needed */
mdelay(3);
/* exit standby state(internal state) */
phy_write(phydev, MII_ADDR_C45 | 0x03FE1B, 0x0048);
/* exit IEEE power down */
phy_write(phydev, MII_ADDR_C45 | 0x010000, 0x0000);
phy_write(phydev, MII_ADDR_C45 | 0x030000, 0x0000);
/* configure TC10 loc_act_detect for rev B1 */
phy_write(phydev, MII_ADDR_C45 | 0x03FCAD, 0x030C);
phy_write(phydev, MII_ADDR_C45 | 0x038032, 0x6001);
phy_write(phydev, MII_ADDR_C45 | 0x03FDFF, 0x05A5);
phy_write(phydev, MII_ADDR_C45 | 0x03FDEC, 0xDBAF);
phy_write(phydev, MII_ADDR_C45 | 0x03FCAB, 0x1054);
phy_write(phydev, MII_ADDR_C45 | 0x03FCAC, 0x1483);
/* set DISABLE_WAIT_COMM to 0 for WUR */
phy_write(phydev, MII_ADDR_C45 | 0x038033, 0xC801);
/* send_s detection threshold, slave and master */
phy_write(phydev, MII_ADDR_C45 | 0x78032, 0x2020);
phy_write(phydev, MII_ADDR_C45 | 0x78031, 0xA28);
phy_write(phydev, MII_ADDR_C45 | 0x78031, 0xC28);
/* disable DCL calibratin during tear down */
phy_write(phydev, MII_ADDR_C45 | 0x03FFDB, 0xFC10);
/* disable RESET of DCL*/
phy_write(phydev, MII_ADDR_C45 | 0x03FE1B, 0x58);
/* update Initial FFE Coefficients */
phy_write(phydev, MII_ADDR_C45 | 0x03FBBA, 0x0CB2);
phy_write(phydev, MII_ADDR_C45 | 0x03FBBB, 0x0C4A);
/* Toggle Squelch override */
phy_write(phydev, MII_ADDR_C45 | 0x048178, 0x7540);
phy_write(phydev, MII_ADDR_C45 | 0x048178, 0x5540);
/* aneg set pga gain and amp */
phy_write(phydev, MII_ADDR_C45 | 0x03FE5F, 0xE8);
phy_write(phydev, MII_ADDR_C45 | 0x03FE05, 0x755C);
phy_write(phydev, MII_ADDR_C45 | 0x03803A, 0xDA44);
phy_write(phydev, MII_ADDR_C45 | 0x038039, 0x2C0B);
phy_write(phydev, MII_ADDR_C45 | 0x038027, 0x0);//TC10 sleep/wakeup capability disable
q2110_timing_init(phydev);
q2110_dts_init(phydev);
//softResetGe
phy_write(phydev, (MII_ADDR_C45 | 0x03FE1B), 0x48);
phy_write(phydev, (MII_ADDR_C45 | 0x030900), 0x8000);
phy_write(phydev, (MII_ADDR_C45 | 0x03FFE4), 0x000C);
phy_write(phydev, (MII_ADDR_C45 | 0x03FE1B), 0x58);
#if 0
//FE/100BT1
phy_write(phydev, (MII_ADDR_C45 | 0x030900), 0x8000);
phy_write(phydev, (MII_ADDR_C45 | 0x03FFE4), 0x000C);
#endif
printk("phy init ok\n");
return 0;
}
static int q2110_loopback(struct phy_device *phydev, bool enable)
{
int val;
val = phy_read(phydev, Q2110_PCS_CTRL);
if (val < 0)
return val;
val &= ~Q2110_LOOPBACK;
if (enable)
val |= Q2110_LOOPBACK;
val = phy_write(phydev, Q2110_PCS_CTRL, val);
if (val < 0)
return val;
return 0;
}
static int q2110_read_status(struct phy_device *phydev)
{
int val;
unsigned int regVal1, regVal2, regVal3;
phydev->duplex = 1;
phydev->pause = 0;
regVal1 = phy_read(phydev, MII_ADDR_C45 | 0x030901);
regVal1 = phy_read(phydev, MII_ADDR_C45 | 0x030901);
regVal2 = phy_read(phydev, MII_ADDR_C45 | 0x078001);
regVal3 = phy_read(phydev, MII_ADDR_C45 | 0x03FD9D);
phydev->link = ((0x0004U == (regVal1 & 0x0004U))
&& (0x3000U == (regVal2 & 0x3000U))
&& (0x0010U == (regVal3 & 0x0010U))) ? 1 : 0;
if(phydev->link == 0)
{
printk("phy_link_status down: regVal1: %x, regVal2: %x, regVal3: %x.\n",regVal1, regVal2, regVal3);
}
/* val = phy_read(phydev, Q2110_T1_AN_STATUS);
if (val < 0)
return val;
if (val & Q2110_T1_LINK_STATUS)
phydev->link = 1;
else
phydev->link = 0;
*/
val = phy_read(phydev, Q2110_T1_PMA_PMD_CTRL);
if (val < 0)
return val;
if (val & Q2110_T1_LINK_TYPE)
phydev->speed = SPEED_1000;
else
phydev->speed = SPEED_100;
return 0;
}
int q2220_suspend(struct phy_device *phydev)
{
//xf.li 2022/11/9 modify for API-647
printk("phy sleep start\n");
gpio_direction_output(NAD_WAKEUP_PHY1, 0);
mdelay(1);//ensure do not wake up
//phy_write(phydev, MII_ADDR_C45 | 0x038022, 0x1);
phy_write(phydev, Q2220_LPSD_CTRL_1, Q2220_LPSD_DISABLE_REMOTE_WAKE_UP);
mdelay(10);//for enter T1 port sleep, need link parter respond
printk("reg 038021 = %x\n", phy_read(phydev, Q2220_LPSD_CTRL_1));
phy_write(phydev, Q2220_LPSD_STATUS, Q2220_LPSD_POWER_DOWN);// enter LPSD sleep mode
printk("reg 038020 = %x\n", phy_read(phydev, Q2220_LPSD_STATUS));
mdelay(50);
printk("reg 038020 = %x\n", phy_read(phydev, Q2220_LPSD_STATUS));
gpio_direction_output(NAD_RESET_PHY1, 0);
mdelay(1);
gpio_direction_output(PHY_POWER_SUPPLY, 0);
if_suspend = 1;
return 0;
//xf.li 2022/11/9 modify for API-647
}
int q2220_b0_resume(struct phy_device *phydev)
{
//xf.li 2022/11/9 modify for API-647
if(if_suspend == 1)
{
printk("phy awake start\n");
gpio_direction_output(PHY_POWER_SUPPLY, 1);
mdelay(1);
gpio_direction_output(NAD_WAKEUP_PHY1, 1);
mdelay(10);
gpio_direction_output(NAD_WAKEUP_PHY1, 0);
mdelay(5);
gpio_direction_output(NAD_RESET_PHY1, 1);
mdelay(10);
gpio_direction_output(NAD_RESET_PHY1, 0);
mdelay(30);
gpio_direction_output(NAD_RESET_PHY1, 1);
mdelay(10);//at lest 4ms for reset phy
q2220_b0_config_init(phydev);
if_suspend = 0;
}
else
{
printk("q2220_resume: no suspend! In boot state.");
}
return 0;
//xf.li 2022/11/9 modify for API-647
}
int q2220_b1_resume(struct phy_device *phydev)
{
//xf.li 2022/11/9 modify for API-647
if(if_suspend == 1)
{
printk("phy awake start\n");
gpio_direction_output(PHY_POWER_SUPPLY, 1);
mdelay(1);
gpio_direction_output(NAD_WAKEUP_PHY1, 1);
mdelay(10);
gpio_direction_output(NAD_WAKEUP_PHY1, 0);
mdelay(5);
gpio_direction_output(NAD_RESET_PHY1, 1);
mdelay(10);
gpio_direction_output(NAD_RESET_PHY1, 0);
mdelay(30);
gpio_direction_output(NAD_RESET_PHY1, 1);
mdelay(10);//at lest 4ms for reset phy
q2220_b1_config_init(phydev);
if_suspend = 0;
}
else
{
printk("q2220_resume: no suspend! In boot state.");
}
return 0;
//xf.li 2022/11/9 modify for API-647
}
static int q2220_b0_match_phy_device(struct phy_device *phydev)
{
return ((phydev->c45_ids.device_ids[1] & 0xffffffff) == PHY_ID_88Q2220_B0);
}
static int q2220_b1_match_phy_device(struct phy_device *phydev)
{
return ((phydev->c45_ids.device_ids[1] & 0xffffffff) == PHY_ID_88Q2220_B1);
}
static struct phy_driver marvell_88q_driver[] = {
{
.phy_id = PHY_ID_88Q2220_B0,
.phy_id_mask = 0xfffffff0,
.name = "Marvell 88Q2220 B0",
.config_init = q2220_b0_config_init,
.match_phy_device = q2220_b0_match_phy_device,
.set_loopback = &q2110_loopback,
.read_status = q2110_read_status,
.suspend = q2220_suspend,
.resume = q2220_b0_resume,
}, {
.phy_id = PHY_ID_88Q2220_B1,
.phy_id_mask = 0xfffffff0,
.name = "Marvell 88Q2220 B1",
.config_init = q2220_b1_config_init,
.match_phy_device = q2220_b1_match_phy_device,
.set_loopback = &q2110_loopback,
.read_status = q2110_read_status,
.suspend = q2220_suspend,
.resume = q2220_b1_resume,
}
};
module_phy_driver(marvell_88q_driver);