// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Sartura Ltd. * * Author: Robert Marko * Christian Marangi * * Qualcomm QCA8072 and QCA8075 PHY driver */ #include #include #include #include #include #include #include "qcom.h" #define QCA807X_CHIP_CONFIGURATION 0x1f #define QCA807X_BT_BX_REG_SEL BIT(15) #define QCA807X_BT_BX_REG_SEL_FIBER 0 #define QCA807X_BT_BX_REG_SEL_COPPER 1 #define QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK GENMASK(3, 0) #define QCA807X_CHIP_CONFIGURATION_MODE_QSGMII_SGMII 4 #define QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_FIBER 3 #define QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_ALL_COPPER 0 #define QCA807X_MEDIA_SELECT_STATUS 0x1a #define QCA807X_MEDIA_DETECTED_COPPER BIT(5) #define QCA807X_MEDIA_DETECTED_1000_BASE_X BIT(4) #define QCA807X_MEDIA_DETECTED_100_BASE_FX BIT(3) #define QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION 0x807e #define QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION_EN BIT(0) #define QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH 0x801a #define QCA807X_CONTROL_DAC_MASK GENMASK(2, 0) /* List of tweaks enabled by this bit: * - With both FULL amplitude and FULL bias current: bias current * is set to half. * - With only DSP amplitude: bias current is set to half and * is set to 1/4 with cable < 10m. * - With DSP bias current (included both DSP amplitude and * DSP bias current): bias current is half the detected current * with cable < 10m. */ #define QCA807X_CONTROL_DAC_BIAS_CURRENT_TWEAK BIT(2) #define QCA807X_CONTROL_DAC_DSP_BIAS_CURRENT BIT(1) #define QCA807X_CONTROL_DAC_DSP_AMPLITUDE BIT(0) #define QCA807X_MMD7_LED_100N_1 0x8074 #define QCA807X_MMD7_LED_100N_2 0x8075 #define QCA807X_MMD7_LED_1000N_1 0x8076 #define QCA807X_MMD7_LED_1000N_2 0x8077 #define QCA807X_MMD7_LED_CTRL(x) (0x8074 + ((x) * 2)) #define QCA807X_MMD7_LED_FORCE_CTRL(x) (0x8075 + ((x) * 2)) /* LED hw control pattern for fiber port */ #define QCA807X_LED_FIBER_PATTERN_MASK GENMASK(11, 1) #define QCA807X_LED_FIBER_TXACT_BLK_EN BIT(10) #define QCA807X_LED_FIBER_RXACT_BLK_EN BIT(9) #define QCA807X_LED_FIBER_FDX_ON_EN BIT(6) #define QCA807X_LED_FIBER_HDX_ON_EN BIT(5) #define QCA807X_LED_FIBER_1000BX_ON_EN BIT(2) #define QCA807X_LED_FIBER_100FX_ON_EN BIT(1) /* Some device repurpose the LED as GPIO out */ #define QCA807X_GPIO_FORCE_EN QCA808X_LED_FORCE_EN #define QCA807X_GPIO_FORCE_MODE_MASK QCA808X_LED_FORCE_MODE_MASK #define QCA807X_FUNCTION_CONTROL 0x10 #define QCA807X_FC_MDI_CROSSOVER_MODE_MASK GENMASK(6, 5) #define QCA807X_FC_MDI_CROSSOVER_AUTO 3 #define QCA807X_FC_MDI_CROSSOVER_MANUAL_MDIX 1 #define QCA807X_FC_MDI_CROSSOVER_MANUAL_MDI 0 /* PQSGMII Analog PHY specific */ #define PQSGMII_CTRL_REG 0x0 #define PQSGMII_ANALOG_SW_RESET BIT(6) #define PQSGMII_DRIVE_CONTROL_1 0xb #define PQSGMII_TX_DRIVER_MASK GENMASK(7, 4) #define PQSGMII_TX_DRIVER_140MV 0x0 #define PQSGMII_TX_DRIVER_160MV 0x1 #define PQSGMII_TX_DRIVER_180MV 0x2 #define PQSGMII_TX_DRIVER_200MV 0x3 #define PQSGMII_TX_DRIVER_220MV 0x4 #define PQSGMII_TX_DRIVER_240MV 0x5 #define PQSGMII_TX_DRIVER_260MV 0x6 #define PQSGMII_TX_DRIVER_280MV 0x7 #define PQSGMII_TX_DRIVER_300MV 0x8 #define PQSGMII_TX_DRIVER_320MV 0x9 #define PQSGMII_TX_DRIVER_400MV 0xa #define PQSGMII_TX_DRIVER_500MV 0xb #define PQSGMII_TX_DRIVER_600MV 0xc #define PQSGMII_MODE_CTRL 0x6d #define PQSGMII_MODE_CTRL_AZ_WORKAROUND_MASK BIT(0) #define PQSGMII_MMD3_SERDES_CONTROL 0x805a #define PHY_ID_QCA8072 0x004dd0b2 #define PHY_ID_QCA8075 0x004dd0b1 #define QCA807X_COMBO_ADDR_OFFSET 4 #define QCA807X_PQSGMII_ADDR_OFFSET 5 #define SERDES_RESET_SLEEP 100 enum qca807x_global_phy { QCA807X_COMBO_ADDR = 4, QCA807X_PQSGMII_ADDR = 5, }; struct qca807x_shared_priv { unsigned int package_mode; u32 tx_drive_strength; }; struct qca807x_gpio_priv { struct phy_device *phy; }; struct qca807x_priv { bool dac_full_amplitude; bool dac_full_bias_current; bool dac_disable_bias_current_tweak; }; static int qca807x_cable_test_start(struct phy_device *phydev) { /* we do all the (time consuming) work later */ return 0; } static int qca807x_led_parse_netdev(struct phy_device *phydev, unsigned long rules, u16 *offload_trigger) { /* Parsing specific to netdev trigger */ switch (phydev->port) { case PORT_TP: if (test_bit(TRIGGER_NETDEV_TX, &rules)) *offload_trigger |= QCA808X_LED_TX_BLINK; if (test_bit(TRIGGER_NETDEV_RX, &rules)) *offload_trigger |= QCA808X_LED_RX_BLINK; if (test_bit(TRIGGER_NETDEV_LINK_10, &rules)) *offload_trigger |= QCA808X_LED_SPEED10_ON; if (test_bit(TRIGGER_NETDEV_LINK_100, &rules)) *offload_trigger |= QCA808X_LED_SPEED100_ON; if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) *offload_trigger |= QCA808X_LED_SPEED1000_ON; if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules)) *offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON; if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules)) *offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON; break; case PORT_FIBRE: if (test_bit(TRIGGER_NETDEV_TX, &rules)) *offload_trigger |= QCA807X_LED_FIBER_TXACT_BLK_EN; if (test_bit(TRIGGER_NETDEV_RX, &rules)) *offload_trigger |= QCA807X_LED_FIBER_RXACT_BLK_EN; if (test_bit(TRIGGER_NETDEV_LINK_100, &rules)) *offload_trigger |= QCA807X_LED_FIBER_100FX_ON_EN; if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) *offload_trigger |= QCA807X_LED_FIBER_1000BX_ON_EN; if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules)) *offload_trigger |= QCA807X_LED_FIBER_HDX_ON_EN; if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules)) *offload_trigger |= QCA807X_LED_FIBER_FDX_ON_EN; break; default: return -EOPNOTSUPP; } if (rules && !*offload_trigger) return -EOPNOTSUPP; return 0; } static int qca807x_led_hw_control_enable(struct phy_device *phydev, u8 index) { u16 reg; if (index > 1) return -EINVAL; reg = QCA807X_MMD7_LED_FORCE_CTRL(index); return qca808x_led_reg_hw_control_enable(phydev, reg); } static int qca807x_led_hw_is_supported(struct phy_device *phydev, u8 index, unsigned long rules) { u16 offload_trigger = 0; if (index > 1) return -EINVAL; return qca807x_led_parse_netdev(phydev, rules, &offload_trigger); } static int qca807x_led_hw_control_set(struct phy_device *phydev, u8 index, unsigned long rules) { u16 reg, mask, offload_trigger = 0; int ret; if (index > 1) return -EINVAL; ret = qca807x_led_parse_netdev(phydev, rules, &offload_trigger); if (ret) return ret; ret = qca807x_led_hw_control_enable(phydev, index); if (ret) return ret; switch (phydev->port) { case PORT_TP: reg = QCA807X_MMD7_LED_CTRL(index); mask = QCA808X_LED_PATTERN_MASK; break; case PORT_FIBRE: /* HW control pattern bits are in LED FORCE reg */ reg = QCA807X_MMD7_LED_FORCE_CTRL(index); mask = QCA807X_LED_FIBER_PATTERN_MASK; break; default: return -EINVAL; } return phy_modify_mmd(phydev, MDIO_MMD_AN, reg, mask, offload_trigger); } static bool qca807x_led_hw_control_status(struct phy_device *phydev, u8 index) { u16 reg; if (index > 1) return false; reg = QCA807X_MMD7_LED_FORCE_CTRL(index); return qca808x_led_reg_hw_control_status(phydev, reg); } static int qca807x_led_hw_control_get(struct phy_device *phydev, u8 index, unsigned long *rules) { u16 reg; int val; if (index > 1) return -EINVAL; /* Check if we have hw control enabled */ if (qca807x_led_hw_control_status(phydev, index)) return -EINVAL; /* Parsing specific to netdev trigger */ switch (phydev->port) { case PORT_TP: reg = QCA807X_MMD7_LED_CTRL(index); val = phy_read_mmd(phydev, MDIO_MMD_AN, reg); if (val & QCA808X_LED_TX_BLINK) set_bit(TRIGGER_NETDEV_TX, rules); if (val & QCA808X_LED_RX_BLINK) set_bit(TRIGGER_NETDEV_RX, rules); if (val & QCA808X_LED_SPEED10_ON) set_bit(TRIGGER_NETDEV_LINK_10, rules); if (val & QCA808X_LED_SPEED100_ON) set_bit(TRIGGER_NETDEV_LINK_100, rules); if (val & QCA808X_LED_SPEED1000_ON) set_bit(TRIGGER_NETDEV_LINK_1000, rules); if (val & QCA808X_LED_HALF_DUPLEX_ON) set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules); if (val & QCA808X_LED_FULL_DUPLEX_ON) set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules); break; case PORT_FIBRE: /* HW control pattern bits are in LED FORCE reg */ reg = QCA807X_MMD7_LED_FORCE_CTRL(index); val = phy_read_mmd(phydev, MDIO_MMD_AN, reg); if (val & QCA807X_LED_FIBER_TXACT_BLK_EN) set_bit(TRIGGER_NETDEV_TX, rules); if (val & QCA807X_LED_FIBER_RXACT_BLK_EN) set_bit(TRIGGER_NETDEV_RX, rules); if (val & QCA807X_LED_FIBER_100FX_ON_EN) set_bit(TRIGGER_NETDEV_LINK_100, rules); if (val & QCA807X_LED_FIBER_1000BX_ON_EN) set_bit(TRIGGER_NETDEV_LINK_1000, rules); if (val & QCA807X_LED_FIBER_HDX_ON_EN) set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules); if (val & QCA807X_LED_FIBER_FDX_ON_EN) set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules); break; default: return -EINVAL; } return 0; } static int qca807x_led_hw_control_reset(struct phy_device *phydev, u8 index) { u16 reg, mask; if (index > 1) return -EINVAL; switch (phydev->port) { case PORT_TP: reg = QCA807X_MMD7_LED_CTRL(index); mask = QCA808X_LED_PATTERN_MASK; break; case PORT_FIBRE: /* HW control pattern bits are in LED FORCE reg */ reg = QCA807X_MMD7_LED_FORCE_CTRL(index); mask = QCA807X_LED_FIBER_PATTERN_MASK; break; default: return -EINVAL; } return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg, mask); } static int qca807x_led_brightness_set(struct phy_device *phydev, u8 index, enum led_brightness value) { u16 reg; int ret; if (index > 1) return -EINVAL; /* If we are setting off the LED reset any hw control rule */ if (!value) { ret = qca807x_led_hw_control_reset(phydev, index); if (ret) return ret; } reg = QCA807X_MMD7_LED_FORCE_CTRL(index); return qca808x_led_reg_brightness_set(phydev, reg, value); } static int qca807x_led_blink_set(struct phy_device *phydev, u8 index, unsigned long *delay_on, unsigned long *delay_off) { u16 reg; if (index > 1) return -EINVAL; reg = QCA807X_MMD7_LED_FORCE_CTRL(index); return qca808x_led_reg_blink_set(phydev, reg, delay_on, delay_off); } #ifdef CONFIG_GPIOLIB static int qca807x_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) { return GPIO_LINE_DIRECTION_OUT; } static int qca807x_gpio_get(struct gpio_chip *gc, unsigned int offset) { struct qca807x_gpio_priv *priv = gpiochip_get_data(gc); u16 reg; int val; reg = QCA807X_MMD7_LED_FORCE_CTRL(offset); val = phy_read_mmd(priv->phy, MDIO_MMD_AN, reg); return FIELD_GET(QCA807X_GPIO_FORCE_MODE_MASK, val); } static void qca807x_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) { struct qca807x_gpio_priv *priv = gpiochip_get_data(gc); u16 reg; int val; reg = QCA807X_MMD7_LED_FORCE_CTRL(offset); val = phy_read_mmd(priv->phy, MDIO_MMD_AN, reg); val &= ~QCA807X_GPIO_FORCE_MODE_MASK; val |= QCA807X_GPIO_FORCE_EN; val |= FIELD_PREP(QCA807X_GPIO_FORCE_MODE_MASK, value); phy_write_mmd(priv->phy, MDIO_MMD_AN, reg, val); } static int qca807x_gpio_dir_out(struct gpio_chip *gc, unsigned int offset, int value) { qca807x_gpio_set(gc, offset, value); return 0; } static int qca807x_gpio(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; struct qca807x_gpio_priv *priv; struct gpio_chip *gc; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->phy = phydev; gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL); if (!gc) return -ENOMEM; gc->label = dev_name(dev); gc->base = -1; gc->ngpio = 2; gc->parent = dev; gc->owner = THIS_MODULE; gc->can_sleep = true; gc->get_direction = qca807x_gpio_get_direction; gc->direction_output = qca807x_gpio_dir_out; gc->get = qca807x_gpio_get; gc->set = qca807x_gpio_set; return devm_gpiochip_add_data(dev, gc, priv); } #endif static int qca807x_read_fiber_status(struct phy_device *phydev) { bool changed; int ss, err; err = genphy_c37_read_status(phydev, &changed); if (err || !changed) return err; /* Read the QCA807x PHY-Specific Status register fiber page, * which indicates the speed and duplex that the PHY is actually * using, irrespective of whether we are in autoneg mode or not. */ ss = phy_read(phydev, AT803X_SPECIFIC_STATUS); if (ss < 0) return ss; phydev->speed = SPEED_UNKNOWN; phydev->duplex = DUPLEX_UNKNOWN; if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) { switch (FIELD_GET(AT803X_SS_SPEED_MASK, ss)) { case AT803X_SS_SPEED_100: phydev->speed = SPEED_100; break; case AT803X_SS_SPEED_1000: phydev->speed = SPEED_1000; break; } if (ss & AT803X_SS_DUPLEX) phydev->duplex = DUPLEX_FULL; else phydev->duplex = DUPLEX_HALF; } return 0; } static int qca807x_read_status(struct phy_device *phydev) { if (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported)) { switch (phydev->port) { case PORT_FIBRE: return qca807x_read_fiber_status(phydev); case PORT_TP: return at803x_read_status(phydev); default: return -EINVAL; } } return at803x_read_status(phydev); } static int qca807x_phy_package_probe_once(struct phy_device *phydev) { struct phy_package_shared *shared = phydev->shared; struct qca807x_shared_priv *priv = shared->priv; unsigned int tx_drive_strength; const char *package_mode_name; /* Default to 600mw if not defined */ if (of_property_read_u32(shared->np, "qcom,tx-drive-strength-milliwatt", &tx_drive_strength)) tx_drive_strength = 600; switch (tx_drive_strength) { case 140: priv->tx_drive_strength = PQSGMII_TX_DRIVER_140MV; break; case 160: priv->tx_drive_strength = PQSGMII_TX_DRIVER_160MV; break; case 180: priv->tx_drive_strength = PQSGMII_TX_DRIVER_180MV; break; case 200: priv->tx_drive_strength = PQSGMII_TX_DRIVER_200MV; break; case 220: priv->tx_drive_strength = PQSGMII_TX_DRIVER_220MV; break; case 240: priv->tx_drive_strength = PQSGMII_TX_DRIVER_240MV; break; case 260: priv->tx_drive_strength = PQSGMII_TX_DRIVER_260MV; break; case 280: priv->tx_drive_strength = PQSGMII_TX_DRIVER_280MV; break; case 300: priv->tx_drive_strength = PQSGMII_TX_DRIVER_300MV; break; case 320: priv->tx_drive_strength = PQSGMII_TX_DRIVER_320MV; break; case 400: priv->tx_drive_strength = PQSGMII_TX_DRIVER_400MV; break; case 500: priv->tx_drive_strength = PQSGMII_TX_DRIVER_500MV; break; case 600: priv->tx_drive_strength = PQSGMII_TX_DRIVER_600MV; break; default: return -EINVAL; } priv->package_mode = PHY_INTERFACE_MODE_NA; if (!of_property_read_string(shared->np, "qcom,package-mode", &package_mode_name)) { if (!strcasecmp(package_mode_name, phy_modes(PHY_INTERFACE_MODE_PSGMII))) priv->package_mode = PHY_INTERFACE_MODE_PSGMII; else if (!strcasecmp(package_mode_name, phy_modes(PHY_INTERFACE_MODE_QSGMII))) priv->package_mode = PHY_INTERFACE_MODE_QSGMII; else return -EINVAL; } return 0; } static int qca807x_phy_package_config_init_once(struct phy_device *phydev) { struct phy_package_shared *shared = phydev->shared; struct qca807x_shared_priv *priv = shared->priv; int val, ret; /* Make sure PHY follow PHY package mode if enforced */ if (priv->package_mode != PHY_INTERFACE_MODE_NA && phydev->interface != priv->package_mode) return -EINVAL; phy_lock_mdio_bus(phydev); /* Set correct PHY package mode */ val = __phy_package_read(phydev, QCA807X_COMBO_ADDR, QCA807X_CHIP_CONFIGURATION); val &= ~QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK; /* package_mode can be QSGMII or PSGMII and we validate * this in probe_once. * With package_mode to NA, we default to PSGMII. */ switch (priv->package_mode) { case PHY_INTERFACE_MODE_QSGMII: val |= QCA807X_CHIP_CONFIGURATION_MODE_QSGMII_SGMII; break; case PHY_INTERFACE_MODE_PSGMII: default: val |= QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_ALL_COPPER; } ret = __phy_package_write(phydev, QCA807X_COMBO_ADDR, QCA807X_CHIP_CONFIGURATION, val); if (ret) goto exit; /* After mode change Serdes reset is required */ val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR, PQSGMII_CTRL_REG); val &= ~PQSGMII_ANALOG_SW_RESET; ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR, PQSGMII_CTRL_REG, val); if (ret) goto exit; msleep(SERDES_RESET_SLEEP); val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR, PQSGMII_CTRL_REG); val |= PQSGMII_ANALOG_SW_RESET; ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR, PQSGMII_CTRL_REG, val); if (ret) goto exit; /* Workaround to enable AZ transmitting ability */ val = __phy_package_read_mmd(phydev, QCA807X_PQSGMII_ADDR, MDIO_MMD_PMAPMD, PQSGMII_MODE_CTRL); val &= ~PQSGMII_MODE_CTRL_AZ_WORKAROUND_MASK; ret = __phy_package_write_mmd(phydev, QCA807X_PQSGMII_ADDR, MDIO_MMD_PMAPMD, PQSGMII_MODE_CTRL, val); if (ret) goto exit; /* Set PQSGMII TX AMP strength */ val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR, PQSGMII_DRIVE_CONTROL_1); val &= ~PQSGMII_TX_DRIVER_MASK; val |= FIELD_PREP(PQSGMII_TX_DRIVER_MASK, priv->tx_drive_strength); ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR, PQSGMII_DRIVE_CONTROL_1, val); if (ret) goto exit; /* Prevent PSGMII going into hibernation via PSGMII self test */ val = __phy_package_read_mmd(phydev, QCA807X_COMBO_ADDR, MDIO_MMD_PCS, PQSGMII_MMD3_SERDES_CONTROL); val &= ~BIT(1); ret = __phy_package_write_mmd(phydev, QCA807X_COMBO_ADDR, MDIO_MMD_PCS, PQSGMII_MMD3_SERDES_CONTROL, val); exit: phy_unlock_mdio_bus(phydev); return ret; } static int qca807x_sfp_insert(void *upstream, const struct sfp_eeprom_id *id) { struct phy_device *phydev = upstream; __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, }; phy_interface_t iface; int ret; DECLARE_PHY_INTERFACE_MASK(interfaces); sfp_parse_support(phydev->sfp_bus, id, support, interfaces); iface = sfp_select_interface(phydev->sfp_bus, support); dev_info(&phydev->mdio.dev, "%s SFP module inserted\n", phy_modes(iface)); switch (iface) { case PHY_INTERFACE_MODE_1000BASEX: case PHY_INTERFACE_MODE_100BASEX: /* Set PHY mode to PSGMII combo (1/4 copper + combo ports) mode */ ret = phy_modify(phydev, QCA807X_CHIP_CONFIGURATION, QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK, QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_FIBER); /* Enable fiber mode autodection (1000Base-X or 100Base-FX) */ ret = phy_set_bits_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION, QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION_EN); /* Select fiber page */ ret = phy_clear_bits(phydev, QCA807X_CHIP_CONFIGURATION, QCA807X_BT_BX_REG_SEL); phydev->port = PORT_FIBRE; break; default: dev_err(&phydev->mdio.dev, "Incompatible SFP module inserted\n"); return -EINVAL; } return ret; } static void qca807x_sfp_remove(void *upstream) { struct phy_device *phydev = upstream; /* Select copper page */ phy_set_bits(phydev, QCA807X_CHIP_CONFIGURATION, QCA807X_BT_BX_REG_SEL); phydev->port = PORT_TP; } static const struct sfp_upstream_ops qca807x_sfp_ops = { .attach = phy_sfp_attach, .detach = phy_sfp_detach, .module_insert = qca807x_sfp_insert, .module_remove = qca807x_sfp_remove, }; static int qca807x_probe(struct phy_device *phydev) { struct device_node *node = phydev->mdio.dev.of_node; struct qca807x_shared_priv *shared_priv; struct device *dev = &phydev->mdio.dev; struct phy_package_shared *shared; struct qca807x_priv *priv; int ret; ret = devm_of_phy_package_join(dev, phydev, sizeof(*shared_priv)); if (ret) return ret; if (phy_package_probe_once(phydev)) { ret = qca807x_phy_package_probe_once(phydev); if (ret) return ret; } shared = phydev->shared; shared_priv = shared->priv; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dac_full_amplitude = of_property_read_bool(node, "qcom,dac-full-amplitude"); priv->dac_full_bias_current = of_property_read_bool(node, "qcom,dac-full-bias-current"); priv->dac_disable_bias_current_tweak = of_property_read_bool(node, "qcom,dac-disable-bias-current-tweak"); #if IS_ENABLED(CONFIG_GPIOLIB) /* Make sure we don't have mixed leds node and gpio-controller * to prevent registering leds and having gpio-controller usage * conflicting with them. */ if (of_find_property(node, "leds", NULL) && of_find_property(node, "gpio-controller", NULL)) { phydev_err(phydev, "Invalid property detected. LEDs and gpio-controller are mutually exclusive."); return -EINVAL; } /* Do not register a GPIO controller unless flagged for it */ if (of_property_read_bool(node, "gpio-controller")) { ret = qca807x_gpio(phydev); if (ret) return ret; } #endif /* Attach SFP bus on combo port*/ if (phy_read(phydev, QCA807X_CHIP_CONFIGURATION)) { ret = phy_sfp_probe(phydev, &qca807x_sfp_ops); if (ret) return ret; linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported); linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->advertising); } phydev->priv = priv; return 0; } static int qca807x_config_init(struct phy_device *phydev) { struct qca807x_priv *priv = phydev->priv; u16 control_dac; int ret; if (phy_package_init_once(phydev)) { ret = qca807x_phy_package_config_init_once(phydev); if (ret) return ret; } control_dac = phy_read_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH); control_dac &= ~QCA807X_CONTROL_DAC_MASK; if (!priv->dac_full_amplitude) control_dac |= QCA807X_CONTROL_DAC_DSP_AMPLITUDE; if (!priv->dac_full_amplitude) control_dac |= QCA807X_CONTROL_DAC_DSP_BIAS_CURRENT; if (!priv->dac_disable_bias_current_tweak) control_dac |= QCA807X_CONTROL_DAC_BIAS_CURRENT_TWEAK; return phy_write_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH, control_dac); } static struct phy_driver qca807x_drivers[] = { { PHY_ID_MATCH_EXACT(PHY_ID_QCA8072), .name = "Qualcomm QCA8072", .flags = PHY_POLL_CABLE_TEST, /* PHY_GBIT_FEATURES */ .probe = qca807x_probe, .config_init = qca807x_config_init, .read_status = qca807x_read_status, .config_intr = at803x_config_intr, .handle_interrupt = at803x_handle_interrupt, .soft_reset = genphy_soft_reset, .get_tunable = at803x_get_tunable, .set_tunable = at803x_set_tunable, .resume = genphy_resume, .suspend = genphy_suspend, .cable_test_start = qca807x_cable_test_start, .cable_test_get_status = qca808x_cable_test_get_status, }, { PHY_ID_MATCH_EXACT(PHY_ID_QCA8075), .name = "Qualcomm QCA8075", .flags = PHY_POLL_CABLE_TEST, /* PHY_GBIT_FEATURES */ .probe = qca807x_probe, .config_init = qca807x_config_init, .read_status = qca807x_read_status, .config_intr = at803x_config_intr, .handle_interrupt = at803x_handle_interrupt, .soft_reset = genphy_soft_reset, .get_tunable = at803x_get_tunable, .set_tunable = at803x_set_tunable, .resume = genphy_resume, .suspend = genphy_suspend, .cable_test_start = qca807x_cable_test_start, .cable_test_get_status = qca808x_cable_test_get_status, .led_brightness_set = qca807x_led_brightness_set, .led_blink_set = qca807x_led_blink_set, .led_hw_is_supported = qca807x_led_hw_is_supported, .led_hw_control_set = qca807x_led_hw_control_set, .led_hw_control_get = qca807x_led_hw_control_get, }, }; module_phy_driver(qca807x_drivers); static struct mdio_device_id __maybe_unused qca807x_tbl[] = { { PHY_ID_MATCH_EXACT(PHY_ID_QCA8072) }, { PHY_ID_MATCH_EXACT(PHY_ID_QCA8075) }, { } }; MODULE_AUTHOR("Robert Marko "); MODULE_AUTHOR("Christian Marangi "); MODULE_DESCRIPTION("Qualcomm QCA807x PHY driver"); MODULE_DEVICE_TABLE(mdio, qca807x_tbl); MODULE_LICENSE("GPL");