#include <linux/of_device.h>
 #include <linux/of_mdio.h>
 #include <linux/pinctrl/consumer.h>
+#include <linux/mdio-bitbang.h>
+#include <linux/sys_soc.h>
 
 /*
  * This timeout definition is a worst-case ultra defensive measure against
 
 struct davinci_mdio_of_param {
        int autosuspend_delay_ms;
+       bool manual_mode;
 };
 
 struct davinci_mdio_regs {
 #define CONTROL_IDLE           BIT(31)
 #define CONTROL_ENABLE         BIT(30)
 #define CONTROL_MAX_DIV                (0xffff)
+#define CONTROL_CLKDIV         GENMASK(15, 0)
+
+#define MDIO_MAN_MDCLK_O       BIT(2)
+#define MDIO_MAN_OE            BIT(1)
+#define MDIO_MAN_PIN           BIT(0)
+#define MDIO_MANUALMODE                BIT(31)
+
+#define MDIO_PIN               0
+
 
        u32     alive;
        u32     link;
        u32     userintmasked;
        u32     userintmaskset;
        u32     userintmaskclr;
-       u32     __reserved_1[20];
+       u32     manualif;
+       u32     poll;
+       u32     __reserved_1[18];
 
        struct {
                u32     access;
 
 struct davinci_mdio_data {
        struct mdio_platform_data pdata;
+       struct mdiobb_ctrl bb_ctrl;
        struct davinci_mdio_regs __iomem *regs;
        struct clk      *clk;
        struct device   *dev;
         */
        bool            skip_scan;
        u32             clk_div;
+       bool            manual_mode;
 };
 
 static void davinci_mdio_init_clk(struct davinci_mdio_data *data)
        writel(data->clk_div | CONTROL_ENABLE, &data->regs->control);
 }
 
-static int davinci_mdio_reset(struct mii_bus *bus)
+static void davinci_mdio_disable(struct davinci_mdio_data *data)
+{
+       u32 reg;
+
+       /* Disable MDIO state machine */
+       reg = readl(&data->regs->control);
+
+       reg &= ~CONTROL_CLKDIV;
+       reg |= data->clk_div;
+
+       reg &= ~CONTROL_ENABLE;
+       writel(reg, &data->regs->control);
+}
+
+static void davinci_mdio_enable_manual_mode(struct davinci_mdio_data *data)
+{
+       u32 reg;
+       /* set manual mode */
+       reg = readl(&data->regs->poll);
+       reg |= MDIO_MANUALMODE;
+       writel(reg, &data->regs->poll);
+}
+
+static void davinci_set_mdc(struct mdiobb_ctrl *ctrl, int level)
+{
+       struct davinci_mdio_data *data;
+       u32 reg;
+
+       data = container_of(ctrl, struct davinci_mdio_data, bb_ctrl);
+       reg = readl(&data->regs->manualif);
+
+       if (level)
+               reg |= MDIO_MAN_MDCLK_O;
+       else
+               reg &= ~MDIO_MAN_MDCLK_O;
+
+       writel(reg, &data->regs->manualif);
+}
+
+static void davinci_set_mdio_dir(struct mdiobb_ctrl *ctrl, int output)
+{
+       struct davinci_mdio_data *data;
+       u32 reg;
+
+       data = container_of(ctrl, struct davinci_mdio_data, bb_ctrl);
+       reg = readl(&data->regs->manualif);
+
+       if (output)
+               reg |= MDIO_MAN_OE;
+       else
+               reg &= ~MDIO_MAN_OE;
+
+       writel(reg, &data->regs->manualif);
+}
+
+static void  davinci_set_mdio_data(struct mdiobb_ctrl *ctrl, int value)
+{
+       struct davinci_mdio_data *data;
+       u32 reg;
+
+       data = container_of(ctrl, struct davinci_mdio_data, bb_ctrl);
+       reg = readl(&data->regs->manualif);
+
+       if (value)
+               reg |= MDIO_MAN_PIN;
+       else
+               reg &= ~MDIO_MAN_PIN;
+
+       writel(reg, &data->regs->manualif);
+}
+
+static int davinci_get_mdio_data(struct mdiobb_ctrl *ctrl)
+{
+       struct davinci_mdio_data *data;
+       unsigned long reg;
+
+       data = container_of(ctrl, struct davinci_mdio_data, bb_ctrl);
+       reg = readl(&data->regs->manualif);
+       return test_bit(MDIO_PIN, ®);
+}
+
+static int davinci_mdiobb_read(struct mii_bus *bus, int phy, int reg)
+{
+       int ret;
+
+       ret = pm_runtime_resume_and_get(bus->parent);
+       if (ret < 0)
+               return ret;
+
+       ret = mdiobb_read(bus, phy, reg);
+
+       pm_runtime_mark_last_busy(bus->parent);
+       pm_runtime_put_autosuspend(bus->parent);
+
+       return ret;
+}
+
+static int davinci_mdiobb_write(struct mii_bus *bus, int phy, int reg,
+                               u16 val)
+{
+       int ret;
+
+       ret = pm_runtime_resume_and_get(bus->parent);
+       if (ret < 0)
+               return ret;
+
+       ret = mdiobb_write(bus, phy, reg, val);
+
+       pm_runtime_mark_last_busy(bus->parent);
+       pm_runtime_put_autosuspend(bus->parent);
+
+       return ret;
+}
+
+static int davinci_mdio_common_reset(struct davinci_mdio_data *data)
 {
-       struct davinci_mdio_data *data = bus->priv;
        u32 phy_mask, ver;
        int ret;
 
        if (ret < 0)
                return ret;
 
+       if (data->manual_mode) {
+               davinci_mdio_disable(data);
+               davinci_mdio_enable_manual_mode(data);
+       }
+
        /* wait for scan logic to settle */
        msleep(PHY_MAX_ADDR * data->access_time);
 
        return 0;
 }
 
