drivers: mdio: add adin2111

Adds MDIO driver. Works via exposed
ADIN2111 functions.

It is possible to access Clause 45 and 22 registers.

Due to MDIO API limitation Clause 45 access
is done using driver specific MDIO functions.

Provides API and functions for PHY driver.

Signed-off-by: Georgij Cernysiov <geo.cgv@gmail.com>
diff --git a/CODEOWNERS b/CODEOWNERS
index ae0f1b4..fad3a13 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -288,6 +288,7 @@
 /drivers/ethernet/*adin2111*              @GeorgeCGV
 /drivers/ethernet/phy/                    @rlubos @tbursztyka @arvinf
 /drivers/mdio/                            @rlubos @tbursztyka @arvinf
+/drivers/mdio/*adin2111*                  @GeorgeCGV
 /drivers/flash/                           @nashif @de-nordic
 /drivers/flash/*stm32_qspi*               @lmajewski
 /drivers/flash/*b91*                      @andy-liu-telink
diff --git a/drivers/mdio/CMakeLists.txt b/drivers/mdio/CMakeLists.txt
index bb93928..7fc03a8 100644
--- a/drivers/mdio/CMakeLists.txt
+++ b/drivers/mdio/CMakeLists.txt
@@ -6,3 +6,4 @@
 zephyr_library_sources_ifdef(CONFIG_MDIO_ATMEL_SAM	mdio_sam.c)
 zephyr_library_sources_ifdef(CONFIG_MDIO_ESP32		mdio_esp32.c)
 zephyr_library_sources_ifdef(CONFIG_MDIO_NXP_S32_NETC	mdio_nxp_s32_netc.c)
+zephyr_library_sources_ifdef(CONFIG_MDIO_ADIN2111	mdio_adin2111.c)
diff --git a/drivers/mdio/Kconfig b/drivers/mdio/Kconfig
index 4fa8b9d..0f2d3b8 100644
--- a/drivers/mdio/Kconfig
+++ b/drivers/mdio/Kconfig
@@ -28,6 +28,7 @@
 source "drivers/mdio/Kconfig.esp32"
 source "drivers/mdio/Kconfig.sam"
 source "drivers/mdio/Kconfig.nxp_s32"
+source "drivers/mdio/Kconfig.adin2111"
 
 config MDIO_INIT_PRIORITY
 	int "Init priority"
diff --git a/drivers/mdio/Kconfig.adin2111 b/drivers/mdio/Kconfig.adin2111
new file mode 100644
index 0000000..778bbbc
--- /dev/null
+++ b/drivers/mdio/Kconfig.adin2111
@@ -0,0 +1,10 @@
+# Copyright 2023 PHOENIX CONTACT Electronics GmbH
+# SPDX-License-Identifier: Apache-2.0
+
+config MDIO_ADIN2111
+	bool "NXP S32 NETC External MDIO driver"
+	default y
+	depends on DT_HAS_ADI_ADIN2111_MDIO_ENABLED
+	depends on ETH_ADIN2111
+	help
+	  Enable ADIN2111 MDIO driver.
diff --git a/drivers/mdio/mdio_adin2111.c b/drivers/mdio/mdio_adin2111.c
new file mode 100644
index 0000000..39d994c
--- /dev/null
+++ b/drivers/mdio/mdio_adin2111.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr/logging/log.h>
+LOG_MODULE_REGISTER(mdio_adin2111, CONFIG_MDIO_LOG_LEVEL);
+
+#define DT_DRV_COMPAT adi_adin2111_mdio
+
+#include <stdint.h>
+#include <errno.h>
+#include <zephyr/device.h>
+#include <zephyr/kernel.h>
+#include <zephyr/drivers/mdio.h>
+#include <zephyr/drivers/mdio/mdio_adin2111.h>
+#include <zephyr/drivers/ethernet/eth_adin2111.h>
+
+/* MDIO ready check retry delay */
+#define ADIN2111_MDIO_READY_AWAIT_DELAY_POLL_US		5U
+/* Number of retries for MDIO ready check */
+#define ADIN2111_MDIO_READY_AWAIT_RETRY_COUNT		10U
+
+/* MDIO Access Register 1 */
+#define ADIN2111_MDIOACC0				0x20U
+/* MDIO Access Register 2 */
+#define ADIN2111_MDIOACC1				0x21U
+
+/* MDIO MDIOACC Transaction Done */
+#define ADIN211_MDIOACC_MDIO_TRDONE			BIT(31)
+
+struct mdio_adin2111_config {
+	const struct device *adin;
+};
+
+static int mdio_adin2111_wait_ready(const struct device *dev, uint16_t reg,
+				    uint32_t *out)
+{
+	const struct mdio_adin2111_config *const cfg = dev->config;
+	uint32_t count;
+	int ret;
+
+	for (count = 0U; count < ADIN2111_MDIO_READY_AWAIT_RETRY_COUNT; ++count) {
+		ret = eth_adin2111_reg_read(cfg->adin, reg, out);
+		if (ret >= 0) {
+			if ((*out) & ADIN211_MDIOACC_MDIO_TRDONE) {
+				break;
+			}
+			ret = -ETIMEDOUT;
+		}
+		k_sleep(K_USEC(ADIN2111_MDIO_READY_AWAIT_DELAY_POLL_US));
+	}
+
+	return ret;
+}
+
+
+int adin2111_mdio_c45_read(const struct device *dev, uint8_t prtad,
+			   uint8_t devad, uint16_t regad,
+			   uint16_t *data)
+{
+	const struct mdio_adin2111_config *const cfg = dev->config;
+	uint32_t rdy;
+	uint32_t cmd;
+	int ret;
+
+	/* address op */
+	cmd = (prtad & 0x1FU) << 21;
+	cmd |= (devad & 0x1FU) << 16;
+	cmd |= regad;
+
+	ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC0, cmd);
+	if (ret < 0) {
+		return ret;
+	}
+
+	/* read op */
+	cmd = (cmd & ~UINT16_MAX) | (0x3U << 26);
+
+	ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC1, cmd);
+	if (ret < 0) {
+		return ret;
+	}
+
+	ret = mdio_adin2111_wait_ready(dev, ADIN2111_MDIOACC1, &rdy);
+	if (ret < 0) {
+		return ret;
+	}
+
+	/* read out */
+	ret = eth_adin2111_reg_read(cfg->adin, ADIN2111_MDIOACC1, &cmd);
+
+	*data = cmd & UINT16_MAX;
+
+	return ret;
+}
+
+int adin2111_mdio_c45_write(const struct device *dev, uint8_t prtad,
+			    uint8_t devad, uint16_t regad,
+			    uint16_t data)
+{
+	const struct mdio_adin2111_config *const cfg = dev->config;
+
+	uint32_t rdy;
+	uint32_t cmd;
+	int ret;
+
+	/* address op */
+	cmd = (prtad & 0x1FU) << 21;
+	cmd |= (devad & 0x1FU) << 16;
+	cmd |= regad;
+
+	ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC0, cmd);
+	if (ret < 0) {
+		return ret;
+	}
+
+	/* write op */
+	cmd |= BIT(26);
+	cmd = (cmd & ~UINT16_MAX) | data;
+
+	ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC1, cmd);
+	if (ret < 0) {
+		return ret;
+	}
+
+	ret = mdio_adin2111_wait_ready(dev, ADIN2111_MDIOACC1, &rdy);
+
+	return ret;
+}
+
+static int mdio_adin2111_read(const struct device *dev, uint8_t prtad,
+			      uint8_t devad, uint16_t *data)
+{
+	const struct mdio_adin2111_config *const cfg = dev->config;
+	uint32_t read;
+	uint32_t cmd;
+	int ret;
+
+	cmd = BIT(28);
+	cmd |= 0x3U << 26;
+	cmd |= (prtad & 0x1FU) << 21;
+	cmd |= (devad & 0x1FU) << 16;
+
+	ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC0, cmd);
+	if (ret >= 0) {
+		ret = mdio_adin2111_wait_ready(dev, ADIN2111_MDIOACC0, &read);
+		*data = read & UINT16_MAX;
+	}
+
+	return ret;
+}
+
+static int mdio_adin2111_write(const struct device *dev, uint8_t prtad,
+			       uint8_t devad, uint16_t data)
+{
+	const struct mdio_adin2111_config *const cfg = dev->config;
+	uint32_t cmd;
+	uint32_t rdy;
+	int ret;
+
+	cmd = BIT(28);
+	cmd |= BIT(26);
+	cmd |= (prtad & 0x1FU) << 21;
+	cmd |= (devad & 0x1FU) << 16;
+	cmd |= data;
+
+	ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC0, cmd);
+	if (ret >= 0) {
+		ret = mdio_adin2111_wait_ready(dev, ADIN2111_MDIOACC0, &rdy);
+	}
+
+	return ret;
+}
+
+static void mdio_adin2111_bus_enable(const struct device *dev)
+{
+	const struct mdio_adin2111_config *const cfg = dev->config;
+
+	eth_adin2111_lock(cfg->adin, K_FOREVER);
+}
+
+static void mdio_adin2111_bus_disable(const struct device *dev)
+{
+	const struct mdio_adin2111_config *const cfg = dev->config;
+
+	eth_adin2111_unlock(cfg->adin);
+}
+
+static const struct mdio_driver_api mdio_adin2111_api = {
+	.read = mdio_adin2111_read,
+	.write = mdio_adin2111_write,
+	.bus_enable = mdio_adin2111_bus_enable,
+	.bus_disable = mdio_adin2111_bus_disable
+};
+
+#define ADIN2111_MDIO_INIT(n)							\
+	static const struct mdio_adin2111_config mdio_adin2111_config_##n = {	\
+		.adin = DEVICE_DT_GET(DT_INST_BUS(n)),				\
+	};									\
+	DEVICE_DT_INST_DEFINE(n, NULL, NULL,					\
+			      NULL, &mdio_adin2111_config_##n,			\
+			      POST_KERNEL, CONFIG_MDIO_INIT_PRIORITY,		\
+			      &mdio_adin2111_api);
+
+DT_INST_FOREACH_STATUS_OKAY(ADIN2111_MDIO_INIT)
diff --git a/dts/bindings/mdio/adi,adin2111-mdio.yaml b/dts/bindings/mdio/adi,adin2111-mdio.yaml
new file mode 100644
index 0000000..ab20d5c
--- /dev/null
+++ b/dts/bindings/mdio/adi,adin2111-mdio.yaml
@@ -0,0 +1,19 @@
+# Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH
+# SPDX-License-Identifier: Apache-2.0
+
+description: ADIN2111 MDIO Driver node
+
+compatible: "adi,adin2111-mdio"
+
+include: mdio-controller.yaml
+
+on-bus: adin2111
+
+properties:
+  "#address-cells":
+    required: true
+    const: 1
+
+  "#size-cells":
+    required: true
+    const: 0
diff --git a/include/zephyr/drivers/mdio/mdio_adin2111.h b/include/zephyr/drivers/mdio/mdio_adin2111.h
new file mode 100644
index 0000000..bf97186
--- /dev/null
+++ b/include/zephyr/drivers/mdio/mdio_adin2111.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef ZEPHYR_INCLUDE_DRIVERS_MDIO_ADIN2111_H__
+#define ZEPHYR_INCLUDE_DRIVERS_MDIO_ADIN2111_H__
+
+#include <stdint.h>
+#include <zephyr/device.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Read from MDIO Bus using Clause 45 access
+ *
+ * @note The caller is responsible for device lock.
+ *       Shall not be called from ISR.
+ *
+ * @param[in] dev MDIO device.
+ * @param[in] prtad Port address.
+ * @param[in] devad Device address.
+ * @param[in] regad Register address.
+ * @param[out] data Pointer to receive read data.
+ *
+ * @retval 0 If successful.
+ * @retval -EIO General input / output error.
+ * @retval -ETIMEDOUT If transaction timedout on the bus.
+ * @retval <0 Error, a negative errno code.
+ */
+int adin2111_mdio_c45_read(const struct device *dev, uint8_t prtad,
+			   uint8_t devad, uint16_t regad, uint16_t *data);
+
+/**
+ * @brief Write to MDIO bus using Clause 45 access
+ *
+ * @note The caller is responsible for device lock.
+ *       Shall not be called from ISR.
+ *
+ * @param[in] dev MDIO device.
+ * @param[in] prtad Port address.
+ * @param[in] devad Device address.
+ * @param[in] regad Register address.
+ * @param[in] data Data to write.
+ *
+ * @retval 0 If successful.
+ * @retval -EIO General input / output error.
+ * @retval -ETIMEDOUT If transaction timedout on the bus.
+ * @retval <0 Error, a negative errno code.
+ */
+int adin2111_mdio_c45_write(const struct device *dev, uint8_t prtad,
+			    uint8_t devad, uint16_t regad, uint16_t data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZEPHYR_INCLUDE_DRIVERS_MDIO_ADIN2111_H__ */