diff options
| -rwxr-xr-x | .github/ci/doc.sh | 1 | ||||
| -rwxr-xr-x | .github/ci/test.sh | 2 | ||||
| -rw-r--r-- | embassy-net-adin1110/Cargo.toml | 41 | ||||
| -rw-r--r-- | embassy-net-adin1110/README.md | 55 | ||||
| -rw-r--r-- | embassy-net-adin1110/src/crc32.rs | 358 | ||||
| -rw-r--r-- | embassy-net-adin1110/src/crc8.rs | 53 | ||||
| -rw-r--r-- | embassy-net-adin1110/src/lib.rs | 1046 | ||||
| -rw-r--r-- | embassy-net-adin1110/src/mdio.rs | 175 | ||||
| -rw-r--r-- | embassy-net-adin1110/src/phy.rs | 142 | ||||
| -rw-r--r-- | embassy-net-adin1110/src/regs.rs | 408 | ||||
| -rw-r--r-- | examples/stm32l4/.cargo/config.toml | 2 | ||||
| -rw-r--r-- | examples/stm32l4/Cargo.toml | 12 | ||||
| -rw-r--r-- | examples/stm32l4/src/bin/spe_adin1110_http_server.rs | 438 |
13 files changed, 2730 insertions, 3 deletions
diff --git a/.github/ci/doc.sh b/.github/ci/doc.sh index 57184dc1d..c317a12e3 100755 --- a/.github/ci/doc.sh +++ b/.github/ci/doc.sh | |||
| @@ -39,6 +39,7 @@ docserver-builder -i ./embassy-net-wiznet -o webroot/crates/embassy-net-wiznet/g | |||
| 39 | docserver-builder -i ./embassy-net-enc28j60 -o webroot/crates/embassy-net-enc28j60/git.zup | 39 | docserver-builder -i ./embassy-net-enc28j60 -o webroot/crates/embassy-net-enc28j60/git.zup |
| 40 | docserver-builder -i ./embassy-net-esp-hosted -o webroot/crates/embassy-net-esp-hosted/git.zup | 40 | docserver-builder -i ./embassy-net-esp-hosted -o webroot/crates/embassy-net-esp-hosted/git.zup |
| 41 | docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/git.zup --output-static webroot/static | 41 | docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/git.zup --output-static webroot/static |
| 42 | docserver-builder -i ./embassy-net-adin1110 -o webroot/crates/embassy-net-adin1110/git.zup | ||
| 42 | 43 | ||
| 43 | export KUBECONFIG=/ci/secrets/kubeconfig.yml | 44 | export KUBECONFIG=/ci/secrets/kubeconfig.yml |
| 44 | POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) | 45 | POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) |
diff --git a/.github/ci/test.sh b/.github/ci/test.sh index 2892bcf8d..04f4fc7c4 100755 --- a/.github/ci/test.sh +++ b/.github/ci/test.sh | |||
| @@ -28,3 +28,5 @@ cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --featu | |||
| 28 | cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f429vg,exti,time-driver-any,exti | 28 | cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f429vg,exti,time-driver-any,exti |
| 29 | cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f732ze,exti,time-driver-any,exti | 29 | cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f732ze,exti,time-driver-any,exti |
| 30 | cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f769ni,exti,time-driver-any,exti | 30 | cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f769ni,exti,time-driver-any,exti |
| 31 | |||
| 32 | cargo test --manifest-path ./embassy-net-adin1110/Cargo.toml | ||
diff --git a/embassy-net-adin1110/Cargo.toml b/embassy-net-adin1110/Cargo.toml new file mode 100644 index 000000000..e74fb7cd4 --- /dev/null +++ b/embassy-net-adin1110/Cargo.toml | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | [package] | ||
| 2 | name = "embassy-net-adin1110" | ||
| 3 | version = "0.1.0" | ||
| 4 | description = "embassy-net driver for the ADIN1110 ethernet chip" | ||
| 5 | keywords = ["embedded", "ADIN1110", "embassy-net", "embedded-hal-async", "ethernet", "async"] | ||
| 6 | categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] | ||
| 7 | license = "MIT OR Apache-2.0" | ||
| 8 | edition = "2021" | ||
| 9 | |||
| 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
| 11 | |||
| 12 | [dependencies] | ||
| 13 | heapless = "0.7.16" | ||
| 14 | defmt = { version = "0.3", optional = true } | ||
| 15 | log = { version = "0.4.4", default-features = false, optional = true } | ||
| 16 | embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } | ||
| 17 | embedded-hal-async = { version = "=1.0.0-rc.1" } | ||
| 18 | embedded-hal-bus = { version = "=0.1.0-rc.1", features = ["async"] } | ||
| 19 | embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } | ||
| 20 | embassy-time = { version = "0.1.0" } | ||
| 21 | embassy-futures = { version = "0.1.0", path = "../embassy-futures" } | ||
| 22 | bitfield = "0.14.0" | ||
| 23 | |||
| 24 | |||
| 25 | [dev-dependencies] | ||
| 26 | # reenable when https://github.com/dbrgn/embedded-hal-mock/pull/86 is merged. | ||
| 27 | #embedded-hal-mock = { git = "https://github.com/dbrgn/embedded-hal-mock", branch = "1-alpha", features = ["embedded-hal-async", "eh1"] }] } | ||
| 28 | embedded-hal-mock = { git = "https://github.com/newAM/embedded-hal-mock", branch = "eh1-rc.1", features = ["embedded-hal-async", "eh1"] } | ||
| 29 | crc = "3.0.1" | ||
| 30 | env_logger = "0.10" | ||
| 31 | critical-section = { version = "1.1.1", features = ["std"] } | ||
| 32 | futures-test = "0.3.17" | ||
| 33 | |||
| 34 | [features] | ||
| 35 | default = [ ] | ||
| 36 | defmt = [ "dep:defmt" ] | ||
| 37 | |||
| 38 | [package.metadata.embassy_docs] | ||
| 39 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-adin1110-v$VERSION/embassy-net-adin1110/src/" | ||
| 40 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-adin1110/src/" | ||
| 41 | target = "thumbv7em-none-eabi" | ||
diff --git a/embassy-net-adin1110/README.md b/embassy-net-adin1110/README.md new file mode 100644 index 000000000..3c2804183 --- /dev/null +++ b/embassy-net-adin1110/README.md | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | # SPE ADIN1110 `embassy-net` integration | ||
| 2 | |||
| 3 | [`embassy-net`](https://crates.io/crates/embassy-net) integration for the `Analog ADIN1110` SPI SPE ethernet chips. | ||
| 4 | |||
| 5 | ## What is SPE or Single Pair Ethernet / 10 BASE-T1L | ||
| 6 | |||
| 7 | SPE stands for Single Pair Ethernet. As the names implies, SPE uses differential signalling with 2 wires (a twisted-pair) in a cable as the physical medium. | ||
| 8 | SPE is full-duplex - it can transmit and receive ethernet packets at the same time. SPE is still ethernet, only the physical layer is different. | ||
| 9 | |||
| 10 | SPE also supports [`PoDL (Power over Data Line)`](https://www.ti.com/lit/an/snla395/snla395.pdf), power delivery from 0.5 up to 50 Watts, similar to [`PoE`](https://en.wikipedia.org/wiki/Power_over_Ethernet), but an additional hardware and handshake protocol are needed. | ||
| 11 | |||
| 12 | SPE has many link speeds but only `10 BASE-T1L` is able to reach cable lengths up to 1000 meters in `2.4 Vpp` transmit amplitude. | ||
| 13 | Currently in 2023, none of the standards are compatible with each other. | ||
| 14 | Thus `10 BASE-T1L` won't work with a `10 BASE-T1S`, `100 BASE-T1` or any standard `x BASE-T`. | ||
| 15 | |||
| 16 | In the industry SPE is also called [`APL (Advanced Physical Layer)`](https://www.ethernet-apl.org), and is based on the `10 BASE-T1L` standard. | ||
| 17 | |||
| 18 | APL can be used in [`intrinsic safety applications/explosion hazardous areas`](https://en.wikipedia.org/wiki/Electrical_equipment_in_hazardous_areas) which has its own name and standard called [`2-WISE (2-wire intrinsically safe ethernet) IEC TS 60079-47:2021`](https://webstore.iec.ch/publication/64292). | ||
| 19 | |||
| 20 | `10 BASE-T1L` and `ADIN1110` are designed to support intrinsic safety applications. The power supply energy is fixed and PDoL is not supported. | ||
| 21 | |||
| 22 | ## Supported SPI modes | ||
| 23 | |||
| 24 | `ADIN1110` supports two SPI modes. `Generic` and [`OPEN Alliance 10BASE-T1x MAC-PHY serial interface`](https://opensig.org/download/document/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf) | ||
| 25 | |||
| 26 | Both modes support with and without additional CRC. | ||
| 27 | Currently only `Generic` SPI with or without CRC is supported. | ||
| 28 | |||
| 29 | *NOTE:* SPI Mode is selected by the hardware pins `SPI_CFG0` and `SPI_CFG1`. Software can't detect nor change the mode. | ||
| 30 | |||
| 31 | ## Hardware | ||
| 32 | |||
| 33 | - Tested on [`Analog Devices EVAL-ADIN1110EBZ`](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adin1110.html) with an `STM32L4S5QII3P`, see [`spe_adin1110_http_server`](../examples/stm32l4/src/bin/spe_adin1110_http_server.rs) dor an example. | ||
| 34 | - [`SparkFun MicroMod Single Pair Ethernet Function Board`](https://www.sparkfun.com/products/19038) or [`SparkFun MicroMod Single Pair Ethernet Kit`](https://www.sparkfun.com/products/19628), supporting multiple microcontrollers. **Make sure to check if it's a microcontroller that is supported by Embassy!** | ||
| 35 | |||
| 36 | ## Other SPE chips | ||
| 37 | |||
| 38 | * [`Analog ADIN2111`](https://www.analog.com/en/products/adin2111.html) 2 Port SPI version. Can work with this driver. | ||
| 39 | * [`Analog ADIN1100`](https://www.analog.com/en/products/adin1100.html) RGMII version. | ||
| 40 | |||
| 41 | ## Testing | ||
| 42 | |||
| 43 | ADIN1110 library can tested on the host with a mock SPI driver. | ||
| 44 | |||
| 45 | $ `cargo test --target x86_64-unknown-linux-gnu` | ||
| 46 | |||
| 47 | ## License | ||
| 48 | |||
| 49 | This work is licensed under either of | ||
| 50 | |||
| 51 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||
| 52 | http://www.apache.org/licenses/LICENSE-2.0) | ||
| 53 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) | ||
| 54 | |||
| 55 | at your option. | ||
diff --git a/embassy-net-adin1110/src/crc32.rs b/embassy-net-adin1110/src/crc32.rs new file mode 100644 index 000000000..a3474f70a --- /dev/null +++ b/embassy-net-adin1110/src/crc32.rs | |||
| @@ -0,0 +1,358 @@ | |||
| 1 | pub const CRC32R_LOOKUP_TABLE: [u32; 256] = [ | ||
| 2 | 0x0000_0000, | ||
| 3 | 0x7707_3096, | ||
| 4 | 0xEE0E_612C, | ||
| 5 | 0x9909_51BA, | ||
| 6 | 0x076D_C419, | ||
| 7 | 0x706A_F48F, | ||
| 8 | 0xE963_A535, | ||
| 9 | 0x9E64_95A3, | ||
| 10 | 0x0EDB_8832, | ||
| 11 | 0x79DC_B8A4, | ||
| 12 | 0xE0D5_E91E, | ||
| 13 | 0x97D2_D988, | ||
| 14 | 0x09B6_4C2B, | ||
| 15 | 0x7EB1_7CBD, | ||
| 16 | 0xE7B8_2D07, | ||
| 17 | 0x90BF_1D91, | ||
| 18 | 0x1DB7_1064, | ||
| 19 | 0x6AB0_20F2, | ||
| 20 | 0xF3B9_7148, | ||
| 21 | 0x84BE_41DE, | ||
| 22 | 0x1ADA_D47D, | ||
| 23 | 0x6DDD_E4EB, | ||
| 24 | 0xF4D4_B551, | ||
| 25 | 0x83D3_85C7, | ||
| 26 | 0x136C_9856, | ||
| 27 | 0x646B_A8C0, | ||
| 28 | 0xFD62_F97A, | ||
| 29 | 0x8A65_C9EC, | ||
| 30 | 0x1401_5C4F, | ||
| 31 | 0x6306_6CD9, | ||
| 32 | 0xFA0F_3D63, | ||
| 33 | 0x8D08_0DF5, | ||
| 34 | 0x3B6E_20C8, | ||
| 35 | 0x4C69_105E, | ||
| 36 | 0xD560_41E4, | ||
| 37 | 0xA267_7172, | ||
| 38 | 0x3C03_E4D1, | ||
| 39 | 0x4B04_D447, | ||
| 40 | 0xD20D_85FD, | ||
| 41 | 0xA50A_B56B, | ||
| 42 | 0x35B5_A8FA, | ||
| 43 | 0x42B2_986C, | ||
| 44 | 0xDBBB_C9D6, | ||
| 45 | 0xACBC_F940, | ||
| 46 | 0x32D8_6CE3, | ||
| 47 | 0x45DF_5C75, | ||
| 48 | 0xDCD6_0DCF, | ||
| 49 | 0xABD1_3D59, | ||
| 50 | 0x26D9_30AC, | ||
| 51 | 0x51DE_003A, | ||
| 52 | 0xC8D7_5180, | ||
| 53 | 0xBFD0_6116, | ||
| 54 | 0x21B4_F4B5, | ||
| 55 | 0x56B3_C423, | ||
| 56 | 0xCFBA_9599, | ||
| 57 | 0xB8BD_A50F, | ||
| 58 | 0x2802_B89E, | ||
| 59 | 0x5F05_8808, | ||
| 60 | 0xC60C_D9B2, | ||
| 61 | 0xB10B_E924, | ||
| 62 | 0x2F6F_7C87, | ||
| 63 | 0x5868_4C11, | ||
| 64 | 0xC161_1DAB, | ||
| 65 | 0xB666_2D3D, | ||
| 66 | 0x76DC_4190, | ||
| 67 | 0x01DB_7106, | ||
| 68 | 0x98D2_20BC, | ||
| 69 | 0xEFD5_102A, | ||
| 70 | 0x71B1_8589, | ||
| 71 | 0x06B6_B51F, | ||
| 72 | 0x9FBF_E4A5, | ||
| 73 | 0xE8B8_D433, | ||
| 74 | 0x7807_C9A2, | ||
| 75 | 0x0F00_F934, | ||
| 76 | 0x9609_A88E, | ||
| 77 | 0xE10E_9818, | ||
| 78 | 0x7F6A_0DBB, | ||
| 79 | 0x086D_3D2D, | ||
| 80 | 0x9164_6C97, | ||
| 81 | 0xE663_5C01, | ||
| 82 | 0x6B6B_51F4, | ||
| 83 | 0x1C6C_6162, | ||
| 84 | 0x8565_30D8, | ||
| 85 | 0xF262_004E, | ||
| 86 | 0x6C06_95ED, | ||
| 87 | 0x1B01_A57B, | ||
| 88 | 0x8208_F4C1, | ||
| 89 | 0xF50F_C457, | ||
| 90 | 0x65B0_D9C6, | ||
| 91 | 0x12B7_E950, | ||
| 92 | 0x8BBE_B8EA, | ||
| 93 | 0xFCB9_887C, | ||
| 94 | 0x62DD_1DDF, | ||
| 95 | 0x15DA_2D49, | ||
| 96 | 0x8CD3_7CF3, | ||
| 97 | 0xFBD4_4C65, | ||
| 98 | 0x4DB2_6158, | ||
| 99 | 0x3AB5_51CE, | ||
| 100 | 0xA3BC_0074, | ||
| 101 | 0xD4BB_30E2, | ||
| 102 | 0x4ADF_A541, | ||
| 103 | 0x3DD8_95D7, | ||
| 104 | 0xA4D1_C46D, | ||
| 105 | 0xD3D6_F4FB, | ||
| 106 | 0x4369_E96A, | ||
| 107 | 0x346E_D9FC, | ||
| 108 | 0xAD67_8846, | ||
| 109 | 0xDA60_B8D0, | ||
| 110 | 0x4404_2D73, | ||
| 111 | 0x3303_1DE5, | ||
| 112 | 0xAA0A_4C5F, | ||
| 113 | 0xDD0D_7CC9, | ||
| 114 | 0x5005_713C, | ||
| 115 | 0x2702_41AA, | ||
| 116 | 0xBE0B_1010, | ||
| 117 | 0xC90C_2086, | ||
| 118 | 0x5768_B525, | ||
| 119 | 0x206F_85B3, | ||
| 120 | 0xB966_D409, | ||
| 121 | 0xCE61_E49F, | ||
| 122 | 0x5EDE_F90E, | ||
| 123 | 0x29D9_C998, | ||
| 124 | 0xB0D0_9822, | ||
| 125 | 0xC7D7_A8B4, | ||
| 126 | 0x59B3_3D17, | ||
| 127 | 0x2EB4_0D81, | ||
| 128 | 0xB7BD_5C3B, | ||
| 129 | 0xC0BA_6CAD, | ||
| 130 | 0xEDB8_8320, | ||
| 131 | 0x9ABF_B3B6, | ||
| 132 | 0x03B6_E20C, | ||
| 133 | 0x74B1_D29A, | ||
| 134 | 0xEAD5_4739, | ||
| 135 | 0x9DD2_77AF, | ||
| 136 | 0x04DB_2615, | ||
| 137 | 0x73DC_1683, | ||
| 138 | 0xE363_0B12, | ||
| 139 | 0x9464_3B84, | ||
| 140 | 0x0D6D_6A3E, | ||
| 141 | 0x7A6A_5AA8, | ||
| 142 | 0xE40E_CF0B, | ||
| 143 | 0x9309_FF9D, | ||
| 144 | 0x0A00_AE27, | ||
| 145 | 0x7D07_9EB1, | ||
| 146 | 0xF00F_9344, | ||
| 147 | 0x8708_A3D2, | ||
| 148 | 0x1E01_F268, | ||
| 149 | 0x6906_C2FE, | ||
| 150 | 0xF762_575D, | ||
| 151 | 0x8065_67CB, | ||
| 152 | 0x196C_3671, | ||
| 153 | 0x6E6B_06E7, | ||
| 154 | 0xFED4_1B76, | ||
| 155 | 0x89D3_2BE0, | ||
| 156 | 0x10DA_7A5A, | ||
| 157 | 0x67DD_4ACC, | ||
| 158 | 0xF9B9_DF6F, | ||
| 159 | 0x8EBE_EFF9, | ||
| 160 | 0x17B7_BE43, | ||
| 161 | 0x60B0_8ED5, | ||
| 162 | 0xD6D6_A3E8, | ||
| 163 | 0xA1D1_937E, | ||
| 164 | 0x38D8_C2C4, | ||
| 165 | 0x4FDF_F252, | ||
| 166 | 0xD1BB_67F1, | ||
| 167 | 0xA6BC_5767, | ||
| 168 | 0x3FB5_06DD, | ||
| 169 | 0x48B2_364B, | ||
| 170 | 0xD80D_2BDA, | ||
| 171 | 0xAF0A_1B4C, | ||
| 172 | 0x3603_4AF6, | ||
| 173 | 0x4104_7A60, | ||
| 174 | 0xDF60_EFC3, | ||
| 175 | 0xA867_DF55, | ||
| 176 | 0x316E_8EEF, | ||
| 177 | 0x4669_BE79, | ||
| 178 | 0xCB61_B38C, | ||
| 179 | 0xBC66_831A, | ||
| 180 | 0x256F_D2A0, | ||
| 181 | 0x5268_E236, | ||
| 182 | 0xCC0C_7795, | ||
| 183 | 0xBB0B_4703, | ||
| 184 | 0x2202_16B9, | ||
| 185 | 0x5505_262F, | ||
| 186 | 0xC5BA_3BBE, | ||
| 187 | 0xB2BD_0B28, | ||
| 188 | 0x2BB4_5A92, | ||
| 189 | 0x5CB3_6A04, | ||
| 190 | 0xC2D7_FFA7, | ||
| 191 | 0xB5D0_CF31, | ||
| 192 | 0x2CD9_9E8B, | ||
| 193 | 0x5BDE_AE1D, | ||
| 194 | 0x9B64_C2B0, | ||
| 195 | 0xEC63_F226, | ||
| 196 | 0x756A_A39C, | ||
| 197 | 0x026D_930A, | ||
| 198 | 0x9C09_06A9, | ||
| 199 | 0xEB0E_363F, | ||
| 200 | 0x7207_6785, | ||
| 201 | 0x0500_5713, | ||
| 202 | 0x95BF_4A82, | ||
| 203 | 0xE2B8_7A14, | ||
| 204 | 0x7BB1_2BAE, | ||
| 205 | 0x0CB6_1B38, | ||
| 206 | 0x92D2_8E9B, | ||
| 207 | 0xE5D5_BE0D, | ||
| 208 | 0x7CDC_EFB7, | ||
| 209 | 0x0BDB_DF21, | ||
| 210 | 0x86D3_D2D4, | ||
| 211 | 0xF1D4_E242, | ||
| 212 | 0x68DD_B3F8, | ||
| 213 | 0x1FDA_836E, | ||
| 214 | 0x81BE_16CD, | ||
| 215 | 0xF6B9_265B, | ||
| 216 | 0x6FB0_77E1, | ||
| 217 | 0x18B7_4777, | ||
| 218 | 0x8808_5AE6, | ||
| 219 | 0xFF0F_6A70, | ||
| 220 | 0x6606_3BCA, | ||
| 221 | 0x1101_0B5C, | ||
| 222 | 0x8F65_9EFF, | ||
| 223 | 0xF862_AE69, | ||
| 224 | 0x616B_FFD3, | ||
| 225 | 0x166C_CF45, | ||
| 226 | 0xA00A_E278, | ||
| 227 | 0xD70D_D2EE, | ||
| 228 | 0x4E04_8354, | ||
| 229 | 0x3903_B3C2, | ||
| 230 | 0xA767_2661, | ||
| 231 | 0xD060_16F7, | ||
| 232 | 0x4969_474D, | ||
| 233 | 0x3E6E_77DB, | ||
| 234 | 0xAED1_6A4A, | ||
| 235 | 0xD9D6_5ADC, | ||
| 236 | 0x40DF_0B66, | ||
| 237 | 0x37D8_3BF0, | ||
| 238 | 0xA9BC_AE53, | ||
| 239 | 0xDEBB_9EC5, | ||
| 240 | 0x47B2_CF7F, | ||
| 241 | 0x30B5_FFE9, | ||
| 242 | 0xBDBD_F21C, | ||
| 243 | 0xCABA_C28A, | ||
| 244 | 0x53B3_9330, | ||
| 245 | 0x24B4_A3A6, | ||
| 246 | 0xBAD0_3605, | ||
| 247 | 0xCDD7_0693, | ||
| 248 | 0x54DE_5729, | ||
| 249 | 0x23D9_67BF, | ||
| 250 | 0xB366_7A2E, | ||
| 251 | 0xC461_4AB8, | ||
| 252 | 0x5D68_1B02, | ||
| 253 | 0x2A6F_2B94, | ||
| 254 | 0xB40B_BE37, | ||
| 255 | 0xC30C_8EA1, | ||
| 256 | 0x5A05_DF1B, | ||
| 257 | 0x2D02_EF8D, | ||
| 258 | ]; | ||
| 259 | |||
| 260 | #[allow(non_camel_case_types)] | ||
| 261 | #[derive(Debug)] | ||
| 262 | pub struct ETH_FSC(pub u32); | ||
| 263 | |||
| 264 | impl ETH_FSC { | ||
| 265 | pub const CRC32_OK: u32 = 0x2144_df1c; | ||
| 266 | |||
| 267 | #[must_use] | ||
| 268 | pub fn new(data: &[u8]) -> Self { | ||
| 269 | let fsc = data.iter().fold(u32::MAX, |crc, byte| { | ||
| 270 | let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte; | ||
| 271 | CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8) | ||
| 272 | }) ^ u32::MAX; | ||
| 273 | Self(fsc) | ||
| 274 | } | ||
| 275 | |||
| 276 | #[must_use] | ||
| 277 | pub fn update(self, data: &[u8]) -> Self { | ||
| 278 | let fsc = data.iter().fold(self.0 ^ u32::MAX, |crc, byte| { | ||
| 279 | let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte; | ||
| 280 | CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8) | ||
| 281 | }) ^ u32::MAX; | ||
| 282 | Self(fsc) | ||
| 283 | } | ||
| 284 | |||
| 285 | #[must_use] | ||
| 286 | pub fn crc_ok(&self) -> bool { | ||
| 287 | self.0 == Self::CRC32_OK | ||
| 288 | } | ||
| 289 | |||
| 290 | #[must_use] | ||
| 291 | pub fn hton_bytes(&self) -> [u8; 4] { | ||
| 292 | self.0.to_le_bytes() | ||
| 293 | } | ||
| 294 | |||
| 295 | #[must_use] | ||
| 296 | pub fn hton(&self) -> u32 { | ||
| 297 | self.0.to_le() | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 301 | #[cfg(test)] | ||
| 302 | mod tests { | ||
| 303 | use super::*; | ||
| 304 | |||
| 305 | #[test] | ||
| 306 | fn crc32_ethernet_frame() { | ||
| 307 | let packet_a = &[ | ||
| 308 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xff, 0x06, 0x00, 0x01, 0x08, 0x00, | ||
| 309 | 0x06, 0x04, 0x00, 0x01, 0x00, 0xe0, 0x4c, 0x68, 0x0e, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 310 | 0x00, 0x00, 0xc0, 0xa8, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 311 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x65, 0x90, 0x3d, | ||
| 312 | ]; | ||
| 313 | |||
| 314 | let packet_b = &[ | ||
| 315 | 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xdd, 0x06, 0x00, 0x01, 0x08, 0x00, | ||
| 316 | 0x06, 0x04, 0x00, 0x02, 0x00, 0xe0, 0x4c, 0x68, 0x09, 0xde, 0xc0, 0xa8, 0x01, 0x02, 0x12, 0x34, 0x56, 0x78, | ||
| 317 | 0x9a, 0xbc, 0xc0, 0xa8, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 318 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3d, 0x67, 0x7c, | ||
| 319 | ]; | ||
| 320 | |||
| 321 | // Packet A | ||
| 322 | let own_crc = ETH_FSC::new(&packet_a[0..60]); | ||
| 323 | let crc_bytes = own_crc.hton_bytes(); | ||
| 324 | println!("{:08x} {:02x?}", own_crc.0, crc_bytes); | ||
| 325 | assert_eq!(&crc_bytes, &packet_a[60..64]); | ||
| 326 | |||
| 327 | let own_crc = ETH_FSC::new(packet_a); | ||
| 328 | println!("{:08x}", own_crc.0); | ||
| 329 | assert_eq!(own_crc.0, ETH_FSC::CRC32_OK); | ||
| 330 | |||
| 331 | // Packet B | ||
| 332 | let own_crc = ETH_FSC::new(&packet_b[0..60]); | ||
| 333 | let crc_bytes = own_crc.hton_bytes(); | ||
| 334 | println!("{:08x} {:02x?}", own_crc.0, crc_bytes); | ||
| 335 | assert_eq!(&crc_bytes, &packet_b[60..64]); | ||
| 336 | |||
| 337 | let own_crc = ETH_FSC::new(packet_b); | ||
| 338 | println!("{:08x}", own_crc.0); | ||
| 339 | assert_eq!(own_crc.0, ETH_FSC::CRC32_OK); | ||
| 340 | } | ||
| 341 | |||
| 342 | #[test] | ||
| 343 | fn crc32_update() { | ||
| 344 | let full_data = &[ | ||
| 345 | 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xdd, 0x06, 0x00, 0x01, 0x08, 0x00, | ||
| 346 | 0x06, 0x04, 0x00, 0x02, 0x00, 0xe0, 0x4c, 0x68, 0x09, 0xde, 0xc0, 0xa8, 0x01, 0x02, 0x12, 0x34, 0x56, 0x78, | ||
| 347 | 0x9a, 0xbc, 0xc0, 0xa8, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 348 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3d, 0x67, 0x7c, | ||
| 349 | ]; | ||
| 350 | |||
| 351 | let (part_a, part_b) = full_data.split_at(16); | ||
| 352 | let crc_partially = ETH_FSC::new(part_a).update(part_b); | ||
| 353 | |||
| 354 | let crc_full = ETH_FSC::new(full_data); | ||
| 355 | |||
| 356 | assert_eq!(crc_full.0, crc_partially.0); | ||
| 357 | } | ||
| 358 | } | ||
diff --git a/embassy-net-adin1110/src/crc8.rs b/embassy-net-adin1110/src/crc8.rs new file mode 100644 index 000000000..7d20a7401 --- /dev/null +++ b/embassy-net-adin1110/src/crc8.rs | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | /// CRC-8/ITU | ||
| 2 | const CRC8X_TABLE: [u8; 256] = [ | ||
| 3 | 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, | ||
| 4 | 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, | ||
| 5 | 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, | ||
| 6 | 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, | ||
| 7 | 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, | ||
| 8 | 0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, | ||
| 9 | 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80, 0x95, | ||
| 10 | 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, | ||
| 11 | 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f, | ||
| 12 | 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, | ||
| 13 | 0x33, 0x34, 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 0x3e, | ||
| 14 | 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7, | ||
| 15 | 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, | ||
| 16 | 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3, | ||
| 17 | ]; | ||
| 18 | |||
| 19 | /// Calculate the crc of a pease of data. | ||
| 20 | pub fn crc8(data: &[u8]) -> u8 { | ||
| 21 | data.iter().fold(0, |crc, &byte| CRC8X_TABLE[usize::from(byte ^ crc)]) | ||
| 22 | } | ||
| 23 | |||
| 24 | #[cfg(test)] | ||
| 25 | mod tests { | ||
| 26 | use ::crc::{Crc, CRC_8_SMBUS}; | ||
| 27 | |||
| 28 | use super::crc8; | ||
| 29 | |||
| 30 | #[test] | ||
| 31 | fn spi_header_crc8() { | ||
| 32 | let data = &[0x80, 0x00]; | ||
| 33 | |||
| 34 | let c = Crc::<u8>::new(&CRC_8_SMBUS); | ||
| 35 | let mut dig = c.digest(); | ||
| 36 | dig.update(data); | ||
| 37 | let sw_crc = dig.finalize(); | ||
| 38 | |||
| 39 | let own_crc = crc8(data); | ||
| 40 | |||
| 41 | assert_eq!(own_crc, sw_crc); | ||
| 42 | assert_eq!(own_crc, 182); | ||
| 43 | |||
| 44 | let data = &[0x80, 0x01]; | ||
| 45 | let mut dig = c.digest(); | ||
| 46 | dig.update(data); | ||
| 47 | let sw_crc = dig.finalize(); | ||
| 48 | let own_crc = crc8(data); | ||
| 49 | |||
| 50 | assert_eq!(own_crc, sw_crc); | ||
| 51 | assert_eq!(own_crc, 177); | ||
| 52 | } | ||
| 53 | } | ||
diff --git a/embassy-net-adin1110/src/lib.rs b/embassy-net-adin1110/src/lib.rs new file mode 100644 index 000000000..c0a9b44ee --- /dev/null +++ b/embassy-net-adin1110/src/lib.rs | |||
| @@ -0,0 +1,1046 @@ | |||
| 1 | #![deny(clippy::pedantic)] | ||
| 2 | #![feature(async_fn_in_trait)] | ||
| 3 | #![cfg_attr(not(any(test, feature = "std")), no_std)] | ||
| 4 | #![allow(clippy::module_name_repetitions)] | ||
| 5 | #![allow(clippy::missing_errors_doc)] | ||
| 6 | #![allow(clippy::missing_panics_doc)] | ||
| 7 | #![doc = include_str!("../README.md")] | ||
| 8 | |||
| 9 | mod crc32; | ||
| 10 | mod crc8; | ||
| 11 | mod mdio; | ||
| 12 | mod phy; | ||
| 13 | mod regs; | ||
| 14 | |||
| 15 | use ch::driver::LinkState; | ||
| 16 | pub use crc32::ETH_FSC; | ||
| 17 | use crc8::crc8; | ||
| 18 | use embassy_futures::select::{select, Either}; | ||
| 19 | use embassy_net_driver_channel as ch; | ||
| 20 | use embassy_time::{Duration, Timer}; | ||
| 21 | use embedded_hal_1::digital::OutputPin; | ||
| 22 | use embedded_hal_async::digital::Wait; | ||
| 23 | use embedded_hal_async::spi::{Operation, SpiDevice}; | ||
| 24 | use heapless::Vec; | ||
| 25 | pub use mdio::MdioBus; | ||
| 26 | pub use phy::{Phy10BaseT1x, RegsC22, RegsC45}; | ||
| 27 | pub use regs::{Config0, Config2, SpiRegisters as sr, Status0, Status1}; | ||
| 28 | |||
| 29 | use crate::regs::{LedCntrl, LedFunc, LedPol, LedPolarity, SpiHeader}; | ||
| 30 | |||
| 31 | pub const PHYID: u32 = 0x0283_BC91; | ||
| 32 | |||
| 33 | /// Error values ADIN1110 | ||
| 34 | #[derive(Debug)] | ||
| 35 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 36 | #[allow(non_camel_case_types)] | ||
| 37 | pub enum AdinError<E> { | ||
| 38 | Spi(E), | ||
| 39 | SENDERROR, | ||
| 40 | READERROR, | ||
| 41 | CRC, | ||
| 42 | PACKET_TOO_BIG, | ||
| 43 | PACKET_TOO_SMALL, | ||
| 44 | MDIO_ACC_TIMEOUT, | ||
| 45 | } | ||
| 46 | |||
| 47 | pub type AEResult<T, SPIError> = core::result::Result<T, AdinError<SPIError>>; | ||
| 48 | pub const MDIO_PHY_ADDR: u8 = 0x01; | ||
| 49 | |||
| 50 | /// Maximum Transmission Unit | ||
| 51 | pub const MTU: usize = 1514; | ||
| 52 | |||
| 53 | /// Max SPI/Frame buffer size | ||
| 54 | pub const MAX_BUFF: usize = 2048; | ||
| 55 | |||
| 56 | const DONT_CARE_BYTE: u8 = 0x00; | ||
| 57 | const TURN_AROUND_BYTE: u8 = 0x00; | ||
| 58 | |||
| 59 | /// Packet minimal frame/packet length | ||
| 60 | const ETH_MIN_LEN: usize = 64; | ||
| 61 | |||
| 62 | /// Ethernet `Frame Check Sequence` length | ||
| 63 | const FSC_LEN: usize = 4; | ||
| 64 | /// SPI Header, contains SPI action and register id. | ||
| 65 | const SPI_HEADER_LEN: usize = 2; | ||
| 66 | /// SPI Header CRC length | ||
| 67 | const SPI_HEADER_CRC_LEN: usize = 1; | ||
| 68 | /// Frame Header, | ||
| 69 | const FRAME_HEADER_LEN: usize = 2; | ||
| 70 | |||
| 71 | // P1 = 0x00, P2 = 0x01 | ||
| 72 | const PORT_ID_BYTE: u8 = 0x00; | ||
| 73 | |||
| 74 | pub type Packet = Vec<u8, { SPI_HEADER_LEN + FRAME_HEADER_LEN + MTU + FSC_LEN + 1 + 4 }>; | ||
| 75 | |||
| 76 | /// Type alias for the embassy-net driver for ADIN1110 | ||
| 77 | pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; | ||
| 78 | |||
| 79 | /// Internal state for the embassy-net integration. | ||
| 80 | pub struct State<const N_RX: usize, const N_TX: usize> { | ||
| 81 | ch_state: ch::State<MTU, N_RX, N_TX>, | ||
| 82 | } | ||
| 83 | impl<const N_RX: usize, const N_TX: usize> State<N_RX, N_TX> { | ||
| 84 | /// Create a new `State`. | ||
| 85 | #[must_use] | ||
| 86 | pub const fn new() -> Self { | ||
| 87 | Self { | ||
| 88 | ch_state: ch::State::new(), | ||
| 89 | } | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | #[derive(Debug)] | ||
| 94 | pub struct ADIN1110<SPI> { | ||
| 95 | /// SPI bus | ||
| 96 | spi: SPI, | ||
| 97 | /// Enable CRC on SPI transfer. | ||
| 98 | /// This must match with the hardware pin `SPI_CFG0` were low = CRC enable, high = CRC disabled. | ||
| 99 | crc: bool, | ||
| 100 | } | ||
| 101 | |||
| 102 | impl<SPI: SpiDevice> ADIN1110<SPI> { | ||
| 103 | pub fn new(spi: SPI, crc: bool) -> Self { | ||
| 104 | Self { spi, crc } | ||
| 105 | } | ||
| 106 | |||
| 107 | pub async fn read_reg(&mut self, reg: sr) -> AEResult<u32, SPI::Error> { | ||
| 108 | let mut tx_buf = Vec::<u8, 16>::new(); | ||
| 109 | |||
| 110 | let mut spi_hdr = SpiHeader(0); | ||
| 111 | spi_hdr.set_control(true); | ||
| 112 | spi_hdr.set_addr(reg); | ||
| 113 | let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); | ||
| 114 | |||
| 115 | if self.crc { | ||
| 116 | // Add CRC for header data | ||
| 117 | let _ = tx_buf.push(crc8(&tx_buf)); | ||
| 118 | } | ||
| 119 | |||
| 120 | // Turn around byte, TODO: Unknown that this is. | ||
| 121 | let _ = tx_buf.push(TURN_AROUND_BYTE); | ||
| 122 | |||
| 123 | let mut rx_buf = [0; 5]; | ||
| 124 | |||
| 125 | let spi_read_len = if self.crc { rx_buf.len() } else { rx_buf.len() - 1 }; | ||
| 126 | |||
| 127 | let mut spi_op = [Operation::Write(&tx_buf), Operation::Read(&mut rx_buf[0..spi_read_len])]; | ||
| 128 | |||
| 129 | self.spi.transaction(&mut spi_op).await.map_err(AdinError::Spi)?; | ||
| 130 | |||
| 131 | if self.crc { | ||
| 132 | let crc = crc8(&rx_buf[0..4]); | ||
| 133 | if crc != rx_buf[4] { | ||
| 134 | return Err(AdinError::CRC); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | let value = u32::from_be_bytes(rx_buf[0..4].try_into().unwrap()); | ||
| 139 | |||
| 140 | #[cfg(feature = "defmt")] | ||
| 141 | defmt::trace!("REG Read {} = {:08x} SPI {:02x}", reg, value, &tx_buf); | ||
| 142 | |||
| 143 | Ok(value) | ||
| 144 | } | ||
| 145 | |||
| 146 | pub async fn write_reg(&mut self, reg: sr, value: u32) -> AEResult<(), SPI::Error> { | ||
| 147 | let mut tx_buf = Vec::<u8, 16>::new(); | ||
| 148 | |||
| 149 | let mut spi_hdr = SpiHeader(0); | ||
| 150 | spi_hdr.set_control(true); | ||
| 151 | spi_hdr.set_write(true); | ||
| 152 | spi_hdr.set_addr(reg); | ||
| 153 | let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); | ||
| 154 | |||
| 155 | if self.crc { | ||
| 156 | // Add CRC for header data | ||
| 157 | let _ = tx_buf.push(crc8(&tx_buf)); | ||
| 158 | } | ||
| 159 | |||
| 160 | let val = value.to_be_bytes(); | ||
| 161 | let _ = tx_buf.extend_from_slice(val.as_slice()); | ||
| 162 | |||
| 163 | if self.crc { | ||
| 164 | // Add CRC for header data | ||
| 165 | let _ = tx_buf.push(crc8(val.as_slice())); | ||
| 166 | } | ||
| 167 | |||
| 168 | #[cfg(feature = "defmt")] | ||
| 169 | defmt::trace!("REG Write {} = {:08x} SPI {:02x}", reg, value, &tx_buf); | ||
| 170 | |||
| 171 | self.spi.write(&tx_buf).await.map_err(AdinError::Spi) | ||
| 172 | } | ||
| 173 | |||
| 174 | /// helper function for write to `MDIO_ACC` register and wait for ready! | ||
| 175 | async fn write_mdio_acc_reg(&mut self, mdio_acc_val: u32) -> AEResult<u32, SPI::Error> { | ||
| 176 | self.write_reg(sr::MDIO_ACC, mdio_acc_val).await?; | ||
| 177 | |||
| 178 | // TODO: Add proper timeout! | ||
| 179 | for _ in 0..100_000 { | ||
| 180 | let val = self.read_reg(sr::MDIO_ACC).await?; | ||
| 181 | if val & 0x8000_0000 != 0 { | ||
| 182 | return Ok(val); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | Err(AdinError::MDIO_ACC_TIMEOUT) | ||
| 187 | } | ||
| 188 | |||
| 189 | /// Read out fifo ethernet packet memory received via the wire. | ||
| 190 | pub async fn read_fifo(&mut self, packet: &mut [u8]) -> AEResult<usize, SPI::Error> { | ||
| 191 | let mut tx_buf = Vec::<u8, 16>::new(); | ||
| 192 | |||
| 193 | // Size of the frame, also includes the appednded header. | ||
| 194 | let packet_size = self.read_reg(sr::RX_FSIZE).await? as usize; | ||
| 195 | |||
| 196 | // Packet read of write to the MAC packet buffer must be a multipul of 4! | ||
| 197 | let read_size = packet_size.next_multiple_of(4); | ||
| 198 | |||
| 199 | if packet_size < (SPI_HEADER_LEN + FSC_LEN) { | ||
| 200 | return Err(AdinError::PACKET_TOO_SMALL); | ||
| 201 | } | ||
| 202 | |||
| 203 | if read_size > packet.len() { | ||
| 204 | #[cfg(feature = "defmt")] | ||
| 205 | defmt::trace!("MAX: {} WANT: {}", packet.len(), read_size); | ||
| 206 | return Err(AdinError::PACKET_TOO_BIG); | ||
| 207 | } | ||
| 208 | |||
| 209 | let mut spi_hdr = SpiHeader(0); | ||
| 210 | spi_hdr.set_control(true); | ||
| 211 | spi_hdr.set_addr(sr::RX); | ||
| 212 | let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); | ||
| 213 | |||
| 214 | if self.crc { | ||
| 215 | // Add CRC for header data | ||
| 216 | let _ = tx_buf.push(crc8(&tx_buf)); | ||
| 217 | } | ||
| 218 | |||
| 219 | // Turn around byte, TODO: Unknown that this is. | ||
| 220 | let _ = tx_buf.push(TURN_AROUND_BYTE); | ||
| 221 | |||
| 222 | let spi_packet = &mut packet[0..read_size]; | ||
| 223 | |||
| 224 | assert_eq!(spi_packet.len() & 0x03, 0x00); | ||
| 225 | |||
| 226 | let mut pkt_header = [0, 0]; | ||
| 227 | let mut fsc = [0, 0, 0, 0]; | ||
| 228 | |||
| 229 | let mut spi_op = [ | ||
| 230 | Operation::Write(&tx_buf), | ||
| 231 | Operation::Read(&mut pkt_header), | ||
| 232 | Operation::Read(spi_packet), | ||
| 233 | Operation::Read(&mut fsc), | ||
| 234 | ]; | ||
| 235 | |||
| 236 | self.spi.transaction(&mut spi_op).await.map_err(AdinError::Spi)?; | ||
| 237 | |||
| 238 | Ok(packet_size as usize) | ||
| 239 | } | ||
| 240 | |||
| 241 | /// Write to fifo ethernet packet memory send over the wire. | ||
| 242 | pub async fn write_fifo(&mut self, frame: &[u8]) -> AEResult<(), SPI::Error> { | ||
| 243 | const HEAD_LEN: usize = SPI_HEADER_LEN + SPI_HEADER_CRC_LEN + FRAME_HEADER_LEN; | ||
| 244 | const TAIL_LEN: usize = ETH_MIN_LEN - FSC_LEN + FSC_LEN + 1; | ||
| 245 | |||
| 246 | if frame.len() < (6 + 6 + 2) { | ||
| 247 | return Err(AdinError::PACKET_TOO_SMALL); | ||
| 248 | } | ||
| 249 | if frame.len() > (MAX_BUFF - FRAME_HEADER_LEN) { | ||
| 250 | return Err(AdinError::PACKET_TOO_BIG); | ||
| 251 | } | ||
| 252 | |||
| 253 | // SPI HEADER + [OPTIONAL SPI CRC] + FRAME HEADER | ||
| 254 | let mut head_data = Vec::<u8, HEAD_LEN>::new(); | ||
| 255 | // [OPTIONAL PAD DATA] + FCS + [OPTINAL BYTES MAKE SPI FRAME EVEN] | ||
| 256 | let mut tail_data = Vec::<u8, TAIL_LEN>::new(); | ||
| 257 | |||
| 258 | let mut spi_hdr = SpiHeader(0); | ||
| 259 | spi_hdr.set_control(true); | ||
| 260 | spi_hdr.set_write(true); | ||
| 261 | spi_hdr.set_addr(sr::TX); | ||
| 262 | |||
| 263 | head_data | ||
| 264 | .extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()) | ||
| 265 | .map_err(|_e| AdinError::PACKET_TOO_BIG)?; | ||
| 266 | |||
| 267 | if self.crc { | ||
| 268 | // Add CRC for header data | ||
| 269 | head_data | ||
| 270 | .push(crc8(&head_data[0..2])) | ||
| 271 | .map_err(|_| AdinError::PACKET_TOO_BIG)?; | ||
| 272 | } | ||
| 273 | |||
| 274 | // Add port number, ADIN1110 its fixed to zero/P1, but for ADIN2111 has two ports. | ||
| 275 | head_data | ||
| 276 | .extend_from_slice(u16::from(PORT_ID_BYTE).to_be_bytes().as_slice()) | ||
| 277 | .map_err(|_e| AdinError::PACKET_TOO_BIG)?; | ||
| 278 | |||
| 279 | let mut frame_fcs = ETH_FSC::new(frame); | ||
| 280 | |||
| 281 | // ADIN1110 MAC and PHY don´t accept ethernet packet smaller than 64 bytes. | ||
| 282 | // So padded the data minus the FCS, FCS is automatilly added to by the MAC. | ||
| 283 | if let Some(pad_len) = (ETH_MIN_LEN - FSC_LEN).checked_sub(frame.len()) { | ||
| 284 | let _ = tail_data.resize(pad_len, 0x00); | ||
| 285 | frame_fcs = frame_fcs.update(&tail_data); | ||
| 286 | } | ||
| 287 | |||
| 288 | // Add ethernet FCS only over the ethernet packet. | ||
| 289 | // Only usefull when `CONFIG0`, `Transmit Frame Check Sequence Validation Enable` bit is enabled. | ||
| 290 | let _ = tail_data.extend_from_slice(frame_fcs.hton_bytes().as_slice()); | ||
| 291 | |||
| 292 | // len = frame_size + optional padding + 2 bytes Frame header | ||
| 293 | let send_len_orig = frame.len() + tail_data.len() + FRAME_HEADER_LEN; | ||
| 294 | let spi_pad_len = send_len_orig.next_multiple_of(4); | ||
| 295 | let send_len = u32::try_from(send_len_orig).map_err(|_| AdinError::PACKET_TOO_BIG)?; | ||
| 296 | |||
| 297 | // Packet read of write to the MAC packet buffer must be a multipul of 4 bytes! | ||
| 298 | if spi_pad_len != send_len_orig { | ||
| 299 | let spi_pad_len = spi_pad_len - send_len_orig; | ||
| 300 | let _ = tail_data.extend_from_slice(&[DONT_CARE_BYTE, DONT_CARE_BYTE, DONT_CARE_BYTE][..spi_pad_len]); | ||
| 301 | } | ||
| 302 | |||
| 303 | #[cfg(feature = "defmt")] | ||
| 304 | defmt::trace!( | ||
| 305 | "TX: hdr {} [{}] {:02x}-{:02x}-{:02x} SIZE: {}", | ||
| 306 | head_data.len(), | ||
| 307 | frame.len(), | ||
| 308 | head_data.as_slice(), | ||
| 309 | frame, | ||
| 310 | tail_data.as_slice(), | ||
| 311 | send_len, | ||
| 312 | ); | ||
| 313 | |||
| 314 | self.write_reg(sr::TX_FSIZE, send_len).await?; | ||
| 315 | |||
| 316 | let mut transaction = [ | ||
| 317 | Operation::Write(head_data.as_slice()), | ||
| 318 | Operation::Write(frame), | ||
| 319 | Operation::Write(tail_data.as_slice()), | ||
| 320 | ]; | ||
| 321 | |||
| 322 | self.spi.transaction(&mut transaction).await.map_err(AdinError::Spi) | ||
| 323 | } | ||
| 324 | |||
| 325 | /// Programs the mac address in the mac filters. | ||
| 326 | /// Also set the boardcast address. | ||
| 327 | /// The chip supports 2 priority queues but current code doesn't support this mode. | ||
| 328 | pub async fn set_mac_addr(&mut self, mac: &[u8; 6]) -> AEResult<(), SPI::Error> { | ||
| 329 | let mac_high_part = u16::from_be_bytes(mac[0..2].try_into().unwrap()); | ||
| 330 | let mac_low_part = u32::from_be_bytes(mac[2..6].try_into().unwrap()); | ||
| 331 | |||
| 332 | // program our mac address in the mac address filter | ||
| 333 | self.write_reg(sr::ADDR_FILT_UPR0, (1 << 16) | (1 << 30) | u32::from(mac_high_part)) | ||
| 334 | .await?; | ||
| 335 | self.write_reg(sr::ADDR_FILT_LWR0, mac_low_part).await?; | ||
| 336 | |||
| 337 | self.write_reg(sr::ADDR_MSK_UPR0, u32::from(mac_high_part)).await?; | ||
| 338 | self.write_reg(sr::ADDR_MSK_LWR0, mac_low_part).await?; | ||
| 339 | |||
| 340 | // Also program broadcast address in the mac address filter | ||
| 341 | self.write_reg(sr::ADDR_FILT_UPR1, (1 << 16) | (1 << 30) | 0xFFFF) | ||
| 342 | .await?; | ||
| 343 | self.write_reg(sr::ADDR_FILT_LWR1, 0xFFFF_FFFF).await?; | ||
| 344 | self.write_reg(sr::ADDR_MSK_UPR1, 0xFFFF).await?; | ||
| 345 | self.write_reg(sr::ADDR_MSK_LWR1, 0xFFFF_FFFF).await?; | ||
| 346 | |||
| 347 | Ok(()) | ||
| 348 | } | ||
| 349 | } | ||
| 350 | |||
| 351 | impl<SPI: SpiDevice> mdio::MdioBus for ADIN1110<SPI> { | ||
| 352 | type Error = AdinError<SPI::Error>; | ||
| 353 | |||
| 354 | /// Read from the PHY Registers as Clause 22. | ||
| 355 | async fn read_cl22(&mut self, phy_id: u8, reg: u8) -> Result<u16, Self::Error> { | ||
| 356 | let mdio_acc_val: u32 = | ||
| 357 | (0x1 << 28) | u32::from(phy_id & 0x1F) << 21 | u32::from(reg & 0x1F) << 16 | (0x3 << 26); | ||
| 358 | |||
| 359 | // Result is in the lower half of the answer. | ||
| 360 | #[allow(clippy::cast_possible_truncation)] | ||
| 361 | self.write_mdio_acc_reg(mdio_acc_val).await.map(|val| val as u16) | ||
| 362 | } | ||
| 363 | |||
| 364 | /// Read from the PHY Registers as Clause 45. | ||
| 365 | async fn read_cl45(&mut self, phy_id: u8, regc45: (u8, u16)) -> Result<u16, Self::Error> { | ||
| 366 | let mdio_acc_val = u32::from(phy_id & 0x1F) << 21 | u32::from(regc45.0 & 0x1F) << 16 | u32::from(regc45.1); | ||
| 367 | |||
| 368 | self.write_mdio_acc_reg(mdio_acc_val).await?; | ||
| 369 | |||
| 370 | let mdio_acc_val = u32::from(phy_id & 0x1F) << 21 | u32::from(regc45.0 & 0x1F) << 16 | (0x03 << 26); | ||
| 371 | |||
| 372 | // Result is in the lower half of the answer. | ||
| 373 | #[allow(clippy::cast_possible_truncation)] | ||
| 374 | self.write_mdio_acc_reg(mdio_acc_val).await.map(|val| val as u16) | ||
| 375 | } | ||
| 376 | |||
| 377 | /// Write to the PHY Registers as Clause 22. | ||
| 378 | async fn write_cl22(&mut self, phy_id: u8, reg: u8, val: u16) -> Result<(), Self::Error> { | ||
| 379 | let mdio_acc_val: u32 = | ||
| 380 | (0x1 << 28) | u32::from(phy_id & 0x1F) << 21 | u32::from(reg & 0x1F) << 16 | (0x1 << 26) | u32::from(val); | ||
| 381 | |||
| 382 | self.write_mdio_acc_reg(mdio_acc_val).await.map(|_| ()) | ||
| 383 | } | ||
| 384 | |||
| 385 | /// Write to the PHY Registers as Clause 45. | ||
| 386 | async fn write_cl45(&mut self, phy_id: u8, regc45: (u8, u16), value: u16) -> AEResult<(), SPI::Error> { | ||
| 387 | let phy_id = u32::from(phy_id & 0x1F) << 21; | ||
| 388 | let dev_addr = u32::from(regc45.0 & 0x1F) << 16; | ||
| 389 | let reg = u32::from(regc45.1); | ||
| 390 | |||
| 391 | let mdio_acc_val: u32 = phy_id | dev_addr | reg; | ||
| 392 | self.write_mdio_acc_reg(mdio_acc_val).await?; | ||
| 393 | |||
| 394 | let mdio_acc_val: u32 = phy_id | dev_addr | (0x01 << 26) | u32::from(value); | ||
| 395 | self.write_mdio_acc_reg(mdio_acc_val).await.map(|_| ()) | ||
| 396 | } | ||
| 397 | } | ||
| 398 | |||
| 399 | /// Background runner for the ADIN110. | ||
| 400 | /// | ||
| 401 | /// You must call `.run()` in a background task for the ADIN1100 to operate. | ||
| 402 | pub struct Runner<'d, SPI, INT, RST> { | ||
| 403 | mac: ADIN1110<SPI>, | ||
| 404 | ch: ch::Runner<'d, MTU>, | ||
| 405 | int: INT, | ||
| 406 | is_link_up: bool, | ||
| 407 | _reset: RST, | ||
| 408 | } | ||
| 409 | |||
| 410 | impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> { | ||
| 411 | #[allow(clippy::too_many_lines)] | ||
| 412 | pub async fn run(mut self) -> ! { | ||
| 413 | loop { | ||
| 414 | let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); | ||
| 415 | |||
| 416 | loop { | ||
| 417 | #[cfg(feature = "defmt")] | ||
| 418 | defmt::debug!("Waiting for interrupts"); | ||
| 419 | match select(self.int.wait_for_low(), tx_chan.tx_buf()).await { | ||
| 420 | Either::First(_) => { | ||
| 421 | let mut status1_clr = Status1(0); | ||
| 422 | let mut status1 = Status1(self.mac.read_reg(sr::STATUS1).await.unwrap()); | ||
| 423 | |||
| 424 | while status1.p1_rx_rdy() { | ||
| 425 | #[cfg(feature = "defmt")] | ||
| 426 | defmt::debug!("alloc RX packet buffer"); | ||
| 427 | match select(rx_chan.rx_buf(), tx_chan.tx_buf()).await { | ||
| 428 | // Handle frames that needs to transmit from the wire. | ||
| 429 | // Note: rx_chan.rx_buf() channel don´t accept new request | ||
| 430 | // when the tx_chan is full. So these will be handled | ||
| 431 | // automaticly. | ||
| 432 | Either::First(frame) => match self.mac.read_fifo(frame).await { | ||
| 433 | Ok(n) => { | ||
| 434 | rx_chan.rx_done(n); | ||
| 435 | } | ||
| 436 | Err(e) => match e { | ||
| 437 | AdinError::PACKET_TOO_BIG => { | ||
| 438 | #[cfg(feature = "defmt")] | ||
| 439 | defmt::error!("RX Packet to big, DROP"); | ||
| 440 | self.mac.write_reg(sr::FIFO_CLR, 1).await.unwrap(); | ||
| 441 | } | ||
| 442 | AdinError::Spi(_) => { | ||
| 443 | #[cfg(feature = "defmt")] | ||
| 444 | defmt::error!("RX Spi error") | ||
| 445 | } | ||
| 446 | _ => { | ||
| 447 | #[cfg(feature = "defmt")] | ||
| 448 | defmt::error!("RX Error") | ||
| 449 | } | ||
| 450 | }, | ||
| 451 | }, | ||
| 452 | Either::Second(frame) => { | ||
| 453 | // Handle frames that needs to transmit to the wire. | ||
| 454 | self.mac.write_fifo(frame).await.unwrap(); | ||
| 455 | tx_chan.tx_done(); | ||
| 456 | } | ||
| 457 | } | ||
| 458 | status1 = Status1(self.mac.read_reg(sr::STATUS1).await.unwrap()); | ||
| 459 | } | ||
| 460 | |||
| 461 | let status0 = Status0(self.mac.read_reg(sr::STATUS0).await.unwrap()); | ||
| 462 | if status1.0 & !0x1b != 0 { | ||
| 463 | #[cfg(feature = "defmt")] | ||
| 464 | defmt::error!("SPE CHIP STATUS 0:{:08x} 1:{:08x}", status0.0, status1.0); | ||
| 465 | } | ||
| 466 | |||
| 467 | if status1.tx_rdy() { | ||
| 468 | status1_clr.set_tx_rdy(true); | ||
| 469 | #[cfg(feature = "defmt")] | ||
| 470 | defmt::info!("TX_DONE"); | ||
| 471 | } | ||
| 472 | |||
| 473 | if status1.link_change() { | ||
| 474 | let link = status1.p1_link_status(); | ||
| 475 | self.is_link_up = link; | ||
| 476 | |||
| 477 | #[cfg(feature = "defmt")] | ||
| 478 | if link { | ||
| 479 | let link_status = self | ||
| 480 | .mac | ||
| 481 | .read_cl45(MDIO_PHY_ADDR, RegsC45::DA7::AN_STATUS_EXTRA.into()) | ||
| 482 | .await | ||
| 483 | .unwrap(); | ||
| 484 | |||
| 485 | let volt = if link_status & (0b11 << 5) == (0b11 << 5) { | ||
| 486 | "2.4" | ||
| 487 | } else { | ||
| 488 | "1.0" | ||
| 489 | }; | ||
| 490 | |||
| 491 | let mse = self | ||
| 492 | .mac | ||
| 493 | .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1::MSE_VAL.into()) | ||
| 494 | .await | ||
| 495 | .unwrap(); | ||
| 496 | |||
| 497 | defmt::info!("LINK Changed: Link Up, Volt: {} V p-p, MSE: {:0004}", volt, mse); | ||
| 498 | } else { | ||
| 499 | defmt::info!("LINK Changed: Link Down"); | ||
| 500 | } | ||
| 501 | |||
| 502 | state_chan.set_link_state(if link { LinkState::Up } else { LinkState::Down }); | ||
| 503 | status1_clr.set_link_change(true); | ||
| 504 | } | ||
| 505 | |||
| 506 | if status1.tx_ecc_err() { | ||
| 507 | #[cfg(feature = "defmt")] | ||
| 508 | defmt::error!("SPI TX_ECC_ERR error, CLEAR TX FIFO"); | ||
| 509 | self.mac.write_reg(sr::FIFO_CLR, 2).await.unwrap(); | ||
| 510 | status1_clr.set_tx_ecc_err(true); | ||
| 511 | } | ||
| 512 | |||
| 513 | if status1.rx_ecc_err() { | ||
| 514 | #[cfg(feature = "defmt")] | ||
| 515 | defmt::error!("SPI RX_ECC_ERR error"); | ||
| 516 | status1_clr.set_rx_ecc_err(true); | ||
| 517 | } | ||
| 518 | |||
| 519 | if status1.spi_err() { | ||
| 520 | #[cfg(feature = "defmt")] | ||
| 521 | defmt::error!("SPI SPI_ERR CRC error"); | ||
| 522 | status1_clr.set_spi_err(true); | ||
| 523 | } | ||
| 524 | |||
| 525 | if status0.phyint() { | ||
| 526 | #[cfg_attr(not(feature = "defmt"), allow(unused_variables))] | ||
| 527 | let crsm_irq_st = self | ||
| 528 | .mac | ||
| 529 | .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::CRSM_IRQ_STATUS.into()) | ||
| 530 | .await | ||
| 531 | .unwrap(); | ||
| 532 | |||
| 533 | #[cfg_attr(not(feature = "defmt"), allow(unused_variables))] | ||
| 534 | let phy_irq_st = self | ||
| 535 | .mac | ||
| 536 | .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1F::PHY_SYBSYS_IRQ_STATUS.into()) | ||
| 537 | .await | ||
| 538 | .unwrap(); | ||
| 539 | |||
| 540 | #[cfg(feature = "defmt")] | ||
| 541 | defmt::warn!( | ||
| 542 | "SPE CHIP PHY CRSM_IRQ_STATUS {:04x} PHY_SUBSYS_IRQ_STATUS {:04x}", | ||
| 543 | crsm_irq_st, | ||
| 544 | phy_irq_st | ||
| 545 | ); | ||
| 546 | } | ||
| 547 | |||
| 548 | if status0.txfcse() { | ||
| 549 | #[cfg(feature = "defmt")] | ||
| 550 | defmt::error!("SPE CHIP PHY TX Frame CRC error"); | ||
| 551 | } | ||
| 552 | |||
| 553 | // Clear status0 | ||
| 554 | self.mac.write_reg(sr::STATUS0, 0xFFF).await.unwrap(); | ||
| 555 | self.mac.write_reg(sr::STATUS1, status1_clr.0).await.unwrap(); | ||
| 556 | } | ||
| 557 | Either::Second(packet) => { | ||
| 558 | // Handle frames that needs to transmit to the wire. | ||
| 559 | self.mac.write_fifo(packet).await.unwrap(); | ||
| 560 | tx_chan.tx_done(); | ||
| 561 | } | ||
| 562 | } | ||
| 563 | } | ||
| 564 | } | ||
| 565 | } | ||
| 566 | } | ||
| 567 | |||
| 568 | /// Obtain a driver for using the ADIN1110 with [`embassy-net`](crates.io/crates/embassy-net). | ||
| 569 | pub async fn new<const N_RX: usize, const N_TX: usize, SPI: SpiDevice, INT: Wait, RST: OutputPin>( | ||
| 570 | mac_addr: [u8; 6], | ||
| 571 | state: &'_ mut State<N_RX, N_TX>, | ||
| 572 | spi_dev: SPI, | ||
| 573 | int: INT, | ||
| 574 | mut reset: RST, | ||
| 575 | crc: bool, | ||
| 576 | ) -> (Device<'_>, Runner<'_, SPI, INT, RST>) { | ||
| 577 | use crate::regs::{IMask0, IMask1}; | ||
| 578 | |||
| 579 | #[cfg(feature = "defmt")] | ||
| 580 | defmt::info!("INIT ADIN1110"); | ||
| 581 | |||
| 582 | // Reset sequence | ||
| 583 | reset.set_low().unwrap(); | ||
| 584 | |||
| 585 | // Wait t1: 20-43mS | ||
| 586 | Timer::after(Duration::from_millis(30)).await; | ||
| 587 | |||
| 588 | reset.set_high().unwrap(); | ||
| 589 | |||
| 590 | // Wait t3: 50mS | ||
| 591 | Timer::after(Duration::from_millis(50)).await; | ||
| 592 | |||
| 593 | // Create device | ||
| 594 | let mut mac = ADIN1110::new(spi_dev, crc); | ||
| 595 | |||
| 596 | // Check PHYID | ||
| 597 | let id = mac.read_reg(sr::PHYID).await.unwrap(); | ||
| 598 | assert_eq!(id, PHYID); | ||
| 599 | |||
| 600 | #[cfg(feature = "defmt")] | ||
| 601 | defmt::debug!("SPE: CHIP MAC/ID: {:08x}", id); | ||
| 602 | |||
| 603 | #[cfg(feature = "defmt")] | ||
| 604 | let adin_phy = Phy10BaseT1x::default(); | ||
| 605 | #[cfg(feature = "defmt")] | ||
| 606 | let phy_id = adin_phy.get_id(&mut mac).await.unwrap(); | ||
| 607 | #[cfg(feature = "defmt")] | ||
| 608 | defmt::debug!("SPE: CHIP: PHY ID: {:08x}", phy_id); | ||
| 609 | |||
| 610 | let mi_control = mac.read_cl22(MDIO_PHY_ADDR, RegsC22::CONTROL as u8).await.unwrap(); | ||
| 611 | #[cfg(feature = "defmt")] | ||
| 612 | defmt::println!("SPE CHIP PHY MI_CONTROL {:04x}", mi_control); | ||
| 613 | if mi_control & 0x0800 != 0 { | ||
| 614 | let val = mi_control & !0x0800; | ||
| 615 | #[cfg(feature = "defmt")] | ||
| 616 | defmt::println!("SPE CHIP PHY MI_CONTROL Disable PowerDown"); | ||
| 617 | mac.write_cl22(MDIO_PHY_ADDR, RegsC22::CONTROL as u8, val) | ||
| 618 | .await | ||
| 619 | .unwrap(); | ||
| 620 | } | ||
| 621 | |||
| 622 | // Config2: CRC_APPEND | ||
| 623 | let mut config2 = Config2(0x0000_0800); | ||
| 624 | config2.set_crc_append(true); | ||
| 625 | mac.write_reg(sr::CONFIG2, config2.0).await.unwrap(); | ||
| 626 | |||
| 627 | // Pin Mux Config 1 | ||
| 628 | let led_val = (0b11 << 6) | (0b11 << 4); // | (0b00 << 1); | ||
| 629 | mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::DIGIO_PINMUX.into(), led_val) | ||
| 630 | .await | ||
| 631 | .unwrap(); | ||
| 632 | |||
| 633 | let mut led_pol = LedPolarity(0); | ||
| 634 | led_pol.set_led1_polarity(LedPol::ActiveLow); | ||
| 635 | led_pol.set_led0_polarity(LedPol::ActiveLow); | ||
| 636 | |||
| 637 | // Led Polarity Regisgere Active Low | ||
| 638 | mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::LED_POLARITY.into(), led_pol.0) | ||
| 639 | .await | ||
| 640 | .unwrap(); | ||
| 641 | |||
| 642 | // Led Both On | ||
| 643 | let mut led_cntr = LedCntrl(0x0); | ||
| 644 | |||
| 645 | // LED1: Yellow | ||
| 646 | led_cntr.set_led1_en(true); | ||
| 647 | led_cntr.set_led1_function(LedFunc::TxLevel2P4); | ||
| 648 | // LED0: Green | ||
| 649 | led_cntr.set_led0_en(true); | ||
| 650 | led_cntr.set_led0_function(LedFunc::LinkupTxRxActicity); | ||
| 651 | |||
| 652 | mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::LED_CNTRL.into(), led_cntr.0) | ||
| 653 | .await | ||
| 654 | .unwrap(); | ||
| 655 | |||
| 656 | // Set ADIN1110 Interrupts, RX_READY and LINK_CHANGE | ||
| 657 | // Enable interrupts LINK_CHANGE, TX_RDY, RX_RDY(P1), SPI_ERR | ||
| 658 | // Have to clear the mask the enable it. | ||
| 659 | let mut imask0_val = IMask0(0x0000_1FBF); | ||
| 660 | imask0_val.set_txfcsem(false); | ||
| 661 | imask0_val.set_phyintm(false); | ||
| 662 | imask0_val.set_txboem(false); | ||
| 663 | imask0_val.set_rxboem(false); | ||
| 664 | imask0_val.set_txpem(false); | ||
| 665 | |||
| 666 | mac.write_reg(sr::IMASK0, imask0_val.0).await.unwrap(); | ||
| 667 | |||
| 668 | // Set ADIN1110 Interrupts, RX_READY and LINK_CHANGE | ||
| 669 | // Enable interrupts LINK_CHANGE, TX_RDY, RX_RDY(P1), SPI_ERR | ||
| 670 | // Have to clear the mask the enable it. | ||
| 671 | let mut imask1_val = IMask1(0x43FA_1F1A); | ||
| 672 | imask1_val.set_link_change_mask(false); | ||
| 673 | imask1_val.set_p1_rx_rdy_mask(false); | ||
| 674 | imask1_val.set_spi_err_mask(false); | ||
| 675 | imask1_val.set_tx_ecc_err_mask(false); | ||
| 676 | imask1_val.set_rx_ecc_err_mask(false); | ||
| 677 | |||
| 678 | mac.write_reg(sr::IMASK1, imask1_val.0).await.unwrap(); | ||
| 679 | |||
| 680 | // Program mac address but also sets mac filters. | ||
| 681 | mac.set_mac_addr(&mac_addr).await.unwrap(); | ||
| 682 | |||
| 683 | let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr)); | ||
| 684 | ( | ||
| 685 | device, | ||
| 686 | Runner { | ||
| 687 | ch: runner, | ||
| 688 | mac, | ||
| 689 | int, | ||
| 690 | is_link_up: false, | ||
| 691 | _reset: reset, | ||
| 692 | }, | ||
| 693 | ) | ||
| 694 | } | ||
| 695 | |||
| 696 | #[allow(clippy::similar_names)] | ||
| 697 | #[cfg(test)] | ||
| 698 | mod tests { | ||
| 699 | use core::convert::Infallible; | ||
| 700 | |||
| 701 | use embedded_hal_1::digital::{ErrorType, OutputPin}; | ||
| 702 | use embedded_hal_async::delay::DelayUs; | ||
| 703 | use embedded_hal_bus::spi::ExclusiveDevice; | ||
| 704 | use embedded_hal_mock::eh1::spi::{Mock as SpiMock, Transaction as SpiTransaction}; | ||
| 705 | |||
| 706 | #[derive(Debug, Default)] | ||
| 707 | struct CsPinMock { | ||
| 708 | pub high: u32, | ||
| 709 | pub low: u32, | ||
| 710 | } | ||
| 711 | impl OutputPin for CsPinMock { | ||
| 712 | fn set_low(&mut self) -> Result<(), Self::Error> { | ||
| 713 | self.low += 1; | ||
| 714 | Ok(()) | ||
| 715 | } | ||
| 716 | |||
| 717 | fn set_high(&mut self) -> Result<(), Self::Error> { | ||
| 718 | self.high += 1; | ||
| 719 | Ok(()) | ||
| 720 | } | ||
| 721 | } | ||
| 722 | impl ErrorType for CsPinMock { | ||
| 723 | type Error = Infallible; | ||
| 724 | } | ||
| 725 | |||
| 726 | use super::*; | ||
| 727 | |||
| 728 | // TODO: This is currently a workaround unit `ExclusiveDevice` is moved to `embedded-hal-bus` | ||
| 729 | // see https://github.com/rust-embedded/embedded-hal/pull/462#issuecomment-1560014426 | ||
| 730 | struct MockDelay {} | ||
| 731 | |||
| 732 | impl DelayUs for MockDelay { | ||
| 733 | async fn delay_us(&mut self, _us: u32) { | ||
| 734 | todo!() | ||
| 735 | } | ||
| 736 | |||
| 737 | async fn delay_ms(&mut self, _ms: u32) { | ||
| 738 | todo!() | ||
| 739 | } | ||
| 740 | } | ||
| 741 | |||
| 742 | #[futures_test::test] | ||
| 743 | async fn mac_read_registers_without_crc() { | ||
| 744 | // Configure expectations | ||
| 745 | let expectations = [ | ||
| 746 | // 1st | ||
| 747 | SpiTransaction::write_vec(vec![0x80, 0x01, TURN_AROUND_BYTE]), | ||
| 748 | SpiTransaction::read_vec(vec![0x02, 0x83, 0xBC, 0x91]), | ||
| 749 | SpiTransaction::flush(), | ||
| 750 | // 2nd | ||
| 751 | SpiTransaction::write_vec(vec![0x80, 0x02, TURN_AROUND_BYTE]), | ||
| 752 | SpiTransaction::read_vec(vec![0x00, 0x00, 0x06, 0xC3]), | ||
| 753 | SpiTransaction::flush(), | ||
| 754 | ]; | ||
| 755 | let mut spi = SpiMock::new(&expectations); | ||
| 756 | |||
| 757 | let cs = CsPinMock::default(); | ||
| 758 | let delay = MockDelay {}; | ||
| 759 | let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); | ||
| 760 | let mut spe = ADIN1110::new(spi_dev, false); | ||
| 761 | |||
| 762 | // Read PHIID | ||
| 763 | let val = spe.read_reg(sr::PHYID).await.expect("Error"); | ||
| 764 | assert_eq!(val, 0x0283_BC91); | ||
| 765 | |||
| 766 | // Read CAPAVILITY | ||
| 767 | let val = spe.read_reg(sr::CAPABILITY).await.expect("Error"); | ||
| 768 | assert_eq!(val, 0x0000_06C3); | ||
| 769 | |||
| 770 | spi.done(); | ||
| 771 | } | ||
| 772 | |||
| 773 | #[futures_test::test] | ||
| 774 | async fn mac_read_registers_with_crc() { | ||
| 775 | // Configure expectations | ||
| 776 | let expectations = [ | ||
| 777 | // 1st | ||
| 778 | SpiTransaction::write_vec(vec![0x80, 0x01, 177, TURN_AROUND_BYTE]), | ||
| 779 | SpiTransaction::read_vec(vec![0x02, 0x83, 0xBC, 0x91, 215]), | ||
| 780 | SpiTransaction::flush(), | ||
| 781 | // 2nd | ||
| 782 | SpiTransaction::write_vec(vec![0x80, 0x02, 184, TURN_AROUND_BYTE]), | ||
| 783 | SpiTransaction::read_vec(vec![0x00, 0x00, 0x06, 0xC3, 57]), | ||
| 784 | SpiTransaction::flush(), | ||
| 785 | ]; | ||
| 786 | let mut spi = SpiMock::new(&expectations); | ||
| 787 | |||
| 788 | let cs = CsPinMock::default(); | ||
| 789 | let delay = MockDelay {}; | ||
| 790 | let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); | ||
| 791 | |||
| 792 | let mut spe = ADIN1110::new(spi_dev, true); | ||
| 793 | |||
| 794 | assert_eq!(crc8(0x0283_BC91_u32.to_be_bytes().as_slice()), 215); | ||
| 795 | assert_eq!(crc8(0x0000_06C3_u32.to_be_bytes().as_slice()), 57); | ||
| 796 | |||
| 797 | // Read PHIID | ||
| 798 | let val = spe.read_reg(sr::PHYID).await.expect("Error"); | ||
| 799 | assert_eq!(val, 0x0283_BC91); | ||
| 800 | |||
| 801 | // Read CAPAVILITY | ||
| 802 | let val = spe.read_reg(sr::CAPABILITY).await.expect("Error"); | ||
| 803 | assert_eq!(val, 0x0000_06C3); | ||
| 804 | |||
| 805 | spi.done(); | ||
| 806 | } | ||
| 807 | |||
| 808 | #[futures_test::test] | ||
| 809 | async fn mac_write_registers_without_crc() { | ||
| 810 | // Configure expectations | ||
| 811 | let expectations = [ | ||
| 812 | SpiTransaction::write_vec(vec![0xA0, 0x09, 0x12, 0x34, 0x56, 0x78]), | ||
| 813 | SpiTransaction::flush(), | ||
| 814 | ]; | ||
| 815 | let mut spi = SpiMock::new(&expectations); | ||
| 816 | |||
| 817 | let cs = CsPinMock::default(); | ||
| 818 | let delay = MockDelay {}; | ||
| 819 | let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); | ||
| 820 | |||
| 821 | let mut spe = ADIN1110::new(spi_dev, false); | ||
| 822 | |||
| 823 | // Write reg: 0x1FFF | ||
| 824 | assert!(spe.write_reg(sr::STATUS1, 0x1234_5678).await.is_ok()); | ||
| 825 | |||
| 826 | spi.done(); | ||
| 827 | } | ||
| 828 | |||
| 829 | #[futures_test::test] | ||
| 830 | async fn mac_write_registers_with_crc() { | ||
| 831 | // Configure expectations | ||
| 832 | let expectations = [ | ||
| 833 | SpiTransaction::write_vec(vec![0xA0, 0x09, 39, 0x12, 0x34, 0x56, 0x78, 28]), | ||
| 834 | SpiTransaction::flush(), | ||
| 835 | ]; | ||
| 836 | |||
| 837 | // Basic test init block | ||
| 838 | let mut spi = SpiMock::new(&expectations); | ||
| 839 | let cs = CsPinMock::default(); | ||
| 840 | let delay = MockDelay {}; | ||
| 841 | let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); | ||
| 842 | let mut spe = ADIN1110::new(spi_dev, true); | ||
| 843 | |||
| 844 | // Write reg: 0x1FFF | ||
| 845 | assert!(spe.write_reg(sr::STATUS1, 0x1234_5678).await.is_ok()); | ||
| 846 | |||
| 847 | spi.done(); | ||
| 848 | } | ||
| 849 | |||
| 850 | #[futures_test::test] | ||
| 851 | async fn write_packet_to_fifo_minimal_with_crc() { | ||
| 852 | // Configure expectations | ||
| 853 | let mut expectations = vec![]; | ||
| 854 | |||
| 855 | // Write TX_SIZE reg | ||
| 856 | expectations.push(SpiTransaction::write_vec(vec![160, 48, 136, 0, 0, 0, 66, 201])); | ||
| 857 | expectations.push(SpiTransaction::flush()); | ||
| 858 | |||
| 859 | // Write TX reg. | ||
| 860 | // SPI Header + optional CRC + Frame Header | ||
| 861 | expectations.push(SpiTransaction::write_vec(vec![160, 49, 143, 0, 0])); | ||
| 862 | // Packet data | ||
| 863 | let packet = [0xFF_u8; 60]; | ||
| 864 | expectations.push(SpiTransaction::write_vec(packet.to_vec())); | ||
| 865 | |||
| 866 | let mut tail = std::vec::Vec::<u8>::with_capacity(100); | ||
| 867 | // Padding | ||
| 868 | if let Some(padding_len) = (ETH_MIN_LEN - FSC_LEN).checked_sub(packet.len()) { | ||
| 869 | tail.resize(padding_len, 0x00); | ||
| 870 | } | ||
| 871 | // Packet FCS + optinal padding | ||
| 872 | tail.extend_from_slice(&[77, 241, 140, 244, DONT_CARE_BYTE, DONT_CARE_BYTE]); | ||
| 873 | |||
| 874 | expectations.push(SpiTransaction::write_vec(tail)); | ||
| 875 | expectations.push(SpiTransaction::flush()); | ||
| 876 | |||
| 877 | let mut spi = SpiMock::new(&expectations); | ||
| 878 | |||
| 879 | let cs = CsPinMock::default(); | ||
| 880 | let delay = MockDelay {}; | ||
| 881 | let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); | ||
| 882 | |||
| 883 | let mut spe = ADIN1110::new(spi_dev, true); | ||
| 884 | |||
| 885 | assert!(spe.write_fifo(&packet).await.is_ok()); | ||
| 886 | |||
| 887 | spi.done(); | ||
| 888 | } | ||
| 889 | |||
| 890 | #[futures_test::test] | ||
| 891 | async fn write_packet_to_fifo_max_mtu_with_crc() { | ||
| 892 | assert_eq!(MTU, 1514); | ||
| 893 | // Configure expectations | ||
| 894 | let mut expectations = vec![]; | ||
| 895 | |||
| 896 | // Write TX_SIZE reg | ||
| 897 | expectations.push(SpiTransaction::write_vec(vec![160, 48, 136, 0, 0, 5, 240, 159])); | ||
| 898 | expectations.push(SpiTransaction::flush()); | ||
| 899 | |||
| 900 | // Write TX reg. | ||
| 901 | // SPI Header + optional CRC + Frame Header | ||
| 902 | expectations.push(SpiTransaction::write_vec(vec![160, 49, 143, 0, 0])); | ||
| 903 | // Packet data | ||
| 904 | let packet = [0xAA_u8; MTU]; | ||
| 905 | expectations.push(SpiTransaction::write_vec(packet.to_vec())); | ||
| 906 | |||
| 907 | let mut tail = std::vec::Vec::<u8>::with_capacity(100); | ||
| 908 | // Padding | ||
| 909 | if let Some(padding_len) = (ETH_MIN_LEN - FSC_LEN).checked_sub(packet.len()) { | ||
| 910 | tail.resize(padding_len, 0x00); | ||
| 911 | } | ||
| 912 | // Packet FCS + optinal padding | ||
| 913 | tail.extend_from_slice(&[49, 196, 205, 160]); | ||
| 914 | |||
| 915 | expectations.push(SpiTransaction::write_vec(tail)); | ||
| 916 | expectations.push(SpiTransaction::flush()); | ||
| 917 | |||
| 918 | let mut spi = SpiMock::new(&expectations); | ||
| 919 | |||
| 920 | let cs = CsPinMock::default(); | ||
| 921 | let delay = MockDelay {}; | ||
| 922 | let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); | ||
| 923 | |||
| 924 | let mut spe = ADIN1110::new(spi_dev, true); | ||
| 925 | |||
| 926 | assert!(spe.write_fifo(&packet).await.is_ok()); | ||
| 927 | |||
| 928 | spi.done(); | ||
| 929 | } | ||
| 930 | |||
| 931 | #[futures_test::test] | ||
| 932 | async fn write_packet_to_fifo_invalid_lengths() { | ||
| 933 | assert_eq!(MTU, 1514); | ||
| 934 | |||
| 935 | // Configure expectations | ||
| 936 | let expectations = vec![]; | ||
| 937 | |||
| 938 | // Max packet size = MAX_BUFF - FRAME_HEADER_LEN | ||
| 939 | let packet = [0xAA_u8; MAX_BUFF - FRAME_HEADER_LEN + 1]; | ||
| 940 | |||
| 941 | let mut spi = SpiMock::new(&expectations); | ||
| 942 | |||
| 943 | let cs = CsPinMock::default(); | ||
| 944 | let delay = MockDelay {}; | ||
| 945 | let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); | ||
| 946 | |||
| 947 | let mut spe = ADIN1110::new(spi_dev, true); | ||
| 948 | |||
| 949 | // minimal | ||
| 950 | assert!(matches!( | ||
| 951 | spe.write_fifo(&packet[0..(6 + 6 + 2 - 1)]).await, | ||
| 952 | Err(AdinError::PACKET_TOO_SMALL) | ||
| 953 | )); | ||
| 954 | |||
| 955 | // max + 1 | ||
| 956 | assert!(matches!(spe.write_fifo(&packet).await, Err(AdinError::PACKET_TOO_BIG))); | ||
| 957 | |||
| 958 | spi.done(); | ||
| 959 | } | ||
| 960 | |||
| 961 | #[futures_test::test] | ||
| 962 | async fn write_packet_to_fifo_arp_46bytes_with_crc() { | ||
| 963 | // Configure expectations | ||
| 964 | let mut expectations = vec![]; | ||
| 965 | |||
| 966 | // Write TX_SIZE reg | ||
| 967 | expectations.push(SpiTransaction::write_vec(vec![160, 48, 136, 0, 0, 0, 66, 201])); | ||
| 968 | expectations.push(SpiTransaction::flush()); | ||
| 969 | |||
| 970 | // Write TX reg. | ||
| 971 | // Header | ||
| 972 | expectations.push(SpiTransaction::write_vec(vec![160, 49, 143, 0, 0])); | ||
| 973 | // Packet data | ||
| 974 | let packet = [ | ||
| 975 | 34, 51, 68, 85, 102, 119, 18, 52, 86, 120, 154, 188, 8, 6, 0, 1, 8, 0, 6, 4, 0, 2, 18, 52, 86, 120, 154, | ||
| 976 | 188, 192, 168, 16, 4, 34, 51, 68, 85, 102, 119, 192, 168, 16, 1, | ||
| 977 | ]; | ||
| 978 | expectations.push(SpiTransaction::write_vec(packet.to_vec())); | ||
| 979 | |||
| 980 | let mut tail = std::vec::Vec::<u8>::with_capacity(100); | ||
| 981 | // Padding | ||
| 982 | if let Some(padding_len) = (ETH_MIN_LEN - FSC_LEN).checked_sub(packet.len()) { | ||
| 983 | tail.resize(padding_len, 0x00); | ||
| 984 | } | ||
| 985 | // Packet FCS + optinal padding | ||
| 986 | tail.extend_from_slice(&[147, 149, 213, 68, DONT_CARE_BYTE, DONT_CARE_BYTE]); | ||
| 987 | |||
| 988 | expectations.push(SpiTransaction::write_vec(tail)); | ||
| 989 | expectations.push(SpiTransaction::flush()); | ||
| 990 | |||
| 991 | let mut spi = SpiMock::new(&expectations); | ||
| 992 | |||
| 993 | let cs = CsPinMock::default(); | ||
| 994 | let delay = MockDelay {}; | ||
| 995 | let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); | ||
| 996 | |||
| 997 | let mut spe = ADIN1110::new(spi_dev, true); | ||
| 998 | |||
| 999 | assert!(spe.write_fifo(&packet).await.is_ok()); | ||
| 1000 | |||
| 1001 | spi.done(); | ||
| 1002 | } | ||
| 1003 | |||
| 1004 | #[futures_test::test] | ||
| 1005 | async fn write_packet_to_fifo_arp_46bytes_without_crc() { | ||
| 1006 | // Configure expectations | ||
| 1007 | let mut expectations = vec![]; | ||
| 1008 | |||
| 1009 | // Write TX_SIZE reg | ||
| 1010 | expectations.push(SpiTransaction::write_vec(vec![160, 48, 0, 0, 0, 66])); | ||
| 1011 | expectations.push(SpiTransaction::flush()); | ||
| 1012 | |||
| 1013 | // Write TX reg. | ||
| 1014 | // SPI Header + Frame Header | ||
| 1015 | expectations.push(SpiTransaction::write_vec(vec![160, 49, 0, 0])); | ||
| 1016 | // Packet data | ||
| 1017 | let packet = [ | ||
| 1018 | 34, 51, 68, 85, 102, 119, 18, 52, 86, 120, 154, 188, 8, 6, 0, 1, 8, 0, 6, 4, 0, 2, 18, 52, 86, 120, 154, | ||
| 1019 | 188, 192, 168, 16, 4, 34, 51, 68, 85, 102, 119, 192, 168, 16, 1, | ||
| 1020 | ]; | ||
| 1021 | expectations.push(SpiTransaction::write_vec(packet.to_vec())); | ||
| 1022 | |||
| 1023 | let mut tail = std::vec::Vec::<u8>::with_capacity(100); | ||
| 1024 | // Padding | ||
| 1025 | if let Some(padding_len) = (ETH_MIN_LEN - FSC_LEN).checked_sub(packet.len()) { | ||
| 1026 | tail.resize(padding_len, 0x00); | ||
| 1027 | } | ||
| 1028 | // Packet FCS + optinal padding | ||
| 1029 | tail.extend_from_slice(&[147, 149, 213, 68, DONT_CARE_BYTE, DONT_CARE_BYTE]); | ||
| 1030 | |||
| 1031 | expectations.push(SpiTransaction::write_vec(tail)); | ||
| 1032 | expectations.push(SpiTransaction::flush()); | ||
| 1033 | |||
| 1034 | let mut spi = SpiMock::new(&expectations); | ||
| 1035 | |||
| 1036 | let cs = CsPinMock::default(); | ||
| 1037 | let delay = MockDelay {}; | ||
| 1038 | let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); | ||
| 1039 | |||
| 1040 | let mut spe = ADIN1110::new(spi_dev, false); | ||
| 1041 | |||
| 1042 | assert!(spe.write_fifo(&packet).await.is_ok()); | ||
| 1043 | |||
| 1044 | spi.done(); | ||
| 1045 | } | ||
| 1046 | } | ||
diff --git a/embassy-net-adin1110/src/mdio.rs b/embassy-net-adin1110/src/mdio.rs new file mode 100644 index 000000000..68477006a --- /dev/null +++ b/embassy-net-adin1110/src/mdio.rs | |||
| @@ -0,0 +1,175 @@ | |||
| 1 | /// PHY Address: (0..=0x1F), 5-bits long. | ||
| 2 | #[allow(dead_code)] | ||
| 3 | type PhyAddr = u8; | ||
| 4 | |||
| 5 | /// PHY Register: (0..=0x1F), 5-bits long. | ||
| 6 | #[allow(dead_code)] | ||
| 7 | type RegC22 = u8; | ||
| 8 | |||
| 9 | /// PHY Register Clause 45. | ||
| 10 | #[allow(dead_code)] | ||
| 11 | type RegC45 = u16; | ||
| 12 | |||
| 13 | /// PHY Register Value | ||
| 14 | #[allow(dead_code)] | ||
| 15 | type RegVal = u16; | ||
| 16 | |||
| 17 | #[allow(dead_code)] | ||
| 18 | const REG13: RegC22 = 13; | ||
| 19 | #[allow(dead_code)] | ||
| 20 | const REG14: RegC22 = 14; | ||
| 21 | |||
| 22 | #[allow(dead_code)] | ||
| 23 | const PHYADDR_MASK: u8 = 0x1f; | ||
| 24 | #[allow(dead_code)] | ||
| 25 | const DEV_MASK: u8 = 0x1f; | ||
| 26 | |||
| 27 | #[allow(dead_code)] | ||
| 28 | #[repr(u16)] | ||
| 29 | enum Reg13Op { | ||
| 30 | Addr = 0b00 << 14, | ||
| 31 | Write = 0b01 << 14, | ||
| 32 | PostReadIncAddr = 0b10 << 14, | ||
| 33 | Read = 0b11 << 14, | ||
| 34 | } | ||
| 35 | /// `MdioBus` trait | ||
| 36 | /// Driver needs to implement the Clause 22 | ||
| 37 | /// Optional Clause 45 is the device supports this. | ||
| 38 | /// | ||
| 39 | /// Claus 45 methodes are bases on <https://www.ieee802.org/3/efm/public/nov02/oam/pannell_oam_1_1102.pdf> | ||
| 40 | pub trait MdioBus { | ||
| 41 | type Error; | ||
| 42 | |||
| 43 | /// Read, Clause 22 | ||
| 44 | async fn read_cl22(&mut self, phy_id: PhyAddr, reg: RegC22) -> Result<RegVal, Self::Error>; | ||
| 45 | |||
| 46 | /// Write, Clause 22 | ||
| 47 | async fn write_cl22(&mut self, phy_id: PhyAddr, reg: RegC22, reg_val: RegVal) -> Result<(), Self::Error>; | ||
| 48 | |||
| 49 | /// Read, Clause 45 | ||
| 50 | /// This is the default implementation. | ||
| 51 | /// Many hardware these days support direct Clause 45 operations. | ||
| 52 | /// Implement this function when your hardware supports it. | ||
| 53 | async fn read_cl45(&mut self, phy_id: PhyAddr, regc45: (u8, RegC45)) -> Result<RegVal, Self::Error> { | ||
| 54 | // Write FN | ||
| 55 | let val = (Reg13Op::Addr as RegVal) | RegVal::from(regc45.0 & DEV_MASK); | ||
| 56 | |||
| 57 | self.write_cl22(phy_id, REG13, val).await?; | ||
| 58 | // Write Addr | ||
| 59 | self.write_cl22(phy_id, REG14, regc45.1).await?; | ||
| 60 | |||
| 61 | // Write FN | ||
| 62 | let val = (Reg13Op::Read as RegVal) | RegVal::from(regc45.0 & DEV_MASK); | ||
| 63 | self.write_cl22(phy_id, REG13, val).await?; | ||
| 64 | // Write Addr | ||
| 65 | self.read_cl22(phy_id, REG14).await | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Write, Clause 45 | ||
| 69 | /// This is the default implementation. | ||
| 70 | /// Many hardware these days support direct Clause 45 operations. | ||
| 71 | /// Implement this function when your hardware supports it. | ||
| 72 | async fn write_cl45(&mut self, phy_id: PhyAddr, regc45: (u8, RegC45), reg_val: RegVal) -> Result<(), Self::Error> { | ||
| 73 | let dev_addr = RegVal::from(regc45.0 & DEV_MASK); | ||
| 74 | let reg = regc45.1; | ||
| 75 | |||
| 76 | // Write FN | ||
| 77 | let val = (Reg13Op::Addr as RegVal) | dev_addr; | ||
| 78 | self.write_cl22(phy_id, REG13, val).await?; | ||
| 79 | // Write Addr | ||
| 80 | self.write_cl22(phy_id, REG14, reg).await?; | ||
| 81 | |||
| 82 | // Write FN | ||
| 83 | let val = (Reg13Op::Write as RegVal) | dev_addr; | ||
| 84 | self.write_cl22(phy_id, REG13, val).await?; | ||
| 85 | // Write Addr | ||
| 86 | self.write_cl22(phy_id, REG14, reg_val).await | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | // #[cfg(test)] | ||
| 91 | // mod tests { | ||
| 92 | // use core::convert::Infallible; | ||
| 93 | |||
| 94 | // use super::{MdioBus, PhyAddr, RegC22, RegVal}; | ||
| 95 | |||
| 96 | // #[derive(Debug, PartialEq, Eq)] | ||
| 97 | // enum A { | ||
| 98 | // Read(PhyAddr, RegC22), | ||
| 99 | // Write(PhyAddr, RegC22, RegVal), | ||
| 100 | // } | ||
| 101 | |||
| 102 | // struct MockMdioBus(Vec<A>); | ||
| 103 | |||
| 104 | // impl MockMdioBus { | ||
| 105 | // pub fn clear(&mut self) { | ||
| 106 | // self.0.clear(); | ||
| 107 | // } | ||
| 108 | // } | ||
| 109 | |||
| 110 | // impl MdioBus for MockMdioBus { | ||
| 111 | // type Error = Infallible; | ||
| 112 | |||
| 113 | // fn write_cl22( | ||
| 114 | // &mut self, | ||
| 115 | // phy_id: super::PhyAddr, | ||
| 116 | // reg: super::RegC22, | ||
| 117 | // reg_val: super::RegVal, | ||
| 118 | // ) -> Result<(), Self::Error> { | ||
| 119 | // self.0.push(A::Write(phy_id, reg, reg_val)); | ||
| 120 | // Ok(()) | ||
| 121 | // } | ||
| 122 | |||
| 123 | // fn read_cl22( | ||
| 124 | // &mut self, | ||
| 125 | // phy_id: super::PhyAddr, | ||
| 126 | // reg: super::RegC22, | ||
| 127 | // ) -> Result<super::RegVal, Self::Error> { | ||
| 128 | // self.0.push(A::Read(phy_id, reg)); | ||
| 129 | // Ok(0) | ||
| 130 | // } | ||
| 131 | // } | ||
| 132 | |||
| 133 | // #[test] | ||
| 134 | // fn read_test() { | ||
| 135 | // let mut mdiobus = MockMdioBus(Vec::with_capacity(20)); | ||
| 136 | |||
| 137 | // mdiobus.clear(); | ||
| 138 | // mdiobus.read_cl22(0x01, 0x00).unwrap(); | ||
| 139 | // assert_eq!(mdiobus.0, vec![A::Read(0x01, 0x00)]); | ||
| 140 | |||
| 141 | // mdiobus.clear(); | ||
| 142 | // mdiobus.read_cl45(0x01, (0xBB, 0x1234)).unwrap(); | ||
| 143 | // assert_eq!( | ||
| 144 | // mdiobus.0, | ||
| 145 | // vec![ | ||
| 146 | // #[allow(clippy::identity_op)] | ||
| 147 | // A::Write(0x01, 13, (0b00 << 14) | 27), | ||
| 148 | // A::Write(0x01, 14, 0x1234), | ||
| 149 | // A::Write(0x01, 13, (0b11 << 14) | 27), | ||
| 150 | // A::Read(0x01, 14) | ||
| 151 | // ] | ||
| 152 | // ); | ||
| 153 | // } | ||
| 154 | |||
| 155 | // #[test] | ||
| 156 | // fn write_test() { | ||
| 157 | // let mut mdiobus = MockMdioBus(Vec::with_capacity(20)); | ||
| 158 | |||
| 159 | // mdiobus.clear(); | ||
| 160 | // mdiobus.write_cl22(0x01, 0x00, 0xABCD).unwrap(); | ||
| 161 | // assert_eq!(mdiobus.0, vec![A::Write(0x01, 0x00, 0xABCD)]); | ||
| 162 | |||
| 163 | // mdiobus.clear(); | ||
| 164 | // mdiobus.write_cl45(0x01, (0xBB, 0x1234), 0xABCD).unwrap(); | ||
| 165 | // assert_eq!( | ||
| 166 | // mdiobus.0, | ||
| 167 | // vec![ | ||
| 168 | // A::Write(0x01, 13, 27), | ||
| 169 | // A::Write(0x01, 14, 0x1234), | ||
| 170 | // A::Write(0x01, 13, (0b01 << 14) | 27), | ||
| 171 | // A::Write(0x01, 14, 0xABCD) | ||
| 172 | // ] | ||
| 173 | // ); | ||
| 174 | // } | ||
| 175 | // } | ||
diff --git a/embassy-net-adin1110/src/phy.rs b/embassy-net-adin1110/src/phy.rs new file mode 100644 index 000000000..176ad019b --- /dev/null +++ b/embassy-net-adin1110/src/phy.rs | |||
| @@ -0,0 +1,142 @@ | |||
| 1 | use crate::mdio::MdioBus; | ||
| 2 | |||
| 3 | #[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)] | ||
| 4 | #[repr(u8)] | ||
| 5 | /// Clause 22 Registers | ||
| 6 | pub enum RegsC22 { | ||
| 7 | /// MII Control Register | ||
| 8 | CONTROL = 0x00, | ||
| 9 | /// MII Status Register | ||
| 10 | STATUS = 0x01, | ||
| 11 | /// PHY Identifier 1 Register | ||
| 12 | PHY_ID1 = 0x02, | ||
| 13 | /// PHY Identifier 2 Register. | ||
| 14 | PHY_ID2 = 0x03, | ||
| 15 | } | ||
| 16 | |||
| 17 | /// Clause 45 Registers | ||
| 18 | #[allow(non_snake_case, dead_code)] | ||
| 19 | pub mod RegsC45 { | ||
| 20 | /// Device Address: 0x01 | ||
| 21 | #[allow(non_camel_case_types, clippy::upper_case_acronyms)] | ||
| 22 | #[repr(u16)] | ||
| 23 | pub enum DA1 { | ||
| 24 | /// PMA/PMD Control 1 Register | ||
| 25 | PMA_PMD_CNTRL1 = 0x0000, | ||
| 26 | /// PMA/PMD Status 1 Register | ||
| 27 | PMA_PMD_STAT1 = 0x0001, | ||
| 28 | /// MSE Value Register | ||
| 29 | MSE_VAL = 0x830B, | ||
| 30 | } | ||
| 31 | |||
| 32 | impl DA1 { | ||
| 33 | #[must_use] | ||
| 34 | pub fn into(self) -> (u8, u16) { | ||
| 35 | (0x01, self as u16) | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | /// Device Address: 0x03 | ||
| 40 | #[allow(non_camel_case_types, clippy::upper_case_acronyms)] | ||
| 41 | #[repr(u16)] | ||
| 42 | pub enum DA3 { | ||
| 43 | /// PCS Control 1 Register | ||
| 44 | PCS_CNTRL1 = 0x0000, | ||
| 45 | /// PCS Status 1 Register | ||
| 46 | PCS_STAT1 = 0x0001, | ||
| 47 | /// PCS Status 2 Register | ||
| 48 | PCS_STAT2 = 0x0008, | ||
| 49 | } | ||
| 50 | |||
| 51 | impl DA3 { | ||
| 52 | #[must_use] | ||
| 53 | pub fn into(self) -> (u8, u16) { | ||
| 54 | (0x03, self as u16) | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | /// Device Address: 0x07 | ||
| 59 | #[allow(non_camel_case_types, clippy::upper_case_acronyms)] | ||
| 60 | #[repr(u16)] | ||
| 61 | pub enum DA7 { | ||
| 62 | /// Extra Autonegotiation Status Register | ||
| 63 | AN_STATUS_EXTRA = 0x8001, | ||
| 64 | } | ||
| 65 | |||
| 66 | impl DA7 { | ||
| 67 | #[must_use] | ||
| 68 | pub fn into(self) -> (u8, u16) { | ||
| 69 | (0x07, self as u16) | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | /// Device Address: 0x1E | ||
| 74 | #[allow(non_camel_case_types, clippy::upper_case_acronyms)] | ||
| 75 | #[repr(u16)] | ||
| 76 | pub enum DA1E { | ||
| 77 | /// System Interrupt Status Register | ||
| 78 | CRSM_IRQ_STATUS = 0x0010, | ||
| 79 | /// System Interrupt Mask Register | ||
| 80 | CRSM_IRQ_MASK = 0x0020, | ||
| 81 | /// Pin Mux Configuration 1 Register | ||
| 82 | DIGIO_PINMUX = 0x8c56, | ||
| 83 | /// LED Control Register. | ||
| 84 | LED_CNTRL = 0x8C82, | ||
| 85 | /// LED Polarity Register | ||
| 86 | LED_POLARITY = 0x8C83, | ||
| 87 | } | ||
| 88 | |||
| 89 | impl DA1E { | ||
| 90 | #[must_use] | ||
| 91 | pub fn into(self) -> (u8, u16) { | ||
| 92 | (0x1e, self as u16) | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | /// Device Address: 0x1F | ||
| 97 | #[allow(non_camel_case_types, clippy::upper_case_acronyms)] | ||
| 98 | #[repr(u16)] | ||
| 99 | pub enum DA1F { | ||
| 100 | /// PHY Subsystem Interrupt Status Register | ||
| 101 | PHY_SYBSYS_IRQ_STATUS = 0x0011, | ||
| 102 | /// PHY Subsystem Interrupt Mask Register | ||
| 103 | PHY_SYBSYS_IRQ_MASK = 0x0021, | ||
| 104 | } | ||
| 105 | |||
| 106 | impl DA1F { | ||
| 107 | #[must_use] | ||
| 108 | pub fn into(self) -> (u8, u16) { | ||
| 109 | (0x1f, self as u16) | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | pub struct Phy10BaseT1x(u8); | ||
| 115 | |||
| 116 | impl Default for Phy10BaseT1x { | ||
| 117 | fn default() -> Self { | ||
| 118 | Self(0x01) | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | impl Phy10BaseT1x { | ||
| 123 | /// Get the both parts of the PHYID. | ||
| 124 | pub async fn get_id<MDIOBUS, MDE>(&self, mdiobus: &mut MDIOBUS) -> Result<u32, MDE> | ||
| 125 | where | ||
| 126 | MDIOBUS: MdioBus<Error = MDE>, | ||
| 127 | MDE: core::fmt::Debug, | ||
| 128 | { | ||
| 129 | let mut phyid = u32::from(mdiobus.read_cl22(self.0, RegsC22::PHY_ID1 as u8).await?) << 16; | ||
| 130 | phyid |= u32::from(mdiobus.read_cl22(self.0, RegsC22::PHY_ID2 as u8).await?); | ||
| 131 | Ok(phyid) | ||
| 132 | } | ||
| 133 | |||
| 134 | /// Get the Mean Squared Error Value. | ||
| 135 | pub async fn get_sqi<MDIOBUS, MDE>(&self, mdiobus: &mut MDIOBUS) -> Result<u16, MDE> | ||
| 136 | where | ||
| 137 | MDIOBUS: MdioBus<Error = MDE>, | ||
| 138 | MDE: core::fmt::Debug, | ||
| 139 | { | ||
| 140 | mdiobus.read_cl45(self.0, RegsC45::DA1::MSE_VAL.into()).await | ||
| 141 | } | ||
| 142 | } | ||
diff --git a/embassy-net-adin1110/src/regs.rs b/embassy-net-adin1110/src/regs.rs new file mode 100644 index 000000000..4557929f0 --- /dev/null +++ b/embassy-net-adin1110/src/regs.rs | |||
| @@ -0,0 +1,408 @@ | |||
| 1 | use bitfield::{bitfield, bitfield_bitrange, bitfield_fields}; | ||
| 2 | |||
| 3 | #[allow(non_camel_case_types)] | ||
| 4 | #[derive(Debug, Copy, Clone)] | ||
| 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 6 | #[repr(u16)] | ||
| 7 | /// SPI REGISTER DETAILS | ||
| 8 | /// Table 38. | ||
| 9 | pub enum SpiRegisters { | ||
| 10 | IDVER = 0x00, | ||
| 11 | PHYID = 0x01, | ||
| 12 | CAPABILITY = 0x02, | ||
| 13 | RESET = 0x03, | ||
| 14 | CONFIG0 = 0x04, | ||
| 15 | CONFIG2 = 0x06, | ||
| 16 | STATUS0 = 0x08, | ||
| 17 | STATUS1 = 0x09, | ||
| 18 | IMASK0 = 0x0C, | ||
| 19 | IMASK1 = 0x0D, | ||
| 20 | MDIO_ACC = 0x20, | ||
| 21 | TX_FSIZE = 0x30, | ||
| 22 | TX = 0x31, | ||
| 23 | TX_SPACE = 0x32, | ||
| 24 | FIFO_CLR = 0x36, | ||
| 25 | ADDR_FILT_UPR0 = 0x50, | ||
| 26 | ADDR_FILT_LWR0 = 0x51, | ||
| 27 | ADDR_FILT_UPR1 = 0x52, | ||
| 28 | ADDR_FILT_LWR1 = 0x53, | ||
| 29 | ADDR_MSK_LWR0 = 0x70, | ||
| 30 | ADDR_MSK_UPR0 = 0x71, | ||
| 31 | ADDR_MSK_LWR1 = 0x72, | ||
| 32 | ADDR_MSK_UPR1 = 0x73, | ||
| 33 | RX_FSIZE = 0x90, | ||
| 34 | RX = 0x91, | ||
| 35 | } | ||
| 36 | |||
| 37 | impl From<SpiRegisters> for u16 { | ||
| 38 | fn from(val: SpiRegisters) -> Self { | ||
| 39 | val as u16 | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | impl From<u16> for SpiRegisters { | ||
| 44 | fn from(value: u16) -> Self { | ||
| 45 | match value { | ||
| 46 | 0x00 => Self::IDVER, | ||
| 47 | 0x01 => Self::PHYID, | ||
| 48 | 0x02 => Self::CAPABILITY, | ||
| 49 | 0x03 => Self::RESET, | ||
| 50 | 0x04 => Self::CONFIG0, | ||
| 51 | 0x06 => Self::CONFIG2, | ||
| 52 | 0x08 => Self::STATUS0, | ||
| 53 | 0x09 => Self::STATUS1, | ||
| 54 | 0x0C => Self::IMASK0, | ||
| 55 | 0x0D => Self::IMASK1, | ||
| 56 | 0x20 => Self::MDIO_ACC, | ||
| 57 | 0x30 => Self::TX_FSIZE, | ||
| 58 | 0x31 => Self::TX, | ||
| 59 | 0x32 => Self::TX_SPACE, | ||
| 60 | 0x36 => Self::FIFO_CLR, | ||
| 61 | 0x50 => Self::ADDR_FILT_UPR0, | ||
| 62 | 0x51 => Self::ADDR_FILT_LWR0, | ||
| 63 | 0x52 => Self::ADDR_FILT_UPR1, | ||
| 64 | 0x53 => Self::ADDR_FILT_LWR1, | ||
| 65 | 0x70 => Self::ADDR_MSK_LWR0, | ||
| 66 | 0x71 => Self::ADDR_MSK_UPR0, | ||
| 67 | 0x72 => Self::ADDR_MSK_LWR1, | ||
| 68 | 0x73 => Self::ADDR_MSK_UPR1, | ||
| 69 | 0x90 => Self::RX_FSIZE, | ||
| 70 | 0x91 => Self::RX, | ||
| 71 | e => panic!("Unknown value {e}"), | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | // Register definitions | ||
| 77 | bitfield! { | ||
| 78 | /// Status0 Register bits | ||
| 79 | pub struct Status0(u32); | ||
| 80 | impl Debug; | ||
| 81 | u32; | ||
| 82 | /// Control Data Protection Error | ||
| 83 | pub cdpe, _ : 12; | ||
| 84 | /// Transmit Frame Check Squence Error | ||
| 85 | pub txfcse, _: 11; | ||
| 86 | /// Transmit Time Stamp Capture Available C | ||
| 87 | pub ttscac, _ : 10; | ||
| 88 | /// Transmit Time Stamp Capture Available B | ||
| 89 | pub ttscab, _ : 9; | ||
| 90 | /// Transmit Time Stamp Capture Available A | ||
| 91 | pub ttscaa, _ : 8; | ||
| 92 | /// PHY Interrupt for Port 1 | ||
| 93 | pub phyint, _ : 7; | ||
| 94 | /// Reset Complete | ||
| 95 | pub resetc, _ : 6; | ||
| 96 | /// Header error | ||
| 97 | pub hdre, _ : 5; | ||
| 98 | /// Loss of Frame Error | ||
| 99 | pub lofe, _ : 4; | ||
| 100 | /// Receiver Buffer Overflow Error | ||
| 101 | pub rxboe, _ : 3; | ||
| 102 | /// Host Tx FIFO Under Run Error | ||
| 103 | pub txbue, _ : 2; | ||
| 104 | /// Host Tx FIFO Overflow | ||
| 105 | pub txboe, _ : 1; | ||
| 106 | /// Transmit Protocol Error | ||
| 107 | pub txpe, _ : 0; | ||
| 108 | } | ||
| 109 | |||
| 110 | bitfield! { | ||
| 111 | /// Status1 Register bits | ||
| 112 | pub struct Status1(u32); | ||
| 113 | impl Debug; | ||
| 114 | u32; | ||
| 115 | /// ECC Error on Reading the Frame Size from a Tx FIFO | ||
| 116 | pub tx_ecc_err, set_tx_ecc_err: 12; | ||
| 117 | /// ECC Error on Reading the Frame Size from an Rx FIFO | ||
| 118 | pub rx_ecc_err, set_rx_ecc_err : 11; | ||
| 119 | /// Detected an Error on an SPI Transaction | ||
| 120 | pub spi_err, set_spi_err: 10; | ||
| 121 | /// Rx MAC Interframe Gap Error | ||
| 122 | pub p1_rx_ifg_err, set_p1_rx_ifg_err : 8; | ||
| 123 | /// Port1 Rx Ready High Priority | ||
| 124 | pub p1_rx_rdy_hi, set_p1_rx_rdy_hi : 5; | ||
| 125 | /// Port 1 Rx FIFO Contains Data | ||
| 126 | pub p1_rx_rdy, set_p1_rx_rdy : 4; | ||
| 127 | /// Tx Ready | ||
| 128 | pub tx_rdy, set_tx_rdy : 3; | ||
| 129 | /// Link Status Changed | ||
| 130 | pub link_change, set_link_change : 1; | ||
| 131 | /// Port 1 Link Status | ||
| 132 | pub p1_link_status, _ : 0; | ||
| 133 | } | ||
| 134 | |||
| 135 | bitfield! { | ||
| 136 | /// Config0 Register bits | ||
| 137 | pub struct Config0(u32); | ||
| 138 | impl Debug; | ||
| 139 | u32; | ||
| 140 | /// Configuration Synchronization | ||
| 141 | pub sync, set_sync : 15; | ||
| 142 | /// Transmit Frame Check Sequence Validation Enable | ||
| 143 | pub txfcsve, set_txfcsve : 14; | ||
| 144 | /// !CS Align Receive Frame Enable | ||
| 145 | pub csarfe, set_csarfe : 13; | ||
| 146 | /// Zero Align Receive Frame Enable | ||
| 147 | pub zarfe, set_zarfe : 12; | ||
| 148 | /// Transmit Credit Threshold | ||
| 149 | pub tcxthresh, set_tcxthresh : 11, 10; | ||
| 150 | /// Transmit Cut Through Enable | ||
| 151 | pub txcte, set_txcte : 9; | ||
| 152 | /// Receive Cut Through Enable | ||
| 153 | pub rxcte, set_rxcte : 8; | ||
| 154 | /// Frame Time Stamp Enable | ||
| 155 | pub ftse, set_ftse : 7; | ||
| 156 | /// Receive Frame Time Stamp Select | ||
| 157 | pub ftss, set_ftss : 6; | ||
| 158 | /// Enable Control Data Read Write Protection | ||
| 159 | pub prote, set_prote : 5; | ||
| 160 | /// Enable TX Data Chunk Sequence and Retry | ||
| 161 | pub seqe, set_seqe : 4; | ||
| 162 | /// Chunk Payload Selector (N). | ||
| 163 | pub cps, set_cps : 2, 0; | ||
| 164 | } | ||
| 165 | |||
| 166 | bitfield! { | ||
| 167 | /// Config2 Register bits | ||
| 168 | pub struct Config2(u32); | ||
| 169 | impl Debug; | ||
| 170 | u32; | ||
| 171 | /// Assert TX_RDY When the Tx FIFO is Empty | ||
| 172 | pub tx_rdy_on_empty, set_tx_rdy_on_empty : 8; | ||
| 173 | /// Determines If the SFD is Detected in the PHY or MAC | ||
| 174 | pub sdf_detect_src, set_sdf_detect_src : 7; | ||
| 175 | /// Statistics Clear on Reading | ||
| 176 | pub stats_clr_on_rd, set_stats_clr_on_rd : 6; | ||
| 177 | /// Enable CRC Append | ||
| 178 | pub crc_append, set_crc_append : 5; | ||
| 179 | /// Admit Frames with IFG Errors on Port 1 (P1) | ||
| 180 | pub p1_rcv_ifg_err_frm, set_p1_rcv_ifg_err_frm : 4; | ||
| 181 | /// Forward Frames Not Matching Any MAC Address to the Host | ||
| 182 | pub p1_fwd_unk2host, set_p1_fwd_unk2host : 2; | ||
| 183 | /// SPI to MDIO Bridge MDC Clock Speed | ||
| 184 | pub mspeed, set_mspeed : 0; | ||
| 185 | } | ||
| 186 | |||
| 187 | bitfield! { | ||
| 188 | /// IMASK0 Register bits | ||
| 189 | pub struct IMask0(u32); | ||
| 190 | impl Debug; | ||
| 191 | u32; | ||
| 192 | /// Control Data Protection Error Mask | ||
| 193 | pub cppem, set_cppem : 12; | ||
| 194 | /// Transmit Frame Check Sequence Error Mask | ||
| 195 | pub txfcsem, set_txfcsem : 11; | ||
| 196 | /// Transmit Time Stamp Capture Available C Mask | ||
| 197 | pub ttscacm, set_ttscacm : 10; | ||
| 198 | /// Transmit Time Stamp Capture Available B Mask | ||
| 199 | pub ttscabm, set_ttscabm : 9; | ||
| 200 | /// Transmit Time Stamp Capture Available A Mask | ||
| 201 | pub ttscaam, set_ttscaam : 8; | ||
| 202 | /// Physical Layer Interrupt Mask | ||
| 203 | pub phyintm, set_phyintm : 7; | ||
| 204 | /// RESET Complete Mask | ||
| 205 | pub resetcm, set_resetcm : 6; | ||
| 206 | /// Header Error Mask | ||
| 207 | pub hdrem, set_hdrem : 5; | ||
| 208 | /// Loss of Frame Error Mask | ||
| 209 | pub lofem, set_lofem : 4; | ||
| 210 | /// Receive Buffer Overflow Error Mask | ||
| 211 | pub rxboem, set_rxboem : 3; | ||
| 212 | /// Transmit Buffer Underflow Error Mask | ||
| 213 | pub txbuem, set_txbuem : 2; | ||
| 214 | /// Transmit Buffer Overflow Error Mask | ||
| 215 | pub txboem, set_txboem : 1; | ||
| 216 | /// Transmit Protocol Error Mask | ||
| 217 | pub txpem, set_txpem : 0; | ||
| 218 | } | ||
| 219 | |||
| 220 | bitfield! { | ||
| 221 | /// IMASK1 Register bits | ||
| 222 | pub struct IMask1(u32); | ||
| 223 | impl Debug; | ||
| 224 | u32; | ||
| 225 | /// Mask Bit for TXF_ECC_ERR | ||
| 226 | pub tx_ecc_err_mask, set_tx_ecc_err_mask : 12; | ||
| 227 | /// Mask Bit for RXF_ECC_ERR | ||
| 228 | pub rx_ecc_err_mask, set_rx_ecc_err_mask : 11; | ||
| 229 | /// Mask Bit for SPI_ERR | ||
| 230 | /// This field is only used with the generic SPI protocol | ||
| 231 | pub spi_err_mask, set_spi_err_mask : 10; | ||
| 232 | /// Mask Bit for RX_IFG_ERR | ||
| 233 | pub p1_rx_ifg_err_mask, set_p1_rx_ifg_err_mask : 8; | ||
| 234 | /// Mask Bit for P1_RX_RDY | ||
| 235 | /// This field is only used with the generic SPI protocol | ||
| 236 | pub p1_rx_rdy_mask, set_p1_rx_rdy_mask : 4; | ||
| 237 | /// Mask Bit for TX_FRM_DONE | ||
| 238 | /// This field is only used with the generic SPI protocol | ||
| 239 | pub tx_rdy_mask, set_tx_rdy_mask : 3; | ||
| 240 | /// Mask Bit for LINK_CHANGE | ||
| 241 | pub link_change_mask, set_link_change_mask : 1; | ||
| 242 | } | ||
| 243 | |||
| 244 | /// LED Functions | ||
| 245 | #[repr(u8)] | ||
| 246 | pub enum LedFunc { | ||
| 247 | LinkupTxRxActicity = 0, | ||
| 248 | LinkupTxActicity, | ||
| 249 | LinkupRxActicity, | ||
| 250 | LinkupOnly, | ||
| 251 | TxRxActivity, | ||
| 252 | TxActivity, | ||
| 253 | RxActivity, | ||
| 254 | LinkupRxEr, | ||
| 255 | LinkupRxTxEr, | ||
| 256 | RxEr, | ||
| 257 | RxTxEr, | ||
| 258 | TxSop, | ||
| 259 | RxSop, | ||
| 260 | On, | ||
| 261 | Off, | ||
| 262 | Blink, | ||
| 263 | TxLevel2P4, | ||
| 264 | TxLevel1P0, | ||
| 265 | Master, | ||
| 266 | Slave, | ||
| 267 | IncompatiableLinkCfg, | ||
| 268 | AnLinkGood, | ||
| 269 | AnComplete, | ||
| 270 | TsTimer, | ||
| 271 | LocRcvrStatus, | ||
| 272 | RemRcvrStatus, | ||
| 273 | Clk25Ref, | ||
| 274 | TxTCLK, | ||
| 275 | Clk120MHz, | ||
| 276 | } | ||
| 277 | |||
| 278 | impl From<LedFunc> for u8 { | ||
| 279 | fn from(val: LedFunc) -> Self { | ||
| 280 | val as u8 | ||
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | impl From<u8> for LedFunc { | ||
| 285 | fn from(value: u8) -> Self { | ||
| 286 | match value { | ||
| 287 | 0 => LedFunc::LinkupTxRxActicity, | ||
| 288 | 1 => LedFunc::LinkupTxActicity, | ||
| 289 | 2 => LedFunc::LinkupRxActicity, | ||
| 290 | 3 => LedFunc::LinkupOnly, | ||
| 291 | 4 => LedFunc::TxRxActivity, | ||
| 292 | 5 => LedFunc::TxActivity, | ||
| 293 | 6 => LedFunc::RxActivity, | ||
| 294 | 7 => LedFunc::LinkupRxEr, | ||
| 295 | 8 => LedFunc::LinkupRxTxEr, | ||
| 296 | 9 => LedFunc::RxEr, | ||
| 297 | 10 => LedFunc::RxTxEr, | ||
| 298 | 11 => LedFunc::TxSop, | ||
| 299 | 12 => LedFunc::RxSop, | ||
| 300 | 13 => LedFunc::On, | ||
| 301 | 14 => LedFunc::Off, | ||
| 302 | 15 => LedFunc::Blink, | ||
| 303 | 16 => LedFunc::TxLevel2P4, | ||
| 304 | 17 => LedFunc::TxLevel1P0, | ||
| 305 | 18 => LedFunc::Master, | ||
| 306 | 19 => LedFunc::Slave, | ||
| 307 | 20 => LedFunc::IncompatiableLinkCfg, | ||
| 308 | 21 => LedFunc::AnLinkGood, | ||
| 309 | 22 => LedFunc::AnComplete, | ||
| 310 | 23 => LedFunc::TsTimer, | ||
| 311 | 24 => LedFunc::LocRcvrStatus, | ||
| 312 | 25 => LedFunc::RemRcvrStatus, | ||
| 313 | 26 => LedFunc::Clk25Ref, | ||
| 314 | 27 => LedFunc::TxTCLK, | ||
| 315 | 28 => LedFunc::Clk120MHz, | ||
| 316 | e => panic!("Invalid value {e}"), | ||
| 317 | } | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | /// LED Control Register | ||
| 322 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] | ||
| 323 | pub struct LedCntrl(pub u16); | ||
| 324 | bitfield_bitrange! {struct LedCntrl(u16)} | ||
| 325 | |||
| 326 | impl LedCntrl { | ||
| 327 | bitfield_fields! { | ||
| 328 | u8; | ||
| 329 | /// LED 0 Pin Function | ||
| 330 | pub from into LedFunc, led0_function, set_led0_function: 4, 0; | ||
| 331 | /// LED 0 Mode Selection | ||
| 332 | pub led0_mode, set_led0_mode: 5; | ||
| 333 | /// Qualify Certain LED 0 Options with Link Status. | ||
| 334 | pub led0_link_st_qualify, set_led0_link_st_qualify: 6; | ||
| 335 | /// LED 0 Enable | ||
| 336 | pub led0_en, set_led0_en: 7; | ||
| 337 | /// LED 1 Pin Function | ||
| 338 | pub from into LedFunc, led1_function, set_led1_function: 12, 8; | ||
| 339 | /// /// LED 1 Mode Selection | ||
| 340 | pub led1_mode, set_led1_mode: 13; | ||
| 341 | /// Qualify Certain LED 1 Options with Link Status. | ||
| 342 | pub led1_link_st_qualify, set_led1_link_st_qualify: 14; | ||
| 343 | /// LED 1 Enable | ||
| 344 | pub led1_en, set_led1_en: 15; | ||
| 345 | } | ||
| 346 | |||
| 347 | pub fn new() -> Self { | ||
| 348 | LedCntrl(0) | ||
| 349 | } | ||
| 350 | } | ||
| 351 | |||
| 352 | // LED Polarity | ||
| 353 | #[repr(u8)] | ||
| 354 | pub enum LedPol { | ||
| 355 | AutoSense = 0, | ||
| 356 | ActiveHigh, | ||
| 357 | ActiveLow, | ||
| 358 | } | ||
| 359 | |||
| 360 | impl From<LedPol> for u8 { | ||
| 361 | fn from(val: LedPol) -> Self { | ||
| 362 | val as u8 | ||
| 363 | } | ||
| 364 | } | ||
| 365 | |||
| 366 | impl From<u8> for LedPol { | ||
| 367 | fn from(value: u8) -> Self { | ||
| 368 | match value { | ||
| 369 | 0 => LedPol::AutoSense, | ||
| 370 | 1 => LedPol::ActiveHigh, | ||
| 371 | 2 => LedPol::ActiveLow, | ||
| 372 | e => panic!("Invalid value {e}"), | ||
| 373 | } | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | /// LED Control Register | ||
| 378 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] | ||
| 379 | pub struct LedPolarity(pub u16); | ||
| 380 | bitfield_bitrange! {struct LedPolarity(u16)} | ||
| 381 | |||
| 382 | impl LedPolarity { | ||
| 383 | bitfield_fields! { | ||
| 384 | u8; | ||
| 385 | /// LED 1 Polarity | ||
| 386 | pub from into LedPol, led1_polarity, set_led1_polarity: 3, 2; | ||
| 387 | /// LED 0 Polarity | ||
| 388 | pub from into LedPol, led0_polarity, set_led0_polarity: 1, 0; | ||
| 389 | } | ||
| 390 | } | ||
| 391 | |||
| 392 | /// SPI Header | ||
| 393 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] | ||
| 394 | pub struct SpiHeader(pub u16); | ||
| 395 | bitfield_bitrange! {struct SpiHeader(u16)} | ||
| 396 | |||
| 397 | impl SpiHeader { | ||
| 398 | bitfield_fields! { | ||
| 399 | u16; | ||
| 400 | /// Mask Bit for TXF_ECC_ERR | ||
| 401 | pub control, set_control : 15; | ||
| 402 | pub full_duplex, set_full_duplex : 14; | ||
| 403 | /// Read or Write to register | ||
| 404 | pub write, set_write : 13; | ||
| 405 | /// Registers ID/addr | ||
| 406 | pub from into SpiRegisters, addr, set_addr: 11, 0; | ||
| 407 | } | ||
| 408 | } | ||
diff --git a/examples/stm32l4/.cargo/config.toml b/examples/stm32l4/.cargo/config.toml index 36e74e5a5..db3a7ceff 100644 --- a/examples/stm32l4/.cargo/config.toml +++ b/examples/stm32l4/.cargo/config.toml | |||
| @@ -2,7 +2,7 @@ | |||
| 2 | # replace STM32F429ZITx with your chip as listed in `probe-rs chip list` | 2 | # replace STM32F429ZITx with your chip as listed in `probe-rs chip list` |
| 3 | #runner = "probe-rs run --chip STM32L475VGT6" | 3 | #runner = "probe-rs run --chip STM32L475VGT6" |
| 4 | #runner = "probe-rs run --chip STM32L475VG" | 4 | #runner = "probe-rs run --chip STM32L475VG" |
| 5 | runner = "probe-rs run --chip STM32L4S5VI" | 5 | runner = "probe-run --chip STM32L4S5QI" |
| 6 | 6 | ||
| 7 | [build] | 7 | [build] |
| 8 | target = "thumbv7em-none-eabi" | 8 | target = "thumbv7em-none-eabi" |
diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index f552a6109..e5be94eda 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml | |||
| @@ -6,12 +6,17 @@ license = "MIT OR Apache-2.0" | |||
| 6 | 6 | ||
| 7 | [dependencies] | 7 | [dependencies] |
| 8 | # Change stm32l4s5vi to your chip name, if necessary. | 8 | # Change stm32l4s5vi to your chip name, if necessary. |
| 9 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5vi", "memory-x", "time-driver-any", "exti", "unstable-traits", "chrono"] } | 9 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5qi", "memory-x", "time-driver-any", "exti", "unstable-traits", "chrono"] } |
| 10 | embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } | 10 | embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } |
| 11 | embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } | 11 | embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } |
| 12 | embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } | 12 | embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", "unstable-traits", "nightly"] } |
| 13 | embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } | 13 | embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } |
| 14 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } | 14 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } |
| 15 | embassy-net-adin1110 = { version = "0.1.0", path = "../../embassy-net-adin1110", default-features = false } | ||
| 16 | embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "udp", "tcp", "dhcpv4", "medium-ethernet"] } | ||
| 17 | embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } | ||
| 18 | embedded-io-async = { version = "0.5.0", features = ["defmt-03"] } | ||
| 19 | embedded-io = { version = "0.5.0", features = ["defmt-03"] } | ||
| 15 | 20 | ||
| 16 | defmt = "0.3" | 21 | defmt = "0.3" |
| 17 | defmt-rtt = "0.4" | 22 | defmt-rtt = "0.4" |
| @@ -21,10 +26,13 @@ cortex-m-rt = "0.7.0" | |||
| 21 | embedded-hal = "0.2.6" | 26 | embedded-hal = "0.2.6" |
| 22 | embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } | 27 | embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } |
| 23 | embedded-hal-async = { version = "=1.0.0-rc.1" } | 28 | embedded-hal-async = { version = "=1.0.0-rc.1" } |
| 29 | embedded-hal-bus = { version = "=0.1.0-rc.1", features = ["async"] } | ||
| 24 | panic-probe = { version = "0.3", features = ["print-defmt"] } | 30 | panic-probe = { version = "0.3", features = ["print-defmt"] } |
| 25 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } | 31 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } |
| 26 | heapless = { version = "0.7.5", default-features = false } | 32 | heapless = { version = "0.7.5", default-features = false } |
| 27 | chrono = { version = "^0.4", default-features = false } | 33 | chrono = { version = "^0.4", default-features = false } |
| 34 | rand = { version = "0.8.5", default-features = false } | ||
| 35 | static_cell = {version = "1.1", features = ["nightly"]} | ||
| 28 | 36 | ||
| 29 | micromath = "2.0.0" | 37 | micromath = "2.0.0" |
| 30 | 38 | ||
diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs new file mode 100644 index 000000000..148c58771 --- /dev/null +++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs | |||
| @@ -0,0 +1,438 @@ | |||
| 1 | #![deny(clippy::pedantic)] | ||
| 2 | #![allow(clippy::doc_markdown)] | ||
| 3 | #![no_main] | ||
| 4 | #![no_std] | ||
| 5 | // Needed unitl https://github.com/rust-lang/rust/issues/63063 is stablised. | ||
| 6 | #![feature(type_alias_impl_trait)] | ||
| 7 | #![feature(associated_type_bounds)] | ||
| 8 | #![allow(clippy::missing_errors_doc)] | ||
| 9 | |||
| 10 | // This example works on a ANALOG DEVICE EVAL-ADIN110EBZ board. | ||
| 11 | // Settings switch S201 "HW CFG": | ||
| 12 | // - Without SPI CRC: OFF-ON-OFF-OFF-OFF | ||
| 13 | // - With SPI CRC: ON -ON-OFF-OFF-OFF | ||
| 14 | // Settings switch S303 "uC CFG": CFG0: On = static ip, Off = Dhcp | ||
| 15 | // The webserver shows the actual temperature of the onboard i2c temp sensor. | ||
| 16 | |||
| 17 | use core::marker::PhantomData; | ||
| 18 | use core::sync::atomic::{AtomicI32, Ordering}; | ||
| 19 | |||
| 20 | use defmt::{error, info, println, unwrap, Format}; | ||
| 21 | use defmt_rtt as _; // global logger | ||
| 22 | use embassy_executor::Spawner; | ||
| 23 | use embassy_futures::select::{select, Either}; | ||
| 24 | use embassy_futures::yield_now; | ||
| 25 | use embassy_net::tcp::TcpSocket; | ||
| 26 | use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources, StaticConfigV4}; | ||
| 27 | use embassy_time::{Delay, Duration, Ticker, Timer}; | ||
| 28 | use embedded_hal_async::i2c::I2c as I2cBus; | ||
| 29 | use embedded_io::Write as bWrite; | ||
| 30 | use embedded_io_async::Write; | ||
| 31 | use hal::gpio::{Input, Level, Output, Speed}; | ||
| 32 | use hal::i2c::{self, I2c}; | ||
| 33 | use hal::rcc::{self}; | ||
| 34 | use hal::rng::{self, Rng}; | ||
| 35 | use hal::{bind_interrupts, exti, pac, peripherals}; | ||
| 36 | use heapless::Vec; | ||
| 37 | use rand::RngCore; | ||
| 38 | use static_cell::make_static; | ||
| 39 | use {embassy_stm32 as hal, panic_probe as _}; | ||
| 40 | |||
| 41 | bind_interrupts!(struct Irqs { | ||
| 42 | I2C3_EV => i2c::InterruptHandler<peripherals::I2C3>; | ||
| 43 | RNG => rng::InterruptHandler<peripherals::RNG>; | ||
| 44 | }); | ||
| 45 | |||
| 46 | use embassy_net_adin1110::{self, Device, Runner, ADIN1110}; | ||
| 47 | use embedded_hal_bus::spi::ExclusiveDevice; | ||
| 48 | use hal::gpio::Pull; | ||
| 49 | use hal::i2c::Config as I2C_Config; | ||
| 50 | use hal::rcc::{ClockSrc, PLLClkDiv, PLLMul, PLLSource, PLLSrcDiv}; | ||
| 51 | use hal::spi::{Config as SPI_Config, Spi}; | ||
| 52 | use hal::time::Hertz; | ||
| 53 | |||
| 54 | // Basic settings | ||
| 55 | // MAC-address used by the adin1110 | ||
| 56 | const MAC: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; | ||
| 57 | // Static IP settings | ||
| 58 | const IP_ADDRESS: Ipv4Cidr = Ipv4Cidr::new(Ipv4Address([192, 168, 1, 5]), 24); | ||
| 59 | // Listen port for the webserver | ||
| 60 | const HTTP_LISTEN_PORT: u16 = 80; | ||
| 61 | |||
| 62 | pub type SpeSpi = Spi<'static, peripherals::SPI2, peripherals::DMA1_CH1, peripherals::DMA1_CH2>; | ||
| 63 | pub type SpeSpiCs = ExclusiveDevice<SpeSpi, Output<'static, peripherals::PB12>, Delay>; | ||
| 64 | pub type SpeInt = exti::ExtiInput<'static, peripherals::PB11>; | ||
| 65 | pub type SpeRst = Output<'static, peripherals::PC7>; | ||
| 66 | pub type Adin1110T = ADIN1110<SpeSpiCs>; | ||
| 67 | pub type TempSensI2c = I2c<'static, peripherals::I2C3, peripherals::DMA1_CH6, peripherals::DMA1_CH7>; | ||
| 68 | |||
| 69 | static TEMP: AtomicI32 = AtomicI32::new(0); | ||
| 70 | |||
| 71 | #[embassy_executor::main] | ||
| 72 | async fn main(spawner: Spawner) { | ||
| 73 | defmt::println!("Start main()"); | ||
| 74 | |||
| 75 | let mut config = embassy_stm32::Config::default(); | ||
| 76 | |||
| 77 | // 80Mhz clock (Source: 8 / SrcDiv: 1 * PLLMul 20 / ClkDiv 2) | ||
| 78 | // 80MHz highest frequency for flash 0 wait. | ||
| 79 | config.rcc.mux = ClockSrc::PLL( | ||
| 80 | PLLSource::HSE(Hertz(8_000_000)), | ||
| 81 | PLLClkDiv::Div2, | ||
| 82 | PLLSrcDiv::Div1, | ||
| 83 | PLLMul::Mul20, | ||
| 84 | None, | ||
| 85 | ); | ||
| 86 | config.rcc.hsi48 = true; // needed for rng | ||
| 87 | config.rcc.rtc_mux = rcc::RtcClockSource::LSI32; | ||
| 88 | |||
| 89 | let dp = embassy_stm32::init(config); | ||
| 90 | |||
| 91 | // RM0432rev9, 5.1.2: Independent I/O supply rail | ||
| 92 | // After reset, the I/Os supplied by VDDIO2 are logically and electrically isolated and | ||
| 93 | // therefore are not available. The isolation must be removed before using any I/O from | ||
| 94 | // PG[15:2], by setting the IOSV bit in the PWR_CR2 register, once the VDDIO2 supply is present | ||
| 95 | pac::PWR.cr2().modify(|w| w.set_iosv(true)); | ||
| 96 | |||
| 97 | let reset_status = pac::RCC.bdcr().read().0; | ||
| 98 | defmt::println!("bdcr before: 0x{:X}", reset_status); | ||
| 99 | |||
| 100 | defmt::println!("Setup IO pins"); | ||
| 101 | |||
| 102 | // Setup LEDs | ||
| 103 | let _led_uc1_green = Output::new(dp.PC13, Level::Low, Speed::Low); | ||
| 104 | let mut led_uc2_red = Output::new(dp.PE2, Level::High, Speed::Low); | ||
| 105 | let led_uc3_yellow = Output::new(dp.PE6, Level::High, Speed::Low); | ||
| 106 | let led_uc4_blue = Output::new(dp.PG15, Level::High, Speed::Low); | ||
| 107 | |||
| 108 | // Read the uc_cfg switches | ||
| 109 | let uc_cfg0 = Input::new(dp.PB2, Pull::None); | ||
| 110 | let _uc_cfg1 = Input::new(dp.PF11, Pull::None); | ||
| 111 | let _uc_cfg2 = Input::new(dp.PG6, Pull::None); | ||
| 112 | let _uc_cfg3 = Input::new(dp.PG11, Pull::None); | ||
| 113 | |||
| 114 | // Setup I2C pins | ||
| 115 | let temp_sens_i2c = I2c::new( | ||
| 116 | dp.I2C3, | ||
| 117 | dp.PG7, | ||
| 118 | dp.PG8, | ||
| 119 | Irqs, | ||
| 120 | dp.DMA1_CH6, | ||
| 121 | dp.DMA1_CH7, | ||
| 122 | Hertz(100_000), | ||
| 123 | I2C_Config::default(), | ||
| 124 | ); | ||
| 125 | |||
| 126 | // Setup IO and SPI for the SPE chip | ||
| 127 | let spe_reset_n = Output::new(dp.PC7, Level::Low, Speed::Low); | ||
| 128 | let spe_cfg0 = Input::new(dp.PC8, Pull::None); | ||
| 129 | let spe_cfg1 = Input::new(dp.PC9, Pull::None); | ||
| 130 | let _spe_ts_capt = Output::new(dp.PC6, Level::Low, Speed::Low); | ||
| 131 | |||
| 132 | let spe_int = Input::new(dp.PB11, Pull::None); | ||
| 133 | let spe_int = exti::ExtiInput::new(spe_int, dp.EXTI11); | ||
| 134 | |||
| 135 | let spe_spi_cs_n = Output::new(dp.PB12, Level::High, Speed::High); | ||
| 136 | let spe_spi_sclk = dp.PB13; | ||
| 137 | let spe_spi_miso = dp.PB14; | ||
| 138 | let spe_spi_mosi = dp.PB15; | ||
| 139 | |||
| 140 | // Don't turn the clock to high, clock must fit within the system clock as we get a runtime panic. | ||
| 141 | let mut spi_config = SPI_Config::default(); | ||
| 142 | spi_config.frequency = Hertz(25_000_000); | ||
| 143 | |||
| 144 | let spe_spi: SpeSpi = Spi::new( | ||
| 145 | dp.SPI2, | ||
| 146 | spe_spi_sclk, | ||
| 147 | spe_spi_mosi, | ||
| 148 | spe_spi_miso, | ||
| 149 | dp.DMA1_CH1, | ||
| 150 | dp.DMA1_CH2, | ||
| 151 | spi_config, | ||
| 152 | ); | ||
| 153 | let spe_spi = SpeSpiCs::new(spe_spi, spe_spi_cs_n, Delay); | ||
| 154 | |||
| 155 | let cfg0_without_crc = spe_cfg0.is_high(); | ||
| 156 | let cfg1_spi_mode = spe_cfg1.is_high(); | ||
| 157 | |||
| 158 | defmt::println!( | ||
| 159 | "ADIN1110: CFG SPI-MODE 1-{}, CRC-bit 0-{}", | ||
| 160 | cfg1_spi_mode, | ||
| 161 | cfg0_without_crc | ||
| 162 | ); | ||
| 163 | |||
| 164 | // Check the SPI mode selected with the "HW CFG" dip-switch | ||
| 165 | if !cfg1_spi_mode { | ||
| 166 | error!("Driver doesn´t support SPI Protolcol \"OPEN Alliance\".\nplease use the \"Generic SPI\"! Turn On \"HW CFG\": \"SPI_CFG1\""); | ||
| 167 | loop { | ||
| 168 | led_uc2_red.toggle(); | ||
| 169 | Timer::after(Duration::from_hz(10)).await; | ||
| 170 | } | ||
| 171 | }; | ||
| 172 | |||
| 173 | let state = make_static!(embassy_net_adin1110::State::<8, 8>::new()); | ||
| 174 | |||
| 175 | let (device, runner) = | ||
| 176 | embassy_net_adin1110::new(MAC, state, spe_spi, spe_int, spe_reset_n, !cfg0_without_crc).await; | ||
| 177 | |||
| 178 | // Start task blink_led | ||
| 179 | unwrap!(spawner.spawn(heartbeat_led(led_uc3_yellow))); | ||
| 180 | // Start task temperature measurement | ||
| 181 | unwrap!(spawner.spawn(temp_task(temp_sens_i2c, led_uc4_blue))); | ||
| 182 | // Start ethernet task | ||
| 183 | unwrap!(spawner.spawn(ethernet_task(runner))); | ||
| 184 | |||
| 185 | let mut rng = Rng::new(dp.RNG, Irqs); | ||
| 186 | // Generate random seed | ||
| 187 | let seed = rng.next_u64(); | ||
| 188 | |||
| 189 | let ip_cfg = if uc_cfg0.is_low() { | ||
| 190 | println!("Waiting for DHCP..."); | ||
| 191 | let dhcp4_config = embassy_net::DhcpConfig::default(); | ||
| 192 | embassy_net::Config::dhcpv4(dhcp4_config) | ||
| 193 | } else { | ||
| 194 | embassy_net::Config::ipv4_static(StaticConfigV4 { | ||
| 195 | address: IP_ADDRESS, | ||
| 196 | gateway: None, | ||
| 197 | dns_servers: Vec::new(), | ||
| 198 | }) | ||
| 199 | }; | ||
| 200 | |||
| 201 | // Init network stack | ||
| 202 | let stack = &*make_static!(Stack::new( | ||
| 203 | device, | ||
| 204 | ip_cfg, | ||
| 205 | make_static!(StackResources::<2>::new()), | ||
| 206 | seed | ||
| 207 | )); | ||
| 208 | |||
| 209 | // Launch network task | ||
| 210 | unwrap!(spawner.spawn(net_task(stack))); | ||
| 211 | |||
| 212 | let cfg = wait_for_config(stack).await; | ||
| 213 | let local_addr = cfg.address.address(); | ||
| 214 | |||
| 215 | // Then we can use it! | ||
| 216 | let mut rx_buffer = [0; 4096]; | ||
| 217 | let mut tx_buffer = [0; 4096]; | ||
| 218 | let mut mb_buf = [0; 4096]; | ||
| 219 | loop { | ||
| 220 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); | ||
| 221 | socket.set_timeout(Some(Duration::from_secs(1))); | ||
| 222 | |||
| 223 | info!("Listening on http://{}:{}...", local_addr, HTTP_LISTEN_PORT); | ||
| 224 | if let Err(e) = socket.accept(HTTP_LISTEN_PORT).await { | ||
| 225 | defmt::error!("accept error: {:?}", e); | ||
| 226 | continue; | ||
| 227 | } | ||
| 228 | |||
| 229 | loop { | ||
| 230 | let _n = match socket.read(&mut mb_buf).await { | ||
| 231 | Ok(0) => { | ||
| 232 | defmt::info!("read EOF"); | ||
| 233 | break; | ||
| 234 | } | ||
| 235 | Ok(n) => n, | ||
| 236 | Err(e) => { | ||
| 237 | defmt::error!("{:?}", e); | ||
| 238 | break; | ||
| 239 | } | ||
| 240 | }; | ||
| 241 | led_uc2_red.set_low(); | ||
| 242 | |||
| 243 | let status_line = "HTTP/1.1 200 OK"; | ||
| 244 | let contents = PAGE; | ||
| 245 | let length = contents.len(); | ||
| 246 | |||
| 247 | let _ = write!( | ||
| 248 | &mut mb_buf[..], | ||
| 249 | "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}\r\n\0" | ||
| 250 | ); | ||
| 251 | let loc = mb_buf.iter().position(|v| *v == b'#').unwrap(); | ||
| 252 | |||
| 253 | let temp = TEMP.load(Ordering::Relaxed); | ||
| 254 | let cel = temp / 1000; | ||
| 255 | let mcel = temp % 1000; | ||
| 256 | |||
| 257 | info!("{}.{}", cel, mcel); | ||
| 258 | |||
| 259 | let _ = write!(&mut mb_buf[loc..loc + 7], "{cel}.{mcel}"); | ||
| 260 | |||
| 261 | let n = mb_buf.iter().position(|v| *v == 0).unwrap(); | ||
| 262 | |||
| 263 | if let Err(e) = socket.write_all(&mb_buf[..n]).await { | ||
| 264 | error!("write error: {:?}", e); | ||
| 265 | break; | ||
| 266 | } | ||
| 267 | |||
| 268 | led_uc2_red.set_high(); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | async fn wait_for_config(stack: &'static Stack<Device<'static>>) -> embassy_net::StaticConfigV4 { | ||
| 274 | loop { | ||
| 275 | if let Some(config) = stack.config_v4() { | ||
| 276 | return config; | ||
| 277 | } | ||
| 278 | yield_now().await; | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
| 282 | #[embassy_executor::task] | ||
| 283 | async fn heartbeat_led(mut led: Output<'static, peripherals::PE6>) { | ||
| 284 | let mut tmr = Ticker::every(Duration::from_hz(3)); | ||
| 285 | loop { | ||
| 286 | led.toggle(); | ||
| 287 | tmr.next().await; | ||
| 288 | } | ||
| 289 | } | ||
| 290 | |||
| 291 | // ADT7422 | ||
| 292 | #[embassy_executor::task] | ||
| 293 | async fn temp_task(temp_dev_i2c: TempSensI2c, mut led: Output<'static, peripherals::PG15>) -> ! { | ||
| 294 | let mut tmr = Ticker::every(Duration::from_hz(1)); | ||
| 295 | let mut temp_sens = ADT7422::new(temp_dev_i2c, 0x48).unwrap(); | ||
| 296 | |||
| 297 | loop { | ||
| 298 | led.set_low(); | ||
| 299 | match select(temp_sens.read_temp(), Timer::after(Duration::from_millis(500))).await { | ||
| 300 | Either::First(i2c_ret) => match i2c_ret { | ||
| 301 | Ok(value) => { | ||
| 302 | led.set_high(); | ||
| 303 | let temp = i32::from(value); | ||
| 304 | println!("TEMP: {:04x}, {}", temp, temp * 78 / 10); | ||
| 305 | TEMP.store(temp * 78 / 10, Ordering::Relaxed); | ||
| 306 | } | ||
| 307 | Err(e) => defmt::println!("ADT7422: {}", e), | ||
| 308 | }, | ||
| 309 | Either::Second(_) => println!("Timeout"), | ||
| 310 | } | ||
| 311 | |||
| 312 | tmr.next().await; | ||
| 313 | } | ||
| 314 | } | ||
| 315 | |||
| 316 | #[embassy_executor::task] | ||
| 317 | async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! { | ||
| 318 | runner.run().await | ||
| 319 | } | ||
| 320 | |||
| 321 | #[embassy_executor::task] | ||
| 322 | async fn net_task(stack: &'static Stack<Device<'static>>) -> ! { | ||
| 323 | stack.run().await | ||
| 324 | } | ||
| 325 | |||
| 326 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message | ||
| 327 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked | ||
| 328 | #[defmt::panic_handler] | ||
| 329 | fn panic() -> ! { | ||
| 330 | cortex_m::asm::udf() | ||
| 331 | } | ||
| 332 | |||
| 333 | #[allow(non_camel_case_types)] | ||
| 334 | #[repr(C)] | ||
| 335 | pub enum Registers { | ||
| 336 | Temp_MSB = 0x00, | ||
| 337 | Temp_LSB, | ||
| 338 | Status, | ||
| 339 | Cfg, | ||
| 340 | T_HIGH_MSB, | ||
| 341 | T_HIGH_LSB, | ||
| 342 | T_LOW_MSB, | ||
| 343 | T_LOW_LSB, | ||
| 344 | T_CRIT_MSB, | ||
| 345 | T_CRIT_LSB, | ||
| 346 | T_HYST, | ||
| 347 | ID, | ||
| 348 | SW_RESET = 0x2F, | ||
| 349 | } | ||
| 350 | |||
| 351 | pub struct ADT7422<'d, BUS: I2cBus> { | ||
| 352 | addr: u8, | ||
| 353 | phantom: PhantomData<&'d ()>, | ||
| 354 | bus: BUS, | ||
| 355 | } | ||
| 356 | |||
| 357 | #[derive(Debug, Format)] | ||
| 358 | pub enum Error<I2cError: Format> { | ||
| 359 | I2c(I2cError), | ||
| 360 | Address, | ||
| 361 | } | ||
| 362 | |||
| 363 | impl<'d, BUS> ADT7422<'d, BUS> | ||
| 364 | where | ||
| 365 | BUS: I2cBus, | ||
| 366 | BUS::Error: Format, | ||
| 367 | { | ||
| 368 | pub fn new(bus: BUS, addr: u8) -> Result<Self, Error<BUS::Error>> { | ||
| 369 | if !(0x48..=0x4A).contains(&addr) { | ||
| 370 | return Err(Error::Address); | ||
| 371 | } | ||
| 372 | |||
| 373 | Ok(Self { | ||
| 374 | bus, | ||
| 375 | phantom: PhantomData, | ||
| 376 | addr, | ||
| 377 | }) | ||
| 378 | } | ||
| 379 | |||
| 380 | pub async fn init(&mut self) -> Result<(), Error<BUS::Error>> { | ||
| 381 | let mut cfg = 0b000_0000; | ||
| 382 | // if self.int.is_some() { | ||
| 383 | // // Set 1 SPS mode | ||
| 384 | // cfg |= 0b10 << 5; | ||
| 385 | // } else { | ||
| 386 | // One shot mode | ||
| 387 | cfg |= 0b01 << 5; | ||
| 388 | // } | ||
| 389 | |||
| 390 | self.write_cfg(cfg).await | ||
| 391 | } | ||
| 392 | |||
| 393 | pub async fn read(&mut self, reg: Registers) -> Result<u8, Error<BUS::Error>> { | ||
| 394 | let mut buffer = [0u8; 1]; | ||
| 395 | self.bus | ||
| 396 | .write_read(self.addr, &[reg as u8], &mut buffer) | ||
| 397 | .await | ||
| 398 | .map_err(Error::I2c)?; | ||
| 399 | Ok(buffer[0]) | ||
| 400 | } | ||
| 401 | |||
| 402 | pub async fn write_cfg(&mut self, cfg: u8) -> Result<(), Error<BUS::Error>> { | ||
| 403 | let buf = [Registers::Cfg as u8, cfg]; | ||
| 404 | self.bus.write(self.addr, &buf).await.map_err(Error::I2c) | ||
| 405 | } | ||
| 406 | |||
| 407 | pub async fn read_temp(&mut self) -> Result<i16, Error<BUS::Error>> { | ||
| 408 | let mut buffer = [0u8; 2]; | ||
| 409 | |||
| 410 | // if let Some(int) = &mut self.int { | ||
| 411 | // // Wait for interrupt | ||
| 412 | // int.wait_for_low().await.unwrap(); | ||
| 413 | // } else { | ||
| 414 | // Start: One shot | ||
| 415 | let cfg = 0b01 << 5; | ||
| 416 | self.write_cfg(cfg).await?; | ||
| 417 | Timer::after(Duration::from_millis(250)).await; | ||
| 418 | self.bus | ||
| 419 | .write_read(self.addr, &[Registers::Temp_MSB as u8], &mut buffer) | ||
| 420 | .await | ||
| 421 | .map_err(Error::I2c)?; | ||
| 422 | Ok(i16::from_be_bytes(buffer)) | ||
| 423 | } | ||
| 424 | } | ||
| 425 | |||
| 426 | // Web page | ||
| 427 | const PAGE: &str = r#"<!DOCTYPE html> | ||
| 428 | <html lang="en"> | ||
| 429 | <head> | ||
| 430 | <meta charset="utf-8"> | ||
| 431 | <meta http-equiv="refresh" content="1" > | ||
| 432 | <title>ADIN1110 with Rust</title> | ||
| 433 | </head> | ||
| 434 | <body> | ||
| 435 | <p>EVAL-ADIN1110EBZ</p> | ||
| 436 | <table><td>Temp Sensor ADT7422:</td><td> #00.00 °C</td></table> | ||
| 437 | </body> | ||
| 438 | </html>"#; | ||