+static int davinci_mdio_reset(struct mii_bus *bus)
+{
+       struct davinci_mdio_data *data = bus->priv;
+
+       return davinci_mdio_common_reset(data);
+}
+
+static int davinci_mdiobb_reset(struct mii_bus *bus)
+{
+       struct mdiobb_ctrl *ctrl = bus->priv;
+       struct davinci_mdio_data *data;
+
+       data = container_of(ctrl, struct davinci_mdio_data, bb_ctrl);
+
+       return davinci_mdio_common_reset(data);
+}
+
 /* wait until hardware is ready for another user access */
 static inline int wait_for_user_access(struct davinci_mdio_data *data)
 {
        return 0;
 }
 
+struct k3_mdio_soc_data {
+       bool manual_mode;
+};
+
+static const struct k3_mdio_soc_data am65_mdio_soc_data = {
+       .manual_mode = true,
+};
+
+static const struct soc_device_attribute k3_mdio_socinfo[] = {
+       { .family = "AM62X", .revision = "SR1.0", .data = &am65_mdio_soc_data },
+       { .family = "AM64X", .revision = "SR1.0", .data = &am65_mdio_soc_data },
+       { .family = "AM64X", .revision = "SR2.0", .data = &am65_mdio_soc_data },
+       { .family = "AM65X", .revision = "SR1.0", .data = &am65_mdio_soc_data },
+       { .family = "AM65X", .revision = "SR2.0", .data = &am65_mdio_soc_data },
+       { .family = "J7200", .revision = "SR1.0", .data = &am65_mdio_soc_data },
+       { .family = "J7200", .revision = "SR2.0", .data = &am65_mdio_soc_data },
+       { .family = "J721E", .revision = "SR1.0", .data = &am65_mdio_soc_data },
+       { .family = "J721E", .revision = "SR2.0", .data = &am65_mdio_soc_data },
+       { .family = "J721S2", .revision = "SR1.0", .data = &am65_mdio_soc_data},
+       { /* sentinel */ },
+};
+
 #if IS_ENABLED(CONFIG_OF)
 static const struct davinci_mdio_of_param of_cpsw_mdio_data = {
        .autosuspend_delay_ms = 100,
 MODULE_DEVICE_TABLE(of, davinci_mdio_of_mtable);
 #endif
 
+static const struct mdiobb_ops davinci_mdiobb_ops = {
+       .owner = THIS_MODULE,
+       .set_mdc = davinci_set_mdc,
+       .set_mdio_dir = davinci_set_mdio_dir,
+       .set_mdio_data = davinci_set_mdio_data,
+       .get_mdio_data = davinci_get_mdio_data,
+};
+
 static int davinci_mdio_probe(struct platform_device *pdev)
 {
        struct mdio_platform_data *pdata = dev_get_platdata(&pdev->dev);
        if (!data)
                return -ENOMEM;
 
-       data->bus = devm_mdiobus_alloc(dev);
+       data->manual_mode = false;
+       data->bb_ctrl.ops = &davinci_mdiobb_ops;
+
+       if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
+               const struct soc_device_attribute *soc_match_data;
+
+               soc_match_data = soc_device_match(k3_mdio_socinfo);
+               if (soc_match_data && soc_match_data->data) {
+                       const struct k3_mdio_soc_data *socdata =
+                                               soc_match_data->data;
+
+                       data->manual_mode = socdata->manual_mode;
+               }
+       }
+
+       if (data->manual_mode)
+               data->bus = alloc_mdio_bitbang(&data->bb_ctrl);
+       else
+               data->bus = devm_mdiobus_alloc(dev);
+
        if (!data->bus) {
                dev_err(dev, "failed to alloc mii bus\n");
                return -ENOMEM;
        }
 
        data->bus->name         = dev_name(dev);
-       data->bus->read         = davinci_mdio_read;
-       data->bus->write        = davinci_mdio_write;
-       data->bus->reset        = davinci_mdio_reset;
+
+       if (data->manual_mode) {
+               data->bus->read         = davinci_mdiobb_read;
+               data->bus->write        = davinci_mdiobb_write;
+               data->bus->reset        = davinci_mdiobb_reset;
+
+               dev_info(dev, "Configuring MDIO in manual mode\n");
+       } else {
+               data->bus->read         = davinci_mdio_read;
+               data->bus->write        = davinci_mdio_write;
+               data->bus->reset        = davinci_mdio_reset;
+               data->bus->priv         = data;
+       }
        data->bus->parent       = dev;
-       data->bus->priv         = data;
 
        data->clk = devm_clk_get(dev, "fck");
        if (IS_ERR(data->clk)) {
 {
        struct davinci_mdio_data *data = platform_get_drvdata(pdev);
 
-       if (data->bus)
+       if (data->bus) {
                mdiobus_unregister(data->bus);
 
+               if (data->manual_mode)
+                       free_mdio_bitbang(data->bus);
+       }
+
        pm_runtime_dont_use_autosuspend(&pdev->dev);
        pm_runtime_disable(&pdev->dev);
 
        ctrl = readl(&data->regs->control);
        ctrl &= ~CONTROL_ENABLE;
        writel(ctrl, &data->regs->control);
-       wait_for_idle(data);
+
+       if (!data->manual_mode)
+               wait_for_idle(data);
 
        return 0;
 }
 {
        struct davinci_mdio_data *data = dev_get_drvdata(dev);
 
-       davinci_mdio_enable(data);
+       if (data->manual_mode) {
+               davinci_mdio_disable(data);
+               davinci_mdio_enable_manual_mode(data);
+       } else {
+               davinci_mdio_enable(data);
+       }
        return 0;
 }
 #endif