/* * 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 #include #include #include #include #include #include #include #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); /* 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); return 0; /* error handling */ phy_configure_error: dev_err(&phydev->mdio.dev, "phy r/w error: resume failed\n"); return err; phy_transition_error: dev_err(&phydev->mdio.dev, "power mode transition failed\n"); return err; phy_pll_error: 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");