diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 698bea312adc..626ec44248dc 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -223,6 +223,11 @@ config MICROSEMI_PHY help Currently supports VSC8514, VSC8530, VSC8531, VSC8540 and VSC8541 PHYs +config MOTORCOMM_PHY + tristate "Motorcomm PHYs" + help + Supports the YT8010, YT8510, YT8511, YT8512 PHYs. + config NATIONAL_PHY tristate "National Semiconductor PHYs" help diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index a13e402074cf..c99b00bc24b5 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_MICREL_PHY) += micrel.o obj-$(CONFIG_MICROCHIP_PHY) += microchip.o obj-$(CONFIG_MICROCHIP_T1_PHY) += microchip_t1.o obj-$(CONFIG_MICROSEMI_PHY) += mscc/ +obj-$(CONFIG_MOTORCOMM_PHY) += motorcomm.o obj-$(CONFIG_NATIONAL_PHY) += national.o obj-$(CONFIG_NXP_TJA11XX_PHY) += nxp-tja11xx.o obj-$(CONFIG_QSEMI_PHY) += qsemi.o diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c new file mode 100644 index 000000000000..c53b5b88b226 --- /dev/null +++ b/drivers/net/phy/motorcomm.c @@ -0,0 +1,350 @@ +/* + * drivers/net/phy/motorcomm.c + * + * Driver for Motorcomm PHYs + * + * Author: Leilei Zhao + * + * 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 +#include +#include +#include +#include +#include + +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; +} + +static int yt8512_clk_init(struct phy_device *phydev) +{ + int ret; + int val; + + 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; + + val = ytphy_read_ext(phydev, YT8512_EXTREG_EXTEND_COMBO); + if (val < 0) + return val; + + val |= YT8512_CONTROL1_RMII_EN; + + 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); + + 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; + + 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 = SPEED_UNKNOWN; + break; + } + + phydev->speed = speed; + phydev->duplex = duplex; + + return 0; +} + +static int yt8521_config_init(struct phy_device *phydev) +{ + int ret; + int val; + + /* 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; + + /* switch to access UTP */ + ret = ytphy_write_ext(phydev, 0xa000, 0); + if (ret < 0) + return ret; + + /* enable RXC clock when no wire plug */ + 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; + + /* output SyncE clock (125mhz) even link is down */ + ret = ytphy_write_ext(phydev, 0xa012, 0x38); + if (ret < 0) + return ret; + + /* disable rgmii clk 2ns delay */ + val = ytphy_read_ext(phydev, 0xa001); + if (val < 0) + return val; + + val &= ~(1 << 8); + ret = ytphy_write_ext(phydev, 0xa001, val); + if (ret < 0) + return ret; + + /* setup delay */ + val = (1 << 10) | (0xf << 4) | 5; + ret = ytphy_write_ext(phydev, 0xa003, val); + if (ret < 0) + return ret; + + /* LED0: Unused/Off, LED1: Link, LED2: Activity, 8Hz */ + ytphy_write_ext(phydev, 0xa00b, 0xe004); + ytphy_write_ext(phydev, 0xa00c, 0); + ytphy_write_ext(phydev, 0xa00d, 0x2600); + ytphy_write_ext(phydev, 0xa00e, 0x0070); + ytphy_write_ext(phydev, 0xa00f, 0x000a); + + return 0; +} + +static int yt8521_ack_interrupt(struct phy_device *phydev) +{ + int val; + + val = phy_read(phydev, REG_INT_STATUS); + phydev_dbg(phydev, "intr status 0x04%x\n", val); + + return (val < 0) ? val : 0; +} + +static int yt8521_config_intr(struct phy_device *phydev) +{ + int val; + int err; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + val = BIT(14) | BIT(13) | BIT(11) | BIT(10); + else + val = 0; + + err = phy_write(phydev, REG_INT_MASK, val); + if (err) + return err; + err = yt8521_ack_interrupt(phydev); + + return err; +} + +static struct phy_driver ytphy_drvs[] = { + { + .phy_id = PHY_ID_YT8010, + .name = "YT8010 Automotive Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_BASIC_FEATURES, + .config_aneg = yt8010_config_aneg, + .read_status = genphy_read_status, + }, { + .phy_id = PHY_ID_YT8510, + .name = "YT8510 100/10Mb Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_BASIC_FEATURES, + .read_status = genphy_read_status, + }, { + .phy_id = PHY_ID_YT8511, + .name = "YT8511 Gigabit Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + .features = PHY_GBIT_FEATURES, + .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, + .features = PHY_BASIC_FEATURES, + .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, + .features = PHY_BASIC_FEATURES, + .config_init = yt8512_config_init, + .read_status = yt8512_read_status, + .suspend = genphy_suspend, + .resume = genphy_resume, + }, { + .phy_id = PHY_ID_YT8521, + .name = "YT8521 Ethernet", + .phy_id_mask = MOTORCOMM_PHY_ID_MASK, + /* PHY_GBIT_FEATURES */ + .config_init = yt8521_config_init, + .config_intr = yt8521_config_intr, + .suspend = genphy_suspend, + .resume = genphy_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); diff --git a/include/linux/motorcomm_phy.h b/include/linux/motorcomm_phy.h new file mode 100644 index 000000000000..1e48c3671b69 --- /dev/null +++ b/include/linux/motorcomm_phy.h @@ -0,0 +1,67 @@ +/* + * include/linux/motorcomm_phy.h + * + * Motorcomm PHY IDs + * + * 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. + * + */ + +#ifndef _MOTORCOMM_PHY_H +#define _MOTORCOMM_PHY_H + +#define MOTORCOMM_PHY_ID_MASK 0x00000fff + +#define PHY_ID_YT8010 0x00000309 +#define PHY_ID_YT8510 0x00000109 +#define PHY_ID_YT8511 0x0000010a +#define PHY_ID_YT8512 0x00000118 +#define PHY_ID_YT8512B 0x00000128 +#define PHY_ID_YT8521 0x0000011a + +#define REG_PHY_SPEC_STATUS 0x11 +#define REG_INT_MASK 0x12 +#define REG_INT_STATUS 0x13 +#define REG_DEBUG_ADDR_OFFSET 0x1e +#define REG_DEBUG_DATA 0x1f + +#define YT8512_EXTREG_AFE_PLL 0x50 +#define YT8512_EXTREG_EXTEND_COMBO 0x4000 +#define YT8512_EXTREG_LED0 0x40c0 +#define YT8512_EXTREG_LED1 0x40c3 + +#define YT8512_EXTREG_SLEEP_CONTROL1 0x2027 + +#define YT_SOFTWARE_RESET 0x8000 + +#define YT8512_CONFIG_PLL_REFCLK_SEL_EN 0x0040 +#define YT8512_CONTROL1_RMII_EN 0x0001 +#define YT8512_LED0_ACT_BLK_IND 0x1000 +#define YT8512_LED0_DIS_LED_AN_TRY 0x0001 +#define YT8512_LED0_BT_BLK_EN 0x0002 +#define YT8512_LED0_HT_BLK_EN 0x0004 +#define YT8512_LED0_COL_BLK_EN 0x0008 +#define YT8512_LED0_BT_ON_EN 0x0010 +#define YT8512_LED1_BT_ON_EN 0x0010 +#define YT8512_LED1_TXACT_BLK_EN 0x0100 +#define YT8512_LED1_RXACT_BLK_EN 0x0200 +#define YT8512_SPEED_MODE 0xc000 +#define YT8512_DUPLEX 0x2000 + +#define YT8512_SPEED_MODE_BIT 14 +#define YT8512_DUPLEX_BIT 13 +#define YT8512_EN_SLEEP_SW_BIT 15 + +#define YT8521_EXTREG_SLEEP_CONTROL1 0x27 +#define YT8521_EN_SLEEP_SW_BIT 15 + +#define YT8521_SPEED_MODE 0xc000 +#define YT8521_DUPLEX 0x2000 +#define YT8521_SPEED_MODE_BIT 14 +#define YT8521_DUPLEX_BIT 13 +#define YT8521_LINK_STATUS_BIT 10 + +#endif /* _MOTORCOMM_PHY_H */