162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for Microchip 10BASE-T1S PHYs 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Support: Microchip Phys: 662306a36Sopenharmony_ci * lan8670/1/2 Rev.B1 762306a36Sopenharmony_ci * lan8650/1 Rev.B0 Internal PHYs 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/phy.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#define PHY_ID_LAN867X_REVB1 0x0007C162 1562306a36Sopenharmony_ci#define PHY_ID_LAN865X_REVB0 0x0007C1B3 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define LAN867X_REG_STS2 0x0019 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define LAN867x_RESET_COMPLETE_STS BIT(11) 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define LAN865X_REG_CFGPARAM_ADDR 0x00D8 2262306a36Sopenharmony_ci#define LAN865X_REG_CFGPARAM_DATA 0x00D9 2362306a36Sopenharmony_ci#define LAN865X_REG_CFGPARAM_CTRL 0x00DA 2462306a36Sopenharmony_ci#define LAN865X_REG_STS2 0x0019 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define LAN865X_CFGPARAM_READ_ENABLE BIT(1) 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci/* The arrays below are pulled from the following table from AN1699 2962306a36Sopenharmony_ci * Access MMD Address Value Mask 3062306a36Sopenharmony_ci * RMW 0x1F 0x00D0 0x0002 0x0E03 3162306a36Sopenharmony_ci * RMW 0x1F 0x00D1 0x0000 0x0300 3262306a36Sopenharmony_ci * RMW 0x1F 0x0084 0x3380 0xFFC0 3362306a36Sopenharmony_ci * RMW 0x1F 0x0085 0x0006 0x000F 3462306a36Sopenharmony_ci * RMW 0x1F 0x008A 0xC000 0xF800 3562306a36Sopenharmony_ci * RMW 0x1F 0x0087 0x801C 0x801C 3662306a36Sopenharmony_ci * RMW 0x1F 0x0088 0x033F 0x1FFF 3762306a36Sopenharmony_ci * W 0x1F 0x008B 0x0404 ------ 3862306a36Sopenharmony_ci * RMW 0x1F 0x0080 0x0600 0x0600 3962306a36Sopenharmony_ci * RMW 0x1F 0x00F1 0x2400 0x7F00 4062306a36Sopenharmony_ci * RMW 0x1F 0x0096 0x2000 0x2000 4162306a36Sopenharmony_ci * W 0x1F 0x0099 0x7F80 ------ 4262306a36Sopenharmony_ci */ 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic const u32 lan867x_revb1_fixup_registers[12] = { 4562306a36Sopenharmony_ci 0x00D0, 0x00D1, 0x0084, 0x0085, 4662306a36Sopenharmony_ci 0x008A, 0x0087, 0x0088, 0x008B, 4762306a36Sopenharmony_ci 0x0080, 0x00F1, 0x0096, 0x0099, 4862306a36Sopenharmony_ci}; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic const u16 lan867x_revb1_fixup_values[12] = { 5162306a36Sopenharmony_ci 0x0002, 0x0000, 0x3380, 0x0006, 5262306a36Sopenharmony_ci 0xC000, 0x801C, 0x033F, 0x0404, 5362306a36Sopenharmony_ci 0x0600, 0x2400, 0x2000, 0x7F80, 5462306a36Sopenharmony_ci}; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic const u16 lan867x_revb1_fixup_masks[12] = { 5762306a36Sopenharmony_ci 0x0E03, 0x0300, 0xFFC0, 0x000F, 5862306a36Sopenharmony_ci 0xF800, 0x801C, 0x1FFF, 0xFFFF, 5962306a36Sopenharmony_ci 0x0600, 0x7F00, 0x2000, 0xFFFF, 6062306a36Sopenharmony_ci}; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci/* LAN865x Rev.B0 configuration parameters from AN1760 */ 6362306a36Sopenharmony_cistatic const u32 lan865x_revb0_fixup_registers[28] = { 6462306a36Sopenharmony_ci 0x0091, 0x0081, 0x0043, 0x0044, 6562306a36Sopenharmony_ci 0x0045, 0x0053, 0x0054, 0x0055, 6662306a36Sopenharmony_ci 0x0040, 0x0050, 0x00D0, 0x00E9, 6762306a36Sopenharmony_ci 0x00F5, 0x00F4, 0x00F8, 0x00F9, 6862306a36Sopenharmony_ci 0x00B0, 0x00B1, 0x00B2, 0x00B3, 6962306a36Sopenharmony_ci 0x00B4, 0x00B5, 0x00B6, 0x00B7, 7062306a36Sopenharmony_ci 0x00B8, 0x00B9, 0x00BA, 0x00BB, 7162306a36Sopenharmony_ci}; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic const u16 lan865x_revb0_fixup_values[28] = { 7462306a36Sopenharmony_ci 0x9660, 0x00C0, 0x00FF, 0xFFFF, 7562306a36Sopenharmony_ci 0x0000, 0x00FF, 0xFFFF, 0x0000, 7662306a36Sopenharmony_ci 0x0002, 0x0002, 0x5F21, 0x9E50, 7762306a36Sopenharmony_ci 0x1CF8, 0xC020, 0x9B00, 0x4E53, 7862306a36Sopenharmony_ci 0x0103, 0x0910, 0x1D26, 0x002A, 7962306a36Sopenharmony_ci 0x0103, 0x070D, 0x1720, 0x0027, 8062306a36Sopenharmony_ci 0x0509, 0x0E13, 0x1C25, 0x002B, 8162306a36Sopenharmony_ci}; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic const u16 lan865x_revb0_fixup_cfg_regs[5] = { 8462306a36Sopenharmony_ci 0x0084, 0x008A, 0x00AD, 0x00AE, 0x00AF 8562306a36Sopenharmony_ci}; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci/* Pulled from AN1760 describing 'indirect read' 8862306a36Sopenharmony_ci * 8962306a36Sopenharmony_ci * write_register(0x4, 0x00D8, addr) 9062306a36Sopenharmony_ci * write_register(0x4, 0x00DA, 0x2) 9162306a36Sopenharmony_ci * return (int8)(read_register(0x4, 0x00D9)) 9262306a36Sopenharmony_ci * 9362306a36Sopenharmony_ci * 0x4 refers to memory map selector 4, which maps to MDIO_MMD_VEND2 9462306a36Sopenharmony_ci */ 9562306a36Sopenharmony_cistatic int lan865x_revb0_indirect_read(struct phy_device *phydev, u16 addr) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci int ret; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_ADDR, 10062306a36Sopenharmony_ci addr); 10162306a36Sopenharmony_ci if (ret) 10262306a36Sopenharmony_ci return ret; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_CTRL, 10562306a36Sopenharmony_ci LAN865X_CFGPARAM_READ_ENABLE); 10662306a36Sopenharmony_ci if (ret) 10762306a36Sopenharmony_ci return ret; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci return phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_DATA); 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci/* This is pulled straight from AN1760 from 'calculation of offset 1' & 11362306a36Sopenharmony_ci * 'calculation of offset 2' 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_cistatic int lan865x_generate_cfg_offsets(struct phy_device *phydev, s8 offsets[2]) 11662306a36Sopenharmony_ci{ 11762306a36Sopenharmony_ci const u16 fixup_regs[2] = {0x0004, 0x0008}; 11862306a36Sopenharmony_ci int ret; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci for (int i = 0; i < ARRAY_SIZE(fixup_regs); i++) { 12162306a36Sopenharmony_ci ret = lan865x_revb0_indirect_read(phydev, fixup_regs[i]); 12262306a36Sopenharmony_ci if (ret < 0) 12362306a36Sopenharmony_ci return ret; 12462306a36Sopenharmony_ci if (ret & BIT(4)) 12562306a36Sopenharmony_ci offsets[i] = ret | 0xE0; 12662306a36Sopenharmony_ci else 12762306a36Sopenharmony_ci offsets[i] = ret; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci return 0; 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic int lan865x_read_cfg_params(struct phy_device *phydev, u16 cfg_params[]) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci int ret; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs); i++) { 13862306a36Sopenharmony_ci ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 13962306a36Sopenharmony_ci lan865x_revb0_fixup_cfg_regs[i]); 14062306a36Sopenharmony_ci if (ret < 0) 14162306a36Sopenharmony_ci return ret; 14262306a36Sopenharmony_ci cfg_params[i] = (u16)ret; 14362306a36Sopenharmony_ci } 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci return 0; 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic int lan865x_write_cfg_params(struct phy_device *phydev, u16 cfg_params[]) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci int ret; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs); i++) { 15362306a36Sopenharmony_ci ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, 15462306a36Sopenharmony_ci lan865x_revb0_fixup_cfg_regs[i], 15562306a36Sopenharmony_ci cfg_params[i]); 15662306a36Sopenharmony_ci if (ret) 15762306a36Sopenharmony_ci return ret; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci return 0; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic int lan865x_setup_cfgparam(struct phy_device *phydev) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci u16 cfg_params[ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs)]; 16662306a36Sopenharmony_ci u16 cfg_results[5]; 16762306a36Sopenharmony_ci s8 offsets[2]; 16862306a36Sopenharmony_ci int ret; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci ret = lan865x_generate_cfg_offsets(phydev, offsets); 17162306a36Sopenharmony_ci if (ret) 17262306a36Sopenharmony_ci return ret; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci ret = lan865x_read_cfg_params(phydev, cfg_params); 17562306a36Sopenharmony_ci if (ret) 17662306a36Sopenharmony_ci return ret; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci cfg_results[0] = (cfg_params[0] & 0x000F) | 17962306a36Sopenharmony_ci FIELD_PREP(GENMASK(15, 10), 9 + offsets[0]) | 18062306a36Sopenharmony_ci FIELD_PREP(GENMASK(15, 4), 14 + offsets[0]); 18162306a36Sopenharmony_ci cfg_results[1] = (cfg_params[1] & 0x03FF) | 18262306a36Sopenharmony_ci FIELD_PREP(GENMASK(15, 10), 40 + offsets[1]); 18362306a36Sopenharmony_ci cfg_results[2] = (cfg_params[2] & 0xC0C0) | 18462306a36Sopenharmony_ci FIELD_PREP(GENMASK(15, 8), 5 + offsets[0]) | 18562306a36Sopenharmony_ci (9 + offsets[0]); 18662306a36Sopenharmony_ci cfg_results[3] = (cfg_params[3] & 0xC0C0) | 18762306a36Sopenharmony_ci FIELD_PREP(GENMASK(15, 8), 9 + offsets[0]) | 18862306a36Sopenharmony_ci (14 + offsets[0]); 18962306a36Sopenharmony_ci cfg_results[4] = (cfg_params[4] & 0xC0C0) | 19062306a36Sopenharmony_ci FIELD_PREP(GENMASK(15, 8), 17 + offsets[0]) | 19162306a36Sopenharmony_ci (22 + offsets[0]); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci return lan865x_write_cfg_params(phydev, cfg_results); 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic int lan865x_revb0_config_init(struct phy_device *phydev) 19762306a36Sopenharmony_ci{ 19862306a36Sopenharmony_ci int ret; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci /* Reference to AN1760 20162306a36Sopenharmony_ci * https://ww1.microchip.com/downloads/aemDocuments/documents/AIS/ProductDocuments/SupportingCollateral/AN-LAN8650-1-Configuration-60001760.pdf 20262306a36Sopenharmony_ci */ 20362306a36Sopenharmony_ci for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_registers); i++) { 20462306a36Sopenharmony_ci ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, 20562306a36Sopenharmony_ci lan865x_revb0_fixup_registers[i], 20662306a36Sopenharmony_ci lan865x_revb0_fixup_values[i]); 20762306a36Sopenharmony_ci if (ret) 20862306a36Sopenharmony_ci return ret; 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci /* Function to calculate and write the configuration parameters in the 21162306a36Sopenharmony_ci * 0x0084, 0x008A, 0x00AD, 0x00AE and 0x00AF registers (from AN1760) 21262306a36Sopenharmony_ci */ 21362306a36Sopenharmony_ci return lan865x_setup_cfgparam(phydev); 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_cistatic int lan867x_revb1_config_init(struct phy_device *phydev) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci int err; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci /* The chip completes a reset in 3us, we might get here earlier than 22162306a36Sopenharmony_ci * that, as an added margin we'll conditionally sleep 5us. 22262306a36Sopenharmony_ci */ 22362306a36Sopenharmony_ci err = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN867X_REG_STS2); 22462306a36Sopenharmony_ci if (err < 0) 22562306a36Sopenharmony_ci return err; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci if (!(err & LAN867x_RESET_COMPLETE_STS)) { 22862306a36Sopenharmony_ci udelay(5); 22962306a36Sopenharmony_ci err = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN867X_REG_STS2); 23062306a36Sopenharmony_ci if (err < 0) 23162306a36Sopenharmony_ci return err; 23262306a36Sopenharmony_ci if (!(err & LAN867x_RESET_COMPLETE_STS)) { 23362306a36Sopenharmony_ci phydev_err(phydev, "PHY reset failed\n"); 23462306a36Sopenharmony_ci return -ENODEV; 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci /* Reference to AN1699 23962306a36Sopenharmony_ci * https://ww1.microchip.com/downloads/aemDocuments/documents/AIS/ProductDocuments/SupportingCollateral/AN-LAN8670-1-2-config-60001699.pdf 24062306a36Sopenharmony_ci * AN1699 says Read, Modify, Write, but the Write is not required if the 24162306a36Sopenharmony_ci * register already has the required value. So it is safe to use 24262306a36Sopenharmony_ci * phy_modify_mmd here. 24362306a36Sopenharmony_ci */ 24462306a36Sopenharmony_ci for (int i = 0; i < ARRAY_SIZE(lan867x_revb1_fixup_registers); i++) { 24562306a36Sopenharmony_ci err = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 24662306a36Sopenharmony_ci lan867x_revb1_fixup_registers[i], 24762306a36Sopenharmony_ci lan867x_revb1_fixup_masks[i], 24862306a36Sopenharmony_ci lan867x_revb1_fixup_values[i]); 24962306a36Sopenharmony_ci if (err) 25062306a36Sopenharmony_ci return err; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci return 0; 25462306a36Sopenharmony_ci} 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_cistatic int lan86xx_read_status(struct phy_device *phydev) 25762306a36Sopenharmony_ci{ 25862306a36Sopenharmony_ci /* The phy has some limitations, namely: 25962306a36Sopenharmony_ci * - always reports link up 26062306a36Sopenharmony_ci * - only supports 10MBit half duplex 26162306a36Sopenharmony_ci * - does not support auto negotiate 26262306a36Sopenharmony_ci */ 26362306a36Sopenharmony_ci phydev->link = 1; 26462306a36Sopenharmony_ci phydev->duplex = DUPLEX_HALF; 26562306a36Sopenharmony_ci phydev->speed = SPEED_10; 26662306a36Sopenharmony_ci phydev->autoneg = AUTONEG_DISABLE; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci return 0; 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cistatic struct phy_driver microchip_t1s_driver[] = { 27262306a36Sopenharmony_ci { 27362306a36Sopenharmony_ci PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1), 27462306a36Sopenharmony_ci .name = "LAN867X Rev.B1", 27562306a36Sopenharmony_ci .features = PHY_BASIC_T1S_P2MP_FEATURES, 27662306a36Sopenharmony_ci .config_init = lan867x_revb1_config_init, 27762306a36Sopenharmony_ci .read_status = lan86xx_read_status, 27862306a36Sopenharmony_ci .get_plca_cfg = genphy_c45_plca_get_cfg, 27962306a36Sopenharmony_ci .set_plca_cfg = genphy_c45_plca_set_cfg, 28062306a36Sopenharmony_ci .get_plca_status = genphy_c45_plca_get_status, 28162306a36Sopenharmony_ci }, 28262306a36Sopenharmony_ci { 28362306a36Sopenharmony_ci PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB0), 28462306a36Sopenharmony_ci .name = "LAN865X Rev.B0 Internal Phy", 28562306a36Sopenharmony_ci .features = PHY_BASIC_T1S_P2MP_FEATURES, 28662306a36Sopenharmony_ci .config_init = lan865x_revb0_config_init, 28762306a36Sopenharmony_ci .read_status = lan86xx_read_status, 28862306a36Sopenharmony_ci .get_plca_cfg = genphy_c45_plca_get_cfg, 28962306a36Sopenharmony_ci .set_plca_cfg = genphy_c45_plca_set_cfg, 29062306a36Sopenharmony_ci .get_plca_status = genphy_c45_plca_get_status, 29162306a36Sopenharmony_ci }, 29262306a36Sopenharmony_ci}; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cimodule_phy_driver(microchip_t1s_driver); 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic struct mdio_device_id __maybe_unused tbl[] = { 29762306a36Sopenharmony_ci { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1) }, 29862306a36Sopenharmony_ci { PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB0) }, 29962306a36Sopenharmony_ci { } 30062306a36Sopenharmony_ci}; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(mdio, tbl); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ciMODULE_DESCRIPTION("Microchip 10BASE-T1S PHYs driver"); 30562306a36Sopenharmony_ciMODULE_AUTHOR("Ramón Nordin Rodriguez"); 30662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 307