+ dio_reg |= NCN26000_DIO_CFG_VAL1;
+ else
+ dio_reg &= ~NCN26000_DIO_CFG_VAL1;
+ break;
+
+ default:
+ dev_err(priv->dev, "invalid GPIO offset: %d\n", offset);
+ return;
+ }
+
+ phy_write(priv->phydev, NCN26000_REG_DIO_CONFIG, dio_reg);
+}
+
+static int ncn26000_gpio_get_dir(struct gpio_chip *gc, unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int ncn26000_gpio_dir_out(struct gpio_chip *gc, unsigned int offset,
+ int val)
+{
+ ncn26000_gpio_set(gc, offset, val);
+ return 0;
+}
+
+static int ncn26000_gpio_setup(struct ncn26000_priv *priv)
+{
+ struct gpio_chip *gc = &priv->gpio_chip;
+
+ gc->request = ncn26000_gpio_request;
+ gc->get_direction = ncn26000_gpio_get_dir;
+ gc->direction_output = ncn26000_gpio_dir_out;
+ gc->set = ncn26000_gpio_set;
+ gc->label = "ncn26000-gpio";
+ gc->base = -1;
+ gc->ngpio = 2;
+ gc->parent = priv->dev;
+ gc->owner = THIS_MODULE;
+
+ return devm_gpiochip_add_data(priv->dev, gc, priv);
+}
+
+static void ncn26000_tweaks_config(struct ncn26000_priv *priv)
+{
+ u16 val = NCN26000_TWEAKS_CD_THRESH(NCN26000_CD_THRESH_DEFAULT) |
+ NCN26000_TWEAKS_ED_THRESH(NCN26000_ED_THRESH_DEFAULT) |
+ NCN26000_DIG_SLEW_FAST;
+
+ struct device *dev = &priv->phydev->mdio.dev;
+ const char *s;
+ int ret;
+
+ ret = device_property_read_string(dev, "tx-gain", &s);
+ if (!ret) {
+ dev_info(dev, "tx-gain = %s\n", s);
+ if (strcmp(s, "1v1") == 0)
+ val |= NCN26000_TWEAKS_TX_GAIN(NCN26000_TX_GAIN_1V1);
+ else if (strcmp(s, "0v9") == 0)
+ val |= NCN26000_TWEAKS_TX_GAIN(NCN26000_TX_GAIN_0V9);
+ else if (strcmp(s, "0v8") == 0)
+ val |= NCN26000_TWEAKS_TX_GAIN(NCN26000_TX_GAIN_0V8);
+ else if (strcmp(s, "1v0") != 0)
+ dev_err(dev, "tx-gain is invalid: %s\n", s);
+ }
+
+ ret = device_property_read_string(dev, "tx-slew", &s);
+ if (!ret) {
+ dev_info(dev, "tx-slew = %s\n", s);
+ if (strcmp(s, "fast") == 0)
+ val |= NCN26000_TX_SLEW_FAST;
+ else if (strcmp(s, "slow") != 0)
+ dev_err(dev, "tx-slew is invalid: %s\n", s);
+ }
+
+ ret = device_property_read_string(dev, "dig-slew", &s);
+ if (!ret) {
+ dev_info(dev, "dig-slew = %s\n", s);
+ if (strcmp(s, "slow") == 0)
+ val &= ~NCN26000_DIG_SLEW_FAST;
+ else if (strcmp(s, "fast") != 0)
+ dev_err(dev, "dig-slew is invalid: %s\n", s);
+ }
+
+ ret = device_property_read_string(dev, "cmc-comp", &s);
+ if (!ret) {
+ dev_info(dev, "cmc-comp = %s\n", s);
+ if (strcmp(s, "1p37") == 0)
+ val |= NCN26000_TWEAKS_CMC_COMP(NCN26000_CMC_COMP_1P37);
+ else if (strcmp(s, "3p00") == 0)
+ val |= NCN26000_TWEAKS_CMC_COMP(NCN26000_CMC_COMP_3P00);
+ else if (strcmp(s, "4p37") == 0)
+ val |= NCN26000_TWEAKS_CMC_COMP(NCN26000_CMC_COMP_4P37);
+ else if (strcmp(s, "0p25") != 0)
+ dev_err(dev, "cmc-comp is invalid: %s\n", s);
+ }
+
+ priv->tweaks = val;
+}
+
+static void ncn26000_plcaext_config(struct ncn26000_priv *priv)
+{
+ u16 val = NCN26000_PLCAEXT_MIIEXTDIS;
+
+ if (device_property_read_bool(priv->dev, "plca-precedence")) {
+ dev_info(priv->dev, "PLCA precedence mode enabled\n");
+ val |= NCN26000_PLCAEXT_PRECEDENCE;
+ }
+
+ priv->plcaext = val;
+}
+
+static void ncn26000_phycfg0_config(struct ncn26000_priv *priv)
+{
+ u16 val = NCN26000_PHYCFG1_RX_DLY | NCN26000_PHYCFG1_NO_COL_MASK |
+ NCN26000_PHYCFG1_UNJAB_TMR;
+
+ struct device *dev = &priv->phydev->mdio.dev;
+ const char *s;
+ int ret;
+
+ priv->eni_mode = ENI_AUTO;
+ ret = device_property_read_string(dev, "eni-mode", &s);
+ if (!ret) {
+ dev_info(dev, "eni-mode = %s\n", s);
+ if (strcmp(s, "force-on") == 0) {
+ priv->eni_mode = ENI_FORCE_ON;
+ val |= NCN26000_PHYCFG1_ENI;
+ } else if (strcmp(s, "force-off") == 0) {
+ priv->eni_mode = ENI_FORCE_OFF;
+ } else if (strcmp(s, "auto") != 0) {
+ dev_err(dev, "eni-mode is invalid: %s\n", s);
+ }
+ }
+
+ if (device_property_read_bool(priv->dev, "tx-pkt-loop")) {
+ dev_info(priv->dev, "TX packet loop enabled\n");
+ val |= NCN26000_PHYCFG1_PKT_LOOP;
+ }
+
+ if (device_property_read_bool(priv->dev, "unjab-tmr-disable")) {
+ dev_info(priv->dev, "unjab timer disabled\n");
+ val &= ~NCN26000_PHYCFG1_UNJAB_TMR;
+ }
+
+ if (device_property_read_bool(priv->dev, "col-disable")) {
+ dev_info(priv->dev, "ENI collision detect disabled\n");
+ val &= ~NCN26000_PHYCFG1_NO_COL_MASK;
+ }
+
+ if (device_property_read_bool(priv->dev, "no-rx-delay")) {
+ dev_info(priv->dev, "RX delay disabled\n");
+ val &= ~NCN26000_PHYCFG1_RX_DLY;
+ }
+
+ priv->phycfg0 = val;
+}
+
+static void ncn26000_dio_config(struct ncn26000_priv *priv)
+{
+ struct device *dev = &priv->phydev->mdio.dev;
+ const char *s;
+ u16 val = 0;
+ int ret;
+
+ ret = device_property_read_string(dev, "dio0-fn", &s);
+ if (!ret) {
+ dev_info(dev, "dio0-fn = %s\n", s);
+ if (strcmp(s, "sfd-tx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_SFD_TX);
+ else if (strcmp(s, "sfd-rx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_SFD_RX);
+ else if (strcmp(s, "sfd-rxtx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_SFD_RXTX);
+ else if (strcmp(s, "led-link") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_LED_LINK);
+ else if (strcmp(s, "led-plca") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_LED_PLCA);
+ else if (strcmp(s, "led-tx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_LED_TX);
+ else if (strcmp(s, "led-rx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_LED_RX);
+ else if (strcmp(s, "led-rxtx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_LED_RXTX);
+ else if (strcmp(s, "clk25m") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_CLK25M);
+ else
+ dev_err(dev, "dio0-fn is invalid: %s\n", s);
+ }
+
+ if (device_property_read_bool(dev, "dio0-pullup"))
+ val |= NCN26000_DIO_CFG_PULL_EN0;
+
+ if (device_property_read_bool(dev, "dio0-active-high"))
+ val |= NCN26000_DIO_CFG_VAL0;
+
+ ret = device_property_read_string(dev, "dio0-slew", &s);
+ if (!ret) {
+ dev_info(dev, "dio0-slew = %s\n", s);
+ if (strcmp(s, "slow") == 0)
+ val |= NCN26000_DIO_CFG_SLEW_RATE0;
+ else if (strcmp(s, "fast") != 0)
+ dev_err(dev, "dio0-slew is invalid: %s\n", s);
+ }
+
+ ret = device_property_read_string(dev, "dio1-fn", &s);
+ if (!ret) {
+ dev_info(dev, "dio1-fn = %s\n", s);
+ if (strcmp(s, "sfd-tx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_SFD_TX);
+ else if (strcmp(s, "sfd-rx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_SFD_RX);
+ else if (strcmp(s, "sfd-rxtx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_SFD_RXTX);
+ else if (strcmp(s, "led-link") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_LED_LINK);
+ else if (strcmp(s, "led-plca") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_LED_PLCA);
+ else if (strcmp(s, "led-tx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_LED_TX);
+ else if (strcmp(s, "led-rx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_LED_RX);
+ else if (strcmp(s, "led-rxtx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_LED_RXTX);
+ else if (strcmp(s, "clk25m") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_CLK25M);
+ else
+ dev_err(dev, "dio1-fn is invalid: %s\n", s);
+ }
+
+ if (device_property_read_bool(dev, "dio1-pullup"))
+ val |= NCN26000_DIO_CFG_PULL_EN1;
+
+ if (device_property_read_bool(dev, "dio1-active-high"))
+ val |= NCN26000_DIO_CFG_VAL1;
+
+ ret = device_property_read_string(dev, "dio1-slew", &s);
+ if (!ret) {
+ dev_info(dev, "dio1-slew = %s\n", s);
+ if (strcmp(s, "slow") == 0)
+ val |= NCN26000_DIO_CFG_SLEW_RATE1;
+ else if (strcmp(s, "fast") != 0)
+ dev_err(dev, "dio1-slew is invalid: %s\n", s);
+ }
+
+ priv->diocfg = val;
+}
static int ncn26000_config_init(struct phy_device *phydev)
{
+ struct ncn26000_priv *priv = phydev->priv;
+ int ret;
+
+ // configure the DIOx function
+ ret = phy_write(phydev, NCN26000_REG_DIO_CONFIG, priv->diocfg);
+ if (ret)
+ goto out;
+
+ // configure proprietary PHY features
+ ret = phy_write_mmd(phydev, NCN26000_REG_PHYCFG1_MMD,
+ NCN26000_REG_PHYCFG1, priv->phycfg0);
+ if (ret)
+ goto out;
+
+ // configure the PLCA extensions
+ ret = phy_write_mmd(phydev, NCN26000_REG_PLCAEXT_MMD,
+ NCN26000_REG_PLCAEXT, priv->plcaext);
+ if (ret)
+ goto out;
+
+ // configure additional PHY tweaks
+ ret = phy_write_mmd(phydev, NCN26000_REG_TWEAKS_MMD,
+ NCN26000_REG_TWEAKS, priv->tweaks);
+ if (ret)
+ goto out;
+
/* HW bug workaround: the default value of the PLCA TO_TIMER should be
* 32, where the current version of NCN26000 reports 24. This will be
* fixed in future PHY versions. For the time being, we force the
* correct default here.
*/
- return phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR,
- TO_TMR_DEFAULT);
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR,
+ TO_TMR_DEFAULT);
+
+out:
+ return ret;
}
static int ncn26000_config_aneg(struct phy_device *phydev)
@@ -100,6 +490,36 @@ static int ncn26000_read_status(struct phy_device *phydev)
return 0;
}
+static int ncn26000_plca_set_cfg(struct phy_device *phydev,
+ const struct phy_plca_cfg *plca_cfg)
+{
+ struct ncn26000_priv *priv = phydev->priv;
+ int ret;
+
+ /* We capture calls to PLCA set config to intercept PLCA enable/disable
+ * requests and set the proprietary ENI mode accordingly
+ */
+ ret = genphy_c45_plca_set_cfg(phydev, plca_cfg);
+ if (ret || plca_cfg->enabled < 0 || priv->eni_mode != ENI_AUTO)
+ goto out;
+
+ ret = phy_read_mmd(phydev, NCN26000_REG_PHYCFG1_MMD,
+ NCN26000_REG_PHYCFG1);
+
+ if (ret < 0)
+ goto out;
+
+ if (plca_cfg->enabled)
+ ret |= NCN26000_PHYCFG1_ENI;
+ else
+ ret &= ~NCN26000_PHYCFG1_ENI;
+
+ ret = phy_write_mmd(phydev, NCN26000_REG_PHYCFG1_MMD,
+ NCN26000_REG_PHYCFG1, ret);
+out:
+ return ret;
+}
+
static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
{
int ret;
@@ -140,20 +560,51 @@ static int ncn26000_config_intr(struct phy_device *phydev)
return 0;
}
+static int ncn26000_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct ncn26000_priv *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->phydev = phydev;
+ phydev->priv = priv;
+
+ ncn26000_dio_config(priv);
+ if (priv->gpiomask)
+ ncn26000_gpio_setup(priv);
+
+ ncn26000_phycfg0_config(priv);
+ ncn26000_plcaext_config(priv);
+ ncn26000_tweaks_config(priv);
+
+ return 0;
+}
+
+static void ncn26000_remove(struct phy_device *phydev)
+{
+ kfree(phydev->priv);
+}
+
static struct phy_driver ncn26000_driver[] = {
{
PHY_ID_MATCH_MODEL(PHY_ID_NCN26000),
.name = "NCN26000",
.features = PHY_BASIC_T1S_P2MP_FEATURES,
- .config_init = ncn26000_config_init,
- .config_intr = ncn26000_config_intr,
+ .probe = ncn26000_probe,
+ .remove = ncn26000_remove,
+ .config_init = ncn26000_config_init,
+ .config_intr = ncn26000_config_intr,
.config_aneg = ncn26000_config_aneg,
.read_status = ncn26000_read_status,
- .handle_interrupt = ncn26000_handle_interrupt,
+ .handle_interrupt = ncn26000_handle_interrupt,
+ .set_plca_cfg = ncn26000_plca_set_cfg,
.get_plca_cfg = genphy_c45_plca_get_cfg,
- .set_plca_cfg = genphy_c45_plca_set_cfg,
.get_plca_status = genphy_c45_plca_get_status,
- .soft_reset = genphy_soft_reset,
+ .soft_reset = genphy_soft_reset,
},
};