diff options
Diffstat (limited to 'drivers/net/phy/tja110x.c')
-rw-r--r-- | drivers/net/phy/tja110x.c | 2547 |
1 files changed, 2547 insertions, 0 deletions
diff --git a/drivers/net/phy/tja110x.c b/drivers/net/phy/tja110x.c new file mode 100644 index 000000000000..bf680ecf3d7b --- /dev/null +++ b/drivers/net/phy/tja110x.c @@ -0,0 +1,2547 @@ +/* + * Copyright 2017-2018 NXP + * + * 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. + * + */ + +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/of_mdio.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mii.h> +#include <linux/phy.h> +#include <linux/delay.h> + +#include "tja110x.h" + +/* load driver for TJA1102p1. It needs to be ensured, + * that no other mdio device with phy id 0 is present + */ +#define CONFIG_TJA1102_FIX + +/* listen for NETDEV_GOING_DOWN and NETDEV_UP of the ethernet interface, + * that controls the mdio bus to which the nxp phy(s) is/are connected to. + * Polling is stopped/started accordingly, to prevent mdio read timeouts + * This fix requires MDIO_INTERFACE_NAME and MII_BUS_NAME to be set + */ +#define NETDEV_NOTIFICATION_FIX + +/* Name of the eth interface, that controlls the mdio bus, + * to which the phy(s) is/are connected to + */ +#ifndef MDIO_INTERFACE_NAME +#define MDIO_INTERFACE_NAME "eth0" +#endif + +/* Name of the mdio bus, + * to which the phy(s) is/are connected to + */ +#ifndef MII_BUS_NAME +#define MII_BUS_NAME "fec_enet_mii_bus" +#endif + +#define TJA110X_REFCLK_IN (1 << 0) + +/* Variable can be modified via parameter passed at load time + * A nonzero value indicates that we should operate in managed mode + */ +static int managed_mode; +/* Permission: do not show up in sysfs */ +module_param(managed_mode, int, 0000); +MODULE_PARM_DESC(managed_mode, "Use PHY in managed or autonomous mode"); + +/* A nonzero value indicates that we should not poll the interrupt register */ +static int no_poll; +/* Permission: do not show up in sysfs */ +module_param(no_poll, int, 0000); +MODULE_PARM_DESC(no_poll, "Do not poll the interrupt register"); + +/* Detemines the level of verbosity for debug messages */ +static int verbosity; +/* Permission: do not show up in sysfs */ +module_param(verbosity, int, 0000); +MODULE_PARM_DESC(verbosity, "Set verbosity level"); + +/* Called to initialize the PHY, + * including after a reset + */ +static int nxp_config_init(struct phy_device *phydev) +{ + struct nxp_specific_data *nxp_specific = phydev->priv; + int reg_val; + int reg_name, reg_value = -1, reg_mask; + int err; + + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, "initializing phy %x\n", phydev->mdio.addr); + + /* set features of the PHY */ + reg_val = phy_read(phydev, MII_BMSR); + if (reg_val < 0) + goto phy_read_error; + if (reg_val & BMSR_ESTATEN) { + reg_val = phy_read(phydev, MII_ESTATUS); + + if (reg_val < 0) + goto phy_read_error; + + if (reg_val & ESTATUS_100T1_FULL) { + /* update phydev to include the supported features */ + phydev->supported |= SUPPORTED_100BASET1_FULL; + phydev->advertising |= ADVERTISED_100BASET1_FULL; + } + } + + /* enable configuration register access once during initialization */ + err = phy_configure_bit(phydev, MII_ECTRL, ECTRL_CONFIG_EN, 1); + if (err < 0) + goto phy_configure_error; + + /* -enter managed or autonomous mode, + * depending on the value of managed_mode. + * The register layout changed between TJA1100 and TJA1102 + * -configure LED mode (only tja1100 has LEDs) + */ + switch (phydev->phy_id & NXP_PHY_ID_MASK) { + case NXP_PHY_ID_TJA1100: + reg_name = MII_CFG1; + reg_value = TJA1100_CFG1_LED_EN | CFG1_LED_LINKUP; + if (!managed_mode) + reg_value |= TJA1100_CFG1_AUTO_OP; + reg_mask = TJA1100_CFG1_AUTO_OP | + TJA1100_CFG1_LED_EN | TJA1100_CFG1_LED_MODE; + + if (nxp_specific->quirks & TJA110X_REFCLK_IN) { + reg_value |= TJA1100_CFG1_MII_MODE_REFCLK_IN; + reg_mask |= CFG1_MII_MODE; + } + break; + case NXP_PHY_ID_TJA1101: + /* fall through */ + case NXP_PHY_ID_TJA1102P0: + reg_name = MII_COMMCFG; + reg_value = 0; + if (!managed_mode) + reg_value |= COMMCFG_AUTO_OP; + reg_mask = COMMCFG_AUTO_OP; + break; + + case NXP_PHY_ID_TJA1102P1: + /* does not have an auto_op bit */ + break; + + default: + goto unsupported_phy_error; + } + + /* only configure the phys that have an auto_op bit or leds */ + if (reg_value != -1) { + err = phy_configure_bits(phydev, reg_name, reg_mask, reg_value); + if (err < 0) + goto phy_configure_error; + } + + /* enable sleep confirm */ + err = phy_configure_bit(phydev, MII_CFG1, CFG1_SLEEP_CONFIRM, 1); + if (err < 0) + goto phy_configure_error; + + /* set sleep request timeout to 16ms */ + err = phy_configure_bits(phydev, MII_CFG2, CFG2_SLEEP_REQUEST_TO, + SLEEP_REQUEST_TO_16MS); + if (err < 0) + goto phy_configure_error; + + /* if in managed mode: + * -go to normal mode, if currently in standby + * (PHY might be pinstrapped to managed mode, + * and therefore not in normal mode yet) + * -enable link control + */ + if (managed_mode) { + reg_val = phy_read(phydev, MII_ECTRL); + if (reg_val < 0) + goto phy_read_error; + + /* mask power mode bits */ + reg_val &= ECTRL_POWER_MODE; + + if (reg_val == POWER_MODE_STANDBY) { + err = phydev->drv->resume(phydev); + if (err < 0) + goto phy_pmode_transit_error; + } + + set_link_control(phydev, 1); + } + + /* clear any pending interrupts */ + phydev->drv->ack_interrupt(phydev); + + phydev->irq = PHY_POLL; + + /* enable all interrupts */ + phydev->interrupts = PHY_INTERRUPT_ENABLED; + phydev->drv->config_intr(phydev); + + /* Setup and queue a polling function */ + if (!no_poll) { + setup_polling(phydev); + start_polling(phydev); + } + + return 0; + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: config_init failed\n"); + return reg_val; + +phy_pmode_transit_error: + dev_err(&phydev->mdio.dev, "pmode error: config_init failed\n"); + return err; + +phy_configure_error: + dev_err(&phydev->mdio.dev, "read/write error: config_init failed\n"); + return err; + +unsupported_phy_error: + dev_err(&phydev->mdio.dev, "unsupported phy, config_init failed\n"); + return -1; +} + +/* Called during discovery. + * Used to set up device-specific structures + */ +static int nxp_probe(struct phy_device *phydev) +{ + int err; + struct device *dev = &phydev->mdio.dev; + struct nxp_specific_data *nxp_specific; + + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, "probing PHY %x\n", phydev->mdio.addr); + + nxp_specific = kzalloc(sizeof(*nxp_specific), GFP_KERNEL); + if (!nxp_specific) + goto phy_allocation_error; + + if (of_property_read_bool(dev->of_node, "tja110x,refclk_in")) + nxp_specific->quirks |= TJA110X_REFCLK_IN; + + nxp_specific->is_master = get_master_cfg(phydev); + nxp_specific->is_polling = 0; + nxp_specific->is_poll_setup = 0; + + phydev->priv = nxp_specific; + + /* register sysfs files */ + err = sysfs_create_group(&phydev->mdio.dev.kobj, &nxp_attribute_group); + if (err) + goto register_sysfs_error; + + return 0; + +/* error handling */ +register_sysfs_error: + dev_err(&phydev->mdio.dev, "sysfs file creation failed\n"); + return -ENOMEM; + +phy_allocation_error: + dev_err(&phydev->mdio.dev, "memory allocation for priv data failed\n"); + return -ENOMEM; +} + +/* Clears up any memory, removes sysfs nodes and cancels polling */ +static void nxp_remove(struct phy_device *phydev) +{ + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, "removing PHY %x\n", phydev->mdio.addr); + + /* unregister sysfs files */ + sysfs_remove_group(&phydev->mdio.dev.kobj, &nxp_attribute_group); + + if (!no_poll) + stop_polling(phydev); + + /* free custom data struct */ + if (phydev->priv) { + kzfree(phydev->priv); + phydev->priv = NULL; + } +} + +/* Clears any pending interrupts */ +static int nxp_ack_interrupt(struct phy_device *phydev) +{ + int err; + + if (verbosity > 3) + dev_alert(&phydev->mdio.dev, "acknowledging interrupt of PHY %x\n", + phydev->mdio.addr); + + /* interrupts are acknowledged by reading, ie. clearing MII_INTSRC */ + err = phy_read(phydev, MII_INTSRC); + if (err < 0) + goto phy_read_error; + return 0; + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: ack_interrupt failed\n"); + return err; +} + +/* Checks if the PHY generated an interrupt */ +static int nxp_did_interrupt(struct phy_device *phydev) +{ + int reg_val; + + reg_val = phy_read(phydev, MII_INTSRC); + if (reg_val < 0) + goto phy_read_error; + + /* return bitmask of possible interrupts bits that are set */ + return (reg_val & INTERRUPT_ALL); + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: did_interrupt failed\n"); + return 0; +} + +/* Enables or disables interrupts */ +static int nxp_config_intr(struct phy_device *phydev) +{ + int err; + int interrupts; + + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, + "configuring interrupts of phy %x to [%x]\n", + phydev->mdio.addr, phydev->interrupts); + + interrupts = phydev->interrupts; + + if (interrupts == PHY_INTERRUPT_ENABLED) { + /* enable all interrupts + * PHY_INTERRUPT_ENABLED macro does not interfere with any + * of the possible interrupt source macros + */ + err = phy_write(phydev, MII_INTMASK, INTERRUPT_ALL); + } else if (interrupts == PHY_INTERRUPT_DISABLED) { + /* disable all interrupts */ + err = phy_write(phydev, MII_INTMASK, INTERRUPT_NONE); + } else { + /* interpret value of interrupts as interrupt mask */ + err = phy_write(phydev, MII_INTMASK, interrupts); + } + + if (err < 0) + goto phy_write_error; + return 0; + +phy_write_error: + dev_err(&phydev->mdio.dev, "write error: config_intr failed\n"); + return err; +} + +/* interrupt handler for pwon interrupts */ +static inline void handle_pwon_interrupt(struct phy_device *phydev) +{ + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, + "reinitializing phy [%08x] @ [%04x] after powerdown\n", + phydev->phy_id, phydev->mdio.addr); + /* after a power down reinitialize the phy */ + phydev->drv->config_init(phydev); + + /* update master/slave setting */ + ((struct nxp_specific_data *)phydev->priv)->is_master = + get_master_cfg(phydev); + + /* For TJA1102, pwon interrupts only exist on TJA1102p0 + * Find TJA1102p1 to reinitialize it too + */ + if ((phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1102P0) { + int p1_addr = phydev->mdio.addr + 1; + struct phy_device *phydevp1; + + if (p1_addr >= PHY_MAX_ADDR) + return; + + phydevp1 = mdiobus_get_phy(phydev->mdio.bus, p1_addr); + if (!phydevp1) + return; + + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, + "reinit phy [%08x] @ [%04x] after pDown\n", + phydevp1->phy_id, phydevp1->mdio.addr); + phydevp1->drv->config_init(phydevp1); + ((struct nxp_specific_data *)phydevp1->priv)->is_master = + get_master_cfg(phydevp1); + } +} + +/* interrupt handler for undervoltage recovery interrupts */ +static inline void handle_uvr_interrupt(struct phy_device *phydev) +{ + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, + "resuming phy [%08x] @ [%04x] after uvr\n", + phydev->phy_id, phydev->mdio.addr); + phydev->drv->resume(phydev); + + /* For TJA1102, UVR interrupts only exist on TJA1102p0 + * Find TJA1102p1 to resume it too + */ + if ((phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1102P0) { + int p1_addr = phydev->mdio.addr + 1; + struct phy_device *phydevp1; + + if (p1_addr >= PHY_MAX_ADDR) + return; + + phydevp1 = mdiobus_get_phy(phydev->mdio.bus, p1_addr); + if (!phydevp1) + return; + + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, + "resuming phy [%08x] @ [%04x] after uvr\n", + phydevp1->phy_id, phydevp1->mdio.addr); + phydevp1->drv->resume(phydevp1); + } +} + +/* polling function, that is executed regularly to handle phy interrupts */ +static void poll(struct work_struct *work) +{ + int interrupts, interrupt_mask; + struct phy_device *phydev = + container_of(work, struct phy_device, phy_queue); + + /* query phy for interrupts */ + interrupts = nxp_did_interrupt(phydev); + + /* mask out all disabled interrupts */ + interrupt_mask = phy_read(phydev, MII_INTMASK); + if (verbosity > 4) + dev_alert(&phydev->mdio.dev, + "interrupt on phy [%08x]@[%04x], mask [%08x], ISR [%08x]\n", + phydev->phy_id, phydev->mdio.addr, interrupt_mask, interrupts); + + interrupts &= interrupt_mask; + + /* handle interrupts + * - reinitialize after power down + * - resume PHY after an external WAKEUP was received + * - resume PHY after an undervoltage recovery + * - adjust state on link changes + * - check for some PHY errors + */ + + /* SMI not disabled and read was successful */ + if ((interrupts != 0xffff) && (interrupt_mask >= 0)) { + if (interrupts & INTERRUPT_PWON) + handle_pwon_interrupt(phydev); + else if (interrupts & INTERRUPT_UV_RECOVERY) + handle_uvr_interrupt(phydev); + else if (interrupts & INTERRUPT_WAKEUP) + phydev->drv->resume(phydev); + + /* warnings */ + if (interrupts & INTERRUPT_PHY_INIT_FAIL) + dev_err(&phydev->mdio.dev, "PHY initialization failed\n"); + if (interrupts & INTERRUPT_LINK_STATUS_FAIL) + dev_err(&phydev->mdio.dev, "PHY link status failed\n"); + if (interrupts & INTERRUPT_SYM_ERR) + dev_err(&phydev->mdio.dev, "PHY symbol error detected\n"); + if (interrupts & INTERRUPT_SNR_WARNING) + dev_err(&phydev->mdio.dev, "PHY SNR warning\n"); + if (interrupts & INTERRUPT_CONTROL_ERROR) + dev_err(&phydev->mdio.dev, "PHY control error\n"); + if (interrupts & INTERRUPT_UV_ERR) + dev_err(&phydev->mdio.dev, "PHY undervoltage error\n"); + if (interrupts & INTERRUPT_TEMP_ERROR) + dev_err(&phydev->mdio.dev, "PHY temperature error\n"); + + /* Notify state machine about any link changes */ + if (interrupts & INTERRUPT_LINK_STATUS_UP || + interrupts & INTERRUPT_LINK_STATUS_FAIL) { + mutex_lock(&phydev->lock); + + /* only indicate a link change to state machine + * if phydev is attached to a netdevice + */ + if (phydev->attached_dev) + phydev->state = PHY_CHANGELINK; + + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, + "state was %d, now going %s\n", phydev->state, + (interrupts & INTERRUPT_LINK_STATUS_UP) ? + "UP":"DOWN"); + phydev->link = + (interrupts & INTERRUPT_LINK_STATUS_UP) ? 1 : 0; + mutex_unlock(&phydev->lock); + } + } + + /* requeue poll function */ + msleep(POLL_PAUSE); /* msleep is non-blocking */ + queue_work(system_power_efficient_wq, &phydev->phy_queue); +} + +static void setup_polling(struct phy_device *phydev) +{ + /* + * The phy_queue is normally used to schedule the interrupt handler + * from interrupt context after an irq has been received. + * Here it is repurposed as scheduling mechanism for the poll function + */ + struct nxp_specific_data *priv = phydev->priv; + + if (!priv->is_poll_setup) { + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, + "initialize polling for PHY %x\n", phydev->mdio.addr); + cancel_work_sync(&phydev->phy_queue); + INIT_WORK(&phydev->phy_queue, poll); + priv->is_poll_setup = 1; + } +} + +static void start_polling(struct phy_device *phydev) +{ + struct nxp_specific_data *priv = phydev->priv; + + if (priv->is_poll_setup && !priv->is_polling) { + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, "start polling PHY %x\n", + phydev->mdio.addr); + /* schedule execution of polling function */ + queue_work(system_power_efficient_wq, &phydev->phy_queue); + priv->is_polling = 1; + } +} + +static void stop_polling(struct phy_device *phydev) +{ + struct nxp_specific_data *priv = phydev->priv; + + if (priv->is_poll_setup && priv->is_polling) { + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, "stop polling PHY %x\n", + phydev->mdio.addr); + /* cancel scheduled work */ + cancel_work_sync(&phydev->phy_queue); + priv->is_polling = 0; + } +} + +/* helper function, waits until a given condition is met + * + * The function delays until the part of the register at reg_addr, + * defined by reg_mask equals cond, or a timeout (timeout*DELAY_LENGTH) occurs. + * @return 0 if condition is met, <0 if timeout or read error occurred + */ +static int wait_on_condition(struct phy_device *phydev, int reg_addr, + int reg_mask, int cond, int timeout) +{ + int reg_val; + + if (verbosity > 3) + dev_alert(&phydev->mdio.dev, "waiting on condidition\n"); + + do { + udelay(DELAY_LENGTH); + reg_val = phy_read(phydev, reg_addr); + if (reg_val < 0) + return reg_val; + } while ((reg_val & reg_mask) != cond && --timeout); + + if (verbosity > 3) + dev_alert(&phydev->mdio.dev, "%s", + (timeout?"condidition met\n" : "timeout occured\n")); + + if (timeout) + return 0; + return -1; +} + +/* helper function, enables or disables link control */ +static void set_link_control(struct phy_device *phydev, int enable_link_control) +{ + int err; + + err = phy_configure_bit(phydev, MII_ECTRL, ECTRL_LINK_CONTROL, + enable_link_control); + if (err < 0) + goto phy_configure_error; + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, + "set link ctrl to [%d] for phy %x completed\n", + enable_link_control, phydev->mdio.addr); + + return; + +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: setting link control failed\n"); + return; +} + +/* Helper function, configures phy as master or slave + * @param phydev the phy to be configured + * @param setMaster ==0: set to slave + * !=0: set to master + * @return 0 on success, error code on failure + */ +static int set_master_cfg(struct phy_device *phydev, int setMaster) +{ + int err; + + /* disable link control prior to master/slave cfg */ + set_link_control(phydev, 0); + + /* write configuration to the phy */ + err = phy_configure_bit(phydev, MII_CFG1, CFG1_MASTER_SLAVE, setMaster); + if (err < 0) + goto phy_configure_error; + + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, "set master cfg completed\n"); + + /* enable link control after master/slave cfg was set */ + set_link_control(phydev, 1); + + return 0; + +/* error handling */ +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: set_master_cfg failed\n"); + return err; +} + +/* Helper function, reads master/slave configuration of phy + * @param phydev the phy to be read + * + * @return ==0: is slave + * !=0: is master + */ +static int get_master_cfg(struct phy_device *phydev) +{ + int reg_val; + + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, "getting master cfg PHY %x\n", + phydev->mdio.addr); + + /* read the current configuration */ + reg_val = phy_read(phydev, MII_CFG1); + if (reg_val < 0) + goto phy_read_error; + + return reg_val & CFG1_MASTER_SLAVE; + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: get_master_cfg failed\n"); + return reg_val; +} + +/* retrieves the link status from COMMSTAT register */ +static int get_link_status(struct phy_device *phydev) +{ + int reg_val; + + reg_val = phy_read(phydev, MII_COMMSTAT); + if (reg_val < 0) + goto phy_read_error; + + if (verbosity > 0) { + if (reg_val & COMMSTAT_LOC_RCVR_STATUS) + dev_alert(&phydev->mdio.dev, "local receiver OK\n"); + else + dev_alert(&phydev->mdio.dev, "local receiver NOT OK\n"); + } + + return reg_val & COMMSTAT_LINK_UP; + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: get_link_status failed\n"); + return reg_val; +} + +/* issues a sleep request, if in managed mode */ +static int nxp_sleep(struct phy_device *phydev) +{ + int err; + + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, "PHY %x going to sleep\n", + phydev->mdio.addr); + + if (!managed_mode) + goto phy_auto_op_error; + + /* clear power mode bits and set them to sleep request */ + err = phy_configure_bits(phydev, MII_ECTRL, ECTRL_POWER_MODE, + POWER_MODE_SLEEPREQUEST); + if (err < 0) + goto phy_configure_error; + + if ((phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1102P0 || + (phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1102P1 || + (phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1101) { + /* tja1102 and tja1102 have an extra sleep state indicator + * in ECTRL. + * If transition is successful this can be detected immediately, + * without waiting for SLEEP_REQUEST_TO to pass + */ + err = wait_on_condition(phydev, MII_ECTRL, ECTRL_POWER_MODE, + POWER_MODE_SLEEP, SLEEP_REQUEST_TO); + if (err < 0) + goto phy_transition_error; + } else if ((phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1100) { + /* TJA1100 disables SMI when entering SLEEP + * The SMI bus is pulled up, that means every + * SMI read will return 0xffff. + * We can use this to check if PHY entered SLEEP. + */ + err = wait_on_condition(phydev, MII_ECTRL, + 0xffff, 0xffff, SLEEP_REQUEST_TO); + if (err < 0) + goto phy_transition_error; + } + + return 0; + +/* error handling */ +phy_auto_op_error: + dev_info(&phydev->mdio.dev, "phy is in auto mode: sleep not possible\n"); + return 0; + +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: entering sleep failed\n"); + return err; + +phy_transition_error: + dev_err(&phydev->mdio.dev, "sleep request timed out\n"); + return err; +} + +/* wakes up the phy from sleep mode */ +static int wakeup_from_sleep(struct phy_device *phydev) +{ + int err; + unsigned long wakeup_delay; + struct nxp_specific_data *nxp_specific = phydev->priv; + + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, "PHY %x waking up from sleep\n", + phydev->mdio.addr); + + if (!managed_mode) + goto phy_auto_op_error; + + /* set power mode bits to standby mode */ + err = phy_configure_bits(phydev, MII_ECTRL, ECTRL_POWER_MODE, + POWER_MODE_STANDBY); + if (err < 0) + goto phy_configure_error; + + /* wait until power mode transition is completed */ + err = wait_on_condition(phydev, MII_ECTRL, ECTRL_POWER_MODE, + POWER_MODE_STANDBY, POWER_MODE_TIMEOUT); + if (err < 0) + goto phy_transition_error; + + /* set power mode bits to normal mode */ + err = phy_configure_bits(phydev, MII_ECTRL, ECTRL_POWER_MODE, + POWER_MODE_NORMAL); + if (err < 0) + goto phy_configure_error; + + if (!(nxp_specific->quirks & TJA110X_REFCLK_IN)) { + /* wait until the PLL is locked, indicating a completed transition */ + err = wait_on_condition(phydev, MII_GENSTAT, GENSTAT_PLL_LOCKED, + GENSTAT_PLL_LOCKED, POWER_MODE_TIMEOUT); + if (err < 0) + goto phy_transition_error; + } + + /* if phy is configured as slave, also send a wakeup request + * to master + */ + if (!((struct nxp_specific_data *)phydev->priv)->is_master) { + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, + "Phy is slave, send wakeup request master\n"); + /* link control must be reset for wake request */ + set_link_control(phydev, 0); + + /* start sending bus wakeup signal */ + err = phy_configure_bit(phydev, MII_ECTRL, + ECTRL_WAKE_REQUEST, 1); + if (err < 0) + goto phy_configure_error; + + switch (phydev->phy_id & NXP_PHY_ID_MASK) { + case NXP_PHY_ID_TJA1100: + wakeup_delay = TJA100_WAKE_REQUEST_TIMEOUT_US; + break; + case NXP_PHY_ID_TJA1102P0: + /* fall through */ + case NXP_PHY_ID_TJA1101: + /* fall through */ + case NXP_PHY_ID_TJA1102P1: + wakeup_delay = TJA102_WAKE_REQUEST_TIMEOUT_US; + break; + default: + goto unsupported_phy_error; + } + + /* wait until link partner is guranteed to be awake */ + usleep_range(wakeup_delay, wakeup_delay + 1U); + + /* stop sending bus wakeup signal */ + err = phy_configure_bit(phydev, MII_ECTRL, + ECTRL_WAKE_REQUEST, 0); + if (err < 0) + goto phy_configure_error; + } + + /* reenable link control */ + set_link_control(phydev, 1); + + return 0; + +/* error handling */ +phy_auto_op_error: + dev_dbg(&phydev->mdio.dev, "phy is in auto mode: wakeup not possible\n"); + return 0; + +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: wakeup failed\n"); + return err; + +phy_transition_error: + dev_err(&phydev->mdio.dev, "power mode transition failed\n"); + return err; + +unsupported_phy_error: + dev_err(&phydev->mdio.dev, "unsupported phy, wakeup failed\n"); + return -1; +} + +/* send a wakeup request to the link partner */ +static int wakeup_from_normal(struct phy_device *phydev) +{ + int err; + + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, + "PHY %x waking up from normal (send wur)\n", phydev->mdio.addr); + + /* start sending bus wakeup signal */ + err = phy_configure_bit(phydev, MII_ECTRL, ECTRL_WAKE_REQUEST, 1); + if (err < 0) + goto phy_configure_error; + + /* stop sending bus wakeup signal */ + err = phy_configure_bit(phydev, MII_ECTRL, ECTRL_WAKE_REQUEST, 0); + if (err < 0) + goto phy_configure_error; + + return 0; + +/* error handling */ +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: wakeup_from_normal failed\n"); + return err; +} + +/* wake up phy if is in sleep mode, send wakeup request if in normal mode */ +static int nxp_wakeup(struct phy_device *phydev) +{ + int reg_val; + int err = 0; + + reg_val = phy_read(phydev, MII_ECTRL); + if (reg_val < 0) + goto phy_read_error; + + reg_val &= ECTRL_POWER_MODE; + switch (reg_val) { + case POWER_MODE_NORMAL: + err = wakeup_from_normal(phydev); + break; + case POWER_MODE_SLEEP: + err = wakeup_from_sleep(phydev); + break; + case 0xffff & ECTRL_POWER_MODE: + /* TJA1100 disables SMI during sleep */ + goto phy_SMI_disabled; + default: + break; + } + if (err < 0) + goto phy_configure_error; + + return 0; + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: nxp_wakeup failed\n"); + return reg_val; + +phy_SMI_disabled: + dev_err(&phydev->mdio.dev, "SMI interface disabled, cannot be woken up\n"); + return 0; + +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: wakeup_from_normal failed\n"); + return err; +} + +/* power mode transition to standby */ +static int nxp_suspend(struct phy_device *phydev) +{ + int err = 0; + + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, "suspending PHY %x\n", phydev->mdio.addr); + + if (!managed_mode) + goto phy_auto_op_error; + + mutex_lock(&phydev->lock); + /* set BMCR_PDOWN bit in MII_BMCR register */ + err = phy_configure_bit(phydev, MII_BMCR, BMCR_PDOWN, 1); + if (err < 0) + dev_err(&phydev->mdio.dev, "phy r/w error: resume failed\n"); + mutex_unlock(&phydev->lock); + + return err; + +phy_auto_op_error: + dev_dbg(&phydev->mdio.dev, "phy is in auto mode: suspend not possible\n"); + return 0; +} + +/* power mode transition from standby to normal */ +static int nxp_resume(struct phy_device *phydev) +{ + int err; + struct nxp_specific_data *nxp_specific = phydev->priv; + + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, "resuming PHY %x\n", phydev->mdio.addr); + + mutex_lock(&phydev->lock); + /* clear BMCR_PDOWN bit in MII_BMCR register */ + err = phy_configure_bit(phydev, MII_BMCR, BMCR_PDOWN, 0); + if (err < 0) + goto phy_configure_error; + + /* transit to normal mode */ + err = phy_configure_bits(phydev, MII_ECTRL, ECTRL_POWER_MODE, + POWER_MODE_NORMAL); + if (err < 0) + goto phy_configure_error; + + /* wait until power mode transition is completed */ + err = wait_on_condition(phydev, MII_ECTRL, ECTRL_POWER_MODE, + POWER_MODE_NORMAL, POWER_MODE_TIMEOUT); + if (err < 0) + goto phy_transition_error; + + if (!(nxp_specific->quirks & TJA110X_REFCLK_IN)) { + /* wait until the PLL is locked, indicating a completed transition */ + err = wait_on_condition(phydev, MII_GENSTAT, GENSTAT_PLL_LOCKED, + GENSTAT_PLL_LOCKED, POWER_MODE_TIMEOUT); + if (err < 0) + goto phy_pll_error; + } + + /* reenable link control */ + set_link_control(phydev, 1); + mutex_unlock(&phydev->lock); + + return 0; + +/* error handling */ +phy_configure_error: + mutex_unlock(&phydev->lock); + dev_err(&phydev->mdio.dev, "phy r/w error: resume failed\n"); + return err; + +phy_transition_error: + mutex_unlock(&phydev->lock); + dev_err(&phydev->mdio.dev, "power mode transition failed\n"); + return err; + +phy_pll_error: + mutex_unlock(&phydev->lock); + dev_err(&phydev->mdio.dev, "Error: PLL is unstable and not locked\n"); + return err; +} + +/* Configures the autonegotiation capabilities */ +static int nxp_config_aneg(struct phy_device *phydev) +{ + if (verbosity > 0) + dev_alert(&phydev->mdio.dev, "configuring autoneg\n"); + + /* disable autoneg and manually configure speed, duplex, pause frames */ + phydev->autoneg = 0; + + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + + phydev->pause = 0; + phydev->asym_pause = 0; + + return 0; +} + +/* helper function, enters the test mode specified by tmode + * @return 0 if test mode was entered, <0 on read or write error + */ +static int enter_test_mode(struct phy_device *phydev, enum test_mode tmode) +{ + int reg_val = -1; + int err; + + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, "phy %x entering test mode: %d\n", + phydev->mdio.addr, tmode); + switch (tmode) { + case NO_TMODE: + reg_val = ECTRL_NO_TMODE; + break; + case TMODE1: + reg_val = ECTRL_TMODE1; + break; + case TMODE2: + reg_val = ECTRL_TMODE2; + break; + case TMODE3: + reg_val = ECTRL_TMODE3; + break; + case TMODE4: + reg_val = ECTRL_TMODE4; + break; + case TMODE5: + reg_val = ECTRL_TMODE5; + break; + case TMODE6: + reg_val = ECTRL_TMODE6; + break; + default: + break; + } + + if (reg_val >= 0) { + /* set test mode bits accordingly */ + err = phy_configure_bits(phydev, MII_ECTRL, ECTRL_TEST_MODE, + reg_val); + if (err < 0) + goto phy_configure_error; + } + + return 0; + +/* error handling */ +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: setting test mode failed\n"); + return err; +} + +/* helper function, enables or disables loopback mode + * @return 0 if loopback mode was configured, <0 on read or write error + */ +static int set_loopback(struct phy_device *phydev, int enable_loopback) +{ + int err; + + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, "phy %x setting loopback: %d\n", + phydev->mdio.addr, enable_loopback); + err = phy_configure_bit(phydev, MII_BMCR, BMCR_LOOPBACK, + enable_loopback); + if (err < 0) + goto phy_configure_error; + + return 0; + +/* error handling */ +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: configuring loopback failed\n"); + return err; +} + +/* helper function, enters the loopback mode specified by lmode + * @return 0 if loopback mode was entered, <0 on read or write error + */ +static int enter_loopback_mode(struct phy_device *phydev, + enum loopback_mode lmode) +{ + int reg_val = -1; + int err; + + /* disable link control prior to loopback cfg */ + set_link_control(phydev, 0); + + switch (lmode) { + case NO_LMODE: + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, + "phy %x disabling loopback mode\n", phydev->mdio.addr); + /* disable loopback */ + err = set_loopback(phydev, 0); + if (err < 0) + goto phy_set_loopback_error; + break; + case INTERNAL_LMODE: + reg_val = ECTRL_INTERNAL_LMODE; + break; + case EXTERNAL_LMODE: + reg_val = ECTRL_EXTERNAL_LMODE; + break; + case REMOTE_LMODE: + reg_val = ECTRL_REMOTE_LMODE; + break; + default: + break; + } + + if (reg_val >= 0) { + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, "setting loopback mode %d\n", + lmode); + err = phy_configure_bits(phydev, MII_ECTRL, + ECTRL_LOOPBACK_MODE, reg_val); + if (err < 0) + goto phy_configure_error; + + /* enable loopback */ + err = set_loopback(phydev, 1); + if (err < 0) + goto phy_set_loopback_error; + } + + /* enable link control after loopback cfg was set */ + set_link_control(phydev, 1); + + return 0; + +/* error handling */ +phy_set_loopback_error: + dev_err(&phydev->mdio.dev, "error: enable/disable loopback failed\n"); + return err; + +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: setting loopback mode failed\n"); + return err; +} + +/* helper function, enters the led mode specified by lmode + * @return 0 if led mode was entered, <0 on read or write error + */ +static int enter_led_mode(struct phy_device *phydev, enum led_mode lmode) +{ + int reg_val = -1; + int err; + + switch (lmode) { + case NO_LED_MODE: + /* disable led */ + err = phy_configure_bit(phydev, MII_CFG1, + TJA1100_CFG1_LED_EN, 0); + if (err < 0) + goto phy_configure_error; + break; + case LINKUP_LED_MODE: + reg_val = CFG1_LED_LINKUP; + break; + case FRAMEREC_LED_MODE: + reg_val = CFG1_LED_FRAMEREC; + break; + case SYMERR_LED_MODE: + reg_val = CFG1_LED_SYMERR; + break; + case CRSSIG_LED_MODE: + reg_val = CFG1_LED_CRSSIG; + break; + default: + break; + } + + if (reg_val >= 0) { + err = phy_configure_bits(phydev, MII_CFG1, + TJA1100_CFG1_LED_MODE, reg_val); + if (err < 0) + goto phy_configure_error; + + /* enable led */ + err = phy_configure_bit(phydev, MII_CFG1, + TJA1100_CFG1_LED_EN, 1); + if (err < 0) + goto phy_configure_error; + } + + return 0; + +/* error handling */ +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: setting led mode failed\n"); + return err; +} + +/* This function handles read accesses to the node 'master_cfg' in + * sysfs. + * Depending on current configuration of the phy, the node reads + * 'master' or 'slave' + */ +static ssize_t sysfs_get_master_cfg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int is_master; + struct phy_device *phydev = to_phy_device(dev); + + is_master = get_master_cfg(phydev); + + /* write result into the buffer */ + return scnprintf(buf, PAGE_SIZE, "%s\n", + is_master ? "master" : "slave"); +} + +/* This function handles write accesses to the node 'master_cfg' in sysfs. + * Depending on the value written to it, the phy is configured as + * master or slave + */ +static ssize_t sysfs_set_master_cfg(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + int setMaster; + struct phy_device *phydev = to_phy_device(dev); + + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, "setting master cfg PHY %x\n", + phydev->mdio.addr); + + /* parse the buffer */ + err = kstrtoint(buf, 10, &setMaster); + if (err < 0) + goto phy_parse_error; + + /* write configuration to the phy */ + err = set_master_cfg(phydev, setMaster); + if (err < 0) + goto phy_cfg_error; + + /* update phydev */ + ((struct nxp_specific_data *)phydev->priv)->is_master = setMaster; + + return count; + +/* error handling */ +phy_parse_error: + dev_err(&phydev->mdio.dev, "parse error: sysfs_set_master_cfg failed\n"); + return err; + +phy_cfg_error: + dev_err(&phydev->mdio.dev, "phy cfg error: sysfs_set_master_cfg failed\n"); + return err; +} + +/* This function handles read accesses to the node 'power_cfg' in sysfs. + * Reading the node returns the current power state + */ +static ssize_t sysfs_get_power_cfg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int reg_val; + char *pmode; + struct phy_device *phydev = to_phy_device(dev); + + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, "getting power cfg\n"); + + reg_val = phy_read(phydev, MII_ECTRL); + if (reg_val < 0) + goto phy_read_error; + + /* mask power mode bits */ + reg_val &= ECTRL_POWER_MODE; + + switch (reg_val) { + case POWER_MODE_NORMAL: + pmode = "POWER_MODE_NORMAL\n"; + break; + case POWER_MODE_SLEEPREQUEST: + pmode = "POWER_MODE_SLEEPREQUEST\n"; + break; + case POWER_MODE_SLEEP: + pmode = "POWER_MODE_SLEEP\n"; + break; + case POWER_MODE_SILENT: + pmode = "POWER_MODE_SILENT\n"; + break; + case POWER_MODE_STANDBY: + pmode = "POWER_MODE_STANDBY\n"; + break; + case POWER_MODE_NOCHANGE: + pmode = "POWER_MODE_NOCHANGE\n"; + break; + default: + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, + "unknown reg val is [%08x]\n", reg_val); + pmode = "unknown\n"; + } + + /* write result into the buffer */ + return scnprintf(buf, PAGE_SIZE, pmode); + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: sysfs_get_power_cfg failed\n"); + return reg_val; +} + +/* This function handles write accesses to the node 'power_cfg' in + * sysfs. + * Depending on the value written to it, the phy enters a certain + * power state. + */ +static ssize_t sysfs_set_power_cfg(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + int pmode; + struct phy_device *phydev = to_phy_device(dev); + + /* parse the buffer */ + err = kstrtoint(buf, 10, &pmode); + if (err < 0) + goto phy_parse_error; + + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, "set pmode to %d\n", pmode); + + switch (pmode) { + case 0: + err = phydev->drv->suspend(phydev); + break; + case 1: + err = phydev->drv->resume(phydev); + break; + case 2: + err = nxp_sleep(phydev); + break; + case 3: + err = nxp_wakeup(phydev); + break; + default: + break; + } + + if (err) + goto phy_pmode_transit_error; + + return count; + +/* error handling */ +phy_parse_error: + dev_err(&phydev->mdio.dev, "parse error: sysfs_set_power_cfg failed\n"); + return err; + +phy_pmode_transit_error: + dev_err(&phydev->mdio.dev, "pmode error: sysfs_set_power_cfg failed\n"); + return err; +} + +/* This function handles read accesses to the node 'loopback_cfg' in sysfs + * Reading the node returns the current loopback configuration + */ +static ssize_t sysfs_get_loopback_cfg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int reg_val; + char *lmode; + struct phy_device *phydev = to_phy_device(dev); + + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, "getting loopback cfg\n"); + + reg_val = phy_read(phydev, MII_BMCR); + if (reg_val < 0) + goto phy_read_error; + + if (reg_val & BMCR_LOOPBACK) { + /* loopback enabled */ + reg_val = phy_read(phydev, MII_ECTRL); + if (reg_val < 0) + goto phy_read_error; + + /* mask loopback mode bits */ + reg_val &= ECTRL_LOOPBACK_MODE; + + switch (reg_val) { + case ECTRL_INTERNAL_LMODE: + lmode = "INTERNAL_LOOPBACK\n"; + break; + case ECTRL_EXTERNAL_LMODE: + lmode = "EXTERNAL_LOOPBACK\n"; + break; + case ECTRL_REMOTE_LMODE: + lmode = "REMOTE_LOOPBACK\n"; + break; + default: + lmode = "unknown\n"; + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, + "unknown reg val is [%08x]\n", reg_val); + } + } else { + /* loopback disabled */ + lmode = "LOOPBACK_DISABLED\n"; + } + + /* write result into the buffer */ + return scnprintf(buf, PAGE_SIZE, lmode); + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: sysfs_get_loopback_cfg failed\n"); + return reg_val; +} + +/* This function handles write accesses to the node 'loopback_cfg' + * in sysfs. + * Depending on the value written to it, the phy enters a certain + * loopback state. + */ +static ssize_t sysfs_set_loopback_cfg(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + int lmode; + struct phy_device *phydev = to_phy_device(dev); + + if (!managed_mode) + goto phy_auto_op_error; + + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, "setting loopback cfg PHY %x\n", + phydev->mdio.addr); + + /* parse the buffer */ + err = kstrtoint(buf, 10, &lmode); + if (err < 0) + goto phy_parse_error; + + switch (lmode) { + case 0: + err = enter_loopback_mode(phydev, NO_LMODE); + if (!no_poll) + start_polling(phydev); + break; + case 1: + if (!no_poll) + stop_polling(phydev); + err = enter_loopback_mode(phydev, INTERNAL_LMODE); + break; + case 2: + if (!no_poll) + stop_polling(phydev); + err = enter_loopback_mode(phydev, EXTERNAL_LMODE); + break; + case 3: + if (!no_poll) + stop_polling(phydev); + err = enter_loopback_mode(phydev, REMOTE_LMODE); + break; + default: + break; + } + + if (err) + goto phy_lmode_transit_error; + + return count; + +/* error handling */ +phy_auto_op_error: + dev_info(&phydev->mdio.dev, "phy is in auto mode: loopback not available\n"); + return count; + +phy_parse_error: + dev_err(&phydev->mdio.dev, "parse error: sysfs_set_loopback_cfg failed\n"); + return err; + +phy_lmode_transit_error: + dev_err(&phydev->mdio.dev, "lmode error: sysfs_set_loopback_cfg failed\n"); + return err; +} + +/* This function handles read accesses to the node 'cable_test' in sysfs + * Reading the node executes a cable test and returns the result + */ +static ssize_t sysfs_get_cable_test(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int reg_val; + int err; + char *c_test_result; + struct phy_device *phydev = to_phy_device(dev); + + if (!managed_mode) + goto phy_auto_op_error; + + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, "phy %x executing cable test\n", + phydev->mdio.addr); + + /* disable link control prior to cable test */ + set_link_control(phydev, 0); + + /* execute a cable test */ + err = phy_configure_bit(phydev, MII_ECTRL, ECTRL_CABLE_TEST, 1); + if (err < 0) + goto phy_configure_error; + + /* wait until test is completed */ + err = wait_on_condition(phydev, MII_ECTRL, ECTRL_CABLE_TEST, + 0, CABLE_TEST_TIMEOUT); + if (err < 0) + goto phy_transition_error; + + /* evaluate the test results */ + reg_val = phy_read(phydev, MII_EXTERNAL_STATUS); + if (reg_val < 0) + goto phy_read_error; + + if (reg_val & EXTSTAT_SHORT_DETECT) + c_test_result = "SHORT_DETECT\n"; + else if (reg_val & EXTSTAT_OPEN_DETECT) + c_test_result = "OPEN_DETECT\n"; + else + c_test_result = "NO_ERROR\n"; + + /* reenable link control after cable test */ + set_link_control(phydev, 1); + + /* write result into the buffer */ + return scnprintf(buf, PAGE_SIZE, c_test_result); + +/* error handling */ +phy_auto_op_error: + dev_info(&phydev->mdio.dev, "phy is in auto mode: cabletest not available\n"); + return 0; + +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: sysfs_get_cable_test failed\n"); + return reg_val; + +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: sysfs_get_cable_test failed\n"); + return err; + +phy_transition_error: + dev_err(&phydev->mdio.dev, "Timeout: cable test failed to finish in time\n"); + return err; +} + +/* This function handles read accesses to the node 'test_mode' in sysfs + * Reading the node returns the current test mode configuration + */ +static ssize_t sysfs_get_test_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int reg_val; + char *tmode; + struct phy_device *phydev = to_phy_device(dev); + + reg_val = phy_read(phydev, MII_ECTRL); + if (reg_val < 0) + goto phy_read_error; + + /* mask test mode bits */ + reg_val &= ECTRL_TEST_MODE; + + switch (reg_val) { + case ECTRL_NO_TMODE: + tmode = "NO_TMODE\n"; + break; + case ECTRL_TMODE1: + tmode = "TMODE1\n"; + break; + case ECTRL_TMODE2: + tmode = "TMODE2\n"; + break; + case ECTRL_TMODE3: + tmode = "TMODE3\n"; + break; + case ECTRL_TMODE4: + tmode = "TMODE4\n"; + break; + case ECTRL_TMODE5: + tmode = "TMODE5\n"; + break; + case ECTRL_TMODE6: + tmode = "TMODE6\n"; + break; + default: + tmode = "unknown\n"; + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, + "unknown reg val is [%08x]\n", reg_val); + } + + /* write result into the buffer */ + return scnprintf(buf, PAGE_SIZE, tmode); + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: sysfs_get_test_mode failed\n"); + return reg_val; +} + +/* This function handles write accesses to the node 'test_mode' in sysfs + * Depending on the value written to it, the phy enters a certain test mode + */ +static ssize_t sysfs_set_test_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + int tmode; + struct phy_device *phydev = to_phy_device(dev); + + if (!managed_mode) + goto phy_auto_op_error; + + /* parse the buffer */ + err = kstrtoint(buf, 10, &tmode); + if (err < 0) + goto phy_parse_error; + + switch (tmode) { + case 0: + err = enter_test_mode(phydev, NO_TMODE); + /* enable link control after exiting test */ + set_link_control(phydev, 1); + break; + case 1: + /* disbale link control before entering test */ + set_link_control(phydev, 0); + err = enter_test_mode(phydev, TMODE1); + break; + case 2: + /* disbale link control before entering test */ + set_link_control(phydev, 0); + err = enter_test_mode(phydev, TMODE2); + break; + case 3: + /* disbale link control before entering test */ + set_link_control(phydev, 0); + err = enter_test_mode(phydev, TMODE3); + break; + case 4: + /* disbale link control before entering test */ + set_link_control(phydev, 0); + err = enter_test_mode(phydev, TMODE4); + break; + case 5: + /* disbale link control before entering test */ + set_link_control(phydev, 0); + err = enter_test_mode(phydev, TMODE5); + break; + case 6: + /* disbale link control before entering test */ + set_link_control(phydev, 0); + err = enter_test_mode(phydev, TMODE6); + break; + default: + break; + } + + if (err) + goto phy_tmode_transit_error; + + return count; + +/* error handling */ +phy_auto_op_error: + dev_info(&phydev->mdio.dev, "phy is in auto mode: testmodes not available\n"); + return count; + +phy_parse_error: + dev_err(&phydev->mdio.dev, "parse error: sysfs_get_test_mode failed\n"); + return err; + +phy_tmode_transit_error: + dev_err(&phydev->mdio.dev, "tmode error: sysfs_get_test_mode failed\n"); + return err; +} + +/* This function handles read accesses to the node 'led_cfg' in sysfs. + * Reading the node returns the current led configuration + */ +static ssize_t sysfs_get_led_cfg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int reg_val; + char *lmode; + struct phy_device *phydev = to_phy_device(dev); + + lmode = "DISABLED\n"; + + /* only TJA1100 has leds */ + if ((phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1100) { + reg_val = phy_read(phydev, MII_CFG1); + if (reg_val < 0) + goto phy_read_error; + + if (reg_val & TJA1100_CFG1_LED_EN) { + /* mask led mode bits */ + reg_val &= TJA1100_CFG1_LED_MODE; + + switch (reg_val) { + case CFG1_LED_LINKUP: + lmode = "LINKUP\n"; + break; + case CFG1_LED_FRAMEREC: + lmode = "FRAMEREC\n"; + break; + case CFG1_LED_SYMERR: + lmode = "SYMERR\n"; + break; + case CFG1_LED_CRSSIG: + lmode = "CRSSIG\n"; + break; + default: + lmode = "unknown\n"; + if (verbosity > 1) + dev_alert(&phydev->mdio.dev, + "unknown reg val is [%08x]\n", reg_val); + } + } + } + + /* write result into the buffer */ + return scnprintf(buf, PAGE_SIZE, lmode); + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: sysfs_get_led_cfg failed\n"); + return reg_val; +} + +/* This function handles write accesses to the node 'led_cfg' in sysfs + * Depending on the value written to it, the led mode is configured + * accordingly. + */ +static ssize_t sysfs_set_led_cfg(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + int lmode; + struct phy_device *phydev = to_phy_device(dev); + + if ((phydev->phy_id & NXP_PHY_ID_MASK) != NXP_PHY_ID_TJA1100) + goto no_led_error; + + /* parse the buffer */ + err = kstrtoint(buf, 10, &lmode); + if (err < 0) + goto phy_parse_error; + + switch (lmode) { + case 0: + err = enter_led_mode(phydev, NO_LED_MODE); + break; + case 1: + err = enter_led_mode(phydev, LINKUP_LED_MODE); + break; + case 2: + err = enter_led_mode(phydev, FRAMEREC_LED_MODE); + break; + case 3: + err = enter_led_mode(phydev, SYMERR_LED_MODE); + break; + case 4: + err = enter_led_mode(phydev, CRSSIG_LED_MODE); + break; + default: + break; + } + + if (err) + goto phy_lmode_transit_error; + + return count; + +/* error handling */ +phy_parse_error: + dev_err(&phydev->mdio.dev, "parse error: sysfs_set_led_cfg failed\n"); + return err; + +phy_lmode_transit_error: + dev_err(&phydev->mdio.dev, "lmode error: sysfs_set_led_cfg failed\n"); + return err; + +no_led_error: + dev_info(&phydev->mdio.dev, "phy has no led support\n"); + return count; +} + +/* This function handles read accesses to the node 'link_status' in sysfs + * Depending on current link status of the phy, the node reads + * 'up' or 'down' + */ +static ssize_t sysfs_get_link_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int linkup; + struct phy_device *phydev = to_phy_device(dev); + + linkup = get_link_status(phydev); + + /* write result into the buffer */ + return scnprintf(buf, PAGE_SIZE, "%s\n", linkup ? "up" : "down"); +} + +/* This function handles read accesses to the node 'wakeup_cfg' in sysfs + * Reading the node returns the current status of the bits + * FWDPHYLOC, REMWUPHY, LOCWUPHY, FWDPHYREM + */ +static ssize_t sysfs_get_wakeup_cfg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int reg_val; + int fwdphyloc_en, remwuphy_en, locwuphy_en, fwdphyrem_en; + struct phy_device *phydev = to_phy_device(dev); + + if ((phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1102P0 || + (phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1102P1 || + (phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1101) { + reg_val = phy_read(phydev, MII_CFG1); + if (reg_val < 0) + goto phy_read_error; + + fwdphyloc_en = 0; + remwuphy_en = 0; + locwuphy_en = 0; + fwdphyrem_en = 0; + + if (reg_val & TJA1102_CFG1_FWDPHYLOC) + fwdphyloc_en = 1; + if (reg_val & CFG1_REMWUPHY) + remwuphy_en = 1; + if (reg_val & CFG1_LOCWUPHY) + locwuphy_en = 1; + if (reg_val & CFG1_FWDPHYREM) + fwdphyrem_en = 1; + } else if ((phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1100) { + remwuphy_en = 1; /* not configurable, always enabled */ + fwdphyloc_en = 0; /* not supported */ + + /* The status LED and WAKE input share a pin, so ultimately + * configuration depends on the hardware setup. + * If LED is disabled, we assume the pin is used for WAKE. + * In this case, the phy wakes up upon local wakeup event + * via the WAKE pin and also forwards it. + */ + reg_val = phy_read(phydev, MII_CFG1); + if (reg_val < 0) + goto phy_read_error; + + if (reg_val & TJA1100_CFG1_LED_EN) { + locwuphy_en = 0; + fwdphyrem_en = 0; + } else { + locwuphy_en = 1; + fwdphyrem_en = 1; + } + } else { + goto unsupported_phy_error; + } + + /* write result into the buffer */ + return scnprintf(buf, PAGE_SIZE, + "fwdphyloc[%s], remwuphy[%s], locwuphy[%s], fwdphyrem[%s]\n", + (fwdphyloc_en ? "on" : "off"), + (remwuphy_en ? "on" : "off"), + (locwuphy_en ? "on" : "off"), + (fwdphyrem_en ? "on" : "off")); + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: sysfs_get_wakeup_cfg failed\n"); + return reg_val; + +unsupported_phy_error: + dev_err(&phydev->mdio.dev, "unsupported phy, sysfs_get_wakeup_cfg failed\n"); + return -1; +} + +/* This function handles write accesses to the node 'wakeup_cfg' in sysfs + * Depending on the hexadecimal value written, the bits + * FWDPHYLOC, REMWUPHY, LOCWUPHY, FWDPHYREM are configured + */ +static ssize_t sysfs_set_wakeup_cfg(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err, reg_val, reg_mask, wakeup_cfg; + struct phy_device *phydev = to_phy_device(dev); + + /* parse the buffer */ + err = kstrtoint(buf, 16, &wakeup_cfg); + if (err < 0) + goto phy_parse_error; + + if ((phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1102P0 || + (phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1102P1 || + (phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1101) { + reg_val = 0; + + /* the first 4 bits of the supplied hexadecimal value + * are interpreted as the wakeup configuration + */ + if (wakeup_cfg & SYSFS_FWDPHYLOC) + reg_val |= TJA1102_CFG1_FWDPHYLOC; + if (wakeup_cfg & SYSFS_REMWUPHY) + reg_val |= CFG1_REMWUPHY; + if (wakeup_cfg & SYSFS_LOCWUPHY) + reg_val |= CFG1_LOCWUPHY; + if (wakeup_cfg & SYSFS_FWDPHYREM) + reg_val |= CFG1_FWDPHYREM; + + reg_mask = (TJA1102_CFG1_FWDPHYLOC | CFG1_REMWUPHY | + CFG1_LOCWUPHY | CFG1_FWDPHYREM); + + err = phy_configure_bits(phydev, MII_CFG1, reg_mask, reg_val); + if (err < 0) + goto phy_configure_error; + } else if ((phydev->phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1100) { + /* FWDPHYLOC MUST be off + * REMWUPHY MUST be on + * only LOCWUPHY and FWDPHYREM are configurable + * Possible configurations: + * - BOTH enabled (then led MUST be off) + * - BOTH disabled (then led CAN be on) + * all other configurations are invalid. + * + * Therefore valid values to write to sysfs are: + * - 2 (LOCWUPHY and FWDPHYREM off) + * - E (LOCWUPHY and FWDPHYREM on) + */ + if (((wakeup_cfg & SYSFS_LOCWUPHY) != + (wakeup_cfg & SYSFS_FWDPHYREM)) || + wakeup_cfg & SYSFS_FWDPHYLOC || + !(wakeup_cfg & SYSFS_REMWUPHY)) { + dev_alert(&phydev->mdio.dev, "Invalid configuration\n"); + } else if (wakeup_cfg & SYSFS_LOCWUPHY && + wakeup_cfg & SYSFS_FWDPHYREM) { + err = enter_led_mode(phydev, NO_LED_MODE); + if (err) + goto phy_lmode_transit_error; + } + } + + return count; + +/* error handling */ +phy_parse_error: + dev_err(&phydev->mdio.dev, "parse error: sysfs_set_wakeup_cfg failed\n"); + return err; + +phy_configure_error: + dev_err(&phydev->mdio.dev, "phy r/w error: sysfs_set_wakeup_cfg failed\n"); + return err; + +phy_lmode_transit_error: + dev_err(&phydev->mdio.dev, "lmode error: sysfs_set_wakeup_cfg failed\n"); + return err; +} + +/* This function handles read accesses to the node 'snr_wlimit_cfg' in sysfs. + * Reading the node returns the current snr warning limit. + */ +static ssize_t sysfs_get_snr_wlimit_cfg(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int reg_val; + char *snr_limit; + struct phy_device *phydev = to_phy_device(dev); + + reg_val = phy_read(phydev, MII_CFG2); + if (reg_val < 0) + goto phy_read_error; + + /* mask snr wlimit bits */ + reg_val &= CFG2_SNR_WLIMIT; + + switch (reg_val) { + case SNR_CLASS_NONE: + snr_limit = "no fail limit\n"; + break; + case SNR_CLASS_A: + snr_limit = "CLASS_A\n"; + break; + case SNR_CLASS_B: + snr_limit = "CLASS_B\n"; + break; + case SNR_CLASS_C: + snr_limit = "CLASS_C\n"; + break; + case SNR_CLASS_D: + snr_limit = "CLASS_D\n"; + break; + case SNR_CLASS_E: + snr_limit = "CLASS_E\n"; + break; + case SNR_CLASS_F: + snr_limit = "CLASS_F\n"; + break; + case SNR_CLASS_G: + snr_limit = "CLASS_G\n"; + break; + default: + snr_limit = "unknown\n"; + } + + /* write result into the buffer */ + return scnprintf(buf, PAGE_SIZE, snr_limit); + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: sysfs_get_snr_wlimit_cfg failed\n"); + return reg_val; +} + +/* This function handles write accesses to the node 'snr_wlimit_cfg' in sysfs + * Depending on the value written to it, the snr warning limit is configured + * accordingly. + */ +static ssize_t sysfs_set_snr_wlimit_cfg(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err, snr_limit, reg_val; + struct phy_device *phydev = to_phy_device(dev); + + /* parse the buffer */ + err = kstrtoint(buf, 10, &snr_limit); + if (err < 0) + goto phy_parse_error; + + switch (snr_limit) { + case 0: + reg_val = SNR_CLASS_NONE; + break; + case 1: + reg_val = SNR_CLASS_A; + break; + case 2: + reg_val = SNR_CLASS_B; + break; + case 3: + reg_val = SNR_CLASS_C; + break; + case 4: + reg_val = SNR_CLASS_D; + break; + case 5: + reg_val = SNR_CLASS_E; + break; + case 6: + reg_val = SNR_CLASS_F; + break; + case 7: + reg_val = SNR_CLASS_G; + break; + default: + reg_val = -1; + break; + } + + if (reg_val != -1) { + err = phy_configure_bits(phydev, MII_CFG2, + CFG2_SNR_WLIMIT, reg_val); + if (err) + goto phy_configure_error; + } + + return count; + +/* error handling */ +phy_parse_error: + dev_err(&phydev->mdio.dev, "parse error: sysfs_set_snr_wlimit_cfg failed\n"); + return err; + +phy_configure_error: + dev_err(&phydev->mdio.dev, + "phy r/w error: sysfs_set_snr_wlimit_cfg failed\n"); + return err; +} + +/* r/w access for everyone */ +static DEVICE_ATTR(master_cfg, S_IWUSR | S_IRUSR, + sysfs_get_master_cfg, sysfs_set_master_cfg); +static DEVICE_ATTR(power_cfg, S_IWUSR | S_IRUSR, + sysfs_get_power_cfg, sysfs_set_power_cfg); +static DEVICE_ATTR(loopback_cfg, S_IWUSR | S_IRUSR, + sysfs_get_loopback_cfg, sysfs_set_loopback_cfg); +static DEVICE_ATTR(cable_test, S_IRUSR, sysfs_get_cable_test, NULL); +static DEVICE_ATTR(test_mode, S_IWUSR | S_IRUSR, + sysfs_get_test_mode, sysfs_set_test_mode); +static DEVICE_ATTR(led_cfg, S_IWUSR | S_IRUSR, + sysfs_get_led_cfg, sysfs_set_led_cfg); +static DEVICE_ATTR(link_status, S_IRUSR, sysfs_get_link_status, NULL); +static DEVICE_ATTR(wakeup_cfg, S_IWUSR | S_IRUSR, + sysfs_get_wakeup_cfg, sysfs_set_wakeup_cfg); +static DEVICE_ATTR(snr_wlimit_cfg, S_IWUSR | S_IRUSR, + sysfs_get_snr_wlimit_cfg, sysfs_set_snr_wlimit_cfg); + +static struct attribute *nxp_sysfs_entries[] = { + &dev_attr_master_cfg.attr, + &dev_attr_power_cfg.attr, + &dev_attr_loopback_cfg.attr, + &dev_attr_cable_test.attr, + &dev_attr_test_mode.attr, + &dev_attr_led_cfg.attr, + &dev_attr_link_status.attr, + &dev_attr_wakeup_cfg.attr, + &dev_attr_snr_wlimit_cfg.attr, + NULL +}; + +static struct attribute_group nxp_attribute_group = { + .name = "configuration", + .attrs = nxp_sysfs_entries, +}; + +/* helper function, configures a register of phydev + * + * The function sets the bit of register reg_name, + * defined by bit_mask to 0 if (bit_value == 0), else to 1 + * @return 0 if configuration completed, <0 if read/write + * error occurred + */ +static inline int phy_configure_bit(struct phy_device *phydev, int reg_name, + int bit_mask, int bit_value) +{ + int reg_val, err; + + if (verbosity > 2) + dev_alert(&phydev->mdio.dev, "%s bit on mask [%08x] of reg [%d] of phy %x\n", (bit_value?"enabling":"disabling"), + bit_mask, reg_name, phydev->mdio.addr); + + reg_val = phy_read(phydev, reg_name); + if (reg_val < 0) + goto phy_read_error; + + if (bit_value) + reg_val |= bit_mask; + else + reg_val &= ~bit_mask; + + err = phy_write(phydev, reg_name, reg_val); + if (err < 0) + goto phy_write_error; + + return 0; + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: phy config failed\n"); + return reg_val; + +phy_write_error: + dev_err(&phydev->mdio.dev, "write error: phy config failed\n"); + return err; +} + +/* helper function, configures a register of phydev + * + * The function sets the bits of register reg_name, + * defined by bit_mask to bit_value + * @return 0 if configuration completed, <0 if read/write + * error occurred + */ +static inline int phy_configure_bits(struct phy_device *phydev, int reg_name, + int bit_mask, int bit_value) +{ + int reg_val, err; + + if (verbosity > 2) + dev_alert(&phydev->mdio.dev, "set mask [%08x] of reg [%d] of phy %x to value [%08x]\n", + bit_mask, reg_name, phydev->mdio.addr, bit_value); + + reg_val = phy_read(phydev, reg_name); + if (reg_val < 0) + goto phy_read_error; + + reg_val &= ~bit_mask; + reg_val |= bit_value; + + err = phy_write(phydev, reg_name, reg_val); + if (err < 0) + goto phy_write_error; + + return 0; + +/* error handling */ +phy_read_error: + dev_err(&phydev->mdio.dev, "read error: phy config failed\n"); + return reg_val; + +phy_write_error: + dev_err(&phydev->mdio.dev, "write error: phy config failed\n"); + return err; +} + +#ifdef NETDEV_NOTIFICATION_FIX +static struct class *bus_class_from_net_device(struct net_device *net_device, + const char *required_name) +{ + struct class *bus_class; + + if (!net_device || + !net_device->phydev || + !net_device->phydev->mdio.bus || + !net_device->phydev->mdio.bus->dev.class || + !net_device->phydev->mdio.bus->dev.class->name) + return NULL; + + bus_class = net_device->phydev->mdio.bus->dev.class; + if (strcmp(bus_class->name, required_name) != 0) + return NULL; + + return bus_class; +} + +static int mdio_bus_name_matches(struct device *found_device, + const void *desired_name) +{ + struct mii_bus *mdio_bus; + + /* Since we know 'found_dev' belongs to a class with the name + 'mdio_bus', we assume it is a member of a 'struct mii_bus', + and therefore it is safe to call container_of */ + mdio_bus = container_of(found_device, struct mii_bus, dev); + + /* Double check that this is indeed a 'struct mii_bus'. If it is, + it's state should be MDIO_REGISTERED at this point. If it is not, it is + either not a 'struct mii_bus', either it is in an undesired state. */ + if (mdio_bus->state != MDIOBUS_REGISTERED) + return 0; + + if (strcmp(mdio_bus->name, (char *)desired_name) == 0) + return 1; + return 0; +} + +static struct mii_bus *find_mdio_bus_by_name(const char *name, + struct class *mdio_bus_class) +{ + struct device *found_device; + + found_device = class_find_device(mdio_bus_class, + NULL, + (void *)name, + mdio_bus_name_matches); + if (found_device) + return container_of(found_device, struct mii_bus, dev); + else + return NULL; +} + +/* helper function, check if given phy id belongs to a nxp phy + * + * @return 0 if not an nxp phy, != 0 else + */ +static int is_nxp_phy(int phy_id) +{ + return ((phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1100 || + (phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1101 || + (phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1102P0 || + (phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1102P1 || + (phy_id & NXP_PHY_ID_MASK) == NXP_PHY_ID_TJA1102S); + +} + +/* traverse the phy_map of the given mdio_bus, and manipulate any phys found + * that are found according to the value of the event, ie. + * - start (resume) on NETDEV_UP + * - stop (suspend) on NETDEV_GOING_DOWN + */ +static void mdio_netdev_change_event(struct mii_bus *mdio_bus, int event) +{ + /* normally on NETDEV_GOING_DOWN the kernel calls ndo_stop() + * of the eth controller, which stops and disconnects the one phy + * that is associated with the ethernet controller + * [see fec_enet_close() in fec_main.c l 2740]. + * We need to do this manually for every NXP phy, + * however we do not (necessarily) have an attached_dev, so phy_detach, + * which is called by phy_disconnect(), would crash + */ + int phy_addr; + struct phy_device *phydev; + + for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) { + phydev = mdiobus_get_phy(mdio_bus, phy_addr); + if (!phydev) + continue; + + if (!is_nxp_phy(phydev->phy_id) || !phydev->priv) + continue; + + if (event == NETDEV_GOING_DOWN) { + /* stop polling, + * as mdio bus will become unavailable as soon as + * fec_runtime_suspend() (fec_main.c l4801) is called + */ + if (!no_poll) + stop_polling(phydev); + + /* sets state to PHY_HALTED */ + phy_stop(phydev); + } else if (event == NETDEV_UP) { + /* updates the phy state and resumes, + * if state previously was PHY_HALTED + */ + phy_start(phydev); + + if (!no_poll) + start_polling(phydev); + } + } +} + +/* callback, called whenever a netdev changes its state. + * + * Handles only NETDEV_GOING_DOWN and NETDEV_UP events of interface called + * MDIO_INTERFACE_NAME. Phys on the mdio bus "fec_enet_mii_bus" + * are stopped (suspended) and started (resumed) accordingly. + * + * @return NOTIFY_DONE + */ +static int netdev_state_change_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + struct mii_bus *mdio_bus; + struct class *bus_class; + struct net_device *net_device; + + /* currently the eth0 interface controlls the mdio bus. + * However as CONFIG_FIXED_PHY is configured, + * eth0 is associated with "Fixed MDIO Bus", but the phydev + * is associated with "fec_enet_mii_bus". If eth0 goes down, + * only devices on "Fixed MDIO Bus" are notified (ie removed). + * We need to manually listen to eth0 events + * stops the phy and the polling + */ + if (event != NETDEV_GOING_DOWN && event != NETDEV_UP) + goto skip; + + if (strcmp(dev->name, MDIO_INTERFACE_NAME) != 0) + goto skip; + + net_device = first_net_device(&init_net); + do { + bus_class = bus_class_from_net_device(net_device, "mdio_bus"); + if (!bus_class) + continue; + mdio_bus = find_mdio_bus_by_name(MII_BUS_NAME, bus_class); + if (!mdio_bus) + continue; + + if (verbosity > 0) + pr_alert("NXP PHY: received event [%lx] for [%s]: Notifying phys on bus [%s]\n", + event, dev->name, mdio_bus->name); + + mdio_netdev_change_event(mdio_bus, event); + } while ((net_device = next_net_device(net_device))); + +skip: + return NOTIFY_DONE; +} + +/* netdev notification infrastructure */ +struct notifier_block netdev_notifier = { + .notifier_call = netdev_state_change_event +}; +#endif + +static struct phy_driver nxp_drivers[] = { +{ + .phy_id = NXP_PHY_ID_TJA1100, + .name = "TJA1100", + .phy_id_mask = NXP_PHY_ID_MASK, + .features = (SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_100BASET1_FULL), + .flags = 0, + .probe = &nxp_probe, + .remove = &nxp_remove, + .config_init = &nxp_config_init, + .config_aneg = &nxp_config_aneg, + .read_status = &genphy_read_status, + .resume = &nxp_resume, + .suspend = &nxp_suspend, + .config_intr = &nxp_config_intr, + .ack_interrupt = &nxp_ack_interrupt, + .did_interrupt = &nxp_did_interrupt, +}, { + .phy_id = NXP_PHY_ID_TJA1102P0, + .name = "TJA1102_p0", + .phy_id_mask = NXP_PHY_ID_MASK, + .features = (SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_100BASET1_FULL), + .flags = 0, + .probe = &nxp_probe, + .remove = &nxp_remove, + .config_init = &nxp_config_init, + .config_aneg = &nxp_config_aneg, + .read_status = &genphy_read_status, + .resume = &nxp_resume, + .suspend = &nxp_suspend, + .config_intr = &nxp_config_intr, + .ack_interrupt = &nxp_ack_interrupt, + .did_interrupt = &nxp_did_interrupt, +}, { + .phy_id = NXP_PHY_ID_TJA1101, + .name = "TJA1101", + .phy_id_mask = NXP_PHY_ID_MASK, + .features = (SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_100BASET1_FULL), + .flags = 0, + .probe = &nxp_probe, + .remove = &nxp_remove, + .config_init = &nxp_config_init, + .config_aneg = &nxp_config_aneg, + .read_status = &genphy_read_status, + .resume = &nxp_resume, + .suspend = &nxp_suspend, + .config_intr = &nxp_config_intr, + .ack_interrupt = &nxp_ack_interrupt, + .did_interrupt = &nxp_did_interrupt, +}, { + .phy_id = NXP_PHY_ID_TJA1102S, + .name = "TJA1102S", + .phy_id_mask = NXP_PHY_ID_MASK, + .features = (SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_100BASET1_FULL), + .flags = 0, + .probe = &nxp_probe, + .remove = &nxp_remove, + .config_init = &nxp_config_init, + .config_aneg = &nxp_config_aneg, + .read_status = &genphy_read_status, + .resume = &nxp_resume, + .suspend = &nxp_suspend, + .config_intr = &nxp_config_intr, + .ack_interrupt = &nxp_ack_interrupt, + .did_interrupt = &nxp_did_interrupt, +#ifdef CONFIG_TJA1102_FIX +}, { + .phy_id = NXP_PHY_ID_TJA1102P1, + .name = "TJA1102_p1", + .phy_id_mask = NXP_PHY_ID_MASK, + .features = (SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_100BASET1_FULL), + .flags = 0, + .probe = &nxp_probe, + .remove = &nxp_remove, + .config_init = &nxp_config_init, + .config_aneg = &nxp_config_aneg, + .read_status = &genphy_read_status, + .resume = &nxp_resume, + .suspend = &nxp_suspend, + .config_intr = &nxp_config_intr, + .ack_interrupt = &nxp_ack_interrupt, + .did_interrupt = &nxp_did_interrupt, +#endif +} }; + +/* module init function */ +static int __init nxp_init(void) +{ + int err; + + pr_alert("NXP PHY: loading NXP PHY driver: [%s%s]\n", + (managed_mode ? "managed mode" : "autonomous mode"), + (no_poll ? ", polling disabled" : "")); + + err = phy_drivers_register(nxp_drivers, ARRAY_SIZE(nxp_drivers), THIS_MODULE); + if (err) + goto drv_registration_error; + +#ifdef NETDEV_NOTIFICATION_FIX + if (!no_poll) { + err = register_netdevice_notifier(&netdev_notifier); + if (err) + goto notification_registration_error; + } +#endif + + return 0; + +/* error handling */ +drv_registration_error: + pr_err("NXP PHY: driver registration failed\n"); + return err; + +#ifdef NETDEV_NOTIFICATION_FIX +notification_registration_error: + pr_err("NXP PHY: could not register notification handler\n"); + unregister_netdevice_notifier(&netdev_notifier); + return err; +#endif +} + +module_init(nxp_init); + +/* module exit function */ +static void __exit nxp_exit(void) +{ + pr_alert("NXP PHY: unloading NXP PHY driver\n"); +#ifdef NETDEV_NOTIFICATION_FIX + if (!no_poll) + unregister_netdevice_notifier(&netdev_notifier); +#endif + phy_drivers_unregister(nxp_drivers, ARRAY_SIZE(nxp_drivers)); +} + +module_exit(nxp_exit); + +/* use module device table for hotplugging support */ +static struct mdio_device_id __maybe_unused nxp_tbl[] = { + {NXP_PHY_ID_TJA1100, NXP_PHY_ID_MASK}, + {NXP_PHY_ID_TJA1102P0, NXP_PHY_ID_MASK}, + {NXP_PHY_ID_TJA1102S, NXP_PHY_ID_MASK}, + {} +}; + +MODULE_DEVICE_TABLE(mdio, nxp_tbl); + +MODULE_DESCRIPTION("NXP PHY driver"); +MODULE_AUTHOR("Marco Hartmann"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.3"); |