| /* |
| * 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); |
| |