summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/net/phy/Kconfig7
-rw-r--r--drivers/net/phy/marvell-88x2222.c291
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;
}