Compare commits

...

116 Commits

Author SHA1 Message Date
muellerr 18b6ee2c41 Embassy Multi-Priority Example
ci / Check build (push) Waiting to run
ci / Check formatting (push) Waiting to run
ci / Check Documentation Build (push) Waiting to run
ci / Clippy (push) Waiting to run
2026-05-18 13:20:05 +02:00
muellerr dfe68131c2 Merge pull request 'continue SPI slave' (#85) from spi-slave into main
ci / Check build (push) Waiting to run
ci / Check formatting (push) Waiting to run
ci / Check Documentation Build (push) Waiting to run
ci / Clippy (push) Waiting to run
Reviewed-on: #85
2026-05-18 11:48:55 +02:00
muellerr af23c47fc7 continue SPI slave
ci / Check build (push) Waiting to run
ci / Check formatting (push) Waiting to run
ci / Check Documentation Build (push) Waiting to run
ci / Clippy (push) Waiting to run
ci / Check build (pull_request) Waiting to run
ci / Check formatting (pull_request) Waiting to run
ci / Check Documentation Build (pull_request) Waiting to run
ci / Clippy (pull_request) Waiting to run
2026-05-18 11:38:41 +02:00
muellerr 32e0e27ca7 Merge pull request 'prepare next zynq7000 version' (#84) from bump-zynq7000 into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #84
2026-05-15 19:30:08 +02:00
muellerr bdc4780bcc small tweak
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-15 19:29:58 +02:00
muellerr 7df10e6ea1 prepare next zynq7000 version
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-15 19:28:25 +02:00
muellerr 306ef90094 Merge pull request 'UART improvements' (#83) from update-uart-interrupt-reception into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: #83
2026-05-15 14:37:36 +02:00
muellerr fcd971a7d3 UART improvements
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-15 14:37:21 +02:00
muellerr c09d75b602 Merge pull request 'zynq7000-rt and zynq7000-mmu release' (#82) from zynq7000-rt-zynq7000-mmu-release into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #82
2026-05-08 17:18:21 +02:00
muellerr c72ba780d4 zynq7000-rt and zynq7000-mmu release
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
2026-05-08 17:16:10 +02:00
muellerr 5472293907 Merge pull request 'prepare PAC release' (#81) from prep-pac-release into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #81
2026-05-08 17:09:09 +02:00
muellerr 80ad791061 prepare PAC release
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-08 17:07:08 +02:00
muellerr f78f159fde Merge pull request 'add clock enable for SPI and UART' (#80) from add-clk-enables into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: #80
2026-05-08 17:00:50 +02:00
muellerr 1d66fcd077 add clock enable for SPI and UART
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-08 17:00:15 +02:00
muellerr 7bf6322fc2 Merge pull request 'improve register names' (#79) from improve-register-names into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: #79
2026-05-08 16:48:13 +02:00
muellerr 90e4604187 improve register names
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-08 16:47:05 +02:00
muellerr 34727bf48e Merge pull request 'introduce interrupt registry' (#78) from interrupt-registry into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: #78
2026-05-08 16:30:58 +02:00
muellerr 3bd62fc1eb introduce interrupt registry
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-08 16:29:59 +02:00
muellerr 11b37e9c2b Merge pull request 'Async OLED example' (#77) from oled-asynch into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: #77
2026-05-08 14:43:49 +02:00
muellerr 9ed6ac32ce Async OLED example
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-08 14:42:50 +02:00
muellerr 7eeedec527 Merge pull request 'bump aarch32 deps' (#76) from bump-aarch32-dependencies into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: #76
2026-05-08 12:55:37 +02:00
muellerr d30273aa0c bump aarch32 deps
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-08 12:53:31 +02:00
muellerr 79c6aea160 Merge pull request 'fix ping reply for net example' (#75) from fix-ping-for-net-example into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: #75
2026-05-08 12:50:43 +02:00
muellerr b85e2cf1a3 fix ping reply for net example
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-08 12:50:21 +02:00
muellerr 78e2dd23f9 Merge pull request 'OLED example' (#74) from oled-driver into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: #74
2026-05-07 20:04:59 +02:00
muellerr df887d5665 Add OLED example
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-07 20:01:58 +02:00
muellerr 62eebc6770 Merge pull request 'update async SPI module' (#70) from update-asynch-spi-module into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: #70
2026-05-07 18:02:27 +02:00
muellerr 5a44b3f658 update async SPI module
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-05 18:41:21 +02:00
muellerr c85f492c03 Merge pull request 'bugfix for SPI AMBA clock control' (#73) from spi-amba-clk-ctrl-bugfix into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: http://localhost:3000/rust/zynq7000-rs/pulls/73
2026-05-05 18:41:00 +02:00
muellerr 20102a2b7a bugfix for SPI AMBA clock control
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-05 18:40:42 +02:00
muellerr 110adc8a63 Merge pull request 'configure pull up properly' (#72) from oled-sdin-pullup into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: http://localhost:3000/rust/zynq7000-rs/pulls/72
2026-05-05 15:46:31 +02:00
muellerr 6fe5ac5edb configure pull up properly
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-05 15:05:38 +02:00
muellerr 923f988a32 Merge pull request 'fix asynch uart TX' (#71) from fix-asynch-uart into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: http://localhost:3000/rust/zynq7000-rs/pulls/71
2026-05-04 19:32:26 +02:00
muellerr a6c40c5fa9 fix asynch uart TX
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-04 19:31:50 +02:00
muellerr 21a930dc09 Merge pull request 'update asynch logger, simplify it' (#68) from update-asynch-logger into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: http://localhost:3000/rust/zynq7000-rs/pulls/68
2026-05-04 18:01:29 +02:00
muellerr 8215eec0cf clippy fix
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-04 17:54:44 +02:00
muellerr bf6f492c49 update asynch logger, simplify it
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-04 17:02:22 +02:00
muellerr f7074bcf7d Merge pull request 'update BD and XDC' (#69) from update-hw-design into main
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (push) Has been cancelled
Reviewed-on: http://localhost:3000/rust/zynq7000-rs/pulls/69
2026-05-04 16:57:46 +02:00
muellerr 349509c90c some minor clean up
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-04 16:56:31 +02:00
muellerr c38c98dfb0 update BD and XDC
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
2026-05-04 16:39:33 +02:00
muellerr 69537126ec formatting
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-04-15 18:14:20 +02:00
muellerr 004e862715 Merge pull request 'Function to set FIFO Trigger of PS UART' (#67) from meier/ps-uart-set-fifo-trigger into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #67
2026-04-15 10:02:46 +02:00
meierj f1312c1b17 added pub function to set the rx fifo trigger of the ps uart
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-04-14 17:01:39 +02:00
muellerr 602448b456 Merge pull request 'use local path in README links' (#66) from another-small-readme-tweak into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #66
2026-04-01 14:25:30 +02:00
muellerr 21cafe4668 use local path in README links
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-04-01 14:23:36 +02:00
muellerr 00f64ad2be Merge pull request 'try this little readme tweak' (#65) from small-readme-tweak into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #65
2026-04-01 14:17:37 +02:00
muellerr 41af66433a try this little readme tweak
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-04-01 14:16:17 +02:00
muellerr 7dc471d687 Merge pull request 'prepare PAC v0.2.0 release' (#64) from prepare-pac-release into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #64
2026-04-01 14:14:55 +02:00
muellerr 41078f9150 prepare PAC v0.2.0 release
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-04-01 14:13:58 +02:00
muellerr e9a56f73af Merge pull request 'add defmt support for PAC' (#63) from add-defmt-support-pac into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #63
2026-04-01 14:01:41 +02:00
muellerr 7f91e039cb add defmt support for PAC
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-04-01 13:58:48 +02:00
muellerr 9e185fe353 Merge pull request 'bugfix for DDR init' (#62) from bugfix-ddr-init into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #62
2026-04-01 11:01:18 +02:00
muellerr b17470a12b bugfix for DDR init
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-04-01 10:59:58 +02:00
muellerr 2a6012cbc3 Merge pull request 'improve FSBL' (#61) from improve-fsbl into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #61
2026-03-31 20:12:51 +02:00
muellerr b786a67d38 improve FSBL
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-03-31 20:10:52 +02:00
muellerr b9b0d7b1bc Merge pull request 'Add PL reset de-assert in FSBL' (#60) from add-pl-deassert-in-fsbl into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #60
2026-03-31 18:29:05 +02:00
muellerr 56692b7dae Add PL reset de-assert in FSBL
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-03-31 18:28:35 +02:00
muellerr 06f08b2378 Merge pull request 'smaller tweaks' (#59) from smaller-tweaks into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #59
2026-03-31 18:21:03 +02:00
muellerr 0722681674 smaller tweaks
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-03-31 18:20:39 +02:00
muellerr caf9c49422 Merge pull request 'Add SDIO support' (#21) from add-sdio-support into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #21
2026-03-27 12:58:27 +01:00
Robin Mueller 3890795aa9 initial SD card support
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-03-27 12:56:00 +01:00
muellerr b9a64b76a4 Merge pull request 'readme tweak' (#58) from readme-tweak into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #58
2026-03-25 10:39:03 +01:00
muellerr c51e0c782a readme tweak
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-03-25 10:38:48 +01:00
muellerr 70fa1db75f Merge pull request 'bump dependencies and improve documentation' (#57) from bump-deps-improve-docs into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #57
2026-03-25 10:37:36 +01:00
muellerr a196d6c139 bump dependencies and improve documentation
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-03-25 10:36:06 +01:00
muellerr 530fc7a8dc Merge pull request 'firmware/zynq7000-hal/Cargo.toml aktualisiert: Replace dependency' (#56) from nehlichp/replace-paste into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #56
2026-03-25 10:26:23 +01:00
muellerr d2372ad41c actually use pastey as well
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-03-25 10:24:19 +01:00
nehlichp 9cdec1ffba firmware/zynq7000-hal/Cargo.toml aktualisiert
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
https://rustsec.org/advisories/RUSTSEC-2024-0436
2026-03-23 14:57:25 +01:00
muellerr 5e4e687403 Merge pull request 'try-to-fix-embassy-docs' (#55) from try-to-fix-embassy-docs into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #55
2026-03-13 17:52:16 +01:00
muellerr d07ffb3ae1 try to fix embassy docs
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-03-13 17:50:35 +01:00
muellerr 332779326a Merge pull request 'some CI fixes' (#54) from ci-fixes into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #54
2026-03-13 17:47:32 +01:00
muellerr 558f38682d some CI fixes
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-03-13 17:45:57 +01:00
muellerr 3263b9964d Merge pull request 'some CI fixes' (#53) from ci-fixes into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #53
2026-03-13 17:35:11 +01:00
muellerr 0d11210173 some CI fixes
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-03-13 17:34:46 +01:00
muellerr 9c187bd657 Merge pull request 'another tweak for the QSPI spansion driver' (#52) from tweak-for-qspi-spansion-driver into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #52
2026-03-10 19:46:25 +01:00
muellerr a2d1efde46 another tweak for the QSPI spansion driver
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-03-10 15:00:20 +01:00
muellerr 16ecc6e71c Merge pull request 'QSPI Spansion Remove Page Alignement Constraint' (#51) from meier/qspi-remove-page-alignment-constraint into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #51
Reviewed-by: Robin Müller <muellerr@irs.uni-stuttgart.de>
2026-03-10 13:28:43 +01:00
meierj ecabeb1a4b adapted qspi example and qpsi-flasher to api changes in qspi_spansion
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-03-10 11:25:55 +01:00
meierj 7494036099 changed read_page to read
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-03-06 16:04:07 +01:00
meierj 21427bf019 removed page alignment constraint for qspi spansion programming
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-03-06 15:35:45 +01:00
muellerr fc1fdada3e changelog
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-03-06 13:43:50 +01:00
muellerr 3f01b61770 Merge pull request 'Fixed Bit Positions of QSPI Reset Register' (#50) from meier/qspi-reset-bugfix into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #50
Reviewed-by: Robin Müller <muellerr@irs.uni-stuttgart.de>
2026-03-06 13:34:47 +01:00
meierj dd97bc3177 corrected bit positions of qspi reset register
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-03-06 13:15:57 +01:00
muellerr efd55042b2 Merge pull request 'fmt' (#49) from minor-qspi-improvements-and-tweaks into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #49
2026-02-28 12:49:36 +01:00
muellerr 8bafdc20e7 fmt
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-02-28 12:49:18 +01:00
muellerr 401643f8ec Merge pull request 'minor-qspi-improvements-and-tweaks' (#48) from minor-qspi-improvements-and-tweaks into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #48
2026-02-28 12:47:32 +01:00
muellerr 2b11516bf5 more double reads
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-02-28 12:46:19 +01:00
muellerr 703af93bb9 more improvements
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-02-28 12:37:06 +01:00
muellerr 1fe16466ab Merge pull request 'use div-ceil for QSPI clock calc' (#45) from small-qspi-clock-tweak into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #45
2026-02-26 17:20:41 +01:00
muellerr 47a17e7550 use div-ceil for QSPI clock calc
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-02-26 17:20:32 +01:00
muellerr af025c5490 Merge pull request 'chunked read' (#47) from chunked-read into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #47
2026-02-26 17:19:40 +01:00
muellerr c15e3828b7 Spansion QSPI module improvements
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-02-26 17:19:08 +01:00
muellerr 9ef548e1bc Merge pull request 'Add slow read for Spansion QSPI' (#46) from add-slow-read-spansion-qspi into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #46
2026-02-25 17:31:59 +01:00
muellerr 3eb8467bf5 add register double read
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-02-25 15:57:21 +01:00
muellerr 4112e336ef add slow read
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-02-25 15:51:37 +01:00
muellerr a58d398d82 Merge pull request 'prepare zedboard BSP release' (#44) from prepare-zedboard-bsp-release into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #44
2026-02-14 19:37:31 +01:00
muellerr fd178e1d3b prepare zedboard BSP release
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-02-14 19:30:13 +01:00
muellerr e8a6c88e2b Merge pull request 'prepare zynq7000-embassy release' (#43) from prepare-zynq7000-embassy-release into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #43
2026-02-14 19:23:53 +01:00
muellerr 8606bcbd63 prepare zynq7000-embassy release
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-02-14 19:23:38 +01:00
muellerr 2b6e27875f re-generate lock file
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-02-14 19:15:43 +01:00
muellerr fa6f7d836e Merge pull request 'prepare zynq-mmu v0.1.2' (#42) from zynq-mmu-release into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #42
2026-02-14 19:10:59 +01:00
muellerr b7093706f5 prepare zynq-mmu v0.1.2
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-02-14 19:10:32 +01:00
Robin Mueller 56131100e7 Merge pull request #2 from us-irs/prep-rt-patch-release
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
zynq7000-rt v0.2.0
2026-02-14 18:56:23 +01:00
Robin Mueller 9357464db0 zynq7000-rt v0.2.0
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
2026-02-14 18:54:23 +01:00
muellerr 79161ffe58 Merge pull request 'try allowing hw server ip' (#41) from try-allowing-setting-hw-server-ip into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #41
2026-02-14 17:49:09 +01:00
muellerr 7c3051db46 try allowing hw server ip
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-02-14 17:48:56 +01:00
muellerr 89ee6d7451 Merge pull request 'improve SPI implemenetation' (#40) from improve-spi-implementation into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #40
2026-02-14 17:45:36 +01:00
muellerr 3b23e9be05 Merge pull request 'division by zero check to avoid panic' (#39) from mohr/div_zero into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #39
Reviewed-by: Robin Müller <muellerr@irs.uni-stuttgart.de>
2026-02-14 17:45:20 +01:00
muellerr d34143ceef improve SPI implemenetation
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-02-12 16:56:13 +01:00
mohr 1abbe7b9c9 division by zero check to avoid panic
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-02-11 12:06:21 +01:00
muellerr 802ba665c7 Merge pull request 'bugfix for PL programming routine' (#38) from pl-programming-bugfix into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #38
2026-02-07 01:36:05 +01:00
muellerr 3ba6c63554 bugfix for PL programming routine
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-02-07 01:35:27 +01:00
Robin Mueller e3a05cc650 Merge pull request #1 from adamgreig/fixes
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Fix typos in zynq7000-hal
2026-02-04 14:04:41 +01:00
Adam Greig 0583c9231d Fix typos in zynq7000-hal 2026-02-03 22:39:23 +00:00
muellerr 4ec2f2bae3 Merge pull request 'bumped aarch32 dependencies' (#37) from bump-aarch32-crates into main
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #37
2026-02-02 15:40:03 +01:00
muellerr 0d6713f248 bumped aarch32 dependencies
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2026-02-02 15:39:18 +01:00
137 changed files with 8501 additions and 2219 deletions
+1
View File
@@ -10,3 +10,4 @@
# running the application. You only need to do this once for unchanged bitstream as long as you
# do not reset the whole board.
# ZYNQ_BITSTREAM = "/home/$user/$project/$sdt_dir/bitstream.bit"
# HW_SERVER_IP = "localhost"
+8 -8
View File
@@ -11,8 +11,8 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
targets: "armv7a-none-eabihf"
- run: just check firmware
- run: just check host
- run: just check-dir firmware
- run: just check-dir host
build:
name: Check build
@@ -24,8 +24,8 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
targets: "armv7a-none-eabihf"
- run: just check firmware
- run: just check host
- run: just build-zynq
- run: just build-dir host
fmt:
name: Check formatting
@@ -37,8 +37,8 @@ jobs:
with:
components: rustfmt
targets: "armv7a-none-eabihf"
- run: just check-fmt firmware
- run: just check-fmt host
- run: just check-fmt-dir firmware
- run: just check-fmt-dir host
docs:
name: Check Documentation Build
@@ -62,9 +62,9 @@ jobs:
with:
components: clippy, rust-src
targets: "armv7a-none-eabihf"
- run: just clippy firmware
- run: just clippy-dir firmware
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: just clippy host
- run: just clippy-dir host
+20 -17
View File
@@ -4,52 +4,55 @@ Zynq 7000 Bare-Metal Rust Support
This crate collection provides support to write bare-metal Rust applications for the AMD Zynq 7000
family of SoCs.
<p align="center">
<img src="./ferris-zedboard.jpeg" alt="Ferris on the Zedboard" width="400" />
</p>
# List of crates
This project contains the following crates:
## [Firmware Workspace](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware)
## [Firmware Workspace](./firmware)
This workspace contains libraries and application which can only be run on the target system.
- The [`zynq7000-rt`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zynq7000-rt)
- The [`zynq7000-rt`](./firmware/zynq7000-rt)
run-time crate containing basic low-level startup code necessary to boot a Rust app on the
Zynq7000.
- The [`zynq7000`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zynq7000) PAC
crate containing basic low-level register definitions.
- The [`zynq7000-mmu`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zynq7000-hal)
- The [`zynq7000`](./firmware/zynq7000) PAC crate containing basic low-level register access API.
- The [`zynq7000-mmu`](./firmware/zynq7000-mmu)
crate containing common MMU abstractions used by both the HAL and the run-time crate.
- The [`zynq7000-hal`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zynq7000-hal)
HAL crate containing higher-level abstractions on top of the PAC register crate.
- The [`zynq7000-embassy`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zynq7000-embassy)
crate containing support for running the embassy-rs asynchronous run-time.
- The [`zynq7000-hal`](./firmware/zynq7000-hal) HAL crate containing higher-level abstractions on
top of the PAC register crate.
- The [`zynq7000-embassy`](./firmware/zynq7000-embassy) crate containing an embassy-rs time driver
using the global timer counter peripheral.
This project was developed using a Zedboard, so there are several crates available targeted towards
this board:
- The [`zedboard-bsp`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zedboard-bsp)
- The [`zedboard-bsp`](./firmware/zedboard-bsp)
crate containing board specific components for the Zedboard.
- The [`zedboard-fsbl`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zedboard-fsbl)
- The [`zedboard-fsbl`](./firmware/zedboard-fsbl)
contains a simple first-stage bootloader application for the Zedboard.
- The [`zedboard-qspi-flasher`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zedboard-qspi-flasher)
- The [`zedboard-qspi-flasher`](./firmware/zedboard-qspi-flasher)
contains an application which is able to flash a boot binary from DDR to the QSPI.
It also contains the following helper crates:
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/examples)
- The [`examples`](./firmware/examples)
folder contains various example applications crates using the HAL and the PAC.
This folder also contains dedicated example applications using the
[`embassy`](https://github.com/embassy-rs/embassy) native Rust RTOS.
## Other libraries and tools
- The [`zedboard-fpga-design`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zedboard-fpga-design)
- The [`zedboard-fpga-design`](./zedboard-fpga-design)
folder contains a sample FPGA design and block design which was used in some of the provided software examples. The project was created with Vivado version 2024.1.
The folder contains a README with all the steps required to load this project from a TCL script.
- The [`zynq7000-boot-image`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/host/zynq7000-boot-image)
- The [`zynq7000-boot-image`](./host/zynq7000-boot-image)
library contains generic helpers to interface with the AMD
[boot binary](https://docs.amd.com/r/en-US/ug1283-bootgen-user-guide).
- The [`tools/zynq7000-ps7init-extract`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/host/zynq7000-ps7init-extract)
- The [`zynq7000-ps7init-extract`](./host/zynq7000-ps7init-extract)
tool allows extracting configuration from the AMD generated `ps7init.tcl` file which contains
static configuration parameters for DDR initialization.
@@ -157,7 +160,7 @@ Zedboard are configured for JTAG boot.
You can use the `-tui` argument to also have a terminal UI.
This repository provides a `scripts/runner.sh` which performs all the steps specified above.
The `.cargo/def-config.toml` script contains the runner and some template environmental
The `.cargo/config.toml.template` script contains the runner and some template environmental
variables that need to be set for this to work. The command above also loaded the app, but
this task can be performed by the `zynq7000-init.py` wrapper as well.
Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

+9
View File
@@ -11,6 +11,7 @@ members = [
"examples/embassy",
"examples/zedboard",
"examples/defmt",
"examples/multiprio",
"zedboard-bsp",
"zedboard-qspi-flasher",
@@ -40,3 +41,11 @@ codegen-units = 1
lto = 'fat'
# Leave the debug symbols in (default is no debug info)
debug = 2
[patch.crates-io]
embassy-executor = { path = "../../embassy/embassy-executor" }
embassy-executor-macros = { path = "../../embassy/embassy-executor-macros" }
embassy-time-driver = { path = "../../embassy/embassy-time-driver" }
embassy-time = { path = "../../embassy/embassy-time" }
embassy-executor-timer-queue = { path = "../../embassy/embassy-executor-timer-queue" }
+2 -2
View File
@@ -9,7 +9,7 @@ repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
license = "MIT OR Apache-2.0"
[dependencies]
aarch32-cpu = { version = "0.1" }
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal", features = ["defmt"] }
@@ -17,5 +17,5 @@ defmt = "1"
defmt-rtt = "1"
embedded-io = "0.7"
embedded-hal = "1"
fugit = "0.3"
fugit = "0.4"
log = "0.4"
+12
View File
@@ -4,12 +4,17 @@ MEMORY
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
recommended for something like DMA descriptors. */
/*CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M*/
/* CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M */
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K
/* STACK: ORIGIN = 0x3F00000, LENGTH = 1M */
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
}
REGION_ALIAS("VECTORS", CODE);
REGION_ALIAS("DATA", CODE);
/* Use the upper OCM as the stack */
REGION_ALIAS("STACKS", OCM_UPPER);
SECTIONS
{
@@ -22,3 +27,10 @@ SECTIONS
_ebss_uncached = .;
} > UNCACHED
}
PROVIDE(_und_stack_size = 2K);
PROVIDE(_svc_stack_size = 2K);
PROVIDE(_abt_stack_size = 2K);
PROVIDE(_hyp_stack_size = 1K);
PROVIDE(_sys_stack_size = 32K);
+10 -6
View File
@@ -11,23 +11,27 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
aarch32-cpu = { version = "0.1", features = ["critical-section-single-core"] }
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
zynq7000-embassy = { path = "../../zynq7000-embassy" }
dht-sensor = { git = "https://github.com/michaelbeaumont/dht-sensor.git", rev = "10319bdeae9ace3bb0fc79a15da2869c5bf50f52", features = ["async"] }
embedded-hal-async = "1"
embassy-sync = "0.8"
static_cell = "2"
critical-section = "1"
heapless = "0.9"
embedded-io = "0.7"
embedded-hal = "1"
fugit = "0.3"
fugit = "0.4"
log = "0.4"
embassy-executor = { version = "0.9", features = [
"arch-cortex-ar",
embassy-executor = { version = "0.10", features = [
"platform-z7",
"executor-thread",
]}
# TODO: Remove generic-queue-16 feature as soon as upstream executor is used again.
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000", "generic-queue-16"] }
# embassy-executor = { git = "https://github.com/robamu/embassy.git", branch = "add-z7-arch-support", features = ["platform-z7", "executor-thread"] }
# embassy-executor = { git = "https://github.com/robamu/embassy.git", features = ["platform-cortex-ar", "executor-thread"] }
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000"] }
+6 -4
View File
@@ -1,10 +1,12 @@
MEMORY
{
/* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app.
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
recommended for something like DMA descriptors. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
1 MB stack memory and 1 MB of memory which will be configured as uncached device memory by the
MMU. This is recommended for something like DMA descriptors. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
}
REGION_ALIAS("VECTORS", CODE);
@@ -12,7 +12,8 @@ use log::{error, info, warn};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
generic_interrupt_handler,
gic::Configurator,
gpio::{Flex, Output, PinState, mio},
gtc::GlobalTimerCounter,
l2_cache,
@@ -45,7 +46,7 @@ async fn main(_spawner: Spawner) -> ! {
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
@@ -71,13 +72,7 @@ async fn main(_spawner: Spawner) -> ! {
uart.write_all(b"-- Zynq 7000 DHT22 --\n\r").unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
let mut delay = Delay;
@@ -157,23 +152,12 @@ async fn main(_spawner: Spawner) -> ! {
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(_spi_interrupt) => (),
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
@@ -8,7 +8,9 @@ use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zynq7000_hal::{BootMode, InteruptConfig, clocks, gic, gpio, gtc, time::Hertz, uart};
use zynq7000_hal::{
BootMode, InteruptConfig, clocks, generic_interrupt_handler, gpio, gtc, time::Hertz, uart,
};
use zynq7000_rt as _;
@@ -49,14 +51,7 @@ async fn main(_spawner: Spawner) -> ! {
.unwrap();
uart.write_all(b"-- Zynq 7000 Embassy Hello World --\n\r")
.unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
@@ -71,23 +66,12 @@ async fn main(_spawner: Spawner) -> ! {
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = gic::GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
gic::Interrupt::Sgi(_) => (),
gic::Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
gic::Interrupt::Spi(_spi_interrupt) => (),
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
@@ -8,17 +8,18 @@ use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use log::info;
use zynq7000::Peripherals;
use zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
generic_interrupt_handler,
gic::Configurator,
gpio::{Output, PinState, mio},
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfig, Config, TxAsync, Uart, on_interrupt_tx},
uart::{self, ClockConfig, Config, TxAsync, Uart},
};
use zynq7000_rt as _;
@@ -40,7 +41,7 @@ async fn main(spawner: Spawner) -> ! {
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
@@ -68,56 +69,58 @@ async fn main(spawner: Spawner) -> ! {
uart.flush().unwrap();
let (tx, _rx) = uart.split();
let mut logger = TxAsync::new(tx);
let logger = TxAsync::new(tx, true);
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
let mut log_runner =
zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, logger).unwrap();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let led = Output::new_for_mio(mio_pins.mio7, PinState::Low);
spawner.spawn(led_task(led)).unwrap();
let mut log_buf: [u8; 2048] = [0; 2048];
let frame_queue = zynq7000_hal::log::rb::get_frame_queue();
loop {
let next_frame_len = frame_queue.receive().await;
zynq7000_hal::log::rb::read_next_frame(next_frame_len, &mut log_buf);
logger.write(&log_buf[0..next_frame_len]).await;
}
spawner.spawn(led_task(led).unwrap());
spawner.spawn(hello_task().unwrap());
log_runner.run().await
}
#[embassy_executor::task]
async fn led_task(mut mio_led: Output) {
static ATOMIC_COUNTER: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
let mut ticker = Ticker::every(Duration::from_millis(1000));
loop {
mio_led.toggle().unwrap();
info!("Toggling LED");
info!(
"Toggling LED ({})",
ATOMIC_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
);
ticker.next().await;
}
}
#[embassy_executor::task]
async fn hello_task() {
static ATOMIC_COUNTER: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
let mut ticker = Ticker::every(Duration::from_millis(1000));
loop {
info!(
"Hello from another task ({})",
ATOMIC_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
);
ticker.next().await;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(spi_interrupt) => {
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Uart1 {
on_interrupt_tx(zynq7000_hal::uart::UartId::Uart1);
}
}
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
#[zynq7000_rt::irq]
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
@@ -144,6 +147,7 @@ fn prefetch_handler(_faulting_addr: usize) -> ! {
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) };
writeln!(uart, "panic: {}\r", info).ok();
loop {}
}
@@ -0,0 +1,183 @@
#![no_std]
#![no_main]
use aarch32_cpu::asm::nop;
use core::panic::PanicInfo;
use embassy_executor::Spawner;
use embassy_time::{Delay, Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_hal_async::delay::DelayNs as _;
use embedded_io::Write as _;
use log::error;
use zynq7000_hal::{
InteruptConfig, clocks,
executor::InterruptExecutor,
generic_interrupt_handler,
gic::{SgiInterrupt, TargetCpus},
gpio::{self, Output},
gtc,
time::Hertz,
uart,
};
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
static INTERUPT_EXECUTOR: InterruptExecutor = InterruptExecutor::new();
/// Entry point which calls the embassy main method.
#[zynq7000_rt::entry]
fn entry_point() -> ! {
main();
}
#[embassy_executor::main(executor = "zynq7000_hal::executor::Executor")]
async fn main(_spawner: Spawner) -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up global timer counter and embassy time driver.
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
let mio_pins = gpio::mio::Pins::new(periphs.gpio);
let led = gpio::Output::new_for_mio(mio_pins.mio7, gpio::PinState::Low);
let sgi_interrupt = SgiInterrupt::new(0).unwrap();
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio_for_uart_1(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
uart.write_all(b"-- Zynq 7000 Interrupt Executor --\n\r")
.unwrap();
uart.flush().unwrap();
//let (tx, _rx) = uart.split();
// let mut logger = uart::TxAsync::new(tx, true);
//let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace).unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
log::info!(
"interrupt counter: {}",
CNTR.load(core::sync::atomic::Ordering::Relaxed)
);
let spawner = INTERUPT_EXECUTOR.start(sgi_interrupt, TargetCpus::CPU_0);
zynq7000_hal::register_interrupt(
zynq7000_hal::Interrupt::Sgi(sgi_interrupt),
on_interrupt_low_prio,
);
spawner.spawn(led_task(led).unwrap());
log::info!(
"interrupt counter: {}",
CNTR.load(core::sync::atomic::Ordering::Relaxed)
);
//Delay.delay_ms(1).await;
/*
log::info!(
"interrupt counter: {}",
CNTR.load(core::sync::atomic::Ordering::Relaxed)
);
*/
//let mut log_buf: [u8; 2048] = [0; 2048];
loop {
log::info!("THR alive");
/*
let read_bytes = log_reader.read(&mut log_buf).await;
if read_bytes > 0 {
// Unwrap okay, checked that size is larger than 0.
logger.write(&log_buf[0..read_bytes]).unwrap().await;
}
*/
Ticker::every(Duration::from_millis(1000)).next().await;
}
}
static CNTR: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
unsafe fn interrupt_handler(_ctx: *mut ()) {
CNTR.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
unsafe {
INTERUPT_EXECUTOR.on_interrupt();
}
CNTR.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
#[embassy_executor::task]
async fn led_task(mut mio_led: Output) {
static ATOMIC_COUNTER: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
let mut ticker = Ticker::every(Duration::from_millis(1000));
loop {
mio_led.toggle().unwrap();
log::info!(
"Toggling LED ({})",
ATOMIC_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
);
ticker.next().await;
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
log::info!(
"Interrupt handled, counter: {}",
CNTR.load(core::sync::atomic::Ordering::Relaxed)
);
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}
+9 -25
View File
@@ -18,7 +18,8 @@ use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
generic_interrupt_handler,
gic::Configurator,
gpio::{Output, PinState, mio},
gtc::GlobalTimerCounter,
l2_cache,
@@ -46,7 +47,7 @@ async fn main(_spawner: Spawner) -> ! {
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
@@ -78,13 +79,7 @@ async fn main(_spawner: Spawner) -> ! {
.unwrap();
uart.write_all(b"-- Zynq 7000 PWM example--\n\r").unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
@@ -107,23 +102,12 @@ async fn main(_spawner: Spawner) -> ! {
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(_spi_interrupt) => (),
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
+5 -16
View File
@@ -7,7 +7,7 @@ use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use log::error;
use zynq7000_hal::{InteruptConfig, clocks, gic, gpio, gtc, time::Hertz};
use zynq7000_hal::{InteruptConfig, clocks, generic_interrupt_handler, gpio, gtc, time::Hertz};
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
@@ -42,22 +42,11 @@ async fn main(_spawner: Spawner) -> ! {
#[zynq7000_rt::irq]
pub fn irq_handler() {
let mut gic_helper = gic::GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
gic::Interrupt::Sgi(_) => (),
gic::Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
gic::Interrupt::Spi(_spi_interrupt) => (),
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
+35
View File
@@ -0,0 +1,35 @@
[package]
name = "embassy-multiprio"
version = "0.1.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "Embassy examples for the Zynq7000 SoC"
homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
license = "MIT OR Apache-2.0"
keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
zynq7000-embassy = { path = "../../zynq7000-embassy" }
dht-sensor = { git = "https://github.com/michaelbeaumont/dht-sensor.git", rev = "10319bdeae9ace3bb0fc79a15da2869c5bf50f52", features = ["async"] }
embedded-hal-async = "1"
embassy-sync = "0.8"
static_cell = "2"
critical-section = "1"
heapless = "0.9"
embedded-io = "0.7"
embedded-hal = "1"
arbitrary-int = "2"
fugit = "0.4"
log = "0.4"
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000"] }
embassy-executor = { version = "0.10", features = [
"platform-z7",
"executor-thread",
"executor-interrupt",
]}
+31
View File
@@ -0,0 +1,31 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
}
+25
View File
@@ -0,0 +1,25 @@
MEMORY
{
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
1 MB stack memory and 1 MB of memory which will be configured as uncached device memory by the
MMU. This is recommended for something like DMA descriptors. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
}
REGION_ALIAS("VECTORS", CODE);
REGION_ALIAS("DATA", CODE);
SECTIONS
{
/* Uncached memory */
.uncached (NOLOAD) : ALIGN(4) {
. = ALIGN(4);
_sbss_uncached = .;
*(.uncached .uncached.*);
. = ALIGN(4);
_ebss_uncached = .;
} > UNCACHED
}
+170
View File
@@ -0,0 +1,170 @@
#![no_std]
#![no_main]
use aarch32_cpu::asm::nop;
use arbitrary_int::u5;
use core::panic::PanicInfo;
use embassy_executor::{InterruptExecutor, Spawner};
use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write as _;
use log::error;
use zynq7000_hal::{
InteruptConfig, clocks, generic_interrupt_handler,
gic::SgiInterrupt,
gpio::{self, Output},
gtc,
time::Hertz,
uart,
};
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
static INTERUPT_EXECUTOR_LOW_PRIO: InterruptExecutor = InterruptExecutor::new();
static INTERUPT_EXECUTOR_MED_PRIO: InterruptExecutor = InterruptExecutor::new();
/// Entry point which calls the embassy main method.
#[zynq7000_rt::entry]
fn entry_point() -> ! {
main();
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
let mut gic = unsafe { zynq7000_hal::gic::Configurator::steal() };
gic.set_all_sgi_interrupt_targets_cpu0();
// Set up global timer counter and embassy time driver.
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
let mio_pins = gpio::mio::Pins::new(periphs.gpio);
let led = gpio::Output::new_for_mio(mio_pins.mio7, gpio::PinState::Low);
let sgi_interrupt_low_prio = SgiInterrupt::new(0).unwrap();
let sgi_interrupt_med_prio = SgiInterrupt::new(1).unwrap();
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio_for_uart_1(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
uart.write_all(b"-- Zynq 7000 Interrupt Executor --\n\r")
.unwrap();
uart.flush().unwrap();
let (tx, _rx) = uart.split();
let mut logger = uart::TxAsync::new(tx, true);
let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace).unwrap();
gic.set_sgi_interrupt_priority(sgi_interrupt_low_prio, u5::new(2));
gic.set_sgi_interrupt_priority(sgi_interrupt_med_prio, u5::new(1));
zynq7000_hal::register_interrupt(
zynq7000_hal::Interrupt::Sgi(sgi_interrupt_low_prio),
on_interrupt_low_prio,
);
zynq7000_hal::register_interrupt(
zynq7000_hal::Interrupt::Sgi(sgi_interrupt_med_prio),
on_interrupt_med_prio,
);
let spawner = INTERUPT_EXECUTOR_LOW_PRIO.start(sgi_interrupt_low_prio.as_u4());
spawner.spawn(led_task(led).unwrap());
let spawner = INTERUPT_EXECUTOR_MED_PRIO.start(sgi_interrupt_med_prio.as_u4());
spawner.spawn(hello_task().unwrap());
let mut log_buf: [u8; 2048] = [0; 2048];
loop {
let read_bytes = log_reader.read(&mut log_buf).await;
if read_bytes > 0 {
// Unwrap okay, checked that size is larger than 0.
logger.write(&log_buf[0..read_bytes]).unwrap().await;
}
Ticker::every(Duration::from_millis(1000)).next().await;
}
}
#[embassy_executor::task]
async fn led_task(mut mio_led: Output) {
static ATOMIC_COUNTER: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
let mut ticker = Ticker::every(Duration::from_millis(1000));
loop {
mio_led.toggle().unwrap();
log::info!(
"Toggling LED ({})",
ATOMIC_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
);
ticker.next().await;
}
}
#[embassy_executor::task]
async fn hello_task() {
let mut ticker = Ticker::every(Duration::from_millis(1000));
loop {
log::info!("Hello from the low priority task");
ticker.next().await;
}
}
unsafe fn on_interrupt_low_prio() {
unsafe {
INTERUPT_EXECUTOR_LOW_PRIO.on_interrupt();
}
}
unsafe fn on_interrupt_med_prio() {
unsafe {
INTERUPT_EXECUTOR_MED_PRIO.on_interrupt();
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}
+2 -2
View File
@@ -9,11 +9,11 @@ repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
license = "MIT OR Apache-2.0"
[dependencies]
aarch32-cpu = { version = "0.1" }
aarch32-cpu = { version = "0.3" }
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
embedded-io = "0.7"
embedded-hal = "1"
fugit = "0.3"
fugit = "0.4"
log = "0.4"
+12 -4
View File
@@ -1,10 +1,12 @@
MEMORY
{
/* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app.
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
recommended for something like DMA descriptors. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
1 MB stack memory and 1 MB of memory which will be configured as uncached device memory by the
MMU. This is recommended for something like DMA descriptors. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
}
REGION_ALIAS("VECTORS", CODE);
@@ -21,3 +23,9 @@ SECTIONS
_ebss_uncached = .;
} > UNCACHED
}
PROVIDE(_und_stack_size = 2K);
PROVIDE(_svc_stack_size = 2K);
PROVIDE(_abt_stack_size = 2K);
PROVIDE(_hyp_stack_size = 1K);
PROVIDE(_sys_stack_size = 32K);
+6 -12
View File
@@ -8,8 +8,9 @@ use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zynq7000_hal::{
Interrupt,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gic,
gpio::{Output, PinState, mio},
gtc::GlobalTimerCounter,
l2_cache,
@@ -31,7 +32,7 @@ fn main() -> ! {
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
let mut gic = gic::Configurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
@@ -59,14 +60,7 @@ fn main() -> ! {
.unwrap();
uart.write_all(b"-- Zynq 7000 GTC Ticks example --\n\r")
.unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low);
loop {
@@ -83,8 +77,8 @@ fn main() -> ! {
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
let mut gic_helper = gic::InterruptGuard::new();
let irq_info = gic_helper.interrupt_info();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
+5 -12
View File
@@ -10,7 +10,7 @@ use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gic::{self, Configurator, Interrupt},
gpio::{Output, PinState, mio},
gtc::GlobalTimerCounter,
l2_cache,
@@ -32,7 +32,7 @@ fn main() -> ! {
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
@@ -60,13 +60,7 @@ fn main() -> ! {
uart.write_all(b"-- Zynq 7000 Logging example --\n\r")
.unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {boot_mode:?}");
@@ -85,13 +79,12 @@ fn main() -> ! {
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
let mut gic_helper = gic::InterruptGuard::new();
let irq_info = gic_helper.interrupt_info();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
// TODO: Call embassy on interrupt handler here soon.
MS_TICKS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
+123
View File
@@ -0,0 +1,123 @@
//! Example which uses the UART1 to send log messages.
#![no_std]
#![no_main]
use aarch32_cpu::asm::nop;
use core::{panic::PanicInfo, sync::atomic::AtomicU64};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{self, Configurator, Interrupt, SgiInterrupt},
gpio::{Output, PinState, mio},
l2_cache,
time::Hertz,
uart::{ClockConfig, Config, Uart},
};
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
static SGI_COUNTER: AtomicU64 = AtomicU64::new(0);
#[zynq7000_rt::entry]
fn main() -> ! {
let mut dp = zynq7000::Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.set_all_sgi_interrupt_targets_cpu0();
gic.enable();
// Enable interrupt exception.
unsafe { gic.enable_interrupts() };
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mio_pins = mio::Pins::new(dp.gpio);
let mut uart = Uart::new_with_mio_for_uart_1(
dp.uart_1,
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
uart.write_all(b"-- Zynq 7000 Software Interrupt example --\n\r")
.unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {boot_mode:?}");
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low);
loop {
let gtc = SGI_COUNTER.load(core::sync::atomic::Ordering::Relaxed);
info!("Hello, world!");
info!("SGI counter: {gtc}");
gic.trigger_software_interrupt(SgiInterrupt::new(0).unwrap());
led.toggle().unwrap();
for _ in 0..5_000_000 {
nop();
}
}
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = gic::InterruptGuard::new();
let irq_info = gic_helper.interrupt_info();
match irq_info.interrupt() {
Interrupt::Sgi(_sgi) => {
// TODO: Send ID to main thread.
SGI_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
Interrupt::Ppi(_ppi_interrupt) => (),
Interrupt::Spi(_spi_interrupt) => (),
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}
+18 -16
View File
@@ -11,7 +11,7 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
aarch32-cpu = { version = "0.1" }
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
@@ -20,27 +20,29 @@ zedboard-bsp = { path = "../../zedboard-bsp" }
num_enum = { version = "0.7", default-features = false }
l3gd20 = { git = "https://github.com/us-irs/l3gd20.git", branch = "add-async-if" }
embedded-io = "0.7"
bitbybit = "1.4"
bitbybit = "2"
arbitrary-int = "2"
embedded-io-async = "0.6"
embedded-io-async = "0.7"
critical-section = "1"
static_cell = "2"
embedded-alloc = "0.6"
embedded-alloc = "0.7"
embedded-hal = "1"
embedded-hal-bus = { version = "0.3", features = ["async"] }
embedded-hal-async = "1"
fugit = "0.3"
dummy-pin = "1"
fugit = "0.4"
fugit-03 = { version = "0.3", package = "fugit" }
embedded-graphics = "0.8"
log = "0.4"
rand = { version = "0.9", default-features = false, features = ["small_rng"] }
ssd1306 = { version = "0.10", features = ["async"] }
tinybmp = "0.7"
rand = { version = "0.10", default-features = false }
embassy-executor = { git = "https://github.com/us-irs/embassy.git", branch = "cortex-ar-update", features = [
"arch-cortex-ar",
"executor-thread",
]}
# TODO: Remove generic-queue-16 feature as soon as upstream executor is used again.
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000", "generic-queue-16"] }
embassy-net = { version = "0.7", features = ["dhcpv4", "packet-trace", "medium-ethernet", "icmp", "tcp", "udp"] }
embassy-sync = { version = "0.7" }
# TODO: Bump as soon as new compatible smoltcp/embassy-net version is released.
heapless = "0.8"
embassy-executor = { version = "0.10", features = ["platform-cortex-ar", "executor-thread"] }
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000"] }
embassy-net = { version = "0.9", features = ["dhcpv4", "packet-trace", "medium-ethernet", "icmp", "tcp", "udp", "auto-icmp-echo-reply"] }
embedded-sdmmc = { git = "https://github.com/robamu/embedded-sdmmc-rs.git", branch = "all-features" }
embassy-sync = { version = "0.8" }
heapless = "0.9"
axi-uartlite = { version = "0.1" }
axi-uart16550 = { version = "0.1" }
Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
version="1.1"
width="0.16in" height="0.106667in"
viewBox="0 0 48 32">
<defs>
</defs>
<image id="raster0"
x="0"
y="0"
width="48"
height="32"
opacity="1.000000"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAgCAQAAAD+3TOXAAAHDmVYSWZJSSoACAAAAAAADgAAAAkA/gAEAAEAAAABAAAAAAEEAAEAAAAAAQAAAQEEAAEAAACqAAAAAgEDAAMAAACAAAAAAwEDAAEAAAAGAAAABgEDAAEAAAAGAAAAFQEDAAEAAAADAAAAAQIEAAEAAACGAAAAAgIEAAEAAACIBgAAAAAAAAgACAAIAP/Y/+AAEEpGSUYAAQEAAAEAAQAA/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAqgEAAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A+f6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr6/+CX/ACSHQv8At4/9KJK+QK+v/gl/ySHQv+3j/wBKJKAPnzxr8Jte8B6NDqmqXemzQS3C26rayOzBirNk7kUYwh7+lcHX0/8AtHf8k80//sKx/wDoqWvmCgAroPAn/JQ/DX/YVtf/AEatc/XQeBP+Sh+Gv+wra/8Ao1aAPqP4s+CtS8eeFbXS9LntIZ4r1LhmunZVKhHXA2qxzlx29a+VPFPhu88I+I7vQ7+SCS6tdm94GJQ7kVxgkA9GHavuevkD42/8le13/t3/APSeOgDz+iiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK+g/hL8WdB0rw74f8Hz2mpNqD3BtxIkaGLdLOxU5Lg4+cZ49etfPlWLC+uNM1G2v7OTy7q1lSaF9oO11IKnB4OCB1oA+968j+J3xZ0HSofEXg+e01JtQeye3EiRoYt0sOVOS4OPnGePXrXjn/AAu34h/9DD/5JW//AMbrj9b1vUfEesT6tq1x9ovp9vmS7FTdtUKOFAA4AHAoAz6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/9mZmF3EAAAAAXNSR0IB2cksfwAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAhhJREFUSMft1T1I1WEUBvDfVQn1hoHoDQlMqCSlaLAiCKMhbIpqKLCtTQKJoLWh76GgpQ+isKWMgoiiocgpKoKEoCHCoHJJ1GjRAvu4p8GPrt57Ta/W5HmW9/w553ne9z3nf14WbJ6tXMu/or7vPM76aoVavbbMH3W/NtXSwnU/hJd6hA5LPXZ0btQbJNQLYVBMQdqgcBc1agoVCD1uZ1FnYtAt313+O9WiPAIzw8UpeUnnbEZy1C1WpskzqzJCLvnozowF3nnkm4ax3OU+CJU4IQU3bFMuDGuaEDgyY/I/GN1vwhshUKbf3hJb7TOiy4CUbgmstER61vXqsxpvbZo4yXEpQZuQtt4LIdTjaQG7H0erPWOrDmFENc1C6NUlhEYcKJj+s3IpPyf8U6NHuTDxoQ8NDhUs8FqzSmfGvIdKxu+vzZAQ2rXnS45pvEkYViuE04oyS1TlldAs6WM+gcjrTcJJXcL27C6o0yvtis45CaQ9Fw7mbrRlHsyhf/7gZiZpccZ6SNKOeZi+a5R6kutPapEWwq95OMW+cdLMSo9IgGO+ueTLdNMvhw1M8u7JOz2HVQuh1G7XsnsqR3n7ddqpGKFp6g4SWRKw2LAKQ6DGOiusVaF1SuxVfd7q1pORW+mT0izWHLdQ53ABz1OxEMr/HhiSNs6SvkSjIu9zFyiXRCG2KzuvKGdgwn4VBQhU5eHLI7Jg/8d+A9OUD/TqXTt5AAAAAElFTkSuQmCCAA==" />
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

+13 -4
View File
@@ -1,10 +1,12 @@
MEMORY
{
/* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app.
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
recommended for something like DMA descriptors. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
1 MB stack memory and 1 MB of memory which will be configured as uncached device memory by the
MMU. This is recommended for something like DMA descriptors. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
}
REGION_ALIAS("VECTORS", CODE);
@@ -21,3 +23,10 @@ SECTIONS
_ebss_uncached = .;
} > UNCACHED
}
PROVIDE(_und_stack_size = 2K);
PROVIDE(_svc_stack_size = 2K);
PROVIDE(_abt_stack_size = 2K);
PROVIDE(_hyp_stack_size = 1K);
PROVIDE(_irq_stack_size = 2K);
PROVIDE(_sys_stack_size = 32K);
+31 -36
View File
@@ -32,7 +32,7 @@ use embassy_time::{Duration, Timer};
use embedded_io::Write;
use embedded_io_async::Write as _;
use log::{LevelFilter, debug, error, info, warn};
use rand::{RngCore, SeedableRng};
use rand::{Rng, SeedableRng};
use zedboard::PS_CLOCK_FREQUENCY;
use zedboard_bsp::phy_marvell;
use zynq7000_hal::{
@@ -42,7 +42,8 @@ use zynq7000_hal::{
eth::{
AlignedBuffer, ClockDivSet, EthernetConfig, EthernetLowLevel, embassy_net::InterruptResult,
},
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
generic_interrupt_handler,
gic::{Configurator, Interrupt},
gpio::{GpioPins, Output, PinState},
gtc::GlobalTimerCounter,
l2_cache,
@@ -216,7 +217,7 @@ async fn main(spawner: Spawner) -> ! {
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
@@ -241,7 +242,7 @@ async fn main(spawner: Spawner) -> ! {
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Safety: We are not multi-threaded yet.
unsafe { zynq7000_hal::log::uart_blocking::init_unsafe_single_core(uart, LOG_LEVEL, false) };
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, LOG_LEVEL, false);
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
@@ -278,6 +279,12 @@ async fn main(spawner: Spawner) -> ! {
"Calculated RGMII clock configuration: {:?}, errors (missmatch from ideal rate in hertz): {:?}",
clk_divs, clk_errors
);
zynq7000_hal::register_interrupt(
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Eth0),
custom_eth_interupt_handler,
);
// Unwrap okay, we use a standard clock config, and the clock config should never fail.
let eth_cfg = EthernetConfig::new(
zynq7000_hal::eth::ClockConfig::new(clk_divs.cfg_1000_mbps),
@@ -373,9 +380,9 @@ async fn main(spawner: Spawner) -> ! {
let tcp_socket = TcpSocket::new(stack, RX_TCP_BUFS.take(), TX_TCP_BUFS.take());
// Spawn all embassy tasks.
spawner.spawn(embassy_net_task(runner)).unwrap();
spawner.spawn(udp_task(udp_socket)).unwrap();
spawner.spawn(tcp_task(tcp_socket)).unwrap();
spawner.spawn(embassy_net_task(runner).unwrap());
spawner.spawn(udp_task(udp_socket).unwrap());
spawner.spawn(tcp_task(tcp_socket).unwrap());
let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low);
@@ -461,36 +468,24 @@ async fn main(spawner: Spawner) -> ! {
}
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(spi_interrupt) => {
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Eth0 {
// This generic library provided interrupt handler takes care of waking
// the driver on received or sent frames while also reporting anomalies
// and errors.
let result = zynq7000_hal::eth::embassy_net::on_interrupt(
zynq7000_hal::eth::EthernetId::Eth0,
);
if result.has_errors() {
ETH_ERR_QUEUE.try_send(result).ok();
}
}
}
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
// Safety: Only called by interrupt handler, registered in global interrupt handler map.
unsafe fn custom_eth_interupt_handler() {
// This generic library provided interrupt handler takes care of waking
// the driver on received or sent frames while also reporting anomalies
// and errors.
let result = zynq7000_hal::eth::embassy_net::on_interrupt(zynq7000_hal::eth::EthernetId::Eth0);
if result.has_errors() {
ETH_ERR_QUEUE.try_send(result).ok();
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
@@ -20,8 +20,8 @@ use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
configure_level_shifter, generic_interrupt_handler,
gic::Configurator,
gpio::{GpioPins, Output, PinState},
gtc::GlobalTimerCounter,
i2c, l2_cache,
@@ -54,7 +54,7 @@ async fn main(_spawner: Spawner) -> ! {
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
@@ -80,14 +80,8 @@ async fn main(_spawner: Spawner) -> ! {
.unwrap();
uart.write_all(b"-- Zynq 7000 Zedboard I2C L3GD20H example --\n\r")
.unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
@@ -153,24 +147,13 @@ async fn main(_spawner: Spawner) -> ! {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(_spi_interrupt) => (),
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
#[zynq7000_rt::irq]
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
@@ -20,14 +20,15 @@ use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
configure_level_shifter, generic_interrupt_handler,
gic::Configurator,
gpio::{GpioPins, Output, PinState},
gtc::GlobalTimerCounter,
l2_cache,
spi::{self, SpiAsync, SpiId, SpiWithHwCs, SpiWithHwCsAsync, on_interrupt},
log::asynch::UartLoggerRunner,
spi::{self, SpiAsync, SpiWithHwCs, SpiWithHwCsAsync},
time::Hertz,
uart::{self, TxAsync, on_interrupt_tx},
uart::{self, TxAsync},
};
use zynq7000::{Peripherals, slcr::LevelShifterConfig, spi::DelayControl};
@@ -56,15 +57,20 @@ async fn main(spawner: Spawner) -> ! {
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let mut clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// SPI reference clock must be larger than the CPU 1x clock.
let spi_ref_clk_div = spi::calculate_largest_allowed_spi_ref_clk_divisor(&clocks)
.unwrap()
.value()
- 1;
spi::configure_spi_ref_clk(&mut clocks, arbitrary_int::u6::new(spi_ref_clk_div as u8));
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
// the CPU 1x clock which is around 110 MHz.
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
assert!(
clocks.io_clocks().spi_clk().to_raw()
> (clocks.arm_clocks().cpu_1x_clk().to_raw() as f32 * 1.2) as u32,
"SPI reference clock must be larger than CPU 1x clock"
);
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
@@ -90,30 +96,32 @@ async fn main(spawner: Spawner) -> ! {
.unwrap();
uart.write_all(b"-- Zynq 7000 Zedboard SPI L3GD20H example --\n\r")
.unwrap();
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
let (tx, _) = uart.split();
let tx_async = TxAsync::new(tx, true);
let log_runner =
zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_async).unwrap();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
if DEBUG_SPI_CLK_CONFIG {
info!(
"SPI Clock Information: CPU 1x: {:?}, IO Ref Clk: {:?}, SPI Ref Clk: {:?}, DIV: {:?}",
"SPI Clock Information: CPU 1x: {:?}, IO Ref Clk: {:?}, SPI Ref Clk: {:?}",
clocks.arm_clocks().cpu_1x_clk(),
clocks.io_clocks().ref_clk(),
clocks.io_clocks().spi_clk(),
spi_ref_clk_div
);
}
let mut spi = spi::Spi::new_one_hw_cs(
dp.spi_1,
clocks.io_clocks(),
spi::Config::new(
// 10 MHz maximum rating of the sensor.
zynq7000::spi::BaudDivSel::By64,
//l3gd20::MODE,
// l3gd20::MODE,
embedded_hal::spi::MODE_3,
spi::SlaveSelectConfig::AutoWithAutoStart,
spi::SlaveSelectConfig::AutoCsAutoStart,
),
(
gpio_pins.mio.mio12,
@@ -123,10 +131,13 @@ async fn main(spawner: Spawner) -> ! {
gpio_pins.mio.mio13,
)
.unwrap();
let sclk = Hertz::from_raw(
clocks.io_clocks().spi_clk().to_raw() / zynq7000::spi::BaudDivSel::By64.div_value() as u32,
);
let mod_id = spi.regs().read_mod_id();
assert_eq!(mod_id, spi::MODULE_ID);
assert!(spi.sclk() <= Hertz::from_raw(10_000_000));
let min_delay = (spi.sclk().raw() * 5) / 1_000_000_000;
assert!(sclk <= Hertz::from_raw(10_000_000));
let min_delay = (sclk.to_raw() * 5) / 1_000_000_000;
spi.inner().configure_delays(
DelayControl::builder()
.with_inter_word_cs_deassert(0)
@@ -155,25 +166,17 @@ async fn main(spawner: Spawner) -> ! {
}
}
spawner.spawn(logger_task(uart)).unwrap();
spawner.spawn(logger_task(log_runner).unwrap());
if BLOCKING {
blocking_application(mio_led, emio_leds, spi).await;
blocking_application(mio_led, emio_leds, spi).await
} else {
non_blocking_application(mio_led, emio_leds, spi).await;
non_blocking_application(mio_led, emio_leds, spi).await
}
}
#[embassy_executor::task]
pub async fn logger_task(uart: uart::Uart) {
let (tx, _) = uart.split();
let mut tx_async = TxAsync::new(tx);
let frame_queue = zynq7000_hal::log::rb::get_frame_queue();
let mut log_buf: [u8; 2048] = [0; 2048];
loop {
let next_frame_len = frame_queue.receive().await;
zynq7000_hal::log::rb::read_next_frame(next_frame_len, &mut log_buf);
tx_async.write(&log_buf[0..next_frame_len]).await;
}
pub async fn logger_task(mut log_runner: UartLoggerRunner) -> ! {
log_runner.run().await
}
pub async fn blocking_application(
@@ -237,30 +240,13 @@ pub async fn non_blocking_application(
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(spi_interrupt) => {
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Spi1 {
on_interrupt(SpiId::Spi1);
} else if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Uart1 {
on_interrupt_tx(zynq7000_hal::uart::UartId::Uart1);
}
}
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
#[zynq7000_rt::irq]
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
@@ -0,0 +1,276 @@
#![no_std]
#![no_main]
use aarch32_cpu::asm::nop;
use core::panic::PanicInfo;
use dummy_pin::DummyPin;
use embassy_executor::Spawner;
use embassy_time::{Delay, Duration, Ticker};
use embedded_graphics::{Drawable as _, geometry::Point};
use embedded_hal::digital::StatefulOutputPin;
use embedded_hal_async::delay::DelayNs as _;
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
use embedded_io::Write;
use log::info;
use ssd1306::{Ssd1306Async, prelude::*};
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::{
BootMode, clocks, generic_interrupt_handler, gpio, gtc,
log::asynch::UartLoggerRunner,
spi::{self, SpiAsync},
time::Hertz,
uart::{self, TxAsync},
};
use embedded_graphics::image::Image;
use tinybmp::Bmp;
use zynq7000_rt as _;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard Async OLED example --\n\r";
/// Entry point which calls the embassy main method.
#[zynq7000_rt::entry]
fn entry_point() -> ! {
main();
}
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let mut clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
// the CPU 1x clock which is around 110 MHz.
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
assert!(
clocks.io_clocks().spi_clk().to_raw()
> (clocks.arm_clocks().cpu_1x_clk().to_raw() as f32 * 1.2) as u32,
"SPI reference clock must be larger than CPU 1x clock"
);
let mut gpio_pins = gpio::GpioPins::new(periphs.gpio);
// Set up global timer counter and embassy time driver.
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio_for_uart_1(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
uart.flush().unwrap();
let (tx, _) = uart.split();
let tx_async = TxAsync::new(tx, true);
let log_runner =
zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_async)
.expect("Failed to initialize async logger");
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
info!(
"SPI reference clock speed: {:?}",
clocks.io_clocks().spi_clk()
);
spawner.spawn(logger_task(log_runner).unwrap());
let mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
let emio_leds: [gpio::Output; 8] = [
gpio::Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), gpio::PinState::Low),
];
spawner.spawn(blinky_task(mio_led, emio_leds).unwrap());
let dc_pin = gpio::Output::new_for_emio(gpio_pins.emio.take(11).unwrap(), gpio::PinState::High);
let mut reset_pin =
gpio::Output::new_for_emio(gpio_pins.emio.take(12).unwrap(), gpio::PinState::High);
let mut oled_vdd_switch =
gpio::Output::new_for_emio(gpio_pins.emio.take(13).unwrap(), gpio::PinState::High);
let mut oled_vbat_switch =
gpio::Output::new_for_emio(gpio_pins.emio.take(14).unwrap(), gpio::PinState::High);
Delay.delay_ms(100).await;
let spi = spi::Spi::new_for_emio(
periphs.spi_0,
spi::Config::calculate_for_io_clock(
Hertz::MHz(8),
clocks.io_clocks(),
embedded_hal::spi::MODE_0,
spi::SlaveSelectConfig::AutoCsManualStart,
),
)
.expect("Failed to initialize SPI");
let spi_asynch = SpiAsync::new(spi);
let exclusive_device = ExclusiveDevice::new(spi_asynch, DummyPin::new_high(), NoDelay)
.expect("Failed to create exclusive SPI device");
let spi_if = SPIInterface::new(exclusive_device, dc_pin);
let mut ssd1306 = Ssd1306Async::new(spi_if, DisplaySize128x32, DisplayRotation::Rotate180);
oled_vdd_switch.set_low();
oled_vbat_switch.set_low();
Delay.delay_ms(100).await;
ssd1306
.reset(&mut reset_pin, &mut embassy_time::Delay {})
.await
.expect("display reset error");
let mut display = ssd1306.into_buffered_graphics_mode();
display.init().await.expect("display init error");
// Include the BMP file data.
let ferris_data = include_bytes!("../../assets/ferris-flat-happy-small.bmp");
let rust_logo_data = include_bytes!("../../assets/rust-logo-single-path.bmp");
// Parse the BMP file.
let bmp_rust = Bmp::from_slice(rust_logo_data).expect("BMP loading error");
let bmp_ferris = Bmp::from_slice(ferris_data).expect("BMP loading error");
// Draw the image with the top left corner at (10, 20) by wrapping it in
// an embedded-graphics `Image`.
Image::new(&bmp_rust, Point::new(0, 0))
.draw(&mut display)
.expect("image drawing error");
Image::new(&bmp_ferris, Point::new(32, 0))
.draw(&mut display)
.expect("image drawing error");
display.flush().await.unwrap();
let mut ticker = Ticker::every(Duration::from_millis(50));
let mut ferris = FerrisMovement::new(32, Direction::Right, 32, 76);
loop {
Image::new(&bmp_ferris, Point::new(ferris.pos as i32, 0))
.draw(&mut display)
.expect("image drawing error");
display.flush().await.expect("flush error");
ferris.step();
ticker.next().await;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Right,
Left,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FerrisMovement {
pub pos: u16,
pub dir: Direction,
pub left_threshold: u16,
pub right_threshold: u16,
}
impl FerrisMovement {
/// Creates a new movement controller.
/// Assumes: left_threshold <= right_threshold and pos is within that range.
pub fn new(pos: u16, dir: Direction, left_threshold: u16, right_threshold: u16) -> Self {
Self {
pos,
dir,
left_threshold,
right_threshold,
}
}
/// Move one tick and "bounce" between thresholds.
pub fn step(&mut self) {
match self.dir {
Direction::Right => {
if self.pos >= self.right_threshold {
self.dir = Direction::Left; // flip at right boundary
} else {
self.pos += 1;
}
}
Direction::Left => {
if self.pos <= self.left_threshold {
self.dir = Direction::Right; // flip at left boundary
} else {
self.pos -= 1;
}
}
}
}
}
#[embassy_executor::task]
pub async fn logger_task(mut log_runner: UartLoggerRunner) -> ! {
log_runner.run().await
}
#[embassy_executor::task]
pub async fn blinky_task(mut mio_led: gpio::Output, mut emio_leds: [gpio::Output; 8]) {
let mut ticker = Ticker::every(Duration::from_millis(200));
loop {
mio_led.toggle().unwrap();
// Create a wave pattern for emio_leds
for led in emio_leds.iter_mut() {
led.toggle().unwrap();
ticker.next().await; // Wait for the next ticker for each toggle
}
ticker.next().await;
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) };
writeln!(uart, "panic: {}\r", info).ok();
loop {}
}
+271
View File
@@ -0,0 +1,271 @@
#![no_std]
#![no_main]
use aarch32_cpu::asm::nop;
use core::panic::PanicInfo;
use dummy_pin::DummyPin;
use embassy_executor::Spawner;
use embassy_time::{Delay, Duration, Ticker};
use embedded_graphics::{Drawable as _, geometry::Point};
use embedded_hal::digital::StatefulOutputPin;
use embedded_hal_async::delay::DelayNs as _;
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
use embedded_io::Write;
use log::info;
use ssd1306::{Ssd1306, prelude::*};
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::{
BootMode, clocks, generic_interrupt_handler, gpio, gtc,
log::asynch::UartLoggerRunner,
spi,
time::Hertz,
uart::{self, TxAsync},
};
use embedded_graphics::image::Image;
use tinybmp::Bmp;
use zynq7000_rt as _;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard OLED example --\n\r";
/// Entry point which calls the embassy main method.
#[zynq7000_rt::entry]
fn entry_point() -> ! {
main();
}
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let mut clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
// the CPU 1x clock which is around 110 MHz.
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
assert!(
clocks.io_clocks().spi_clk().to_raw()
> (clocks.arm_clocks().cpu_1x_clk().to_raw() as f32 * 1.2) as u32,
"SPI reference clock must be larger than CPU 1x clock"
);
let mut gpio_pins = gpio::GpioPins::new(periphs.gpio);
// Set up global timer counter and embassy time driver.
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio_for_uart_1(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
uart.flush().unwrap();
let (tx, _) = uart.split();
let tx_async = TxAsync::new(tx, true);
let log_runner =
zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_async)
.expect("Failed to initialize async logger");
spawner.spawn(logger_task(log_runner).unwrap());
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
info!(
"SPI reference clock speed: {:?}",
clocks.io_clocks().spi_clk()
);
let mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
let emio_leds: [gpio::Output; 8] = [
gpio::Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), gpio::PinState::Low),
];
spawner.spawn(blinky_task(mio_led, emio_leds).unwrap());
let dc_pin = gpio::Output::new_for_emio(gpio_pins.emio.take(11).unwrap(), gpio::PinState::High);
let mut reset_pin =
gpio::Output::new_for_emio(gpio_pins.emio.take(12).unwrap(), gpio::PinState::High);
let mut oled_vdd_switch =
gpio::Output::new_for_emio(gpio_pins.emio.take(13).unwrap(), gpio::PinState::High);
let mut oled_vbat_switch =
gpio::Output::new_for_emio(gpio_pins.emio.take(14).unwrap(), gpio::PinState::High);
Delay.delay_ms(100).await;
let spi = spi::Spi::new_for_emio(
periphs.spi_0,
spi::Config::calculate_for_io_clock(
Hertz::MHz(8),
clocks.io_clocks(),
embedded_hal::spi::MODE_0,
spi::SlaveSelectConfig::AutoCsManualStart,
),
)
.expect("Failed to initialize SPI");
let exclusive_device = ExclusiveDevice::new(spi, DummyPin::new_high(), NoDelay)
.expect("Failed to create exclusive SPI device");
let spi_if = SPIInterface::new(exclusive_device, dc_pin);
let mut ssd1306 = Ssd1306::new(spi_if, DisplaySize128x32, DisplayRotation::Rotate180);
oled_vdd_switch.set_low();
oled_vbat_switch.set_low();
Delay.delay_ms(100).await;
ssd1306.reset(&mut reset_pin, &mut embassy_time::Delay {});
let mut display = ssd1306.into_buffered_graphics_mode();
display.init().expect("display init error");
// Include the BMP file data.
let ferris_data = include_bytes!("../../assets/ferris-flat-happy-small.bmp");
let rust_logo_data = include_bytes!("../../assets/rust-logo-single-path.bmp");
// Parse the BMP file.
let bmp_rust = Bmp::from_slice(rust_logo_data).unwrap();
let bmp_ferris = Bmp::from_slice(ferris_data).unwrap();
// Draw the image with the top left corner at (10, 20) by wrapping it in
// an embedded-graphics `Image`.
Image::new(&bmp_rust, Point::new(0, 0))
.draw(&mut display)
.expect("image draw error");
Image::new(&bmp_ferris, Point::new(32, 0))
.draw(&mut display)
.expect("image draw error");
display.flush().expect("display flush error");
let mut ticker = Ticker::every(Duration::from_millis(50));
let mut ferris = FerrisMovement::new(32, Direction::Right, 32, 76);
loop {
Image::new(&bmp_ferris, Point::new(ferris.pos as i32, 0))
.draw(&mut display)
.expect("image draw error");
display.flush().expect("display flush error");
ferris.step();
ticker.next().await;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Right,
Left,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FerrisMovement {
pub pos: u16,
pub dir: Direction,
pub left_threshold: u16,
pub right_threshold: u16,
}
impl FerrisMovement {
/// Creates a new movement controller.
/// Assumes: left_threshold <= right_threshold and pos is within that range.
pub fn new(pos: u16, dir: Direction, left_threshold: u16, right_threshold: u16) -> Self {
Self {
pos,
dir,
left_threshold,
right_threshold,
}
}
/// Move one tick and "bounce" between thresholds.
pub fn step(&mut self) {
match self.dir {
Direction::Right => {
if self.pos >= self.right_threshold {
self.dir = Direction::Left; // flip at right boundary
} else {
self.pos += 1;
}
}
Direction::Left => {
if self.pos <= self.left_threshold {
self.dir = Direction::Right; // flip at left boundary
} else {
self.pos -= 1;
}
}
}
}
}
#[embassy_executor::task]
pub async fn logger_task(mut log_runner: UartLoggerRunner) -> ! {
log_runner.run().await
}
#[embassy_executor::task]
pub async fn blinky_task(mut mio_led: gpio::Output, mut emio_leds: [gpio::Output; 8]) {
let mut ticker = Ticker::every(Duration::from_millis(200));
loop {
mio_led.toggle().unwrap();
// Create a wave pattern for emio_leds
for led in emio_leds.iter_mut() {
led.toggle().unwrap();
ticker.next().await; // Wait for the next ticker for each toggle
}
ticker.next().await;
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) };
writeln!(uart, "panic: {}\r", info).ok();
loop {}
}
+54 -36
View File
@@ -2,6 +2,7 @@
#![no_main]
use aarch32_cpu::asm::nop;
use arbitrary_int::{traits::Integer as _, u2};
use core::panic::PanicInfo;
use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
@@ -10,10 +11,13 @@ use embedded_io::Write;
use log::{error, info};
use zedboard::PS_CLOCK_FREQUENCY;
use zedboard_bsp::qspi_spansion;
use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, prelude::*, qspi, uart};
use zynq7000_hal::{
BootMode, clocks, generic_interrupt_handler, gpio, gtc, prelude::*, qspi, uart,
};
use zynq7000_rt as _;
const DISPLAY_CLOCK_CONFIG: bool = false;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard QSPI example --\n\r";
const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination {
vendor: qspi::QspiVendor::WinbondAndSpansion,
@@ -28,6 +32,7 @@ fn entry_point() -> ! {
}
const ERASE_PROGRAM_READ_TEST: bool = false;
const TEST_QSPI_BASE: u32 = 0x20000;
#[embassy_executor::main]
async fn main(_spawner: Spawner) -> ! {
@@ -57,17 +62,13 @@ async fn main(_spawner: Spawner) -> ! {
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
if DISPLAY_CLOCK_CONFIG {
log::debug!("clock config: {:?}", clocks);
}
let qspi_clock_config =
qspi::ClockConfig::calculate_with_loopback(qspi::SrcSelIo::IoPll, &clocks, 100.MHz())
@@ -90,7 +91,14 @@ async fn main(_spawner: Spawner) -> ! {
let qspi_io_mode = qspi.into_io_mode(false);
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(
qspi_io_mode,
qspi_spansion::Config {
set_quad_bit_if_necessary: true,
latency_config: Some(u2::ZERO),
clear_write_protection: true,
},
);
let rdid = spansion_qspi.read_rdid_extended();
info!(
@@ -103,27 +111,48 @@ async fn main(_spawner: Spawner) -> ! {
);
let cr1 = spansion_qspi.read_configuration_register();
info!("QSPI Configuration Register 1: {:?}", cr1);
let sr = spansion_qspi.read_status_register_1();
info!("QSPI Status Register: {:?}", sr);
let mut write_buf: [u8; u8::MAX as usize + 1] = [0x0; u8::MAX as usize + 1];
let mut write_buf: [u8; 100 * qspi_spansion::PAGE_SIZE] = [0x0; 100 * qspi_spansion::PAGE_SIZE];
for (idx, byte) in write_buf.iter_mut().enumerate() {
*byte = idx as u8;
*byte = (idx % u8::MAX as usize) as u8;
}
let mut read_buf = [0u8; 256];
let mut read_buf = [0u8; 100 * qspi_spansion::PAGE_SIZE];
if ERASE_PROGRAM_READ_TEST {
info!("performing erase, program, read test");
spansion_qspi
.erase_sector(0x10000)
.erase_sector(TEST_QSPI_BASE)
.expect("erasing sector failed");
spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true);
spansion_qspi.read_fast_read(TEST_QSPI_BASE, &mut read_buf, true);
for read in read_buf.iter() {
assert_eq!(*read, 0xFF);
}
read_buf.fill(0);
spansion_qspi.program_page(0x10000, &write_buf).unwrap();
spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true);
for (read, written) in read_buf.iter().zip(write_buf.iter()) {
assert_eq!(read, written);
let mut current_offset = 0_usize;
let mut current_qspi_offset = TEST_QSPI_BASE;
for (idx, chunk) in write_buf
.chunks(qspi_spansion::RECOMMENDED_PROGRAM_PAGE_SIZE)
.enumerate()
{
spansion_qspi
.program(current_qspi_offset, chunk)
.expect("programming failed");
spansion_qspi.read_fast_read(
current_qspi_offset,
&mut read_buf[current_offset..current_offset + chunk.len()],
true,
);
assert_eq!(
chunk,
&read_buf[current_offset..current_offset + chunk.len()],
"read and write missmatch at chunk index {}, data offset {}",
idx,
current_offset
);
current_offset += chunk.len();
current_qspi_offset += chunk.len() as u32;
}
info!("test successful");
}
@@ -133,7 +162,7 @@ async fn main(_spawner: Spawner) -> ! {
let guard = spansion_lqspi.read_guard();
unsafe {
core::ptr::copy_nonoverlapping(
(qspi::QSPI_START_ADDRESS + 0x10000) as *const u8,
(qspi::QSPI_START_ADDRESS + TEST_QSPI_BASE as usize) as *const u8,
read_buf.as_mut_ptr(),
256,
);
@@ -159,23 +188,12 @@ async fn main(_spawner: Spawner) -> ! {
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = gic::GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
gic::Interrupt::Sgi(_) => (),
gic::Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
gic::Interrupt::Spi(_spi_interrupt) => (),
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
+258
View File
@@ -0,0 +1,258 @@
#![no_std]
#![no_main]
use aarch32_cpu::asm::nop;
use core::panic::PanicInfo;
use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::error;
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::gpio::Input;
use zynq7000_hal::sd::SdClockConfig;
use zynq7000_hal::{BootMode, clocks, gpio, gtc, sd::SdCardUninit, uart};
use zynq7000_hal::{generic_interrupt_handler, prelude::*};
use zynq7000_rt as _;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard SDIO example --\n\r";
// These are off by default because they write to the SD card as well.
const LOW_LEVEL_TESTS: bool = false;
const SDMMC_RS_TESTS: bool = false;
#[derive(Debug, Clone, Copy)]
pub struct DummyTimeSource;
impl embedded_sdmmc::TimeSource for DummyTimeSource {
fn get_timestamp(&self) -> embedded_sdmmc::Timestamp {
embedded_sdmmc::Timestamp::from_calendar(1970, 1, 1, 0, 0, 0).unwrap()
}
}
/// Entry point which calls the embassy main method.
#[zynq7000_rt::entry]
fn entry_point() -> ! {
main();
}
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
let gpio_pins = gpio::GpioPins::new(periphs.gpio);
// Set up global timer counter and embassy time driver.
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio_for_uart_1(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Safety: We are not multi-threaded yet.
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
let sdio_clock_config =
SdClockConfig::calculate_for_io_clock(clocks.io_clocks(), 100.MHz(), 10.MHz()).unwrap();
log::info!("SDIO clock config: {:?}", sdio_clock_config);
let sd_card_uninit = SdCardUninit::new_for_sdio_0(
periphs.sdio_0,
sdio_clock_config,
// On the zedboard, the bank has a 1.8 V voltage which is shifted up to 3.3 V by a
// level shifter.
zynq7000_hal::sd::IoType::LvCmos18,
gpio_pins.mio.mio40,
gpio_pins.mio.mio41,
(
gpio_pins.mio.mio42,
gpio_pins.mio.mio43,
gpio_pins.mio.mio44,
gpio_pins.mio.mio45,
),
)
.unwrap();
let card_detect = Input::new_for_mio(gpio_pins.mio.mio47).unwrap();
let write_protect = Input::new_for_mio(gpio_pins.mio.mio46).unwrap();
// The card detect being active low makes sense according to the Zedboard docs. Not sure
// about write-protect though.. It seems that write protect on means that the
// the pin is pulled high.
log::info!("Card detect state: {:?}", card_detect.is_low());
log::info!("Write protect state: {:?}", write_protect.is_high());
let capabilities = sd_card_uninit.ll().capabilities();
log::debug!("SDIO Capabilities: {:?}", capabilities);
let present_state = sd_card_uninit.ll().read_present_state();
log::debug!("SD present state: {:?}", present_state);
let boot_mode = BootMode::new_from_regs();
log::info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(200));
let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
let sd_result = sd_card_uninit.initialize();
let sd_card = match sd_result {
Ok(card) => {
log::info!("SD card info: {:?}", card.card_info());
card
}
Err(e) => {
panic!("SDIO init error: {e:?}");
}
};
let mut buf: [u8; 4096] = [0; 4096];
if LOW_LEVEL_TESTS {
log::info!("doing SD card low-level tests");
let mut cache_buf: [u8; 4096] = [0; 4096];
// cache the data, will be written back later..
sd_card
.read_multiple_blocks(&mut cache_buf, 0x1000)
.unwrap();
let mut write_data: [u8; 4096] = [0; 4096];
for chunk in write_data.chunks_mut(u8::MAX as usize) {
for (idx, byte) in chunk.iter_mut().enumerate() {
*byte = idx as u8;
}
}
sd_card.write_multiple_blocks(&write_data, 0x1000).unwrap();
sd_card.read_multiple_blocks(&mut buf, 0x1000).unwrap();
for chunk in buf.chunks(u8::MAX as usize) {
for (idx, byte) in chunk.iter().enumerate() {
assert_eq!(idx as u8, *byte);
}
}
sd_card.write_multiple_blocks(&cache_buf, 0x1000).unwrap();
log::info!("SD card low-level tests success");
}
buf.fill(0);
if SDMMC_RS_TESTS {
log::info!("doing SD card embedded-sdmmc-rs tests");
// Now let's look for volumes (also known as partitions) on our block device.
// To do this we need a Volume Manager. It will take ownership of the block device.
let volume_mgr = embedded_sdmmc::VolumeManager::new(sd_card, DummyTimeSource);
// Try and access Volume 0 (i.e. the first partition).
// The volume object holds information about the filesystem on that volume.
let volume0 = volume_mgr
.open_volume(embedded_sdmmc::VolumeIdx(0))
.unwrap();
// Open the root directory (mutably borrows from the volume).
let mut current_dir = volume0.open_root_dir().unwrap();
log::info!("iterating root directory");
current_dir
.iterate_dir(|entry| {
log::info!("{:?}", entry);
core::ops::ControlFlow::Continue(())
})
.unwrap();
let new_file = current_dir
.open_file_in_dir("__T.TXT", embedded_sdmmc::Mode::ReadWriteCreateOrTruncate)
.unwrap();
let string = "test string\n";
new_file.write(string.as_bytes()).unwrap();
new_file.close().unwrap();
let read_new = current_dir
.open_file_in_dir("__T.TXT", embedded_sdmmc::Mode::ReadOnly)
.unwrap();
assert_eq!(read_new.length(), string.len() as u32);
read_new.read(&mut buf).unwrap();
let buf_as_str = core::str::from_utf8(&buf[0..string.len()]).unwrap();
assert_eq!(buf_as_str, string);
read_new.close().unwrap();
current_dir.delete_entry_in_dir("__T.TXT").unwrap();
assert_eq!(
current_dir.find_directory_entry("__T.TXT").unwrap_err(),
embedded_sdmmc::Error::NotFound
);
if current_dir.find_directory_entry("_TDIR").is_ok() {
current_dir.delete_entry_in_dir("_TDIR").unwrap();
}
current_dir.make_dir_in_dir("_TDIR").unwrap();
current_dir.change_dir("_TDIR").unwrap();
current_dir.change_dir("..").unwrap();
current_dir.delete_entry_in_dir("_TDIR").unwrap();
current_dir.close().unwrap();
log::info!("SD card embedded-sdmmc-rs success");
}
loop {
mio_led.toggle().unwrap();
ticker.next().await; // Wait for the next cycle of the ticker
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}
@@ -0,0 +1,587 @@
#![no_std]
#![no_main]
use aarch32_cpu::asm::nop;
use core::{cell::RefCell, panic::PanicInfo, sync::atomic::AtomicU8};
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::{Delay, Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_hal_async::delay::DelayNs as _;
use embedded_io::Write;
use embedded_io_async::Read as _;
use log::info;
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000::spi::FifoWrite;
use zynq7000_hal::{
BootMode, clocks, generic_interrupt_handler, gpio, gtc,
log::asynch::UartLoggerRunner,
spi::{self, SpiAsync},
uart::{self, TxAsync},
};
use zynq7000_rt as _;
#[derive(Debug, PartialEq, Eq)]
pub enum TestType {
Blocking,
Async,
}
const TEST_TYPE: TestType = TestType::Async;
const INIT_STRING: &str = "-- Zynq 7000 SPI slave example --\n\r";
/// Entry point which calls the embassy main method.
#[zynq7000_rt::entry]
fn entry_point() -> ! {
main();
}
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let mut clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
// the CPU 1x clock which is around 110 MHz.
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
let mut gpio_pins = gpio::GpioPins::new(periphs.gpio);
// Set up global timer counter and embassy time driver.
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio_for_uart_1(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
uart.flush().unwrap();
Delay.delay_ms(1).await;
let (tx, _rx) = uart.split();
let tx_asynch = TxAsync::new(tx, true);
let logger_runner =
zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_asynch)
.expect("Failed to initialize async logger");
spawner.spawn(logger_task(logger_runner).unwrap());
// This pin is used to select between a routing from SPI0 to OLED, or SPI0 to SPI1. Low selects
// the SPI0 to OLED interface.
let _spi_mux_pin =
gpio::Output::new_for_emio(gpio_pins.emio.take(15).unwrap(), gpio::PinState::High);
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
let emio_leds: [gpio::Output; 8] = [
gpio::Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), gpio::PinState::Low),
];
spawner.spawn(blinky_task(mio_led, emio_leds).unwrap());
// Enable loopback.
spi::enable_spi0_to_spi1_loopback();
let mut spi_slave = spi::SpiSlave::new(
spi::SpiId::Spi1,
periphs.spi_1,
spi::SlaveConfig {
mode: spi::MODE_0,
enable_modefail: true,
},
);
zynq7000_hal::register_interrupt(spi_slave.interrupt_id(), spi_interrupt_handler);
let spi_master = spi::Spi::new_for_emio(
periphs.spi_0,
spi::Config::new(
// 10 MHz maximum rating of the sensor.
zynq7000::spi::BaudDivSel::By64,
// l3gd20::MODE,
embedded_hal::spi::MODE_0,
spi::SlaveSelectConfig::AutoCsManualStart,
),
)
.unwrap();
let cs_pin = spi::ChipSelectPin::new(spi_master.id(), spi::ChipSelect::Slave0);
// We pre-load the FIFO with a fixed, known sequence.
for i in 0..spi::FIFO_DEPTH / 2 {
spi_slave.write_tx_data(i as u8);
}
log::info!("SPI slave initialized, enabling interrupts and peripheral");
spi_slave.enable_interrupts(true);
spi_slave.enable();
static RX_DATA_PIPE: static_cell::ConstStaticCell<
embassy_sync::pipe::Pipe<CriticalSectionRawMutex, 256>,
> = static_cell::ConstStaticCell::new(embassy_sync::pipe::Pipe::new());
let pipe = RX_DATA_PIPE.take();
let (mut slave_rx_reader, writer) = pipe.split();
critical_section::with(|cs| {
RX_DATA_WRITER.borrow(cs).replace(Some(writer));
});
if TEST_TYPE == TestType::Blocking {
let mut spi_master =
embedded_hal_bus::spi::ExclusiveDevice::new(spi_master, cs_pin, embassy_time::Delay)
.expect("creating exlusive SPI master failed");
let current_rx_val = blocking_tests_small(&mut spi_master, &mut slave_rx_reader).await;
log::info!("blocking tests with small data blocks done");
Delay.delay_ms(10).await;
blocking_tests_large(&mut spi_master, current_rx_val, &mut slave_rx_reader).await;
log::info!("blocking tests with large data blocks done");
} else {
let spi_master = SpiAsync::new(spi_master);
let mut spi_master =
embedded_hal_bus::spi::ExclusiveDevice::new(spi_master, cs_pin, embassy_time::Delay)
.expect("creating exlusive SPI master failed");
let current_rx_val =
blocking_tests_small_async(&mut spi_master, &mut slave_rx_reader).await;
log::info!("async tests with small data blocks done");
Delay.delay_ms(10).await;
blocking_tests_large_async(&mut spi_master, current_rx_val, &mut slave_rx_reader).await;
log::info!("blocking tests with large data blocks done");
}
log::info!("SPI slave tests done");
let mut ticker = Ticker::every(Duration::from_millis(1000));
loop {
ticker.next().await; // Wait for the next cycle of the ticker
}
}
pub async fn blocking_tests_small(
spi_master: &mut impl embedded_hal::spi::SpiDevice,
slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>,
) -> u8 {
let mut tx_buf = [0; 64];
let mut buf = [0; 64];
// Write test.
// We should read back 0,1,2,3 here but the sent values are ignored.
spi_master.write(&[1, 2, 3, 4]).unwrap();
slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap();
assert_eq!(
&buf[0..4],
&[1, 2, 3, 4],
"slave did not receive the expected data"
);
// Read test.
spi_master.read(&mut buf[0..4]).unwrap();
// Now we expect 4,5,6,7 sent by the SPI slave
assert_eq!(
&buf[0..4],
&[4, 5, 6, 7],
"slave did not send the expected data"
);
// Slave should receive dummy data
slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap();
assert_eq!(
&buf[0..4],
&[0, 0, 0, 0],
"slave did not receive the expected data"
);
// Transfer test.
for (i, item) in tx_buf.iter_mut().enumerate().take(16) {
*item = (i * 2) as u8;
}
spi_master
.transfer(&mut buf[0..16], &tx_buf[0..16])
.unwrap();
for i in 8..24 {
assert_eq!(
buf[i - 8],
i as u8,
"slave did not receive the expected data"
);
}
slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap();
for (i, item) in buf.iter().enumerate().take(16) {
assert_eq!(
*item,
(i * 2) as u8,
"slave did not send the expected data"
);
}
// Transfer in place test.
for (i, item) in buf.iter_mut().enumerate().take(16) {
*item = (i * 2) as u8;
}
spi_master.transfer_in_place(&mut buf[0..16]).unwrap();
for i in 24..40 {
assert_eq!(
buf[i - 24],
i as u8,
"slave did not receive the expected data"
);
}
slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap();
for (i, item) in buf.iter().enumerate().take(16) {
assert_eq!(
*item,
(i * 2) as u8,
"slave did not send the expected data"
);
}
40
}
pub async fn blocking_tests_large(
spi_master: &mut impl embedded_hal::spi::SpiDevice,
mut current_rx_val: u8,
slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>,
) -> u8 {
const BUF_LEN: usize = 164;
let mut buf = [0; BUF_LEN];
// Write test.
for (i, item) in buf.iter_mut().enumerate() {
*item = i as u8;
}
spi_master.write(&buf).unwrap();
slave_rx_reader
.read_exact(&mut buf[0..BUF_LEN])
.await
.unwrap();
for (i, item) in buf.iter().enumerate() {
assert_eq!(*item, i as u8, "slave did not receive the expected data");
}
current_rx_val = current_rx_val.wrapping_add(buf.len() as u8);
// Read test.
spi_master.read(&mut buf).unwrap();
for item in &buf {
assert_eq!(
*item, current_rx_val,
"slave did not send the expected data"
);
current_rx_val = current_rx_val.wrapping_add(1);
}
slave_rx_reader
.read_exact(&mut buf[0..BUF_LEN])
.await
.unwrap();
for item in &buf {
assert_eq!(*item, 0, "slave did not receive the expected data");
}
// Transfer test.
let mut tx_buf = [0; BUF_LEN];
for (i, item) in tx_buf.iter_mut().enumerate() {
*item = i as u8;
}
spi_master.transfer(&mut buf, &tx_buf).unwrap();
for item in &buf {
assert_eq!(
*item, current_rx_val,
"slave did not send the expected data"
);
current_rx_val = current_rx_val.wrapping_add(1);
}
slave_rx_reader
.read_exact(&mut buf[0..BUF_LEN])
.await
.unwrap();
for (i, item) in buf.iter().enumerate() {
assert_eq!(*item, i as u8, "slave did not receive the expected data");
}
// Transfer in place test.
for (i, item) in buf.iter_mut().enumerate() {
*item = i as u8;
}
spi_master.transfer_in_place(&mut buf).unwrap();
for item in &buf {
assert_eq!(
*item, current_rx_val,
"slave did not send the expected data"
);
current_rx_val = current_rx_val.wrapping_add(1);
}
slave_rx_reader
.read_exact(&mut buf[0..BUF_LEN])
.await
.unwrap();
for (i, item) in buf.iter().enumerate() {
assert_eq!(*item, i as u8, "slave did not receive the expected data");
}
current_rx_val
}
pub async fn blocking_tests_small_async(
spi_master: &mut impl embedded_hal_async::spi::SpiDevice,
slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>,
) -> u8 {
let mut tx_buf = [0; 64];
let mut buf = [0; 64];
// Write test.
// We should read back 0,1,2,3 here but the sent values are ignored.
spi_master.write(&[1, 2, 3, 4]).await.unwrap();
slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap();
assert_eq!(
&buf[0..4],
&[1, 2, 3, 4],
"slave did not receive the expected data"
);
// Read test.
spi_master.read(&mut buf[0..4]).await.unwrap();
// Now we expect 4,5,6,7 sent by the SPI slave
assert_eq!(
&buf[0..4],
&[4, 5, 6, 7],
"slave did not send the expected data"
);
// Slave should receive dummy data
slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap();
assert_eq!(
&buf[0..4],
&[0, 0, 0, 0],
"slave did not receive the expected data"
);
// Transfer test.
for (i, item) in tx_buf.iter_mut().enumerate().take(16) {
*item = (i * 2) as u8;
}
spi_master
.transfer(&mut buf[0..16], &tx_buf[0..16])
.await
.unwrap();
for i in 8..24 {
assert_eq!(
buf[i - 8],
i as u8,
"slave did not receive the expected data"
);
}
slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap();
for (i, item) in buf.iter().enumerate().take(16) {
assert_eq!(
*item,
(i * 2) as u8,
"slave did not send the expected data"
);
}
// Transfer in place test.
for (i, item) in buf.iter_mut().enumerate().take(16) {
*item = (i * 2) as u8;
}
spi_master.transfer_in_place(&mut buf[0..16]).await.unwrap();
for i in 24..40 {
assert_eq!(
buf[i - 24],
i as u8,
"slave did not receive the expected data"
);
}
slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap();
for (i, item) in buf.iter().enumerate().take(16) {
assert_eq!(
*item,
(i * 2) as u8,
"slave did not send the expected data"
);
}
40
}
pub async fn blocking_tests_large_async(
spi_master: &mut impl embedded_hal_async::spi::SpiDevice,
mut current_rx_val: u8,
slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>,
) -> u8 {
const BUF_LEN: usize = 164;
let mut buf = [0; BUF_LEN];
// Write test.
for (i, item) in buf.iter_mut().enumerate() {
*item = i as u8;
}
spi_master.write(&buf).await.unwrap();
slave_rx_reader
.read_exact(&mut buf[0..BUF_LEN])
.await
.unwrap();
for (i, &item) in buf.iter().enumerate() {
assert_eq!(item, i as u8, "slave did not receive the expected data");
}
current_rx_val = current_rx_val.wrapping_add(buf.len() as u8);
// Read test.
spi_master.read(&mut buf).await.unwrap();
for &item in &buf {
assert_eq!(item, current_rx_val, "slave did not send the expected data");
current_rx_val = current_rx_val.wrapping_add(1);
}
slave_rx_reader
.read_exact(&mut buf[0..BUF_LEN])
.await
.unwrap();
for &item in &buf {
assert_eq!(item, 0, "slave did not receive the expected data");
}
// Transfer test.
let mut tx_buf = [0; BUF_LEN];
for (i, item) in tx_buf.iter_mut().enumerate() {
*item = i as u8;
}
spi_master.transfer(&mut buf, &tx_buf).await.unwrap();
for &item in &buf {
assert_eq!(item, current_rx_val, "slave did not send the expected data");
current_rx_val = current_rx_val.wrapping_add(1);
}
slave_rx_reader
.read_exact(&mut buf[0..BUF_LEN])
.await
.unwrap();
for (i, &item) in buf.iter().enumerate() {
assert_eq!(item, i as u8, "slave did not receive the expected data");
}
// Transfer in place test.
for (i, item) in buf.iter_mut().enumerate() {
*item = i as u8;
}
spi_master.transfer_in_place(&mut buf).await.unwrap();
for &item in &buf {
assert_eq!(item, current_rx_val, "slave did not send the expected data");
current_rx_val = current_rx_val.wrapping_add(1);
}
slave_rx_reader
.read_exact(&mut buf[0..BUF_LEN])
.await
.unwrap();
for (i, &item) in buf.iter().enumerate() {
assert_eq!(item, i as u8, "slave did not receive the expected data");
}
current_rx_val
}
#[embassy_executor::task]
pub async fn logger_task(mut logger_task: UartLoggerRunner) {
logger_task.run().await;
}
static RX_DATA_WRITER: critical_section::Mutex<
RefCell<Option<embassy_sync::pipe::Writer<'static, CriticalSectionRawMutex, 256>>>,
> = critical_section::Mutex::new(RefCell::new(None));
unsafe fn spi_interrupt_handler() {
static CURRENT_TX_VAL: AtomicU8 = AtomicU8::new((spi::FIFO_DEPTH / 2) as u8);
let mut buf = [0; 64];
let mut current_val = CURRENT_TX_VAL.load(core::sync::atomic::Ordering::Relaxed);
let mut spi = unsafe { spi::SpiLowLevel::steal(spi::SpiId::Spi1) };
let status = spi.read_interrupt_status();
spi.write_interrupt_status(status);
let mut index = 0;
while spi.read_interrupt_status().rx_not_empty() && index < buf.len() {
buf[index] = spi.read_rx_data().value();
index += 1;
}
while !spi.read_interrupt_status().tx_full() {
spi.write_tx_data(FifoWrite::new(current_val));
current_val = current_val.wrapping_add(1);
}
CURRENT_TX_VAL.store(current_val, core::sync::atomic::Ordering::Relaxed);
critical_section::with(|cs| {
let opt_writer = RX_DATA_WRITER.borrow(cs).borrow();
if let Some(writer) = opt_writer.as_ref() {
// In a real application, you would read the received data from the SPI peripheral here.
// For this example, we just write a dummy value to the pipe to demonstrate the concept.
let _ = writer.try_write(&buf[0..index]);
}
});
}
#[embassy_executor::task]
pub async fn blinky_task(mut mio_led: gpio::Output, mut emio_leds: [gpio::Output; 8]) {
let mut ticker = Ticker::every(Duration::from_millis(200));
loop {
mio_led.toggle().unwrap();
// Create a wave pattern for emio_leds
for led in emio_leds.iter_mut() {
led.toggle().unwrap();
ticker.next().await; // Wait for the next ticker for each toggle
}
ticker.next().await;
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) };
writeln!(uart, "panic: {}\r", info).ok();
loop {}
}
@@ -9,14 +9,12 @@ use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use fugit::RateExtU32;
use log::{error, info};
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
configure_level_shifter, generic_interrupt_handler, gic,
gpio::{GpioPins, Output, PinState},
gtc::GlobalTimerCounter,
l2_cache,
@@ -105,7 +103,7 @@ async fn main(_spawner: Spawner) -> ! {
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
let mut gic = gic::Configurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
@@ -131,13 +129,7 @@ async fn main(_spawner: Spawner) -> ! {
log_uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Safety: Co-operative multi-tasking is used.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
log_uart,
log::LevelFilter::Trace,
false,
)
};
zynq7000_hal::log::uart_blocking::init_with_busy_flag(log_uart, log::LevelFilter::Trace, false);
// UART0 routed through EMIO to PL pins.
let mut uart_0 =
@@ -147,7 +139,8 @@ async fn main(_spawner: Spawner) -> ! {
// TODO: Can we determine/read the clock frequency to the FPGAs as well?
let (clk_config, error) =
axi_uart16550::ClockConfig::new_autocalc_with_error(100.MHz(), 115200).unwrap();
axi_uart16550::ClockConfig::new_autocalc_with_error(fugit_03::HertzU32::MHz(100), 115200)
.unwrap();
assert!(error < 0.02);
let mut uart_16550 = unsafe {
AxiUart16550::new(
@@ -215,23 +208,12 @@ async fn main(_spawner: Spawner) -> ! {
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(_spi_interrupt) => (),
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
@@ -37,19 +37,20 @@ use embedded_alloc::LlffHeap as Heap;
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write as _;
use heapless::spsc::Queue;
use log::{error, info, warn};
use log::{info, warn};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
configure_level_shifter, generic_interrupt_handler,
gic::{Configurator, Interrupt},
gpio::{GpioPins, Output, PinState},
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfig, Config, Uart},
uart::{self, ClockConfig, Config, Uart},
};
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum UartMode {
Uart0ToUartlite,
Uart0ToUart16550,
@@ -74,6 +75,14 @@ const AXI_UAR16550_BASE_ADDR: u32 = 0x43C0_0000;
pub const UARTLITE_PL_INT_ID: usize = 0;
pub const UART16550_PL_INT_ID: usize = 1;
pub const UART_SPEED: u32 = 115_200;
// Other common baud rates to test with:
// pub const UART_SPEED: u32 = 9600;
// pub const UART_SPEED: u32 = 230_400;
// pub const UART_SPEED: u32 = 912_600;
const RB_SIZE: usize = 512;
// These queues are used to send all data received in the UART interrupt handlers to the main
@@ -88,11 +97,11 @@ static QUEUE_UART16550: static_cell::ConstStaticCell<heapless::spsc::Queue<u8, R
// Those are all used by the interrupt handler, so we have to do the Mutex dance.
static RX_UART_0: Mutex<RefCell<Option<zynq7000_hal::uart::Rx>>> = Mutex::new(RefCell::new(None));
static UART_0_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
static UART_0_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8>>>> =
Mutex::new(RefCell::new(None));
static UARTLITE_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
static UARTLITE_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8>>>> =
Mutex::new(RefCell::new(None));
static UART16550_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
static UART16550_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8>>>> =
Mutex::new(RefCell::new(None));
/// Entry point which calls the embassy main method.
@@ -171,7 +180,7 @@ async fn main(spawner: Spawner) -> ! {
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
// AXI UARTLite documentation mentions that a rising-edge sensitive interrupt is generated,
@@ -210,14 +219,21 @@ async fn main(spawner: Spawner) -> ! {
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
}
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
log_uart,
log::LevelFilter::Trace,
false,
)
};
// Register the interrupts for the PL.
zynq7000_hal::register_interrupt(
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Pl0),
on_interrupt_axi_uartlite,
);
zynq7000_hal::register_interrupt(
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Pl1),
on_interrupt_axi_16550,
);
zynq7000_hal::register_interrupt(
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Uart0),
on_interrupt_uart_0,
);
zynq7000_hal::log::uart_blocking::init_with_busy_flag(log_uart, log::LevelFilter::Trace, false);
// Set up UART multiplexing before creating and configuring the UARTs.
let mut uart_mux = UartMultiplexer::new([
@@ -225,24 +241,42 @@ async fn main(spawner: Spawner) -> ! {
Output::new_for_emio(gpio_pins.emio.take(9).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(10).unwrap(), PinState::Low),
]);
let mut uart_speed = UART_SPEED;
match UART_MODE {
UartMode::Uart0ToUartlite => uart_mux.select(UartSel::Uart0ToUartlite),
UartMode::Uart0ToUart16550 => uart_mux.select(UartSel::Uart0ToUart16550),
UartMode::UartliteToUart16550 => uart_mux.select(UartSel::UartliteToUart16550),
}
if (UART_MODE == UartMode::Uart0ToUartlite || UART_MODE == UartMode::UartliteToUart16550)
&& uart_speed != 115200
{
log::warn!("UARTLITE speed is not configurable. Hardcoding UART speed to 115200");
uart_speed = 115200;
}
let uart0_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), uart_speed)
.unwrap()
.0;
// UART0 routed through EMIO to PL pins.
let uart_0 =
Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart_clk_config)).unwrap();
Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart0_clk_config)).unwrap();
// Safety: Valid address of AXI UARTLITE.
let mut uartlite = unsafe { AxiUartlite::new(AXI_UARTLITE_BASE_ADDR) };
// We need to call this before splitting the structure, because the interrupt signal is
// used for both TX and RX, so the API is only exposed for this structure.
uartlite.enable_interrupt();
let (clk_config, error) =
axi_uart16550::ClockConfig::new_autocalc_with_error(clocks.pl_clocks()[0], 115200).unwrap();
assert!(error < 0.02);
let (clk_config, error) = axi_uart16550::ClockConfig::new_autocalc_with_error(
fugit_03::HertzU32::from_raw(clocks.pl_clocks()[0].to_raw()),
uart_speed,
)
.unwrap();
if error > 0.02 {
log::warn!(
"Calculated clock config for AXI UART16550 has error of {} %, which is higher than 2%. This may lead to incorrect baud rate. Consider changing the input clock or the target baud rate.",
(error * 100.0)
);
}
let _uart_16550 = unsafe {
AxiUart16550::new(
AXI_UAR16550_BASE_ADDR,
@@ -272,7 +306,7 @@ async fn main(spawner: Spawner) -> ! {
let (uartlite_prod, mut uartlite_cons) = QUEUE_UARTLITE.take().split();
let (uart16550_prod, mut uart16550_cons) = QUEUE_UART16550.take().split();
// Use our helper function to start RX handling.
uart_0_rx.start_interrupt_driven_reception();
uart_0_rx.start_interrupt_driven_reception(0xFF);
// Use our helper function to start RX handling.
uart_16550_rx.start_interrupt_driven_reception();
critical_section::with(|cs| {
@@ -284,20 +318,20 @@ async fn main(spawner: Spawner) -> ! {
.replace(uart16550_prod);
RX_UART_0.borrow(cs).borrow_mut().replace(uart_0_rx);
});
spawner.spawn(led_task(mio_led, emio_leds)).unwrap();
spawner.spawn(led_task(mio_led, emio_leds).unwrap());
match UART_MODE {
UartMode::Uart0ToUartlite => {
spawner.spawn(uartlite_task(uartlite_tx)).unwrap();
spawner.spawn(uart_0_task(uart_0_tx)).unwrap();
spawner.spawn(uartlite_task(uartlite_tx).unwrap());
spawner.spawn(uart_0_task(uart_0_tx).unwrap());
}
UartMode::Uart0ToUart16550 => {
spawner.spawn(uart_0_task(uart_0_tx)).unwrap();
spawner.spawn(uart_16550_task(uart_16550_tx)).unwrap();
spawner.spawn(uart_0_task(uart_0_tx).unwrap());
spawner.spawn(uart_16550_task(uart_16550_tx).unwrap());
}
UartMode::UartliteToUart16550 => {
spawner.spawn(uartlite_task(uartlite_tx)).unwrap();
spawner.spawn(uart_16550_task(uart_16550_tx)).unwrap();
spawner.spawn(uartlite_task(uartlite_tx).unwrap());
spawner.spawn(uart_16550_task(uart_16550_tx).unwrap());
}
}
let mut read_buf: [u8; RB_SIZE] = [0; RB_SIZE];
@@ -387,7 +421,8 @@ async fn uartlite_task(uartlite: axi_uartlite::Tx) {
#[embassy_executor::task]
async fn uart_0_task(uart_tx: zynq7000_hal::uart::Tx) {
let mut ticker = Ticker::every(Duration::from_millis(1000));
let mut tx_async = zynq7000_hal::uart::TxAsync::new(uart_tx);
let mut tx_async = zynq7000_hal::uart::TxAsync::new(uart_tx, false);
let str0 = build_print_string("UART0:", "Hello World");
let str1 = build_print_string(
"UART0:",
@@ -396,7 +431,7 @@ async fn uart_0_task(uart_tx: zynq7000_hal::uart::Tx) {
let mut idx = 0;
let print_strs = [str0.as_bytes(), str1.as_bytes()];
loop {
tx_async.write(print_strs[idx]).await;
tx_async.write(print_strs[idx]).unwrap().await;
idx += 1;
if idx == 2 {
idx = 0;
@@ -429,36 +464,12 @@ async fn uart_16550_task(uart_tx: axi_uart16550::Tx) {
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(spi_interrupt) => match spi_interrupt {
zynq7000_hal::gic::SpiInterrupt::Pl0 => {
on_interrupt_axi_uartlite();
}
zynq7000_hal::gic::SpiInterrupt::Pl1 => {
on_interrupt_axi_16550();
}
zynq7000_hal::gic::SpiInterrupt::Uart0 => {
on_interrupt_uart_0();
}
_ => (),
},
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
fn on_interrupt_axi_uartlite() {
@@ -527,8 +538,9 @@ fn on_interrupt_uart_0() {
.on_interrupt(&mut buf, true)
.read_bytes();
});
// Safety: This function is only called once inside the interrupt handler.
// Handle TX next: Handle pending asynchronous TX operations.
zynq7000_hal::uart::on_interrupt_tx(zynq7000_hal::uart::UartId::Uart0);
unsafe { zynq7000_hal::uart::on_interrupt_tx(zynq7000_hal::uart::UartId::Uart0) };
// Send received RX data to main task.
if read_bytes > 0 {
critical_section::with(|cs| {
@@ -565,6 +577,7 @@ fn prefetch_handler(_faulting_addr: usize) -> ! {
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) };
writeln!(uart, "panic: {}\r", info).ok();
loop {}
}
+8 -25
View File
@@ -9,7 +9,7 @@ use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, uart};
use zynq7000_hal::{BootMode, clocks, generic_interrupt_handler, gpio, gtc, uart};
use zynq7000_rt as _;
@@ -49,14 +49,8 @@ async fn main(_spawner: Spawner) -> ! {
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
@@ -88,23 +82,12 @@ async fn main(_spawner: Spawner) -> ! {
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = gic::GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
gic::Interrupt::Sgi(_) => (),
gic::Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
gic::Interrupt::Spi(_spi_interrupt) => (),
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
panic!("Generic interrupt handler failed handling {:?}", e);
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
+1 -1
View File
@@ -5,4 +5,4 @@ break main
load
continue
# continue
+13
View File
@@ -8,6 +8,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
## Fixed
- QSPI robustness fixes. Read, fast-read and write operations are now chunked according to the 252
byte limit specified in the TRM.
## Added
- QSPI constructor can now optionally clear block protection and set latency configuration.
## Changed
- Alignment rules of Spansion QSPI page program now only require 4 byte aligned size.
# [v0.1.0]
Initial release
+7 -2
View File
@@ -10,9 +10,14 @@ keywords = ["no-std", "zedboard", "bare-metal", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
zynq7000 = { path = "../zynq7000", version = "0.1" }
zynq7000 = { path = "../zynq7000", version = "0.4" }
zynq7000-hal = { path = "../zynq7000-hal", version = "0.1" }
bitbybit = "1.4"
bitbybit = "2"
log = "0.4"
arbitrary-int = "2"
num_enum = { version = "0.7", default-features = false }
thiserror = { version = "2", default-features = false }
[package.metadata.docs.rs]
targets = ["armv7a-none-eabihf"]
rustdoc-args = ["--generate-link-to-definition"]
@@ -1,4 +1,4 @@
#![doc = r"This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract) program."]
#![doc = r"This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/host/zynq7000-ps7init-extract) program."]
#![doc = r""]
#![doc = r"This configuration file contains static DDR configuration parameters extracted from the"]
#![doc = r"AMD ps7init.tcl file. It was generated for the MT41K128M16JT-125 DDR chip."]
@@ -34,7 +34,7 @@ pub const DDRC_CONFIG_ZEDBOARD: DdrcConfigSet = DdrcConfigSet {
ctrl_reg5: regs::CtrlReg5::new_with_raw_value(0x00466111),
ctrl_reg6: regs::CtrlReg6::new_with_raw_value(0x00032222),
che_t_zq: regs::CheTZq::new_with_raw_value(0x10200802),
che_t_zq_short_interval_reg: regs::CheTZqShortInterval::new_with_raw_value(0x10200802),
che_t_zq_short_interval_reg: regs::CheTZqShortInterval::new_with_raw_value(0x0690cb73),
deep_powerdown: regs::DeepPowerdown::new_with_raw_value(0x000001fe),
reg_2c: regs::Reg2c::new_with_raw_value(0x1cffffff),
reg_2d: regs::Reg2d::new_with_raw_value(0x00000200),
@@ -1,10 +1,11 @@
#![doc = r"This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract) program."]
#![doc = r"This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/host/zynq7000-ps7init-extract) program."]
#![doc = r""]
#![doc = r"This configuration file contains static DDRIOB configuration parameters extracted from the"]
#![doc = r"AMD ps7init.tcl file. It was generated for the MT41K128M16JT-125 DDR chip."]
use zynq7000::ddrc::regs;
use zynq7000_hal::ddr::DdriobConfigSet;
pub const DDRIOB_CONFIG_SET_ZEDBOARD: DdriobConfigSet = DdriobConfigSet {
ddr_control: zynq7000::slcr::ddriob::DdrControl::new_with_raw_value(0x00000260),
addr0: regs::DdriobConfig::new_with_raw_value(0x00000600),
addr1: regs::DdriobConfig::new_with_raw_value(0x00000600),
data0: regs::DdriobConfig::new_with_raw_value(0x00000672),
+259 -153
View File
@@ -2,10 +2,17 @@ use core::cell::RefCell;
use arbitrary_int::{prelude::*, u24};
use zynq7000_hal::qspi::{
FIFO_DEPTH, LinearQspiConfig, QspiIoMode, QspiIoTransferGuard, QspiLinearAddressing,
FIFO_DEPTH, LinearQspiConfig, MAX_BYTES_PER_TRANSFER_IO_MODE, QspiIoMode, QspiLinearAddressing,
QspiLinearReadGuard,
};
/// 4 bytes are reserved for command byte and address. Rounded down at a 16 byte boundary,
/// recommended by flash memory datasheet.
pub const MAX_DATA_BYTES_PER_WRITE: usize = 240;
/// Probably the most performant chunk/program size to program the chip without crossing page
/// boundaries without exceeding the FIFO size.
pub const RECOMMENDED_PROGRAM_PAGE_SIZE: usize = 128;
pub const QSPI_DEV_COMBINATION_REV_F: zynq7000_hal::qspi::QspiDeviceCombination =
zynq7000_hal::qspi::QspiDeviceCombination {
vendor: zynq7000_hal::qspi::QspiVendor::WinbondAndSpansion,
@@ -13,7 +20,8 @@ pub const QSPI_DEV_COMBINATION_REV_F: zynq7000_hal::qspi::QspiDeviceCombination
two_devices: false,
};
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum RegisterId {
/// WRR
WriteRegisters = 0x01,
@@ -64,7 +72,8 @@ pub enum SectorArchictecture {
Hybrid = 0x01,
}
pub const PAGE_SIZE: usize = 256;
pub const PAGE_SIZE: usize = 0x100;
pub const SECTOR_SIZE: usize = 0x10000;
#[derive(Debug, Clone, Copy)]
pub struct BaseDeviceId {
@@ -159,8 +168,7 @@ impl ExtendedDeviceId {
}
}
#[bitbybit::bitfield(u8)]
#[derive(Debug)]
#[bitbybit::bitfield(u8, debug, forbid_overlaps)]
pub struct StatusRegister1 {
#[bit(7, rw)]
status_register_write_disable: bool,
@@ -168,25 +176,18 @@ pub struct StatusRegister1 {
programming_error: bool,
#[bit(5, r)]
erase_error: bool,
#[bit(4, r)]
bp_2: bool,
#[bit(3, r)]
bp_1: bool,
#[bit(2, r)]
bp_0: bool,
#[bits(2..=4, rw)]
block_protection: u3,
#[bit(1, r)]
write_enable_latch: bool,
#[bit(0, r)]
write_in_progress: bool,
}
#[bitbybit::bitfield(u8)]
#[derive(Debug)]
#[bitbybit::bitfield(u8, debug, forbid_overlaps)]
pub struct ConfigRegister1 {
#[bit(7, rw)]
latency_code_1: bool,
#[bit(6, rw)]
latency_code_0: bool,
#[bits(6..=7, rw)]
latency_code: u2,
/// This is an OTP bit. It can not be set back to 0 once it has been set to 1!
#[bit(5, rw)]
tbprot: bool,
@@ -223,27 +224,60 @@ pub enum ProgramPageError {
ProgrammingErrorBitSet,
#[error("address error: {0}")]
Addr(#[from] AddrError),
#[error("data is larger than page size {PAGE_SIZE}")]
DataLargerThanPage,
#[error("program data is larger than page size {PAGE_SIZE}")]
DataTooLarge,
#[error("program data is not aligned to 4 bytes")]
NotAligned,
#[error("program data crosses page boundary")]
CrossesPageBoundary,
}
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
pub struct Config {
pub set_quad_bit_if_necessary: bool,
pub latency_config: Option<u2>,
pub clear_write_protection: bool,
}
impl Config {
pub fn sr_or_cr_update_possibly_required(&self) -> bool {
self.set_quad_bit_if_necessary
|| self.latency_config.is_some()
|| self.clear_write_protection
}
}
pub struct QspiSpansionS25Fl256SIoMode(RefCell<QspiIoMode>);
impl QspiSpansionS25Fl256SIoMode {
pub fn new(qspi: QspiIoMode, set_quad_bit_if_necessary: bool) -> Self {
pub fn new(qspi: QspiIoMode, config: Config) -> Self {
let mut spansion_qspi = QspiSpansionS25Fl256SIoMode(RefCell::new(qspi));
if set_quad_bit_if_necessary {
spansion_qspi.clear_status();
let mut write_required = false;
if config.sr_or_cr_update_possibly_required() {
let mut cr1 = spansion_qspi.read_configuration_register();
if cr1.quad() {
// Quad bit is already set.
return spansion_qspi;
if config.set_quad_bit_if_necessary && !cr1.quad() {
cr1.set_quad(true);
write_required = true;
}
if let Some(latency_config) = config.latency_config
&& cr1.latency_code() != latency_config
{
cr1.set_latency_code(latency_config);
write_required = true;
}
cr1.set_quad(true);
// Preserve the status register by reading it first.
let sr1 = spansion_qspi.read_status_register_1();
// Safety: Only the QUAD bit was set while all other bits are preserved.
unsafe {
spansion_qspi.write_status_and_config_register(sr1, cr1);
let mut sr1 = spansion_qspi.read_status_register_1();
if config.clear_write_protection && sr1.block_protection() != u3::ZERO {
sr1.set_status_register_write_disable(false);
sr1.set_block_protection(u3::ZERO);
write_required = true;
}
if write_required {
// Safety: Only the QUAD bit was set while all other bits are preserved.
unsafe {
spansion_qspi.write_status_and_config_register(sr1, cr1);
}
}
}
spansion_qspi
@@ -257,6 +291,16 @@ impl QspiSpansionS25Fl256SIoMode {
QspiSpansionS25Fl256SLinearMode(qspi)
}
pub fn set_write_protection(&mut self, write_protection: u3) {
unsafe {
self.modify_status_and_config_register(|mut sr, cr| {
sr.set_status_register_write_disable(false);
sr.set_block_protection(write_protection);
(sr, cr)
});
}
}
pub fn write_enable(&mut self) {
let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard();
@@ -301,7 +345,38 @@ impl QspiSpansionS25Fl256SIoMode {
/// # Safety
///
/// Misuse of this API does not lead to undefined behavior. However, it writes the
/// configuration register, which as OTP bits. Changing these bits from 0 to 1 is an
/// configuration register, which is OTP bits. Changing these bits from 0 to 1 is an
/// irreversible operation.
pub unsafe fn modify_status_and_config_register(
&mut self,
f: impl FnOnce(StatusRegister1, ConfigRegister1) -> (StatusRegister1, ConfigRegister1),
) {
self.write_enable();
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
let sr1 = self.read_status_register_1();
let cr1 = self.read_configuration_register();
let (sr1, cr1) = f(sr1, cr1);
transfer.write_word_txd_11(u32::from_ne_bytes([
RegisterId::WriteRegisters as u8,
sr1.raw_value(),
cr1.raw_value(),
0x00,
]));
transfer.start();
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
}
/// Write a new value for the status register. It is strongly recommended to read both
/// the status and config register first and preserve all unchanged bits.
///
/// This API must be used if the QUAD bit (CR1\[1\]) is set.
///
/// # Safety
///
/// Misuse of this API does not lead to undefined behavior. However, it writes the
/// configuration register, which is OTP bits. Changing these bits from 0 to 1 is an
/// irreversible operation.
pub unsafe fn write_status_and_config_register(
&mut self,
@@ -388,10 +463,10 @@ impl QspiSpansionS25Fl256SIoMode {
/// This function will block until the operation has completed.
pub fn erase_sector(&mut self, addr: u32) -> Result<(), EraseError> {
if addr + 0x10000 > u24::MAX.as_u32() {
if addr + SECTOR_SIZE as u32 > u24::MAX.as_u32() {
return Err(AddrError::OutOfRange.into());
}
if !addr.is_multiple_of(0x10000) {
if !addr.is_multiple_of(SECTOR_SIZE as u32) {
return Err(AddrError::Alignment.into());
}
self.write_enable();
@@ -429,19 +504,35 @@ impl QspiSpansionS25Fl256SIoMode {
}
}
/// This function also takes care of enabling writes before programming the page.
/// This function will block until the operation has completed.
///
/// The data length max not exceed the page size [PAGE_SIZE].
pub fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ProgramPageError> {
pub fn write_pages(&mut self, mut addr: u32, data: &[u8]) -> Result<(), ProgramPageError> {
if addr + data.len() as u32 > u24::MAX.as_u32() {
return Err(AddrError::OutOfRange.into());
}
if !addr.is_multiple_of(0x100) {
return Err(AddrError::Alignment.into());
for chunk in data.chunks(RECOMMENDED_PROGRAM_PAGE_SIZE) {
self.program(addr, chunk)?;
addr += chunk.len() as u32;
}
if data.len() > PAGE_SIZE {
return Err(ProgramPageError::DataLargerThanPage);
Ok(())
}
/// This function also takes care of enabling writes before programming the page.
/// This function will block until the operation has completed.
///
/// The data length may not exceed [MAX_DATA_BYTES_PER_WRITE]. Furthermore, the data needs
/// to be aligned to 4 bytes and the programming operation is not allowed to cross a page.
/// boundary. It is recommended to program in 128 byte chunks.
pub fn program(&mut self, addr: u32, data: &[u8]) -> Result<(), ProgramPageError> {
if addr + data.len() as u32 > u24::MAX.as_u32() {
return Err(AddrError::OutOfRange.into());
}
if data.len() > MAX_DATA_BYTES_PER_WRITE {
return Err(ProgramPageError::DataTooLarge);
}
if !data.len().is_multiple_of(4) {
return Err(ProgramPageError::NotAligned);
}
if (addr as usize % PAGE_SIZE) + data.len() > PAGE_SIZE {
return Err(ProgramPageError::CrossesPageBoundary);
}
self.write_enable();
let qspi = self.0.get_mut();
@@ -455,7 +546,8 @@ impl QspiSpansionS25Fl256SIoMode {
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
let mut read_index: u32 = 0;
let mut current_byte_index = 0;
let fifo_writes = data.len().div_ceil(4);
// Full four byte writes.
let fifo_writes = data.len() / 4;
// Fill the FIFO until it is full.
for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
transfer.write_word_txd_00(u32::from_ne_bytes(
@@ -465,52 +557,14 @@ impl QspiSpansionS25Fl256SIoMode {
));
current_byte_index += 4;
}
transfer.start();
let mut wait_for_tx_slot = |transfer: &mut QspiIoTransferGuard| loop {
let status = transfer.read_status();
if status.rx_above_threshold() {
transfer.read_rx_data();
read_index = read_index.wrapping_add(4);
}
if !status.tx_full() {
break;
}
};
while current_byte_index < data.len() {
// Immediately fill the FIFO again with the remaining 8 bytes.
wait_for_tx_slot(&mut transfer);
let word = match core::cmp::min(4, data.len() - current_byte_index) {
1 => {
let mut bytes = [0; 4];
bytes[0] = data[current_byte_index];
u32::from_ne_bytes(bytes)
}
2 => {
let mut bytes = [0; 4];
bytes[0..2].copy_from_slice(&data[current_byte_index..current_byte_index + 2]);
u32::from_ne_bytes(bytes)
}
3 => {
let mut bytes = [0; 4];
bytes[0..3].copy_from_slice(&data[current_byte_index..current_byte_index + 3]);
u32::from_ne_bytes(bytes)
}
4 => u32::from_ne_bytes(
data[current_byte_index..current_byte_index + 4]
.try_into()
.unwrap(),
),
_ => unreachable!(),
};
transfer.write_word_txd_00(word);
current_byte_index += 4;
}
// Wait until the transfer is done by waiting until all RX bytes have been received.
while read_index < data.len() as u32 {
if transfer.read_status().rx_above_threshold() {
// Double read to avoid RX underflows as specified in TRM.
let status_read = transfer.read_status();
if status_read.rx_above_threshold() && transfer.read_status().rx_above_threshold() {
transfer.read_rx_data();
read_index = read_index.wrapping_add(4);
}
@@ -533,84 +587,134 @@ impl QspiSpansionS25Fl256SIoMode {
}
}
pub fn read_page_fast_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool) {
fn generic_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool, fast_read: bool) {
let mut offset = 0;
let reg_id = if fast_read {
RegisterId::FastRead
} else {
RegisterId::Read
};
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
let raw_word: [u8; 4] = [
RegisterId::FastRead as u8,
((addr >> 16) & 0xff) as u8,
((addr >> 8) & 0xff) as u8,
(addr & 0xff) as u8,
];
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
let mut read_index = 0;
let mut written_words = 0;
let mut bytes_to_write = buf.len();
let mut max_chunk_size = MAX_BYTES_PER_TRANSFER_IO_MODE - 4;
if dummy_byte {
bytes_to_write += 1;
}
let fifo_writes = bytes_to_write.div_ceil(4);
// Fill the FIFO until it is full or all 0 bytes have been written.
for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
transfer.write_word_txd_00(0);
written_words += 1;
max_chunk_size -= 1;
}
transfer.start();
let mut reply_word_index = 0;
while offset < buf.len() {
// Calculate the size of the current chunk (max 248 bytes)
let chunk_size = core::cmp::min(max_chunk_size, buf.len() - offset);
let current_addr = addr + offset as u32;
while read_index < buf.len() {
if transfer.read_status().rx_above_threshold() {
let reply = transfer.read_rx_data();
if reply_word_index == 0 {
reply_word_index += 1;
continue;
// Create a mutable slice for the current chunk
let chunk_slice = &mut buf[offset..offset + chunk_size];
// This ensures the hardware transaction (Chip Select, etc.) restarts for each chunk.
{
let mut transfer = qspi.transfer_guard();
let raw_word: [u8; 4] = [
reg_id as u8,
((current_addr >> 16) & 0xff) as u8,
((current_addr >> 8) & 0xff) as u8,
(current_addr & 0xff) as u8,
];
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
let mut read_index = 0;
let mut written_words = 0;
// Use chunk_size instead of the full buffer length
let mut bytes_to_write = chunk_size;
if dummy_byte {
bytes_to_write += 1;
}
let reply_as_bytes = reply.to_ne_bytes();
let reply_size = core::cmp::min(buf.len() - read_index, 4);
read_index += match (reply_size, reply_word_index == 1 && dummy_byte) {
(1, false) => {
buf[read_index] = reply_as_bytes[0];
1
let fifo_writes = bytes_to_write.div_ceil(4);
// Fill the FIFO until it is full or all 0 bytes have been written.
for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
transfer.write_word_txd_00(0);
written_words += 1;
}
transfer.start();
let mut reply_word_index = 0;
// Loop based on the current chunk's size
while read_index < chunk_size {
let rx_is_above_threshold = transfer.read_status().rx_above_threshold();
// See p.374 of the TRM: Do a double read to ensure this is correct information.
if rx_is_above_threshold && transfer.read_status().rx_above_threshold() {
let reply = transfer.read_rx_data();
if reply_word_index == 0 {
reply_word_index += 1;
continue;
}
let reply_as_bytes = reply.to_ne_bytes();
// Calculate remaining bytes in this specific chunk
let reply_size = core::cmp::min(chunk_size - read_index, 4);
read_index += match (reply_size, reply_word_index == 1 && dummy_byte) {
(1, false) => {
chunk_slice[read_index] = reply_as_bytes[0];
1
}
(1, true) => {
chunk_slice[read_index] = reply_as_bytes[1];
1
}
(2, false) => {
chunk_slice[read_index..read_index + 2]
.copy_from_slice(&reply_as_bytes[0..2]);
2
}
(2, true) => {
chunk_slice[read_index..read_index + 2]
.copy_from_slice(&reply_as_bytes[1..3]);
2
}
(3, false) => {
chunk_slice[read_index..read_index + 3]
.copy_from_slice(&reply_as_bytes[0..3]);
3
}
(3, true) => {
chunk_slice[read_index..read_index + 3]
.copy_from_slice(&reply_as_bytes[1..4]);
3
}
(4, false) => {
chunk_slice[read_index..read_index + 4]
.copy_from_slice(&reply_as_bytes[0..4]);
4
}
(4, true) => {
chunk_slice[read_index..read_index + 3]
.copy_from_slice(&reply_as_bytes[1..4]);
3
}
_ => unreachable!(),
};
reply_word_index += 1;
}
(1, true) => {
buf[read_index] = reply_as_bytes[1];
1
if written_words < fifo_writes && !transfer.read_status().tx_full() {
transfer.write_word_txd_00(0);
written_words += 1;
}
(2, false) => {
buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[0..2]);
2
}
(2, true) => {
buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[1..3]);
2
}
(3, false) => {
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[0..3]);
3
}
(3, true) => {
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]);
3
}
(4, false) => {
buf[read_index..read_index + 4].copy_from_slice(&reply_as_bytes[0..4]);
4
}
(4, true) => {
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]);
3
}
_ => unreachable!(),
};
reply_word_index += 1;
}
if written_words < fifo_writes && !transfer.read_status().tx_full() {
transfer.write_word_txd_00(0);
written_words += 1;
}
}
offset += chunk_size;
}
}
pub fn read_fast_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool) {
self.generic_read(addr, buf, dummy_byte, true)
}
/// Only works if the clock speed is slower than 50 MHz according to datasheet.
pub fn read_page_read(&self, addr: u32, buf: &mut [u8]) {
self.generic_read(addr, buf, false, false)
}
}
/// If the Spansion QSPI is used in linear addressed mode, no IO operations are allowed.
@@ -618,10 +722,12 @@ pub struct QspiSpansionS25Fl256SLinearMode(QspiLinearAddressing);
impl QspiSpansionS25Fl256SLinearMode {
pub const BASE_ADDR: usize = QspiLinearAddressing::BASE_ADDRESS;
pub const PAGE_SIZE: usize = PAGE_SIZE;
pub const SECTOR_SIZE: usize = SECTOR_SIZE;
pub fn into_io_mode(self, dual_flash: bool) -> QspiSpansionS25Fl256SIoMode {
let qspi = self.0.into_io_mode(dual_flash);
QspiSpansionS25Fl256SIoMode::new(qspi, false)
QspiSpansionS25Fl256SIoMode::new(qspi, Config::default())
}
pub fn read_guard(&mut self) -> QspiLinearReadGuard<'_> {
+2 -2
View File
@@ -9,7 +9,7 @@ repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
license = "MIT OR Apache-2.0"
[dependencies]
aarch32-cpu = { version = "0.1", features = ["critical-section-single-core"] }
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../zynq7000-rt" }
zynq7000 = { path = "../zynq7000" }
zynq7000-hal = { path = "../zynq7000-hal" }
@@ -17,7 +17,7 @@ zynq7000-boot-image = { path = "../../host/zynq7000-boot-image" }
zedboard-bsp = { path = "../zedboard-bsp" }
embedded-io = "0.7"
embedded-hal = "1"
fugit = "0.3"
fugit = "0.4"
log = "0.4"
arbitrary-int = "2"
+10 -2
View File
@@ -1,7 +1,7 @@
MEMORY
{
/* The Zynq7000 has 192 kB of OCM memory which can be used for the FSBL */
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K
/* The Zynq7000 has 256 kB of OCM memory of which 196 kB can be used for the FSBL */
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 196K
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
/* Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This can
be used for something like DMA descriptors, but the DDR needs to be set up first in addition
@@ -11,6 +11,8 @@ MEMORY
REGION_ALIAS("VECTORS", CODE);
REGION_ALIAS("DATA", CODE);
/* Use the upper OCM as the stack */
REGION_ALIAS("STACKS", OCM_UPPER);
SECTIONS
{
@@ -23,3 +25,9 @@ SECTIONS
_ebss_uncached = .;
} > UNCACHED
}
PROVIDE(_und_stack_size = 2K);
PROVIDE(_svc_stack_size = 2K);
PROVIDE(_abt_stack_size = 2K);
PROVIDE(_hyp_stack_size = 2K);
PROVIDE(_sys_stack_size = 32K);
+47 -16
View File
@@ -8,14 +8,15 @@
#![no_std]
#![no_main]
use arbitrary_int::u6;
use core::panic::PanicInfo;
use aarch32_cpu::asm::nop;
use arbitrary_int::traits::Integer as _;
use arbitrary_int::{u2, u6};
use core::panic::PanicInfo;
use embedded_io::Write as _;
use log::{error, info};
use zedboard_bsp::qspi_spansion::{self, QspiSpansionS25Fl256SLinearMode};
use zynq7000_boot_image::DestinationDevice;
use zynq7000_hal::priv_tim;
use zynq7000_hal::clocks::ArmClocks;
use zynq7000_hal::{
BootMode,
clocks::{
@@ -23,12 +24,13 @@ use zynq7000_hal::{
pll::{PllConfig, configure_arm_pll, configure_io_pll},
},
ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest},
devcfg, gic, gpio, l2_cache,
gic, gpio, l2_cache,
prelude::*,
qspi::{self, QSPI_START_ADDRESS},
time::Hertz,
uart::{ClockConfig, Config, Uart},
};
use zynq7000_hal::{generic_interrupt_handler, priv_tim};
// PS clock input frequency.
const PS_CLK: Hertz = Hertz::from_raw(33_333_333);
@@ -42,7 +44,7 @@ const IO_CLK: Hertz = Hertz::from_raw(1_000_000_000);
const DDR_FREQUENCY: Hertz = Hertz::from_raw(533_333_333);
/// 1067 MHz.
const DDR_CLK: Hertz = Hertz::from_raw(2 * DDR_FREQUENCY.raw());
const DDR_CLK: Hertz = Hertz::from_raw(2 * DDR_FREQUENCY.to_raw());
const PERFORM_DDR_MEMTEST: bool = false;
@@ -74,6 +76,20 @@ fn main() -> ! {
);
let mut periphs = zynq7000::Peripherals::take().unwrap();
l2_cache::disable();
// Initialize the ARM clock. Safety: We only run this once.
unsafe {
ArmClocks::new_with_cpu_clock_init(
ARM_CLK,
zynq7000_hal::clocks::CpuClockRatio::SixToTwoToOne,
u6::new(2),
);
// This is done by the AMD FSBL.
zynq7000_hal::Slcr::with(|val| {
val.gpiob().modify_ctrl(|val| val.with_vref_en(true));
});
}
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLK).unwrap();
@@ -93,17 +109,10 @@ fn main() -> ! {
logger_uart
.write_all(b"-- Zedboard Rust FSBL --\n\r")
.unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
logger_uart,
log::LevelFilter::Trace,
false,
)
};
zynq7000_hal::log::uart_blocking::init_with_busy_flag(logger_uart, log::LevelFilter::Trace, true);
// Set up the global interrupt controller.
let mut gic = gic::GicConfigurator::new_with_init(periphs.gicc, periphs.gicd);
let mut gic = gic::Configurator::new_with_init(periphs.gicc, periphs.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
@@ -162,11 +171,19 @@ fn main() -> ! {
);
let qspi_io_mode = qspi.into_io_mode(false);
let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(
qspi_io_mode,
qspi_spansion::Config {
set_quad_bit_if_necessary: true,
latency_config: Some(u2::ZERO),
clear_write_protection: true,
},
);
let spansion_lqspi =
spansion_qspi.into_linear_addressed(qspi_spansion::QSPI_DEV_COMBINATION_REV_F.into());
qspi_boot(spansion_lqspi, priv_tim);
}
loop {
aarch32_cpu::asm::nop();
}
@@ -256,7 +273,7 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
};
// The DMA will read from the linear mapped QSPI directly, so it
// has to be configured for reads using the guard!
devcfg::configure_bitstream_non_secure(true, boot_bin_slice)
zynq7000_hal::pl::configure_bitstream_non_secure(true, boot_bin_slice)
.expect("unexpected unaligned address");
log::info!("loaded bitstream successfully");
}
@@ -304,6 +321,10 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
}
}
// The PL is in reset state after power-up. This method needs to be called in the first-stage
// bootloader to put it out of reset.
zynq7000_hal::pl::deassert_reset();
match opt_jump_addr {
Some(jump_addr) => {
log::info!("jumping to address {}", jump_addr);
@@ -313,6 +334,7 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
zynq7000_hal::cache::clean_and_invalidate_data_cache();
aarch32_cpu::register::TlbIAll::write();
aarch32_cpu::register::BpIAll::write();
l2_cache::disable();
aarch32_cpu::asm::dsb();
aarch32_cpu::asm::isb();
@@ -323,6 +345,15 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {
// Safety: Called here once.
let result = unsafe { generic_interrupt_handler() };
if let Err(e) = result {
log::warn!("Generic interrupt handler failed handling {:?}", e);
}
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
@@ -0,0 +1 @@
/boot.bin
+2 -1
View File
@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
aarch32-cpu = { version = "0.1", features = ["critical-section-single-core"] }
aarch32-cpu = { version = "0.2", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../zynq7000-rt" }
zynq7000 = { path = "../zynq7000" }
zynq7000-hal = { path = "../zynq7000-hal" }
@@ -12,5 +12,6 @@ zynq7000-boot-image = { path = "../../host/zynq7000-boot-image" }
zedboard-bsp = { path = "../zedboard-bsp" }
embedded-io = "0.7"
embedded-hal = "1"
arbitrary-int = "2"
log = "0.4"
libm = "0.2"
+14 -1
View File
@@ -3,4 +3,17 @@ Zedboard QSPI flasher
This application flashes a boot binary generated by the AMD `bootgen` utility from DDR
to the Zedboard QSPI. This project contains a `qspi-flasher.tcl` script which can be invoked
with `xsct` to flash a `boot.bin` and the QSPI flasher to DDR adn then run the application.
with `xsct` to flash a `boot.bin` and the QSPI flasher to DDR and then run the application.
The main `justfile` provides a convenience runner:
```sh
just flash-nor-zedboard <path to my boot.bin>
```
Please note that `xsct` must be callable for this to be usable which is part of a Xilinx Vitis installation.
If the hardware server is running on a remote target the IP address can be specified by setting the environment variable ip_address_hw_server.
````sh
$ export ip_address_hw_server=<ip-address>
````
+6 -4
View File
@@ -1,9 +1,11 @@
MEMORY
{
/* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app.
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
recommended for something like DMA descriptors. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
1 MB stack memory and 1 MB of memory which will be configured as uncached device memory by the
MMU. This is recommended for something like DMA descriptors. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
}
+17 -17
View File
@@ -4,6 +4,7 @@
#![no_main]
use aarch32_cpu::asm::nop;
use arbitrary_int::{traits::Integer as _, u2};
use core::panic::PanicInfo;
use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _};
use embedded_io::Write as _;
@@ -64,14 +65,7 @@ fn main() -> ! {
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Info,
false,
)
};
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Info, true);
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
@@ -97,7 +91,14 @@ fn main() -> ! {
let qspi_io_mode = qspi.into_io_mode(false);
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(
qspi_io_mode,
qspi_spansion::Config {
set_quad_bit_if_necessary: true,
latency_config: Some(u2::ZERO),
clear_write_protection: true,
},
);
let mut boot_bin_slice = unsafe {
core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, BootHeader::FIXED_SIZED_PART)
@@ -121,7 +122,7 @@ fn main() -> ! {
);
let mut current_addr = 0;
let mut read_buf = [0u8; 256];
let mut read_buf = [0u8; qspi_spansion::PAGE_SIZE];
let mut next_checkpoint = 0.05;
while current_addr < boot_bin_size {
if current_addr % 0x10000 == 0 {
@@ -137,10 +138,13 @@ fn main() -> ! {
}
}
}
let write_size = core::cmp::min(256, boot_bin_size - current_addr);
let write_size = core::cmp::min(
qspi_spansion::RECOMMENDED_PROGRAM_PAGE_SIZE,
boot_bin_size - current_addr,
);
let write_slice = &boot_bin_slice[current_addr..current_addr + write_size];
log::debug!("Programming address {:#x}", current_addr);
match spansion_qspi.program_page(current_addr as u32, write_slice) {
match spansion_qspi.program(current_addr as u32, write_slice) {
Ok(()) => {}
Err(e) => {
log::error!(
@@ -152,11 +156,7 @@ fn main() -> ! {
}
}
if VERIFY_PROGRAMMING {
spansion_qspi.read_page_fast_read(
current_addr as u32,
&mut read_buf[0..write_size],
true,
);
spansion_qspi.read_fast_read(current_addr as u32, &mut read_buf[0..write_size], true);
if &read_buf[0..write_size] != write_slice {
error!(
"data verification failed at address {:#x}: wrote {:x?}, read {:x?}",
+7 -2
View File
@@ -8,9 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.1.0] 2025-10-09
# [v0.1.1] 2026-03-13
- Try to fix docs build for docs.rs
# [v0.1.0] 2026-02-14
Initial release
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/v0.1.0...HEAD
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-embassy-v0.1.0...HEAD
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-embassy-v0.1.0...zynq7000-embassy-v0.1.1
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/tag/zynq7000-embassy-v0.1.0
+6 -2
View File
@@ -1,9 +1,9 @@
[package]
name = "zynq7000-embassy"
version = "0.1.0"
version = "0.1.1"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "Embassy-rs support for the Zynq7000 family of SoCs"
description = "Embassy time support for the Zynq7000 family of SoCs"
homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
license = "MIT OR Apache-2.0"
@@ -17,3 +17,7 @@ zynq7000-hal = { path = "../zynq7000-hal", version = "0.1" }
embassy-time-driver = "0.2"
embassy-time-queue-utils = "0.3"
[package.metadata.docs.rs]
targets = ["armv7a-none-eabihf"]
rustdoc-args = ["--generate-link-to-definition"]
+4 -5
View File
@@ -2,11 +2,10 @@
[![docs.rs](https://img.shields.io/docsrs/zynq7000-embassy)](https://docs.rs/zynq7000-embassy)
[![ci](https://github.com/us-irs/zynq7000-rs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/us-irs/zynq7000-rs/actions/workflows/ci.yml)
# Embassy-rs support for the AMD Zynq7000 SoC family
# Embassy time support for the AMD Zynq7000 SoC family
This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the
AMD Zynq7000 SoC family. Currently, it contains the time driver to allow using embassy-rs. It
currently provides one driver using the global timer peripheral provided by the Zynq7000 PS for
this purpose.
This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) time support for
the AMD Zynq7000 SoC family. It currently provides one driver using the global timer peripheral
provided by the Zynq7000 PS for this purpose.
The documentation contains more information on how to use this crate.
+18 -1
View File
@@ -1,3 +1,10 @@
//! # Embassy time support for the AMD Zynq7000 SoC family
//!
//! This project contains the [embassy-rs](https://github.com/embassy-rs/embassy) time support for
//! the AMD Zynq7000 SoC family. It currently provides one driver using the global timer peripheral
//! provided by the Zynq7000 PS for this purpose.
//!
//! The [crate::init] method must be called once for the time driver to work properly.
#![no_std]
use core::cell::{Cell, RefCell};
@@ -59,9 +66,19 @@ impl GtcTimerDriver {
///
/// This has to be called ONCE at system initialization.
pub unsafe fn init(&'static self, arm_clock: &ArmClocks, mut gtc: GlobalTimerCounter) {
fn safe_interrupt_handler() {
// Safety: See safety notes of [zynq7000_hal::generic_interrupt_handler].
unsafe {
on_interrupt();
}
}
zynq7000_hal::register_interrupt(
zynq7000_hal::gic::Interrupt::Ppi(zynq7000_hal::gic::PpiInterrupt::GlobalTimer),
safe_interrupt_handler,
);
CPU_3X2X_CLK.set(arm_clock.cpu_3x2x_clk()).unwrap();
SCALE
.set(arm_clock.cpu_3x2x_clk().raw() as u64 / TICK_HZ)
.set(arm_clock.cpu_3x2x_clk().to_raw() as u64 / TICK_HZ)
.unwrap();
gtc.set_cpu_3x2x_clock(arm_clock.cpu_3x2x_clk());
gtc.set_prescaler(0);
+30 -3
View File
@@ -8,13 +8,40 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
## Changed
## Fixed
- Increased UART type safety by providing dedicated MIO constructors for UART 0 and UART 1
respectively.
- Bugfix for DDR initialization: `calibrate_iob_impedance_for_ddr3` and `calibrate_iob_impedance`
now expect a `zynq7000::slcr::ddriob::DdrControl` input argument. This register write was
missing
- Several bugfixes and improvements for GIC module. Some of the registers previously were
completely overwritten instead of only modifying their own bit portions. Also allow targeting
interrupts without clearing other CPU target.
- Do not reset the UART on TX future creation anymore, which lead to glitches and invalid data.
- Robustness improvements for the asynchronous UART TX module.
- SPI1 AMBA clock control bits are now enabled and disabled properly
## Changed
- Increased reliabily of PS UART interrupt reception, which was proven to be buggy for higher baud
rates: Force user to configure RTO value, encouraging non-zero values, and use a RX FIFO trigger
value of FIFO depth divided by 2 by default.
- `devcfg` moved to `pl` module
- Added division by zero check in gtc frequency_to_ticks to avoid runtime panic
- Increased UART type safety by providing dedicated MIO constructors for UART 0 and UART 1
respectively.
- `log::rb` module replaced by `log::asynch` module which uses an asynchronous embassy pipe
for logging.
- GIC data structures: Removed the `Gic` prefix which already is part of the module name.
- Renamed `GicInterruptHelper` to `InterruptGuard`. It acknowledges the end of interrupts on drop.
## Added
- Method to de-assert PL reset.
- ARM clock initialization for the `ArmClocks` structure
- The `ArmClocks` structure now caches the CPU clock ratio
- New generic interrupt registry and generic interrupt handler which uses the registry.
Primary interface is the `crate::generic_interrupt_handler` function and the
`crate::register_interrupt` function.
# [v0.1.1] 2025-10-10
+11 -12
View File
@@ -11,17 +11,15 @@ keywords = ["no-std", "hal", "amd", "zynq7000", "bare-metal"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
aarch32-cpu = { version = "0.1" }
zynq7000 = { path = "../zynq7000", version = "0.1" }
zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.1" }
aarch32-cpu = { version = "0.3" }
zynq7000 = { path = "../zynq7000", version = "0.4" }
zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.2" }
static_assertions = "1.1"
bitbybit = "1.4"
bitbybit = "2"
arbitrary-int = "2"
thiserror = { version = "2", default-features = false }
num_enum = { version = "0.7", default-features = false }
bitflags = "2"
ringbuf = { version = "0.4.8", default-features = false }
embedded-hal-nb = "1"
embedded-io = "0.7"
embedded-hal = "1"
@@ -29,25 +27,27 @@ embedded-hal-async = "1"
heapless = "0.9"
static_cell = "2"
delegate = "0.13"
paste = "1"
pastey = "0.2.1"
nb = "1"
fugit = "0.3"
fugit = "0.4"
critical-section = "1"
libm = "0.2"
log = "0.4"
embassy-sync = "0.7"
embassy-sync = "0.8"
embassy-net-driver = "0.2"
smoltcp = { version = "0.12", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
smoltcp = { version = "0.13", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
vcell = "0.1"
raw-slicee = "0.1"
embedded-io-async = "0.7"
serde = { version = "1", optional = true, features = ["derive"] }
defmt = { version = "1", optional = true }
embedded-sdmmc = { git = "https://github.com/robamu/embedded-sdmmc-rs.git", branch = "all-features" }
bytemuck = "1.25"
[features]
std = ["thiserror/std", "alloc"]
alloc = []
defmt = ["dep:defmt", "fugit/defmt"]
defmt = ["dep:defmt", "fugit/defmt", "zynq7000/defmt"]
# These devices have a lower pin count.
7z010-7z007s-clg225 = []
@@ -57,5 +57,4 @@ approx = "0.5"
[package.metadata.docs.rs]
features = ["alloc"]
targets = ["armv7a-none-eabihf"]
cargo-args = ["-Z", "build-std=core,alloc"]
rustdoc-args = ["--generate-link-to-definition"]
+2 -2
View File
@@ -5,12 +5,12 @@
# HAL for the AMD Zynq 7000 SoC family
This repository contains the **H**ardware **A**bstraction **L**ayer (HAL), which is an additional
hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000).
hardware abstraction on top of the [peripheral access API](../zynq7000).
It is the result of reading the datasheet for the device and encoding a type-safe layer over the
raw PAC. This crate also implements traits specified by the
[embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
various drivers in the embedded rust ecosystem.
The [top-level README](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs) and the documentation
The [top-level README](../../README.md) and the documentation
contain more information on how to use this crate.
+69 -5
View File
@@ -3,11 +3,12 @@ use arbitrary_int::{prelude::*, u6};
pub mod pll;
pub use zynq7000::slcr::clocks::CpuClockRatio;
use zynq7000::slcr::{
ClockControlRegisters,
clocks::{
ClockkRatioSelect, DualCommonPeriphIoClockControl, FpgaClockControl, GigEthClockControl,
SingleCommonPeriphIoClockControl,
ArmClockControl, ClockRatioSelectReg, DualCommonPeriphIoClockControl, FpgaClockControl,
GigEthClockControl, SingleCommonPeriphIoClockControl,
},
};
@@ -17,6 +18,7 @@ use super::time::Hertz;
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ArmClocks {
ref_clk: Hertz,
ratio: CpuClockRatio,
cpu_1x_clk: Hertz,
cpu_2x_clk: Hertz,
cpu_3x2x_clk: Hertz,
@@ -24,23 +26,82 @@ pub struct ArmClocks {
}
impl ArmClocks {
/// Configure the ARM clocks based on the ARM PLL input clock.
///
/// # Safety
///
/// This changes the CPU clock frequency. You must pass the ARM PLL clock frequency and
/// you must ensure that this is only run once during system initialization, for example
/// in the first-stage bootloader.
pub unsafe fn new_with_cpu_clock_init(
arm_pll_clk: Hertz,
clock_ratio: CpuClockRatio,
divisor: u6,
) -> Self {
unsafe {
crate::slcr::Slcr::with(|slcr| {
slcr.clk_ctrl().write_clk_ratio_select(
ClockRatioSelectReg::builder().with_sel(clock_ratio).build(),
);
slcr.clk_ctrl().write_arm_clk_ctrl(
ArmClockControl::builder()
.with_cpu_peri_clk_act(true)
.with_cpu_1x_clk_act(true)
.with_cpu_2x_clk_act(true)
.with_cpu_3or2x_clk_act(true)
.with_cpu_6or4x_clk_act(true)
.with_divisor(divisor)
.with_srcsel(zynq7000::slcr::clocks::SrcSelArm::ArmPll)
.build(),
);
});
}
let cpu_6x4x_clk = arm_pll_clk / divisor.as_u32();
let cpu_1x_clk = match clock_ratio {
CpuClockRatio::FourToTwoToOne => cpu_6x4x_clk / 4,
CpuClockRatio::SixToTwoToOne => cpu_6x4x_clk / 6,
};
Self {
ref_clk: arm_pll_clk,
ratio: clock_ratio,
cpu_1x_clk,
cpu_2x_clk: cpu_1x_clk * 2,
cpu_3x2x_clk: match clock_ratio {
CpuClockRatio::SixToTwoToOne => cpu_1x_clk * 3,
CpuClockRatio::FourToTwoToOne => cpu_1x_clk * 2,
},
cpu_6x4x_clk,
}
}
#[inline]
pub const fn ratio(&self) -> CpuClockRatio {
self.ratio
}
/// Reference clock provided by ARM PLL which is used to calculate all other clock frequencies.
#[inline]
pub const fn ref_clk(&self) -> Hertz {
self.ref_clk
}
#[inline]
pub const fn cpu_1x_clk(&self) -> Hertz {
self.cpu_1x_clk
}
#[inline]
pub const fn cpu_2x_clk(&self) -> Hertz {
self.cpu_2x_clk
}
#[inline]
pub const fn cpu_3x2x_clk(&self) -> Hertz {
self.cpu_3x2x_clk
}
#[inline]
pub const fn cpu_6x4x_clk(&self) -> Hertz {
self.cpu_6x4x_clk
}
@@ -197,6 +258,7 @@ impl IoClocks {
}
}
// TODO: Display impl for clock config.
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Clocks {
@@ -277,25 +339,27 @@ impl Clocks {
zynq7000::slcr::clocks::SrcSelArm::DdrPll => ddr_pll_out,
zynq7000::slcr::clocks::SrcSelArm::IoPll => io_pll_out,
};
let clk_sel = clk_regs.read_clk_621_true();
let clk_sel = clk_regs.read_clk_ratio_select();
if arm_clk_ctrl.divisor().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Arm)));
}
let arm_clk_divided = arm_base_clk / arm_clk_ctrl.divisor().as_u32();
let arm_clks = match clk_sel.sel() {
ClockkRatioSelect::FourToTwoToOne => ArmClocks {
CpuClockRatio::FourToTwoToOne => ArmClocks {
ref_clk: arm_pll_out,
cpu_1x_clk: arm_clk_divided / 4,
cpu_2x_clk: arm_clk_divided / 2,
cpu_3x2x_clk: arm_clk_divided / 2,
cpu_6x4x_clk: arm_clk_divided,
ratio: clk_sel.sel(),
},
ClockkRatioSelect::SixToTwoToOne => ArmClocks {
CpuClockRatio::SixToTwoToOne => ArmClocks {
ref_clk: arm_pll_out,
cpu_1x_clk: arm_clk_divided / 6,
cpu_2x_clk: arm_clk_divided / 3,
cpu_3x2x_clk: arm_clk_divided / 2,
cpu_6x4x_clk: arm_clk_divided,
ratio: clk_sel.sel(),
},
};
+6 -6
View File
@@ -38,7 +38,7 @@ impl PllConfig {
ps_clk: Hertz,
target_clk: Hertz,
) -> Result<Self, PllConfigCtorError> {
if ps_clk.raw() == 0 {
if ps_clk.to_raw() == 0 {
return Err(PllConfigCtorError::InvalidInput);
}
let mul = target_clk / ps_clk;
@@ -196,7 +196,7 @@ impl PllConfig {
/// This function configures the ARM PLL based on the provided [PllConfig].
pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) {
if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::Relaxed) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
@@ -205,20 +205,20 @@ pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) {
/// This function configures the IO PLL based on the provided [PllConfig].
pub fn configure_io_pll(boot_mode: BootMode, pll_config: PllConfig) {
if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::Relaxed) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
unsafe { configure_io_pll_unchecked(boot_mode, pll_config) };
}
/// This function configures the DDR PLL based on the provided [PllConfig].
pub fn configure_ddr_pll(boot_mode: BootMode, pll_config: PllConfig) {
if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::Relaxed) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
unsafe { configure_ddr_pll_unchecked(boot_mode, pll_config) };
}
/// This function configures the ARM PLL based on the provided [PllConfig].
+44 -32
View File
@@ -28,7 +28,7 @@ pub fn calculate_dci_divisors(ddr_clks: &DdrClocks) -> DciClkConfig {
/// Calculate the required DCI divisors for the given DDR clock frequency.
pub fn calculate_dci_divisors_with_ddr_clk(ddr_clk: Hertz) -> DciClkConfig {
let target_div = ddr_clk.raw().div_ceil(DCI_MAX_FREQ.raw());
let target_div = ddr_clk.to_raw().div_ceil(DCI_MAX_FREQ.to_raw());
let mut config = DciClkConfig {
div0: u6::new(u6::MAX.value()),
div1: u6::new(u6::MAX.value()),
@@ -86,20 +86,42 @@ pub unsafe fn configure_dci(ddr_clk: &DdrClocks) {
///
/// This function writes to the DDR IOB related registers. It should only be called once during
/// DDR initialization.
pub unsafe fn calibrate_iob_impedance_for_ddr3(dci_clk_cfg: DciClkConfig, poll_for_done: bool) {
pub unsafe fn calibrate_iob_impedance_for_ddr3(
ddr_control: zynq7000::slcr::ddriob::DdrControl,
dci_clk_cfg: DciClkConfig,
poll_for_done: bool,
) {
unsafe {
calibrate_iob_impedance(
ddr_control,
dci_clk_cfg,
u3::new(0),
u2::new(0),
u3::new(0b001),
u3::new(0),
u2::new(0),
CalibrationParams::new_ddr3(),
poll_for_done,
);
}
}
/// DDR IOB impedance calibration parameters.
pub struct CalibrationParams {
pub pref_opt2: u3,
pub pref_opt1: u2,
pub nref_opt4: u3,
pub nref_opt2: u3,
pub nref_opt1: u2,
}
impl CalibrationParams {
pub const fn new_ddr3() -> Self {
Self {
pref_opt2: u3::new(0),
pref_opt1: u2::new(0),
nref_opt4: u3::new(0b001),
nref_opt2: u3::new(0),
nref_opt1: u2::new(0),
}
}
}
/// Calibrates the IOB impedance according to to TRM p.325, DDR IOB Impedance calibration.
///
/// This function will also enable the DCI clock with the provided clock configuration.
@@ -115,12 +137,9 @@ pub unsafe fn calibrate_iob_impedance_for_ddr3(dci_clk_cfg: DciClkConfig, poll_f
/// This function writes to the DDR IOB related registers. It should only be called once during
/// DDR initialization.
pub unsafe fn calibrate_iob_impedance(
ddr_control: zynq7000::slcr::ddriob::DdrControl,
dci_clk_cfg: DciClkConfig,
pref_opt2: u3,
pref_opt1: u2,
nref_opt4: u3,
nref_opt2: u3,
nref_opt1: u2,
calibration_params: CalibrationParams,
poll_for_done: bool,
) {
// Safety: Only writes to DDR IOB related registers.
@@ -134,31 +153,23 @@ pub unsafe fn calibrate_iob_impedance(
.build(),
);
let mut ddriob = slcr.ddriob();
ddriob.modify_dci_ctrl(|mut val| {
val.set_reset(true);
ddriob.write_ddr_control(ddr_control);
ddriob.modify_dci_control(|val| val.with_reset(true));
ddriob.modify_dci_control(|val| val.with_reset(false));
ddriob.modify_dci_control(|val| val.with_reset(true));
ddriob.modify_dci_control(|mut val| {
val.set_pref_opt2(calibration_params.pref_opt2);
val.set_pref_opt1(calibration_params.pref_opt1);
val.set_nref_opt4(calibration_params.nref_opt4);
val.set_nref_opt2(calibration_params.nref_opt2);
val.set_nref_opt1(calibration_params.nref_opt1);
val
});
ddriob.modify_dci_ctrl(|mut val| {
val.set_reset(false);
val
});
ddriob.modify_dci_ctrl(|mut val| {
val.set_reset(true);
val
});
ddriob.modify_dci_ctrl(|mut val| {
val.set_pref_opt2(pref_opt2);
val.set_pref_opt1(pref_opt1);
val.set_nref_opt4(nref_opt4);
val.set_nref_opt2(nref_opt2);
val.set_nref_opt1(nref_opt1);
val
});
ddriob.modify_dci_ctrl(|mut val| {
ddriob.modify_dci_control(|mut val| {
val.set_update_control(false);
val
});
ddriob.modify_dci_ctrl(|mut val| {
ddriob.modify_dci_control(|mut val| {
val.set_enable(true);
val
});
@@ -170,6 +181,7 @@ pub unsafe fn calibrate_iob_impedance(
/// Static configuration for DDR IOBs.
pub struct DdriobConfigSet {
pub ddr_control: zynq7000::slcr::ddriob::DdrControl,
pub addr0: DdriobConfig,
pub addr1: DdriobConfig,
pub data0: DdriobConfig,
+2 -2
View File
@@ -76,7 +76,7 @@ pub fn configure_ddr_for_ddr3(
ll::configure_iob(ddriob_cfg);
// Do not wait for completion, it takes a bit of time. We can set all the DDR config registers
// before polling for completion.
ll::calibrate_iob_impedance_for_ddr3(dci_clk_cfg, false);
ll::calibrate_iob_impedance_for_ddr3(ddriob_cfg.ddr_control, dci_clk_cfg, false);
}
ll::configure_ddr_config(&mut ddrc_regs, ddr_cfg);
// Safety: This is only called once during DDR initialization, and we only modify DDR related
@@ -129,7 +129,7 @@ pub mod memtest {
/// This tests writes and reads on a memory block starting at the base address
/// with the size `words` times 4.
pub unsafe fn walking_one_test(base_addr: usize, words: usize) -> Result<(), MemTestError> {
unsafe { walking_value_test(true, base_addr, words) }
unsafe { walking_value_test(false, base_addr, words) }
}
/// # Safety
+47 -35
View File
@@ -8,11 +8,6 @@ use crate::{clocks::IoClocks, enable_amba_peripheral_clock, slcr::Slcr, time::He
use super::{EthernetId, PsEthernet as _};
pub struct EthernetLowLevel {
id: EthernetId,
pub regs: zynq7000::eth::MmioRegisters<'static>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Speed {
Mbps10,
@@ -52,7 +47,10 @@ impl ClockDivisors {
/// Calls [Self::calculate_for_rgmii], assuming that the IO clock is the reference clock,
/// which is the default clock for the Ethernet module.
pub fn calculate_for_rgmii_and_io_clock(io_clks: IoClocks, target_speed: Speed) -> (Self, u32) {
pub fn calculate_for_rgmii_and_io_clock(
io_clks: &IoClocks,
target_speed: Speed,
) -> (Self, u32) {
Self::calculate_for_rgmii(io_clks.ref_clk(), target_speed)
}
@@ -71,8 +69,8 @@ impl ClockDivisors {
let mut best_div_1 = u6::new(0);
for div_1 in 1..=u6::MAX.as_usize() {
for div_0 in 1..=u6::MAX.as_usize() {
let clk_rate = ref_clk.raw() / div_0 as u32 / div_1 as u32;
let diff = (target_speed.raw() as i64 - clk_rate as i64).unsigned_abs() as u32;
let clk_rate = ref_clk.to_raw() / div_0 as u32 / div_1 as u32;
let diff = (target_speed.to_raw() as i64 - clk_rate as i64).unsigned_abs() as u32;
if diff < smallest_diff {
smallest_diff = diff;
best_div_0 = u6::new(div_0 as u8);
@@ -174,8 +172,17 @@ impl ClockDivSet {
/// Ethernet low-level interface.
///
/// Basic building block for higher-level abstraction.
pub struct EthernetLowLevel {
id: EthernetId,
/// Register block. Direct public access is allowed to allow low-level operations.
pub regs: zynq7000::eth::MmioRegisters<'static>,
}
impl EthernetLowLevel {
/// Creates a new instance of the Ethernet low-level interface.
///
/// Returns [None] if the given registers block base address does not correspond to a valid
/// Ethernet peripheral.
#[inline]
pub fn new(regs: zynq7000::eth::MmioRegisters<'static>) -> Option<Self> {
regs.id()?;
@@ -204,33 +211,7 @@ impl EthernetLowLevel {
}
pub fn reset(&mut self, cycles: usize) {
let assert_reset = match self.id {
EthernetId::Eth0 => EthernetReset::builder()
.with_gem1_ref_rst(false)
.with_gem0_ref_rst(true)
.with_gem1_rx_rst(false)
.with_gem0_rx_rst(true)
.with_gem1_cpu1x_rst(false)
.with_gem0_cpu1x_rst(true)
.build(),
EthernetId::Eth1 => EthernetReset::builder()
.with_gem1_ref_rst(true)
.with_gem0_ref_rst(false)
.with_gem1_rx_rst(true)
.with_gem0_rx_rst(false)
.with_gem1_cpu1x_rst(true)
.with_gem0_cpu1x_rst(false)
.build(),
};
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl().write_eth(assert_reset);
for _ in 0..cycles {
aarch32_cpu::asm::nop();
}
regs.reset_ctrl().write_eth(EthernetReset::DEFAULT);
});
}
reset(self.id, cycles);
}
#[inline]
@@ -383,3 +364,34 @@ impl EthernetLowLevel {
self.id
}
}
/// Resets the Ethernet peripheral with the given ID.
pub fn reset(id: EthernetId, cycles: usize) {
let assert_reset = match id {
EthernetId::Eth0 => EthernetReset::builder()
.with_gem1_ref_rst(false)
.with_gem0_ref_rst(true)
.with_gem1_rx_rst(false)
.with_gem0_rx_rst(true)
.with_gem1_cpu1x_rst(false)
.with_gem0_cpu1x_rst(true)
.build(),
EthernetId::Eth1 => EthernetReset::builder()
.with_gem1_ref_rst(true)
.with_gem0_ref_rst(false)
.with_gem1_rx_rst(true)
.with_gem0_rx_rst(false)
.with_gem1_cpu1x_rst(true)
.with_gem0_cpu1x_rst(false)
.build(),
};
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl().write_eth(assert_reset);
for _ in 0..cycles {
aarch32_cpu::asm::nop();
}
regs.reset_ctrl().write_eth(EthernetReset::DEFAULT);
});
}
}
+4 -1
View File
@@ -179,7 +179,10 @@ impl Eth1RxData3Pin for Pin<Mio38> {}
/// Calculate the CPU 1x clock divisor required to achieve a clock speed which is below
/// 2.5 MHz, as specified by the 802.3 standard.
pub fn calculate_mdc_clk_div(arm_clks: &ArmClocks) -> Option<MdcClockDivisor> {
let div = arm_clks.cpu_1x_clk().raw().div_ceil(MAX_MDC_SPEED.raw());
let div = arm_clks
.cpu_1x_clk()
.to_raw()
.div_ceil(MAX_MDC_SPEED.to_raw());
match div {
0..8 => Some(MdcClockDivisor::Div8),
8..16 => Some(MdcClockDivisor::Div16),
+148 -26
View File
@@ -1,7 +1,10 @@
//! # Global Interrupt Controller (GIC) module
//!
//! The primary interface to configure and allow handling the interrupts are the
//! [GicConfigurator] and the [GicInterruptHelper] structures.
//! [Configurator] and the [InterruptGuard] structures.
//!
//! The HAL provides a more convenient interface through the [crate::register_interrupt] and
//! [crate::generic_interrupt_handler] functions.
#![deny(missing_docs)]
use arbitrary_int::prelude::*;
@@ -9,7 +12,7 @@ use aarch32_cpu::interrupt;
use zynq7000::gic::{
CpuInterfaceRegisters, DistributorControlRegister, DistributorRegisters, InterfaceControl,
InterruptProcessorTargetRegister, InterruptSignalRegister, MmioCpuInterfaceRegisters,
MmioDistributorRegisters, PriorityRegister,
MmioDistributorRegisters, PriorityRegister, SoftwareGeneratedInterruptRegister,
};
/// Spurious interrupt ID.
@@ -85,8 +88,9 @@ bitflags::bitflags! {
}
/// Private Peripheral Interrupt (PPI) which are private to the CPU.
#[derive(Debug, Eq, PartialEq, Clone, Copy, num_enum::TryFromPrimitive)]
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, num_enum::TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum PpiInterrupt {
/// Global timer.
@@ -102,8 +106,9 @@ pub enum PpiInterrupt {
}
/// Shared Peripheral Interrupt IDs.
#[derive(Debug, Eq, PartialEq, Clone, Copy, num_enum::TryFromPrimitive)]
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, num_enum::TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum SpiInterrupt {
/// CPU 0.
@@ -230,12 +235,41 @@ pub enum SpiInterrupt {
ScuParity = 92,
}
/// Interrupt ID wrapper.
#[derive(Debug, Clone, Copy)]
/// SGI interrupt ID wrapper.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SgiInterrupt(usize);
impl SgiInterrupt {
/// Create a new SGI interrupt ID wrapper.
pub const fn new(int_id: usize) -> Result<Self, InvalidSgiInterruptId> {
if int_id >= 16 {
return Err(InvalidSgiInterruptId(int_id));
}
Ok(SgiInterrupt(int_id))
}
/// Returns the raw interrupt ID of the SGI.
#[inline]
pub const fn raw_id(&self) -> usize {
self.0
}
/// Returns the raw interrupt ID of the SGI as [u4].
#[inline]
pub const fn as_u4(&self) -> u4 {
u4::new(self.0 as u8)
}
}
/// Interrupt ID wrapper.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Interrupt {
/// Software-generated interrupt (SGI).
Sgi(usize),
Sgi(SgiInterrupt),
/// Private peripheral interrupt (PPI).
Ppi(PpiInterrupt),
/// Shared peripheral interrupt (SPI).
@@ -247,8 +281,9 @@ pub enum Interrupt {
}
/// Interrupt information structure.
#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InterruptInfo {
raw_reg: InterruptSignalRegister,
interrupt: Interrupt,
@@ -334,16 +369,16 @@ pub struct InvalidSgiInterruptId(pub usize);
/// with [Self::enable] which assumes a certain configuration.
/// 5. Enable interrupts for the Cortex-A core by calling [Self::enable_interrupts].
///
/// For the handling of the interrupts, you can use the [GicInterruptHelper] which assumes a
/// For the handling of the interrupts, you can use the [InterruptGuard] which assumes a
/// properly configured GIC.
pub struct GicConfigurator {
pub struct Configurator {
/// GIC CPU interface registers.
pub gicc: MmioCpuInterfaceRegisters<'static>,
/// GIC Distributor interface registers.
pub gicd: MmioDistributorRegisters<'static>,
}
impl GicConfigurator {
impl Configurator {
/// Create a new GIC controller instance and calls [Self::initialize] to perform
/// strongly recommended initialization routines for the GIC.
#[inline]
@@ -351,7 +386,7 @@ impl GicConfigurator {
gicc: MmioCpuInterfaceRegisters<'static>,
gicd: MmioDistributorRegisters<'static>,
) -> Self {
let mut gic = GicConfigurator { gicc, gicd };
let mut gic = Configurator { gicc, gicd };
gic.initialize();
gic
}
@@ -365,7 +400,7 @@ impl GicConfigurator {
/// used inside the interrupt handler.
#[inline]
pub unsafe fn steal() -> Self {
GicConfigurator {
Configurator {
gicc: unsafe { CpuInterfaceRegisters::new_mmio_fixed() },
gicd: unsafe { DistributorRegisters::new_mmio_fixed() },
}
@@ -491,7 +526,29 @@ impl GicConfigurator {
.unwrap();
}
/// Utility function to set all SGI interrupt targets to CPU0.
/// Set the interrupt priority for a SGI interrupt.
///
/// There are 32 priority levels, and a lower value means a higher priority.
#[inline]
pub fn set_sgi_interrupt_priority(&mut self, sgi: SgiInterrupt, priority: u5) {
// The IPR arrays are byte accessible.
let base_ptr = self.gicd.pointer_to_ipr_start() as *mut u8;
let raw_index = sgi.raw_id();
unsafe {
core::ptr::write_volatile(base_ptr.add(raw_index), priority.as_u8() << 3);
}
}
/// Read the interrupt priority for a SGI interrupt.
#[inline]
pub fn read_sgi_interrupt_priority(&mut self, sgi: SgiInterrupt) -> u5 {
// The IPR arrays are byte accessible.
let base_ptr = self.gicd.pointer_to_ipr_start() as *const u8;
let raw_index = sgi.raw_id();
unsafe { u5::new(core::ptr::read_volatile(base_ptr.add(raw_index)) >> 3) }
}
/// Utility function to set all SPI interrupt targets to CPU0.
///
/// This does not clear interrupt target bits for CPU1, it only activates the interrupts for
/// CPU 0 as well.
@@ -510,6 +567,25 @@ impl GicConfigurator {
}
}
/// Utility function to set all SGI interrupt targets to CPU0.
///
/// This does not clear interrupt target bits for CPU1, it only activates the interrupts for
/// CPU 0 as well.
/// This is useful if only CPU0 is active in a system, or if CPU0 handles most interrupts in
/// the system.
#[inline]
pub fn set_all_sgi_interrupt_targets_cpu0(&mut self) {
for i in 0..4 {
self.gicd
.modify_iptr_sgi(i, |v| {
InterruptProcessorTargetRegister::new_with_raw_value(
v.raw_value() | TARGETS_ALL_CPU_0_IPTR_VAL.raw_value(),
)
})
.unwrap();
}
}
/// Enable a specific SGI interrupt.
#[inline]
pub fn enable_sgi_interrupt(&mut self, int_id: usize) -> Result<(), InvalidSpiInterruptId> {
@@ -554,6 +630,19 @@ impl GicConfigurator {
})
};
}
/// Enable all PPI interrupts.
#[inline]
pub fn trigger_software_interrupt(&mut self, sgi_id: SgiInterrupt) {
self.gicd.write_sgir(
SoftwareGeneratedInterruptRegister::builder()
.with_target_list_filter(zynq7000::gic::TargetListFilter::SendToSelf)
.with_security_condition(zynq7000::gic::SecurityCondition::IfConfiguredAsSecure)
.with_sbz(u11::ZERO)
.with_cpu_target_list(0)
.with_interrupt_id(u4::new(sgi_id.raw_id() as u8))
.build(),
);
}
/// Enable specific SPI interrupt.
#[inline]
@@ -648,26 +737,42 @@ impl GicConfigurator {
}
/// Helper structure which should only be used inside the interrupt handler once the GIC has
/// been configured with the [GicConfigurator].
pub struct GicInterruptHelper(MmioCpuInterfaceRegisters<'static>);
/// been configured with the [Configurator].
pub struct InterruptGuard {
regs: MmioCpuInterfaceRegisters<'static>,
interrupt_info: InterruptInfo,
acknowledged: bool,
}
impl GicInterruptHelper {
impl InterruptGuard {
/// Create the interrupt helper with the fixed GICC MMIO instance.
pub const fn new() -> Self {
GicInterruptHelper(unsafe { CpuInterfaceRegisters::new_mmio_fixed() })
pub fn new() -> Self {
let mut regs = unsafe { CpuInterfaceRegisters::new_mmio_fixed() };
let interrupt_info = Self::acknowledge_interrupt(&mut regs);
InterruptGuard {
regs: unsafe { CpuInterfaceRegisters::new_mmio_fixed() },
interrupt_info,
acknowledged: false,
}
}
/// Acknowledges an interrupt by reading the IAR register and returning the interrupt context
/// information structure.
///
/// This should be called at the start of an interrupt handler.
pub fn acknowledge_interrupt(&mut self) -> InterruptInfo {
let iar = self.0.read_iar();
fn acknowledge_interrupt(regs: &mut MmioCpuInterfaceRegisters<'static>) -> InterruptInfo {
let iar = regs.read_iar();
let int_id = iar.ack_int_id().as_u32();
let interrupt = match int_id {
0..=15 => Interrupt::Sgi(int_id as usize),
27..=31 => Interrupt::Ppi(PpiInterrupt::try_from(int_id as u8).unwrap()),
32..=92 => Interrupt::Spi(SpiInterrupt::try_from(int_id as u8).unwrap()),
0..=15 => Interrupt::Sgi(
SgiInterrupt::new(int_id as usize).expect("invalid SGI interrupt number"),
),
27..=31 => Interrupt::Ppi(
PpiInterrupt::try_from(int_id as u8).expect("invalid PPI interrupt number"),
),
32..=92 => Interrupt::Spi(
SpiInterrupt::try_from(int_id as u8).expect("invalid SPI interrupt number"),
),
SPURIOUS_INTERRUPT_ID => Interrupt::Spurious,
_ => Interrupt::Invalid(int_id as usize),
};
@@ -678,16 +783,33 @@ impl GicInterruptHelper {
}
}
/// Returns the interrupt information structure which was read from the IAR register.
///
/// This is used to determine the cause of the interrupt.
#[inline]
pub fn interrupt_info(&self) -> InterruptInfo {
self.interrupt_info
}
/// Acknowledges the end of an interrupt by writing the EOIR register of the GICC.
///
/// This should be called at the end of an interrupt handler.
pub fn end_of_interrupt(&mut self, irq_info: InterruptInfo) {
self.0.write_eoir(irq_info.raw_reg())
self.acknowledged = true;
self.regs.write_eoir(irq_info.raw_reg())
}
}
impl Default for GicInterruptHelper {
impl Default for InterruptGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for InterruptGuard {
fn drop(&mut self) {
if !self.acknowledged {
self.regs.write_eoir(self.interrupt_info.raw_reg());
}
}
}
+1 -1
View File
@@ -84,7 +84,7 @@ pub trait PinId {
macro_rules! pin_id {
($Id:ident, $num:literal) => {
// Need paste macro to use ident in doc attribute
paste::paste! {
pastey::paste! {
#[doc = "Pin ID representing pin " $Id]
#[derive(Debug)]
pub enum $Id {}
+14 -10
View File
@@ -18,7 +18,11 @@ unsafe impl Send for GlobalTimerCounter {}
/// Convert a frequency to GTC ticks given a clock frequency.
pub const fn frequency_to_ticks(clock: Hertz, frequency: Hertz) -> u32 {
clock.raw().div_ceil(frequency.raw())
if frequency.to_raw() != 0 {
clock.to_raw().div_ceil(frequency.to_raw())
} else {
0
}
}
impl GlobalTimerCounter {
@@ -71,13 +75,13 @@ impl GlobalTimerCounter {
/// Set the comparator which can be used to trigger an interrupt in the future.
#[inline]
pub fn set_comparator(&mut self, comparator: u64) {
self.regs.modify_ctrl(|mut ctrl| {
self.regs.modify_control(|mut ctrl| {
ctrl.set_comparator_enable(false);
ctrl
});
self.regs.write_comparator_upper((comparator >> 32) as u32);
self.regs.write_comparator_lower(comparator as u32);
self.regs.modify_ctrl(|mut ctrl| {
self.regs.modify_control(|mut ctrl| {
ctrl.set_comparator_enable(true);
ctrl
});
@@ -108,7 +112,7 @@ impl GlobalTimerCounter {
/// Enable the GTC.
#[inline]
pub fn enable(&mut self) {
self.regs.modify_ctrl(|mut ctrl| {
self.regs.modify_control(|mut ctrl| {
ctrl.set_enable(true);
ctrl
});
@@ -117,7 +121,7 @@ impl GlobalTimerCounter {
/// Enable auto-increment.
#[inline]
pub fn enable_auto_increment(&mut self) {
self.regs.modify_ctrl(|mut ctrl| {
self.regs.modify_control(|mut ctrl| {
ctrl.set_auto_increment(true);
ctrl
});
@@ -126,7 +130,7 @@ impl GlobalTimerCounter {
/// Set a pre-scaler.
#[inline]
pub fn set_prescaler(&mut self, prescaler: u8) {
self.regs.modify_ctrl(|mut ctrl| {
self.regs.modify_control(|mut ctrl| {
ctrl.set_prescaler(prescaler);
ctrl
});
@@ -135,7 +139,7 @@ impl GlobalTimerCounter {
/// Disable the GTC.
#[inline]
pub fn disable(&mut self) {
self.regs.modify_ctrl(|mut ctrl| {
self.regs.modify_control(|mut ctrl| {
ctrl.set_enable(false);
ctrl
});
@@ -144,7 +148,7 @@ impl GlobalTimerCounter {
/// Enable the comparator interrupt.
#[inline]
pub fn enable_interrupt(&mut self) {
self.regs.modify_ctrl(|mut ctrl| {
self.regs.modify_control(|mut ctrl| {
ctrl.set_irq_enable(true);
ctrl
});
@@ -153,7 +157,7 @@ impl GlobalTimerCounter {
/// Disable the comparator interrupt.
#[inline]
pub fn disable_interrupt(&mut self) {
self.regs.modify_ctrl(|mut ctrl| {
self.regs.modify_control(|mut ctrl| {
ctrl.set_irq_enable(false);
ctrl
});
@@ -167,7 +171,7 @@ impl embedded_hal::delay::DelayNs for GlobalTimerCounter {
return;
}
let end_of_delay = self.read_timer()
+ (((ns as u64) * self.cpu_3x2x_clock.unwrap().raw() as u64) / 1_000_000_000);
+ (((ns as u64) * self.cpu_3x2x_clock.unwrap().to_raw() as u64) / 1_000_000_000);
while self.read_timer() < end_of_delay {}
}
}
+29 -29
View File
@@ -266,7 +266,8 @@ pub fn calculate_divisors(
for divisor_a in 1..=4 {
for divisor_b in 1..=64 {
let i2c_clock = cpu_1x_clk / (22 * divisor_a * divisor_b);
let deviation = (target_speed.raw() as i32 - i2c_clock.raw() as i32).unsigned_abs();
let deviation =
(target_speed.to_raw() as i32 - i2c_clock.to_raw() as i32).unsigned_abs();
if deviation < smallest_deviation {
smallest_deviation = deviation;
best_div_a = divisor_a;
@@ -352,8 +353,7 @@ impl I2c {
I2cId::I2c1 => crate::PeriphSelect::I2c1,
};
enable_amba_peripheral_clock(periph_sel);
//reset(id);
regs.write_cr(
regs.write_control(
Control::builder()
.with_div_a(u2::new(clk_cfg.div_a()))
.with_div_b(u6::new(clk_cfg.div_b()))
@@ -378,7 +378,7 @@ impl I2c {
#[inline]
pub fn set_hold_bit(&mut self) {
self.regs.modify_cr(|mut cr| {
self.regs.modify_control(|mut cr| {
cr.set_hold_bus(true);
cr
});
@@ -386,7 +386,7 @@ impl I2c {
#[inline]
pub fn clear_hold_bit(&mut self) {
self.regs.modify_cr(|mut cr| {
self.regs.modify_control(|mut cr| {
cr.set_hold_bus(false);
cr
});
@@ -398,7 +398,7 @@ impl I2c {
data: &[u8],
generate_stop: bool,
) -> Result<(), I2cTxError> {
self.regs.modify_cr(|mut cr| {
self.regs.modify_control(|mut cr| {
cr.set_acken(true);
cr.set_mode(zynq7000::i2c::Mode::Master);
cr.set_clear_fifo(true);
@@ -412,7 +412,7 @@ impl I2c {
let mut addr_set = false;
let mut written = 0;
// Clear the interrupt status register before using it to monitor the transfer.
self.regs.modify_isr(|isr| isr);
self.regs.modify_interrupt_status(|isr| isr);
loop {
let bytes_to_write = core::cmp::min(
FIFO_DEPTH - self.regs.read_transfer_size().size() as usize,
@@ -429,13 +429,13 @@ impl I2c {
self.start_transfer(addr);
addr_set = true;
}
let mut status = self.regs.read_sr();
let mut status = self.regs.read_status();
// While the hardware is busy sending out data, we poll for errors.
while status.tx_busy() {
let isr = self.regs.read_isr();
let isr = self.regs.read_interrupt_status();
self.check_and_handle_tx_errors(isr, first_write_cycle, bytes_to_write)?;
// Re-read for next check.
status = self.regs.read_sr();
status = self.regs.read_status();
}
first_write_cycle = false;
// Just need to poll to completion now.
@@ -444,8 +444,8 @@ impl I2c {
}
}
// Poll to completion.
while !self.regs.read_isr().complete() {
let isr = self.regs.read_isr();
while !self.regs.read_interrupt_status().complete() {
let isr = self.regs.read_interrupt_status();
self.check_and_handle_tx_errors(isr, first_write_cycle, data.len())?;
}
if generate_stop {
@@ -489,7 +489,7 @@ impl I2c {
}
pub fn clean_up_after_transfer_or_on_error(&mut self) {
self.regs.modify_cr(|mut cr| {
self.regs.modify_control(|mut cr| {
cr.set_acken(false);
cr.set_clear_fifo(true);
cr
@@ -497,7 +497,7 @@ impl I2c {
}
pub fn read_transfer_blocking(&mut self, addr: u8, data: &mut [u8]) -> Result<(), I2cRxError> {
self.regs.modify_cr(|mut cr| {
self.regs.modify_control(|mut cr| {
cr.set_acken(true);
cr.set_mode(zynq7000::i2c::Mode::Master);
cr.set_clear_fifo(true);
@@ -512,23 +512,23 @@ impl I2c {
return Err(I2cRxError::ReadDataLenTooLarge);
}
// Clear the interrupt status register before using it to monitor the transfer.
self.regs.modify_isr(|isr| isr);
self.regs.modify_interrupt_status(|isr| isr);
self.regs
.write_transfer_size(TransferSize::new_with_raw_value(data.len() as u32));
self.start_transfer(addr);
loop {
let mut status = self.regs.read_sr();
let mut status = self.regs.read_status();
loop {
let isr = self.regs.read_isr();
let isr = self.regs.read_interrupt_status();
self.check_and_handle_rx_errors(read, isr)?;
if status.rx_valid() {
break;
}
// Re-read for next check.
status = self.regs.read_sr();
status = self.regs.read_status();
}
// Data to be read.
while self.regs.read_sr().rx_valid() {
while self.regs.read_status().rx_valid() {
data[read] = self.regs.read_data().data();
read += 1;
}
@@ -544,8 +544,8 @@ impl I2c {
}
// Poll to completion.
while !self.regs.read_isr().complete() {
let isr = self.regs.read_isr();
while !self.regs.read_interrupt_status().complete() {
let isr = self.regs.read_interrupt_status();
self.check_and_handle_rx_errors(read, isr)?
}
self.clear_hold_bit();
@@ -678,8 +678,8 @@ mod tests {
assert_eq!(clk_cfg.div_a(), 0);
assert_eq!(clk_cfg.div_b(), 55);
let speed = calculate_i2c_speed(111.MHz(), clk_cfg);
assert!(speed.raw() < 100_000);
assert!(speed.raw() > 85_000);
assert!(speed.to_raw() < 100_000);
assert!(speed.to_raw() > 85_000);
}
#[test]
@@ -688,8 +688,8 @@ mod tests {
assert_eq!(clk_cfg.div_a(), 0);
assert_eq!(clk_cfg.div_b(), 12);
let speed = calculate_i2c_speed(111.MHz(), clk_cfg);
assert!(speed.raw() < 400_000);
assert!(speed.raw() > 360_000);
assert!(speed.to_raw() < 400_000);
assert!(speed.to_raw() > 360_000);
}
#[test]
@@ -698,8 +698,8 @@ mod tests {
assert_eq!(clk_cfg.div_a(), 1);
assert_eq!(clk_cfg.div_b(), 33);
let speed = calculate_i2c_speed(133.MHz(), clk_cfg);
assert!(speed.raw() < 100_000);
assert!(speed.raw() > 85_000);
assert!(speed.to_raw() < 100_000);
assert!(speed.to_raw() > 85_000);
}
#[test]
@@ -708,7 +708,7 @@ mod tests {
assert_eq!(clk_cfg.div_a(), 0);
assert_eq!(clk_cfg.div_b(), 15);
let speed = calculate_i2c_speed(133.MHz(), clk_cfg);
assert!(speed.raw() < 400_000);
assert!(speed.raw() > 360_000);
assert!(speed.to_raw() < 400_000);
assert!(speed.to_raw() > 360_000);
}
}
+54
View File
@@ -0,0 +1,54 @@
use crate::gic::{Interrupt, InterruptGuard};
pub type InterruptMap = heapless::index_map::FnvIndexMap<Interrupt, unsafe fn(), 128>;
static INTERRUPT_MAP: critical_section::Mutex<core::cell::RefCell<InterruptMap>> =
critical_section::Mutex::new(core::cell::RefCell::new(
heapless::index_map::FnvIndexMap::new(),
));
/// Register an interrupt handler for a specific [Interrupt].
///
/// It should be noted that the current implementation only allows one function for each interrupts.
/// If the HAL provided interrupt handler does not fulfill all your requirements, you need
/// to define your own interrupt handler and register it.
/// For example, you might need to handle both UART RX and TX, and the HAL handler only handles TX.
pub fn register_interrupt(interrupt: Interrupt, handler: unsafe fn()) {
critical_section::with(|cs| {
let mut map = INTERRUPT_MAP.borrow(cs).borrow_mut();
map.insert(interrupt, handler).ok();
});
}
/// Generic interrupt handler which retrieves the interrupt handler for individual [Interrupt]s
/// from a registry and calls it.
///
/// If no interrupt was registered or the number is [Interrupt::Invalid], returns the [Interrupt]
/// ID as an error. In any case, the generic handler acknowledges the interrupt in the GIC.
///
/// # Safety
///
/// This needs to be called ONCE in the interrupt handler function, which is any function annotated
/// with the `irq` attribute provided by `aarch32-rt`.
pub unsafe fn generic_interrupt_handler() -> Result<(), Interrupt> {
let mut gic_guard = InterruptGuard::new();
let irq_info = gic_guard.interrupt_info();
let interrupt = irq_info.interrupt();
if let Interrupt::Invalid(_) = interrupt {
gic_guard.end_of_interrupt(irq_info);
return Err(interrupt);
}
let opt_interrupt_handler = critical_section::with(|cs| {
let map = INTERRUPT_MAP.borrow(cs).borrow_mut();
map.get(&interrupt).copied()
});
if let Some(interrupt_handler) = opt_interrupt_handler {
// Safety: The user made sure that this is only called once in the interrupt handler
// function.
unsafe {
interrupt_handler();
}
}
gic_guard.end_of_interrupt(irq_info);
opt_interrupt_handler.ok_or(interrupt)?;
Ok(())
}
+7
View File
@@ -81,3 +81,10 @@ pub fn init(
}
l2c_mmio.write_control(Control::new_enabled());
}
/// Disable the L2 cache.
#[inline]
pub fn disable() {
let mut l2c_mmio = unsafe { zynq7000::l2_cache::Registers::new_mmio_fixed() };
l2c_mmio.write_control(Control::new_disabled());
}
+10 -6
View File
@@ -10,7 +10,7 @@
//!
//! ## Examples
//!
//! All exaples can be found inside the [examples folder](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/examples)
//! All examples can be found inside the [examples folder](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/examples)
//! and [firmware folder](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware) of the project
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
@@ -18,7 +18,7 @@
#[cfg(feature = "alloc")]
extern crate alloc;
use slcr::Slcr;
pub use slcr::Slcr;
use zynq7000::{
SpiClockPhase, SpiClockPolarity,
slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister},
@@ -27,23 +27,27 @@ use zynq7000::{
pub mod cache;
pub mod clocks;
pub mod ddr;
pub mod devcfg;
pub mod eth;
pub mod gic;
pub mod gpio;
pub mod gtc;
pub mod i2c;
pub mod interrupt;
pub mod l2_cache;
pub mod log;
pub mod pl;
pub mod prelude;
pub mod priv_tim;
pub mod qspi;
pub mod sd;
pub mod slcr;
pub mod spi;
pub mod time;
pub mod ttc;
pub mod uart;
pub use gic::{Interrupt, PpiInterrupt, SpiInterrupt};
pub use interrupt::{generic_interrupt_handler, register_interrupt};
pub use zynq7000 as pac;
pub use zynq7000::slcr::LevelShifterConfig;
@@ -79,7 +83,7 @@ pub fn init(config: Config) -> Result<zynq7000::Peripherals, InitError> {
configure_level_shifter(config);
}
if let Some(interrupt_config) = config.interrupt_config {
let mut gic = gic::GicConfigurator::new_with_init(periphs.gicc, periphs.gicd);
let mut gic = gic::Configurator::new_with_init(periphs.gicc, periphs.gicd);
match interrupt_config {
InteruptConfig::AllInterruptsToCpu0 => {
gic.enable_all_interrupts();
@@ -207,7 +211,7 @@ pub fn enable_amba_peripheral_clock(select: PeriphSelect) {
PeriphSelect::Can1 => val.set_can_1_1x_clk_act(true),
PeriphSelect::Can0 => val.set_can_0_1x_clk_act(true),
PeriphSelect::Spi1 => val.set_spi_1_1x_clk_act(true),
PeriphSelect::Spi0 => val.set_spi_1_1x_clk_act(true),
PeriphSelect::Spi0 => val.set_spi_0_1x_clk_act(true),
PeriphSelect::Sdio1 => val.set_sdio_1_1x_clk_act(true),
PeriphSelect::Sdio0 => val.set_sdio_0_1x_clk_act(true),
PeriphSelect::Gem1 => val.set_gem_1_1x_clk_act(true),
@@ -240,7 +244,7 @@ pub fn disable_amba_periph_clk(select: PeriphSelect) {
PeriphSelect::Can1 => val.set_can_1_1x_clk_act(false),
PeriphSelect::Can0 => val.set_can_0_1x_clk_act(false),
PeriphSelect::Spi1 => val.set_spi_1_1x_clk_act(false),
PeriphSelect::Spi0 => val.set_spi_1_1x_clk_act(false),
PeriphSelect::Spi0 => val.set_spi_0_1x_clk_act(false),
PeriphSelect::Sdio1 => val.set_sdio_1_1x_clk_act(false),
PeriphSelect::Sdio0 => val.set_sdio_0_1x_clk_act(false),
PeriphSelect::Gem1 => val.set_gem_1_1x_clk_act(false),
+124 -80
View File
@@ -12,10 +12,9 @@ static LOG_SEL: AtomicU8 = AtomicU8::new(0);
/// Blocking UART loggers.
pub mod uart_blocking {
use super::*;
use core::cell::{Cell, RefCell, UnsafeCell};
use core::cell::{RefCell, UnsafeCell};
use embedded_io::Write as _;
use aarch32_cpu::register::Cpsr;
use critical_section::Mutex;
use log::{LevelFilter, Log, set_logger, set_max_level};
@@ -78,28 +77,64 @@ pub mod uart_blocking {
}
}
pub struct UartLoggerUnsafeSingleThread {
skip_in_isr: Cell<bool>,
pub struct UartLoggerWithBusyFlag {
busy: AtomicBool,
skip_in_isr: AtomicBool,
uart: UnsafeCell<Option<Uart>>,
}
unsafe impl Send for UartLoggerUnsafeSingleThread {}
unsafe impl Sync for UartLoggerUnsafeSingleThread {}
unsafe impl Send for UartLoggerWithBusyFlag {}
unsafe impl Sync for UartLoggerWithBusyFlag {}
static UART_LOGGER_UNSAFE_SINGLE_THREAD: UartLoggerUnsafeSingleThread =
UartLoggerUnsafeSingleThread {
skip_in_isr: Cell::new(false),
uart: UnsafeCell::new(None),
};
static UART_LOGGER_UNSAFE_SINGLE_THREAD: UartLoggerWithBusyFlag = UartLoggerWithBusyFlag {
busy: AtomicBool::new(false),
skip_in_isr: AtomicBool::new(false),
uart: UnsafeCell::new(None),
};
/// Initialize the logger with a blocking UART instance which does not use locks.
struct UartGuard<'lock>(&'lock AtomicBool);
impl<'lock> UartGuard<'lock> {
pub fn new(flag: &'lock AtomicBool) -> Option<Self> {
let proc_mode = aarch32_cpu::register::Cpsr::read().mode().ok()?;
let is_irq = proc_mode == aarch32_cpu::register::cpsr::ProcessorMode::Fiq
|| proc_mode == aarch32_cpu::register::cpsr::ProcessorMode::Irq;
// For IRQs, only try once.
if is_irq {
if UART_LOGGER_UNSAFE_SINGLE_THREAD
.skip_in_isr
.load(core::sync::atomic::Ordering::Relaxed)
{
return None;
}
if flag.swap(true, core::sync::atomic::Ordering::AcqRel) {
return None;
}
return Some(Self(flag));
}
// For threaded code, spinning is allowed.
while flag.swap(true, core::sync::atomic::Ordering::AcqRel) {}
Some(Self(flag))
}
}
impl Drop for UartGuard<'_> {
fn drop(&mut self) {
self.0.store(false, core::sync::atomic::Ordering::Release);
}
}
/// Initialize the logger with a blocking UART instance which spins on a busy flag in threaded
/// mode, and does not log in interrupt contexts if the main task was busy with logging.
///
/// # Safety
/// It should be noted that this is still a blocking logger, and using it in an ISR might
/// invalidate application logic and introduce problematic delays in the system.
///
/// This is a blocking logger which performs a write WITHOUT a critical section. This logger is
/// NOT thread-safe, which might lead to garbled output. Log output in ISRs can optionally be
/// surpressed.
pub unsafe fn init_unsafe_single_core(uart: Uart, level: LevelFilter, skip_in_isr: bool) {
/// Therefore, the initialization also allows skipping logging in ISRs completely.
pub fn init_with_busy_flag(uart: Uart, level: LevelFilter, skip_in_isr: bool) {
if LOGGER_INIT_DONE.swap(true, core::sync::atomic::Ordering::Relaxed) {
return;
}
@@ -109,34 +144,31 @@ pub mod uart_blocking {
);
let opt_uart = unsafe { &mut *UART_LOGGER_UNSAFE_SINGLE_THREAD.uart.get() };
opt_uart.replace(uart);
UART_LOGGER_UNSAFE_SINGLE_THREAD
.skip_in_isr
.set(skip_in_isr);
.store(skip_in_isr, core::sync::atomic::Ordering::Relaxed);
set_logger(&UART_LOGGER_UNSAFE_SINGLE_THREAD).unwrap();
set_max_level(level); // Adjust as needed
}
impl log::Log for UartLoggerUnsafeSingleThread {
impl log::Log for UartLoggerWithBusyFlag {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if self.skip_in_isr.get() {
match Cpsr::read().mode().unwrap() {
aarch32_cpu::register::cpsr::ProcessorMode::Fiq
| aarch32_cpu::register::cpsr::ProcessorMode::Irq => {
return;
}
_ => {}
}
let guard = UartGuard::new(&self.busy);
if guard.is_none() {
return;
}
let uart_mut = unsafe { &mut *self.uart.get() }.as_mut();
if uart_mut.is_none() {
return;
}
writeln!(
uart_mut.unwrap(),
"{} - {}\r",
@@ -147,6 +179,11 @@ pub mod uart_blocking {
}
fn flush(&self) {
let guard = UartGuard::new(&self.busy);
if guard.is_none() {
return;
}
let uart_mut = unsafe { &mut *self.uart.get() }.as_mut();
if uart_mut.is_none() {
return;
@@ -165,96 +202,103 @@ pub mod uart_blocking {
}
}
/// Logger module which logs into a ring buffer to allow asynchronous logging handling.
pub mod rb {
use core::cell::RefCell;
/// Logger module which logs into a pipe to allow asynchronous logging handling.
pub mod asynch {
use core::fmt::Write as _;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use log::{LevelFilter, set_logger, set_max_level};
use ringbuf::{
StaticRb,
traits::{Consumer, Producer},
};
use crate::uart::TxAsync;
/// Logger implementation which logs frames via a ring buffer and sends the frame sizes
/// as messages.
///
/// The logger does not require allocation and reserved a generous amount of 4096 bytes for
/// both data buffer and ring buffer. This should be sufficient for most logging needs.
/// The logger does not require allocation and reserves a generous amount of 4096 bytes for
/// log data. This should be sufficient for most logging needs.
pub struct Logger {
frame_queue: embassy_sync::channel::Channel<CriticalSectionRawMutex, usize, 32>,
data_buf: critical_section::Mutex<RefCell<heapless::String<4096>>>,
ring_buf: critical_section::Mutex<RefCell<Option<StaticRb<u8, 4096>>>>,
pipe: core::cell::RefCell<
Option<embassy_sync::pipe::Writer<'static, CriticalSectionRawMutex, 4096>>,
>,
buf: critical_section::Mutex<core::cell::RefCell<heapless::String<4096>>>,
}
unsafe impl Send for Logger {}
unsafe impl Sync for Logger {}
static LOGGER_RB: Logger = Logger {
frame_queue: embassy_sync::channel::Channel::new(),
data_buf: critical_section::Mutex::new(RefCell::new(heapless::String::new())),
ring_buf: critical_section::Mutex::new(RefCell::new(None)),
static LOGGER: Logger = Logger {
pipe: core::cell::RefCell::new(None),
buf: critical_section::Mutex::new(core::cell::RefCell::new(heapless::String::new())),
};
static PIPE: static_cell::ConstStaticCell<
embassy_sync::pipe::Pipe<CriticalSectionRawMutex, 4096>,
> = static_cell::ConstStaticCell::new(embassy_sync::pipe::Pipe::new());
impl log::Log for Logger {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if self.pipe.borrow().is_none() {
return;
}
critical_section::with(|cs| {
let ref_buf = self.data_buf.borrow(cs);
let mut buf = ref_buf.borrow_mut();
let mut buf = self.buf.borrow(cs).borrow_mut();
buf.clear();
let _ = writeln!(buf, "{} - {}\r", record.level(), record.args());
let rb_ref = self.ring_buf.borrow(cs);
let mut rb_opt = rb_ref.borrow_mut();
if rb_opt.is_none() {
panic!("log call on uninitialized logger");
let mut written = 0;
let pipe_writer_ref = self.pipe.borrow();
let pipe_writer = pipe_writer_ref.as_ref().unwrap();
while let Ok(written_in_this_call) =
pipe_writer.try_write(&buf.as_bytes()[written..])
{
written += written_in_this_call;
if written >= buf.len() {
break;
}
}
rb_opt.as_mut().unwrap().push_slice(buf.as_bytes());
let _ = self.frame_queue.try_send(buf.len());
});
}
fn flush(&self) {
while !self.frame_queue().is_empty() {}
}
fn flush(&self) {}
}
impl Logger {
pub fn frame_queue(
&self,
) -> &embassy_sync::channel::Channel<CriticalSectionRawMutex, usize, 32> {
&self.frame_queue
}
}
pub fn init(level: LevelFilter) {
pub fn init_generic(
level: LevelFilter,
) -> Option<embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 4096>> {
if super::LOGGER_INIT_DONE.swap(true, core::sync::atomic::Ordering::Relaxed) {
return;
return None;
}
critical_section::with(|cs| {
let rb = StaticRb::<u8, 4096>::default();
let rb_ref = LOGGER_RB.ring_buf.borrow(cs);
rb_ref.borrow_mut().replace(rb);
});
set_logger(&LOGGER_RB).unwrap();
let (reader, writer) = PIPE.take().split();
LOGGER.pipe.borrow_mut().replace(writer);
set_logger(&LOGGER).unwrap();
set_max_level(level); // Adjust as needed
Some(reader)
}
pub fn read_next_frame(frame_len: usize, buf: &mut [u8]) {
let read_len = core::cmp::min(frame_len, buf.len());
critical_section::with(|cs| {
let rb_ref = LOGGER_RB.ring_buf.borrow(cs);
let mut rb = rb_ref.borrow_mut();
rb.as_mut().unwrap().pop_slice(&mut buf[0..read_len]);
})
pub fn init_with_uart_tx(level: LevelFilter, tx: TxAsync) -> Option<UartLoggerRunner> {
init_generic(level).map(|reader| UartLoggerRunner { reader, tx })
}
pub fn get_frame_queue()
-> &'static embassy_sync::channel::Channel<CriticalSectionRawMutex, usize, 32> {
LOGGER_RB.frame_queue()
pub struct UartLoggerRunner {
reader: embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 4096>,
tx: TxAsync,
}
impl UartLoggerRunner {
pub async fn run(&mut self) -> ! {
let mut log_buf = [0u8; 1024];
loop {
let read_bytes = self.reader.read(&mut log_buf).await;
if read_bytes > 0 {
self.tx.write(&log_buf[..read_bytes]).unwrap().await;
}
}
}
}
}
@@ -1,4 +1,33 @@
//! # Device Configuration Module
//! # Programmable Logic (PL) support module.
//!
//! Provides the [configure_bitstream_non_secure] method to program the PL using the device
//! configuration (`devcfg`) peripheral.
use arbitrary_int::{traits::Integer as _, u17};
use zynq7000::slcr::reset::FpgaResetControl;
use crate::slcr::Slcr;
/// Put the PL out of reset.
///
/// The PL is in reset state after power-up. This method should be called in the first-stage
/// bootloader to put it out of reset.
pub fn deassert_reset() {
// Safety: We only touch the PL reset register here.
unsafe {
Slcr::with(|slcr| {
slcr.reset_ctrl().write_fpga(
FpgaResetControl::builder()
.with_zero_block_0(u17::ZERO)
.with_fpga_3(false)
.with_fpga_2(false)
.with_fpga_1(false)
.with_fpga_0(false)
.build(),
);
})
};
}
#[derive(Debug, thiserror::Error)]
#[error("unaligned address: {0}")]
pub struct UnalignedAddrError(usize);
@@ -53,10 +82,14 @@ pub fn configure_bitstream_non_secure(
val.set_pcap_rate_enable(false);
val
});
devcfg.write_dma_source_addr(bitstream.as_ptr() as u32);
// As specified in the TMR,
// Setting the two LSBs of the source and destination address to 2'b01 indicates to the DevC
// DMA module the last DMA command of an overall transfer
devcfg.write_dma_source_addr(bitstream.as_ptr() as u32 | 0b01);
devcfg.write_dma_dest_addr(0xFFFF_FFFF);
devcfg.write_dma_source_len(bitstream.len() as u32);
devcfg.write_dma_dest_len(bitstream.len() as u32);
devcfg.write_dma_source_len(bitstream.len() as u32 / 4);
devcfg.write_dma_dest_len(bitstream.len() as u32 / 4);
while !devcfg.read_interrupt_status().dma_done() {}
// TODO: Check for errors.
+1 -1
View File
@@ -76,7 +76,7 @@ impl embedded_hal::delay::DelayNs for CpuPrivateTimer {
fn delay_ns(&mut self, ns: u32) {
// Even for a value of 1000 MHz for CPU 3x2x and u32::MAX for nanoseconds, this will
// never overflow.
let ticks = (ns as u64 * self.cpu_3x2x_clock.raw() as u64) / 1_000_000_000;
let ticks = (ns as u64 * self.cpu_3x2x_clock.to_raw() as u64) / 1_000_000_000;
// Split the total delay into manageable chunks (u32::MAX ticks max).
let mut remaining = ticks;
+24 -16
View File
@@ -8,7 +8,7 @@ use zynq7000::{
BaudRateDivisor, Config, InstructionCode, InterruptStatus, LoopbackMasterClockDelay,
SpiEnable,
},
slcr::{clocks::SingleCommonPeriphIoClockControl, mio::Speed, reset::QspiResetControl},
slcr::{clocks::SingleCommonPeriphIoClockControl, mio::Speed, reset::ResetControlQspiSmc},
};
pub use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, Mode};
@@ -35,6 +35,12 @@ pub(crate) mod lqspi_configs;
pub const QSPI_MUX_CONFIG: MuxConfig = MuxConfig::new_with_l0();
pub const FIFO_DEPTH: usize = 63;
/// From the TRM:
///
/// > The maximum number of bytes per command sequence in this mode is limited by the depth of the
/// > TxFIFO of 252 bytes.
pub const MAX_BYTES_PER_TRANSFER_IO_MODE: usize = FIFO_DEPTH * 4;
/// In linear-addressed mode, the QSPI is memory-mapped, with the address starting here.
pub const QSPI_START_ADDRESS: usize = 0xFC00_0000;
@@ -206,7 +212,7 @@ impl ClockConfig {
SrcSelIo::ArmPll => clocks.arm_clocks().ref_clk(),
SrcSelIo::DdrPll => clocks.ddr_clocks().ref_clk(),
};
let ref_clk_div = ref_clk.raw().div_ceil(target_ref_clock.raw());
let ref_clk_div = ref_clk.to_raw().div_ceil(target_ref_clock.to_raw());
if ref_clk_div > u6::MAX.as_u32() {
return Err(ClockCalculationError::RefDivOutOfRange);
}
@@ -233,24 +239,24 @@ impl ClockConfig {
clocks
.io_clocks()
.ref_clk()
.raw()
.div_ceil(target_qspi_ref_clock.raw()),
.to_raw()
.div_ceil(target_qspi_ref_clock.to_raw()),
clocks.io_clocks().ref_clk(),
),
SrcSelIo::ArmPll => (
clocks
.arm_clocks()
.ref_clk()
.raw()
.div_ceil(target_qspi_ref_clock.raw()),
.to_raw()
.div_ceil(target_qspi_ref_clock.to_raw()),
clocks.arm_clocks().ref_clk(),
),
SrcSelIo::DdrPll => (
clocks
.ddr_clocks()
.ref_clk()
.raw()
.div_ceil(target_qspi_ref_clock.raw()),
.to_raw()
.div_ceil(target_qspi_ref_clock.to_raw()),
clocks.ddr_clocks().ref_clk(),
),
};
@@ -261,7 +267,9 @@ impl ClockConfig {
if qspi_ref_clk < clocks.arm_clocks().cpu_1x_clk() {
return Err(ClockCalculationError::RefClockSmallerThanCpu1xClock);
}
let qspi_baud_rate_div = qspi_ref_clk / target_qspi_interface_clock;
let qspi_baud_rate_div = qspi_ref_clk
.to_raw()
.div_ceil(target_qspi_interface_clock.to_raw());
let baud_rate_div = match qspi_baud_rate_div {
0..=2 => BaudRateDivisor::_2,
3..=4 => BaudRateDivisor::_4,
@@ -409,7 +417,7 @@ impl Qspi {
.with_disable_hstl_rcvr(false)
.with_pullup(true)
.with_io_type(voltage)
.with_speed(Speed::SlowCmosEdge)
.with_speed(Speed::FastCmosEdge)
.with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
.with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
.with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
@@ -421,7 +429,7 @@ impl Qspi {
.with_disable_hstl_rcvr(false)
.with_pullup(false)
.with_io_type(voltage)
.with_speed(Speed::SlowCmosEdge)
.with_speed(Speed::FastCmosEdge)
.with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
.with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
.with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
@@ -463,7 +471,7 @@ impl Qspi {
.with_disable_hstl_rcvr(false)
.with_pullup(false)
.with_io_type(voltage)
.with_speed(Speed::SlowCmosEdge)
.with_speed(Speed::FastCmosEdge)
.with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
.with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
.with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
@@ -667,16 +675,16 @@ pub fn reset() {
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl().write_lqspi(
QspiResetControl::builder()
.with_qspi_ref_reset(true)
ResetControlQspiSmc::builder()
.with_ref_reset(true)
.with_cpu_1x_reset(true)
.build(),
);
// Keep it in reset for some cycles.
for _ in 0..3 {
for _ in 0..10 {
aarch32_cpu::asm::nop();
}
regs.reset_ctrl().write_lqspi(QspiResetControl::DEFAULT);
regs.reset_ctrl().write_lqspi(ResetControlQspiSmc::DEFAULT);
});
}
}
+132
View File
@@ -0,0 +1,132 @@
use arbitrary_int::u6;
use zynq7000::sdio::{BlockSelect, CommandRegister, ResponseType};
use embedded_sdmmc::sdcard::{AcmdId, CmdId};
pub struct CommandConfig {
pub id: u6,
pub response_type: ResponseType,
pub index_check: bool,
pub crc_check: bool,
}
impl CommandConfig {
pub const fn new_no_response(id: u6) -> Self {
Self {
id,
response_type: ResponseType::None,
index_check: false,
crc_check: false,
}
}
pub const fn new_with_r1_response(id: u6) -> Self {
Self {
id,
response_type: ResponseType::_48bits,
index_check: true,
crc_check: true,
}
}
pub const fn new_with_r2_response(id: u6) -> Self {
Self {
id,
response_type: ResponseType::_136bits,
index_check: false,
crc_check: true,
}
}
pub const fn new_with_r3_response(id: u6) -> Self {
Self {
id,
response_type: ResponseType::_48bits,
index_check: false,
crc_check: false,
}
}
pub const fn new_with_r6_response(id: u6) -> Self {
Self {
id,
response_type: ResponseType::_48bitsWithCheck,
index_check: false,
crc_check: false,
}
}
}
pub const fn build_command_without_data(config: CommandConfig) -> CommandRegister {
CommandRegister::builder()
.with_command_index(config.id)
.with_command_type(zynq7000::sdio::CommandType::Normal)
.with_data_is_present(false)
.with_command_index_check_enable(config.index_check)
.with_command_crc_check_enable(config.crc_check)
.with_response_type_select(config.response_type)
.with_block_select(zynq7000::sdio::BlockSelect::SingleBlock)
.with_data_transfer_direction(zynq7000::sdio::TransferDirection::Write)
.with_auto_cmd12_enable(false)
.with_block_count_enable(false)
.with_dma_enable(false)
.build()
}
pub const CMD0_GO_IDLE_MODE: CommandRegister = build_command_without_data(
CommandConfig::new_no_response(CmdId::CMD0_GoIdleState.raw_value()),
);
pub const CMD2_ALL_SEND_CID: CommandRegister = build_command_without_data(
CommandConfig::new_with_r2_response(CmdId::CMD2_AllSendCid.raw_value()),
);
pub const CMD3_SEND_RELATIVE_ADDR: CommandRegister = build_command_without_data(
CommandConfig::new_with_r6_response(CmdId::CMD3_SendRelativeAddr.raw_value()),
);
pub const CMD7_SELECT_SD_CARD: CommandRegister = build_command_without_data(
CommandConfig::new_with_r1_response(CmdId::CMD7_SelectCard.raw_value()),
);
pub const CMD8_SEND_IF_COND: CommandRegister = build_command_without_data(
CommandConfig::new_with_r1_response(CmdId::CMD8_SendIfCond.raw_value()),
);
pub const CMD9_SEND_CSD: CommandRegister = build_command_without_data(
CommandConfig::new_with_r2_response(CmdId::CMD9_SendCsd.raw_value()),
);
pub const CMD13_SEND_STATUS: CommandRegister = build_command_without_data(
CommandConfig::new_with_r1_response(CmdId::CMD13_SendStatus.raw_value()),
);
pub const CMD17_READ_SINGLE_BLOCK: CommandRegister = CommandRegister::builder()
.with_command_index(CmdId::CMD17_ReadSingleBlock.raw_value())
.with_command_type(zynq7000::sdio::CommandType::Normal)
.with_data_is_present(true)
.with_command_index_check_enable(true)
.with_command_crc_check_enable(true)
.with_response_type_select(ResponseType::_48bits)
.with_block_select(BlockSelect::SingleBlock)
.with_data_transfer_direction(zynq7000::sdio::TransferDirection::Read)
.with_auto_cmd12_enable(false)
.with_block_count_enable(false)
.with_dma_enable(false)
.build();
pub const CMD24_WRITE_BLOCK: CommandRegister = CommandRegister::builder()
.with_command_index(CmdId::CMD24_WriteBlock.raw_value())
.with_command_type(zynq7000::sdio::CommandType::Normal)
.with_data_is_present(true)
.with_command_index_check_enable(true)
.with_command_crc_check_enable(true)
.with_response_type_select(ResponseType::_48bits)
.with_block_select(BlockSelect::SingleBlock)
.with_data_transfer_direction(zynq7000::sdio::TransferDirection::Write)
.with_auto_cmd12_enable(false)
.with_block_count_enable(false)
.with_dma_enable(false)
.build();
pub const CMD55_APP_CMD: CommandRegister = build_command_without_data(
CommandConfig::new_with_r1_response(CmdId::CMD55_AppCmd.raw_value()),
);
pub const ACMD6_SET_BUS_WIDTH: CommandRegister = build_command_without_data(
CommandConfig::new_with_r1_response(AcmdId::ACMD6_SetBusWidth.raw_value()),
);
pub const ACMD41_SEND_IF_COND: CommandRegister = build_command_without_data(
CommandConfig::new_with_r3_response(AcmdId::ACMD41_SdSendOpCond.raw_value()),
);
File diff suppressed because it is too large Load Diff
+99
View File
@@ -0,0 +1,99 @@
use crate::gpio::mio::{
Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31, Mio32, Mio33, Mio34,
Mio35, Mio36, Mio37, Mio38, Mio39, Mio48, Mio49, MioPin, Pin,
};
#[cfg(not(feature = "7z010-7z007s-clg225"))]
use crate::gpio::mio::{
Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, Mio40,
Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio50, Mio51,
};
pub trait Sdio0ClockPin: MioPin {}
pub trait Sdio0CommandPin: MioPin {}
pub trait Sdio0Data0Pin: MioPin {}
pub trait Sdio0Data1Pin: MioPin {}
pub trait Sdio0Data2Pin: MioPin {}
pub trait Sdio0Data3Pin: MioPin {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0ClockPin for Pin<Mio16> {}
impl Sdio0ClockPin for Pin<Mio28> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0ClockPin for Pin<Mio40> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0CommandPin for Pin<Mio17> {}
impl Sdio0CommandPin for Pin<Mio29> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0CommandPin for Pin<Mio41> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data0Pin for Pin<Mio18> {}
impl Sdio0Data0Pin for Pin<Mio30> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data0Pin for Pin<Mio42> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data1Pin for Pin<Mio19> {}
impl Sdio0Data1Pin for Pin<Mio31> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data1Pin for Pin<Mio43> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data2Pin for Pin<Mio20> {}
impl Sdio0Data2Pin for Pin<Mio32> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data2Pin for Pin<Mio44> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data3Pin for Pin<Mio21> {}
impl Sdio0Data3Pin for Pin<Mio33> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data3Pin for Pin<Mio45> {}
pub trait Sdio1ClockPin: MioPin {}
pub trait Sdio1CommandPin: MioPin {}
pub trait Sdio1Data0Pin: MioPin {}
pub trait Sdio1Data1Pin: MioPin {}
pub trait Sdio1Data2Pin: MioPin {}
pub trait Sdio1Data3Pin: MioPin {}
impl Sdio1ClockPin for Pin<Mio12> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1ClockPin for Pin<Mio24> {}
impl Sdio1ClockPin for Pin<Mio36> {}
impl Sdio1ClockPin for Pin<Mio48> {}
impl Sdio1CommandPin for Pin<Mio11> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1CommandPin for Pin<Mio23> {}
impl Sdio1CommandPin for Pin<Mio35> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1CommandPin for Pin<Mio47> {}
impl Sdio1Data0Pin for Pin<Mio10> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data0Pin for Pin<Mio22> {}
impl Sdio1Data0Pin for Pin<Mio34> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data0Pin for Pin<Mio46> {}
impl Sdio1Data1Pin for Pin<Mio13> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data1Pin for Pin<Mio25> {}
impl Sdio1Data1Pin for Pin<Mio37> {}
impl Sdio1Data1Pin for Pin<Mio49> {}
impl Sdio1Data2Pin for Pin<Mio14> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data2Pin for Pin<Mio26> {}
impl Sdio1Data2Pin for Pin<Mio38> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data2Pin for Pin<Mio50> {}
impl Sdio1Data2Pin for Pin<Mio15> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data3Pin for Pin<Mio27> {}
impl Sdio1Data3Pin for Pin<Mio39> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data3Pin for Pin<Mio51> {}
+277 -169
View File
@@ -1,11 +1,10 @@
//! Asynchronous PS SPI driver.
use core::{cell::RefCell, convert::Infallible, sync::atomic::AtomicBool};
use core::{cell::RefCell, sync::atomic::AtomicBool};
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_hal_async::spi::SpiBus;
use raw_slice::{RawBufSlice, RawBufSliceMut};
use zynq7000::spi::InterruptStatus;
use super::{ChipSelect, FIFO_DEPTH, Spi, SpiId, SpiLowLevel};
@@ -16,119 +15,137 @@ static TRANSFER_CONTEXTS: [Mutex<RefCell<TransferContext>>; 2] =
// critical section.
static DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("SPI RX FIFO overrun")]
pub struct RxOverrunError;
impl embedded_hal_async::spi::Error for RxOverrunError {
fn kind(&self) -> embedded_hal::spi::ErrorKind {
embedded_hal::spi::ErrorKind::Overrun
}
}
/// This is a generic interrupt handler to handle asynchronous SPI operations for a given
/// SPI peripheral.
///
/// The user has to call this once in the interrupt handler responsible for the SPI interrupts on
/// # Safety
///
/// This needs to be called once in the interrupt handler responsible for the SPI interrupts on
/// the given SPI bank.
pub fn on_interrupt(peripheral: SpiId) {
pub unsafe fn on_interrupt(peripheral: SpiId) {
let mut spi = unsafe { SpiLowLevel::steal(peripheral) };
let idx = peripheral as usize;
let imr = spi.read_imr();
let index = peripheral as usize;
let enabled_irqs = spi.read_enabled_interrupts();
// Prevent spurious interrupts from messing with out logic here.
spi.disable_interrupts_master_mode();
let interrupt_status = spi.read_interrupt_status();
spi.clear_interrupts_master_mode();
// IRQ is not related.
if !imr.tx_trig() && !imr.tx_full() && !imr.tx_underflow() && !imr.rx_ovr() && !imr.rx_full() {
if !enabled_irqs.tx_below_threshold()
&& !enabled_irqs.tx_full()
&& !enabled_irqs.tx_underflow()
&& !enabled_irqs.rx_ovr()
&& !enabled_irqs.rx_full()
{
return;
}
// Prevent spurious interrupts from messing with out logic here.
spi.disable_interrupts();
let isr = spi.read_isr();
spi.clear_interrupts();
if interrupt_status.rx_overrun() {
// Not sure how to otherwise handle this cleanly..
return handle_rx_overrun(&mut spi, index);
}
let mut context = critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
let context_ref = TRANSFER_CONTEXTS[index].borrow(cs);
*context_ref.borrow()
});
// No transfer active.
if context.transfer_type.is_none() {
return;
}
// Write the trigger to one, we want to empty the whole FIFO in the transfer handlers.
// The trigger might have been set to a higher value, and the NOT FULL status bit will be
// cleared when the FIFO falls below the threshold.
spi.write_rx_trig(0x1);
let transfer_type = context.transfer_type.unwrap();
match transfer_type {
TransferType::Read => on_interrupt_read(idx, &mut context, &mut spi, isr),
TransferType::Write => on_interrupt_write(idx, &mut context, &mut spi, isr),
TransferType::Transfer => on_interrupt_transfer(idx, &mut context, &mut spi, isr),
TransferType::Read => on_interrupt_read(index, &mut context, &mut spi),
TransferType::Write => on_interrupt_write(index, &mut context, &mut spi),
TransferType::Transfer => on_interrupt_transfer(index, &mut context, &mut spi),
TransferType::TransferInPlace => {
on_interrupt_transfer_in_place(idx, &mut context, &mut spi, isr)
on_interrupt_transfer_in_place(index, &mut context, &mut spi)
}
};
}
fn on_interrupt_read(
idx: usize,
context: &mut TransferContext,
spi: &mut SpiLowLevel,
mut isr: InterruptStatus,
) {
fn handle_rx_overrun(spi: &mut SpiLowLevel, idx: usize) {
critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
context_ref.borrow_mut().rx_overrun = true;
});
// Clean state is re-configured by drop handler.
reset_trigger_levels(spi);
// At the very least disable the peripheral.
spi.disable();
// Interrupts were already disabled and cleared.
DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed);
WAKERS[idx].wake();
}
#[inline]
fn reset_trigger_levels(spi: &mut SpiLowLevel) {
spi.write_rx_trig(0x1);
spi.write_tx_trig(0x1);
}
fn on_interrupt_read(idx: usize, context: &mut TransferContext, spi: &mut SpiLowLevel) {
let read_slice = unsafe { context.rx_slice.get_mut().unwrap() };
let transfer_len = read_slice.len();
// Read data from RX FIFO first.
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
(0..read_len).for_each(|_| {
while spi.read_interrupt_status().rx_not_empty() {
read_slice[context.rx_progress] = spi.read_fifo_unchecked();
context.rx_progress += 1;
});
}
// The FIFO still needs to be pumped.
while context.tx_progress < read_slice.len() && !isr.tx_full() {
while context.tx_progress < read_slice.len() && !spi.read_interrupt_status().tx_full() {
spi.write_fifo_unchecked(0);
context.tx_progress += 1;
isr = spi.read_isr();
}
isr_finish_handler(idx, spi, context, transfer_len)
}
fn on_interrupt_write(
idx: usize,
context: &mut TransferContext,
spi: &mut SpiLowLevel,
mut isr: InterruptStatus,
) {
fn on_interrupt_write(idx: usize, context: &mut TransferContext, spi: &mut SpiLowLevel) {
let write_slice = unsafe { context.tx_slice.get().unwrap() };
let transfer_len = write_slice.len();
// Read data from RX FIFO first.
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
(0..read_len).for_each(|_| {
while spi.read_interrupt_status().rx_not_empty() {
spi.read_fifo_unchecked();
context.rx_progress += 1;
});
}
// Data still needs to be sent
while context.tx_progress < transfer_len && !isr.tx_full() {
while context.tx_progress < transfer_len && !spi.read_interrupt_status().tx_full() {
spi.write_fifo_unchecked(write_slice[context.tx_progress]);
context.tx_progress += 1;
isr = spi.read_isr();
}
isr_finish_handler(idx, spi, context, transfer_len)
}
fn on_interrupt_transfer(
idx: usize,
context: &mut TransferContext,
spi: &mut SpiLowLevel,
mut isr: InterruptStatus,
) {
fn on_interrupt_transfer(idx: usize, context: &mut TransferContext, spi: &mut SpiLowLevel) {
let read_slice = unsafe { context.rx_slice.get_mut().unwrap() };
let read_len = read_slice.len();
let write_slice = unsafe { context.tx_slice.get().unwrap() };
let write_len = write_slice.len();
let transfer_len = core::cmp::max(read_len, write_len);
// Read data from RX FIFO first.
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
(0..read_len).for_each(|_| {
if context.rx_progress < read_len {
read_slice[context.rx_progress] = spi.read_fifo_unchecked();
} else {
spi.read_fifo_unchecked();
}
context.rx_progress += 1;
});
// Data still needs to be sent
while context.tx_progress < transfer_len && !isr.tx_full() {
while context.tx_progress < transfer_len && !spi.read_interrupt_status().tx_full() {
if context.tx_progress < write_len {
spi.write_fifo_unchecked(write_slice[context.tx_progress]);
} else {
@@ -136,7 +153,15 @@ fn on_interrupt_transfer(
spi.write_fifo_unchecked(0);
}
context.tx_progress += 1;
isr = spi.read_isr();
}
while context.rx_progress < transfer_len && spi.read_interrupt_status().rx_not_empty() {
if context.rx_progress < read_len {
read_slice[context.rx_progress] = spi.read_fifo_unchecked();
} else {
spi.read_fifo_unchecked();
}
context.rx_progress += 1;
}
isr_finish_handler(idx, spi, context, transfer_len)
@@ -146,43 +171,25 @@ fn on_interrupt_transfer_in_place(
idx: usize,
context: &mut TransferContext,
spi: &mut SpiLowLevel,
mut isr: InterruptStatus,
) {
let transfer_slice = unsafe { context.rx_slice.get_mut().unwrap() };
let transfer_len = transfer_slice.len();
// Read data from RX FIFO first.
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
(0..read_len).for_each(|_| {
transfer_slice[context.rx_progress] = spi.read_fifo_unchecked();
context.rx_progress += 1;
});
// Data still needs to be sent
while context.tx_progress < transfer_len && !isr.tx_full() {
// Send data first to avoid overwriting data that still needs to be sent.
while context.tx_progress < transfer_len && !spi.read_interrupt_status().tx_full() {
spi.write_fifo_unchecked(transfer_slice[context.tx_progress]);
context.tx_progress += 1;
isr = spi.read_isr();
}
// Read data from RX FIFO.
while spi.read_interrupt_status().rx_not_empty() {
transfer_slice[context.rx_progress] = spi.read_fifo_unchecked();
context.rx_progress += 1;
}
isr_finish_handler(idx, spi, context, transfer_len)
}
fn calculate_read_len(
spi: &mut SpiLowLevel,
isr: InterruptStatus,
total_read_len: usize,
rx_progress: usize,
) -> usize {
if isr.rx_full() {
core::cmp::min(FIFO_DEPTH, total_read_len - rx_progress)
} else if isr.rx_not_empty() {
let trigger = spi.read_rx_not_empty_threshold();
core::cmp::min(total_read_len - rx_progress, trigger as usize)
} else {
0
}
}
/// Generic handler after RX FIFO and TX FIFO were handled. Checks and handles finished
/// and unfinished conditions.
fn isr_finish_handler(
@@ -197,7 +204,7 @@ fn isr_finish_handler(
return;
}
unfinished_transfer(spi, transfer_len, context.rx_progress);
unfinished_transfer(spi, transfer_len, context);
// If the transfer is done, the context structure was already written back.
// Write back updated context structure.
@@ -213,6 +220,7 @@ fn finish_transfer(idx: usize, context: &mut TransferContext, spi: &mut SpiLowLe
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = *context;
});
// Default reset values.
spi.set_rx_fifo_trigger(1).unwrap();
spi.set_tx_fifo_trigger(1).unwrap();
// Interrupts were already disabled and cleared.
@@ -220,11 +228,30 @@ fn finish_transfer(idx: usize, context: &mut TransferContext, spi: &mut SpiLowLe
WAKERS[idx].wake();
}
fn unfinished_transfer(spi: &mut SpiLowLevel, transfer_len: usize, rx_progress: usize) {
let new_trig_level = core::cmp::min(FIFO_DEPTH, transfer_len - rx_progress);
spi.set_rx_fifo_trigger(new_trig_level as u32).unwrap();
// Re-enable interrupts with the new RX FIFO trigger level.
spi.enable_interrupts();
fn unfinished_transfer(spi: &mut SpiLowLevel, transfer_len: usize, context: &TransferContext) {
// Unwraps okay, checks ensure number is below FIFO depth.
// By using the FIFO depth divided by two, give the software some time and allow more
// latency in the system.
spi.set_rx_fifo_trigger(
core::cmp::min(FIFO_DEPTH / 2, transfer_len - context.rx_progress) as u32,
)
.unwrap();
let tx_pending = context.tx_progress < transfer_len;
if tx_pending {
let remaining_tx = transfer_len - context.tx_progress;
let tx_trigger = if remaining_tx < FIFO_DEPTH / 2 {
FIFO_DEPTH as u32 - remaining_tx as u32
} else {
FIFO_DEPTH as u32 / 2
};
spi.set_tx_fifo_trigger(tx_trigger).unwrap();
} else {
spi.set_tx_fifo_trigger(1).unwrap();
}
// Re-enable interrupts with the new RX FIFO trigger level. Only enable TX threshold interrupt
// if we are not done yet with pushing all bytes to the FIFO.
spi.enable_interrupts_master_mode(tx_pending);
}
#[derive(Debug, Clone, Copy)]
@@ -242,6 +269,7 @@ pub struct TransferContext {
rx_progress: usize,
tx_slice: RawBufSlice,
rx_slice: RawBufSliceMut,
rx_overrun: bool,
}
#[allow(clippy::new_without_default)]
@@ -253,173 +281,208 @@ impl TransferContext {
rx_progress: 0,
tx_slice: RawBufSlice::new_nulled(),
rx_slice: RawBufSliceMut::new_nulled(),
rx_overrun: false,
}
}
}
pub struct SpiFuture {
pub struct SpiFuture<'spi> {
id: super::SpiId,
spi: super::SpiLowLevel,
spi: &'spi mut super::SpiLowLevel,
config: super::Config,
finished_regularly: core::cell::Cell<bool>,
}
impl SpiFuture {
fn new_for_read(spi: &mut Spi, spi_id: SpiId, words: &mut [u8]) -> Self {
impl<'spi> SpiFuture<'spi> {
fn new_for_read(spi: &'spi mut Spi, spi_id: SpiId, words: &mut [u8]) -> Self {
if words.is_empty() {
panic!("words length unexpectedly 0");
}
let idx = spi_id as usize;
DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed);
spi.inner.disable_interrupts();
Self::generic_init_transfer(spi, spi_id);
let write_idx = core::cmp::min(super::FIFO_DEPTH, words.len());
let write_index = core::cmp::min(super::FIFO_DEPTH, words.len());
// Send dummy bytes.
(0..write_idx).for_each(|_| {
(0..write_index).for_each(|_| {
spi.inner.write_fifo_unchecked(0);
});
Self::set_triggers(spi, write_idx, words.len());
Self::set_triggers(spi, write_index, words.len());
// We assume that the slave select configuration was already performed, but we take
// care of issuing a start if necessary.
spi.issue_manual_start_for_manual_cfg();
critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
let context_ref = TRANSFER_CONTEXTS[spi_id as usize].borrow(cs);
let mut context = context_ref.borrow_mut();
context.transfer_type = Some(TransferType::Read);
unsafe {
context.rx_slice.set(words);
}
context.tx_slice.set_null();
context.tx_progress = write_idx;
context.tx_progress = write_index;
context.rx_progress = 0;
spi.inner.clear_interrupts();
spi.inner.enable_interrupts();
spi.inner.clear_interrupts_master_mode();
spi.inner
.enable_interrupts_master_mode(write_index < words.len());
spi.inner.enable();
});
Self {
id: spi_id,
config: spi.config,
spi: unsafe { spi.inner.clone() },
spi: &mut spi.inner,
finished_regularly: core::cell::Cell::new(false),
}
}
fn new_for_write(spi: &mut Spi, spi_id: SpiId, words: &[u8]) -> Self {
fn new_for_write(spi: &'spi mut Spi, spi_id: SpiId, words: &[u8]) -> Self {
if words.is_empty() {
panic!("words length unexpectedly 0");
}
let (idx, write_idx) = Self::generic_init_transfer(spi, spi_id, words);
let write_index = Self::generic_init_transfer_write_transfer_in_place(spi, spi_id, words);
critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
let context_ref = TRANSFER_CONTEXTS[spi_id as usize].borrow(cs);
let mut context = context_ref.borrow_mut();
context.transfer_type = Some(TransferType::Write);
unsafe {
context.tx_slice.set(words);
}
context.rx_slice.set_null();
context.tx_progress = write_idx;
context.tx_progress = write_index;
context.rx_progress = 0;
spi.inner.clear_interrupts();
spi.inner.enable_interrupts();
spi.inner.clear_interrupts_master_mode();
spi.inner
.enable_interrupts_master_mode(write_index < words.len());
spi.inner.enable();
});
Self {
id: spi_id,
config: spi.config,
spi: unsafe { spi.inner.clone() },
spi: &mut spi.inner,
finished_regularly: core::cell::Cell::new(false),
}
}
fn new_for_transfer(spi: &mut Spi, spi_id: SpiId, read: &mut [u8], write: &[u8]) -> Self {
fn new_for_transfer(spi: &'spi mut Spi, spi_id: SpiId, read: &mut [u8], write: &[u8]) -> Self {
if read.is_empty() || write.is_empty() {
panic!("read or write buffer unexpectedly empty");
}
let (idx, write_idx) = Self::generic_init_transfer(spi, spi_id, write);
let full_write_len = core::cmp::max(read.len(), write.len());
let fifo_prefill = core::cmp::min(super::FIFO_DEPTH, full_write_len);
Self::generic_init_transfer(spi, spi_id);
for write_index in 0..fifo_prefill {
let value = write.get(write_index).copied().unwrap_or(0);
spi.inner.write_fifo_unchecked(value);
}
Self::set_triggers(spi, fifo_prefill, full_write_len);
// We assume that the slave select configuration was already performed, but we take
// care of issuing a start if necessary.
spi.issue_manual_start_for_manual_cfg();
critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
let context_ref = TRANSFER_CONTEXTS[spi_id as usize].borrow(cs);
let mut context = context_ref.borrow_mut();
context.transfer_type = Some(TransferType::Transfer);
unsafe {
context.tx_slice.set(write);
context.rx_slice.set(read);
}
context.tx_progress = write_idx;
context.tx_progress = fifo_prefill;
context.rx_progress = 0;
spi.inner.clear_interrupts();
spi.inner.enable_interrupts();
spi.inner.clear_interrupts_master_mode();
spi.inner
.enable_interrupts_master_mode(full_write_len > FIFO_DEPTH);
spi.inner.enable();
});
Self {
id: spi_id,
config: spi.config,
spi: unsafe { spi.inner.clone() },
spi: &mut spi.inner,
finished_regularly: core::cell::Cell::new(false),
}
}
fn new_for_transfer_in_place(spi: &mut Spi, spi_id: SpiId, words: &mut [u8]) -> Self {
fn new_for_transfer_in_place(spi: &'spi mut Spi, spi_id: SpiId, words: &mut [u8]) -> Self {
if words.is_empty() {
panic!("read and write buffer unexpectedly empty");
}
let (idx, write_idx) = Self::generic_init_transfer(spi, spi_id, words);
let write_index = Self::generic_init_transfer_write_transfer_in_place(spi, spi_id, words);
critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
let context_ref = TRANSFER_CONTEXTS[spi_id as usize].borrow(cs);
let mut context = context_ref.borrow_mut();
context.transfer_type = Some(TransferType::TransferInPlace);
unsafe {
context.rx_slice.set(words);
}
context.tx_slice.set_null();
context.tx_progress = write_idx;
context.tx_progress = write_index;
context.rx_progress = 0;
spi.inner.clear_interrupts();
spi.inner.enable_interrupts();
spi.inner.clear_interrupts_master_mode();
spi.inner
.enable_interrupts_master_mode(write_index < words.len());
spi.inner.enable();
});
Self {
id: spi_id,
config: spi.config,
spi: unsafe { spi.inner.clone() },
spi: &mut spi.inner,
finished_regularly: core::cell::Cell::new(false),
}
}
fn generic_init_transfer(spi: &mut Spi, spi_id: SpiId, write: &[u8]) -> (usize, usize) {
let idx = spi_id as usize;
fn generic_init_transfer(spi: &mut Spi, id: SpiId) {
let idx = id as usize;
DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed);
spi.inner.disable();
spi.inner.disable_interrupts();
spi.inner.disable_interrupts_master_mode();
}
// Returns amount of bytes written to FIFO.
fn generic_init_transfer_write_transfer_in_place(
spi: &mut Spi,
id: SpiId,
write: &[u8],
) -> usize {
Self::generic_init_transfer(spi, id);
let write_idx = core::cmp::min(super::FIFO_DEPTH, write.len());
Self::set_triggers(spi, write_idx, write.len());
(0..write_idx).for_each(|idx| {
spi.inner.write_fifo_unchecked(write[idx]);
});
Self::set_triggers(spi, write_idx, write.len());
// We assume that the slave select configuration was already performed, but we take
// care of issuing a start if necessary.
spi.issue_manual_start_for_manual_cfg();
(idx, write_idx)
write_idx
}
fn set_triggers(spi: &mut Spi, write_idx: usize, write_len: usize) {
// This should never fail because it is never larger than the FIFO depth.
spi.inner.set_rx_fifo_trigger(write_idx as u32).unwrap();
spi.inner
.set_rx_fifo_trigger(core::cmp::min(
super::FIFO_DEPTH as u32 / 2,
write_idx as u32,
))
.unwrap();
// We want to re-fill the TX FIFO before it is completely empty if the full transfer size
// is larger than the FIFO depth. I am not sure whether the default value of 1 ensures
// this because the TMR says that this interrupt is triggered when the FIFO has less than
// threshold entries.
// is larger than the FIFO depth. Otherwise, set it to 1. Not exactly sure what that does,
// but we do not enable interrupts anyway.
if write_len > super::FIFO_DEPTH {
spi.inner.set_tx_fifo_trigger(2).unwrap();
spi.inner
.set_tx_fifo_trigger(super::FIFO_DEPTH as u32 / 2)
.unwrap();
} else {
spi.inner.set_tx_fifo_trigger(1).unwrap();
}
}
}
impl Future for SpiFuture {
type Output = ();
impl Future for SpiFuture<'_> {
type Output = Result<(), RxOverrunError>;
fn poll(
self: core::pin::Pin<&mut Self>,
@@ -427,18 +490,23 @@ impl Future for SpiFuture {
) -> core::task::Poll<Self::Output> {
WAKERS[self.id as usize].register(cx.waker());
if DONE[self.id as usize].swap(false, core::sync::atomic::Ordering::Relaxed) {
critical_section::with(|cs| {
let rx_overrun = critical_section::with(|cs| {
let mut ctx = TRANSFER_CONTEXTS[self.id as usize].borrow(cs).borrow_mut();
let overrun = ctx.rx_overrun;
*ctx = TransferContext::default();
overrun
});
self.finished_regularly.set(true);
return core::task::Poll::Ready(());
self.finished_regularly.set(!rx_overrun);
if rx_overrun {
return core::task::Poll::Ready(Err(RxOverrunError));
}
return core::task::Poll::Ready(Ok(()));
}
core::task::Poll::Pending
}
}
impl Drop for SpiFuture {
impl Drop for SpiFuture<'_> {
fn drop(&mut self) {
if !self.finished_regularly.get() {
// It might be sufficient to disable and enable the SPI.. But this definitely
@@ -456,68 +524,100 @@ pub struct SpiAsync(pub Spi);
impl SpiAsync {
pub fn new(spi: Spi) -> Self {
match spi.inner.id {
SpiId::Spi0 => {
unsafe fn spi0_interrupt_handler() {
unsafe {
on_interrupt(SpiId::Spi0);
}
}
crate::register_interrupt(
crate::gic::Interrupt::Spi(crate::gic::SpiInterrupt::Spi0),
spi0_interrupt_handler,
)
}
SpiId::Spi1 => {
unsafe fn spi1_interrupt_handler() {
unsafe {
on_interrupt(SpiId::Spi1);
}
}
crate::register_interrupt(
crate::gic::Interrupt::Spi(crate::gic::SpiInterrupt::Spi1),
spi1_interrupt_handler,
)
}
}
Self(spi)
}
async fn read(&mut self, words: &mut [u8]) {
pub fn read(&mut self, words: &mut [u8]) -> Option<SpiFuture<'_>> {
if words.is_empty() {
return;
return None;
}
let id = self.0.inner.id;
let spi_fut = SpiFuture::new_for_read(&mut self.0, id, words);
spi_fut.await;
Some(SpiFuture::new_for_read(&mut self.0, id, words))
}
async fn write(&mut self, words: &[u8]) {
pub fn write(&mut self, words: &[u8]) -> Option<SpiFuture<'_>> {
if words.is_empty() {
return;
return None;
}
let id = self.0.inner.id;
let spi_fut = SpiFuture::new_for_write(&mut self.0, id, words);
spi_fut.await;
Some(SpiFuture::new_for_write(&mut self.0, id, words))
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) {
pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Option<SpiFuture<'_>> {
if read.is_empty() || write.is_empty() {
return;
return None;
}
let id = self.0.inner.id;
let spi_fut = SpiFuture::new_for_transfer(&mut self.0, id, read, write);
spi_fut.await;
Some(SpiFuture::new_for_transfer(&mut self.0, id, read, write))
}
async fn transfer_in_place(&mut self, words: &mut [u8]) {
pub fn transfer_in_place(&mut self, words: &mut [u8]) -> Option<SpiFuture<'_>> {
if words.is_empty() {
return;
return None;
}
let id = self.0.inner.id;
let spi_fut = SpiFuture::new_for_transfer_in_place(&mut self.0, id, words);
spi_fut.await;
Some(SpiFuture::new_for_transfer_in_place(&mut self.0, id, words))
}
}
impl embedded_hal_async::spi::ErrorType for SpiAsync {
type Error = Infallible;
type Error = RxOverrunError;
}
impl embedded_hal_async::spi::SpiBus for SpiAsync {
async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
self.read(words).await;
if words.is_empty() {
return Ok(());
}
self.read(words).unwrap().await?;
Ok(())
}
async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
self.write(words).await;
if words.is_empty() {
return Ok(());
}
self.write(words).unwrap().await?;
Ok(())
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
self.transfer(read, write).await;
if read.is_empty() && write.is_empty() {
return Ok(());
}
self.transfer(read, write).unwrap().await?;
Ok(())
}
async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
self.transfer_in_place(words).await;
if words.is_empty() {
return Ok(());
}
self.transfer_in_place(words).unwrap().await?;
Ok(())
}
@@ -547,7 +647,7 @@ impl<Delay: embedded_hal_async::delay::DelayNs> SpiWithHwCsAsync<Delay> {
impl<Delay: embedded_hal_async::delay::DelayNs> embedded_hal_async::spi::ErrorType
for SpiWithHwCsAsync<Delay>
{
type Error = Infallible;
type Error = RxOverrunError;
}
impl<Delay: embedded_hal_async::delay::DelayNs> embedded_hal_async::spi::SpiDevice
@@ -561,16 +661,24 @@ impl<Delay: embedded_hal_async::delay::DelayNs> embedded_hal_async::spi::SpiDevi
for op in operations {
match op {
embedded_hal::spi::Operation::Read(items) => {
self.spi.read(items).await;
if let Some(fut) = self.spi.read(items) {
fut.await?;
}
}
embedded_hal::spi::Operation::Write(items) => {
self.spi.write(items).await;
if let Some(fut) = self.spi.write(items) {
fut.await?;
}
}
embedded_hal::spi::Operation::Transfer(read, write) => {
self.spi.transfer(read, write).await;
if let Some(fut) = self.spi.transfer(read, write) {
fut.await?;
}
}
embedded_hal::spi::Operation::TransferInPlace(items) => {
self.spi.transfer_in_place(items).await;
if let Some(fut) = self.spi.transfer_in_place(items) {
fut.await?;
}
}
embedded_hal::spi::Operation::DelayNs(delay) => {
self.delay.delay_ns(*delay).await;
+329 -149
View File
@@ -17,12 +17,12 @@ use crate::{enable_amba_peripheral_clock, spi_mode_const_to_cpol_cpha};
use crate::{clocks::IoClocks, slcr::Slcr, time::Hertz};
use arbitrary_int::{prelude::*, u3, u4, u6};
use embedded_hal::delay::DelayNs;
pub use embedded_hal::spi::Mode;
use embedded_hal::spi::SpiBus as _;
use zynq7000::slcr::reset::DualRefAndClockReset;
pub use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, Mode};
use zynq7000::slcr::reset::DualRefAndClockResetSpiUart;
pub use zynq7000::spi::DelayControl;
use zynq7000::spi::{
BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptMask, InterruptStatus,
MmioRegisters, SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
BaudDivSel, FifoWrite, InterruptControl, InterruptEnabled, InterruptStatus, MmioRegisters,
SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
};
pub const FIFO_DEPTH: usize = 128;
@@ -31,12 +31,40 @@ pub const MODULE_ID: u32 = 0x90106;
pub mod asynch;
pub use asynch::*;
pub mod slave;
pub use slave::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpiId {
Spi0 = 0,
Spi1 = 1,
}
impl SpiId {
#[inline]
pub const fn interrupt_id(&self) -> crate::Interrupt {
match self {
SpiId::Spi0 => crate::Interrupt::Spi(crate::SpiInterrupt::Spi0),
SpiId::Spi1 => crate::Interrupt::Spi(crate::SpiInterrupt::Spi1),
}
}
/// Unsafely steal the register block.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
#[inline]
pub unsafe fn steal_regs(&self) -> MmioRegisters<'static> {
match self {
SpiId::Spi0 => unsafe { zynq7000::spi::Registers::new_mmio_fixed_0() },
SpiId::Spi1 => unsafe { zynq7000::spi::Registers::new_mmio_fixed_1() },
}
}
}
pub trait PsSpi {
fn reg_block(&self) -> MmioRegisters<'static>;
fn id(&self) -> Option<SpiId>;
@@ -349,17 +377,56 @@ impl ChipSelect {
}
}
/// This abstraction which can be used to map a hardware chip select pin
/// to [embedded_hal::digital::OutputPin]. This is useful for creating physical chip select
/// pins required by the [embedded_hal_bus](https://docs.rs/embedded-hal-bus/latest/embedded_hal_bus/)
/// API.
pub struct ChipSelectPin {
spi_id: SpiId,
cs: ChipSelect,
}
impl ChipSelectPin {
/// Chip select pin constructor.
pub const fn new(spi_id: SpiId, cs: ChipSelect) -> Self {
Self { spi_id, cs }
}
}
impl embedded_hal::digital::ErrorType for ChipSelectPin {
type Error = Infallible;
}
impl embedded_hal::digital::OutputPin for ChipSelectPin {
fn set_low(&mut self) -> Result<(), Self::Error> {
// Safety: We only touch the CS register bits of the specified peripheral.
let mut spi_regs = unsafe { SpiLowLevel::steal(self.spi_id).regs };
spi_regs.modify_config(|val| val.with_cs_raw(self.cs.raw_reg()));
Ok(())
}
fn set_high(&mut self) -> Result<(), Self::Error> {
// Safety: We only touch the CS register bits of the specified peripheral.
let mut spi_regs = unsafe { SpiLowLevel::steal(self.spi_id).regs };
spi_regs.modify_config(|val| val.with_cs_raw(u4::MAX));
Ok(())
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
/// Slave select configuration.
pub enum SlaveSelectConfig {
/// User must take care of controlling slave select lines as well as issuing a start command.
ManualWithManualStart = 0b11,
ManualAutoStart = 0b10,
ManualCsManualStart = 0b11,
/// Software controls the slave select, but the controller hardware automatically starts to
/// serialize data when there is data in the TxFIFO.
ManualCsAutoStart = 0b10,
/// Hardware slave select, but start needs to be issued manually.
AutoWithManualStart = 0b01,
/// Hardware slave select, auto serialiation if there is data in the TX FIFO.
AutoCsManualStart = 0b01,
/// Hardware slave select, auto serialiation if there is data in the TX FIFO. Might be
/// problematic for higher SPI speeds, where the processor can not fill the TX FIFO fast enough.
#[default]
AutoWithAutoStart = 0b00,
AutoCsAutoStart = 0b00,
}
#[derive(Debug, Copy, Clone)]
@@ -380,6 +447,31 @@ impl Config {
}
}
pub fn calculate_for_io_clock(
target_clock: Hertz,
io_clock: &IoClocks,
init_mode: Mode,
ss_config: SlaveSelectConfig,
) -> Self {
let divisor_raw = io_clock.spi_clk().to_raw().div_ceil(target_clock.to_raw());
let baud_div_sel = match divisor_raw {
0..=4 => BaudDivSel::By4,
5..=8 => BaudDivSel::By8,
9..=16 => BaudDivSel::By16,
17..=32 => BaudDivSel::By32,
33..=64 => BaudDivSel::By64,
65..=128 => BaudDivSel::By128,
129..=256 => BaudDivSel::By256,
_ => BaudDivSel::By256,
};
Self {
baud_div: baud_div_sel,
init_mode,
ss_config,
with_ext_decoding: false,
}
}
pub fn enable_external_decoding(&mut self) {
self.with_ext_decoding = true;
}
@@ -423,6 +515,18 @@ impl SpiLowLevel {
}
}
pub fn enable_ref_clock(&mut self) {
// Safety: We only touch register bits of the specified peripheral to enable the clock.
unsafe {
Slcr::with(|slcr| {
slcr.clk_ctrl().modify_spi_clk_ctrl(|val| match self.id {
SpiId::Spi0 => val.with_clk_0_act(true),
SpiId::Spi1 => val.with_clk_1_act(true),
});
});
}
}
pub fn id(&self) -> SpiId {
self.id
}
@@ -448,7 +552,7 @@ impl SpiLowLevel {
/// the external decoding was enabled via the [Config::enable_external_decoding] option.
#[inline]
pub fn select_hw_cs(&mut self, chip_select: ChipSelect) {
self.regs.modify_cr(|mut val| {
self.regs.modify_config(|mut val| {
val.set_cs_raw(chip_select.raw_reg());
val
});
@@ -458,7 +562,7 @@ impl SpiLowLevel {
#[inline]
pub fn configure_mode(&mut self, mode: Mode) {
let (cpol, cpha) = spi_mode_const_to_cpol_cpha(mode);
self.regs.modify_cr(|mut val| {
self.regs.modify_config(|mut val| {
val.set_cpha(cpha);
val.set_cpol(cpol);
val
@@ -475,14 +579,14 @@ impl SpiLowLevel {
pub fn reconfigure(&mut self, config: Config) {
self.regs.write_enable(0);
let (man_ss, man_start) = match config.ss_config {
SlaveSelectConfig::ManualWithManualStart => (true, true),
SlaveSelectConfig::ManualAutoStart => (true, false),
SlaveSelectConfig::AutoWithManualStart => (false, true),
SlaveSelectConfig::AutoWithAutoStart => (false, false),
SlaveSelectConfig::ManualCsManualStart => (true, true),
SlaveSelectConfig::ManualCsAutoStart => (true, false),
SlaveSelectConfig::AutoCsManualStart => (false, true),
SlaveSelectConfig::AutoCsAutoStart => (false, false),
};
let (cpol, cpha) = spi_mode_const_to_cpol_cpha(config.init_mode);
self.regs.write_cr(
self.regs.write_config(
zynq7000::spi::Config::builder()
.with_modefail_gen_en(false)
.with_manual_start(false)
@@ -493,7 +597,7 @@ impl SpiLowLevel {
.with_baud_rate_div(config.baud_div)
.with_cpha(cpha)
.with_cpol(cpol)
.with_master_ern(true)
.with_mode(zynq7000::spi::Mode::Master)
.build(),
);
// Configures for polling mode by default: TX trigger by one will lead to the
@@ -521,30 +625,27 @@ impl SpiLowLevel {
#[inline(always)]
pub fn write_fifo_unchecked(&mut self, data: u8) {
self.regs.write_txd(FifoWrite::new(data));
self.regs.write_tx_data(FifoWrite::new(data));
}
#[inline(always)]
pub fn read_fifo_unchecked(&mut self) -> u8 {
self.regs.read_rxd().value()
self.regs.read_rx_data().value()
}
#[inline]
pub fn issue_manual_start(&mut self) {
self.regs.modify_cr(|mut val| {
val.set_manual_start(true);
val
});
self.regs.modify_config(|val| val.with_manual_start(true));
}
#[inline]
pub fn read_isr(&self) -> InterruptStatus {
self.regs.read_isr()
pub fn read_interrupt_status(&self) -> InterruptStatus {
self.regs.read_interrupt_status()
}
#[inline]
pub fn read_imr(&self) -> InterruptMask {
self.regs.read_imr()
pub fn read_enabled_interrupts(&self) -> InterruptEnabled {
self.regs.read_enabled_interrupts()
}
#[inline]
@@ -570,17 +671,22 @@ impl SpiLowLevel {
Ok(())
}
#[inline]
pub fn write_delay_control(&mut self, delay_control: DelayControl) {
self.regs.write_delay_control(delay_control);
}
/// This disables all interrupts relevant for non-blocking interrupt driven SPI operation
/// in SPI master mode.
#[inline]
pub fn disable_interrupts(&mut self) {
self.regs.write_idr(
pub fn disable_interrupts_master_mode(&mut self) {
self.regs.write_interupt_disable(
InterruptControl::builder()
.with_tx_underflow(true)
.with_rx_full(true)
.with_rx_not_empty(true)
.with_tx_full(false)
.with_tx_trig(true)
.with_tx_below_threshold(true)
.with_mode_fault(false)
.with_rx_ovr(true)
.build(),
@@ -590,44 +696,73 @@ impl SpiLowLevel {
/// This enables all interrupts relevant for non-blocking interrupt driven SPI operation
/// in SPI master mode.
#[inline]
pub fn enable_interrupts(&mut self) {
self.regs.write_ier(
pub fn enable_interrupts_master_mode(&mut self, tx_below_threshold: bool) {
self.regs.write_interrupt_enable(
InterruptControl::builder()
.with_tx_underflow(true)
.with_rx_full(true)
.with_rx_not_empty(true)
.with_tx_full(false)
.with_tx_trig(true)
.with_tx_below_threshold(tx_below_threshold)
.with_mode_fault(false)
.with_rx_ovr(true)
.build(),
);
}
/// This enables all interrupts relevant for non-blocking interrupt driven SPI operation
/// in SPI slave mode.
#[inline]
pub fn enable_interrupts_slave_mode(&mut self, mode_fault: bool) {
self.regs.write_interrupt_enable(
InterruptControl::builder()
.with_tx_underflow(true)
.with_rx_full(true)
.with_rx_not_empty(true)
.with_tx_full(false)
.with_tx_below_threshold(true)
.with_mode_fault(mode_fault)
.with_rx_ovr(true)
.build(),
);
}
/// This clears all interrupts relevant for non-blocking interrupt driven SPI operation
/// in SPI master mode.
#[inline]
pub fn clear_interrupts(&mut self) {
self.regs.write_isr(
pub fn clear_interrupts_master_mode(&mut self) {
self.regs.write_interrupt_status(
InterruptStatus::builder()
.with_tx_underflow(true)
.with_rx_full(true)
.with_rx_not_empty(true)
.with_tx_full(false)
.with_tx_not_full(true)
.with_tx_below_threshold(true)
.with_mode_fault(false)
.with_rx_ovr(true)
.with_rx_overrun(true)
.build(),
);
}
}
impl core::ops::Deref for SpiLowLevel {
type Target = MmioRegisters<'static>;
fn deref(&self) -> &Self::Target {
&self.regs
}
}
impl core::ops::DerefMut for SpiLowLevel {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.regs
}
}
/// Blocking Driver for the PS SPI peripheral in master mode.
pub struct Spi {
inner: SpiLowLevel,
sclk: Hertz,
config: Config,
outstanding_rx: bool,
}
#[derive(Debug, thiserror::Error)]
@@ -656,7 +791,6 @@ pub enum SpiConstructionError {
impl Spi {
pub fn new_no_hw_ss<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
) -> Result<Self, SpiConstructionError> {
@@ -674,17 +808,11 @@ impl Spi {
IoPeriphPin::new(spi_pins.0, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(spi_pins.1, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
Ok(Self::new_generic_unchecked(
spi_id,
spi.reg_block(),
clocks,
config,
))
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
}
pub fn new_one_hw_cs<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin, Ss: SsPin>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pin: Ss,
@@ -704,17 +832,11 @@ impl Spi {
IoPeriphPin::new(spi_pins.1, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pin, SPI_MUX_CONF, Some(false));
Ok(Self::new_generic_unchecked(
spi_id,
spi.reg_block(),
clocks,
config,
))
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
}
pub fn new_with_two_hw_cs<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin, Ss0: SsPin, Ss1: SsPin>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pins: (Ss0, Ss1),
@@ -744,12 +866,7 @@ impl Spi {
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.0, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.1, SPI_MUX_CONF, Some(false));
Ok(Self::new_generic_unchecked(
spi_id,
spi.reg_block(),
clocks,
config,
))
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
}
pub fn new_with_three_hw_cs<
@@ -761,7 +878,6 @@ impl Spi {
Ss2: SsPin,
>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pins: (Ss0, Ss1, Ss2),
@@ -794,36 +910,50 @@ impl Spi {
IoPeriphPin::new(ss_pins.0, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.1, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.2, SPI_MUX_CONF, Some(false));
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
}
/// Constructor for usage with EMIO pins.
pub fn new_for_emio(spi: impl PsSpi, config: Config) -> Result<Self, InvalidPsSpiError> {
let spi_id = spi.id();
if spi_id.is_none() {
return Err(InvalidPsSpiError);
}
Ok(Self::new_generic_unchecked(
spi_id,
spi_id.unwrap(),
spi.reg_block(),
clocks,
config,
))
}
pub fn new_generic_unchecked(
id: SpiId,
regs: MmioRegisters<'static>,
clocks: &IoClocks,
config: Config,
) -> Self {
pub fn new_generic_unchecked(id: SpiId, regs: MmioRegisters<'static>, config: Config) -> Self {
let periph_sel = match id {
SpiId::Spi0 => crate::PeriphSelect::Spi0,
SpiId::Spi1 => crate::PeriphSelect::Spi1,
};
let mut ll = SpiLowLevel { id, regs };
ll.enable_ref_clock();
enable_amba_peripheral_clock(periph_sel);
let sclk = clocks.spi_clk() / config.baud_div.div_value() as u32;
let mut spi = Self {
inner: SpiLowLevel { regs, id },
sclk,
config,
outstanding_rx: false,
};
let mut spi = Self { inner: ll, config };
spi.reset_and_reconfigure();
spi
}
#[inline]
pub const fn id(&self) -> SpiId {
self.inner.id
}
#[inline]
pub const fn interrupt_id(&self) -> crate::Interrupt {
self.inner.id.interrupt_id()
}
pub fn write_delay_control(&mut self, delay_control: DelayControl) {
self.inner.write_delay_control(delay_control);
}
/// Re-configures the SPI peripheral with the initial configuration.
pub fn reconfigure(&mut self) {
self.inner.reconfigure(self.config);
@@ -839,19 +969,13 @@ impl Spi {
#[inline]
pub fn issue_manual_start_for_manual_cfg(&mut self) {
if self.config.ss_config == SlaveSelectConfig::AutoWithManualStart
|| self.config.ss_config == SlaveSelectConfig::ManualWithManualStart
if self.config.ss_config == SlaveSelectConfig::AutoCsManualStart
|| self.config.ss_config == SlaveSelectConfig::ManualCsManualStart
{
self.inner.issue_manual_start();
}
}
/// Retrieve SCLK clock frequency currently configured for this SPI.
#[inline]
pub const fn sclk(&self) -> Hertz {
self.sclk
}
/// Retrieve inner low-level helper.
#[inline]
pub const fn inner(&mut self) -> &mut SpiLowLevel {
@@ -863,7 +987,7 @@ impl Spi {
&mut self.inner.regs
}
fn initial_fifo_fill(&mut self, words: &[u8]) -> usize {
fn prefill_fifo(&mut self, words: &[u8]) -> usize {
let write_len = core::cmp::min(FIFO_DEPTH, words.len());
(0..write_len).for_each(|idx| {
self.inner.write_fifo_unchecked(words[idx]);
@@ -874,32 +998,26 @@ impl Spi {
fn prepare_generic_blocking_transfer(&mut self, words: &[u8]) -> usize {
// We want to ensure the FIFO is empty for a new transfer. This is the simpler
// implementation for now.
self.flush().unwrap();
self.flush();
// Write this to 1 in any case to allow polling, defensive programming.
self.inner.regs.write_rx_trig(1);
// Fill the FIFO with initial data.
let written = self.initial_fifo_fill(words);
let written = self.prefill_fifo(words);
// We assume that the slave select configuration was already performed, but we take
// care of issuing a start if necessary.
self.issue_manual_start_for_manual_cfg();
written
}
}
impl embedded_hal::spi::ErrorType for Spi {
type Error = Infallible;
}
impl embedded_hal::spi::SpiBus for Spi {
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
pub fn read(&mut self, words: &mut [u8]) {
if words.is_empty() {
return Ok(());
return;
}
// We want to ensure the FIFO is empty for a new transfer. This is the simpler
// implementation for now.
self.flush()?;
self.flush();
// Write this to 1 in any case to allow polling, defensive programming.
self.regs().write_rx_trig(1);
@@ -915,7 +1033,7 @@ impl embedded_hal::spi::SpiBus for Spi {
let mut read_idx = 0;
while read_idx < words.len() {
let status = self.regs().read_isr();
let status = self.regs().read_interrupt_status();
if status.rx_not_empty() {
words[read_idx] = self.inner.read_fifo_unchecked();
read_idx += 1;
@@ -926,40 +1044,42 @@ impl embedded_hal::spi::SpiBus for Spi {
write_idx += 1;
}
}
Ok(())
}
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
pub fn write(&mut self, words: &[u8]) {
if words.is_empty() {
return Ok(());
return;
}
let mut written = self.prepare_generic_blocking_transfer(words);
let mut read_idx = 0;
if words.len() > FIFO_DEPTH {
self.inner.regs.write_tx_trig(FIFO_DEPTH as u32 / 2);
}
while written < words.len() {
let status = self.regs().read_isr();
loop {
let status = self.regs().read_interrupt_status();
let rx_pending = read_idx < words.len();
let tx_pending = written < words.len();
// We empty the FIFO to prevent it filling up completely, as long as we have to write
// bytes
if status.rx_not_empty() {
if status.rx_not_empty() && rx_pending {
self.inner.read_fifo_unchecked();
read_idx += 1;
}
if !status.tx_full() {
if !status.tx_full() && tx_pending {
self.inner.write_fifo_unchecked(words[written]);
written += 1;
}
if !rx_pending && !tx_pending {
break;
}
}
// We exit once all bytes have been written, so some bytes to read might be outstanding.
// We use the FIFO trigger mechanism to determine when we can read all the remaining bytes.
self.regs().write_rx_trig((words.len() - read_idx) as u32);
self.outstanding_rx = true;
Ok(())
self.inner.regs.write_tx_trig(1);
}
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) {
if read.is_empty() {
return Ok(());
return;
}
let mut write_idx = self.prepare_generic_blocking_transfer(write);
let mut read_idx = 0;
@@ -968,7 +1088,7 @@ impl embedded_hal::spi::SpiBus for Spi {
let mut writes_finished = write_idx == max_idx;
let mut reads_finished = false;
while !writes_finished || !reads_finished {
let status = self.regs().read_isr();
let status = self.regs().read_interrupt_status();
if status.rx_not_empty() && !reads_finished {
if read_idx < read.len() {
read[read_idx] = self.inner.read_fifo_unchecked();
@@ -991,13 +1111,11 @@ impl embedded_hal::spi::SpiBus for Spi {
writes_finished = write_idx == max_idx;
reads_finished = read_idx == max_idx;
}
Ok(())
}
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
pub fn transfer_in_place(&mut self, words: &mut [u8]) {
if words.is_empty() {
return Ok(());
return;
}
let mut write_idx = self.prepare_generic_blocking_transfer(words);
let mut read_idx = 0;
@@ -1005,7 +1123,7 @@ impl embedded_hal::spi::SpiBus for Spi {
let mut writes_finished = write_idx == words.len();
let mut reads_finished = false;
while !writes_finished || !reads_finished {
let status = self.inner.read_isr();
let status = self.inner.read_interrupt_status();
if status.rx_not_empty() && !reads_finished {
words[read_idx] = self.inner.read_fifo_unchecked();
read_idx += 1;
@@ -1018,22 +1136,51 @@ impl embedded_hal::spi::SpiBus for Spi {
writes_finished = write_idx == words.len();
reads_finished = read_idx == words.len();
}
}
/// Blocking flush implementation.
fn flush(&mut self) {
self.inner.write_tx_trig(1);
let status = self.inner.read_interrupt_status();
while self.inner.read_interrupt_status().rx_not_empty() {
self.inner.read_fifo_unchecked();
}
while status.tx_full() {
while self.inner.read_interrupt_status().rx_not_empty() {
self.inner.read_fifo_unchecked();
}
}
}
}
impl embedded_hal::spi::ErrorType for Spi {
type Error = Infallible;
}
impl embedded_hal::spi::SpiBus for Spi {
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
Self::read(self, words);
Ok(())
}
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
Self::write(self, words);
Ok(())
}
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
Self::transfer(self, read, write);
Ok(())
}
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
Self::transfer_in_place(self, words);
Ok(())
}
/// Blocking flush implementation.
fn flush(&mut self) -> Result<(), Self::Error> {
if !self.outstanding_rx {
return Ok(());
}
let rx_trig = self.inner.read_rx_not_empty_threshold();
while !self.inner.read_isr().rx_not_empty() {}
(0..rx_trig).for_each(|_| {
self.inner.read_fifo_unchecked();
});
self.inner.set_rx_fifo_trigger(1).unwrap();
self.outstanding_rx = false;
Self::flush(self);
Ok(())
}
}
@@ -1067,23 +1214,23 @@ impl<Delay: DelayNs> embedded_hal::spi::SpiDevice for SpiWithHwCs<Delay> {
for op in operations {
match op {
embedded_hal::spi::Operation::Read(items) => {
self.spi.read(items)?;
self.spi.read(items);
}
embedded_hal::spi::Operation::Write(items) => {
self.spi.write(items)?;
self.spi.write(items);
}
embedded_hal::spi::Operation::Transfer(read, write) => {
self.spi.transfer(read, write)?;
self.spi.transfer(read, write);
}
embedded_hal::spi::Operation::TransferInPlace(items) => {
self.spi.transfer_in_place(items)?;
self.spi.transfer_in_place(items);
}
embedded_hal::spi::Operation::DelayNs(delay) => {
self.delay.delay_ns(*delay);
}
}
}
self.spi.flush()?;
self.spi.flush();
self.spi.inner.no_hw_cs();
Ok(())
}
@@ -1096,13 +1243,13 @@ impl<Delay: DelayNs> embedded_hal::spi::SpiDevice for SpiWithHwCs<Delay> {
#[inline]
pub fn reset(id: SpiId) {
let assert_reset = match id {
SpiId::Spi0 => DualRefAndClockReset::builder()
SpiId::Spi0 => DualRefAndClockResetSpiUart::builder()
.with_periph1_ref_rst(false)
.with_periph0_ref_rst(true)
.with_periph1_cpu1x_rst(false)
.with_periph0_cpu1x_rst(true)
.build(),
SpiId::Spi1 => DualRefAndClockReset::builder()
SpiId::Spi1 => DualRefAndClockResetSpiUart::builder()
.with_periph1_ref_rst(true)
.with_periph0_ref_rst(false)
.with_periph1_cpu1x_rst(true)
@@ -1114,10 +1261,11 @@ pub fn reset(id: SpiId) {
regs.reset_ctrl().write_spi(assert_reset);
// Keep it in reset for some cycles.. The TMR just mentions some small delay,
// no idea what is meant with that.
for _ in 0..3 {
for _ in 0..10 {
aarch32_cpu::asm::nop();
}
regs.reset_ctrl().write_spi(DualRefAndClockReset::DEFAULT);
regs.reset_ctrl()
.write_spi(DualRefAndClockResetSpiUart::ZERO);
});
}
}
@@ -1127,23 +1275,23 @@ pub fn reset(id: SpiId) {
/// The Zynq7000 SPI peripheral has the following requirement for the SPI reference clock:
/// It must be larger than the CPU 1X clock. Therefore, the divisor used to calculate the reference
/// clock has a maximum value, which can be calculated with this function.
/// [configure_spi_ref_clock_with_divisor] can be used to configure the SPI reference clock with a
/// divisor.
///
/// [configure_spi_ref_clk] can be used to configure the SPI reference clock with the calculated
/// value.
/// *NOTE* - It is recommended to avoid the largest theoretical value which was proven to be
/// problematic for driving certain sensors and instead take a smaller value! Reduce the divisor
/// calculated by this function subtracting a small value to get a functioning SPI clock.
pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option<u6> {
let slcr = unsafe { Slcr::steal() };
let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl();
let div = match spi_clk_ctrl.srcsel() {
let ref_clock = match spi_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
clks.io_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
clks.arm_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
}
zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
clks.ddr_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
clks.io_clocks().ref_clk().to_raw()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => clks.arm_clocks().ref_clk().to_raw(),
zynq7000::slcr::clocks::SrcSelIo::DdrPll => clks.ddr_clocks().ref_clk().to_raw(),
};
let div = ref_clock.div_ceil(clks.arm_clocks().cpu_1x_clk().to_raw());
if div > u6::MAX.value() as u32 {
return None;
}
@@ -1151,7 +1299,28 @@ pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option<u6
Some(u6::new(div as u8))
}
pub fn configure_spi_ref_clk(clks: &mut Clocks, divisor: u6) {
/// Configures the SPI reference clock.
///
/// It is strongly advised to take a clock value which is substantially higher than the CPU 1x
/// clock. It was proven that taking values which are only slightly larger than the CPU 1x
/// clock are problematic for driving ceratin devices.
pub fn configure_spi_ref_clock(clks: &mut Clocks, target_clock: Hertz) {
let slcr = unsafe { Slcr::steal() };
let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl();
let ref_clk = match spi_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
clks.io_clocks().ref_clk().to_raw()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => clks.arm_clocks().ref_clk().to_raw(),
zynq7000::slcr::clocks::SrcSelIo::DdrPll => clks.ddr_clocks().ref_clk().to_raw(),
};
let div = ref_clk.div_ceil(target_clock.to_raw());
if div > u6::MAX.value() as u32 {
configure_spi_ref_clock_with_divisor(clks, u6::new(div as u8));
}
}
pub fn configure_spi_ref_clock_with_divisor(clks: &mut Clocks, divisor: u6) {
let mut slcr = unsafe { Slcr::steal() };
let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl();
slcr.modify(|regs| {
@@ -1173,3 +1342,14 @@ pub fn configure_spi_ref_clk(clks: &mut Clocks, divisor: u6) {
};
clks.io_clocks_mut().update_spi_clk(new_clk);
}
/// Connects SPI0 output signals to SPI1 input signals and vice-versa.
#[inline]
pub fn enable_spi0_to_spi1_loopback() {
// Safety: We only modify the SPI bit.
unsafe {
Slcr::with(|slcr| {
slcr.modify_mio_loopback(|val| val.with_spi0_loop_spi1(true));
});
}
}
+76
View File
@@ -0,0 +1,76 @@
use zynq7000::spi::{Config, FifoWrite, MmioRegisters};
use crate::{
enable_amba_peripheral_clock,
spi::{SpiId, SpiLowLevel},
spi_mode_const_to_cpol_cpha,
};
pub struct SpiSlave(pub SpiLowLevel);
pub struct SlaveConfig {
pub mode: embedded_hal::spi::Mode,
pub enable_modefail: bool,
}
impl SpiSlave {
/// Creates a very simple SPI slave driver.
///
/// The SPI slave will not start in the enabled state to allow pre-loading the FIFO.
pub fn new(id: SpiId, regs: MmioRegisters<'static>, config: SlaveConfig) -> Self {
let periph_sel = match id {
SpiId::Spi0 => crate::PeriphSelect::Spi0,
SpiId::Spi1 => crate::PeriphSelect::Spi1,
};
let mut ll = SpiLowLevel { id, regs };
ll.enable_ref_clock();
enable_amba_peripheral_clock(periph_sel);
let (cpol, cpha) = spi_mode_const_to_cpol_cpha(config.mode);
ll.write_config(
Config::ZERO
.with_modefail_gen_en(config.enable_modefail)
.with_cpha(cpha)
.with_cpol(cpol),
);
Self(ll)
}
#[inline]
pub const fn id(&self) -> SpiId {
self.0.id
}
#[inline]
pub const fn interrupt_id(&self) -> crate::Interrupt {
self.id().interrupt_id()
}
#[inline]
pub fn enable_interrupts(&mut self, mode_error: bool) {
self.0.enable_interrupts_slave_mode(mode_error);
}
/// Enable the SPI slave. This will allow it to respond to transfers initiated by the master.
///
/// Please refer to the word detection in the TRM p.568 for more details.
#[inline]
pub fn enable(&mut self) {
self.0.enable();
}
/// This register is used for IDLE state detection on the SCLK line.
///
/// Please refer to the word detection in the TRM p.568 for more details how this is useful.
#[inline]
pub fn write_slave_idle_counter(&mut self, count: u8) {
self.0.write_sicr(count as u32);
}
/// Write data to the TX FIFO, which allows pre-loading data which will be sent via the
/// MISO line when the master initiates a transfer.
#[inline]
pub fn write_tx_data(&mut self, data: u8) {
self.0.write_tx_data(FifoWrite::new(data));
}
}
+2 -2
View File
@@ -275,7 +275,7 @@ impl Pwm {
ref_clk: Hertz,
freq: Hertz,
) -> Result<Self, FrequencyIsZeroError> {
if freq.raw() == 0 {
if freq.to_raw() == 0 {
return Err(FrequencyIsZeroError);
}
let (prescaler_reg, tick_val) = calc_prescaler_reg_and_interval_ticks(ref_clk, freq);
@@ -289,7 +289,7 @@ impl Pwm {
///
/// This resets the duty cycle to 0%.
pub fn set_frequency(&mut self, freq: Hertz) -> Result<(), FrequencyIsZeroError> {
if freq.raw() == 0 {
if freq.to_raw() == 0 {
return Err(FrequencyIsZeroError);
}
let id = self.channel.id() as usize;
+86 -41
View File
@@ -13,7 +13,7 @@ use core::convert::Infallible;
use arbitrary_int::u3;
use libm::round;
use zynq7000::{
slcr::reset::DualRefAndClockReset,
slcr::reset::DualRefAndClockResetSpiUart,
uart::{
BaudRateDivisor, Baudgen, ChMode, ClockSelect, FifoTrigger, InterruptControl,
MmioRegisters, Mode, UART_0_BASE, UART_1_BASE,
@@ -66,6 +66,32 @@ pub enum UartId {
Uart1 = 1,
}
impl UartId {
/// Interrupt ID.
#[inline]
pub const fn interrupt_id(&self) -> crate::Interrupt {
match self {
UartId::Uart0 => crate::Interrupt::Spi(crate::SpiInterrupt::Uart0),
UartId::Uart1 => crate::Interrupt::Spi(crate::SpiInterrupt::Uart1),
}
}
/// Unsafely steal the register block.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
#[inline]
pub unsafe fn steal_regs(&self) -> MmioRegisters<'static> {
match self {
UartId::Uart0 => unsafe { zynq7000::uart::Registers::new_mmio_fixed_0() },
UartId::Uart1 => unsafe { zynq7000::uart::Registers::new_mmio_fixed_1() },
}
}
}
/// Common trait for PS UART peripherals.
pub trait PsUart {
/// UART register block.
@@ -141,7 +167,7 @@ pub struct DivisorZero;
macro_rules! pin_pairs {
($index:literal, $UartPeriph:path, ($( [$(#[$meta:meta], )? $TxMio:ident, $RxMio:ident] ),+ $(,)? )) => {
$(
paste::paste! {
pastey::paste! {
$( #[$meta] )?
impl [<TxPin $index>] for Pin<$TxMio> {}
@@ -254,8 +280,8 @@ pub fn calculate_viable_configs(
}
let mut current_clk_config = ClockConfig::default();
for bdiv in 4..u8::MAX {
let cd =
round(uart_clk.raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64)) as u64;
let cd = round(uart_clk.to_raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64))
as u64;
if cd > u16::MAX as u64 {
continue;
}
@@ -290,8 +316,8 @@ pub fn calculate_raw_baud_cfg_smallest_error(
let mut best_clk_config = ClockConfig::default();
let mut smallest_error: f64 = 100.0;
for bdiv in 4..u8::MAX {
let cd =
round(uart_clk.raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64)) as u64;
let cd = round(uart_clk.to_raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64))
as u64;
if cd > u16::MAX as u64 {
continue;
}
@@ -369,7 +395,7 @@ impl ClockConfig {
/// Actual baudrate.
#[inline]
pub fn actual_baud(&self, sel_clk: Hertz) -> f64 {
sel_clk.raw() as f64 / (self.cd as f64 * (self.bdiv + 1) as f64)
sel_clk.to_raw() as f64 / (self.cd as f64 * (self.bdiv + 1) as f64)
}
}
@@ -466,7 +492,6 @@ impl Config {
pub struct Uart {
rx: Rx,
tx: Tx,
cfg: Config,
}
/// Invalid PS UART error.
@@ -565,35 +590,40 @@ impl Uart {
UartId::Uart0 => crate::PeriphSelect::Uart0,
UartId::Uart1 => crate::PeriphSelect::Uart1,
};
// Safety: We only touch register bits of the specified peripheral to enable the clock.
unsafe {
Slcr::with(|slcr| {
slcr.clk_ctrl().modify_uart_clk_ctrl(|val| match uart_id {
UartId::Uart0 => val.with_clk_0_act(true),
UartId::Uart1 => val.with_clk_1_act(true),
});
});
}
enable_amba_peripheral_clock(periph_sel);
reset(uart_id);
reg_block.modify_cr(|mut v| {
v.set_tx_dis(true);
v.set_rx_dis(true);
v
});
reg_block.modify_control(|v| v.with_tx_disable(true).with_rx_disable(true));
// Disable all interrupts.
reg_block.write_idr(InterruptControl::new_with_raw_value(0xFFFF_FFFF));
reg_block.write_interrupt_disable(InterruptControl::new_with_raw_value(0xFFFF_FFFF));
let mode = Mode::builder()
.with_chmode(cfg.chmode)
.with_nbstop(match cfg.stopbits {
.with_stopbits(match cfg.stopbits {
Stopbits::One => zynq7000::uart::Stopbits::One,
Stopbits::OnePointFive => zynq7000::uart::Stopbits::OnePointFive,
Stopbits::Two => zynq7000::uart::Stopbits::Two,
})
.with_par(match cfg.parity {
.with_parity(match cfg.parity {
Parity::Even => zynq7000::uart::Parity::Even,
Parity::Odd => zynq7000::uart::Parity::Odd,
Parity::None => zynq7000::uart::Parity::NoParity,
})
.with_chrl(match cfg.chrl {
.with_charlen(match cfg.chrl {
CharLen::SixBits => zynq7000::uart::CharLen::SixBits,
CharLen::SevenBits => zynq7000::uart::CharLen::SevenBits,
CharLen::EightBits => zynq7000::uart::CharLen::EightBits,
})
.with_clksel(cfg.clk_sel)
.with_clock_select(cfg.clk_sel)
.build();
reg_block.write_mr(mode);
reg_block.write_mode(mode);
reg_block.write_baudgen(
Baudgen::builder()
.with_cd(cfg.raw_clk_config().cd())
@@ -605,9 +635,9 @@ impl Uart {
.build(),
);
// Soft reset for both TX and RX.
reg_block.modify_cr(|mut v| {
v.set_tx_rst(true);
v.set_rx_rst(true);
reg_block.modify_control(|mut v| {
v.set_tx_reset(true);
v.set_rx_reset(true);
v
});
@@ -617,11 +647,11 @@ impl Uart {
));
// Enable TX and RX.
reg_block.modify_cr(|mut v| {
v.set_tx_dis(false);
v.set_rx_dis(false);
v.set_tx_en(true);
v.set_rx_en(true);
reg_block.modify_control(|mut v| {
v.set_tx_disable(false);
v.set_rx_disable(false);
v.set_tx_enable(true);
v.set_rx_enable(true);
v
});
@@ -631,16 +661,34 @@ impl Uart {
},
tx: Tx {
regs: reg_block,
idx: uart_id,
id: uart_id,
},
}
}
/// Steal a UART without doing ANY configuration.
///
/// # Safety
///
/// Circumvents ownership and safety guarantees by the HAL. Also, the driver will not work
/// unless the UART was configured beforehand.
pub unsafe fn steal(id: UartId) -> Uart {
let reg_block = unsafe { id.steal_regs() };
Self {
rx: Rx {
regs: unsafe { reg_block.clone() },
},
tx: Tx {
regs: reg_block,
id,
},
cfg,
}
}
/// Set character mode.
#[inline]
pub fn set_mode(&mut self, mode: ChMode) {
self.regs().modify_mr(|mut mr| {
self.regs().modify_mode(|mut mr| {
mr.set_chmode(mode);
mr
});
@@ -652,12 +700,6 @@ impl Uart {
&mut self.rx.regs
}
/// Configuration.
#[inline]
pub const fn cfg(&self) -> &Config {
&self.cfg
}
/// Split into TX and RX halves.
#[inline]
pub const fn split(self) -> (Tx, Rx) {
@@ -720,13 +762,13 @@ impl embedded_io::Read for Uart {
#[inline]
pub fn reset(id: UartId) {
let assert_reset = match id {
UartId::Uart0 => DualRefAndClockReset::builder()
UartId::Uart0 => DualRefAndClockResetSpiUart::builder()
.with_periph1_ref_rst(false)
.with_periph0_ref_rst(true)
.with_periph1_cpu1x_rst(false)
.with_periph0_cpu1x_rst(true)
.build(),
UartId::Uart1 => DualRefAndClockReset::builder()
UartId::Uart1 => DualRefAndClockResetSpiUart::builder()
.with_periph1_ref_rst(true)
.with_periph0_ref_rst(false)
.with_periph1_cpu1x_rst(true)
@@ -736,9 +778,12 @@ pub fn reset(id: UartId) {
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl().write_uart(assert_reset);
// Keep it in reset for one cycle.. not sure if this is necessary.
aarch32_cpu::asm::nop();
regs.reset_ctrl().write_uart(DualRefAndClockReset::DEFAULT);
// Keep it in reset for a few cycles.. not sure if this is necessary.
for _ in 0..5 {
aarch32_cpu::asm::nop();
}
regs.reset_ctrl()
.write_uart(DualRefAndClockResetSpiUart::ZERO);
});
}
}
+32 -26
View File
@@ -2,7 +2,7 @@
use core::convert::Infallible;
use arbitrary_int::prelude::*;
use zynq7000::uart::{InterruptControl, InterruptStatus, MmioRegisters};
use zynq7000::uart::{FifoTrigger, InterruptControl, InterruptStatus, MmioRegisters};
use super::FIFO_DEPTH;
@@ -73,7 +73,7 @@ impl Rx {
/// Read one byte from the FIFO in a non-blocking manner.
#[inline]
pub fn read_fifo(&mut self) -> nb::Result<u8, Infallible> {
if self.regs.read_sr().rx_empty() {
if self.regs.read_status().rx_empty() {
return Err(nb::Error::WouldBlock);
}
Ok(self.regs.read_fifo().fifo())
@@ -93,17 +93,17 @@ impl Rx {
/// bit clock, so this value times 4 is the number of UART clock ticks until a timeout occurs.
#[inline]
pub fn set_rx_timeout_value(&mut self, rto: u8) {
self.regs.write_rx_tout(rto as u32);
self.regs.write_rx_timeout(rto as u32);
}
/// Perform a soft-reset of the RX side of the UART.
#[inline]
pub fn soft_reset(&mut self) {
self.regs.modify_cr(|mut cr| {
cr.set_rx_rst(true);
self.regs.modify_control(|mut cr| {
cr.set_rx_reset(true);
cr
});
while self.regs.read_cr().rx_rst() {}
while self.regs.read_control().rx_reset() {}
}
/// Helper function to start the interrupt driven reception of data.
@@ -114,22 +114,34 @@ impl Rx {
///
/// This should be called once at system start-up. After that, you only need to call
/// [Self::on_interrupt] in the interrupt handler for the UART peripheral.
pub fn start_interrupt_driven_reception(&mut self) {
///
/// You can also configure a RX timeout by setting the RX timeout value `rto` which has a unit
/// of bit periods times 4. Setting a value of 0 disables the timeout feature of the hardware,
/// but this is strongly discouraged.
pub fn start_interrupt_driven_reception(&mut self, rto: u8) {
self.soft_reset();
self.set_rx_fifo_trigger_level((FIFO_DEPTH / 2) as u8);
self.set_rx_timeout_value(rto);
self.clear_interrupts();
self.enable_interrupts();
}
/// Sets the RX FIFO trigger level.
pub fn set_rx_fifo_trigger_level(&mut self, level: u8) {
self.regs
.write_rx_fifo_trigger(FifoTrigger::new_with_raw_value(level as u32));
}
/// Enables all interrupts relevant for the RX side of the UART.
///
/// It is recommended to also clear all interrupts immediately after enabling them.
#[inline]
pub fn enable_interrupts(&mut self) {
self.regs.write_ier(
self.regs.write_interrupt_enable(
InterruptControl::builder()
.with_tx_over(false)
.with_tx_near_full(false)
.with_tx_trig(false)
.with_tx_trigger(false)
.with_rx_dms(false)
.with_rx_timeout(true)
.with_rx_parity(true)
@@ -139,7 +151,7 @@ impl Rx {
.with_tx_empty(false)
.with_rx_full(true)
.with_rx_empty(false)
.with_rx_trg(true)
.with_rx_trigger(true)
.build(),
);
}
@@ -153,9 +165,9 @@ impl Rx {
reset_rx_timeout: bool,
) -> RxInterruptResult {
let mut result = RxInterruptResult::default();
let imr = self.regs.read_imr();
let imr = self.regs.read_enabled_interrupts();
if !imr.rx_full()
&& !imr.rx_trg()
&& !imr.rx_trigger()
&& !imr.rx_parity()
&& !imr.rx_framing()
&& !imr.rx_over()
@@ -163,16 +175,10 @@ impl Rx {
{
return result;
}
let isr = self.regs.read_isr();
if isr.rx_full() {
// Read all bytes in the full RX fifo.
for byte in buf.iter_mut() {
*byte = self.read_fifo_unchecked();
}
result.read_bytes = FIFO_DEPTH;
} else if isr.rx_trg() {
let isr = self.regs.read_interrupt_status();
if self.regs.read_interrupt_status().rx_trigger() {
// It is guaranteed that we can read the FIFO level amount of data
let fifo_trigger = self.regs.read_rx_fifo_trigger().trig().as_usize();
let fifo_trigger = self.regs.read_rx_fifo_trigger().trigger().as_usize();
(0..fifo_trigger).for_each(|i| {
buf[i] = self.read_fifo_unchecked();
});
@@ -197,8 +203,8 @@ impl Rx {
}
// Handle timeout event.
if isr.rx_timeout() && reset_rx_timeout {
self.regs.modify_cr(|mut cr| {
cr.set_rstto(true);
self.regs.modify_control(|mut cr| {
cr.set_restart_timeout(true);
cr
});
}
@@ -209,7 +215,7 @@ impl Rx {
/// This clears all RX related interrupts.
#[inline]
pub fn clear_interrupts(&mut self) {
self.regs.write_isr(
self.regs.write_interrupt_status(
InterruptStatus::builder()
.with_tx_over(false)
.with_tx_near_full(false)
@@ -223,7 +229,7 @@ impl Rx {
.with_tx_empty(false)
.with_rx_full(true)
.with_rx_empty(true)
.with_rx_trg(true)
.with_rx_trigger(true)
.build(),
);
}
@@ -256,7 +262,7 @@ impl embedded_io::Read for Rx {
}
let mut read = 0;
loop {
if !self.regs.read_sr().rx_empty() {
if !self.regs.read_status().rx_empty() {
break;
}
}
+33 -33
View File
@@ -8,12 +8,12 @@ use super::UartId;
/// Transmitter (TX) driver.
pub struct Tx {
pub(crate) regs: MmioRegisters<'static>,
pub(crate) idx: UartId,
pub(crate) id: UartId,
}
impl core::fmt::Debug for Tx {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Tx").field("idx", &self.idx).finish()
f.debug_struct("Tx").field("idx", &self.id).finish()
}
}
@@ -24,17 +24,17 @@ impl Tx {
///
/// Circumvents safety guarantees provided by the compiler.
#[inline]
pub const unsafe fn steal(idx: UartId) -> Self {
pub const unsafe fn steal(id: UartId) -> Self {
Tx {
regs: unsafe { idx.regs() },
idx,
regs: unsafe { id.regs() },
id,
}
}
/// UART index.
/// UART ID.
#[inline]
pub const fn uart_idx(&self) -> UartId {
self.idx
pub const fn uart_id(&self) -> UartId {
self.id
}
/// Direct access to the UART MMIO registers.
@@ -48,7 +48,7 @@ impl Tx {
/// [nb] API which returns [nb::Error::WouldBlock] if the FIFO is full.
#[inline]
pub fn write_fifo(&mut self, word: u8) -> nb::Result<(), Infallible> {
if self.regs.read_sr().tx_full() {
if self.regs.read_status().tx_full() {
return Err(nb::Error::WouldBlock);
}
self.write_fifo_unchecked(word);
@@ -61,9 +61,9 @@ impl Tx {
if with_reset {
self.soft_reset();
}
self.regs.modify_cr(|mut val| {
val.set_tx_en(true);
val.set_tx_dis(false);
self.regs.modify_control(|mut val| {
val.set_tx_enable(true);
val.set_tx_disable(false);
val
});
}
@@ -71,9 +71,9 @@ impl Tx {
/// Disables TX side of the UART.
#[inline]
pub fn disable(&mut self) {
self.regs.modify_cr(|mut val| {
val.set_tx_en(false);
val.set_tx_dis(true);
self.regs.modify_control(|mut val| {
val.set_tx_enable(false);
val.set_tx_disable(true);
val
});
}
@@ -81,12 +81,12 @@ impl Tx {
/// Performs a soft-reset of the TX side of the UART.
#[inline]
pub fn soft_reset(&mut self) {
self.regs.modify_cr(|mut val| {
val.set_tx_rst(true);
self.regs.modify_control(|mut val| {
val.set_tx_reset(true);
val
});
loop {
if !self.regs.read_cr().tx_rst() {
if !self.regs.read_control().tx_reset() {
break;
}
}
@@ -94,7 +94,7 @@ impl Tx {
/// Flushes the TX FIFO by blocking until it is empty.
pub fn flush(&mut self) {
while !self.regs.read_sr().tx_empty() {}
while !self.regs.read_status().tx_empty() {}
}
/// Write a byte to the TX FIFO without checking if there is space available.
@@ -105,12 +105,12 @@ impl Tx {
/// Enables interrupts relevant for the TX side of the UART except the TX trigger interrupt.
#[inline]
pub fn enable_interrupts(&mut self) {
self.regs.write_ier(
pub fn enable_interrupts(&mut self, tx_trig: bool) {
self.regs.write_interrupt_enable(
InterruptControl::builder()
.with_tx_over(true)
.with_tx_near_full(true)
.with_tx_trig(false)
.with_tx_near_full(false)
.with_tx_trigger(tx_trig)
.with_rx_dms(false)
.with_rx_timeout(false)
.with_rx_parity(false)
@@ -120,7 +120,7 @@ impl Tx {
.with_tx_empty(true)
.with_rx_full(false)
.with_rx_empty(false)
.with_rx_trg(false)
.with_rx_trigger(false)
.build(),
);
}
@@ -128,11 +128,11 @@ impl Tx {
/// Disable interrupts relevant for the TX side of the UART except the TX trigger interrupt.
#[inline]
pub fn disable_interrupts(&mut self) {
self.regs.write_idr(
self.regs.write_interrupt_disable(
InterruptControl::builder()
.with_tx_over(true)
.with_tx_near_full(true)
.with_tx_trig(false)
.with_tx_near_full(false)
.with_tx_trigger(true)
.with_rx_dms(false)
.with_rx_timeout(false)
.with_rx_parity(false)
@@ -142,7 +142,7 @@ impl Tx {
.with_tx_empty(true)
.with_rx_full(false)
.with_rx_empty(false)
.with_rx_trg(false)
.with_rx_trigger(false)
.build(),
);
}
@@ -150,11 +150,11 @@ impl Tx {
/// Clears interrupts relevant for the TX side of the UART except the TX trigger interrupt.
#[inline]
pub fn clear_interrupts(&mut self) {
self.regs.write_isr(
self.regs.write_interrupt_status(
InterruptStatus::builder()
.with_tx_over(true)
.with_tx_near_full(true)
.with_tx_trig(false)
.with_tx_trig(true)
.with_rx_dms(false)
.with_rx_timeout(false)
.with_rx_parity(false)
@@ -164,7 +164,7 @@ impl Tx {
.with_tx_empty(true)
.with_rx_full(false)
.with_rx_empty(false)
.with_rx_trg(false)
.with_rx_trigger(false)
.build(),
);
}
@@ -181,7 +181,7 @@ impl embedded_hal_nb::serial::Write for Tx {
}
fn flush(&mut self) -> nb::Result<(), Self::Error> {
if self.regs.read_sr().tx_empty() {
if self.regs.read_status().tx_empty() {
return Ok(());
}
Err(nb::Error::WouldBlock)
@@ -199,7 +199,7 @@ impl embedded_io::Write for Tx {
}
let mut written = 0;
loop {
if !self.regs.read_sr().tx_full() {
if !self.regs.read_status().tx_full() {
break;
}
}
+93 -25
View File
@@ -1,9 +1,11 @@
//! Asynchronous UART transmitter (TX) implementation.
use core::{cell::RefCell, convert::Infallible, sync::atomic::AtomicBool};
use core::{cell::RefCell, convert::Infallible, marker::PhantomData, sync::atomic::AtomicBool};
use arbitrary_int::u6;
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use raw_slice::RawBufSlice;
use zynq7000::uart::FifoTrigger;
use crate::uart::{FIFO_DEPTH, Tx, UartId};
@@ -17,20 +19,30 @@ static TX_DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
/// UART peripheral.
///
/// # Safety
///
/// The user has to call this once in the interrupt handler responsible for the TX interrupts on
/// the given UART bank.
pub fn on_interrupt_tx(peripheral: UartId) {
pub unsafe fn on_interrupt_tx(peripheral: UartId) {
let mut tx_with_irq = unsafe { Tx::steal(peripheral) };
let idx = peripheral as usize;
let imr = tx_with_irq.regs().read_imr();
let enabled_irqs = tx_with_irq.regs().read_enabled_interrupts();
// IRQ is not related to TX.
if !imr.tx_over() && !imr.tx_near_full() && !imr.tx_full() && !imr.tx_empty() && !imr.tx_full()
if !enabled_irqs.tx_over()
&& !enabled_irqs.tx_near_full()
&& !enabled_irqs.tx_full()
&& !enabled_irqs.tx_empty()
&& !enabled_irqs.tx_full()
{
return;
}
let isr = tx_with_irq.regs().read_isr();
let unexpected_overrun = isr.tx_over();
let interrupt_status = tx_with_irq.regs().read_interrupt_status();
// Disable interrupts, re-enable them later.
tx_with_irq.disable_interrupts();
// Clear interrupts.
tx_with_irq.clear_interrupts();
let unexpected_overrun = interrupt_status.tx_over();
let mut context = critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow()
@@ -41,7 +53,7 @@ pub fn on_interrupt_tx(peripheral: UartId) {
}
let slice_len = context.slice.len().unwrap();
context.tx_overrun = unexpected_overrun;
if (context.progress >= slice_len && isr.tx_empty()) || slice_len == 0 {
if (context.progress >= slice_len && interrupt_status.tx_empty()) || slice_len == 0 {
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
@@ -57,8 +69,10 @@ pub fn on_interrupt_tx(peripheral: UartId) {
// Safety: We documented that the user provided slice must outlive the future, so we convert
// the raw pointer back to the slice here.
let slice = unsafe { context.slice.get() }.expect("slice is invalid");
// Pump the FIFO.
while context.progress < slice_len {
if tx_with_irq.regs().read_sr().tx_full() {
if tx_with_irq.regs().read_status().tx_full() {
break;
}
// Safety: TX structure is owned by the future which does not write into the the data
@@ -66,14 +80,22 @@ pub fn on_interrupt_tx(peripheral: UartId) {
tx_with_irq.write_fifo_unchecked(slice[context.progress]);
context.progress += 1;
}
let remaining = slice_len - context.progress;
if remaining > FIFO_DEPTH {
tx_with_irq.regs.write_tx_fifo_trigger(
FifoTrigger::builder()
.with_trigger(u6::new((FIFO_DEPTH / 2) as u8))
.build(),
);
}
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = context;
});
// Clear interrupts.
tx_with_irq.clear_interrupts();
tx_with_irq.enable_interrupts(remaining > FIFO_DEPTH);
}
#[derive(Debug, Copy, Clone)]
@@ -95,17 +117,18 @@ impl TxContext {
}
/// Transmission future for UART TX.
pub struct TxFuture {
pub struct TxFuture<'uart> {
id: UartId,
marker: core::marker::PhantomData<&'uart ()>,
}
impl TxFuture {
impl<'uart> TxFuture<'uart> {
/// # Safety
///
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
/// that the slice outlives the data structure.
pub unsafe fn new(tx_with_irq: &mut Tx, data: &[u8]) -> Self {
let idx = tx_with_irq.uart_idx() as usize;
pub unsafe fn new(tx_with_irq: &'uart mut Tx, data: &[u8]) -> TxFuture<'uart> {
let idx = tx_with_irq.uart_id() as usize;
TX_DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed);
tx_with_irq.disable_interrupts();
tx_with_irq.disable();
@@ -119,19 +142,29 @@ impl TxFuture {
}
context.progress = init_fill_count; // We fill the FIFO.
});
tx_with_irq.enable(true);
// Apparently, we need to enable the UART before we are able to write something into
// the FIFO.
tx_with_irq.enable(false);
if data.len() > FIFO_DEPTH {
tx_with_irq.regs.write_tx_fifo_trigger(
FifoTrigger::builder()
.with_trigger(u6::new((FIFO_DEPTH / 2) as u8))
.build(),
);
}
for data in data.iter().take(init_fill_count) {
tx_with_irq.write_fifo_unchecked(*data);
}
tx_with_irq.enable_interrupts();
tx_with_irq.enable_interrupts(data.len() > FIFO_DEPTH);
Self {
id: tx_with_irq.uart_idx(),
id: tx_with_irq.uart_id(),
marker: PhantomData,
}
}
}
impl Future for TxFuture {
impl Future for TxFuture<'_> {
type Output = usize;
fn poll(
@@ -151,7 +184,7 @@ impl Future for TxFuture {
}
}
impl Drop for TxFuture {
impl Drop for TxFuture<'_> {
fn drop(&mut self) {
let mut tx = unsafe { Tx::steal(self.id) };
tx.disable_interrupts();
@@ -165,20 +198,52 @@ pub struct TxAsync {
impl TxAsync {
/// Constructor.
pub fn new(tx: Tx) -> Self {
///
/// The second argument specifies whether the [on_interrupt_tx] function will be registered
/// in the HAL interrupt map. You might need to skip this in case you have your own
/// interrupt handler which also handles RX interrupts.
pub fn new(tx: Tx, register_interrupt_handler: bool) -> Self {
if register_interrupt_handler {
match tx.uart_id() {
UartId::Uart0 => {
unsafe fn uart0_interrupt_handler() {
unsafe {
on_interrupt_tx(UartId::Uart0);
}
}
crate::register_interrupt(
crate::gic::Interrupt::Spi(crate::gic::SpiInterrupt::Uart0),
uart0_interrupt_handler,
)
}
UartId::Uart1 => {
unsafe fn uart1_interrupt_handler() {
unsafe {
on_interrupt_tx(UartId::Uart1);
}
}
crate::register_interrupt(
crate::gic::Interrupt::Spi(crate::gic::SpiInterrupt::Uart1),
uart1_interrupt_handler,
)
}
}
}
Self { tx }
}
/// Write a buffer asynchronously.
///
/// Returns [None] if the passed buffer is empty.
///
/// This implementation is not side effect free, and a started future might have already
/// written part of the passed buffer.
pub async fn write(&mut self, buf: &[u8]) -> usize {
pub fn write(&mut self, buf: &[u8]) -> Option<TxFuture<'_>> {
if buf.is_empty() {
return 0;
return None;
}
let fut = unsafe { TxFuture::new(&mut self.tx, buf) };
fut.await
Some(unsafe { TxFuture::new(&mut self.tx, buf) })
}
/// Release the underlying blocking TX driver.
@@ -197,7 +262,10 @@ impl embedded_io_async::Write for TxAsync {
/// This implementation is not side effect free, and a started future might have already
/// written part of the passed buffer.
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
Ok(self.write(buf).await)
if buf.is_empty() {
return Ok(0);
}
Ok(self.write(buf).unwrap().await)
}
/// This implementation does not do anything.
+11 -1
View File
@@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.2.0] 2026-05-08
- Bumped `aarch32-cpu` to v0.3
# [v0.1.2] 2026-02-14
- Bumped `aarch32-cpu` to v0.2
# [v0.1.1] 2025-10-10
Documentation fixes.
@@ -16,6 +24,8 @@ Documentation fixes.
Initial release
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-mmu-v0.1.0...HEAD
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-mmu-v0.2.0...HEAD
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-mmu-v0.1.2...zynq7000-mmu-v0.2.0
[v0.1.2]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-mmu-v0.1.1...zynq7000-mmu-v0.1.2
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-mmu-v0.1.0...zynq7000-mmu-v0.1.1
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/tag/zynq7000-mmu-v0.1.0
+2 -3
View File
@@ -1,7 +1,7 @@
[package]
name = "zynq7000-mmu"
description = "Zynq7000 MMU structures"
version = "0.1.1"
version = "0.2.0"
edition = "2024"
license = "MIT OR Apache-2.0"
homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
@@ -11,7 +11,7 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
thiserror = { version = "2", default-features = false }
aarch32-cpu = { version = "0.1" }
aarch32-cpu = { version = "0.3" }
[build-dependencies]
arm-targets = { version = "0.4" }
@@ -21,5 +21,4 @@ tools = []
[package.metadata.docs.rs]
targets = ["armv7a-none-eabihf"]
cargo-args = ["-Z", "build-std=core"]
rustdoc-args = ["--generate-link-to-definition"]
-3
View File
@@ -6,7 +6,6 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
use aarch32_cpu::mmu::L1Section;
#[cfg(all(not(feature = "tools"), arm_profile = "a"))]
use aarch32_cpu::{
asm::{dsb, isb},
cache::clean_and_invalidate_l1_data_cache,
@@ -39,7 +38,6 @@ impl L1TableRaw {
self.0.as_mut_ptr() as *mut _
}
#[cfg(all(not(feature = "tools"), arm_profile = "a"))]
pub fn update(
&mut self,
addr: u32,
@@ -92,7 +90,6 @@ impl<'a> L1TableWrapper<'a> {
}
impl L1TableWrapper<'_> {
#[cfg(all(not(feature = "tools"), arm_profile = "a"))]
pub fn update(
&mut self,
addr: u32,
+10 -1
View File
@@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.3.0] 2026-05-08
Bumped `aarch32-rt` and `aarch32-cpu` to v0.3.
# [v0.2.0] 2026-02-14
Bugfixes in startup assembler code.
## Changed
@@ -17,6 +23,7 @@ Bugfixes in startup assembler code.
- Runtime now calls a `kmain` method similar to the re-export `aarch32-rt` crate.
Former `boot_core` method must be renamed to `kmain`, but it is recommended to use
the `zynq7000-rt::entry` proc macro to annotate the main method.
- Bumped `aarch32-rt` to v0.2 which now requires the `memory.x` file to place the `STACKS` segment
## Fixed
@@ -32,6 +39,8 @@ Documentation fixes.
Initial release
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-rt-v0.1.0...HEAD
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-rt-v0.3.0...HEAD
[v0.3.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-rt-v0.2.0...zynq7000-rt-v0.3.0
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-rt-v0.1.1...zynq7000-rt-v0.2.0
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-rt-v0.1.0...zynq7000-rt-v0.1.1
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/tag/zynq7000-rt-v0.1.0
+4 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "zynq7000-rt"
version = "0.1.1"
version = "0.3.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "Run-time support for the Zynq7000 family of SoCs for running bare-metal applications"
@@ -11,10 +11,10 @@ keywords = ["no-std", "rt", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
aarch32-rt = { version = "0.1", optional = true, features = ["fpu-d32"] }
aarch32-cpu = { version = "0.1" }
aarch32-rt = { version = "0.3", optional = true, features = ["fpu-d32"] }
aarch32-cpu = { version = "0.3" }
arbitrary-int = "2"
zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.1" }
zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.2" }
[build-dependencies]
arm-targets = { version = "0.4" }
@@ -25,5 +25,4 @@ rt = ["dep:aarch32-rt"]
[package.metadata.docs.rs]
targets = ["armv7a-none-eabihf"]
cargo-args = ["-Z", "build-std=core"]
rustdoc-args = ["--generate-link-to-definition"]
+5 -5
View File
@@ -85,7 +85,7 @@ pub mod segments {
pub mod section_attrs {
use aarch32_cpu::mmu::{
AccessPermissions, CacheableMemoryAttribute, MemoryRegionAttributes, SectionAttributes,
AccessPermissions, CachePolicy, MemoryRegionAttributes, SectionAttributes,
};
use arbitrary_int::u4;
@@ -103,8 +103,8 @@ pub mod section_attrs {
domain: DDR_DOMAIN,
execute_never: false,
memory_attrs: MemoryRegionAttributes::CacheableMemory {
inner: CacheableMemoryAttribute::WriteBackWriteAlloc,
outer: CacheableMemoryAttribute::WriteBackWriteAlloc,
inner: CachePolicy::WriteBackWriteAlloc,
outer: CachePolicy::WriteBackWriteAlloc,
}
.as_raw(),
};
@@ -157,8 +157,8 @@ pub mod section_attrs {
domain: DEFAULT_DOMAIN,
execute_never: false,
memory_attrs: MemoryRegionAttributes::CacheableMemory {
inner: CacheableMemoryAttribute::WriteThroughNoWriteAlloc,
outer: CacheableMemoryAttribute::NonCacheable,
inner: CachePolicy::WriteThroughNoWriteAlloc,
outer: CachePolicy::NonCacheable,
}
.as_raw(),
};
+114 -194
View File
@@ -5,7 +5,6 @@
//! but does NOT provide the L2 cache initialization.
//!
//! The boot routine includes stack, MMU and .bss/.data section initialization.
use aarch32_cpu::register::{Cpsr, cpsr::ProcessorMode};
use aarch32_rt as _;
// Start-up code for Armv7-A
@@ -13,23 +12,23 @@ use aarch32_rt as _;
// We set up our stacks and `kmain` in system mode.
core::arch::global_asm!(
r#"
.set PSS_L2CC_BASE_ADDR, 0xF8F02000
.set PSS_SLCR_BASE_ADDR, 0xF8000000
.set PSS_L2CC_BASE_ADDR, 0xF8F02000
.set PSS_SLCR_BASE_ADDR, 0xF8000000
.set SLCRlockReg, (PSS_SLCR_BASE_ADDR + 0x04) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_LOCK_OFFSET)*/
.set SLCRUnlockReg, (PSS_SLCR_BASE_ADDR + 0x08) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_UNLOCK_OFFSET)*/
.set SLCRL2cRamReg, (PSS_SLCR_BASE_ADDR + 0xA1C) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_L2C_RAM_OFFSET)*/
.set SLCRCPURSTReg, (0xF8000000 + 0x244) /*(XPS_SYS_CTRL_BASEADDR + A9_CPU_RST_CTRL_OFFSET)*/
.set EFUSEStatus, (0xF800D000 + 0x10) /*(XPS_EFUSE_BASEADDR + EFUSE_STATUS_OFFSET)*/
.set SLCRlockReg, (PSS_SLCR_BASE_ADDR + 0x04) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_LOCK_OFFSET)*/
.set SLCRUnlockReg, (PSS_SLCR_BASE_ADDR + 0x08) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_UNLOCK_OFFSET)*/
.set SLCRL2cRamReg, (PSS_SLCR_BASE_ADDR + 0xA1C) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_L2C_RAM_OFFSET)*/
.set SLCRCPURSTReg, (0xF8000000 + 0x244) /*(XPS_SYS_CTRL_BASEADDR + A9_CPU_RST_CTRL_OFFSET)*/
.set EFUSEStatus, (0xF800D000 + 0x10) /*(XPS_EFUSE_BASEADDR + EFUSE_STATUS_OFFSET)*/
.set CRValMmuCac, 0b01000000000101 /* Enable IDC, and MMU */
.set CRValHiVectorAddr, 0b10000000000000 /* Set the Vector address to high, 0xFFFF0000 */
.set CRValMmuCac, 0b01000000000101 /* Enable IDC, and MMU */
.set CRValHiVectorAddr, 0b10000000000000 /* Set the Vector address to high, 0xFFFF0000 */
.set SLCRlockKey, 0x767B /* SLCR lock key */
.set SLCRUnlockKey, 0xDF0D /* SLCR unlock key */
.set SLCRlockKey, 0x767B /* SLCR lock key */
.set SLCRUnlockKey, 0xDF0D /* SLCR unlock key */
.set SLCRL2cRamConfig, 0x00020202 /* SLCR L2C ram configuration */
.set FPEXC_EN, 0x40000000 /* FPU enable bit, (1 << 30) */
.set FPEXC_EN, 0x40000000 /* FPU enable bit, (1 << 30) */
.section .text.startup
.align 0
@@ -39,137 +38,100 @@ core::arch::global_asm!(
_start:
// only allow cpu0 through
// Read MPIDR
mrc p15,0,r1,c0,c0,5
mrc p15,0,r1,c0,c0,5
// Extract CPU ID bits. For single-core systems, this should always be 0
and r1, r1, #0x3
cmp r1, #0
beq check_efuse
b initialize
and r1, r1, #0x3
cmp r1, #0
beq check_efuse
b initialize
// Zynq specific code. It is recommended to reset CPU1 according to page 160 of the datasheet
check_efuse:
ldr r0,=EFUSEStatus
ldr r1,[r0] /* Read eFuse setting */
ands r1,r1,#0x80 /* Check whether device is having single core */
beq initialize
ldr r0, =EFUSEStatus
// Read eFuse setting
ldr r1, [r0]
// Check whether device is having single core
ands r1,r1,#0x80
beq initialize
/* single core device, reset cpu1 */
ldr r0,=SLCRUnlockReg /* Load SLCR base address base + unlock register */
ldr r1,=SLCRUnlockKey /* set unlock key */
str r1, [r0] /* Unlock SLCR */
ldr r0,=SLCRCPURSTReg
ldr r1,[r0] /* Read CPU Software Reset Control register */
orr r1,r1,#0x22
str r1,[r0] /* Reset CPU1 */
ldr r0,=SLCRCPURSTReg
ldr r1,[r0] /* Read CPU Software Reset Control register */
orr r1,r1,#0x22
str r1,[r0] /* Reset CPU1 */
ldr r0,=SLCRlockReg /* Load SLCR base address base + lock register */
ldr r1,=SLCRlockKey /* set lock key */
str r1, [r0] /* lock SLCR */
ldr r0,=SLCRlockReg /* Load SLCR base address base + lock register */
ldr r1,=SLCRlockKey /* set lock key */
str r1, [r0] /* lock SLCR */
initialize:
mrc p15, 0, r0, c0, c0, 0 /* Get the revision */
mrc p15, 0, r0, c0, c0, 0 /* Get the revision */
and r5, r0, #0x00f00000
and r6, r0, #0x0000000f
orr r6, r6, r5, lsr #20-4
/* set VBAR to the _vector_table address in linker script */
ldr r0, =_vector_table
mcr p15, 0, r0, c12, c0, 0
ldr r0, =_vector_table
mcr p15, 0, r0, c12, c0, 0
/* Invalidate scu */
ldr r7, =0xf8f0000c
ldr r6, =0xffff
str r6, [r7]
ldr r7, =0xf8f0000c
ldr r6, =0xffff
str r6, [r7]
/* Invalidate caches and TLBs */
mov r0,#0 /* r0 = 0 */
mcr p15, 0, r0, c8, c7, 0 /* invalidate TLBs */
mcr p15, 0, r0, c7, c5, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c5, 6 /* Invalidate branch predictor array */
bl invalidate_dcache /* invalidate dcache */
mov r0,#0 /* r0 = 0 */
mcr p15, 0, r0, c8, c7, 0 /* invalidate TLBs */
mcr p15, 0, r0, c7, c5, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c5, 6 /* Invalidate branch predictor array */
bl invalidate_dcache /* invalidate dcache */
/* Disable MMU, if enabled */
mrc p15, 0, r0, c1, c0, 0 /* read CP15 register 1 */
bic r0, r0, #0x1 /* clear bit 0 */
mcr p15, 0, r0, c1, c0, 0 /* write value back */
mrc p15, 0, r0, c1, c0, 0 /* read CP15 register 1 */
bic r0, r0, #0x1 /* clear bit 0 */
mcr p15, 0, r0, c1, c0, 0 /* write value back */
// Set up stacks first.
ldr r3, =_stack_top
// Set stack pointer (right after) and mask interrupts for IRQ mode (Mode 0x12)
msr cpsr_c, {irq_mode}
// IRQ stack pointer
mov sp, r3
ldr r1, =_irq_stack_size
sub r3, r3, r1
// Set stack pointer (right after) and mask interrupts for Supervisor/SVC mode (Mode 0x13)
msr cpsr_c, {svc_mode}
// Supervisor stack pointer
mov sp, r3
ldr r1, =_svc_stack_size
sub r3, r3, r1
// Set stack pointer (right after) and mask interrupts for Abort/ABT mode (Mode 0x17)
msr cpsr_c, {abt_mode}
// Abort stack pointer
mov sp, r3
ldr r1, =_abt_stack_size
sub r3, r3, r1
// Set stack pointer (right after) and mask interrupts for FIQ mode (Mode 0x11)
msr cpsr_c, {fiq_mode}
// FIQ stack pointer
mov sp, r3
ldr r1, =_fiq_stack_size
sub r3, r3, r1
// Set stack pointer (right after) and mask interrupts for Undefined/UND mode (Mode 0x1B)
msr cpsr_c, {und_mode}
// Undefined stack pointer
mov sp, r3
ldr r1, =_und_stack_size
sub r3, r3, r1
// Set stack pointer (right after) and mask interrupts for System/SYS mode (Mode 0x1F)
msr cpsr_c, {sys_mode}
// System stack pointer (main stack)
mov sp, r3
/* We must set the core number for this function */
mov r0,#0
bl _stack_setup_preallocated
// set scu enable bit in scu
ldr r7, =0xf8f00000
ldr r0, [r7]
orr r0, r0, #0x1
str r0, [r7]
ldr r7, =0xf8f00000
ldr r0, [r7]
orr r0, r0, #0x1
str r0, [r7]
/* Write to ACTLR */
mrc p15, 0, r0, c1, c0, 1 /* Read ACTLR*/
orr r0, r0, #(0x01 << 6) /* set SMP bit */
orr r0, r0, #(0x01 ) /* Cache/TLB maintenance broadcast */
mcr p15, 0, r0, c1, c0, 1 /* Write ACTLR*/
mrc p15, 0, r0, c1, c0, 1 /* Read ACTLR*/
orr r0, r0, #(0x01 << 6) /* set SMP bit */
orr r0, r0, #(0x01 ) /* Cache/TLB maintenance broadcast */
mcr p15, 0, r0, c1, c0, 1 /* Write ACTLR*/
mov r0, r0
mrc p15, 0, r1, c1, c0, 2 /* read cp access control register (CACR) into r1 */
orr r1, r1, #(0xf << 20) /* enable full access for p10 & p11 */
mcr p15, 0, r1, c1, c0, 2 /* write back into CACR */
mov r0, r0
mrc p15, 0, r1, c1, c0, 2 /* read cp access control register (CACR) into r1 */
orr r1, r1, #(0xf << 20) /* enable full access for p10 & p11 */
mcr p15, 0, r1, c1, c0, 2 /* write back into CACR */
/* enable vfp */
fmrx r1, FPEXC /* read the exception register */
orr r1,r1, #FPEXC_EN /* set VFP enable bit, leave the others in orig state */
fmxr FPEXC, r1 /* write back the exception register */
fmrx r1, FPEXC /* read the exception register */
orr r1,r1, #FPEXC_EN /* set VFP enable bit, leave the others in orig state */
fmxr FPEXC, r1 /* write back the exception register */
mrc p15,0,r0,c1,c0,0 /* flow prediction enable */
orr r0, r0, #(0x01 << 11) /* #0x8000 */
mcr p15,0,r0,c1,c0,0
mrc p15,0,r0,c1,c0,0 /* flow prediction enable */
orr r0, r0, #(0x01 << 11) /* #0x8000 */
mcr p15,0,r0,c1,c0,0
mrc p15,0,r0,c1,c0,1 /* read Auxiliary Control Register */
orr r0, r0, #(0x1 << 2) /* enable Dside prefetch */
orr r0, r0, #(0x1 << 1) /* enable L2 Prefetch hint */
mcr p15,0,r0,c1,c0,1 /* write Auxiliary Control Register */
mrc p15,0,r0,c1,c0,1 /* read Auxiliary Control Register */
orr r0, r0, #(0x1 << 2) /* enable Dside prefetch */
orr r0, r0, #(0x1 << 1) /* enable L2 Prefetch hint */
mcr p15,0,r0,c1,c0,1 /* write Auxiliary Control Register */
mrs r0, cpsr /* get the current PSR */
bic r0, r0, #0x100 /* enable asynchronous abort exception */
msr cpsr_xsf, r0
mrs r0, cpsr /* get the current PSR */
bic r0, r0, #0x100 /* enable asynchronous abort exception */
msr cpsr_xsf, r0
/* Zero BSS and initialize data before calling any function which might require them. */
@@ -199,20 +161,20 @@ data_init_done:
/* enable MMU and cache */
/* MMU Table is in .data, so this needs to be performed after .data is relocated */
/* (Even if in most cases, .data is already in RAM and relocation is a no-op) */
bl load_mmu_table
bl load_mmu_table
mvn r0,#0 /* Load MMU domains -- all ones=manager */
mcr p15,0,r0,c3,c0,0
mvn r0,#0 /* Load MMU domains -- all ones=manager */
mcr p15,0,r0,c3,c0,0
/* Enable mmu, icache and dcache */
ldr r0,=CRValMmuCac
mcr p15,0,r0,c1,c0,0 /* Enable cache and MMU */
dsb /* dsb allow the MMU to start up */
isb /* isb flush prefetch buffer */
ldr r0,=CRValMmuCac
mcr p15,0,r0,c1,c0,0 /* Enable cache and MMU */
dsb /* dsb allow the MMU to start up */
isb /* isb flush prefetch buffer */
// Jump to application
// Load CPU ID 0, which will be used as a function argument to the boot_core function.
mov r0, #0x0
mov r0, #0x0
bl kmain
// In case the application returns, loop forever
b .
@@ -220,90 +182,48 @@ data_init_done:
.type _invalidate_dcache, %function
invalidate_dcache:
mrc p15, 1, r0, c0, c0, 1 /* read CLIDR */
ands r3, r0, #0x7000000
mov r3, r3, lsr #23 /* cache level value (naturally aligned) */
beq finished
mov r10, #0 /* start with level 0 */
mrc p15, 1, r0, c0, c0, 1 /* read CLIDR */
ands r3, r0, #0x7000000
mov r3, r3, lsr #23 /* cache level value (naturally aligned) */
beq finished
mov r10, #0 /* start with level 0 */
loop1:
add r2, r10, r10, lsr #1 /* work out 3xcachelevel */
mov r1, r0, lsr r2 /* bottom 3 bits are the Cache type for this level */
and r1, r1, #7 /* get those 3 bits alone */
cmp r1, #2
blt skip /* no cache or only instruction cache at this level */
mcr p15, 2, r10, c0, c0, 0 /* write the Cache Size selection register */
isb /* isb to sync the change to the CacheSizeID reg */
mrc p15, 1, r1, c0, c0, 0 /* reads current Cache Size ID register */
and r2, r1, #7 /* extract the line length field */
add r2, r2, #4 /* add 4 for the line length offset (log2 16 bytes) */
ldr r4, =0x3ff
ands r4, r4, r1, lsr #3 /* r4 is the max number on the way size (right aligned) */
clz r5, r4 /* r5 is the bit position of the way size increment */
ldr r7, =0x7fff
ands r7, r7, r1, lsr #13 /* r7 is the max number of the index size (right aligned) */
add r2, r10, r10, lsr #1 /* work out 3xcachelevel */
mov r1, r0, lsr r2 /* bottom 3 bits are the Cache type for this level */
and r1, r1, #7 /* get those 3 bits alone */
cmp r1, #2
blt skip /* no cache or only instruction cache at this level */
mcr p15, 2, r10, c0, c0, 0 /* write the Cache Size selection register */
isb /* isb to sync the change to the CacheSizeID reg */
mrc p15, 1, r1, c0, c0, 0 /* reads current Cache Size ID register */
and r2, r1, #7 /* extract the line length field */
add r2, r2, #4 /* add 4 for the line length offset (log2 16 bytes) */
ldr r4, =0x3ff
ands r4, r4, r1, lsr #3 /* r4 is the max number on the way size (right aligned) */
clz r5, r4 /* r5 is the bit position of the way size increment */
ldr r7, =0x7fff
ands r7, r7, r1, lsr #13 /* r7 is the max number of the index size (right aligned) */
loop2:
mov r9, r4 /* r9 working copy of the max way size (right aligned) */
mov r9, r4 /* r9 working copy of the max way size (right aligned) */
loop3:
orr r11, r10, r9, lsl r5 /* factor in the way number and cache number into r11 */
orr r11, r11, r7, lsl r2 /* factor in the index number */
mcr p15, 0, r11, c7, c6, 2 /* invalidate by set/way */
subs r9, r9, #1 /* decrement the way number */
bge loop3
subs r7, r7, #1 /* decrement the index */
bge loop2
orr r11, r10, r9, lsl r5 /* factor in the way number and cache number into r11 */
orr r11, r11, r7, lsl r2 /* factor in the index number */
mcr p15, 0, r11, c7, c6, 2 /* invalidate by set/way */
subs r9, r9, #1 /* decrement the way number */
bge loop3
subs r7, r7, #1 /* decrement the index */
bge loop2
skip:
add r10, r10, #2 /* increment the cache number */
cmp r3, r10
bgt loop1
add r10, r10, #2 /* increment the cache number */
cmp r3, r10
bgt loop1
finished:
mov r10, #0 /* switch back to cache level 0 */
mcr p15, 2, r10, c0, c0, 0 /* select current cache level in cssr */
mov r10, #0 /* switch back to cache level 0 */
mcr p15, 2, r10, c0, c0, 0 /* select current cache level in cssr */
dsb
isb
bx lr
bx lr
.size invalidate_dcache, . - invalidate_dcache
"#,
fiq_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Fiq)
.with_i(true)
.with_f(true)
.raw_value()
},
irq_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Irq)
.with_i(true)
.with_f(true)
.raw_value()
},
svc_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Svc)
.with_i(true)
.with_f(true)
.raw_value()
},
und_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Und)
.with_i(true)
.with_f(true)
.raw_value()
},
abt_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Abt)
.with_i(true)
.with_f(true)
.raw_value()
},
sys_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Sys)
.with_i(true)
.with_f(true)
.raw_value()
},
);

Some files were not shown because too many files have changed in this diff Show More