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