diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a55a3d3..ce74f74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,12 @@ jobs: strategy: matrix: idf_ver: ["latest"] - example: ["ksz8863/examples/simple_switch", "ksz8863/examples/switch_mode", "ksz8863/examples/two_ports_mode", "lan867x/examples/lan867x_tcp_sockets/client", "lan867x/examples/lan867x_tcp_sockets/server"] + example: ["ksz8863/examples/simple_switch", + "ksz8863/examples/switch_mode", + "ksz8863/examples/two_ports_mode", + "lan867x/examples/lan867x_tcp_sockets/client", + "lan867x/examples/lan867x_tcp_sockets/server" + ] idf_target: ["esp32"] runs-on: ubuntu-20.04 diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index ce2cec5..26ccac9 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -14,6 +14,6 @@ jobs: - name: Upload components to the component registry uses: espressif/github-actions/upload_components@master with: - directories: "ksz8863;adin1200;lan867x;ethernet_init;eth_dummy_phy" + directories: "ksz8863;adin1200;lan867x;ch390;ethernet_init;eth_dummy_phy" namespace: "espressif" api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md index df08ff0..ad4e836 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ List of currently supported chips: - [KSZ8863](ksz8863/README.md) - [ADIN1200](adin1200/README.md) - [LAN867X](lan867x/README.md) +- [CH390](ch390/README.md) - [Dummy PHY (EMAC to EMAC)](eth_dummy_phy/README.md) ## Resources diff --git a/ch390/CMakeLists.txt b/ch390/CMakeLists.txt new file mode 100644 index 0000000..256cbb3 --- /dev/null +++ b/ch390/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "src/esp_eth_mac_ch390.c" "src/esp_eth_phy_ch390.c" + INCLUDE_DIRS "include" + REQUIRES esp_eth driver + PRIV_REQUIRES esp_timer) \ No newline at end of file diff --git a/ch390/LICENSE b/ch390/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/ch390/LICENSE @@ -0,0 +1,202 @@ + + 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/ch390/README.md b/ch390/README.md new file mode 100644 index 0000000..1edbc6e --- /dev/null +++ b/ch390/README.md @@ -0,0 +1,57 @@ +# WCH CH390H Ethernet Driver (beta) + +## Overview + +CH390 is an industrial-grade Ethernet controller IC with 10/100M Ethernet Media Transport Layer (MAC) and Physical Layer Transceiver (PHY), supporting CAT3, 4, 5 and CAT5, and 6 connections for 10BASE-T and 100BASE-TX, supporting HP Auto-MDIX, low power design, and IEEE 802.3u compliant. The CH390 has a built-in 16K byte SRAM, and supports 3.3V or 2.5V parallel interface and SPI serial interface. More information about the chip can be found in the product [datasheets](https://www.wch.cn/downloads/CH390DS1_PDF.html). + +## ESP-IDF Usage + +Add this component from [IDF Component Manager](https://components.espressif.com/) to your project using `idf.py add-dependency` and include `esp_eth_phy_ch390.h` and `esp_eth_mac_ch390.h`, + +```c +#include "esp_eth_phy_ch390.h" +#include "esp_eth_mac_ch390.h" +``` + +create a configuration instance by calling `ETH_CH390_DEFAULT_CONFIG`, + +```c +// Configure SPI interface for specific SPI module +spi_device_interface_config_t spi_devcfg = { + .mode = 0, + .clock_speed_hz = CONFIG_TCPSERVER_ETH_SPI_CLOCK_MHZ * 1000 * 1000, + .queue_size = 16, + .spics_io_num = CONFIG_TCPSERVER_ETH_SPI_CS_GPIO +}; + +eth_ch390_config_t ch390_config = ETH_CH390_DEFAULT_CONFIG(CONFIG_TCPSERVER_ETH_SPI_HOST,&spi_devcfg); +ch390_config.int_gpio_num = CONFIG_TCPSERVER_ETH_SPI_INT_GPIO; + +#if CONFIG_TCPSERVER_ETH_SPI_INT_GPIO < 0 +ch390_config.poll_period_ms = CONFIG_TCPSERVER_ETH_SPI_POLLING_MS_VAL; +#endif +``` + +create a `mac` driver instance by calling `esp_eth_mac_new_ch390`, + +```c +eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + +// To avoid stack overflow, you could choose a larger value +mac_config.rx_task_stack_size = 4096; + +// CH390 has a factory burned MAC. Therefore, configuring MAC address manually is optional. +esp_eth_mac_t *mac = esp_eth_mac_new_ch390(&ch390_config,&mac_config); +``` + +create a `phy` driver instance by calling `esp_eth_phy_new_ch390()`. + + +```c +eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + +esp_eth_phy_t *phy = esp_eth_phy_new_ch390(&phy_config); +``` + +and use the Ethernet driver as you are used to. For more information of how to use ESP-IDF Ethernet driver, visit [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_eth.html). + diff --git a/ch390/idf_component.yml b/ch390/idf_component.yml new file mode 100644 index 0000000..605e52e --- /dev/null +++ b/ch390/idf_component.yml @@ -0,0 +1,7 @@ +version: "0.2.0" +description: CH390 Ethernet Driver +url: https://github.com/espressif/esp-eth-drivers/tree/master/ch390 +dependencies: + idf: ">=5.1" +examples: + - path: ../common_examples/ \ No newline at end of file diff --git a/ch390/include/ch390.h b/ch390/include/ch390.h new file mode 100644 index 0000000..72b6ce9 --- /dev/null +++ b/ch390/include/ch390.h @@ -0,0 +1,238 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2023-2024 NanjingQinhengMicroelectronics CO LTD + * SPDX-FileContributor: 2024 Sergey Kharenko + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************** + * Register definition + */ + +#define CH390_NCR 0x00 // Network control reg +#define NCR_WAKEEN (1<<6) // Enable wakeup function +#define NCR_FDX (1<<3) // Duplex mode of the internal PHY +#define NCR_LBK_MAC (1<<1) // MAC loop-back +#define NCR_RST (1<<0) // Softwate reset + +#define CH390_NSR 0x01 // Network status reg +#define NSR_SPEED (1<<7) // Speed of internal PHY +#define NSR_LINKST (1<<6) // Link status of internal PHY +#define NSR_WAKEST (1<<5) // Wakeup event status +#define NSR_TX2END (1<<3) // Tx packet B complete status +#define NSR_TX1END (1<<2) // Tx packet A complete status +#define NSR_RXOV (1<<1) // Rx fifo overflow +#define NSR_RXRDY (1<<0) + +#define CH390_TCR 0x02 // Transmit control reg +#define TCR_TJDIS (1<<6) // Transmit jabber timer +#define TCR_PAD_DIS2 (1<<4) // PAD appends for packet B +#define TCR_CRC_DIS2 (1<<3) // CRC appends for packet B +#define TCR_PAD_DIS1 (1<<2) // PAD appends for packet A +#define TCR_CRC_DIS1 (1<<1) // CRC appends for packet A +#define TCR_TXREQ (1<<0) // Tx request + +#define CH390_TSRA 0x03 // Transmit status reg A +#define CH390_TSRB 0x04 // Transmit status reg B +#define TSR_TJTO (1<<7) // Transmit jabber time out +#define TSR_LC (1<<6) // Loss of carrier +#define TSR_NC (1<<5) // No carrier +#define TSR_LCOL (1<<4) // Late collision +#define TSR_COL (1<<3) // Collision packet +#define TSR_EC (1<<2) // Excessive collision + +#define CH390_RCR 0x05 // Receive control reg +#define RCR_DEFAULT 0x00 // Default settings +#define RCR_WTDIS (1<<6) // Disable 2048 bytes watch dog +#define RCR_DIS_CRC (1<<4) // Discard CRC error packet +#define RCR_ALL (1<<3) // Pass all multicast +#define RCR_RUNT (1<<2) // Pass runt packet +#define RCR_PRMSC (1<<1) // Promiscuous mode +#define RCR_RXEN (1<<0) // Enable RX + +#define CH390_RSR 0x06 // Receive status reg +#define RSR_RF (1<<7) // Rnt frame +#define RSR_MF (1<<6) // Multicast frame +#define RSR_LCS (1<<5) // Late collision seen +#define RSR_RWTO (1<<4) // Receive watchdog time-out +#define RSR_PLE (1<<3) // Physical layer error +#define RSR_AE (1<<2) // Alignment error +#define RSR_CE (1<<1) // CRC error +#define RSR_FOE (1<<0) // FIFO overflow error + +//Receive status error mask(default) +#define RSR_ERR_MASK (RSR_RF | RSR_LCS | RSR_RWTO | RSR_PLE | \ + RSR_AE | RSR_CE | RSR_FOE) + + +#define CH390_ROCR 0x07 // Receive overflow count reg +#define CH390_BPTR 0x08 // Back pressure threshold reg +#define CH390_FCTR 0x09 // Flow control threshold reg +#define FCTR_HWOT(ot) (( ot & 0xf ) << 4) +#define FCTR_LWOT(ot) ( ot & 0xf ) + +#define CH390_FCR 0x0A // Transmit/Receive flow control reg +#define FCR_FLOW_ENABLE (0x39) // Enable Flow Control + +#define CH390_EPCR 0x0B // EEPROM or PHY control reg +#define EPCR_REEP (1<<5) // Reload EEPROM +#define EPCR_WEP (1<<4) // Write EEPROM enable +#define EPCR_EPOS (1<<3) // EEPROM or PHY operation select +#define EPCR_ERPRR (1<<2) // EEPROM or PHY read command +#define EPCR_ERPRW (1<<1) // EEPROM or PHY write command +#define EPCR_ERRE (1<<0) // EEPROM or PHY access status + +#define CH390_EPAR 0x0C // EEPROM or PHY address reg + +#define CH390_EPDRL 0x0D // EEPROM or PHY low byte data reg +#define CH390_EPDRH 0x0E // EEPROM or PHY high byte data reg + +#define CH390_WCR 0x0F // Wakeup control reg +#define WCR_LINKEN (1<<5) // Link status change wakeup +#define WCR_SAMPLEEN (1<<4) // Sample frame wakeup +#define WCR_MAGICEN (1<<3) // Magic packet wakeup +#define WCR_LINKST (1<<2) // Link status change event +#define WCR_SAMPLEST (1<<1) // Sample frame event +#define WCR_MAGICST (1<<0) // Magic packet event + +#define CH390_PAR 0x10 // MAC address reg +#define CH390_MAR 0x16 // Multicast address reg +#define CH390_GPCR 0x1E // General purpose control reg +#define CH390_GPR 0x1F // General purpose reg +#define CH390_TRPAL 0x22 // Transmit read pointer low byte address reg +#define CH390_TRPAH 0x23 // Transmit read pointer high byte address reg +#define CH390_RWPAL 0x24 // Receive write pointer low byte address reg +#define CH390_RWPAH 0x25 // Receive write pointer high byte address reg +#define CH390_VIDL 0x28 // Vendor ID low byte reg +#define CH390_VIDH 0x29 // Vendor ID high byte reg +#define CH390_PIDL 0x2A // Product ID low byte reg +#define CH390_PIDH 0x2B // Product ID high byte reg +#define CH390_CHIPR 0x2C // Chip reversion reg + +#define CH390_TCR2 0x2D // Transmit control reg II +#define TCR2_RLCP (1<<6) // Retry Late Collision Packet + +#define CH390_ATCR 0x30 // Auto-Transmit control reg +#define ATCR_AUTO_TX (1<<7) // Auto-Transmit Control + +#define CH390_TCSCR 0x31 // Transmit checksum and control reg +#define TCSCR_ALL 0x1F +#define TCSCR_IPv6TCPCSE (1<<4) // IPv6 TCP checksum generation +#define TCSCR_IPv6UDPCSE (1<<3) // IPv6 UDP checksum generation +#define TCSCR_UDPCSE (1<<2) // UDP checksum generation +#define TCSCR_TCPCSE (1<<1) // TCP checksum generation +#define TCSCR_IPCSE (1<<0) // IP checksum generation + +#define CH390_RCSCSR 0x32 // Receive checksum and control reg +#define RCSCSR_UDPS (1<<7) // UDP checksum status +#define RCSCSR_TCPS (1<<6) // TCP checksum status +#define RCSCSR_IPS (1<<5) // IP checksum status +#define RCSCSR_UDPP (1<<4) // UDP packet of current received packet +#define RCSCSR_TCPP (1<<3) // TCP packet of current received packet +#define RCSCSR_IPP (1<<2) // IP packet of current received packet +#define RCSCSR_RCSEN (1<<1) // Receive checksum checking enable +#define RCSCSR_DCSE (1<<0) // Discard checksum error packet + +#define CH390_MPAR 0x33 // MII PHY address reg +#define CH390_SBCR 0x38 // SPI bus control reg + +#define CH390_INTCR 0x39 // INT pin control reg +#define INCR_TYPE_OD 0x02 // Open drain output +#define INCR_TYPE_PP 0x00 // Push pull output +#define INCR_POL_L 0x01 // Low level positive +#define INCR_POL_H 0x00 // High level positive + +#define CH390_ALNCR 0x4A // SPI alignment error count reg +#define CH390_SCCR 0x50 // System clock control reg +#define CH390_RSCCR 0x51 // Recover system clock control reg + +#define CH390_RLENCR 0x52 // Receive data pack lenth control reg +#define RLENCR_RXLEN_EN 0x80 // Enable RX data pack length filter +#define RLENCR_RXLEN_DEFAULT 0x18 // Default MAX length of RX data(div by 64) + +#define CH390_BCASTCR 0x53 // Receive broadcast control reg +#define CH390_INTCKCR 0x54 // INT pin clock output control reg + +#define CH390_MPTRCR 0x55 // Memory pointer control reg +#define MPTRCR_RST_TX (1<<1) // Reset TX Memory Pointer +#define MPTRCR_RST_RX (1<<0) // Reset RX Memory Pointer + +#define CH390_MLEDCR 0x57 // More LED control reg +#define CH390_MRCMDX 0x70 // Memory read command without address increment reg +// Memory read command without data pre-fetch and address increment reg +#define CH390_MRCMDX1 0x71 +#define CH390_MRCMD 0x72 // Memory data read command with address increment reg +#define CH390_MRRL 0x74 // Memory read low byte address reg +#define CH390_MRRH 0x75 // Memory read high byte address reg +#define CH390_MWCMDX 0x76 // Memory write command without ddress increment reg +#define CH390_MWCMD 0x78 // Memory write command +#define CH390_MWRL 0x7A // Memory write low byte address reg +#define CH390_MWRH 0x7B // Memory write high byte address reg +#define CH390_TXPLL 0x7C // Transmit pack low byte length reg +#define CH390_TXPLH 0x7D // Transmit pack high byte length reg + +#define CH390_ISR 0x7E // Interrupt status reg +#define ISR_LNKCHG (1<<5) // Link status change +#define ISR_ROO (1<<3) // Receive overflow counter overflow +#define ISR_ROS (1<<2) // Receive overflow +#define ISR_PT (1<<1) // Packet transmitted +#define ISR_PR (1<<0) // Packet received +#define ISR_CLR_STATUS (ISR_LNKCHG | ISR_ROO | ISR_ROS | ISR_PT | ISR_PR) + +#define CH390_IMR 0x7F // Interrupt mask reg +#define IMR_NONE 0x00 // Disable all interrupt +#define IMR_ALL 0xFF // Enable all interrupt +#define IMR_PAR (1<<7) // Pointer auto-return mode +#define IMR_LNKCHGI (1<<5) // Enable link status change interrupt +#define IMR_UDRUNI (1<<4) // Enable transmit under-run interrupt +#define IMR_ROOI (1<<3) // Enable receive overflow counter overflow interrupt +#define IMR_ROI (1<<2) // Enable receive overflow interrupt +#define IMR_PTI (1<<1) // Enable packet transmitted interrupt +#define IMR_PRI (1<<0) // Enable packet received interrupt + +// SPI commands +#define OPC_REG_W 0x80 // Register Write +#define OPC_REG_R 0x00 // Register Read +#define OPC_MEM_DMY_R 0x70 // Memory Dummy Read +#define OPC_MEM_WRITE 0xF8 // Memory Write +#define OPC_MEM_READ 0x72 // Memory Read + +#define CH390_SPI_RD 0 +#define CH390_SPI_WR 1 + +// GPIO +#define CH390_GPIO1 0x02 +#define CH390_GPIO2 0x04 +#define CH390_GPIO3 0x08 + + +// PHY registers +#define CH390_PHY 0x40 +#define CH390_PHY_BMCR 0x00 +#define CH390_PHY_BMSR 0x01 +#define CH390_PHY_PHYID1 0x02 +#define CH390_PHY_PHYID2 0x03 +#define CH390_PHY_ANAR 0x04 +#define CH390_PHY_ANLPAR 0x05 +#define CH390_PHY_ANER 0x06 +#define CH390_PHY_PAGE_SEL 0x1F + +// Packet status +#define CH390_PKT_NONE 0x00 /* No packet received */ +#define CH390_PKT_RDY 0x01 /* Packet ready to receive */ +#define CH390_PKT_ERR 0xFE /* Un-stable states mask */ +#define CH390_PKT_ERR_WITH_RCSEN 0xE2 /* Un-stable states mask when RCSEN = 1 */ + +#ifdef __cplusplus +} +#endif diff --git a/ch390/include/esp_eth_mac_ch390.h b/ch390/include/esp_eth_mac_ch390.h new file mode 100644 index 0000000..f165430 --- /dev/null +++ b/ch390/include/esp_eth_mac_ch390.h @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2024 Sergey Kharenko + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + */ + +#pragma once + +#include "esp_eth_com.h" +#include "esp_eth_mac.h" + +#include "esp_idf_version.h" +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#include "esp_eth_mac_spi.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief CH390 specific configuration + * + */ +typedef struct { + int int_gpio_num; /*!< Interrupt GPIO number */ + uint32_t poll_period_ms; /*!< Period in ms to poll rx status when interrupt mode is not used */ + spi_host_device_t spi_host_id; /*!< SPI peripheral (this field is invalid when custom SPI driver is defined) */ + spi_device_interface_config_t *spi_devcfg; /*!< SPI device configuration (this field is invalid when custom SPI driver is defined) */ + eth_spi_custom_driver_config_t custom_spi_driver; /*!< Custom SPI driver definitions */ +} eth_ch390_config_t; + +/** + * @brief Default CH390 specific configuration + * + */ +#define ETH_CH390_DEFAULT_CONFIG(spi_host, spi_devcfg_p) \ + { \ + .int_gpio_num = 4, \ + .spi_host_id = spi_host, \ + .spi_devcfg = spi_devcfg_p, \ + .custom_spi_driver = ETH_DEFAULT_SPI, \ + } + +/** +* @brief Create CH390 Ethernet MAC instance +* +* @param ch390_config: CH390 specific configuration +* @param mac_config: Ethernet MAC configuration +* +* @return +* - instance: create MAC instance successfully +* - NULL: create MAC instance failed because some error occurred +*/ +esp_eth_mac_t *esp_eth_mac_new_ch390(const eth_ch390_config_t *ch390_config, const eth_mac_config_t *mac_config); + +#ifdef __cplusplus +} +#endif diff --git a/ch390/include/esp_eth_phy_ch390.h b/ch390/include/esp_eth_phy_ch390.h new file mode 100644 index 0000000..24f9a51 --- /dev/null +++ b/ch390/include/esp_eth_phy_ch390.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2024 Sergey Kharenko + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + */ + +#pragma once + +#include "esp_eth_com.h" +#include "esp_eth_phy.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** +* @brief Create a PHY instance of CH390 +* +* @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_ch390(const eth_phy_config_t *config); + +#ifdef __cplusplus +} +#endif diff --git a/ch390/src/esp_eth_mac_ch390.c b/ch390/src/esp_eth_mac_ch390.c new file mode 100644 index 0000000..709e1a7 --- /dev/null +++ b/ch390/src/esp_eth_mac_ch390.c @@ -0,0 +1,934 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2024 Sergey Kharenko + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + */ + +#include +#include +#include +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include "esp_attr.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_eth_driver.h" +#include "esp_timer.h" +#include "esp_system.h" +#include "esp_intr_alloc.h" +#include "esp_heap_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include "ch390.h" +#include "esp_eth_mac_ch390.h" + +/** @note ----------------------- RX Pack Structure --------------------------- + * | 4 Bytes Frame Head | Data Area(pass to lwip) | + * | Head | Status | Length(Low) | Length(High) | ........ | + * | | + * | | + * | | Should be the value of RSR( @ref CH390_RSR). We use @ref RSR_ERR_MASK to determine + * | | whether the pack has error. + * | |------------------------------------------------------------------------------------- + * | + * | Depends on RCSEN bit( @ref RCSCSR_RCSEN) of RCSCSR( @ref CH390_RCSCSR) + * | - RCSEN = 0, the Head shoud always be 0x01 + * | - RCSEN = 1, bit 7:2 of the Head is the same as that of RCSCSR; + * | This will affect the determination of the validity of the packet. Therefore, + * | we provide discriminant masks for both cases. + * | - RCSEN = 0 ---> @ref CH390_PKT_ERR + * | - RCSEN = 1 ---> @ref CH390_PKT_ERR_WITH_RCSEN + * |---------------------------------------------------------------------------------------------- +*/ + + +static const char *TAG = "ch390.mac"; + +#define CH390_SPI_LOCK_TIMEOUT_MS (50) +#define CH390_MAC_TX_WAIT_TIMEOUT_US (1000) +#define CH390_PHY_OPERATION_TIMEOUT_US (1000) + +typedef struct { + uint8_t flag; + uint8_t status; + uint8_t length_low; + uint8_t length_high; +} ch390_rx_header_t; + +typedef struct { + spi_device_handle_t hdl; + SemaphoreHandle_t lock; +} eth_spi_info_t; + +typedef struct { + void *ctx; + void *(*init)(const void *spi_config); + esp_err_t (*deinit)(void *spi_ctx); + esp_err_t (*read)(void *spi_ctx, uint32_t cmd, uint32_t addr, void *data, uint32_t data_len); + esp_err_t (*write)(void *spi_ctx, uint32_t cmd, uint32_t addr, const void *data, uint32_t data_len); +} eth_spi_custom_driver_t; + +typedef struct { + esp_eth_mac_t parent; + esp_eth_mediator_t *eth; + eth_spi_custom_driver_t spi; + TaskHandle_t rx_task_hdl; + uint32_t sw_reset_timeout_ms; + int int_gpio_num; + esp_timer_handle_t poll_timer; + uint32_t poll_period_ms; + uint8_t addr[ETH_ADDR_LEN]; + bool flow_ctrl_enabled; + uint8_t *rx_buffer; + uint32_t rx_len; +} emac_ch390_t; + + +static inline bool CH390_SPI_LOCK(eth_spi_info_t *spi) +{ + return xSemaphoreTake(spi->lock, pdMS_TO_TICKS(CH390_SPI_LOCK_TIMEOUT_MS)) == pdTRUE; +} + +static inline bool CH390_SPI_UNLOCK(eth_spi_info_t *spi) +{ + return xSemaphoreGive(spi->lock) == pdTRUE; +} + +static void *CH390_SPI_INIT(const void *spi_config) +{ + void *ret = NULL; + eth_ch390_config_t *ch390_config = (eth_ch390_config_t *)spi_config; + eth_spi_info_t *spi = calloc(1, sizeof(eth_spi_info_t)); + ESP_GOTO_ON_FALSE(spi, NULL, err, TAG, "no memory for SPI context data"); + + /* SPI device init */ + spi_device_interface_config_t spi_devcfg; + spi_devcfg = *(ch390_config->spi_devcfg); + if (ch390_config->spi_devcfg->command_bits == 0 && ch390_config->spi_devcfg->address_bits == 0) { + /* configure default SPI frame format */ + spi_devcfg.command_bits = 1; + spi_devcfg.address_bits = 7; + } else { + ESP_GOTO_ON_FALSE(ch390_config->spi_devcfg->command_bits == 1 && ch390_config->spi_devcfg->address_bits == 7, + NULL, err, TAG, "incorrect SPI frame format (command_bits/address_bits)"); + } + ESP_GOTO_ON_FALSE(spi_bus_add_device(ch390_config->spi_host_id, &spi_devcfg, &spi->hdl) == ESP_OK, + NULL, err, TAG, "adding device to SPI host #%d failed", ch390_config->spi_host_id + 1); + + /* create mutex */ + spi->lock = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(spi->lock, NULL, err, TAG, "create lock failed"); + + ret = spi; + return ret; +err: + if (spi) { + if (spi->lock) { + vSemaphoreDelete(spi->lock); + } + free(spi); + } + return ret; +} + +static esp_err_t CH390_SPI_DEINIT(void *spi_ctx) +{ + esp_err_t ret = ESP_OK; + eth_spi_info_t *spi = (eth_spi_info_t *)spi_ctx; + + spi_bus_remove_device(spi->hdl); + vSemaphoreDelete(spi->lock); + + free(spi); + return ret; +} + +static inline esp_err_t CH390_SPI_WRITE(void *spi_ctx, uint32_t cmd, uint32_t addr, const void *value, uint32_t len) +{ + esp_err_t ret = ESP_OK; + eth_spi_info_t *spi = (eth_spi_info_t *)spi_ctx; + + spi_transaction_t trans = { + .cmd = cmd, + .addr = addr, + .length = 8 * len, + .tx_buffer = value + }; + if (CH390_SPI_LOCK(spi)) { + if (spi_device_polling_transmit(spi->hdl, &trans) != ESP_OK) { + ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); + ret = ESP_FAIL; + } + CH390_SPI_UNLOCK(spi); + } else { + ret = ESP_ERR_TIMEOUT; + } + return ret; +} + +static inline esp_err_t CH390_SPI_READ(void *spi_ctx, uint32_t cmd, uint32_t addr, void *value, uint32_t len) +{ + esp_err_t ret = ESP_OK; + eth_spi_info_t *spi = (eth_spi_info_t *)spi_ctx; + + spi_transaction_t trans = { + .cmd = cmd, + .addr = addr, + .length = 8 * len, + .rx_buffer = value + }; + if (CH390_SPI_LOCK(spi)) { + if (spi_device_polling_transmit(spi->hdl, &trans) != ESP_OK) { + ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); + ret = ESP_FAIL; + } + CH390_SPI_UNLOCK(spi); + } else { + ret = ESP_ERR_TIMEOUT; + } + return ret; +} + + +/** + * @brief write value to ch390 internal register + */ +static esp_err_t ch390_io_register_write(emac_ch390_t *emac, uint8_t reg_addr, uint8_t value) +{ + return emac->spi.write(emac->spi.ctx, CH390_SPI_WR, reg_addr, &value, 1); +} + +/** + * @brief read value from ch390 internal register + */ +static esp_err_t ch390_io_register_read(emac_ch390_t *emac, uint8_t reg_addr, uint8_t *value) +{ + return emac->spi.read(emac->spi.ctx, CH390_SPI_RD, reg_addr, value, 1); +} + +/** + * @brief write buffer to ch390 internal memory + */ +static esp_err_t ch390_io_memory_write(emac_ch390_t *emac, uint8_t *buffer, uint32_t len) +{ + return emac->spi.write(emac->spi.ctx, CH390_SPI_WR, CH390_MWCMD, buffer, len); +} + +/** + * @brief read buffer from ch390 internal memory + */ +static esp_err_t ch390_io_memory_read(emac_ch390_t *emac, uint8_t *buffer, uint32_t len) +{ + return emac->spi.read(emac->spi.ctx, CH390_SPI_RD, CH390_MRCMD, buffer, len); +} + + +IRAM_ATTR static void ch390_isr_handler(void *arg) +{ + emac_ch390_t *emac = (emac_ch390_t *)arg; + BaseType_t high_task_wakeup = pdFALSE; + /* notify ch390 task */ + vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup); + if (high_task_wakeup != pdFALSE) { + portYIELD_FROM_ISR(); + } +} + +static void ch390_poll_timer(void *arg) +{ + emac_ch390_t *emac = (emac_ch390_t *)arg; + xTaskNotifyGive(emac->rx_task_hdl); +} + +/** + * @brief read mac address from internal registers + */ +static esp_err_t ch390_get_mac_addr(emac_ch390_t *emac) +{ + esp_err_t ret = ESP_OK; + for (int i = 0; i < ETH_ADDR_LEN; i++) { + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_PAR + i, &emac->addr[i]), err, TAG, "read PAR failed"); + } + return ESP_OK; +err: + return ret; +} + +/** + * @brief set new mac address to internal registers + */ +static esp_err_t ch390_set_mac_addr(emac_ch390_t *emac) +{ + esp_err_t ret = ESP_OK; + for (int i = 0; i < 6; i++) { + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_PAR + i, emac->addr[i]), err, TAG, "write PAR failed"); + } + return ESP_OK; +err: + return ret; +} + +/** + * @brief clear multicast hash table + */ +static esp_err_t ch390_clear_multicast_table(emac_ch390_t *emac) +{ + esp_err_t ret = ESP_OK; + /* rx broadcast packet control by bit7 of MAC register 1DH */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_BCASTCR, 0x00), err, TAG, "write BCASTCR failed"); + for (int i = 0; i < 7; i++) { + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_MAR + i, 0x00), err, TAG, "write MAR failed"); + } + /* enable receive broadcast paclets */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_MAR + 7, 0x80), err, TAG, "write MAR failed"); + return ESP_OK; +err: + return ret; +} + +/** + * @brief software reset ch390 internal register + */ +static esp_err_t ch390_reset(emac_ch390_t *emac) +{ + esp_err_t ret = ESP_OK; + /* software reset */ + uint8_t ncr = NCR_RST; + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_NCR, ncr), err, TAG, "write NCR failed"); + uint32_t to = 0; + for (to = 0; to < emac->sw_reset_timeout_ms / 10; to++) { + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_NCR, &ncr), err, TAG, "read NCR failed"); + if (!(ncr & NCR_RST)) { + break; + } + vTaskDelay(pdMS_TO_TICKS(10)); + } + ESP_GOTO_ON_FALSE(to < emac->sw_reset_timeout_ms / 10, ESP_ERR_TIMEOUT, err, TAG, "reset timeout"); + + /* For CH390H, phy should be power on after software reset !*/ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_GPR, 0x00), err, TAG, "write GPR failed"); + /* mac and phy register won't be accesable within at least 1ms */ + vTaskDelay(pdMS_TO_TICKS(10)); + + return ESP_OK; +err: + return ret; +} + +/** + * @brief verify ch390 chip ID + */ +static esp_err_t ch390_verify_id(emac_ch390_t *emac) +{ + esp_err_t ret = ESP_OK; + uint8_t id[2]; + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_VIDL, &id[0]), err, TAG, "read VIDL failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_VIDH, &id[1]), err, TAG, "read VIDH failed"); + ESP_GOTO_ON_FALSE(0x1C == id[1] && 0x00 == id[0], ESP_ERR_INVALID_VERSION, err, TAG, "wrong Vendor ID"); + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_PIDL, &id[0]), err, TAG, "read PIDL failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_PIDH, &id[1]), err, TAG, "read PIDH failed"); + ESP_GOTO_ON_FALSE(0x91 == id[1] && 0x51 == id[0], ESP_ERR_INVALID_VERSION, err, TAG, "wrong Product ID"); + return ESP_OK; +err: + return ret; +} + +/** + * @brief default setup for ch390 internal registers + */ +static esp_err_t ch390_setup_default(emac_ch390_t *emac) +{ + esp_err_t ret = ESP_OK; + /* disable wakeup */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_NCR, 0x00), err, TAG, "write NCR failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_WCR, 0x00), err, TAG, "write WCR failed"); + /* stop transmitting, enable appending pad, crc for packets */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_TCR, 0x00), err, TAG, "write TCR failed"); + /* stop receiving, no promiscuous mode, no runt packet(size < 64bytes), receive all multicast packets */ + /* discard long packet(size > 1522bytes) and crc error packet, enable watchdog */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_RCR, RCR_DIS_CRC | RCR_ALL), err, TAG, "write RCR failed"); + /* retry late collision packet, at most two transmit command can be issued before transmit complete */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_TCR2, TCR2_RLCP), err, TAG, "write TCR2 failed"); + /* generate checksum for UDP, TCP and IPv4 packets */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_TCSCR, TCSCR_IPCSE | TCSCR_TCPCSE | TCSCR_UDPCSE), err, TAG, "write TCSCR failed"); + /* disable check sum for receive packets */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_RCSCSR, 0x00), err, TAG, "write RCSCSR failed"); + /* interrupt pin config: push-pull output, active high */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_INTCR, 0x00), err, TAG, "write INTCR failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_INTCKCR, 0x00), err, TAG, "write INTCKCR failed"); + /* set length limitation for rx packets to 1536(64*24)*/ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_RLENCR, RLENCR_RXLEN_EN | RLENCR_RXLEN_DEFAULT), err, TAG, "write RLENCR failed"); + /* clear network status: wakeup event, tx complete */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END), err, TAG, "write NSR failed"); + return ESP_OK; +err: + return ret; +} + +static esp_err_t ch390_enable_flow_ctrl(emac_ch390_t *emac, bool enable) +{ + esp_err_t ret = ESP_OK; + if (enable) { + /* send jam pattern (duration time = 1.15ms) when rx free space < 3k bytes */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_BPTR, 0x3F), err, TAG, "write BPTR failed"); + /* flow control: high water threshold = 3k bytes, low water threshold = 8k bytes */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_FCTR, FCTR_HWOT(3) | FCTR_LWOT(8)), err, TAG, "write FCTR failed"); + /* enable flow control */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_FCR, FCR_FLOW_ENABLE), err, TAG, "write FCR failed"); + } else { + /* disable flow control */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_FCR, 0), err, TAG, "write FCR failed"); + } + return ESP_OK; +err: + return ret; +} + +static esp_err_t ch390_drop_frame(emac_ch390_t *emac, uint16_t length) +{ + esp_err_t ret = ESP_OK; + uint8_t mrrh, mrrl; + + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_MRRH, &mrrh), err, TAG, "read MDRAH failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_MRRL, &mrrl), err, TAG, "read MDRAL failed"); + + uint16_t addr = mrrh << 8 | mrrl; + /* include 4B for header */ + addr += length; + + addr = addr < 0x4000 ? addr : addr - 0x3400; + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_MRRH, addr >> 8), err, TAG, "write MDRAH failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_MRRL, addr & 0xFF), err, TAG, "write MDRAL failed"); +err: + return ret; +} + +/** + * @brief start ch390: enable interrupt and start receive + */ +static esp_err_t emac_ch390_start(esp_eth_mac_t *mac) +{ + esp_err_t ret = ESP_OK; + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + /* reset rx memory pointer */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_MPTRCR, MPTRCR_RST_RX), err, TAG, "write MPTRCR failed"); + /* clear interrupt status */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_ISR, ISR_CLR_STATUS), err, TAG, "write ISR failed"); + /* enable only Rx related interrupts as others are processed synchronously */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_IMR, IMR_PAR | IMR_PRI), err, TAG, "write IMR failed"); + /* enable rx */ + uint8_t rcr = 0; + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_RCR, &rcr), err, TAG, "read RCR failed"); + rcr |= RCR_RXEN; + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_RCR, rcr), err, TAG, "write RCR failed"); + return ESP_OK; +err: + return ret; +} + +/** + * @brief stop ch390: disable interrupt and stop receive + */ +static esp_err_t emac_ch390_stop(esp_eth_mac_t *mac) +{ + esp_err_t ret = ESP_OK; + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + /* disable interrupt */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_IMR, 0x00), err, TAG, "write IMR failed"); + /* disable rx */ + uint8_t rcr = 0; + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_RCR, &rcr), err, TAG, "read RCR failed"); + rcr &= ~RCR_RXEN; + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_RCR, rcr), err, TAG, "write RCR failed"); + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ch390_set_mediator(esp_eth_mac_t *mac, esp_eth_mediator_t *eth) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(eth, ESP_ERR_INVALID_ARG, err, TAG, "can't set mac's mediator to null"); + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + emac->eth = eth; + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ch390_write_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, uint32_t phy_reg, uint32_t reg_value) +{ + esp_err_t ret = ESP_OK; + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + /* check if phy access is in progress */ + uint8_t epcr = 0; + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_EPCR, &epcr), err, TAG, "read EPCR failed"); + ESP_GOTO_ON_FALSE(!(epcr & EPCR_ERRE), ESP_ERR_INVALID_STATE, err, TAG, "phy is busy"); + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_EPAR, (uint8_t)( CH390_PHY | phy_reg)), err, TAG, "write EPAR failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_EPDRL, (uint8_t)(reg_value & 0xFF)), err, TAG, "write EPDRL failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_EPDRH, (uint8_t)((reg_value >> 8) & 0xFF)), err, TAG, "write EPDRH failed"); + /* select PHY and select write operation */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_EPCR, EPCR_EPOS | EPCR_ERPRW), err, TAG, "write EPCR failed"); + /* polling the busy flag */ + uint32_t to = 0; + do { + esp_rom_delay_us(100); + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_EPCR, &epcr), err, TAG, "read EPCR failed"); + to += 100; + } while ((epcr & EPCR_ERRE) && to < CH390_PHY_OPERATION_TIMEOUT_US); + ESP_GOTO_ON_FALSE(!(epcr & EPCR_ERRE), ESP_ERR_TIMEOUT, err, TAG, "phy is busy"); + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ch390_read_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, uint32_t phy_reg, uint32_t *reg_value) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(reg_value, ESP_ERR_INVALID_ARG, err, TAG, "can't set reg_value to null"); + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + /* check if phy access is in progress */ + uint8_t epcr = 0; + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_EPCR, &epcr), err, TAG, "read EPCR failed"); + ESP_GOTO_ON_FALSE(!(epcr & 0x01), ESP_ERR_INVALID_STATE, err, TAG, "phy is busy"); + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_EPAR, (uint8_t)(CH390_PHY | phy_reg)), err, TAG, "write EPAR failed"); + /* Select PHY and select read operation */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_EPCR, 0x0C), err, TAG, "write EPCR failed"); + /* polling the busy flag */ + uint32_t to = 0; + do { + esp_rom_delay_us(100); + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_EPCR, &epcr), err, TAG, "read EPCR failed"); + to += 100; + } while ((epcr & EPCR_ERRE) && to < CH390_PHY_OPERATION_TIMEOUT_US); + ESP_GOTO_ON_FALSE(!(epcr & EPCR_ERRE), ESP_ERR_TIMEOUT, err, TAG, "phy is busy"); + uint8_t value_h = 0; + uint8_t value_l = 0; + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_EPDRH, &value_h), err, TAG, "read EPDRH failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_EPDRL, &value_l), err, TAG, "read EPDRL failed"); + *reg_value = (value_h << 8) | value_l; + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ch390_set_addr(esp_eth_mac_t *mac, uint8_t *addr) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(addr, ESP_ERR_INVALID_ARG, err, TAG, "can't set mac addr to null"); + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + memcpy(emac->addr, addr, 6); + ESP_GOTO_ON_ERROR(ch390_set_mac_addr(emac), err, TAG, "set mac address failed"); + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ch390_get_addr(esp_eth_mac_t *mac, uint8_t *addr) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(addr, ESP_ERR_INVALID_ARG, err, TAG, "can't set mac addr to null"); + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + memcpy(addr, emac->addr, 6); + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ch390_set_link(esp_eth_mac_t *mac, eth_link_t link) +{ + esp_err_t ret = ESP_OK; + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + switch (link) { + case ETH_LINK_UP: + ESP_GOTO_ON_ERROR(mac->start(mac), err, TAG, "ch390 start failed"); + if (emac->poll_timer) { + ESP_GOTO_ON_ERROR(esp_timer_start_periodic(emac->poll_timer, emac->poll_period_ms * 1000), + err, TAG, "start poll timer failed"); + } + break; + case ETH_LINK_DOWN: + ESP_GOTO_ON_ERROR(mac->stop(mac), err, TAG, "ch390 stop failed"); + if (emac->poll_timer) { + ESP_GOTO_ON_ERROR(esp_timer_stop(emac->poll_timer), + err, TAG, "stop poll timer failed"); + } + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "unknown link status"); + break; + } + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ch390_set_speed(esp_eth_mac_t *mac, eth_speed_t speed) +{ + esp_err_t ret = ESP_OK; + switch (speed) { + case ETH_SPEED_10M: + ESP_LOGD(TAG, "working in 10Mbps"); + break; + case ETH_SPEED_100M: + ESP_LOGD(TAG, "working in 100Mbps"); + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "unknown speed"); + break; + } + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ch390_set_duplex(esp_eth_mac_t *mac, eth_duplex_t duplex) +{ + esp_err_t ret = ESP_OK; + switch (duplex) { + case ETH_DUPLEX_HALF: + ESP_LOGD(TAG, "working in half duplex"); + break; + case ETH_DUPLEX_FULL: + ESP_LOGD(TAG, "working in full duplex"); + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "unknown duplex"); + break; + } + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ch390_set_promiscuous(esp_eth_mac_t *mac, bool enable) +{ + esp_err_t ret = ESP_OK; + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + uint8_t rcr = 0; + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_RCR, &rcr), err, TAG, "read RCR failed"); + if (enable) { + rcr |= RCR_PRMSC; + } else { + rcr &= ~RCR_PRMSC; + } + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_RCR, rcr), err, TAG, "write RCR failed"); + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ch390_enable_flow_ctrl(esp_eth_mac_t *mac, bool enable) +{ + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + emac->flow_ctrl_enabled = enable; + return ESP_OK; +} + +static esp_err_t emac_ch390_set_peer_pause_ability(esp_eth_mac_t *mac, uint32_t ability) +{ + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + // we want to enable flow control, and peer does support pause function + // then configure the MAC layer to enable flow control feature + if (emac->flow_ctrl_enabled && ability) { + ch390_enable_flow_ctrl(emac, true); + } + + else { + ch390_enable_flow_ctrl(emac, false); + ESP_LOGD(TAG, "Flow control not enabled for the link"); + } + return ESP_OK; +} + +static esp_err_t emac_ch390_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t length) +{ + esp_err_t ret = ESP_OK; + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + uint8_t tcr = 0; + + ESP_GOTO_ON_FALSE(length <= ETH_MAX_PACKET_SIZE, ESP_ERR_INVALID_ARG, err, + TAG, "frame size is too big (actual %lu, maximum %u)", + length, ETH_MAX_PACKET_SIZE); + + /* copy data to tx memory */ + ESP_GOTO_ON_ERROR(ch390_io_memory_write(emac, buf, length), err, TAG, + "write memory failed"); + + /* Check if last transmit complete */ + int64_t wait_time = esp_timer_get_time(); + do { + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_TCR, &tcr), err, TAG, + "read TCR failed"); + } while ((tcr & TCR_TXREQ) && ((esp_timer_get_time() - wait_time) < CH390_MAC_TX_WAIT_TIMEOUT_US)); + + if (tcr & TCR_TXREQ) { + ESP_LOGE(TAG, "last transmit still in progress, cannot send."); + return ESP_ERR_INVALID_STATE; + } + + /* set tx length */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_TXPLL, length & 0xFF), err, TAG, "write TXPLL failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_TXPLH, (length >> 8) & 0xFF), err, TAG, "write TXPLH failed"); + + /* issue tx polling command */ + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_TCR, &tcr), err, TAG, "read TCR failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_TCR, tcr | TCR_TXREQ), err, TAG, "write TCR failed"); + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ch390_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length) +{ + esp_err_t ret = ESP_OK; + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + + uint8_t ready; + /* dummy read, get the most updated data */ + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_MRCMDX, &ready), err, TAG, "read MRCMDX failed"); + ESP_GOTO_ON_ERROR(ch390_io_register_read(emac, CH390_MRCMDX, &ready), err, TAG, "read MRCMDX failed"); + + // if ready != 1 or 0 reset device + if (ready & CH390_PKT_ERR) { + emac_ch390_stop(mac); + esp_rom_delay_us(1000); + emac_ch390_start(mac); + + ESP_LOGE(TAG, "PACK ERR"); + return ESP_ERR_INVALID_RESPONSE; + } else { + __attribute__((aligned(4))) ch390_rx_header_t rx_header; // SPI driver needs the rx buffer 4 byte align + + if (ready & CH390_PKT_RDY) { + ESP_GOTO_ON_ERROR(ch390_io_memory_read(emac, (uint8_t *) & (rx_header), sizeof(rx_header)), + err, TAG, "peek rx header failed"); + *length = (rx_header.length_high << 8) + rx_header.length_low; + if (rx_header.status & RSR_ERR_MASK) { + ch390_drop_frame(emac, *length); + *length = 0; + return ESP_ERR_INVALID_RESPONSE; + } else if (*length > ETH_MAX_PACKET_SIZE) { + /* reset rx memory pointer */ + ESP_GOTO_ON_ERROR(ch390_io_register_write(emac, CH390_MPTRCR, MPTRCR_RST_RX), err, TAG, "reset rx pointer failed"); + return ESP_ERR_INVALID_RESPONSE; + } else { + ESP_GOTO_ON_ERROR(ch390_io_memory_read(emac, buf, *length), err, TAG, "read rx data failed"); + *length -= ETH_CRC_LEN; + } + } else { + *length = 0; + } + return ESP_OK; + } + +err: + *length = 0; + return ret; +} + +static esp_err_t emac_ch390_init(esp_eth_mac_t *mac) +{ + esp_err_t ret = ESP_OK; + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + esp_eth_mediator_t *eth = emac->eth; + if (emac->int_gpio_num >= 0) { + esp_rom_gpio_pad_select_gpio(emac->int_gpio_num); + gpio_set_direction(emac->int_gpio_num, GPIO_MODE_INPUT); + gpio_set_pull_mode(emac->int_gpio_num, GPIO_PULLDOWN_ONLY); + gpio_set_intr_type(emac->int_gpio_num, GPIO_INTR_POSEDGE); + gpio_intr_enable(emac->int_gpio_num); + gpio_isr_handler_add(emac->int_gpio_num, ch390_isr_handler, emac); + } + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_LLINIT, NULL), err, TAG, "lowlevel init failed"); + /* reset ch390 */ + ESP_GOTO_ON_ERROR(ch390_reset(emac), err, TAG, "reset ch390 failed"); + /* verify chip id */ + ESP_GOTO_ON_ERROR(ch390_verify_id(emac), err, TAG, "vefiry chip ID failed"); + /* default setup of internal registers */ + ESP_GOTO_ON_ERROR(ch390_setup_default(emac), err, TAG, "ch390 default setup failed"); + /* clear multicast hash table */ + ESP_GOTO_ON_ERROR(ch390_clear_multicast_table(emac), err, TAG, "clear multicast table failed"); + /* get emac address from eeprom */ + ESP_GOTO_ON_ERROR(ch390_get_mac_addr(emac), err, TAG, "fetch ethernet mac address failed"); + return ESP_OK; +err: + if (emac->int_gpio_num >= 0) { + gpio_isr_handler_remove(emac->int_gpio_num); + gpio_reset_pin(emac->int_gpio_num); + } + eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); + return ret; +} + +static esp_err_t emac_ch390_deinit(esp_eth_mac_t *mac) +{ + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + esp_eth_mediator_t *eth = emac->eth; + mac->stop(mac); + if (emac->int_gpio_num >= 0) { + gpio_isr_handler_remove(emac->int_gpio_num); + gpio_reset_pin(emac->int_gpio_num); + } + if (emac->poll_timer && esp_timer_is_active(emac->poll_timer)) { + esp_timer_stop(emac->poll_timer); + } + eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); + return ESP_OK; +} + +static void emac_ch390_task(void *arg) +{ + emac_ch390_t *emac = (emac_ch390_t *)arg; + uint8_t status = 0; + uint8_t *buffer; + while (1) { + // check if the task receives any notification + if (emac->int_gpio_num >= 0) { // if in interrupt mode + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)) == 0 && // if no notification ... + gpio_get_level(emac->int_gpio_num) == 0) { // ...and no interrupt asserted + continue; // -> just continue to check again + } + } else { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } + + /* clear interrupt status */ + ch390_io_register_read(emac, CH390_ISR, &status); + ch390_io_register_write(emac, CH390_ISR, status); + /* packet received */ + if (status & ISR_PR) { + do { + if (emac->parent.receive(&emac->parent, emac->rx_buffer, &emac->rx_len) == ESP_OK) { + if (emac->rx_len == 0) { + break; + } else { + ESP_LOGD(TAG, "receive len=%lu", emac->rx_len); + + /* allocate memory and check whether allocation failed */ + buffer = malloc(emac->rx_len); + if (buffer == NULL) { + ESP_LOGE(TAG, "no memory for receive buffer"); + continue; + } + + /* pass the buffer to stack (e.g. TCP/IP layer) */ + memcpy(buffer, emac->rx_buffer, emac->rx_len); + emac->eth->stack_input(emac->eth, buffer, emac->rx_len); + } + } else { + ESP_LOGE(TAG, "frame read from module failed"); + break; + } + } while (1); + } + } + vTaskDelete(NULL); +} + +static esp_err_t emac_ch390_del(esp_eth_mac_t *mac) +{ + emac_ch390_t *emac = __containerof(mac, emac_ch390_t, parent); + if (emac->poll_timer) { + esp_timer_delete(emac->poll_timer); + } + vTaskDelete(emac->rx_task_hdl); + emac->spi.deinit(emac->spi.ctx); + heap_caps_free(emac->rx_buffer); + free(emac); + return ESP_OK; +} + +esp_eth_mac_t *esp_eth_mac_new_ch390(const eth_ch390_config_t *ch390_config, const eth_mac_config_t *mac_config) +{ + esp_eth_mac_t *ret = NULL; + emac_ch390_t *emac = NULL; + ESP_GOTO_ON_FALSE(ch390_config, NULL, err, TAG, "can't set ch390 specific config to null"); + ESP_GOTO_ON_FALSE(mac_config, NULL, err, TAG, "can't set mac config to null"); + emac = calloc(1, sizeof(emac_ch390_t)); + ESP_GOTO_ON_FALSE(emac, NULL, err, TAG, "calloc emac failed"); + /* ch390 receive is driven by interrupt or timer signal */ + ESP_GOTO_ON_FALSE((ch390_config->int_gpio_num >= 0) != (ch390_config->poll_period_ms > 0), NULL, err, TAG, "invalid configuration argument combination"); + /* bind methods and attributes */ + emac->sw_reset_timeout_ms = mac_config->sw_reset_timeout_ms; + emac->int_gpio_num = ch390_config->int_gpio_num; + emac->poll_period_ms = ch390_config->poll_period_ms; + emac->parent.set_mediator = emac_ch390_set_mediator; + emac->parent.init = emac_ch390_init; + emac->parent.deinit = emac_ch390_deinit; + emac->parent.start = emac_ch390_start; + emac->parent.stop = emac_ch390_stop; + emac->parent.del = emac_ch390_del; + emac->parent.write_phy_reg = emac_ch390_write_phy_reg; + emac->parent.read_phy_reg = emac_ch390_read_phy_reg; + emac->parent.set_addr = emac_ch390_set_addr; + emac->parent.get_addr = emac_ch390_get_addr; + emac->parent.set_speed = emac_ch390_set_speed; + emac->parent.set_duplex = emac_ch390_set_duplex; + emac->parent.set_link = emac_ch390_set_link; + emac->parent.set_promiscuous = emac_ch390_set_promiscuous; + emac->parent.set_peer_pause_ability = emac_ch390_set_peer_pause_ability; + emac->parent.enable_flow_ctrl = emac_ch390_enable_flow_ctrl; + emac->parent.transmit = emac_ch390_transmit; + emac->parent.receive = emac_ch390_receive; + + if (ch390_config->custom_spi_driver.init != NULL && ch390_config->custom_spi_driver.deinit != NULL + && ch390_config->custom_spi_driver.read != NULL && ch390_config->custom_spi_driver.write != NULL) { + ESP_LOGD(TAG, "Using user's custom SPI Driver"); + emac->spi.init = ch390_config->custom_spi_driver.init; + emac->spi.deinit = ch390_config->custom_spi_driver.deinit; + emac->spi.read = ch390_config->custom_spi_driver.read; + emac->spi.write = ch390_config->custom_spi_driver.write; + /* Custom SPI driver device init */ + ESP_GOTO_ON_FALSE((emac->spi.ctx = emac->spi.init(ch390_config->custom_spi_driver.config)) != NULL, NULL, err, TAG, "SPI initialization failed"); + } else { + ESP_LOGD(TAG, "Using default SPI Driver"); + emac->spi.init = CH390_SPI_INIT; + emac->spi.deinit = CH390_SPI_DEINIT; + emac->spi.read = CH390_SPI_READ; + emac->spi.write = CH390_SPI_WRITE; + /* SPI device init */ + ESP_GOTO_ON_FALSE((emac->spi.ctx = emac->spi.init(ch390_config)) != NULL, NULL, err, TAG, "SPI initialization failed"); + } + + /* create ch390 task */ + BaseType_t core_num = tskNO_AFFINITY; + if (mac_config->flags & ETH_MAC_FLAG_PIN_TO_CORE) { + core_num = esp_cpu_get_core_id(); + } + BaseType_t xReturned = xTaskCreatePinnedToCore(emac_ch390_task, "ch390_tsk", mac_config->rx_task_stack_size, emac, + mac_config->rx_task_prio, &emac->rx_task_hdl, core_num); + ESP_GOTO_ON_FALSE(xReturned == pdPASS, NULL, err, TAG, "create ch390 task failed"); + + emac->rx_buffer = heap_caps_malloc(ETH_MAX_PACKET_SIZE, MALLOC_CAP_DMA); + ESP_GOTO_ON_FALSE(emac->rx_buffer, NULL, err, TAG, "RX buffer allocation failed"); + + if (emac->int_gpio_num < 0) { + const esp_timer_create_args_t poll_timer_args = { + .callback = ch390_poll_timer, + .name = "emac_spi_poll_timer", + .arg = emac, + .skip_unhandled_events = true + }; + ESP_GOTO_ON_FALSE(esp_timer_create(&poll_timer_args, &emac->poll_timer) == ESP_OK, NULL, err, TAG, "create poll timer failed"); + } + return &(emac->parent); + +err: + if (emac) { + if (emac->rx_task_hdl) { + vTaskDelete(emac->rx_task_hdl); + } + if (emac->spi.ctx) { + emac->spi.deinit(emac->spi.ctx); + } + heap_caps_free(emac->rx_buffer); + free(emac); + } + return ret; +} diff --git a/ch390/src/esp_eth_phy_ch390.c b/ch390/src/esp_eth_phy_ch390.c new file mode 100644 index 0000000..9bb25a1 --- /dev/null +++ b/ch390/src/esp_eth_phy_ch390.c @@ -0,0 +1,182 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2024 Sergey Kharenko + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + */ + +#include +#include +#include +#include "esp_log.h" +#include "esp_check.h" +#include "esp_eth_phy_802_3.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_eth_phy_ch390.h" + +/** + * @warning This value is NOT the same as the datasheet!!! Hoping WCH fix it + * in the furture version! +*/ +#define CH390_INFO_OUI 0x1CDC64 + +#define CH390_INFO_MODEL 0x01 + +#define ETH_PHY_PAGE_SEL_REG_ADDR 0x1F + +typedef union { + struct { + uint32_t reserved1 : 3; + uint32_t force_link : 1; + uint32_t remote_lpbk : 1; + uint32_t pcs_lpbk : 1; + uint32_t pma_lpbk : 1; + uint32_t jabber_en : 1; + uint32_t sqe_en : 1; + uint32_t reserved2 : 7; + }; + uint32_t val; +} phy_ctl1_reg_t; + +#define ETH_PHY_CTL1_REG_ADDR 0x19 +#define ETH_PHY_CTL1_REG_PAGE 0x00 + +typedef struct { + phy_802_3_t phy_802_3; +} phy_ch390_t; + +static const char *TAG = "ch390.phy"; + +static esp_err_t ch390_update_link_duplex_speed(phy_ch390_t *ch390) +{ + esp_err_t ret = ESP_OK; + esp_eth_mediator_t *eth = ch390->phy_802_3.eth; + uint32_t addr = ch390->phy_802_3.addr; + eth_speed_t speed = ETH_SPEED_10M; + eth_duplex_t duplex = ETH_DUPLEX_HALF; + bmcr_reg_t bmcr; + bmsr_reg_t bmsr; + uint32_t peer_pause_ability = false; + anlpar_reg_t anlpar; + ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, addr, ETH_PHY_BMSR_REG_ADDR, &(bmsr.val)), err, TAG, "read BMSR failed"); + ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, addr, ETH_PHY_BMSR_REG_ADDR, &(bmsr.val)), err, TAG, "read BMSR failed"); + ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, addr, ETH_PHY_ANLPAR_REG_ADDR, &(anlpar.val)), err, TAG, "read ANLPAR failed"); + eth_link_t link = bmsr.link_status ? ETH_LINK_UP : ETH_LINK_DOWN; + /* check if link status changed */ + if (ch390->phy_802_3.link_status != link) { + /* when link up, read negotiation result */ + if (link == ETH_LINK_UP) { + ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)), err, TAG, "read BMCR failed"); + if (bmcr.speed_select) { + speed = ETH_SPEED_100M; + } else { + speed = ETH_SPEED_10M; + } + if (bmcr.duplex_mode) { + duplex = ETH_DUPLEX_FULL; + } else { + duplex = ETH_DUPLEX_HALF; + } + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_SPEED, (void *)speed), err, TAG, "change speed failed"); + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_DUPLEX, (void *)duplex), err, TAG, "change duplex failed"); + /* if we're in duplex mode, and peer has the flow control ability */ + if (duplex == ETH_DUPLEX_FULL && anlpar.symmetric_pause) { + peer_pause_ability = 1; + } else { + peer_pause_ability = 0; + } + 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 *)link), err, TAG, "change link failed"); + ch390->phy_802_3.link_status = link; + } + return ESP_OK; +err: + return ret; +} + +static esp_err_t ch390_get_link(esp_eth_phy_t *phy) +{ + esp_err_t ret = ESP_OK; + phy_ch390_t *ch390 = __containerof(esp_eth_phy_into_phy_802_3(phy), phy_ch390_t, phy_802_3); + /* Update information about link, speed, duplex */ + ESP_GOTO_ON_ERROR(ch390_update_link_duplex_speed(ch390), err, TAG, "update link duplex speed failed"); + return ESP_OK; +err: + return ret; +} + +static esp_err_t ch390_loopback(esp_eth_phy_t *phy, bool enable) +{ + esp_err_t ret = ESP_OK; + phy_802_3_t *phy_802_3 = esp_eth_phy_into_phy_802_3(phy); + esp_eth_mediator_t *eth = phy_802_3->eth; + /* Set Loopback function */ + // Enable PMA loopback in PHY_Control1 register + bmcr_reg_t bmcr; + phy_ctl1_reg_t phy_ctl1; + ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, phy_802_3->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)), err, TAG, "read BMCR failed"); + ESP_GOTO_ON_ERROR(eth->phy_reg_write(eth, phy_802_3->addr, ETH_PHY_PAGE_SEL_REG_ADDR, ETH_PHY_CTL1_REG_PAGE), + err, TAG, "write PAGE_SEL failed"); + ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, phy_802_3->addr, ETH_PHY_CTL1_REG_ADDR, &(phy_ctl1.val)), err, TAG, "read PHY_CTL1 failed"); + + if (enable) { + bmcr.en_loopback = 1; + phy_ctl1.pma_lpbk = 1; + } else { + bmcr.en_loopback = 0; + phy_ctl1.pma_lpbk = 0; + } + ESP_GOTO_ON_ERROR(eth->phy_reg_write(eth, phy_802_3->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val), err, TAG, "write BMCR failed"); + ESP_GOTO_ON_ERROR(eth->phy_reg_write(eth, phy_802_3->addr, ETH_PHY_PAGE_SEL_REG_ADDR, ETH_PHY_CTL1_REG_PAGE), + err, TAG, "write PAGE_SEL failed"); + ESP_GOTO_ON_ERROR(eth->phy_reg_write(eth, phy_802_3->addr, ETH_PHY_CTL1_REG_ADDR, phy_ctl1.val), err, TAG, "write PHY_CTL1 failed"); + return ESP_OK; +err: + return ret; +} + +static esp_err_t ch390_init(esp_eth_phy_t *phy) +{ + esp_err_t ret = ESP_OK; + phy_802_3_t *phy_802_3 = esp_eth_phy_into_phy_802_3(phy); + + /* Basic PHY init */ + ESP_GOTO_ON_ERROR(esp_eth_phy_802_3_basic_phy_init(phy_802_3), err, TAG, "failed to init PHY"); + + /* Check PHY ID */ + uint32_t oui; + uint8_t model; + ESP_GOTO_ON_ERROR(esp_eth_phy_802_3_read_oui(phy_802_3, &oui), err, TAG, "read OUI failed"); + ESP_GOTO_ON_ERROR(esp_eth_phy_802_3_read_manufac_info(phy_802_3, &model, NULL), err, TAG, "read manufacturer's info failed"); + ESP_GOTO_ON_FALSE(oui == CH390_INFO_OUI && model == CH390_INFO_MODEL, ESP_FAIL, err, TAG, "wrong chip ID"); + + return ESP_OK; +err: + return ret; +} + +esp_eth_phy_t *esp_eth_phy_new_ch390(const eth_phy_config_t *config) +{ + esp_eth_phy_t *ret = NULL; + phy_ch390_t *ch390 = calloc(1, sizeof(phy_ch390_t)); + ESP_GOTO_ON_FALSE(ch390, NULL, err, TAG, "calloc ch390 failed"); + ESP_GOTO_ON_FALSE(esp_eth_phy_802_3_obj_config_init(&ch390->phy_802_3, config) == ESP_OK, + NULL, err, TAG, "configuration initialization of PHY 802.3 failed"); + + // redefine functions which need to be customized for sake of ch390 + ch390->phy_802_3.parent.init = ch390_init; + ch390->phy_802_3.parent.get_link = ch390_get_link; + ch390->phy_802_3.parent.loopback = ch390_loopback; + + return &ch390->phy_802_3.parent; +err: + if (ch390 != NULL) { + free(ch390); + } + return ret; +} diff --git a/common_examples/simple-ethernet/sdkconfig.defaults.ch390 b/common_examples/simple-ethernet/sdkconfig.defaults.ch390 new file mode 100644 index 0000000..f1938da --- /dev/null +++ b/common_examples/simple-ethernet/sdkconfig.defaults.ch390 @@ -0,0 +1,8 @@ +CONFIG_ETHERNET_INTERNAL_SUPPORT=n + +CONFIG_ETHERNET_SPI_SUPPORT=y +CONFIG_ETHERNET_SPI_NUMBER=1 +CONFIG_ETHERNET_USE_DM9051=n +CONFIG_ETHERNET_USE_KSZ8851SNL=n +CONFIG_ETHERNET_USE_W5500=n +CONFIG_ETHERNET_USE_CH390=y diff --git a/ethernet_init/Kconfig.projbuild b/ethernet_init/Kconfig.projbuild index 559be8b..09f6b81 100644 --- a/ethernet_init/Kconfig.projbuild +++ b/ethernet_init/Kconfig.projbuild @@ -141,11 +141,15 @@ menu "Ethernet Configuration" select ETH_SPI_ETHERNET_W5500 help Select external SPI-Ethernet module (W5500). + + config ETHERNET_USE_CH390 + bool "CH390 Module" + help + Select external SPI-Ethernet module (CH390). endchoice config ETHERNET_SPI_HOST - int "SPI Host Number" - range 0 2 + int "SPI Host Number" default 1 help Set the SPI host used to communicate with the SPI Ethernet Controller. diff --git a/ethernet_init/README.md b/ethernet_init/README.md index 3c18a2b..4a02022 100644 --- a/ethernet_init/README.md +++ b/ethernet_init/README.md @@ -13,7 +13,8 @@ Supported devices are: * DM9051 Module * KSZ8851SNL Module * W5500 Module - + + * CH390 Module ## API ### Steps to use the component in an example code: diff --git a/ethernet_init/ethernet_init.c b/ethernet_init/ethernet_init.c index e940b8d..45a15d5 100644 --- a/ethernet_init/ethernet_init.c +++ b/ethernet_init/ethernet_init.c @@ -16,10 +16,15 @@ #if CONFIG_ETH_USE_SPI_ETHERNET #include "driver/spi_master.h" #endif // CONFIG_ETH_USE_SPI_ETHERNET + #if CONFIG_ETHERNET_PHY_LAN867X #include "esp_eth_phy_lan867x.h" #endif // CONFIG_ETHERNET_PHY_LAN867X +#if CONFIG_ETHERNET_USE_CH390 +#include "esp_eth_mac_ch390.h" +#include "esp_eth_phy_ch390.h" +#endif #if CONFIG_ETHERNET_SPI_NUMBER #define SPI_ETHERNETS_NUM CONFIG_ETHERNET_SPI_NUMBER @@ -303,7 +308,14 @@ static esp_eth_handle_t eth_init_spi(spi_eth_module_config_t *spi_eth_module_con dev_out->mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); dev_out->phy = esp_eth_phy_new_w5500(&phy_config); sprintf(dev_out->dev_info.name, "W5500"); -#endif //CONFIG_ETHERNET_USE_W5500 +#elif CONFIG_ETHERNET_USE_CH390 + eth_ch390_config_t ch390_config = ETH_CH390_DEFAULT_CONFIG(CONFIG_ETHERNET_SPI_HOST, &spi_devcfg); + ch390_config.int_gpio_num = spi_eth_module_config->int_gpio; + ch390_config.poll_period_ms = spi_eth_module_config->poll_period_ms; + dev_out->mac = esp_eth_mac_new_ch390(&ch390_config, &mac_config); + dev_out->phy = esp_eth_phy_new_ch390(&phy_config); + sprintf(dev_out->dev_info.name, "CH390"); +#endif // Init Ethernet driver to default and install it esp_eth_handle_t eth_handle = NULL; esp_eth_config_t eth_config_spi = ETH_DEFAULT_CONFIG(dev_out->mac, dev_out->phy); diff --git a/ethernet_init/idf_component.yml b/ethernet_init/idf_component.yml index 08d7955..9bcf08f 100644 --- a/ethernet_init/idf_component.yml +++ b/ethernet_init/idf_component.yml @@ -1,6 +1,9 @@ dependencies: idf: version: '>=5.1' + ch390: + version: '*' + override_path: '../ch390/' examples: - path: ../common_examples/ description: This component initializes Ethernet driver based on Espressif IoT Development Framework Configuration.