diff --git a/.gitignore b/.gitignore
index ab727bd..52402cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,6 +71,7 @@ flake8_output.txt
# ESP-IDF default build directory name
build
+build*/
# lock files for examples and components
dependencies.lock
diff --git a/README.md b/README.md
index cba948a..196f332 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@ List of currently supported chips:
- [KSZ8863](ksz8863/README.md)
- [ADIN1200](adin1200/README.md)
+- [Dummy PHY (EMAC to EMAC)](eth_dummy_phy/README.md)
## Resources
diff --git a/eth_dummy_phy/CMakeLists.txt b/eth_dummy_phy/CMakeLists.txt
new file mode 100644
index 0000000..4bbdabc
--- /dev/null
+++ b/eth_dummy_phy/CMakeLists.txt
@@ -0,0 +1,3 @@
+idf_component_register(SRCS "src/esp_eth_phy_dummy.c"
+ INCLUDE_DIRS "include"
+ PRIV_REQUIRES log esp_eth driver)
\ No newline at end of file
diff --git a/eth_dummy_phy/LICENSE b/eth_dummy_phy/LICENSE
new file mode 100644
index 0000000..f49a4e1
--- /dev/null
+++ b/eth_dummy_phy/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/eth_dummy_phy/README.md b/eth_dummy_phy/README.md
new file mode 100644
index 0000000..1eab268
--- /dev/null
+++ b/eth_dummy_phy/README.md
@@ -0,0 +1,37 @@
+# Dummy PHY (EMAC to EMAC)
+
+The `Dummy PHY` component enables you a direct **EMAC to EMAC** communication between two Ethernet capable devices without usage of external PHY transceiver. The design idea is to provide EMAC to EMAC connection between two devices without any modification to ESP-IDF Ethernet driver.
+
+Note that usage of the `Dummy PHY` component does not conform with the RevMII standard! As such, it does not provide any management interface and so the speed/duplex mode needs to be statically configured to the same mode in both devices. Default configuration is 100Mbps/full duplex mode. If you need to change it, use `esp_eth_ioctl()` function with `ETH_CMD_S_SPEED` or `ETH_CMD_S_DUPLEX_MODE` command.
+
+## API
+
+### Steps to use the component in an example code
+
+1. Add this component to your project using `idf.py add-dependency` command.
+2. Include `esp_eth_phy_dummy.h`
+3. Create a dummy `phy` driver instance by calling `esp_eth_phy_new_dummy`and initialize Ethernet as you are used to. The only difference is to set SMI GPIOs to `-1` since the management interface is not utilized.
+ ```c
+ // Init common MAC and PHY configs to default
+ eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
+ eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
+
+ // Update PHY config based on board specific configuration
+ phy_config.reset_gpio_num = -1; // no HW reset
+
+ // Init vendor specific MAC config to default
+ eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
+ // Update vendor specific MAC config based on board configuration
+ // No SMI, speed/duplex must be statically configured the same in both devices
+ esp32_emac_config.smi_mdc_gpio_num = -1;
+ esp32_emac_config.smi_mdio_gpio_num = -1;
+
+ // Create new ESP32 Ethernet MAC instance
+ esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
+ // Create dummy PHY instance
+ esp_eth_phy_t *phy = esp_eth_phy_new_dummy(&phy_config);
+ ```
+
+## Examples
+
+Please refer to [Simple Example](./examples/simple/README.md) for more information.
diff --git a/eth_dummy_phy/examples/simple/CMakeLists.txt b/eth_dummy_phy/examples/simple/CMakeLists.txt
new file mode 100644
index 0000000..995ec79
--- /dev/null
+++ b/eth_dummy_phy/examples/simple/CMakeLists.txt
@@ -0,0 +1,6 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(simple_emac2emac)
diff --git a/eth_dummy_phy/examples/simple/README.md b/eth_dummy_phy/examples/simple/README.md
new file mode 100644
index 0000000..16f35b8
--- /dev/null
+++ b/eth_dummy_phy/examples/simple/README.md
@@ -0,0 +1,143 @@
+| Supported Targets | ESP32 |
+| ----------------- | ----- |
+
+# Simple EMAC to EMAC Example
+
+## Overview
+
+This example demonstrates basic usage of the `Dummy PHY` component which enables you a direct **EMAC to EMAC** communication between two Ethernet capable devices without usage of external PHY transceiver. The devices EMACs are connected directly via RMII.
+
+Output of the example are two applications with the following workflows:
+
+1. The `RMII CLK Source Device` configured as DHCP Server:
+ 1. Wait for the `RMII CKL Sink Device` is ready, see [here](#interrupt-pin) for more information
+ 2. Install Ethernet driver and so enable RMII CLK output
+ 3. Attach the driver to `esp_netif`
+ 4. The DHCP Server assigns DHCP lease upon DHCP request from the `RMII CLK Sink Device`
+ 5. Reset itself when the `RMII CLK Sink Device` is reset
+
+2. The `RMII CLK Sink Device` configured as DHCP Client:
+ 1. Notify the `RMII CKL Source Device` that we are ready with basic chip initialization (we've entered the `app_main()`)
+ 2. Install Ethernet driver (perform multiple attempts since the CLK may not be stable yet)
+ 3. Attach the driver to `esp_netif`
+ 4. Send DHCP requests and wait for a DHCP lease
+ 5. Get IP address and ping the `RMII CLK Source Device`
+
+## How to use example
+
+### Hardware Required
+
+To run this example, you need one devkit which consists of [ESP32-WROOM module](https://www.espressif.com/en/products/modules/esp32) and one devkit which consists of [ESP32-WROVER module](https://www.espressif.com/en/products/modules/esp32) (or two ESP32-WROOM module devkits). The ESP32-WROOM module is used as source of the REF RMII CLK. Note that that **ESP32-WROVER can't be used as REF RMII CLK source** since the GPIO16 and GPIO17 (REF RMII CLK outputs) are utilized by SPIRAM.
+
+#### Pin Assignment
+
+Connect devkits' pins as shown in the table below. For demonstration purposes, you can interconnect them just by wires. However, the wires **must be** as short as possible (3-4 cm max!) and with matching length. It is also better to solder the wires to avoid loose contacts. Otherwise, you may get significantly over the [RMII specs.](https://resources.pcb.cadence.com/blog/2019-mii-and-rmii-routing-guidelines-for-ethernet) and face high packet loss or the setup may not work at all.
+
+| RMII CLK Sink Device
(ESP32 WROVER) | | RMII CLK Source Device
(ESP32 WROOM) | |
+| ----- | ----- | ----- | ----- |
+| **Func** | **GPIO** | **Func** | **GPIO** |
+| RX[0] | GPIO25 | TX[0] | GPIO19 |
+| RX[1] | GPIO26 | TX[1] | GPIO22 |
+| TX[0] | GPIO19 | RX[0] | GPIO25 |
+| TX[1] | GPIO22 | RX[1] | GPIO26 |
+| TX_EN | GPIO21 | CRS_DV| GPIO27 |
+| CRS_DV| GPIO27 | TX_EN | GPIO21 |
+| CLK_IN | GPIO0 | CLK_O | GPIO17 |
+| READY_IRQ | GPIO4 | IRQ_IN | GPIO4 |
+| GND | GND | GND | GND |
+
+##### Interrupt Pin
+
+The Ready interrupt pin (`READY_IRQ`) is used to synchronize initialization of `RMII CLK Source Device` (ESP32-WROOM) with initialization of `RMII CLK Sink Device` (ESP32-WROVER). The `RMII CLK Source Device` needs to wait with its Ethernet initialization for the `RMII CLK Sink Device` since **the RMII CLK input pin (GPIO0) is also used as a boot strap pin for ESP32 chip**. If the `RMII CLK Source Device` didn't wait, the `RMII CLK Sink Device` could boot into incorrect mode. See the code for more information.
+
+Note that there are more options of how to achieve the intended behavior by hardware means:
+* You can keep the `RMII CKL Source Device` in reset state by pulling the EN pin low and enable it only after the `RMII CLK Sink Device` is ready (similarly how it's realized by [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html) with PHY as a CLK source). However, make sure that there is still a way to handle EN pin for reset and flash operation.
+* Use external 50 MHz oscillator and enable it only after both ESP32 devices are ready.
+* May not be needed at all if `RMII CLK Sink Device` is not ESP32.
+
+
+### Configure the project
+
+As mentioned above, the output of the example are two applications - the `RMII CLK Source Device` and the `RMII CLK Sink Device`. You can configure each application manually using
+
+```
+idf.py menuconfig
+```
+
+or build each application *automatically* based on provided `sdkconfig.defaults` files as follows:
+
+```
+idf.py -B "build_defaults_clk_source" -DSDKCONFIG="build_defaults_clk_source/sdkconfig" -DSDKCONFIG_DEFAULTS="sdkconfig.defaults.clk_source" build
+```
+
+and
+
+```
+idf.py -B "build_defaults_clk_sink" -DSDKCONFIG="build_defaults_clk_sink/sdkconfig" -DSDKCONFIG_DEFAULTS="sdkconfig.defaults.clk_sink" build
+```
+
+
+### Build, Flash, and Run
+
+If you configured our applications manually using `menuconfig`, build the project and flash it to the board, then run monitor tool to view serial output:
+
+```
+idf.py -p PORT build flash monitor
+```
+
+If you built the applications based on on provided `sdkconfig.defaults` files, go to each `build` folder and flash each board by running:
+
+```
+python -m esptool --chip esp32 --port PORT -b 460800 --before default_reset --after hard_reset write_flash "@flash_args"
+```
+
+(Replace PORT with the name of the serial port to use.)
+
+(To exit the serial monitor, type ``Ctrl-]``.)
+
+See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
+
+## Example Output
+
+**The RMII CLK Source Device Output**
+
+```bash
+I (360) main_task: Started on CPU0
+I (370) main_task: Calling app_main()
+W (370) emac2emac: waiting for RMII CLK sink device interrupt
+W (370) emac2emac: if RMII CLK sink device is already running, reset it by `EN` button
+I (9300) emac2emac: starting Ethernet initialization
+I (9310) esp_eth.netif.netif_glue: 94:e6:86:65:9b:27
+I (9310) esp_eth.netif.netif_glue: ethernet attached to netif
+I (9310) emac2emac: Ethernet Started
+I (9320) emac2emac: Ethernet Link Up
+I (9320) emac2emac: Ethernet HW Addr 94:e6:86:65:9b:27
+I (9820) esp_netif_lwip: DHCP server assigned IP to a client, IP is: 192.168.4.2
+```
+
+**The RMII CLK Sink Device Output**
+
+```bash
+I (873) main_task: Returned from app_main()
+I (873) emac2emac: Ethernet Started
+I (883) emac2emac: Ethernet Link Up
+I (893) emac2emac: Ethernet HW Addr e0:e2:e6:6a:7c:ff
+I (2383) esp_netif_handlers: eth ip: 192.168.4.2, mask: 255.255.255.0, gw: 192.168.4.1
+I (2383) emac2emac: Ethernet Got IP Address
+I (2383) emac2emac: ~~~~~~~~~~~
+I (2383) emac2emac: ETHIP:192.168.4.2
+I (2393) emac2emac: ETHMASK:255.255.255.0
+I (2393) emac2emac: ETHGW:192.168.4.1
+I (2403) emac2emac: ~~~~~~~~~~~
+64 bytes from 192.168.4.1 icmp_seq=1 ttl=255 time=1 ms
+64 bytes from 192.168.4.1 icmp_seq=2 ttl=255 time=0 ms
+64 bytes from 192.168.4.1 icmp_seq=3 ttl=255 time=0 ms
+64 bytes from 192.168.4.1 icmp_seq=4 ttl=255 time=0 ms
+64 bytes from 192.168.4.1 icmp_seq=5 ttl=255 time=0 ms
+--- 192.168.4.1 ping statistics ---
+5 packets transmitted, 5 received, 0% packet loss, time 1ms
+```
+
+## General Notes
+
+* There is no speed/duplex auto-negotiation between devices, therefore both devices EMACs must be statically configured to the same speed/duplex. The example default configuration is 100Mbps/full duplex mode. If you need to change it, use `esp_eth_ioctl()` function with `ETH_CMD_S_SPEED` or `ETH_CMD_S_DUPLEX_MODE` command.
diff --git a/eth_dummy_phy/examples/simple/main/CMakeLists.txt b/eth_dummy_phy/examples/simple/main/CMakeLists.txt
new file mode 100644
index 0000000..a7e9618
--- /dev/null
+++ b/eth_dummy_phy/examples/simple/main/CMakeLists.txt
@@ -0,0 +1,2 @@
+idf_component_register(SRCS "emac2emac.c"
+ PRIV_REQUIRES esp_netif driver esp_eth)
diff --git a/eth_dummy_phy/examples/simple/main/Kconfig.projbuild b/eth_dummy_phy/examples/simple/main/Kconfig.projbuild
new file mode 100644
index 0000000..1a78ea8
--- /dev/null
+++ b/eth_dummy_phy/examples/simple/main/Kconfig.projbuild
@@ -0,0 +1,33 @@
+menu "Example Configuration"
+
+ choice EXAMPLE_RMII_CLK_DEVICE_TYPE
+ prompt "RMII CLK"
+ default EXAMPLE_RMII_CLK_SOURCE_DEV
+ help
+ Configure device type. ESP32 device which is RMII CLK source needs to wait
+ with its Ethernet initialization for the "RMII CLK Sink Device" since
+ the RMII CLK input pin (GPIO0) is also used as a boot strap pin. If
+ the "RMII CLK Source Device" didn't wait, the "RMII CLK Sink Device"
+ could boot into incorrect mode.
+
+ config EXAMPLE_RMII_CLK_SOURCE_DEV
+ bool "RMII CLK Source Device"
+ config EXAMPLE_RMII_CLK_SINK_DEV
+ bool "RMII CLK Sink Device"
+ endchoice
+
+ config EXAMPLE_CLK_SINK_READY_GPIO
+ int "RMII CLK Sink Device is ready GPIO"
+ default 4
+ help
+ GPIO number at which the "RMII CLK Sink Device" is ready and so the "RMII
+ CLK Source Device" can continue in its Ethernet initialization.
+
+ config EXAMPLE_DHCP_SERVER_EN
+ bool "Enable DHCP Server"
+ default n
+ help
+ Enable the DHCP Server on the device. The DHCP server is enabled only at
+ the first interface if multiple interfaces are available.
+
+endmenu
diff --git a/eth_dummy_phy/examples/simple/main/emac2emac.c b/eth_dummy_phy/examples/simple/main/emac2emac.c
new file mode 100644
index 0000000..9d35fd0
--- /dev/null
+++ b/eth_dummy_phy/examples/simple/main/emac2emac.c
@@ -0,0 +1,302 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_netif.h"
+#include "esp_eth.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "sdkconfig.h"
+#include "driver/gpio.h"
+#include "esp_system.h"
+#include "esp_eth_driver.h"
+#include "esp_eth_phy_dummy.h"
+
+#include "lwip/inet.h"
+#include "ping/ping_sock.h"
+
+#define STARTUP_DELAY_MS 500
+
+static const char *TAG = "emac2emac";
+
+#if !CONFIG_EXAMPLE_DHCP_SERVER_EN
+static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args)
+{
+ uint8_t ttl;
+ uint16_t seqno;
+ uint32_t elapsed_time, recv_len;
+ ip_addr_t target_addr;
+ esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
+ esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
+ esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
+ esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
+ esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
+ printf("%" PRIu32 " bytes from %s icmp_seq=%" PRIu16 " ttl=%" PRIu16 " time=%" PRIu32 " ms\n",
+ recv_len, ipaddr_ntoa((ip_addr_t *)&target_addr), seqno, ttl, elapsed_time);
+}
+
+static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args)
+{
+ uint16_t seqno;
+ ip_addr_t target_addr;
+ esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
+ esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
+ printf("From %s icmp_seq=%d timeout\n", ipaddr_ntoa((ip_addr_t *)&target_addr), seqno);
+}
+
+static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args)
+{
+ ip_addr_t target_addr;
+ uint32_t transmitted;
+ uint32_t received;
+ uint32_t total_time_ms;
+ uint32_t loss;
+
+ esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
+ esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
+ esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
+ esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));
+
+ if (transmitted > 0) {
+ loss = (uint32_t)((1 - ((float)received) / transmitted) * 100);
+ } else {
+ loss = 0;
+ }
+ if (IP_IS_V4(&target_addr)) {
+ printf("\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr)));
+ } else {
+ printf("\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr)));
+ }
+ printf("%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %" PRIu32 "ms\n",
+ transmitted, received, loss, total_time_ms);
+
+ esp_ping_delete_session(hdl);
+}
+
+static void ping_start(const esp_ip4_addr_t *ip)
+{
+ esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
+
+ ip4_addr_set_u32(ip_2_ip4(&config.target_addr), ip->addr);
+ config.target_addr.type = IPADDR_TYPE_V4; // currently only IPv4 for this example
+
+ /* set callback functions */
+ esp_ping_callbacks_t cbs = {
+ .cb_args = NULL,
+ .on_ping_success = cmd_ping_on_ping_success,
+ .on_ping_timeout = cmd_ping_on_ping_timeout,
+ .on_ping_end = cmd_ping_on_ping_end
+ };
+ esp_ping_handle_t ping;
+ esp_ping_new_session(&config, &cbs, &ping);
+ esp_ping_start(ping);
+}
+#endif // !CONFIG_EXAMPLE_DHCP_SERVER_EN
+
+/** Event handler for Ethernet events */
+static void eth_event_handler(void *arg, esp_event_base_t event_base,
+ int32_t event_id, void *event_data)
+{
+ uint8_t mac_addr[6] = {0};
+ /* we can get the ethernet driver handle from event data */
+ esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;
+
+ switch (event_id) {
+ case ETHERNET_EVENT_CONNECTED:
+ esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr);
+ ESP_LOGI(TAG, "Ethernet Link Up");
+ ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x",
+ mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
+#if CONFIG_EXAMPLE_DHCP_SERVER_EN
+ esp_netif_dhcps_start(esp_netif_get_handle_from_ifkey("ETH_DEF"));
+#endif
+ break;
+ case ETHERNET_EVENT_DISCONNECTED:
+ ESP_LOGI(TAG, "Ethernet Link Down");
+ break;
+ case ETHERNET_EVENT_START:
+ ESP_LOGI(TAG, "Ethernet Started");
+ break;
+ case ETHERNET_EVENT_STOP:
+ ESP_LOGI(TAG, "Ethernet Stopped");
+ break;
+ default:
+ break;
+ }
+}
+
+/** Event handler for IP_EVENT_ETH_GOT_IP */
+static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
+ int32_t event_id, void *event_data)
+{
+ ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
+ const esp_netif_ip_info_t *ip_info = &event->ip_info;
+
+ ESP_LOGI(TAG, "Ethernet Got IP Address");
+ ESP_LOGI(TAG, "~~~~~~~~~~~");
+ ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip));
+ ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask));
+ ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw));
+ ESP_LOGI(TAG, "~~~~~~~~~~~");
+
+#if !CONFIG_EXAMPLE_DHCP_SERVER_EN
+ ping_start(&ip_info->gw);
+#endif
+}
+
+#if CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV
+IRAM_ATTR static void gpio_isr_handler(void *arg)
+{
+ BaseType_t high_task_wakeup = pdFALSE;
+ TaskHandle_t task_handle = (TaskHandle_t)arg;
+
+ vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
+ if (high_task_wakeup != pdFALSE) {
+ portYIELD_FROM_ISR();
+ }
+}
+#endif // CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV
+
+void app_main(void)
+{
+ // ESP32 device which is RMII CLK source needs to wait with its Ethernet initialization for the "RMII CLK Sink Device"
+ // since the RMII CLK input pin (GPIO0) is also used as a boot strap pin. If the "RMII CLK Source Device" didn't wait,
+ // the "RMII CLK Sink Device" could boot into incorrect mode.
+#if CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV
+ esp_rom_gpio_pad_select_gpio(EMAC_CLK_OUT_180_GPIO);
+ gpio_set_pull_mode(EMAC_CLK_OUT_180_GPIO, GPIO_FLOATING); // to not affect GPIO0 (so the Sink Device could be flashed)
+ gpio_install_isr_service(0);
+ gpio_config_t gpio_source_cfg = {
+ .pin_bit_mask = (1ULL << CONFIG_EXAMPLE_CLK_SINK_READY_GPIO),
+ .mode = GPIO_MODE_INPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_ENABLE,
+ .intr_type = GPIO_INTR_ANYEDGE
+ };
+ gpio_config(&gpio_source_cfg);
+ TaskHandle_t task_handle = xTaskGetHandle(pcTaskGetName(NULL));
+ gpio_isr_handler_add(CONFIG_EXAMPLE_CLK_SINK_READY_GPIO, gpio_isr_handler, task_handle);
+ ESP_LOGW(TAG, "waiting for RMII CLK sink device interrupt");
+ ESP_LOGW(TAG, "if RMII CLK sink device is already running, reset it by `EN` button");
+ while (1) {
+ ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
+ if (gpio_get_level(CONFIG_EXAMPLE_CLK_SINK_READY_GPIO) == 1) {
+ break;
+ }
+ }
+ ESP_LOGI(TAG, "starting Ethernet initialization");
+#else
+ gpio_config_t gpio_sink_cfg = {
+ .pin_bit_mask = (1ULL << CONFIG_EXAMPLE_CLK_SINK_READY_GPIO),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE
+ };
+ gpio_config(&gpio_sink_cfg);
+ gpio_set_level(CONFIG_EXAMPLE_CLK_SINK_READY_GPIO, 0);
+ vTaskDelay(pdMS_TO_TICKS(STARTUP_DELAY_MS));
+ gpio_set_level(CONFIG_EXAMPLE_CLK_SINK_READY_GPIO, 1);
+#endif // CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV
+
+ // --- Initialize Ethernet driver ---
+ // Init common MAC and PHY configs to default
+ eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
+ eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
+
+ // Update PHY config based on board specific configuration
+ phy_config.reset_gpio_num = -1; // no HW reset
+
+ // Init vendor specific MAC config to default
+ eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
+ // Update vendor specific MAC config based on board configuration
+ // No SMI, speed/duplex must be statically configured the same in both devices
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
+ esp32_emac_config.smi_gpio.mdc_num = -1;
+ esp32_emac_config.smi_gpio.mdio_num = -1;
+#else
+ esp32_emac_config.smi_mdc_gpio_num = -1;
+ esp32_emac_config.smi_mdio_gpio_num = -1;
+#endif
+#if CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV
+ esp32_emac_config.clock_config.rmii.clock_mode = EMAC_CLK_OUT;
+ esp32_emac_config.clock_config.rmii.clock_gpio = EMAC_CLK_OUT_180_GPIO;
+#else
+ esp32_emac_config.clock_config.rmii.clock_mode = EMAC_CLK_EXT_IN;
+ esp32_emac_config.clock_config.rmii.clock_gpio = EMAC_CLK_IN_GPIO;
+#endif // CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV
+
+ // Create new ESP32 Ethernet MAC instance
+ esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
+ // Create dummy PHY instance
+ esp_eth_phy_t *phy = esp_eth_phy_new_dummy(&phy_config);
+
+ // Init Ethernet driver to default and install it
+ esp_eth_handle_t eth_handle = NULL;
+ esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
+#if CONFIG_EXAMPLE_RMII_CLK_SINK_DEV
+ // REF RMII CLK sink device performs multiple EMAC init attempts since RMII CLK source device may not be ready yet
+ int i;
+ for (i = 1; i <= 5; i++) {
+ ESP_LOGI(TAG, "Ethernet driver install attempt: %i", i);
+ if (esp_eth_driver_install(&config, ð_handle) == ESP_OK) {
+ break;
+ }
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+ if (i > 5) {
+ ESP_LOGE(TAG, "Ethernet driver install failed");
+ abort();
+ return;
+ }
+#else
+ ESP_ERROR_CHECK(esp_eth_driver_install(&config, ð_handle));
+#endif // CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV
+
+ // Initialize TCP/IP network interface aka the esp-netif
+ ESP_ERROR_CHECK(esp_netif_init());
+ // Create default event loop that running in background
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ // Create instance of esp-netif for Ethernet
+ esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_ETH();
+#if CONFIG_EXAMPLE_DHCP_SERVER_EN
+ esp_netif_config.flags = (esp_netif_flags_t)(ESP_NETIF_IPV4_ONLY_FLAGS(ESP_NETIF_DHCP_SERVER));
+ esp_netif_config.ip_info = &_g_esp_netif_soft_ap_ip; // Use the same IP ranges as IDF's soft AP
+ esp_netif_config.get_ip_event = 0;
+ esp_netif_config.lost_ip_event = 0;
+#endif
+ esp_netif_config_t cfg = {
+ .base = &esp_netif_config,
+ .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH
+ };
+ esp_netif_t *eth_netif = esp_netif_new(&cfg);
+ // Attach Ethernet driver to TCP/IP stack
+ ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));
+
+ // Register user defined event handers
+ ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL));
+ ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL));
+
+ ESP_ERROR_CHECK(esp_eth_start(eth_handle));
+
+#if CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV
+ // Wait indefinitely or reset when "RMII CLK Sink Device" resets
+ // We reset the "RMII CLK Source Device" to ensure there is no CLK at GPIO0 of the
+ // "RMII CLK Sink Device" during startup
+ while (1) {
+ ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
+ if (gpio_get_level(CONFIG_EXAMPLE_CLK_SINK_READY_GPIO) == 0) {
+ break;
+ }
+ }
+ ESP_LOGW(TAG, "RMII CLK Sink device reset, I'm going to reset too!");
+ esp_restart();
+#endif // CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV
+}
diff --git a/eth_dummy_phy/examples/simple/main/idf_component.yml b/eth_dummy_phy/examples/simple/main/idf_component.yml
new file mode 100644
index 0000000..1b85c74
--- /dev/null
+++ b/eth_dummy_phy/examples/simple/main/idf_component.yml
@@ -0,0 +1,6 @@
+description: Simple EMAC to EMAC example (ESP32)
+dependencies:
+ espressif/eth_dummy_phy:
+ version: '*'
+ # For local development use the local copy of the component
+ override_path: '../../../'
\ No newline at end of file
diff --git a/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_sink b/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_sink
new file mode 100644
index 0000000..4e5668d
--- /dev/null
+++ b/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_sink
@@ -0,0 +1 @@
+CONFIG_EXAMPLE_RMII_CLK_SINK_DEV=y
diff --git a/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_source b/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_source
new file mode 100644
index 0000000..c00b610
--- /dev/null
+++ b/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_source
@@ -0,0 +1,2 @@
+CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV=y
+CONFIG_EXAMPLE_DHCP_SERVER_EN=y
diff --git a/eth_dummy_phy/idf_component.yml b/eth_dummy_phy/idf_component.yml
new file mode 100644
index 0000000..56fa25e
--- /dev/null
+++ b/eth_dummy_phy/idf_component.yml
@@ -0,0 +1,9 @@
+version: 0.5.0
+license: 'Apache-2.0'
+targets:
+ - esp32
+ - esp32p4
+description: This component creates dummy instance of Ethernet PHY and so two devices can be connected directly by EMAC to EMAC.
+url: https://github.com/espressif/esp-eth-drivers/tree/master/eth_dummy_phy
+dependencies:
+ idf: '>=5.2.2'
diff --git a/eth_dummy_phy/include/esp_eth_phy_dummy.h b/eth_dummy_phy/include/esp_eth_phy_dummy.h
new file mode 100644
index 0000000..cea6bf7
--- /dev/null
+++ b/eth_dummy_phy/include/esp_eth_phy_dummy.h
@@ -0,0 +1,27 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include "esp_eth_phy.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+* @brief Create a dummy PHY instance
+*
+* @param[in] config: configuration of PHY
+*
+* @return
+* - instance: create PHY instance successfully
+* - NULL: create PHY instance failed because some error occurred
+*/
+esp_eth_phy_t *esp_eth_phy_new_dummy(const eth_phy_config_t *config);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/eth_dummy_phy/src/esp_eth_phy_dummy.c b/eth_dummy_phy/src/esp_eth_phy_dummy.c
new file mode 100644
index 0000000..90e9ed2
--- /dev/null
+++ b/eth_dummy_phy/src/esp_eth_phy_dummy.c
@@ -0,0 +1,188 @@
+/*
+ * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include
+#include
+#include
+#include "esp_log.h"
+#include "esp_check.h"
+#include "esp_eth_driver.h"
+#include "driver/gpio.h"
+
+typedef struct {
+ esp_eth_phy_t parent;
+ esp_eth_mediator_t *eth;
+ int reset_gpio_num;
+ eth_link_t link;
+ eth_speed_t speed;
+ eth_duplex_t duplex;
+} phy_dummy_t;
+
+static const char *TAG = "dummy_phy";
+
+static esp_err_t get_link(esp_eth_phy_t *phy)
+{
+ esp_err_t ret = ESP_OK;
+ phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent);
+ esp_eth_mediator_t *eth = dummy_phy->eth;
+
+ // TODO: check if EMAC is working (e.g. External CLK is present)
+
+ if (dummy_phy->link == ETH_LINK_DOWN) {
+ dummy_phy->link = ETH_LINK_UP;
+
+ ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_SPEED, (void *)dummy_phy->speed), err, TAG, "change speed failed");
+ ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_DUPLEX, (void *)dummy_phy->duplex), err, TAG, "change duplex failed");
+
+ bool peer_pause_ability = false; // always false
+ ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_PAUSE, (void *)peer_pause_ability), err, TAG, "change pause ability failed");
+
+ ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_LINK, (void *)dummy_phy->link), err, TAG, "change link failed");
+ }
+
+err:
+ return ret;
+}
+
+static esp_err_t set_link(esp_eth_phy_t *phy, eth_link_t link)
+{
+ esp_err_t ret = ESP_OK;
+ phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent);
+ esp_eth_mediator_t *eth = dummy_phy->eth;
+
+ if (dummy_phy->link != link) {
+ dummy_phy->link = link;
+ // link status changed, inmiedately report to upper layers
+ ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_LINK, (void *)dummy_phy->link), err, TAG, "change link failed");
+ }
+err:
+ return ret;
+}
+
+static esp_err_t set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *eth)
+{
+ esp_err_t ret = ESP_OK;
+ ESP_GOTO_ON_FALSE(eth, ESP_ERR_INVALID_ARG, err, TAG, "mediator can't be null");
+ phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent);
+ dummy_phy->eth = eth;
+err:
+ return ret;
+}
+
+static esp_err_t reset_hw(esp_eth_phy_t *phy)
+{
+ phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent);
+
+ if (dummy_phy->reset_gpio_num >= 0) {
+ esp_rom_gpio_pad_select_gpio(dummy_phy->reset_gpio_num);
+ gpio_set_direction(dummy_phy->reset_gpio_num, GPIO_MODE_OUTPUT);
+ gpio_set_level(dummy_phy->reset_gpio_num, 0);
+ esp_rom_delay_us(100);
+ gpio_set_level(dummy_phy->reset_gpio_num, 1);
+ }
+ return ESP_OK;
+}
+
+static esp_err_t autonego_ctrl(esp_eth_phy_t *phy, eth_phy_autoneg_cmd_t cmd, bool *autonego_en_stat)
+{
+ switch (cmd) {
+ case ESP_ETH_PHY_AUTONEGO_RESTART:
+ // Fallthrough
+ case ESP_ETH_PHY_AUTONEGO_EN:
+ // Fallthrough
+ case ESP_ETH_PHY_AUTONEGO_DIS:
+ return ESP_ERR_NOT_SUPPORTED; // no autonegotiation operations are supported
+ case ESP_ETH_PHY_AUTONEGO_G_STAT:
+ // report that auto-negotiation is not supported
+ *autonego_en_stat = false;
+ break;
+ default:
+ return ESP_ERR_INVALID_ARG;
+ }
+ return ESP_OK;
+}
+
+static esp_err_t set_speed(esp_eth_phy_t *phy, eth_speed_t speed)
+{
+ phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent);
+ dummy_phy->link = ETH_LINK_DOWN;
+ dummy_phy->speed = speed;
+ get_link(phy); // propagate the change to higher layer
+ return ESP_OK;
+}
+
+static esp_err_t set_duplex(esp_eth_phy_t *phy, eth_duplex_t duplex)
+{
+ phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent);
+ dummy_phy->link = ETH_LINK_DOWN;
+ dummy_phy->duplex = duplex;
+ get_link(phy); // propagate the change to higher layer
+ return ESP_OK;
+}
+
+static esp_err_t do_nothing(esp_eth_phy_t *phy)
+{
+ return ESP_OK;
+}
+
+static esp_err_t do_nothing_arg_bool(esp_eth_phy_t *phy, bool option)
+{
+ return ESP_OK;
+}
+
+static esp_err_t do_nothing_arg_uint32(esp_eth_phy_t *phy, uint32_t option)
+{
+ return ESP_OK;
+}
+
+static esp_err_t do_nothing_arg_uint32p(esp_eth_phy_t *phy, uint32_t *option)
+{
+ return ESP_OK;
+}
+
+static esp_err_t del(esp_eth_phy_t *phy)
+{
+ free(phy);
+ return ESP_OK;
+}
+
+esp_eth_phy_t *esp_eth_phy_new_dummy(const eth_phy_config_t *config)
+{
+ esp_eth_phy_t *ret = NULL;
+ phy_dummy_t *dummy_phy = calloc(1, sizeof(phy_dummy_t));
+ ESP_GOTO_ON_FALSE(dummy_phy, NULL, err, TAG, "calloc dummy_phy failed");
+
+ dummy_phy->link = ETH_LINK_DOWN;
+ // default link configuration
+ dummy_phy->speed = ETH_SPEED_100M;
+ dummy_phy->duplex = ETH_DUPLEX_FULL;
+
+ dummy_phy->reset_gpio_num = config->reset_gpio_num;
+
+ dummy_phy->parent.reset = do_nothing;
+ dummy_phy->parent.reset_hw = reset_hw;
+ dummy_phy->parent.init = do_nothing;
+ dummy_phy->parent.deinit = do_nothing;
+ dummy_phy->parent.set_mediator = set_mediator;
+ dummy_phy->parent.autonego_ctrl = autonego_ctrl;
+ dummy_phy->parent.pwrctl = do_nothing_arg_bool;
+ dummy_phy->parent.get_addr = do_nothing_arg_uint32p;
+ dummy_phy->parent.set_addr = do_nothing_arg_uint32;
+ dummy_phy->parent.advertise_pause_ability = do_nothing_arg_uint32;
+ dummy_phy->parent.loopback = do_nothing_arg_bool;
+ dummy_phy->parent.set_speed = set_speed;
+ dummy_phy->parent.set_duplex = set_duplex;
+ dummy_phy->parent.del = del;
+ dummy_phy->parent.get_link = get_link;
+ dummy_phy->parent.set_link = set_link;
+ dummy_phy->parent.custom_ioctl = NULL;
+
+ return &dummy_phy->parent;
+err:
+ if (dummy_phy != NULL) {
+ free(dummy_phy);
+ }
+ return ret;
+}