| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Driver for Motorcomm PHYs |
| * |
| * Author: Peter Geis <pgwipeout@gmail.com> |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/phy.h> |
| |
| #define PHY_ID_YT8511 0x0000010a |
| |
| #define YT8511_PAGE_SELECT 0x1e |
| #define YT8511_PAGE 0x1f |
| #define YT8511_EXT_CLK_GATE 0x0c |
| #define YT8511_EXT_DELAY_DRIVE 0x0d |
| #define YT8511_EXT_SLEEP_CTRL 0x27 |
| |
| /* 2b00 25m from pll |
| * 2b01 25m from xtl *default* |
| * 2b10 62.m from pll |
| * 2b11 125m from pll |
| */ |
| #define YT8511_CLK_125M (BIT(2) | BIT(1)) |
| #define YT8511_PLLON_SLP BIT(14) |
| |
| /* RX Delay enabled = 1.8ns 1000T, 8ns 10/100T */ |
| #define YT8511_DELAY_RX BIT(0) |
| |
| /* TX Gig-E Delay is bits 7:4, default 0x5 |
| * TX Fast-E Delay is bits 15:12, default 0xf |
| * Delay = 150ps * N - 250ps |
| * On = 2000ps, off = 50ps |
| */ |
| #define YT8511_DELAY_GE_TX_EN (0xf << 4) |
| #define YT8511_DELAY_GE_TX_DIS (0x2 << 4) |
| #define YT8511_DELAY_FE_TX_EN (0xf << 12) |
| #define YT8511_DELAY_FE_TX_DIS (0x2 << 12) |
| |
| static int yt8511_read_page(struct phy_device *phydev) |
| { |
| return __phy_read(phydev, YT8511_PAGE_SELECT); |
| }; |
| |
| static int yt8511_write_page(struct phy_device *phydev, int page) |
| { |
| return __phy_write(phydev, YT8511_PAGE_SELECT, page); |
| }; |
| |
| static int yt8511_config_init(struct phy_device *phydev) |
| { |
| int oldpage, ret = 0; |
| unsigned int ge, fe; |
| |
| oldpage = phy_select_page(phydev, YT8511_EXT_CLK_GATE); |
| if (oldpage < 0) |
| goto err_restore_page; |
| |
| /* set rgmii delay mode */ |
| switch (phydev->interface) { |
| case PHY_INTERFACE_MODE_RGMII: |
| ge = YT8511_DELAY_GE_TX_DIS; |
| fe = YT8511_DELAY_FE_TX_DIS; |
| break; |
| case PHY_INTERFACE_MODE_RGMII_RXID: |
| ge = YT8511_DELAY_RX | YT8511_DELAY_GE_TX_DIS; |
| fe = YT8511_DELAY_FE_TX_DIS; |
| break; |
| case PHY_INTERFACE_MODE_RGMII_TXID: |
| ge = YT8511_DELAY_GE_TX_EN; |
| fe = YT8511_DELAY_FE_TX_EN; |
| break; |
| case PHY_INTERFACE_MODE_RGMII_ID: |
| ge = YT8511_DELAY_RX | YT8511_DELAY_GE_TX_EN; |
| fe = YT8511_DELAY_FE_TX_EN; |
| break; |
| default: /* do not support other modes */ |
| ret = -EOPNOTSUPP; |
| goto err_restore_page; |
| } |
| |
| ret = __phy_modify(phydev, YT8511_PAGE, (YT8511_DELAY_RX | YT8511_DELAY_GE_TX_EN), ge); |
| if (ret < 0) |
| goto err_restore_page; |
| |
| /* set clock mode to 125mhz */ |
| ret = __phy_modify(phydev, YT8511_PAGE, 0, YT8511_CLK_125M); |
| if (ret < 0) |
| goto err_restore_page; |
| |
| /* fast ethernet delay is in a separate page */ |
| ret = __phy_write(phydev, YT8511_PAGE_SELECT, YT8511_EXT_DELAY_DRIVE); |
| if (ret < 0) |
| goto err_restore_page; |
| |
| ret = __phy_modify(phydev, YT8511_PAGE, YT8511_DELAY_FE_TX_EN, fe); |
| if (ret < 0) |
| goto err_restore_page; |
| |
| /* leave pll enabled in sleep */ |
| ret = __phy_write(phydev, YT8511_PAGE_SELECT, YT8511_EXT_SLEEP_CTRL); |
| if (ret < 0) |
| goto err_restore_page; |
| |
| ret = __phy_modify(phydev, YT8511_PAGE, 0, YT8511_PLLON_SLP); |
| if (ret < 0) |
| goto err_restore_page; |
| |
| err_restore_page: |
| return phy_restore_page(phydev, oldpage, ret); |
| } |
| |
| static struct phy_driver motorcomm_phy_drvs[] = { |
| { |
| PHY_ID_MATCH_EXACT(PHY_ID_YT8511), |
| .name = "YT8511 Gigabit Ethernet", |
| .config_init = yt8511_config_init, |
| .suspend = genphy_suspend, |
| .resume = genphy_resume, |
| .read_page = yt8511_read_page, |
| .write_page = yt8511_write_page, |
| }, |
| }; |
| |
| module_phy_driver(motorcomm_phy_drvs); |
| |
| MODULE_DESCRIPTION("Motorcomm PHY driver"); |
| MODULE_AUTHOR("Peter Geis"); |
| MODULE_LICENSE("GPL"); |
| |
| static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = { |
| { PHY_ID_MATCH_EXACT(PHY_ID_YT8511) }, |
| { /* sentinal */ } |
| }; |
| |
| MODULE_DEVICE_TABLE(mdio, motorcomm_tbl); |