blob: 6023b2886d8980d73ca48887365e3bdbaad90ecd [file] [log] [blame]
/*
* drivers/net/phy/motorcomm.c
*
* Driver for Motorcomm PHYs
*
* Author: Leilei Zhao <leilei.zhao@motorcomm.com>
*
* Copyright (c) 2019 Motorcomm, Inc.
*
* 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.
*
* Support : Motorcomm Phys:
* Giga phys: yt8511, yt8521
* 100/10 Phys : yt8512, yt8512b, yt8510
* Automotive 100Mb Phys : yt8010
* Automotive 100/10 hyper range Phys: yt8510
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include "motorcomm_phy.h"
static int ytphy_config_init(struct phy_device *phydev)
{
return 0;
}
static int ytphy_read_ext(struct phy_device *phydev, u32 regnum)
{
int ret;
int val;
ret = phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
if (ret < 0)
return ret;
val = phy_read(phydev, REG_DEBUG_DATA);
return val;
}
static int ytphy_write_ext(struct phy_device *phydev, u32 regnum, u16 val)
{
int ret;
ret = phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
if (ret < 0)
return ret;
ret = phy_write(phydev, REG_DEBUG_DATA, val);
return ret;
}
static int yt8010_config_aneg(struct phy_device *phydev)
{
phydev->speed = SPEED_100;
return 0;
}
/*
* Write_ext_reg 0xa010[5:4], 2'b11 mean strong, 2'b00 mean weak.
*/
static int yt8512_set_phy_driver_strength(struct phy_device *phydev, int driver)
{
int val, ret;
val = ytphy_read_ext(phydev, 0x4001);
if (val < 0)
return val;
printk("default ext_reg[0x4001]: 0x%x\r\n", val);
val &= ~(0x3 << 4);
val |= ((driver & 0x3) << 4);
ytphy_write_ext(phydev, 0x4001, val);
if (ret < 0)
return ret;
val = ytphy_read_ext(phydev, 0x4001);
if (val < 0)
return val;
printk("changed ext_reg[0x4001]: 0x%x\r\n", val);
return 0;
}
static int yt8512_config_intr(struct phy_device *phydev)
{
int ret;
int val;
val = phy_read(phydev, 0x12);
if (val < 0) {
dev_err(&phydev->mdio.dev,
"Error: Read of Interrupt Mask Register failed: %d\n",
val);
return 0;
}
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
val |= 0x1 << 10 | 0x1 << 11;
else
val &= ~(0x1 << 10 | 0x1 << 11);
ret = phy_write(phydev, 0x12, val);
return ret;
}
static int yt8512_did_interrupt(struct phy_device *phydev)
{
int val;
val = phy_read(phydev, 0x13);
printk("%s: Interrupt Status Register: 0x%x\r\n", __func__, val);
if (val & (0x1 << 10 | 0x1 << 11))
return 1;
return 0;
}
static int yt8512_ack_interrupt(struct phy_device *phydev)
{
yt8512_did_interrupt(phydev);
return 0;
}
static int yt8512_clk_init(struct phy_device *phydev)
{
struct device_node *np = phydev->mdio.dev.of_node;
int driver_strength;
int ret;
int val;
#if 0
val = ytphy_read_ext(phydev, YT8512_EXTREG_AFE_PLL);
if (val < 0)
return val;
val |= YT8512_CONFIG_PLL_REFCLK_SEL_EN;
ret = ytphy_write_ext(phydev, YT8512_EXTREG_AFE_PLL, val);
if (ret < 0)
return ret;
#endif
val = ytphy_read_ext(phydev, YT8512_EXTREG_EXTEND_COMBO);
if (val < 0)
return val;
//val |= YT8512_CONTROL1_RMII_EN;
/* enable RMII2, MAC need TX_CLK from phy */
val &= ~0x3;
val |= 0x2;
ret = ytphy_write_ext(phydev, YT8512_EXTREG_EXTEND_COMBO, val);
if (ret < 0)
return ret;
val = phy_read(phydev, MII_BMCR);
if (val < 0)
return val;
val |= YT_SOFTWARE_RESET;
ret = phy_write(phydev, MII_BMCR, val);
if (of_property_read_u32(np, "driver_strength", &driver_strength))
driver_strength = 0x2;
else
printk("==> driver_strength: %d\n", driver_strength);
yt8512_set_phy_driver_strength(phydev, driver_strength);
return ret;
}
static int yt8512_led_init(struct phy_device *phydev)
{
int ret;
int val;
int mask;
val = ytphy_read_ext(phydev, YT8512_EXTREG_LED0);
if (val < 0)
return val;
val |= YT8512_LED0_ACT_BLK_IND;
mask = YT8512_LED0_DIS_LED_AN_TRY | YT8512_LED0_BT_BLK_EN |
YT8512_LED0_HT_BLK_EN | YT8512_LED0_COL_BLK_EN |
YT8512_LED0_BT_ON_EN;
val &= ~mask;
ret = ytphy_write_ext(phydev, YT8512_EXTREG_LED0, val);
if (ret < 0)
return ret;
val = ytphy_read_ext(phydev, YT8512_EXTREG_LED1);
if (val < 0)
return val;
val |= YT8512_LED1_BT_ON_EN;
mask = YT8512_LED1_TXACT_BLK_EN | YT8512_LED1_RXACT_BLK_EN;
val &= ~mask;
ret = ytphy_write_ext(phydev, YT8512_LED1_BT_ON_EN, val);
return ret;
}
static int yt8512_config_init(struct phy_device *phydev)
{
int ret;
int val;
printk("enter yt8512_config_init\n");
ret = ytphy_config_init(phydev);
if (ret < 0)
return ret;
ret = yt8512_clk_init(phydev);
if (ret < 0)
return ret;
ret = yt8512_led_init(phydev);
/* disable auto sleep */
val = ytphy_read_ext(phydev, YT8512_EXTREG_SLEEP_CONTROL1);
if (val < 0)
return val;
val &= (~BIT(YT8512_EN_SLEEP_SW_BIT));
ret = ytphy_write_ext(phydev, YT8512_EXTREG_SLEEP_CONTROL1, val);
if (ret < 0)
return ret;
return ret;
}
static int yt8512_read_status(struct phy_device *phydev)
{
int ret;
int val;
int speed, speed_mode, duplex;
ret = genphy_update_link(phydev);
if (ret)
return ret;
val = phy_read(phydev, REG_PHY_SPEC_STATUS);
if (val < 0)
return val;
duplex = (val & YT8512_DUPLEX) >> YT8512_DUPLEX_BIT;
speed_mode = (val & YT8512_SPEED_MODE) >> YT8512_SPEED_MODE_BIT;
switch (speed_mode) {
case 0:
speed = SPEED_10;
break;
case 1:
speed = SPEED_100;
break;
case 2:
case 3:
default:
speed = -1;
break;
}
phydev->speed = speed;
phydev->duplex = duplex;
return 0;
}
/*
* Write_ext_reg 0xa003[3:0], one step means about 150ps,
* valid for RGMII 125MHz
* Write_ext_reg 0xa003[7:4], valid for RGMII 25/2.5MHz
*/
static int yt8521_set_tx_delay(struct phy_device *phydev, int delay)
{
int val;
val = ytphy_read_ext(phydev, 0xa003);
val &= ~0xf;
val |= (delay & 0xf);
ytphy_write_ext(phydev, 0xa003, val);
return 0;
}
/*
* Write_ext_reg 0xa003[13:10], one step means about 150ps,
* valid for RGMII 125/25/2.5MHz
*/
static int yt8521_set_rx_delay(struct phy_device *phydev, int delay)
{
int val;
if (delay > 0) {
/* disable rx 2ns delay from strap pin */
val = ytphy_read_ext(phydev, 0xa001);
val &= ~(0x1 << 8);
ytphy_write_ext(phydev, 0xa001, val);
val = ytphy_read_ext(phydev, 0xa001);
/* config internal delay */
val = ytphy_read_ext(phydev, 0xa003);
val &= ~(0xf << 10);
val |= ((delay & 0xf) << 10);
ytphy_write_ext(phydev, 0xa003, val);
}
return 0;
}
/*
* Write_ext_reg 0xa010[5:4], 2'b11 mean strong, 2'b00 mean weak.
*/
static int yt8521_set_phy_driver_strength(struct phy_device *phydev, int driver)
{
int val;
val = ytphy_read_ext(phydev, 0xa010);
val &= ~(0x3 << 4);
val |= ((driver & 0x3) << 4);
ytphy_write_ext(phydev, 0xa010, val);
return 0;
}
static int yt8521_config_intr(struct phy_device *phydev)
{
int ret;
int val;
ytphy_write_ext(phydev, 0xa000, 0);
/* select UTP interrupt */
val = ytphy_read_ext(phydev, 0xa00a);
if (val < 0)
return val;
val &= (~BIT(6));
ret = ytphy_write_ext(phydev, 0xa00a, val);
if (ret < 0)
return ret;
val = phy_read(phydev, 0x12);
if (val < 0) {
dev_err(&phydev->mdio.dev,
"Error: Read of Interrupt Mask Register failed: %d\n",
val);
return 0;
}
/*
* Speed Changed/Link Failed/Link Succeed INT/WOL INT
*/
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
val |= 0x1 << 6 | 0x1 << 10 | 0x1 << 11 | 0x1 << 14;
else
val &= ~(0x1 << 6 | 0x1 << 10 | 0x1 << 11 | 0x1 << 14);
ret = phy_write(phydev, 0x12, val);
return ret;
}
static int yt8521_did_interrupt(struct phy_device *phydev)
{
int val;
ytphy_write_ext(phydev, 0xa000, 0);
val = phy_read(phydev, 0x13);
printk("%s: Interrupt Status Register: 0x%x\r\n", __func__, val);
if (val & ( 0x1 << 6 | 0x1 << 10 | 0x1 << 11 | 0x1 << 14))
return 1;
return 0;
}
static int yt8521_ack_interrupt(struct phy_device *phydev)
{
yt8512_did_interrupt(phydev);
return 0;
}
static int yt8521_config_init(struct phy_device *phydev)
{
struct device_node *np = phydev->mdio.dev.of_node;
int driver_strength;
int delay[2];
int ret;
int val;
//phydev->irq = PHY_POLL;
printk("enter yt8521_config_init\n");
ytphy_write_ext(phydev, 0xa000, 0);
ret = ytphy_config_init(phydev);
if (ret < 0)
return ret;
if (of_property_read_u32(np, "driver_strength", &driver_strength))
driver_strength = 0x2;
else
printk("==> driver_strength: %d\n", driver_strength);
yt8521_set_phy_driver_strength(phydev, driver_strength);
/* disable auto sleep */
val = ytphy_read_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1);
if (val < 0)
return val;
val &= (~BIT(YT8521_EN_SLEEP_SW_BIT));
ret = ytphy_write_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1, val);
if (ret < 0)
return ret;
/* enable RXC clock when no wire plug */
ret = ytphy_write_ext(phydev, 0xa000, 0);
if (ret < 0)
return ret;
val = ytphy_read_ext(phydev, 0xc);
if (val < 0)
return val;
val &= ~(1 << 12);
ret = ytphy_write_ext(phydev, 0xc, val);
if (ret < 0)
return ret;
val = phy_read(phydev, MII_BMCR);
if (val < 0)
return val;
val |= YT_SOFTWARE_RESET;
ret = phy_write(phydev, MII_BMCR, val);
if (of_property_read_u32_array(np, "tx_rx_delay", delay, 2))
delay[0] = delay[1] = 0;
else
printk("===> tx_rx_delay tx:%d rx:%d\n", delay[0], delay[1]);
/* set tx clock delay to 2.25ns */
if (delay[0])
yt8521_set_tx_delay(phydev, delay[0]);
yt8521_set_rx_delay(phydev, delay[1]);
return ret;
}
static int yt8521_adjust_status(struct phy_device *phydev, int val, int is_utp)
{
int speed_mode, duplex;
int speed = -1;
duplex = (val & YT8512_DUPLEX) >> YT8521_DUPLEX_BIT;
speed_mode = (val & YT8521_SPEED_MODE) >> YT8521_SPEED_MODE_BIT;
switch (speed_mode) {
case 0:
if (is_utp)
speed = SPEED_10;
break;
case 1:
speed = SPEED_100;
break;
case 2:
speed = SPEED_1000;
break;
case 3:
break;
default:
speed = -1;
break;
}
phydev->speed = speed;
phydev->duplex = duplex;
return 0;
}
static int yt8521_read_status(struct phy_device *phydev)
{
int ret;
int val;
int link;
int link_utp, link_fiber;
/* reading UTP */
ret = ytphy_write_ext(phydev, 0xa000, 0);
if (ret < 0)
return ret;
val = phy_read(phydev, REG_PHY_SPEC_STATUS);
if (val < 0)
return val;
link = val & (BIT(YT8521_LINK_STATUS_BIT));
if (link) {
link_utp = 1;
yt8521_adjust_status(phydev, val, 1);
} else {
link_utp = 0;
}
/* reading Fiber */
ret = ytphy_write_ext(phydev, 0xa000, 2);
if (ret < 0)
return ret;
val = phy_read(phydev, REG_PHY_SPEC_STATUS);
if (val < 0)
return val;
link = val & (BIT(YT8521_LINK_STATUS_BIT));
if (link) {
link_fiber = 1;
yt8521_adjust_status(phydev, val, 0);
} else {
link_fiber = 0;
}
if (link_utp || link_fiber) {
phydev->link = 1;
} else {
phydev->link = 0;
}
if (link_utp) {
ytphy_write_ext(phydev, 0xa000, 0);
}
return 0;
}
int yt8521_suspend(struct phy_device *phydev)
{
int value;
//mutex_lock(&phydev->lock);
ytphy_write_ext(phydev, 0xa000, 0);
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);
ytphy_write_ext(phydev, 0xa000, 2);
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);
ytphy_write_ext(phydev, 0xa000, 0);
//mutex_unlock(&phydev->lock);
return 0;
}
int yt8521_resume(struct phy_device *phydev)
{
int value;
ytphy_write_ext(phydev, 0xa000, 0);
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);
ytphy_write_ext(phydev, 0xa000, 2);
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);
ytphy_write_ext(phydev, 0xa000, 0);
return 0;
}
static struct phy_driver ytphy_drvs[] = {
{
.phy_id = PHY_ID_YT8010,
.name = "YT8010 Automotive Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.config_aneg = yt8010_config_aneg,
.config_init = ytphy_config_init,
.read_status = genphy_read_status,
}, {
.phy_id = PHY_ID_YT8510,
.name = "YT8510 100/10Mb Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.config_aneg = genphy_config_aneg,
.config_init = ytphy_config_init,
.read_status = genphy_read_status,
}, {
.phy_id = PHY_ID_YT8511,
.name = "YT8511 Gigabit Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.config_aneg = genphy_config_aneg,
.config_init = ytphy_config_init,
.read_status = genphy_read_status,
.suspend = genphy_suspend,
.resume = genphy_resume,
}, {
.phy_id = PHY_ID_YT8512,
.name = "YT8512 Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.config_aneg = genphy_config_aneg,
.config_init = yt8512_config_init,
.read_status = yt8512_read_status,
.suspend = genphy_suspend,
.resume = genphy_resume,
}, {
.phy_id = PHY_ID_YT8512B,
.name = "YT8512B Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.config_aneg = genphy_config_aneg,
.config_init = yt8512_config_init,
.read_status = yt8512_read_status,
.ack_interrupt = yt8512_ack_interrupt,
.config_intr = yt8512_config_intr,
.did_interrupt = yt8512_did_interrupt,
.suspend = genphy_suspend,
.resume = genphy_resume,
}, {
.phy_id = PHY_ID_YT8521,
.name = "YT8521 Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.config_aneg = genphy_config_aneg,
.config_init = yt8521_config_init,
.read_status = yt8521_read_status,
.ack_interrupt = yt8521_ack_interrupt,
.config_intr = yt8521_config_intr,
.did_interrupt = yt8521_did_interrupt,
.suspend = yt8521_suspend,
.resume = yt8521_resume,
},
};
module_phy_driver(ytphy_drvs);
MODULE_DESCRIPTION("Motorcomm PHY driver");
MODULE_AUTHOR("Leilei Zhao");
MODULE_LICENSE("GPL");
static struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
{ PHY_ID_YT8010, MOTORCOMM_PHY_ID_MASK },
{ PHY_ID_YT8510, MOTORCOMM_PHY_ID_MASK },
{ PHY_ID_YT8511, MOTORCOMM_PHY_ID_MASK },
{ PHY_ID_YT8512, MOTORCOMM_PHY_ID_MASK },
{ PHY_ID_YT8512B, MOTORCOMM_PHY_ID_MASK },
{ PHY_ID_YT8521, MOTORCOMM_PHY_ID_MASK },
{ }
};
MODULE_DEVICE_TABLE(mdio, motorcomm_tbl);