162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * drivers/net/phy/lxt.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Driver for Intel LXT PHYs 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Andy Fleming 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Copyright (c) 2004 Freescale Semiconductor, Inc. 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci#include <linux/kernel.h> 1262306a36Sopenharmony_ci#include <linux/string.h> 1362306a36Sopenharmony_ci#include <linux/errno.h> 1462306a36Sopenharmony_ci#include <linux/unistd.h> 1562306a36Sopenharmony_ci#include <linux/interrupt.h> 1662306a36Sopenharmony_ci#include <linux/init.h> 1762306a36Sopenharmony_ci#include <linux/delay.h> 1862306a36Sopenharmony_ci#include <linux/netdevice.h> 1962306a36Sopenharmony_ci#include <linux/etherdevice.h> 2062306a36Sopenharmony_ci#include <linux/skbuff.h> 2162306a36Sopenharmony_ci#include <linux/spinlock.h> 2262306a36Sopenharmony_ci#include <linux/mm.h> 2362306a36Sopenharmony_ci#include <linux/module.h> 2462306a36Sopenharmony_ci#include <linux/mii.h> 2562306a36Sopenharmony_ci#include <linux/ethtool.h> 2662306a36Sopenharmony_ci#include <linux/phy.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#include <asm/io.h> 2962306a36Sopenharmony_ci#include <asm/irq.h> 3062306a36Sopenharmony_ci#include <linux/uaccess.h> 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* The Level one LXT970 is used by many boards */ 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define MII_LXT970_IER 17 /* Interrupt Enable Register */ 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#define MII_LXT970_IER_IEN 0x0002 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define MII_LXT970_ISR 18 /* Interrupt Status Register */ 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#define MII_LXT970_IRS_MINT BIT(15) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define MII_LXT970_CONFIG 19 /* Configuration Register */ 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci/* ------------------------------------------------------------------------- */ 4562306a36Sopenharmony_ci/* The Level one LXT971 is used on some of my custom boards */ 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci/* register definitions for the 971 */ 4862306a36Sopenharmony_ci#define MII_LXT971_IER 18 /* Interrupt Enable Register */ 4962306a36Sopenharmony_ci#define MII_LXT971_IER_IEN 0x00f2 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#define MII_LXT971_ISR 19 /* Interrupt Status Register */ 5262306a36Sopenharmony_ci#define MII_LXT971_ISR_MASK 0x00f0 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci/* register definitions for the 973 */ 5562306a36Sopenharmony_ci#define MII_LXT973_PCR 16 /* Port Configuration Register */ 5662306a36Sopenharmony_ci#define PCR_FIBER_SELECT 1 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ciMODULE_DESCRIPTION("Intel LXT PHY driver"); 5962306a36Sopenharmony_ciMODULE_AUTHOR("Andy Fleming"); 6062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic int lxt970_ack_interrupt(struct phy_device *phydev) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci int err; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci err = phy_read(phydev, MII_BMSR); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci if (err < 0) 6962306a36Sopenharmony_ci return err; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci err = phy_read(phydev, MII_LXT970_ISR); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci if (err < 0) 7462306a36Sopenharmony_ci return err; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci return 0; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic int lxt970_config_intr(struct phy_device *phydev) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci int err; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 8462306a36Sopenharmony_ci err = lxt970_ack_interrupt(phydev); 8562306a36Sopenharmony_ci if (err) 8662306a36Sopenharmony_ci return err; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci err = phy_write(phydev, MII_LXT970_IER, MII_LXT970_IER_IEN); 8962306a36Sopenharmony_ci } else { 9062306a36Sopenharmony_ci err = phy_write(phydev, MII_LXT970_IER, 0); 9162306a36Sopenharmony_ci if (err) 9262306a36Sopenharmony_ci return err; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci err = lxt970_ack_interrupt(phydev); 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci return err; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic irqreturn_t lxt970_handle_interrupt(struct phy_device *phydev) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci int irq_status; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci /* The interrupt status register is cleared by reading BMSR 10562306a36Sopenharmony_ci * followed by MII_LXT970_ISR 10662306a36Sopenharmony_ci */ 10762306a36Sopenharmony_ci irq_status = phy_read(phydev, MII_BMSR); 10862306a36Sopenharmony_ci if (irq_status < 0) { 10962306a36Sopenharmony_ci phy_error(phydev); 11062306a36Sopenharmony_ci return IRQ_NONE; 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci irq_status = phy_read(phydev, MII_LXT970_ISR); 11462306a36Sopenharmony_ci if (irq_status < 0) { 11562306a36Sopenharmony_ci phy_error(phydev); 11662306a36Sopenharmony_ci return IRQ_NONE; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci if (!(irq_status & MII_LXT970_IRS_MINT)) 12062306a36Sopenharmony_ci return IRQ_NONE; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci phy_trigger_machine(phydev); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci return IRQ_HANDLED; 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic int lxt970_config_init(struct phy_device *phydev) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci return phy_write(phydev, MII_LXT970_CONFIG, 0); 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic int lxt971_ack_interrupt(struct phy_device *phydev) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci int err = phy_read(phydev, MII_LXT971_ISR); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if (err < 0) 13862306a36Sopenharmony_ci return err; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic int lxt971_config_intr(struct phy_device *phydev) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci int err; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 14862306a36Sopenharmony_ci err = lxt971_ack_interrupt(phydev); 14962306a36Sopenharmony_ci if (err) 15062306a36Sopenharmony_ci return err; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci err = phy_write(phydev, MII_LXT971_IER, MII_LXT971_IER_IEN); 15362306a36Sopenharmony_ci } else { 15462306a36Sopenharmony_ci err = phy_write(phydev, MII_LXT971_IER, 0); 15562306a36Sopenharmony_ci if (err) 15662306a36Sopenharmony_ci return err; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci err = lxt971_ack_interrupt(phydev); 15962306a36Sopenharmony_ci } 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci return err; 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic irqreturn_t lxt971_handle_interrupt(struct phy_device *phydev) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci int irq_status; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci irq_status = phy_read(phydev, MII_LXT971_ISR); 16962306a36Sopenharmony_ci if (irq_status < 0) { 17062306a36Sopenharmony_ci phy_error(phydev); 17162306a36Sopenharmony_ci return IRQ_NONE; 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci if (!(irq_status & MII_LXT971_ISR_MASK)) 17562306a36Sopenharmony_ci return IRQ_NONE; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci phy_trigger_machine(phydev); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci return IRQ_HANDLED; 18062306a36Sopenharmony_ci} 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci/* 18362306a36Sopenharmony_ci * A2 version of LXT973 chip has an ERRATA: it randomly return the contents 18462306a36Sopenharmony_ci * of the previous even register when you read a odd register regularly 18562306a36Sopenharmony_ci */ 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic int lxt973a2_update_link(struct phy_device *phydev) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci int status; 19062306a36Sopenharmony_ci int control; 19162306a36Sopenharmony_ci int retry = 8; /* we try 8 times */ 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci /* Do a fake read */ 19462306a36Sopenharmony_ci status = phy_read(phydev, MII_BMSR); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (status < 0) 19762306a36Sopenharmony_ci return status; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci control = phy_read(phydev, MII_BMCR); 20062306a36Sopenharmony_ci if (control < 0) 20162306a36Sopenharmony_ci return control; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci do { 20462306a36Sopenharmony_ci /* Read link and autonegotiation status */ 20562306a36Sopenharmony_ci status = phy_read(phydev, MII_BMSR); 20662306a36Sopenharmony_ci } while (status >= 0 && retry-- && status == control); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci if (status < 0) 20962306a36Sopenharmony_ci return status; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci if ((status & BMSR_LSTATUS) == 0) 21262306a36Sopenharmony_ci phydev->link = 0; 21362306a36Sopenharmony_ci else 21462306a36Sopenharmony_ci phydev->link = 1; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci return 0; 21762306a36Sopenharmony_ci} 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistatic int lxt973a2_read_status(struct phy_device *phydev) 22062306a36Sopenharmony_ci{ 22162306a36Sopenharmony_ci int adv; 22262306a36Sopenharmony_ci int err; 22362306a36Sopenharmony_ci int lpa; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* Update the link, but return if there was an error */ 22662306a36Sopenharmony_ci err = lxt973a2_update_link(phydev); 22762306a36Sopenharmony_ci if (err) 22862306a36Sopenharmony_ci return err; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci if (AUTONEG_ENABLE == phydev->autoneg) { 23162306a36Sopenharmony_ci int retry = 1; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci adv = phy_read(phydev, MII_ADVERTISE); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci if (adv < 0) 23662306a36Sopenharmony_ci return adv; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci do { 23962306a36Sopenharmony_ci lpa = phy_read(phydev, MII_LPA); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci if (lpa < 0) 24262306a36Sopenharmony_ci return lpa; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci /* If both registers are equal, it is suspect but not 24562306a36Sopenharmony_ci * impossible, hence a new try 24662306a36Sopenharmony_ci */ 24762306a36Sopenharmony_ci } while (lpa == adv && retry--); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci mii_lpa_to_linkmode_lpa_t(phydev->lp_advertising, lpa); 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci lpa &= adv; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci phydev->speed = SPEED_10; 25462306a36Sopenharmony_ci phydev->duplex = DUPLEX_HALF; 25562306a36Sopenharmony_ci phydev->pause = phydev->asym_pause = 0; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci if (lpa & (LPA_100FULL | LPA_100HALF)) { 25862306a36Sopenharmony_ci phydev->speed = SPEED_100; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci if (lpa & LPA_100FULL) 26162306a36Sopenharmony_ci phydev->duplex = DUPLEX_FULL; 26262306a36Sopenharmony_ci } else { 26362306a36Sopenharmony_ci if (lpa & LPA_10FULL) 26462306a36Sopenharmony_ci phydev->duplex = DUPLEX_FULL; 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci phy_resolve_aneg_pause(phydev); 26862306a36Sopenharmony_ci } else { 26962306a36Sopenharmony_ci err = genphy_read_status_fixed(phydev); 27062306a36Sopenharmony_ci if (err < 0) 27162306a36Sopenharmony_ci return err; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci phydev->pause = phydev->asym_pause = 0; 27462306a36Sopenharmony_ci linkmode_zero(phydev->lp_advertising); 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci return 0; 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_cistatic int lxt973_probe(struct phy_device *phydev) 28162306a36Sopenharmony_ci{ 28262306a36Sopenharmony_ci int val = phy_read(phydev, MII_LXT973_PCR); 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci if (val & PCR_FIBER_SELECT) { 28562306a36Sopenharmony_ci /* 28662306a36Sopenharmony_ci * If fiber is selected, then the only correct setting 28762306a36Sopenharmony_ci * is 100Mbps, full duplex, and auto negotiation off. 28862306a36Sopenharmony_ci */ 28962306a36Sopenharmony_ci val = phy_read(phydev, MII_BMCR); 29062306a36Sopenharmony_ci val |= (BMCR_SPEED100 | BMCR_FULLDPLX); 29162306a36Sopenharmony_ci val &= ~BMCR_ANENABLE; 29262306a36Sopenharmony_ci phy_write(phydev, MII_BMCR, val); 29362306a36Sopenharmony_ci /* Remember that the port is in fiber mode. */ 29462306a36Sopenharmony_ci phydev->priv = lxt973_probe; 29562306a36Sopenharmony_ci phydev->port = PORT_FIBRE; 29662306a36Sopenharmony_ci } else { 29762306a36Sopenharmony_ci phydev->priv = NULL; 29862306a36Sopenharmony_ci } 29962306a36Sopenharmony_ci return 0; 30062306a36Sopenharmony_ci} 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_cistatic int lxt973_config_aneg(struct phy_device *phydev) 30362306a36Sopenharmony_ci{ 30462306a36Sopenharmony_ci /* Do nothing if port is in fiber mode. */ 30562306a36Sopenharmony_ci return phydev->priv ? 0 : genphy_config_aneg(phydev); 30662306a36Sopenharmony_ci} 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_cistatic struct phy_driver lxt97x_driver[] = { 30962306a36Sopenharmony_ci{ 31062306a36Sopenharmony_ci .phy_id = 0x78100000, 31162306a36Sopenharmony_ci .name = "LXT970", 31262306a36Sopenharmony_ci .phy_id_mask = 0xfffffff0, 31362306a36Sopenharmony_ci /* PHY_BASIC_FEATURES */ 31462306a36Sopenharmony_ci .config_init = lxt970_config_init, 31562306a36Sopenharmony_ci .config_intr = lxt970_config_intr, 31662306a36Sopenharmony_ci .handle_interrupt = lxt970_handle_interrupt, 31762306a36Sopenharmony_ci}, { 31862306a36Sopenharmony_ci .phy_id = 0x001378e0, 31962306a36Sopenharmony_ci .name = "LXT971", 32062306a36Sopenharmony_ci .phy_id_mask = 0xfffffff0, 32162306a36Sopenharmony_ci /* PHY_BASIC_FEATURES */ 32262306a36Sopenharmony_ci .config_intr = lxt971_config_intr, 32362306a36Sopenharmony_ci .handle_interrupt = lxt971_handle_interrupt, 32462306a36Sopenharmony_ci .suspend = genphy_suspend, 32562306a36Sopenharmony_ci .resume = genphy_resume, 32662306a36Sopenharmony_ci}, { 32762306a36Sopenharmony_ci .phy_id = 0x00137a10, 32862306a36Sopenharmony_ci .name = "LXT973-A2", 32962306a36Sopenharmony_ci .phy_id_mask = 0xffffffff, 33062306a36Sopenharmony_ci /* PHY_BASIC_FEATURES */ 33162306a36Sopenharmony_ci .flags = 0, 33262306a36Sopenharmony_ci .probe = lxt973_probe, 33362306a36Sopenharmony_ci .config_aneg = lxt973_config_aneg, 33462306a36Sopenharmony_ci .read_status = lxt973a2_read_status, 33562306a36Sopenharmony_ci .suspend = genphy_suspend, 33662306a36Sopenharmony_ci .resume = genphy_resume, 33762306a36Sopenharmony_ci}, { 33862306a36Sopenharmony_ci .phy_id = 0x00137a10, 33962306a36Sopenharmony_ci .name = "LXT973", 34062306a36Sopenharmony_ci .phy_id_mask = 0xfffffff0, 34162306a36Sopenharmony_ci /* PHY_BASIC_FEATURES */ 34262306a36Sopenharmony_ci .flags = 0, 34362306a36Sopenharmony_ci .probe = lxt973_probe, 34462306a36Sopenharmony_ci .config_aneg = lxt973_config_aneg, 34562306a36Sopenharmony_ci .suspend = genphy_suspend, 34662306a36Sopenharmony_ci .resume = genphy_resume, 34762306a36Sopenharmony_ci} }; 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_cimodule_phy_driver(lxt97x_driver); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_cistatic struct mdio_device_id __maybe_unused lxt_tbl[] = { 35262306a36Sopenharmony_ci { 0x78100000, 0xfffffff0 }, 35362306a36Sopenharmony_ci { 0x001378e0, 0xfffffff0 }, 35462306a36Sopenharmony_ci { 0x00137a10, 0xfffffff0 }, 35562306a36Sopenharmony_ci { } 35662306a36Sopenharmony_ci}; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(mdio, lxt_tbl); 359