From d585c55b9f70cf9e8c66820d7efe7130c683f19e Mon Sep 17 00:00:00 2001
From: Antoine Tenart <antoine.tenart@bootlin.com>
Date: Fri, 21 Feb 2020 11:51:27 +0100
Subject: [PATCH 2/3] net: phy: add an MDIO SMBus library

Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com>
---
 drivers/net/mdio/Kconfig      | 11 +++++++
 drivers/net/mdio/Makefile     |  1 +
 drivers/net/mdio/mdio-smbus.c | 62 +++++++++++++++++++++++++++++++++++
 drivers/net/phy/Kconfig       |  1 +
 include/linux/mdio/mdio-i2c.h | 16 +++++++++
 5 files changed, 91 insertions(+)
 create mode 100644 drivers/net/mdio/mdio-smbus.c

--- a/drivers/net/mdio/Kconfig
+++ b/drivers/net/mdio/Kconfig
@@ -54,6 +54,17 @@ config MDIO_SUN4I
 	  interface units of the Allwinner SoC that have an EMAC (A10,
 	  A12, A10s, etc.)
 
+config MDIO_SMBUS
+	tristate
+	depends on I2C_SMBUS
+	help
+	  Support SMBus based PHYs. This provides a MDIO bus bridged
+	  to SMBus to allow PHYs connected in SMBus mode to be accessed
+	  using the existing infrastructure.
+
+	  This is library mode.
+
+
 config MDIO_XGENE
 	tristate "APM X-Gene SoC MDIO bus controller"
 	depends on ARCH_XGENE || COMPILE_TEST
--- a/drivers/net/mdio/Makefile
+++ b/drivers/net/mdio/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_MDIO_MSCC_MIIM)		+= mdio-ms
 obj-$(CONFIG_MDIO_MVUSB)		+= mdio-mvusb.o
 obj-$(CONFIG_MDIO_OCTEON)		+= mdio-octeon.o
 obj-$(CONFIG_MDIO_REGMAP)		+= mdio-regmap.o
+obj-$(CONFIG_MDIO_SMBUS)		+= mdio-smbus.o
 obj-$(CONFIG_MDIO_SUN4I)		+= mdio-sun4i.o
 obj-$(CONFIG_MDIO_THUNDER)		+= mdio-thunder.o
 obj-$(CONFIG_MDIO_XGENE)		+= mdio-xgene.o
--- /dev/null
+++ b/drivers/net/mdio/mdio-smbus.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MDIO SMBus bridge
+ *
+ * Copyright (C) 2020 Antoine Tenart
+ *
+ * Network PHYs can appear on SMBus when they are part of SFP modules.
+ */
+#include <linux/i2c.h>
+#include <linux/phy.h>
+#include <linux/mdio/mdio-i2c.h>
+
+static int smbus_mii_read(struct mii_bus *mii, int phy_id, int reg)
+{
+	struct i2c_adapter *i2c = mii->priv;
+	union i2c_smbus_data data;
+	int ret;
+
+	ret = i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0, I2C_SMBUS_READ,
+			     reg, I2C_SMBUS_BYTE_DATA, &data);
+	if (ret < 0)
+		return 0xff;
+
+	return data.byte;
+}
+
+static int smbus_mii_write(struct mii_bus *mii, int phy_id, int reg, u16 val)
+{
+	struct i2c_adapter *i2c = mii->priv;
+	union i2c_smbus_data data;
+	int ret;
+
+	data.byte = val;
+
+	ret = i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0, I2C_SMBUS_WRITE,
+			     reg, I2C_SMBUS_BYTE_DATA, &data);
+	return ret < 0 ? ret : 0;
+}
+
+struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c)
+{
+	struct mii_bus *mii;
+
+	if (!i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA))
+		return ERR_PTR(-EINVAL);
+
+	mii = mdiobus_alloc();
+	if (!mii)
+		return ERR_PTR(-ENOMEM);
+
+	snprintf(mii->id, MII_BUS_ID_SIZE, "smbus:%s", dev_name(parent));
+	mii->parent = parent;
+	mii->read = smbus_mii_read;
+	mii->write = smbus_mii_write;
+	mii->priv = i2c;
+
+	return mii;
+}
+
+MODULE_AUTHOR("Antoine Tenart");
+MODULE_DESCRIPTION("MDIO SMBus bridge library");
+MODULE_LICENSE("GPL");
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -65,6 +65,7 @@ config SFP
 	depends on I2C && PHYLINK
 	depends on HWMON || HWMON=n
 	select MDIO_I2C
+	select MDIO_SMBUS
 
 comment "Switch configuration API + drivers"
 
--- a/include/linux/mdio/mdio-i2c.h
+++ b/include/linux/mdio/mdio-i2c.h
@@ -20,5 +20,8 @@ enum mdio_i2c_proto {
 
 struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
 			       enum mdio_i2c_proto protocol);
+struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c);
+bool i2c_mii_valid_phy_id(int phy_id);
+unsigned int i2c_mii_phy_addr(int phy_id);
 
 #endif
--- a/drivers/net/mdio/mdio-i2c.c
+++ b/drivers/net/mdio/mdio-i2c.c
@@ -20,12 +20,12 @@
  * specified to be present in SFP modules.  These correspond with PHY
  * addresses 16 and 17.  Disallow access to these "phy" addresses.
  */
-static bool i2c_mii_valid_phy_id(int phy_id)
+bool i2c_mii_valid_phy_id(int phy_id)
 {
 	return phy_id != 0x10 && phy_id != 0x11;
 }
 
-static unsigned int i2c_mii_phy_addr(int phy_id)
+unsigned int i2c_mii_phy_addr(int phy_id)
 {
 	return phy_id + 0x40;
 }