summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerge Semin <fancer.lancer@gmail.com>2023-07-28 12:57:58 +0300
committerSerge Semin <fancer.lancer@gmail.com>2026-08-13 20:38:19 +0300
commit3b1f813848445bd66116f8b3af2c0c94e70788f5 (patch)
tree9e7f284b4ae87db611ceadc6d2566e830191d385
parent1235ead764fbc95ae89c5c2f95783c654e3d2603 (diff)
downloadlinux-xgmac/ext2/mv.tar.gz
linux-xgmac/ext2/mv.zip
net: phy: marvell-88x2222: Add I2C/SFP+ supportxgmac/ext2/mv
Marvell 88x2222 PHY is equipped with a basic TWSI/SMBus controller which SDA/SCL pins are multiplexed with GPIO pins 10 and 11. The main purpose of the interface is to access the externally attached modules EEPROM (for that sake there is even an internal cache which if enabled can be filled with the EEPROM data) but in general it can be used to access any I2C peripheral device as long as it supports the SMBus transfers the I2C-controller provides. Alas the SMBus controller functionality is very limited. It's just SMBus byte data read and write transfers, which should be enough for the EEPROM IO operations but won't provide a full support of the SFP+ equipments like MDIO-I2C-based PHY or thermal sensors. Anyway if a respective platform is already designed to rely on the Marvell 88x2222 I2C-interface it will be handy to have the interface registered in kernel so the SFP-port driver would at least access the module EEPROM. The provided driver first probes whether the I2C-interface functionality is available and supported by the current platform setup. That is it makes sure that there is no GPIO-function activated on the SDA/SCL pins and there is no device hard-reset capability. Then it pre-initializes the I2C-interface: disables the EEPROM caching and Read-after-write functionality. After that the I2C-adapter is ready to be registered and utilized then by the client drivers (mainly by the SFP-port driver) for the basic SMBus Byte Data Read/Write operations. Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
-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;
}