diff options
| -rw-r--r-- | drivers/net/phy/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/net/phy/marvell-88x2222.c | 291 |
2 files changed, 298 insertions, 0 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index d1f578955b7f..a8272a7c869f 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -284,6 +284,13 @@ config MARVELL_88X2222_GPIO help Support for the Marvell 88X2222 PHY SFP+/GPIO signals. +config MARVELL_88X2222_I2C + bool "Marvell 88X2222 SFP+ I2C support" + depends on MARVELL_88X2222_PHY + depends on I2C + help + Support for the Marvell 88X2222 PHY SFP+/I2C signals. + config MAXLINEAR_GPHY tristate "Maxlinear Ethernet PHYs" select POLYNOMIAL if HWMON diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 46807891a253..f8fd5c71f491 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -9,6 +9,7 @@ */ #include <linux/bitfield.h> #include <linux/bits.h> +#include <linux/i2c.h> #include <linux/irq.h> #include <linux/kernel.h> #include <linux/module.h> @@ -121,6 +122,76 @@ /* 1000Base-X Auto-Negotiation Advertisement Register */ #define MV_1GBX_ADVERTISE (0x2000 + MII_ADVERTISE) +/* Two Wire Interface Caching Control/Status Register */ +#define MV_TWSI_CACHE_CTRL 0x8000 +#define MV_TWSI_CACHE_A0_CTRL GENMASK(1, 0) +#define MV_TWSI_CACHE_NO_AUTO 0 +#define MV_TWSI_CACHE_AT_PLUGIN 1 +#define MV_TWSI_CACHE_AT_PLUGIN_POLL 2 +#define MV_TWSI_CACHE_MANUAL 3 +#define MV_TWSI_CACHE_A0_CMD_STAT GENMASK(3, 2) +#define MV_TWSI_CACHE_NO_UPDATE 0 +#define MV_TWSI_CACHE_UPDATED_ONCE 1 +#define MV_TWSI_CACHE_IS_LOADING 2 +#define MV_TWSI_CACHE_FAILED 3 +#define MV_TWSI_CACHE_A0_VALID BIT(9) +#define MV_TWSI_RESET BIT(10) +#define MV_TWSI_CACHE_A2_CTRL GENMASK(12, 11) +#define MV_TWSI_CACHE_A2_CMD_STAT GENMASK(14, 13) +#define MV_TWSI_CACHE_A2_VALID BIT(15) + +/* Two Wire Interface Memory Address Register */ +#define MV_TWSI_MEM_ADDR 0x8001 +#define MV_TWSI_BYTE_ADDR GENMASK(7, 0) +#define MV_TWSI_BYTE_READ BIT(8) +#define MV_TWSI_SLV_ADDR GENMASK(15, 9) + +/* Two Wire Interface Memory Read Data and Status Register */ +#define MV_TWSI_MEM_READ_STAT 0x8002 +#define MV_TWSI_MEM_READ_DATA GENMASK(7, 0) +#define MV_TWSI_STAT GENMASK(10, 8) +#define MV_TWSI_READY 0 +#define MV_TWSI_CMD_DONE 1 +#define MV_TWSI_CMD_IN_PROG 2 +#define MV_TWSI_CMD_WDONE_RFAIL 3 +#define MV_TWSI_CMD_FAIL 5 +#define MV_TWSI_BUSY 7 +#define MV_TWSI_CACHE_ECC_UNCOR_ERR BIT(11) +#define MV_TWSI_CACHE_ECC_COR_ERR BIT(12) + +/* Two Wire Interface Memory Write Data and Control Register */ +#define MV_TWSI_MEM_WRITE_CTRL 0x8003 +#define MV_TWSI_MEM_WRITE_DATA GENMASK(7, 0) +#define MV_TWSI_MEM_AUTO_RBACK BIT(9) +#define MV_TWSI_MEM_WRITE_TIME GENMASK(15, 12) + +/* Two Wire Interface Caching Delay Register */ +#define MV_TWSI_CACHE_DELAY 0x8004 +#define MV_TWSI_CACHE_A2_ADDR_MSB BIT(0) +#define MV_TWSI_CACHE_A2_SLV_ADDR GENMASK(7, 1) +#define MV_TWSI_CACHE_RELOAD_FREQ GENMASK(10, 9) +#define MV_TWSI_CACHE_RELOAD_250MS 0 +#define MV_TWSI_CACHE_RELOAD_500MS 1 +#define MV_TWSI_CACHE_RELOAD_1S 2 +#define MV_TWSI_CACHE_RELOAD_2S 3 +#define MV_TWSI_CACHE_ECC_UNCOR_INT_EN BIT(11) +#define MV_TWSI_CACHE_ECC_COR_INT_EN BIT(12) +#define MV_TWSI_CACHE_AUTO_DELAY GENMASK(15, 13) +#define MV_TWSI_CACHE_NO_DELAY 0 +#define MV_TWSI_CACHE_DELAY_250MS 1 +#define MV_TWSI_CACHE_DELAY_500MS 2 +#define MV_TWSI_CACHE_DELAY_1S 3 +#define MV_TWSI_CACHE_DELAY_2S 4 +#define MV_TWSI_CACHE_DELAY_4S 5 +#define MV_TWSI_CACHE_DELAY_8S 6 +#define MV_TWSI_CACHE_AUTO_DIS 7 + +/* Two Wire Interface EEPROM Cache Page A0 Registers */ +#define MV_TWSI_CACHE_EEPROM_A0 0x8007 + +/* Two Wire Interface EEPROM Cache Page A2 Registers */ +#define MV_TWSI_CACHE_EEPROM_A2 0x8087 + /* 10GBase-R Interrupt Enable Register */ #define MV_10GBR_INT_EN 0x8000 #define MV_10GBR_INT_BLOCK_LOCK BIT(0) @@ -216,6 +287,9 @@ #define MV_GPIO_VAL_MASK 0xDFF #define MV_GPIO_INT_UPD_FLAG BIT(31) +/* I2C controller settings */ +#define MV_I2C_POLL_NUM 3 + #define AUTONEG_TIMEOUT 3 struct mv2222_data { @@ -238,6 +312,13 @@ struct mv2222_gpio { }; #endif +#ifdef CONFIG_MARVELL_88X2222_I2C +struct mv2222_i2c { + struct i2c_adapter ia; + struct phy_device *phydev; +}; +#endif + /* SFI PMA transmit enable */ static int mv2222_tx_enable(struct phy_device *phydev) { @@ -1583,6 +1664,212 @@ static int mv2222_gpio_probe(struct phy_device *phydev) } #endif /* !CONFIG_MARVELL_88X2222_GPIO */ +#ifdef CONFIG_MARVELL_88X2222_I2C +static int mv2222_i2c_init(struct mv2222_i2c *i2c) +{ + struct i2c_timings t; + u16 val; + int ret; + + /* The I2C bus is available if the SDA/SCL pins function isn't GPIO */ + ret = phy_read_mmd(i2c->phydev, MDIO_MMD_VEND2, MV_GPIO_INT_TYPE3); + if (ret < 0) + return ret; + + if (ret & MV_GPIO_FUNC_SDA_PIN10 || ret & MV_GPIO_FUNC_SCL_PIN11) + return -EBUSY; + + /* Make sure the only supported bus speed is specified */ + i2c_parse_fw_timings(&i2c->phydev->mdio.dev, &t, true); + if (t.bus_freq_hz != I2C_MAX_STANDARD_MODE_FREQ) + return -EINVAL; + + /* Disable the EEPROM caching. It will interfere the I2C-adapter work */ + val = FIELD_PREP(MV_TWSI_CACHE_A0_CTRL, MV_TWSI_CACHE_NO_AUTO) | + FIELD_PREP(MV_TWSI_CACHE_A2_CTRL, MV_TWSI_CACHE_NO_AUTO); + + ret = phy_write_mmd(i2c->phydev, MDIO_MMD_PMAPMD, MV_TWSI_CACHE_CTRL, val); + if (ret < 0) + return ret; + + /* Disable the Read-back-after-write functionality */ + ret = phy_write_mmd(i2c->phydev, MDIO_MMD_PMAPMD, MV_TWSI_MEM_WRITE_CTRL, 0); + if (ret < 0) + return ret; + + /* Once again disable the auto-caching */ + val = FIELD_PREP(MV_TWSI_CACHE_AUTO_DELAY, MV_TWSI_CACHE_AUTO_DIS); + + ret = phy_write_mmd(i2c->phydev, MDIO_MMD_PMAPMD, MV_TWSI_CACHE_DELAY, val); + if (ret < 0) + return ret; + + return 0; +} + +static inline unsigned long mv2222_i2c_get_udelay(char read_write) +{ + const unsigned long p = USEC_PER_SEC / I2C_MAX_STANDARD_MODE_FREQ; + unsigned long ud; + + /* S, addr, Ack, cmd, Ack, data, Ack/Nack and P bits together */ + ud = 29 * p; + + /* Additional Sr, addr and Ack bits in case of the read op */ + if (read_write == I2C_SMBUS_READ) + ud += 10 * p; + + return ud; +} + +static inline bool mv2222_i2c_ready(int val) +{ + if (FIELD_GET(MV_TWSI_STAT, val) == MV_TWSI_READY) + return true; + + return false; +} + +static inline bool mv2222_i2c_cmd_done(int val) +{ + if (FIELD_GET(MV_TWSI_STAT, val) != MV_TWSI_CMD_IN_PROG) + return true; + + return false; +} + +static int mv2222_i2c_xfer(struct i2c_adapter *ia, + u16 addr, unsigned short flags, char read_write, + u8 cmd, int size, union i2c_smbus_data *data) +{ + struct mv2222_i2c *i2c = ia->algo_data; + unsigned long ud; + int val, ret; + + if (flags & I2C_CLIENT_TEN || size != I2C_SMBUS_BYTE_DATA) + return -EOPNOTSUPP; + + /* Make sure the interface is ready to execute the command */ + ud = mv2222_i2c_get_udelay(I2C_SMBUS_READ); + + ret = phy_read_mmd_poll_timeout(i2c->phydev, MDIO_MMD_PMAPMD, + MV_TWSI_MEM_READ_STAT, val, + mv2222_i2c_ready(val), + ud, MV_I2C_POLL_NUM * ud, false); + if (ret < 0) + return ret; + + /* Collect the command parameters */ + if (read_write == I2C_SMBUS_WRITE) { + ret = phy_write_mmd(i2c->phydev, MDIO_MMD_PMAPMD, + MV_TWSI_MEM_WRITE_CTRL, data->byte); + if (ret < 0) + return ret; + + val = 0; + } else { + val = MV_TWSI_BYTE_READ; + } + + val |= FIELD_PREP(MV_TWSI_BYTE_ADDR, cmd) | + FIELD_PREP(MV_TWSI_SLV_ADDR, addr); + + ret = phy_write_mmd(i2c->phydev, MDIO_MMD_PMAPMD, MV_TWSI_MEM_ADDR, val); + if (ret < 0) + return ret; + + /* Issue the command and wait until the CMD in-progress status is set */ + ud = mv2222_i2c_get_udelay(read_write); + + ret = phy_read_mmd_poll_timeout(i2c->phydev, MDIO_MMD_PMAPMD, + MV_TWSI_MEM_READ_STAT, val, + mv2222_i2c_cmd_done(val), + ud, MV_I2C_POLL_NUM * ud, true); + if (ret < 0) + return ret; + + /* The interface may respond with the READY status on the early stage + * of the SFP-module attachement. In that case ask to try again. + */ + switch (FIELD_GET(MV_TWSI_STAT, val)) { + case MV_TWSI_CMD_FAIL: + return -EIO; + case MV_TWSI_BUSY: + return -ENXIO; + case MV_TWSI_READY: + return -EAGAIN; + } + + if (read_write == I2C_SMBUS_READ) + data->byte = FIELD_GET(MV_TWSI_MEM_READ_DATA, val); + + return 0; +} + +static u32 mv2222_i2c_func(struct i2c_adapter *ia) +{ + return I2C_FUNC_SMBUS_BYTE_DATA; +} + +static struct i2c_algorithm mv2222_i2c_algo = { + .smbus_xfer = mv2222_i2c_xfer, + .functionality = mv2222_i2c_func, +}; + +static int mv2222_i2c_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct mv2222_i2c *i2c; + struct i2c_adapter *ia; + int ret; + + /* + * Marvell 88x2222 I2C CSRs are tolerant to the soft-resets. Alas that + * can be said about the hard-resets. It will halt any communications + * with possibly no error reported. So it isn't safe to use the + * interface if a sudden hard-reset might be performed. + */ + if (phydev->mdio.reset_gpio || phydev->mdio.reset_ctrl) { + phydev_warn(phydev, "Hard-reset detected, I2C unsupported\n"); + return 0; + } + + i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + i2c->phydev = phydev; + + /* Make sure the I2C-interface is ready before to proceed */ + ret = mv2222_i2c_init(i2c); + if (ret == -EBUSY) { + phydev_warn(phydev, "I2C interface unavailable\n"); + return 0; + } else if (ret) { + phydev_err(phydev, "Failed to init I2C-interface\n"); + return ret; + } + + ia = &i2c->ia; + strscpy(ia->name, "mv88x2222", sizeof(ia->name)); + ia->dev.parent = dev; + ia->owner = THIS_MODULE; + ia->algo = &mv2222_i2c_algo; + ia->algo_data = i2c; + + ret = devm_i2c_add_adapter(dev, ia); + if (ret) + phydev_err(phydev, "Failed to register I2C adapter\n"); + + return ret; +} +#else +static int mv2222_i2c_probe(struct phy_device *phydev) +{ + return 0; +} +#endif /* !CONFIG_MARVELL_88X2222_I2C */ + static int mv2222_configure_serdes(struct phy_port *port, bool enable, phy_interface_t interface) { @@ -1691,6 +1978,10 @@ static int mv2222_probe(struct phy_device *phydev) if (ret) return ret; + ret = mv2222_i2c_probe(phydev); + if (ret) + return ret; + return 0; } |
